@breign/db-schemas 0.1.56 → 0.1.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,6 +41,7 @@ export type Agent = {
41
41
  memoryConfiguration?: AgentMemoryConfiguration;
42
42
  sandboxConfiguration?: AgentSandboxConfiguration;
43
43
  computerUseConfiguration?: AgentComputerUseConfiguration;
44
+ agentCallConfiguration?: AgentCallConfiguration;
44
45
  speechToText?: SpeechToTextConfig;
45
46
  };
46
47
  export type AgentMemoryConfiguration = {
@@ -51,6 +52,15 @@ export type AgentMemoryConfiguration = {
51
52
  export type AgentComputerUseConfiguration = {
52
53
  enabled?: boolean;
53
54
  };
55
+ /**
56
+ * Per-agent activation of agent-to-agent calls: the agent may question the agents listed in
57
+ * allowedAgentIds, and no other. Opt-in, and useless without at least one callable agent —
58
+ * an empty list keeps the tool out of the agent's toolbox.
59
+ */
60
+ export type AgentCallConfiguration = {
61
+ enabled?: boolean;
62
+ allowedAgentIds?: string[];
63
+ };
54
64
  export type AgentSandboxNetworkConfiguration = {
55
65
  enabled?: boolean;
56
66
  allowedUrlPrefixes?: string[];
@@ -1,19 +1,81 @@
1
1
  import { ChatMessageRoleUio } from '@breign/client';
2
+ import type { ToolCallApprovalScope } from './pending-tool-call';
3
+ import type { ToolAction, ToolResult } from './tool-result';
2
4
  export type ConversationMessageReaction = 'positive' | 'negative';
3
- export type ConversationAttachment = {
5
+ export type ConversationAttachmentStatus = 'TO_UPLOAD' | 'PROCESSING' | 'PROCESSED' | 'ERROR';
6
+ export type ConversationAttachmentErrorCode = 'ATTACHMENT_TOO_LARGE';
7
+ /**
8
+ * Pièce jointe documentaire. `processed` dit où son contenu a fini : recopié tel quel dans le
9
+ * stockage, ou indexé dans un vector store — les deux formes n'ont rien en commun, d'où l'union.
10
+ */
11
+ export type ConversationFileAttachment = {
12
+ type: 'file';
4
13
  id: string;
5
- type: string;
6
14
  name: string;
7
15
  mimeType: string;
8
16
  ext: string;
9
17
  size: number;
10
18
  key: string;
11
- status: 'TO_UPLOAD' | 'PROCESSING' | 'PROCESSED' | 'ERROR';
19
+ status: ConversationAttachmentStatus;
20
+ errorCode?: ConversationAttachmentErrorCode;
21
+ estimatedTokens?: number;
22
+ maxTokens?: number;
12
23
  /** Run the OCR pre-pass before parsing this attachment to Markdown */
13
24
  ocr?: boolean;
25
+ processed?: {
26
+ type: 'file';
27
+ key: string;
28
+ content?: string;
29
+ } | {
30
+ type: 'indexed';
31
+ index: string;
32
+ backend: 'weaviate' | 'qdrant';
33
+ fileId: string;
34
+ sourceKey: string;
35
+ indexedAt: string;
36
+ invalidatedAt?: string;
37
+ };
38
+ };
39
+ export type ConversationImageAttachment = {
40
+ type: 'image';
41
+ id: string;
42
+ name: string;
43
+ mimeType: string;
44
+ ext: string;
45
+ size: number;
46
+ key: string;
14
47
  url?: string;
48
+ status: ConversationAttachmentStatus;
15
49
  inlineDataUrl?: string;
16
- processed?: Record<string, unknown>;
50
+ };
51
+ /** Discriminée par `type` : une image ne passe pas par le parsing documentaire. */
52
+ export type ConversationAttachment = ConversationFileAttachment | ConversationImageAttachment;
53
+ /**
54
+ * Une décision d'autorisation prise pendant ce message : quel outil, sur quoi, par qui, quand.
55
+ *
56
+ * Portée par le MESSAGE et pas seulement par l'appel d'outil qui a posé la question, parce que
57
+ * c'est l'historique qui doit la montrer : la conversation se relit telle quelle, sans aller
58
+ * chercher les appels différés un par un. Un accord ponctuel n'existant nulle part ailleurs — il
59
+ * n'écrit aucune ligne d'autorisation permanente — c'est ici sa seule trace durable.
60
+ *
61
+ * Délibérément à côté de `toolResults` et non dedans : `toolResults` est **rejoué au modèle** à
62
+ * chaque tour (`OUTILS: …`). Un accord `once` y figurant ferait croire au modèle, deux tours plus
63
+ * tard, qu'il a encore un droit qui a expiré — et c'est exactement le malentendu que la portée
64
+ * `once` existe pour éviter.
65
+ */
66
+ export type ConversationMessageApproval = {
67
+ /** L'outil dont un usage a été soumis à autorisation. Dit aussi de quelle famille il s'agit. */
68
+ tool: string;
69
+ /** Les refus sont conservés : une décision de ne pas autoriser se relit autant qu'un accord. */
70
+ granted: boolean;
71
+ /** Ce qui a été accordé — ou, sur un refus, ce qui avait été demandé. Jamais l'URL complète. */
72
+ resource: string;
73
+ /** Absent sur un refus, qui n'accorde rien et n'a donc pas de portée. */
74
+ scope?: ToolCallApprovalScope;
75
+ /** Nom ou e-mail de la personne qui a décidé, figé à cet instant. */
76
+ approvedBy?: string;
77
+ /** Quand la décision a été prise, ISO. */
78
+ at: string;
17
79
  };
18
80
  export type ConversationMessage = {
19
81
  id: string;
@@ -29,8 +91,10 @@ export type ConversationMessage = {
29
91
  toolName?: string;
30
92
  params?: string;
31
93
  };
32
- result?: Record<string, unknown>;
94
+ result?: ToolResult;
33
95
  }[];
96
+ /** Décisions d'autorisation prises pendant ce message. Voir ConversationMessageApproval. */
97
+ approvals?: ConversationMessageApproval[];
34
98
  suggestions?: {
35
99
  id: string;
36
100
  text: string;
@@ -42,9 +106,14 @@ export type ConversationMessage = {
42
106
  flowNodeRequirementsCheckReason?: string;
43
107
  flowNodeIterations?: number;
44
108
  timestamp: string;
45
- actions?: Record<string, unknown>[];
109
+ actions?: ToolAction[];
46
110
  attachments?: ConversationAttachment[];
47
- status?: 'generating' | 'completed' | 'error' | 'tool_calling' | 'moderated';
111
+ /**
112
+ * `waiting_client` : le tour s'est arrêté sur un outil que le client doit exécuter ou autoriser.
113
+ * Ni terminé ni en erreur — le message repartira en `generating` quand le résultat sera remonté.
114
+ * Les appels en attente eux-mêmes se lisent par message (voir `PendingToolCall`).
115
+ */
116
+ status?: 'generating' | 'completed' | 'error' | 'tool_calling' | 'moderated' | 'waiting_client';
48
117
  toolCall?: {
49
118
  name: string;
50
119
  stepIndex: number;
@@ -10,6 +10,7 @@ export * from './flow';
10
10
  export * from './flow-editor';
11
11
  export * from './knowledge';
12
12
  export * from './organization';
13
+ export * from './pending-tool-call';
13
14
  export * from './permissions';
14
15
  export * from './role';
15
16
  export * from './scheduled-task';
@@ -17,5 +18,7 @@ export * from './scheduled-task-run';
17
18
  export * from './skill';
18
19
  export * from './speech-to-text';
19
20
  export * from './tool';
21
+ export * from './tool-approval';
22
+ export * from './tool-result';
20
23
  export * from './user';
21
24
  export * from './workspace-file';
@@ -26,6 +26,7 @@ __exportStar(require("./flow"), exports);
26
26
  __exportStar(require("./flow-editor"), exports);
27
27
  __exportStar(require("./knowledge"), exports);
28
28
  __exportStar(require("./organization"), exports);
29
+ __exportStar(require("./pending-tool-call"), exports);
29
30
  __exportStar(require("./permissions"), exports);
30
31
  __exportStar(require("./role"), exports);
31
32
  __exportStar(require("./scheduled-task"), exports);
@@ -33,5 +34,7 @@ __exportStar(require("./scheduled-task-run"), exports);
33
34
  __exportStar(require("./skill"), exports);
34
35
  __exportStar(require("./speech-to-text"), exports);
35
36
  __exportStar(require("./tool"), exports);
37
+ __exportStar(require("./tool-approval"), exports);
38
+ __exportStar(require("./tool-result"), exports);
36
39
  __exportStar(require("./user"), exports);
37
40
  __exportStar(require("./workspace-file"), exports);
@@ -0,0 +1,82 @@
1
+ import type { ToolResult } from './tool-result';
2
+ export type PendingToolCallStatus = 'pending' | 'completed' | 'failed' | 'refused' | 'expired';
3
+ /** Les statuts qu'un client peut poser : `pending` et `expired` sont au serveur seul. */
4
+ export type PendingToolCallResolution = Exclude<PendingToolCallStatus, 'pending' | 'expired'>;
5
+ /**
6
+ * Jusqu'où porte un « oui » donné sur une demande d'autorisation.
7
+ *
8
+ * `once` = la réponse suspendue, et rien après : **aucune ligne n'est écrite dans `ToolApproval`**,
9
+ * et la même demande est reposée au message suivant. `agentUser` = cet agent, pour cet utilisateur,
10
+ * dans toutes ses conversations, jusqu'à révocation — là une ligne existe.
11
+ *
12
+ * Volontairement distinct de `ToolApprovalScopeKind` : celui-ci nomme des portées de STOCKAGE, et
13
+ * `once` n'en est pas une. Les confondre reviendrait à admettre qu'un accord ponctuel puisse être
14
+ * stocké, ce qui est exactement ce qu'il ne doit pas être.
15
+ */
16
+ export type ToolCallApprovalScope = 'once' | 'agentUser';
17
+ /**
18
+ * Ce qu'un « oui » a accordé, porté par l'appel d'outil qui a posé la question.
19
+ *
20
+ * Deux rôles, et c'est voulu. C'est la TRACE d'une décision — qui, quoi, jusqu'où — que l'écran de
21
+ * conversation réaffiche, et qui reste lisible que le droit permanent ait été créé ou non : avec
22
+ * `once` il n'y en a jamais eu, avec `agentUser` il a pu être révoqué depuis. Et pour la portée
23
+ * `once` c'est **le droit lui-même**, qui n'existe nulle part ailleurs : le tour de reprise le relit
24
+ * ici. D'où la borne — un appel appartient à un message assistant, donc un accord ponctuel ne peut
25
+ * pas survivre à ce message.
26
+ *
27
+ * C'est ici et pas dans `ToolApproval` parce que la question posée est propre à UNE conversation,
28
+ * alors que `ToolApproval` décrit un droit permanent attaché à un agent et à un utilisateur.
29
+ */
30
+ export type ToolCallApproval = {
31
+ scope: ToolCallApprovalScope;
32
+ /**
33
+ * Ce qui est accordé, préfixé par sa famille exactement comme `ToolApproval.resourceKey` — `url:`
34
+ * une adresse, `cmd:` une commande exacte, `glob:` un motif. Un seul analyseur pour les deux, et
35
+ * une adresse approuvée ne peut jamais satisfaire une commande.
36
+ */
37
+ resourceKey: string;
38
+ /**
39
+ * Nom ou e-mail de la personne qui a cliqué, figé à cet instant plutôt que joint plus tard : la
40
+ * trace doit rester lisible après son départ de l'organisation. Absent quand l'appelant n'a pas
41
+ * d'identité.
42
+ */
43
+ approvedBy?: string;
44
+ };
45
+ /**
46
+ * Un appel d'outil que le serveur ne résout pas lui-même. Le tour est clos dès sa création et la
47
+ * réponse du client relance un tour de reprise : un appel non résolu ne retient rien. Générique —
48
+ * `tool` le nomme, `arguments` porte ce que le modèle a passé.
49
+ *
50
+ * Le lien avec la conversation va d'ici vers le message (`chatId` + `messageId`), jamais l'inverse :
51
+ * le message ne porte que le statut `waiting_client`, et les appels se lisent par message.
52
+ */
53
+ export type PendingToolCall = {
54
+ id: string;
55
+ tool: string;
56
+ userId: string;
57
+ chatId?: string;
58
+ /** Message assistant suspendu : celui que le tour de reprise complétera. */
59
+ messageId?: string;
60
+ /** Device visé, à titre informatif : l'autorisation passe par la conversation. */
61
+ deviceId?: string;
62
+ status: PendingToolCallStatus;
63
+ arguments?: Record<string, unknown>;
64
+ /**
65
+ * Ce que la reprise écrit dans la conversation à la place des `arguments` bruts. Le tour de reprise
66
+ * injecte `Outil : <tool>(<arguments>)` dans l'historique **persisté**, rejoué à chaque tour : un
67
+ * argument portant un secret y resterait indéfiniment. Absent ⇒ repli sur les `arguments`.
68
+ */
69
+ displaySummary?: string;
70
+ result?: ToolResult;
71
+ /**
72
+ * Écrit à la résolution, sur les seuls appels d'approbation et seulement quand la réponse est un
73
+ * accord. Posé dans la même écriture que le statut : pour une portée `once` il EST le droit, donc
74
+ * un appel passé à `completed` sans lui relancerait le tour sans rien accorder.
75
+ */
76
+ approval?: ToolCallApproval;
77
+ error?: string;
78
+ createdAt: Date;
79
+ updatedAt: Date;
80
+ /** Échéance optionnelle, posée outil par outil : un outil sans TTL n'expire jamais. */
81
+ expiresAt?: Date;
82
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,63 @@
1
+ /**
2
+ * User approval of one operation the agent's sandbox asked permission for.
3
+ *
4
+ * Two families exist, both belonging to `Bash_exec`: an address its curl may reach, and a python
5
+ * command it may run. When such a call is not already granted, the conversation turn is suspended
6
+ * and the user is asked. A "yes" is stored here ONLY when the user chose to stop being asked: a
7
+ * one-off "allow this once" grants the suspended turn and writes nothing, so every row in this
8
+ * table is a standing right, not a trace of a past decision. That trace lives on the tool call —
9
+ * see `ToolCallApproval`, which is where a one-off is recorded and where it stays confined.
10
+ *
11
+ * Three invariants make this table safe to read on the hot path:
12
+ *
13
+ * - **It can only intersect, never widen.** An approval unlocks an operation of a tool the agent
14
+ * already has, behind the server lock and the per-agent switch. An agent with no sandbox gains
15
+ * nothing because the user approved something elsewhere.
16
+ * - **`resourceKey` is normalised, and carries its family as a prefix.** A URL prefix, a glob or an
17
+ * exact command line — never raw arguments, so a secret passed next to the approved value is not
18
+ * persisted here. The prefix is what stops an approved address from authorising a command: the
19
+ * full arguments live only in the tool call that produced the approval.
20
+ * - **A row is a personal consent, and `userId` says whose.** Nobody can approve on someone else's
21
+ * behalf: the resolution route refuses a caller who is not the person the question was asked of,
22
+ * so the person the right applies to and the person who granted it are the same by construction.
23
+ * One column carries both, and it is what the management routes key on.
24
+ *
25
+ * Revoking one deletes its row: there is no state a revoked approval could be in, so nothing on the
26
+ * read path has to filter it out.
27
+ *
28
+ * Shared with agent-tasks through this package even though agent-tasks asks nobody: the type
29
+ * belongs with the other storage models.
30
+ */
31
+ export type ToolApprovalScopeKind = 'agentUser' | 'user';
32
+ export type ToolApproval = {
33
+ id: string;
34
+ /**
35
+ * British spelling "organisationId" — aligned with Agent.organisationId (domain + Mongo
36
+ * storage). In PostgreSQL the COLUMN is organizationId ("z"), mapped inside the PG adapter.
37
+ * It leads every key: an approval never crosses an organisation boundary.
38
+ */
39
+ organisationId: string;
40
+ /**
41
+ * `agentUser` is the only one written. `user` is kept readable but nothing writes it — see
42
+ * `agentApprovalScope` in brain-api, the single place that builds a stored scope.
43
+ */
44
+ scopeKind: ToolApprovalScopeKind;
45
+ /**
46
+ * Scope folded into a single string, so unicity is a plain unique index. PostgreSQL treats two
47
+ * NULLs as distinct, so a unique index over nullable discriminator columns would not constrain
48
+ * anything — same reason `workspaceSessionKey` exists.
49
+ */
50
+ scopeKey: string;
51
+ /** Set for the 'agentUser' scope. Absent on 'user', which holds for every agent. */
52
+ agentId?: string;
53
+ /** Whose consent it is. Never empty: an approval always belongs to one person. */
54
+ userId: string;
55
+ /** The tool as the model calls it. Part of the key: two tools never share an approval. */
56
+ toolName: string;
57
+ /**
58
+ * What a "yes" grants, prefixed by its family: `url:` an origin plus an optional first path
59
+ * segment, `glob:` a pattern covering a site, `cmd:` one exact command line.
60
+ */
61
+ resourceKey: string;
62
+ createdAt: string;
63
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,19 @@
1
+ import type { ConversationAttachment } from './conversation';
2
+ /**
3
+ * Ce qu'un outil renvoie au tour de conversation. Copie structurelle de `ToolResult` /
4
+ * `ToolAction` de brain-api (`src/shared/init-tools.ts`), qui reste la définition de référence :
5
+ * ce paquet ne dépend pas de brain-api, mais les modèles de stockage qui portent un résultat
6
+ * d'outil — un message de conversation, un appel d'outil en attente — ont besoin de sa forme.
7
+ */
8
+ export type ToolAction = {
9
+ code: string;
10
+ } & Record<string, unknown>;
11
+ export type ToolResult = {
12
+ text: string;
13
+ attachments?: ConversationAttachment[];
14
+ askConfirmation?: boolean;
15
+ confirmationLink?: string;
16
+ actions?: ToolAction[];
17
+ /** Clôt le tour sans réponse finale : le client doit résoudre l'appel avant qu'il reprenne. */
18
+ suspendTurn?: boolean;
19
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@breign/db-schemas",
3
- "version": "0.1.56",
3
+ "version": "0.1.58",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
@@ -36,6 +36,7 @@ export type Agent = {
36
36
  memoryConfiguration?: AgentMemoryConfiguration;
37
37
  sandboxConfiguration?: AgentSandboxConfiguration;
38
38
  computerUseConfiguration?: AgentComputerUseConfiguration;
39
+ agentCallConfiguration?: AgentCallConfiguration;
39
40
  speechToText?: SpeechToTextConfig;
40
41
  };
41
42
 
@@ -50,6 +51,16 @@ export type AgentComputerUseConfiguration = {
50
51
  enabled?: boolean;
51
52
  };
52
53
 
54
+ /**
55
+ * Per-agent activation of agent-to-agent calls: the agent may question the agents listed in
56
+ * allowedAgentIds, and no other. Opt-in, and useless without at least one callable agent —
57
+ * an empty list keeps the tool out of the agent's toolbox.
58
+ */
59
+ export type AgentCallConfiguration = {
60
+ enabled?: boolean;
61
+ allowedAgentIds?: string[];
62
+ };
63
+
53
64
  export type AgentSandboxNetworkConfiguration = {
54
65
  enabled?: boolean;
55
66
  allowedUrlPrefixes?: string[];
@@ -1,21 +1,90 @@
1
1
  import { ChatMessageRoleUio } from '@breign/client';
2
+ import type { ToolCallApprovalScope } from './pending-tool-call';
3
+ import type { ToolAction, ToolResult } from './tool-result';
2
4
 
3
5
  export type ConversationMessageReaction = 'positive' | 'negative';
4
6
 
5
- export type ConversationAttachment = {
7
+ export type ConversationAttachmentStatus = 'TO_UPLOAD' | 'PROCESSING' | 'PROCESSED' | 'ERROR';
8
+
9
+ export type ConversationAttachmentErrorCode = 'ATTACHMENT_TOO_LARGE';
10
+
11
+ /**
12
+ * Pièce jointe documentaire. `processed` dit où son contenu a fini : recopié tel quel dans le
13
+ * stockage, ou indexé dans un vector store — les deux formes n'ont rien en commun, d'où l'union.
14
+ */
15
+ export type ConversationFileAttachment = {
16
+ type: 'file';
6
17
  id: string;
7
- type: string;
8
18
  name: string;
9
19
  mimeType: string;
10
20
  ext: string;
11
21
  size: number;
12
22
  key: string;
13
- status: 'TO_UPLOAD' | 'PROCESSING' | 'PROCESSED' | 'ERROR';
23
+ status: ConversationAttachmentStatus;
24
+ errorCode?: ConversationAttachmentErrorCode;
25
+ estimatedTokens?: number;
26
+ maxTokens?: number;
14
27
  /** Run the OCR pre-pass before parsing this attachment to Markdown */
15
28
  ocr?: boolean;
29
+ processed?:
30
+ | {
31
+ type: 'file';
32
+ key: string;
33
+ content?: string;
34
+ }
35
+ | {
36
+ type: 'indexed';
37
+ index: string;
38
+ backend: 'weaviate' | 'qdrant';
39
+ fileId: string;
40
+ sourceKey: string;
41
+ indexedAt: string;
42
+ invalidatedAt?: string;
43
+ };
44
+ };
45
+
46
+ export type ConversationImageAttachment = {
47
+ type: 'image';
48
+ id: string;
49
+ name: string;
50
+ mimeType: string;
51
+ ext: string;
52
+ size: number;
53
+ key: string;
16
54
  url?: string;
55
+ status: ConversationAttachmentStatus;
17
56
  inlineDataUrl?: string;
18
- processed?: Record<string, unknown>;
57
+ };
58
+
59
+ /** Discriminée par `type` : une image ne passe pas par le parsing documentaire. */
60
+ export type ConversationAttachment = ConversationFileAttachment | ConversationImageAttachment;
61
+
62
+ /**
63
+ * Une décision d'autorisation prise pendant ce message : quel outil, sur quoi, par qui, quand.
64
+ *
65
+ * Portée par le MESSAGE et pas seulement par l'appel d'outil qui a posé la question, parce que
66
+ * c'est l'historique qui doit la montrer : la conversation se relit telle quelle, sans aller
67
+ * chercher les appels différés un par un. Un accord ponctuel n'existant nulle part ailleurs — il
68
+ * n'écrit aucune ligne d'autorisation permanente — c'est ici sa seule trace durable.
69
+ *
70
+ * Délibérément à côté de `toolResults` et non dedans : `toolResults` est **rejoué au modèle** à
71
+ * chaque tour (`OUTILS: …`). Un accord `once` y figurant ferait croire au modèle, deux tours plus
72
+ * tard, qu'il a encore un droit qui a expiré — et c'est exactement le malentendu que la portée
73
+ * `once` existe pour éviter.
74
+ */
75
+ export type ConversationMessageApproval = {
76
+ /** L'outil dont un usage a été soumis à autorisation. Dit aussi de quelle famille il s'agit. */
77
+ tool: string;
78
+ /** Les refus sont conservés : une décision de ne pas autoriser se relit autant qu'un accord. */
79
+ granted: boolean;
80
+ /** Ce qui a été accordé — ou, sur un refus, ce qui avait été demandé. Jamais l'URL complète. */
81
+ resource: string;
82
+ /** Absent sur un refus, qui n'accorde rien et n'a donc pas de portée. */
83
+ scope?: ToolCallApprovalScope;
84
+ /** Nom ou e-mail de la personne qui a décidé, figé à cet instant. */
85
+ approvedBy?: string;
86
+ /** Quand la décision a été prise, ISO. */
87
+ at: string;
19
88
  };
20
89
 
21
90
  export type ConversationMessage = {
@@ -28,8 +97,10 @@ export type ConversationMessage = {
28
97
  confirmationLink?: string;
29
98
  toolResults?: {
30
99
  tool: { tool: string; toolName?: string; params?: string };
31
- result?: Record<string, unknown>;
100
+ result?: ToolResult;
32
101
  }[];
102
+ /** Décisions d'autorisation prises pendant ce message. Voir ConversationMessageApproval. */
103
+ approvals?: ConversationMessageApproval[];
33
104
  suggestions?: {
34
105
  id: string;
35
106
  text: string;
@@ -41,9 +112,14 @@ export type ConversationMessage = {
41
112
  flowNodeRequirementsCheckReason?: string;
42
113
  flowNodeIterations?: number;
43
114
  timestamp: string;
44
- actions?: Record<string, unknown>[];
115
+ actions?: ToolAction[];
45
116
  attachments?: ConversationAttachment[];
46
- status?: 'generating' | 'completed' | 'error' | 'tool_calling' | 'moderated';
117
+ /**
118
+ * `waiting_client` : le tour s'est arrêté sur un outil que le client doit exécuter ou autoriser.
119
+ * Ni terminé ni en erreur — le message repartira en `generating` quand le résultat sera remonté.
120
+ * Les appels en attente eux-mêmes se lisent par message (voir `PendingToolCall`).
121
+ */
122
+ status?: 'generating' | 'completed' | 'error' | 'tool_calling' | 'moderated' | 'waiting_client';
47
123
  toolCall?: { name: string; stepIndex: number };
48
124
  error?: {
49
125
  code: string;
@@ -10,6 +10,7 @@ export * from './flow';
10
10
  export * from './flow-editor';
11
11
  export * from './knowledge';
12
12
  export * from './organization';
13
+ export * from './pending-tool-call';
13
14
  export * from './permissions';
14
15
  export * from './role';
15
16
  export * from './scheduled-task';
@@ -17,5 +18,7 @@ export * from './scheduled-task-run';
17
18
  export * from './skill';
18
19
  export * from './speech-to-text';
19
20
  export * from './tool';
21
+ export * from './tool-approval';
22
+ export * from './tool-result';
20
23
  export * from './user';
21
24
  export * from './workspace-file';
@@ -0,0 +1,87 @@
1
+ import type { ToolResult } from './tool-result';
2
+
3
+ export type PendingToolCallStatus = 'pending' | 'completed' | 'failed' | 'refused' | 'expired';
4
+
5
+ /** Les statuts qu'un client peut poser : `pending` et `expired` sont au serveur seul. */
6
+ export type PendingToolCallResolution = Exclude<PendingToolCallStatus, 'pending' | 'expired'>;
7
+
8
+ /**
9
+ * Jusqu'où porte un « oui » donné sur une demande d'autorisation.
10
+ *
11
+ * `once` = la réponse suspendue, et rien après : **aucune ligne n'est écrite dans `ToolApproval`**,
12
+ * et la même demande est reposée au message suivant. `agentUser` = cet agent, pour cet utilisateur,
13
+ * dans toutes ses conversations, jusqu'à révocation — là une ligne existe.
14
+ *
15
+ * Volontairement distinct de `ToolApprovalScopeKind` : celui-ci nomme des portées de STOCKAGE, et
16
+ * `once` n'en est pas une. Les confondre reviendrait à admettre qu'un accord ponctuel puisse être
17
+ * stocké, ce qui est exactement ce qu'il ne doit pas être.
18
+ */
19
+ export type ToolCallApprovalScope = 'once' | 'agentUser';
20
+
21
+ /**
22
+ * Ce qu'un « oui » a accordé, porté par l'appel d'outil qui a posé la question.
23
+ *
24
+ * Deux rôles, et c'est voulu. C'est la TRACE d'une décision — qui, quoi, jusqu'où — que l'écran de
25
+ * conversation réaffiche, et qui reste lisible que le droit permanent ait été créé ou non : avec
26
+ * `once` il n'y en a jamais eu, avec `agentUser` il a pu être révoqué depuis. Et pour la portée
27
+ * `once` c'est **le droit lui-même**, qui n'existe nulle part ailleurs : le tour de reprise le relit
28
+ * ici. D'où la borne — un appel appartient à un message assistant, donc un accord ponctuel ne peut
29
+ * pas survivre à ce message.
30
+ *
31
+ * C'est ici et pas dans `ToolApproval` parce que la question posée est propre à UNE conversation,
32
+ * alors que `ToolApproval` décrit un droit permanent attaché à un agent et à un utilisateur.
33
+ */
34
+ export type ToolCallApproval = {
35
+ scope: ToolCallApprovalScope;
36
+ /**
37
+ * Ce qui est accordé, préfixé par sa famille exactement comme `ToolApproval.resourceKey` — `url:`
38
+ * une adresse, `cmd:` une commande exacte, `glob:` un motif. Un seul analyseur pour les deux, et
39
+ * une adresse approuvée ne peut jamais satisfaire une commande.
40
+ */
41
+ resourceKey: string;
42
+ /**
43
+ * Nom ou e-mail de la personne qui a cliqué, figé à cet instant plutôt que joint plus tard : la
44
+ * trace doit rester lisible après son départ de l'organisation. Absent quand l'appelant n'a pas
45
+ * d'identité.
46
+ */
47
+ approvedBy?: string;
48
+ };
49
+
50
+ /**
51
+ * Un appel d'outil que le serveur ne résout pas lui-même. Le tour est clos dès sa création et la
52
+ * réponse du client relance un tour de reprise : un appel non résolu ne retient rien. Générique —
53
+ * `tool` le nomme, `arguments` porte ce que le modèle a passé.
54
+ *
55
+ * Le lien avec la conversation va d'ici vers le message (`chatId` + `messageId`), jamais l'inverse :
56
+ * le message ne porte que le statut `waiting_client`, et les appels se lisent par message.
57
+ */
58
+ export type PendingToolCall = {
59
+ id: string;
60
+ tool: string;
61
+ userId: string;
62
+ chatId?: string;
63
+ /** Message assistant suspendu : celui que le tour de reprise complétera. */
64
+ messageId?: string;
65
+ /** Device visé, à titre informatif : l'autorisation passe par la conversation. */
66
+ deviceId?: string;
67
+ status: PendingToolCallStatus;
68
+ arguments?: Record<string, unknown>;
69
+ /**
70
+ * Ce que la reprise écrit dans la conversation à la place des `arguments` bruts. Le tour de reprise
71
+ * injecte `Outil : <tool>(<arguments>)` dans l'historique **persisté**, rejoué à chaque tour : un
72
+ * argument portant un secret y resterait indéfiniment. Absent ⇒ repli sur les `arguments`.
73
+ */
74
+ displaySummary?: string;
75
+ result?: ToolResult;
76
+ /**
77
+ * Écrit à la résolution, sur les seuls appels d'approbation et seulement quand la réponse est un
78
+ * accord. Posé dans la même écriture que le statut : pour une portée `once` il EST le droit, donc
79
+ * un appel passé à `completed` sans lui relancerait le tour sans rien accorder.
80
+ */
81
+ approval?: ToolCallApproval;
82
+ error?: string;
83
+ createdAt: Date;
84
+ updatedAt: Date;
85
+ /** Échéance optionnelle, posée outil par outil : un outil sans TTL n'expire jamais. */
86
+ expiresAt?: Date;
87
+ };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * User approval of one operation the agent's sandbox asked permission for.
3
+ *
4
+ * Two families exist, both belonging to `Bash_exec`: an address its curl may reach, and a python
5
+ * command it may run. When such a call is not already granted, the conversation turn is suspended
6
+ * and the user is asked. A "yes" is stored here ONLY when the user chose to stop being asked: a
7
+ * one-off "allow this once" grants the suspended turn and writes nothing, so every row in this
8
+ * table is a standing right, not a trace of a past decision. That trace lives on the tool call —
9
+ * see `ToolCallApproval`, which is where a one-off is recorded and where it stays confined.
10
+ *
11
+ * Three invariants make this table safe to read on the hot path:
12
+ *
13
+ * - **It can only intersect, never widen.** An approval unlocks an operation of a tool the agent
14
+ * already has, behind the server lock and the per-agent switch. An agent with no sandbox gains
15
+ * nothing because the user approved something elsewhere.
16
+ * - **`resourceKey` is normalised, and carries its family as a prefix.** A URL prefix, a glob or an
17
+ * exact command line — never raw arguments, so a secret passed next to the approved value is not
18
+ * persisted here. The prefix is what stops an approved address from authorising a command: the
19
+ * full arguments live only in the tool call that produced the approval.
20
+ * - **A row is a personal consent, and `userId` says whose.** Nobody can approve on someone else's
21
+ * behalf: the resolution route refuses a caller who is not the person the question was asked of,
22
+ * so the person the right applies to and the person who granted it are the same by construction.
23
+ * One column carries both, and it is what the management routes key on.
24
+ *
25
+ * Revoking one deletes its row: there is no state a revoked approval could be in, so nothing on the
26
+ * read path has to filter it out.
27
+ *
28
+ * Shared with agent-tasks through this package even though agent-tasks asks nobody: the type
29
+ * belongs with the other storage models.
30
+ */
31
+ export type ToolApprovalScopeKind = 'agentUser' | 'user';
32
+
33
+ export type ToolApproval = {
34
+ id: string;
35
+ /**
36
+ * British spelling "organisationId" — aligned with Agent.organisationId (domain + Mongo
37
+ * storage). In PostgreSQL the COLUMN is organizationId ("z"), mapped inside the PG adapter.
38
+ * It leads every key: an approval never crosses an organisation boundary.
39
+ */
40
+ organisationId: string;
41
+ /**
42
+ * `agentUser` is the only one written. `user` is kept readable but nothing writes it — see
43
+ * `agentApprovalScope` in brain-api, the single place that builds a stored scope.
44
+ */
45
+ scopeKind: ToolApprovalScopeKind;
46
+ /**
47
+ * Scope folded into a single string, so unicity is a plain unique index. PostgreSQL treats two
48
+ * NULLs as distinct, so a unique index over nullable discriminator columns would not constrain
49
+ * anything — same reason `workspaceSessionKey` exists.
50
+ */
51
+ scopeKey: string;
52
+ /** Set for the 'agentUser' scope. Absent on 'user', which holds for every agent. */
53
+ agentId?: string;
54
+ /** Whose consent it is. Never empty: an approval always belongs to one person. */
55
+ userId: string;
56
+ /** The tool as the model calls it. Part of the key: two tools never share an approval. */
57
+ toolName: string;
58
+ /**
59
+ * What a "yes" grants, prefixed by its family: `url:` an origin plus an optional first path
60
+ * segment, `glob:` a pattern covering a site, `cmd:` one exact command line.
61
+ */
62
+ resourceKey: string;
63
+ createdAt: string;
64
+ };
@@ -0,0 +1,21 @@
1
+ import type { ConversationAttachment } from './conversation';
2
+
3
+ /**
4
+ * Ce qu'un outil renvoie au tour de conversation. Copie structurelle de `ToolResult` /
5
+ * `ToolAction` de brain-api (`src/shared/init-tools.ts`), qui reste la définition de référence :
6
+ * ce paquet ne dépend pas de brain-api, mais les modèles de stockage qui portent un résultat
7
+ * d'outil — un message de conversation, un appel d'outil en attente — ont besoin de sa forme.
8
+ */
9
+ export type ToolAction = {
10
+ code: string;
11
+ } & Record<string, unknown>;
12
+
13
+ export type ToolResult = {
14
+ text: string;
15
+ attachments?: ConversationAttachment[];
16
+ askConfirmation?: boolean;
17
+ confirmationLink?: string;
18
+ actions?: ToolAction[];
19
+ /** Clôt le tour sans réponse finale : le client doit résoudre l'appel avant qu'il reprenne. */
20
+ suspendTurn?: boolean;
21
+ };