Construire
Autorisations
Liez chaque accord à une action, une ressource et une durée précises — et laissez votre agent refuser tout le reste.
Un widget d'autorisation enregistre que l'utilisateur a dit oui. La politique d'autorisation va plus loin : elle rattache ce oui à une opération précise et ne laisse votre code l'exécuter que tant que l'accord est valable.
Utilisez-la dès que votre agent peut toucher à quelque chose d'important — fichiers, commandes, comptes, argent.
Principe
- Chaque demande reçoit un identifiant de widget unique et décrit une action, une
ressource et des paramètres exacts — par exemple
read_filesur/home/moi/rapport.txtenutf8. - L'accord est lié à l'utilisateur, à la conversation et à une session nommée qui expire. Les réponses doivent arriver par le canal chiffré du SDK.
- Votre code exécute l'opération via
runAuthorized(run_authorized). La politique refuse tout ce qui ne correspond pas exactement à l'accord.
| Portée | Signification |
|---|---|
once | Une seule tentative. Consommée avant le lancement de votre fonction — même si elle échoue. |
session | Tentatives répétées de la même opération, ressource et paramètres, jusqu'à l'expiration, la révocation ou la fin de session. |
Les accords vivent en mémoire. Un redémarrage exige un nouvel accord — c'est voulu.
Exemple : lire un fichier, avec accord
import { readFile, realpath } from "node:fs/promises";
import { PermissionPolicy } from "@pacerelle/sdk/permissions";
const session = {
id: crypto.randomUUID(),
label: "Lecture des rapports locaux",
expiresAt: new Date(Date.now() + 30 * 60_000), // 30 minutes
};
const permissions = new PermissionPolicy({ session });
const pending = new Map(); // identifiant du widget → opération demandée
client.onMessage(async (message, agent) => {
const send = (text) =>
agent.sendMessage({
conversationId: message.conversationId,
to: message.from,
toDeviceId: message.fromDeviceId,
text,
});
// Aligne la carte affichée dans l'application sur ce que la politique a réellement fait.
const report = (ref) => send(JSON.stringify(permissions.createWidgetUpdate(ref)));
// 1. Est-ce une réponse à l'une de nos demandes ?
const decision = permissions.receiveResponse(message);
if (decision.status === "denied" || decision.status === "revoked") {
await report(decision.requestId);
return;
}
if (decision.status === "granted") {
const task = pending.get(decision.grant.requestId);
if (!task) return; // ne jamais déduire l'opération de la réponse elle-même
await report(decision.grant.requestId);
try {
const text = await permissions.runAuthorized(
decision.grant.id,
{ ...task, userId: message.from, conversationId: message.conversationId, sessionId: session.id },
(allowed) => readFile(allowed.resource, "utf8"),
);
await send(`C'est fait — le rapport compte ${text.length} caractères.`);
} finally {
await report(decision.grant.requestId);
}
return;
}
if (message.widgetResponse) return; // réponse sans rapport, rejouée ou invalide
// 2. Une demande qui nécessite un accord.
if (message.text !== "Lis le rapport") return;
const task = {
action: "read_file",
resource: await realpath("./rapport.txt"), // résoudre avant de demander
parameters: { encoding: "utf8" },
};
const widget = permissions.createRequest({
...task,
conversationId: message.conversationId,
userId: message.from,
title: "Lire ce rapport ?",
body: "L'agent lira uniquement ce fichier, en texte.",
expiresAt: new Date(Date.now() + 5 * 60_000),
scopes: ["once", "session"],
});
pending.set(widget.id, task);
await send(JSON.stringify(widget));
});Envoyez le widget renvoyé par createRequest comme texte d'un message, comme ci-dessus. L'assistant
plus simple sendPermissionWidget n'ajoute pas les détails d'autorisation de la politique.
Révoquer et terminer une session
- L'utilisateur peut révoquer un accord depuis l'application après l'avoir donné.
receiveResponsevérifie l'auteur et la conversation, puis bloque toute utilisation future. Une opération déjà lancée ne peut pas être interrompue. - Dans le code,
revoke(grantId)retire un accord,endSession()(end_session()) les retire tous etlistGrants()(list_grants()) liste ceux qui sont actifs.
Règles d'or
- Résolvez avant de demander. Transformez alias et chemins relatifs en ressource réelle, et
incluez dans
parameterschaque argument qui change l'effet. - Ne tirez jamais l'opération de la réponse. Retrouvez-la dans votre propre table
pending. - Gardez des sessions courtes. Des minutes, pas des jours. Il n'existe volontairement pas de portée « pour toujours ».
- Rendez compte honnêtement. Un accord ne prouve pas que l'opération a réussi — dites à l'utilisateur ce qui s'est réellement passé.