@cogenta/channels 0.1.0

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.
Files changed (177) hide show
  1. package/dist/adapter.d.ts +111 -0
  2. package/dist/adapter.d.ts.map +1 -0
  3. package/dist/adapter.js +2 -0
  4. package/dist/adapter.js.map +1 -0
  5. package/dist/approvals/commands.d.ts +28 -0
  6. package/dist/approvals/commands.d.ts.map +1 -0
  7. package/dist/approvals/commands.js +66 -0
  8. package/dist/approvals/commands.js.map +1 -0
  9. package/dist/approvals/dispatch.d.ts +22 -0
  10. package/dist/approvals/dispatch.d.ts.map +1 -0
  11. package/dist/approvals/dispatch.js +22 -0
  12. package/dist/approvals/dispatch.js.map +1 -0
  13. package/dist/approvals/message.d.ts +18 -0
  14. package/dist/approvals/message.d.ts.map +1 -0
  15. package/dist/approvals/message.js +40 -0
  16. package/dist/approvals/message.js.map +1 -0
  17. package/dist/approvals/signed-link.d.ts +19 -0
  18. package/dist/approvals/signed-link.d.ts.map +1 -0
  19. package/dist/approvals/signed-link.js +42 -0
  20. package/dist/approvals/signed-link.js.map +1 -0
  21. package/dist/approvals/store.d.ts +41 -0
  22. package/dist/approvals/store.d.ts.map +1 -0
  23. package/dist/approvals/store.js +41 -0
  24. package/dist/approvals/store.js.map +1 -0
  25. package/dist/approvals/token.d.ts +7 -0
  26. package/dist/approvals/token.d.ts.map +1 -0
  27. package/dist/approvals/token.js +40 -0
  28. package/dist/approvals/token.js.map +1 -0
  29. package/dist/formats/alert.d.ts +18 -0
  30. package/dist/formats/alert.d.ts.map +1 -0
  31. package/dist/formats/alert.js +49 -0
  32. package/dist/formats/alert.js.map +1 -0
  33. package/dist/formats/budget.d.ts +14 -0
  34. package/dist/formats/budget.d.ts.map +1 -0
  35. package/dist/formats/budget.js +13 -0
  36. package/dist/formats/budget.js.map +1 -0
  37. package/dist/formats/notification.d.ts +8 -0
  38. package/dist/formats/notification.d.ts.map +1 -0
  39. package/dist/formats/notification.js +26 -0
  40. package/dist/formats/notification.js.map +1 -0
  41. package/dist/formats/report.d.ts +18 -0
  42. package/dist/formats/report.d.ts.map +1 -0
  43. package/dist/formats/report.js +44 -0
  44. package/dist/formats/report.js.map +1 -0
  45. package/dist/inbound/authorize.d.ts +57 -0
  46. package/dist/inbound/authorize.d.ts.map +1 -0
  47. package/dist/inbound/authorize.js +32 -0
  48. package/dist/inbound/authorize.js.map +1 -0
  49. package/dist/inbound/router.d.ts +67 -0
  50. package/dist/inbound/router.d.ts.map +1 -0
  51. package/dist/inbound/router.js +69 -0
  52. package/dist/inbound/router.js.map +1 -0
  53. package/dist/index.d.ts +77 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +42 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/linking/codes.d.ts +6 -0
  58. package/dist/linking/codes.d.ts.map +1 -0
  59. package/dist/linking/codes.js +37 -0
  60. package/dist/linking/codes.js.map +1 -0
  61. package/dist/linking/store.d.ts +39 -0
  62. package/dist/linking/store.d.ts.map +1 -0
  63. package/dist/linking/store.js +77 -0
  64. package/dist/linking/store.js.map +1 -0
  65. package/dist/linking/tables.d.ts +14 -0
  66. package/dist/linking/tables.d.ts.map +1 -0
  67. package/dist/linking/tables.js +52 -0
  68. package/dist/linking/tables.js.map +1 -0
  69. package/dist/preferences/dispatcher.d.ts +37 -0
  70. package/dist/preferences/dispatcher.d.ts.map +1 -0
  71. package/dist/preferences/dispatcher.js +103 -0
  72. package/dist/preferences/dispatcher.js.map +1 -0
  73. package/dist/preferences/quiet-hours.d.ts +12 -0
  74. package/dist/preferences/quiet-hours.d.ts.map +1 -0
  75. package/dist/preferences/quiet-hours.js +22 -0
  76. package/dist/preferences/quiet-hours.js.map +1 -0
  77. package/dist/preferences/store.d.ts +8 -0
  78. package/dist/preferences/store.d.ts.map +1 -0
  79. package/dist/preferences/store.js +60 -0
  80. package/dist/preferences/store.js.map +1 -0
  81. package/dist/preferences/tables.d.ts +12 -0
  82. package/dist/preferences/tables.d.ts.map +1 -0
  83. package/dist/preferences/tables.js +55 -0
  84. package/dist/preferences/tables.js.map +1 -0
  85. package/dist/preferences/types.d.ts +37 -0
  86. package/dist/preferences/types.d.ts.map +1 -0
  87. package/dist/preferences/types.js +31 -0
  88. package/dist/preferences/types.js.map +1 -0
  89. package/dist/providers/discord/adapter.d.ts +38 -0
  90. package/dist/providers/discord/adapter.d.ts.map +1 -0
  91. package/dist/providers/discord/adapter.js +97 -0
  92. package/dist/providers/discord/adapter.js.map +1 -0
  93. package/dist/providers/discord/client.d.ts +67 -0
  94. package/dist/providers/discord/client.d.ts.map +1 -0
  95. package/dist/providers/discord/client.js +65 -0
  96. package/dist/providers/discord/client.js.map +1 -0
  97. package/dist/providers/discord/gateway.d.ts +55 -0
  98. package/dist/providers/discord/gateway.d.ts.map +1 -0
  99. package/dist/providers/discord/gateway.js +114 -0
  100. package/dist/providers/discord/gateway.js.map +1 -0
  101. package/dist/providers/discord/inbound.d.ts +22 -0
  102. package/dist/providers/discord/inbound.d.ts.map +1 -0
  103. package/dist/providers/discord/inbound.js +75 -0
  104. package/dist/providers/discord/inbound.js.map +1 -0
  105. package/dist/providers/discord/render.d.ts +9 -0
  106. package/dist/providers/discord/render.d.ts.map +1 -0
  107. package/dist/providers/discord/render.js +73 -0
  108. package/dist/providers/discord/render.js.map +1 -0
  109. package/dist/providers/email/adapter.d.ts +18 -0
  110. package/dist/providers/email/adapter.d.ts.map +1 -0
  111. package/dist/providers/email/adapter.js +41 -0
  112. package/dist/providers/email/adapter.js.map +1 -0
  113. package/dist/providers/email/file-transport.d.ts +14 -0
  114. package/dist/providers/email/file-transport.d.ts.map +1 -0
  115. package/dist/providers/email/file-transport.js +33 -0
  116. package/dist/providers/email/file-transport.js.map +1 -0
  117. package/dist/providers/email/render.d.ts +20 -0
  118. package/dist/providers/email/render.d.ts.map +1 -0
  119. package/dist/providers/email/render.js +89 -0
  120. package/dist/providers/email/render.js.map +1 -0
  121. package/dist/providers/email/transport.d.ts +25 -0
  122. package/dist/providers/email/transport.d.ts.map +1 -0
  123. package/dist/providers/email/transport.js +2 -0
  124. package/dist/providers/email/transport.js.map +1 -0
  125. package/dist/providers/slack/adapter.d.ts +35 -0
  126. package/dist/providers/slack/adapter.d.ts.map +1 -0
  127. package/dist/providers/slack/adapter.js +91 -0
  128. package/dist/providers/slack/adapter.js.map +1 -0
  129. package/dist/providers/slack/client.d.ts +45 -0
  130. package/dist/providers/slack/client.d.ts.map +1 -0
  131. package/dist/providers/slack/client.js +54 -0
  132. package/dist/providers/slack/client.js.map +1 -0
  133. package/dist/providers/slack/inbound.d.ts +23 -0
  134. package/dist/providers/slack/inbound.d.ts.map +1 -0
  135. package/dist/providers/slack/inbound.js +76 -0
  136. package/dist/providers/slack/inbound.js.map +1 -0
  137. package/dist/providers/slack/render.d.ts +8 -0
  138. package/dist/providers/slack/render.d.ts.map +1 -0
  139. package/dist/providers/slack/render.js +72 -0
  140. package/dist/providers/slack/render.js.map +1 -0
  141. package/dist/providers/slack/socket.d.ts +34 -0
  142. package/dist/providers/slack/socket.d.ts.map +1 -0
  143. package/dist/providers/slack/socket.js +70 -0
  144. package/dist/providers/slack/socket.js.map +1 -0
  145. package/dist/providers/telegram/adapter.d.ts +35 -0
  146. package/dist/providers/telegram/adapter.d.ts.map +1 -0
  147. package/dist/providers/telegram/adapter.js +109 -0
  148. package/dist/providers/telegram/adapter.js.map +1 -0
  149. package/dist/providers/telegram/client.d.ts +78 -0
  150. package/dist/providers/telegram/client.d.ts.map +1 -0
  151. package/dist/providers/telegram/client.js +53 -0
  152. package/dist/providers/telegram/client.js.map +1 -0
  153. package/dist/providers/telegram/inbound.d.ts +21 -0
  154. package/dist/providers/telegram/inbound.d.ts.map +1 -0
  155. package/dist/providers/telegram/inbound.js +72 -0
  156. package/dist/providers/telegram/inbound.js.map +1 -0
  157. package/dist/providers/telegram/render.d.ts +15 -0
  158. package/dist/providers/telegram/render.d.ts.map +1 -0
  159. package/dist/providers/telegram/render.js +87 -0
  160. package/dist/providers/telegram/render.js.map +1 -0
  161. package/dist/providers/webhook/adapter.d.ts +40 -0
  162. package/dist/providers/webhook/adapter.d.ts.map +1 -0
  163. package/dist/providers/webhook/adapter.js +71 -0
  164. package/dist/providers/webhook/adapter.js.map +1 -0
  165. package/dist/providers/webhook/render.d.ts +26 -0
  166. package/dist/providers/webhook/render.d.ts.map +1 -0
  167. package/dist/providers/webhook/render.js +41 -0
  168. package/dist/providers/webhook/render.js.map +1 -0
  169. package/dist/providers/webhook/signing.d.ts +59 -0
  170. package/dist/providers/webhook/signing.d.ts.map +1 -0
  171. package/dist/providers/webhook/signing.js +108 -0
  172. package/dist/providers/webhook/signing.js.map +1 -0
  173. package/dist/registry.d.ts +16 -0
  174. package/dist/registry.d.ts.map +1 -0
  175. package/dist/registry.js +39 -0
  176. package/dist/registry.js.map +1 -0
  177. package/package.json +42 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * L6 — "Un message est décrit de façon abstraite — titre, sections, niveau
3
+ * de sévérité, actions — et chaque adaptateur le rend selon ses capacités.
4
+ * On n'écrit pas de Markdown Telegram dans le code métier." Three fixed
5
+ * levels (`## Formats de message`), each a distinct shape rather than one
6
+ * loose bag of optional fields, so a caller cannot accidentally send an
7
+ * "Alerte" with no `expectedAction` or a one-line "Notification" carrying a
8
+ * wall of text — the type itself enforces the lot's own formatting rules.
9
+ */
10
+ export type ChannelSeverity = 'info' | 'warning' | 'critical';
11
+ /** A one-click action on a message (e.g. "Approuver"/"Refuser"). Resolving
12
+ * what `id` means (a single-use approval token, later a lot task) is not
13
+ * this layer's job — this is just the shape a `ChannelAdapter` renders as a
14
+ * button (or, on a channel without `capabilities.buttons`, as a link). */
15
+ export interface ChannelAction {
16
+ readonly id: string;
17
+ readonly label: string;
18
+ }
19
+ export interface ChannelMessageSection {
20
+ readonly heading?: string;
21
+ readonly body: string;
22
+ }
23
+ export interface ChannelKeyFigure {
24
+ readonly label: string;
25
+ readonly value: string;
26
+ }
27
+ /** "Titre, gravité, une phrase de contexte, l'action attendue, un lien vers l'admin." */
28
+ export interface AlertChannelMessage {
29
+ readonly level: 'alert';
30
+ readonly title: string;
31
+ readonly severity: ChannelSeverity;
32
+ readonly context: string;
33
+ readonly expectedAction: string;
34
+ readonly adminUrl: string;
35
+ readonly actions?: readonly ChannelAction[];
36
+ }
37
+ /** "Structure fixe, chiffres clés en tête, détail ensuite, jamais plus d'un
38
+ * écran sans repli." `moreUrl` is that repli (fallback) — the point past
39
+ * which detail lives in the admin, not in the message itself. */
40
+ export interface ReportChannelMessage {
41
+ readonly level: 'report';
42
+ readonly title: string;
43
+ readonly keyFigures: readonly ChannelKeyFigure[];
44
+ readonly sections: readonly ChannelMessageSection[];
45
+ readonly moreUrl?: string;
46
+ }
47
+ /** "Information sans action. Une ligne." No title, no sections, no actions —
48
+ * the type has nowhere to put them, which is the point. */
49
+ export interface NotificationChannelMessage {
50
+ readonly level: 'notification';
51
+ readonly text: string;
52
+ }
53
+ export type ChannelMessage = AlertChannelMessage | ReportChannelMessage | NotificationChannelMessage;
54
+ /** Opaque handle to a sent message, returned by `send` and usable by `update`. */
55
+ export type MessageId = string;
56
+ /** Where a message goes on a given channel — a platform-side chat/user/channel
57
+ * identifier. Opaque to everything above the adapter: core logic never
58
+ * inspects `id`, it only ever received it from `verifyIdentity`/linking. */
59
+ export interface ChannelTarget {
60
+ readonly id: string;
61
+ }
62
+ /**
63
+ * A channel-side identity, always distinct from a Cogenta user account.
64
+ *
65
+ * `linkedUserId` is `null` until L6 task 2's linking flow verifies this
66
+ * channel identity belongs to a real Cogenta user — "Une identité de canal
67
+ * non liée à un compte est ignorée, sans réponse" (the security rule this
68
+ * type exists to make representable, not yet enforced here). Nothing in
69
+ * this package may treat a `null` `linkedUserId` as a real user's identity.
70
+ */
71
+ export interface ChannelIdentity {
72
+ readonly channelName: string;
73
+ readonly channelUserId: string;
74
+ readonly linkedUserId: string | null;
75
+ }
76
+ /**
77
+ * A raw inbound command, always carrying the `ChannelIdentity` it came
78
+ * from — "Une commande entrante s'exécute avec les permissions de l'humain
79
+ * identifié, jamais avec celles de l'agent." A later task's router is what
80
+ * turns `identity.linkedUserId` into real permission checks; this shape
81
+ * exists so that seam cannot be bypassed by construction — there is no way
82
+ * to receive a command without also receiving who (if anyone) sent it.
83
+ */
84
+ export interface InboundCommand {
85
+ readonly text: string;
86
+ readonly identity: ChannelIdentity;
87
+ }
88
+ export type InboundHandler = (command: InboundCommand) => Promise<void> | void;
89
+ /**
90
+ * One implementation per platform (Telegram, Slack, Discord, email,
91
+ * webhook — L6 tasks 4/8/9/10/11). `update`/`onInbound` are optional
92
+ * because not every channel supports editing a sent message or receiving
93
+ * commands (`capabilities` says which); `send`/`verifyIdentity` are not
94
+ * optional — every channel can at least deliver a message and check a
95
+ * linking proof.
96
+ */
97
+ export interface ChannelAdapter {
98
+ readonly name: string;
99
+ readonly capabilities: {
100
+ readonly richText: boolean;
101
+ readonly buttons: boolean;
102
+ readonly threads: boolean;
103
+ readonly attachments: boolean;
104
+ readonly inbound: boolean;
105
+ };
106
+ send(target: ChannelTarget, message: ChannelMessage): Promise<MessageId>;
107
+ update?(id: MessageId, message: ChannelMessage): Promise<void>;
108
+ onInbound?(handler: InboundHandler): void;
109
+ verifyIdentity(proof: unknown): Promise<ChannelIdentity>;
110
+ }
111
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ import type { ApprovalQueue, AuditLogLike } from '@cogenta/agents';
2
+ import type { ChannelIdentity, NotificationChannelMessage } from '../adapter.js';
3
+ import type { RegisteredCommand } from '../inbound/router.js';
4
+ import type { ApprovalTokenStore } from './store.js';
5
+ export interface ApprovalCommandsOptions {
6
+ readonly tokenStore: ApprovalTokenStore;
7
+ readonly approvalQueue: ApprovalQueue;
8
+ readonly auditLog: AuditLogLike;
9
+ readonly getUserRoles: (userId: string) => Promise<readonly string[]>;
10
+ /** How a command handler replies — the channel of origin, since `route()` itself never sends anything (see `inbound/router.ts`'s `RouteResult`). */
11
+ readonly reply: (identity: ChannelIdentity, message: NotificationChannelMessage) => Promise<void>;
12
+ readonly channelName: string;
13
+ }
14
+ /**
15
+ * Builds the `/approve <token>` and `/deny <token>` commands — the channel
16
+ * side of "Un agent en niveau `execute_with_approval` produit une entrée
17
+ * dans la file d'approbation [...] deux actions : approuver, refuser."
18
+ * Registered with `requiredRoles: []` on the router deliberately: the role a
19
+ * decider actually needs varies per approval request (whatever the
20
+ * underlying tool requires, e.g. `content.publish`), not per command name,
21
+ * so the router's static per-command check cannot express it — the real
22
+ * check happens here, per token, against `ApprovalTokenRecord.requiredRole`.
23
+ */
24
+ export declare function createApprovalCommands(options: ApprovalCommandsOptions): {
25
+ readonly approve: RegisteredCommand;
26
+ readonly deny: RegisteredCommand;
27
+ };
28
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/approvals/commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAA;AAChF,OAAO,KAAK,EAAuB,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAClF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAA;IACvC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAA;IACrC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,QAAQ,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAA;IACrE,oJAAoJ;IACpJ,QAAQ,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,0BAA0B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACjG,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAMD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG;IACxE,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;IACnC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;CACjC,CAoEA"}
@@ -0,0 +1,66 @@
1
+ function notify(text) {
2
+ return { level: 'notification', text };
3
+ }
4
+ /**
5
+ * Builds the `/approve <token>` and `/deny <token>` commands — the channel
6
+ * side of "Un agent en niveau `execute_with_approval` produit une entrée
7
+ * dans la file d'approbation [...] deux actions : approuver, refuser."
8
+ * Registered with `requiredRoles: []` on the router deliberately: the role a
9
+ * decider actually needs varies per approval request (whatever the
10
+ * underlying tool requires, e.g. `content.publish`), not per command name,
11
+ * so the router's static per-command check cannot express it — the real
12
+ * check happens here, per token, against `ApprovalTokenRecord.requiredRole`.
13
+ */
14
+ export function createApprovalCommands(options) {
15
+ async function handle(decision, input) {
16
+ const token = input.args[0];
17
+ if (token === undefined) {
18
+ await options.reply(input.identity, notify('Précise le jeton : /approve <jeton>.'));
19
+ return;
20
+ }
21
+ const outcome = options.tokenStore.peek(token);
22
+ if (outcome.kind === 'invalid') {
23
+ await options.reply(input.identity, notify('Ce jeton d’approbation est invalide.'));
24
+ return;
25
+ }
26
+ if (outcome.kind === 'expired') {
27
+ await options.reply(input.identity, notify('Ce jeton d’approbation a expiré.'));
28
+ return;
29
+ }
30
+ if (outcome.kind === 'already_decided') {
31
+ await options.reply(input.identity, notify(outcome.decision === 'approved'
32
+ ? 'Cette demande a déjà été approuvée.'
33
+ : 'Cette demande a déjà été refusée.'));
34
+ return;
35
+ }
36
+ if (outcome.requiredRole !== null) {
37
+ const heldRoles = await options.getUserRoles(input.userId);
38
+ if (!heldRoles.includes(outcome.requiredRole)) {
39
+ await options.reply(input.identity, notify(`Il te faut le rôle "${outcome.requiredRole}" pour décider de cette demande.`));
40
+ return;
41
+ }
42
+ }
43
+ options.tokenStore.markDecided(token, decision);
44
+ await options.approvalQueue.decide(outcome.requestId, decision, input.userId);
45
+ await options.auditLog.record({
46
+ actorId: input.userId,
47
+ actorRoles: outcome.requiredRole === null ? [] : [outcome.requiredRole],
48
+ action: 'channel.approval.decide',
49
+ diff: { channel: options.channelName, requestId: outcome.requestId, decision },
50
+ });
51
+ await options.reply(input.identity, notify(decision === 'approved' ? 'Approuvé.' : 'Refusé.'));
52
+ }
53
+ return {
54
+ approve: {
55
+ name: 'approve',
56
+ requiredRoles: [],
57
+ handler: (input) => handle('approved', input),
58
+ },
59
+ deny: {
60
+ name: 'deny',
61
+ requiredRoles: [],
62
+ handler: (input) => handle('rejected', input),
63
+ },
64
+ };
65
+ }
66
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/approvals/commands.ts"],"names":[],"mappings":"AAeA,SAAS,MAAM,CAAC,IAAY;IAC1B,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,CAAA;AACxC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAgC;IAIrE,KAAK,UAAU,MAAM,CACnB,QAAiC,EACjC,KAA0B;QAE1B,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAC3B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,sCAAsC,CAAC,CAAC,CAAA;YACnF,OAAM;QACR,CAAC;QAED,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAE9C,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,sCAAsC,CAAC,CAAC,CAAA;YACnF,OAAM;QACR,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,kCAAkC,CAAC,CAAC,CAAA;YAC/E,OAAM;QACR,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YACvC,MAAM,OAAO,CAAC,KAAK,CACjB,KAAK,CAAC,QAAQ,EACd,MAAM,CACJ,OAAO,CAAC,QAAQ,KAAK,UAAU;gBAC7B,CAAC,CAAC,qCAAqC;gBACvC,CAAC,CAAC,mCAAmC,CACxC,CACF,CAAA;YACD,OAAM;QACR,CAAC;QAED,IAAI,OAAO,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;YAClC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YAC1D,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC9C,MAAM,OAAO,CAAC,KAAK,CACjB,KAAK,CAAC,QAAQ,EACd,MAAM,CAAC,uBAAuB,OAAO,CAAC,YAAY,kCAAkC,CAAC,CACtF,CAAA;gBACD,OAAM;YACR,CAAC;QACH,CAAC;QAED,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;QAC/C,MAAM,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;QAC7E,MAAM,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC5B,OAAO,EAAE,KAAK,CAAC,MAAM;YACrB,UAAU,EAAE,OAAO,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;YACvE,MAAM,EAAE,yBAAyB;YACjC,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,WAAW,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,QAAQ,EAAE;SAC/E,CAAC,CAAA;QAEF,MAAM,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;IAChG,CAAC;IAED,OAAO;QACL,OAAO,EAAE;YACP,IAAI,EAAE,SAAS;YACf,aAAa,EAAE,EAAE;YACjB,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC;SAC9C;QACD,IAAI,EAAE;YACJ,IAAI,EAAE,MAAM;YACZ,aAAa,EAAE,EAAE;YACjB,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC;SAC9C;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { ApprovalRequest } from '@cogenta/agents';
2
+ import type { ChannelAdapter, ChannelTarget, MessageId } from '../adapter.js';
3
+ import type { ApprovalTokenStore } from './store.js';
4
+ export interface DispatchApprovalOptions {
5
+ readonly adapter: ChannelAdapter;
6
+ readonly target: ChannelTarget;
7
+ readonly tokenStore: ApprovalTokenStore;
8
+ /** The role the underlying tool requires to approve/deny it — `null` (or omitted) means any linked user may decide. This is per-request because different tools need different permissions (`content.publish`, `deps.patch`, ...), which `ApprovalRequest` itself does not encode. */
9
+ readonly requiredRole?: string | null;
10
+ readonly buildAdminUrl: (requestId: string) => string;
11
+ }
12
+ /**
13
+ * "Un agent en niveau `execute_with_approval` produit une entrée dans la
14
+ * file d'approbation. Le canal reçoit un message [...]" — this is that
15
+ * send: issues two one-time tokens (approve/deny), renders the request as
16
+ * an `AlertChannelMessage`, and delivers it through the given adapter.
17
+ * Whoever wires an agent's approval flow to a channel calls this once per
18
+ * `ApprovalRequest`; the resulting tokens are what `createApprovalCommands`
19
+ * later redeems.
20
+ */
21
+ export declare function dispatchApproval(request: ApprovalRequest, options: DispatchApprovalOptions): Promise<MessageId>;
22
+ //# sourceMappingURL=dispatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/approvals/dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAA;IACvC,sRAAsR;IACtR,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,QAAQ,CAAC,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAA;CACtD;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,SAAS,CAAC,CAYpB"}
@@ -0,0 +1,22 @@
1
+ import { renderApprovalMessage } from './message.js';
2
+ /**
3
+ * "Un agent en niveau `execute_with_approval` produit une entrée dans la
4
+ * file d'approbation. Le canal reçoit un message [...]" — this is that
5
+ * send: issues two one-time tokens (approve/deny), renders the request as
6
+ * an `AlertChannelMessage`, and delivers it through the given adapter.
7
+ * Whoever wires an agent's approval flow to a channel calls this once per
8
+ * `ApprovalRequest`; the resulting tokens are what `createApprovalCommands`
9
+ * later redeems.
10
+ */
11
+ export async function dispatchApproval(request, options) {
12
+ const requiredRole = options.requiredRole ?? null;
13
+ const approve = options.tokenStore.issue(request.id, requiredRole);
14
+ const deny = options.tokenStore.issue(request.id, requiredRole);
15
+ const message = renderApprovalMessage(request, {
16
+ adminUrl: options.buildAdminUrl(request.id),
17
+ approveToken: approve.token,
18
+ denyToken: deny.token,
19
+ });
20
+ return options.adapter.send(options.target, message);
21
+ }
22
+ //# sourceMappingURL=dispatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/approvals/dispatch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAYpD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAwB,EACxB,OAAgC;IAEhC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,IAAI,CAAA;IACjD,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,YAAY,CAAC,CAAA;IAClE,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,YAAY,CAAC,CAAA;IAE/D,MAAM,OAAO,GAAG,qBAAqB,CAAC,OAAO,EAAE;QAC7C,QAAQ,EAAE,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,YAAY,EAAE,OAAO,CAAC,KAAK;QAC3B,SAAS,EAAE,IAAI,CAAC,KAAK;KACtB,CAAC,CAAA;IAEF,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AACtD,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { ApprovalRequest } from '@cogenta/agents';
2
+ import type { AlertChannelMessage } from '../adapter.js';
3
+ export interface RenderApprovalMessageOptions {
4
+ readonly adminUrl: string;
5
+ readonly approveToken: string;
6
+ readonly denyToken: string;
7
+ }
8
+ /**
9
+ * "Le canal reçoit un message avec le diff résumé et deux actions :
10
+ * approuver, refuser." `input` is summarised rather than dumped — a
11
+ * `Report`-level wall of JSON is exactly what `## Formats de message`'s
12
+ * "jamais plus d'un écran sans repli" rule exists to prevent; the full
13
+ * input lives at `adminUrl`. Built through `buildAlert` (L6 task 6) rather
14
+ * than the literal, so this call site gets the same required-field
15
+ * validation every other alert producer gets, for free.
16
+ */
17
+ export declare function renderApprovalMessage(request: ApprovalRequest, options: RenderApprovalMessageOptions): AlertChannelMessage;
18
+ //# sourceMappingURL=message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../src/approvals/message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAGxD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,4BAA4B,GACpC,mBAAmB,CAerB"}
@@ -0,0 +1,40 @@
1
+ import { buildAlert } from '../formats/alert.js';
2
+ /**
3
+ * "Le canal reçoit un message avec le diff résumé et deux actions :
4
+ * approuver, refuser." `input` is summarised rather than dumped — a
5
+ * `Report`-level wall of JSON is exactly what `## Formats de message`'s
6
+ * "jamais plus d'un écran sans repli" rule exists to prevent; the full
7
+ * input lives at `adminUrl`. Built through `buildAlert` (L6 task 6) rather
8
+ * than the literal, so this call site gets the same required-field
9
+ * validation every other alert producer gets, for free.
10
+ */
11
+ export function renderApprovalMessage(request, options) {
12
+ return buildAlert({
13
+ title: `Approbation requise : ${request.toolName}`,
14
+ severity: 'warning',
15
+ context: `L'agent "${request.agentName}" demande à exécuter "${request.toolName}" : ${summariseInput(request.input)}`,
16
+ expectedAction: 'Approuver ou refuser cette action avant qu’elle ne s’exécute.',
17
+ adminUrl: options.adminUrl,
18
+ // `action.id` becomes the literal command text a button press routes as
19
+ // (`providers/telegram/render.ts`'s `callback_data: action.id`) — so it
20
+ // must be a full `CommandRouter`-parseable string, not the bare token.
21
+ actions: [
22
+ { id: `approve ${options.approveToken}`, label: 'Approuver' },
23
+ { id: `deny ${options.denyToken}`, label: 'Refuser' },
24
+ ],
25
+ });
26
+ }
27
+ function summariseInput(input) {
28
+ const entries = Object.entries(input);
29
+ if (entries.length === 0)
30
+ return '(aucun paramètre)';
31
+ const summary = entries
32
+ .slice(0, 3)
33
+ .map(([key, value]) => `${key}=${truncate(String(value))}`)
34
+ .join(', ');
35
+ return entries.length > 3 ? `${summary}, …` : summary;
36
+ }
37
+ function truncate(value) {
38
+ return value.length > 40 ? `${value.slice(0, 40)}…` : value;
39
+ }
40
+ //# sourceMappingURL=message.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message.js","sourceRoot":"","sources":["../../src/approvals/message.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAQhD;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAwB,EACxB,OAAqC;IAErC,OAAO,UAAU,CAAC;QAChB,KAAK,EAAE,yBAAyB,OAAO,CAAC,QAAQ,EAAE;QAClD,QAAQ,EAAE,SAAS;QACnB,OAAO,EAAE,YAAY,OAAO,CAAC,SAAS,yBAAyB,OAAO,CAAC,QAAQ,OAAO,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QACrH,cAAc,EAAE,+DAA+D;QAC/E,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,wEAAwE;QACxE,wEAAwE;QACxE,uEAAuE;QACvE,OAAO,EAAE;YACP,EAAE,EAAE,EAAE,WAAW,OAAO,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE;YAC7D,EAAE,EAAE,EAAE,QAAQ,OAAO,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE;SACtD;KACF,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAwC;IAC9D,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IACrC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,mBAAmB,CAAA;IACpD,MAAM,OAAO,GAAG,OAAO;SACpB,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;SAC1D,IAAI,CAAC,IAAI,CAAC,CAAA;IACb,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO,CAAA;AACvD,CAAC;AAED,SAAS,QAAQ,CAAC,KAAa;IAC7B,OAAO,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAA;AAC7D,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * "Sur un canal sans boutons (email, webhook), l'action est un lien signé à
3
+ * usage unique." Same construction as `StorageDriver`'s `signedUrl`
4
+ * (`packages/core/src/storage/local.ts`, `signLocalUrl`/`verifyLocalSignedUrl`)
5
+ * — HMAC-SHA256 over the payload plus an expiry, constant-time comparison on
6
+ * verify. No route exists yet to receive a click on this link (no adapter
7
+ * without `capabilities.buttons` is built yet — email/webhook are later lot
8
+ * tasks); this is the primitive a future adapter constructs a URL from and
9
+ * a future verification endpoint calls to check it, tested in isolation.
10
+ */
11
+ export declare function signApprovalLink(signingKey: string, token: string, decision: 'approved' | 'rejected', expiresAtSeconds: number): string;
12
+ export declare function verifyApprovalLinkSignature(signingKey: string, token: string, decision: 'approved' | 'rejected', expiresAtSeconds: number, signature: string, nowSeconds?: number): boolean;
13
+ /**
14
+ * Builds the full signed URL a buttonless channel would send — `baseUrl`
15
+ * points at whatever future route verifies it (not built here; no adapter
16
+ * consumes this yet).
17
+ */
18
+ export declare function buildSignedApprovalLink(baseUrl: string, signingKey: string, token: string, decision: 'approved' | 'rejected', expiresInSeconds: number, nowSeconds?: number): string;
19
+ //# sourceMappingURL=signed-link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signed-link.d.ts","sourceRoot":"","sources":["../../src/approvals/signed-link.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,UAAU,GAAG,UAAU,EACjC,gBAAgB,EAAE,MAAM,GACvB,MAAM,CAIR;AAED,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,UAAU,GAAG,UAAU,EACjC,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAE,MAAM,EACjB,UAAU,GAAE,MAAsC,GACjD,OAAO,CAUT;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,UAAU,GAAG,UAAU,EACjC,gBAAgB,EAAE,MAAM,EACxB,UAAU,GAAE,MAAsC,GACjD,MAAM,CAUR"}
@@ -0,0 +1,42 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto';
2
+ /**
3
+ * "Sur un canal sans boutons (email, webhook), l'action est un lien signé à
4
+ * usage unique." Same construction as `StorageDriver`'s `signedUrl`
5
+ * (`packages/core/src/storage/local.ts`, `signLocalUrl`/`verifyLocalSignedUrl`)
6
+ * — HMAC-SHA256 over the payload plus an expiry, constant-time comparison on
7
+ * verify. No route exists yet to receive a click on this link (no adapter
8
+ * without `capabilities.buttons` is built yet — email/webhook are later lot
9
+ * tasks); this is the primitive a future adapter constructs a URL from and
10
+ * a future verification endpoint calls to check it, tested in isolation.
11
+ */
12
+ export function signApprovalLink(signingKey, token, decision, expiresAtSeconds) {
13
+ return createHmac('sha256', signingKey)
14
+ .update(`${token}:${decision}:${expiresAtSeconds}`)
15
+ .digest('hex');
16
+ }
17
+ export function verifyApprovalLinkSignature(signingKey, token, decision, expiresAtSeconds, signature, nowSeconds = Math.floor(Date.now() / 1000)) {
18
+ if (!Number.isFinite(expiresAtSeconds) || expiresAtSeconds <= nowSeconds)
19
+ return false;
20
+ const expected = Buffer.from(signApprovalLink(signingKey, token, decision, expiresAtSeconds), 'utf8');
21
+ const received = Buffer.from(signature, 'utf8');
22
+ if (expected.length !== received.length)
23
+ return false;
24
+ return timingSafeEqual(expected, received);
25
+ }
26
+ /**
27
+ * Builds the full signed URL a buttonless channel would send — `baseUrl`
28
+ * points at whatever future route verifies it (not built here; no adapter
29
+ * consumes this yet).
30
+ */
31
+ export function buildSignedApprovalLink(baseUrl, signingKey, token, decision, expiresInSeconds, nowSeconds = Math.floor(Date.now() / 1000)) {
32
+ const expiresAtSeconds = nowSeconds + expiresInSeconds;
33
+ const signature = signApprovalLink(signingKey, token, decision, expiresAtSeconds);
34
+ const params = new URLSearchParams({
35
+ token,
36
+ decision,
37
+ expires: String(expiresAtSeconds),
38
+ signature,
39
+ });
40
+ return `${baseUrl}?${params.toString()}`;
41
+ }
42
+ //# sourceMappingURL=signed-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signed-link.js","sourceRoot":"","sources":["../../src/approvals/signed-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEzD;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAC9B,UAAkB,EAClB,KAAa,EACb,QAAiC,EACjC,gBAAwB;IAExB,OAAO,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC;SACpC,MAAM,CAAC,GAAG,KAAK,IAAI,QAAQ,IAAI,gBAAgB,EAAE,CAAC;SAClD,MAAM,CAAC,KAAK,CAAC,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,UAAkB,EAClB,KAAa,EACb,QAAiC,EACjC,gBAAwB,EACxB,SAAiB,EACjB,UAAU,GAAW,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IAElD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,gBAAgB,IAAI,UAAU;QAAE,OAAO,KAAK,CAAA;IAEtF,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAC1B,gBAAgB,CAAC,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,gBAAgB,CAAC,EAC/D,MAAM,CACP,CAAA;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;IAC/C,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACrD,OAAO,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAe,EACf,UAAkB,EAClB,KAAa,EACb,QAAiC,EACjC,gBAAwB,EACxB,UAAU,GAAW,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IAElD,MAAM,gBAAgB,GAAG,UAAU,GAAG,gBAAgB,CAAA;IACtD,MAAM,SAAS,GAAG,gBAAgB,CAAC,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAA;IACjF,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;QACjC,KAAK;QACL,QAAQ;QACR,OAAO,EAAE,MAAM,CAAC,gBAAgB,CAAC;QACjC,SAAS;KACV,CAAC,CAAA;IACF,OAAO,GAAG,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAA;AAC1C,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * What redeeming a token found, so a channel adapter's reply is never a raw
3
+ * error — "Une entrée déjà traitée rend le bouton inopérant, avec message
4
+ * clair — pas d'erreur brute."
5
+ */
6
+ export type ApprovalTokenOutcome = {
7
+ readonly kind: 'invalid';
8
+ } | {
9
+ readonly kind: 'expired';
10
+ } | {
11
+ readonly kind: 'already_decided';
12
+ readonly decision: 'approved' | 'rejected';
13
+ } | {
14
+ readonly kind: 'ready';
15
+ readonly requestId: string;
16
+ readonly requiredRole: string | null;
17
+ };
18
+ export interface GeneratedApprovalToken {
19
+ readonly token: string;
20
+ readonly expiresAt: string;
21
+ }
22
+ export interface ApprovalTokenStore {
23
+ /** Issued when an `ApprovalRequest` is dispatched to a channel. */
24
+ issue(requestId: string, requiredRole?: string | null): GeneratedApprovalToken;
25
+ /** Read-only: does not mark the token used. Safe to call before checking the deciding user's role. */
26
+ peek(token: string): ApprovalTokenOutcome;
27
+ /** Marks a `ready` token as decided. Callers must have already confirmed `peek` returned `ready` and the deciding user is authorised — this function does not re-check either. */
28
+ markDecided(token: string, decision: 'approved' | 'rejected'): void;
29
+ }
30
+ export interface ApprovalTokenStoreOptions {
31
+ readonly now?: () => number;
32
+ }
33
+ /**
34
+ * In-memory, one store per running server — the same durability posture as
35
+ * `createMemoryApprovalQueue` (`@cogenta/agents`) it sits on top of: a
36
+ * request that outlives the process is already meaningless (the agent run
37
+ * it belongs to is gone too), so there is nothing a durable token store
38
+ * would add that the queue itself doesn't already need first.
39
+ */
40
+ export declare function createApprovalTokenStore(options?: ApprovalTokenStoreOptions): ApprovalTokenStore;
41
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/approvals/store.ts"],"names":[],"mappings":"AAUA;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,UAAU,GAAG,UAAU,CAAA;CAAE,GAChF;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAEhG,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,mEAAmE;IACnE,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,sBAAsB,CAAA;IAC9E,sGAAsG;IACtG,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,oBAAoB,CAAA;IACzC,kLAAkL;IAClL,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI,CAAA;CACpE;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,GAAE,yBAA8B,GACtC,kBAAkB,CA6BpB"}
@@ -0,0 +1,41 @@
1
+ import { APPROVAL_TOKEN_TTL_MS, generateApprovalToken, hashApprovalToken } from './token.js';
2
+ /**
3
+ * In-memory, one store per running server — the same durability posture as
4
+ * `createMemoryApprovalQueue` (`@cogenta/agents`) it sits on top of: a
5
+ * request that outlives the process is already meaningless (the agent run
6
+ * it belongs to is gone too), so there is nothing a durable token store
7
+ * would add that the queue itself doesn't already need first.
8
+ */
9
+ export function createApprovalTokenStore(options = {}) {
10
+ const now = options.now ?? Date.now;
11
+ const tokens = new Map();
12
+ return {
13
+ issue(requestId, requiredRole = null) {
14
+ const token = generateApprovalToken();
15
+ const expiresAt = now() + APPROVAL_TOKEN_TTL_MS;
16
+ tokens.set(hashApprovalToken(token), { requestId, requiredRole, expiresAt, decision: null });
17
+ return { token, expiresAt: new Date(expiresAt).toISOString() };
18
+ },
19
+ peek(token) {
20
+ const record = tokens.get(hashApprovalToken(token));
21
+ if (record === undefined)
22
+ return { kind: 'invalid' };
23
+ if (record.decision !== null)
24
+ return { kind: 'already_decided', decision: record.decision };
25
+ if (record.expiresAt <= now())
26
+ return { kind: 'expired' };
27
+ return { kind: 'ready', requestId: record.requestId, requiredRole: record.requiredRole };
28
+ },
29
+ markDecided(token, decision) {
30
+ const record = tokens.get(hashApprovalToken(token));
31
+ if (record === undefined)
32
+ return;
33
+ // First write wins: a caller is expected to have already checked
34
+ // `peek()` returned `ready`, but this is the actual enforcement point
35
+ // — a duplicate/racing call can never flip an already-recorded decision.
36
+ if (record.decision === null)
37
+ record.decision = decision;
38
+ },
39
+ };
40
+ }
41
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/approvals/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAuC5F;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAAO,GAA8B,EAAE;IAEvC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAA;IACnC,MAAM,MAAM,GAAG,IAAI,GAAG,EAA+B,CAAA;IAErD,OAAO;QACL,KAAK,CAAC,SAAS,EAAE,YAAY,GAAG,IAAI;YAClC,MAAM,KAAK,GAAG,qBAAqB,EAAE,CAAA;YACrC,MAAM,SAAS,GAAG,GAAG,EAAE,GAAG,qBAAqB,CAAA;YAC/C,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;YAC5F,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,EAAE,CAAA;QAChE,CAAC;QAED,IAAI,CAAC,KAAK;YACR,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAA;YACnD,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;YACpD,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI;gBAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAA;YAC3F,IAAI,MAAM,CAAC,SAAS,IAAI,GAAG,EAAE;gBAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;YACzD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,CAAA;QAC1F,CAAC;QAED,WAAW,CAAC,KAAK,EAAE,QAAQ;YACzB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAA;YACnD,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAM;YAChC,iEAAiE;YACjE,sEAAsE;YACtE,yEAAyE;YACzE,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI;gBAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAA;QAC1D,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ /** 20 minutes: long enough that a human notified on a phone has time to read a diff summary and decide, short enough that a stale approval request cannot be actioned days later against a world that has since moved on. */
2
+ export declare const APPROVAL_TOKEN_TTL_MS: number;
3
+ export declare function generateApprovalToken(): string;
4
+ /** Stored hashed, exactly like the linking code and a session token — a leaked table hands out nothing usable. */
5
+ export declare function hashApprovalToken(token: string): string;
6
+ export declare function normalizeApprovalToken(token: string): string;
7
+ //# sourceMappingURL=token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/approvals/token.ts"],"names":[],"mappings":"AAwBA,6NAA6N;AAC7N,eAAO,MAAM,qBAAqB,QAAiB,CAAA;AAEnD,wBAAgB,qBAAqB,IAAI,MAAM,CAQ9C;AAED,kHAAkH;AAClH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5D"}
@@ -0,0 +1,40 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ /**
3
+ * Same alphabet as the linking code (`linking/codes.ts`) — Crockford-style,
4
+ * ambiguous characters removed, 32 symbols divides 256 evenly so a
5
+ * byte-to-index mapping via modulo carries no bias. Kept as its own copy
6
+ * rather than imported: this is only the second real usage of this exact
7
+ * alphabet in this package (the linking code is the first), one short of
8
+ * this codebase's "not before three usages" threshold for extracting a
9
+ * shared helper — and the two tokens have deliberately different lengths
10
+ * and TTLs, so a shared constant would need parameterising anyway.
11
+ */
12
+ const TOKEN_ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
13
+ /**
14
+ * 12 symbols x 5 bits = 60 bits of entropy — longer than the 8-symbol (40
15
+ * bit) linking code, because an approval token authorises a real,
16
+ * consequential action (publishing content, patching a dependency, ...)
17
+ * rather than just proving "I received this code", and it lives for longer
18
+ * (`APPROVAL_TOKEN_TTL_MS`) than the linking code's short window, so it
19
+ * needs a wider brute-force margin.
20
+ */
21
+ const TOKEN_LENGTH = 12;
22
+ /** 20 minutes: long enough that a human notified on a phone has time to read a diff summary and decide, short enough that a stale approval request cannot be actioned days later against a world that has since moved on. */
23
+ export const APPROVAL_TOKEN_TTL_MS = 20 * 60 * 1000;
24
+ export function generateApprovalToken() {
25
+ const bytes = randomBytes(TOKEN_LENGTH);
26
+ let token = '';
27
+ for (let i = 0; i < TOKEN_LENGTH; i++) {
28
+ const byte = bytes[i] ?? 0;
29
+ token += TOKEN_ALPHABET[byte % TOKEN_ALPHABET.length];
30
+ }
31
+ return token;
32
+ }
33
+ /** Stored hashed, exactly like the linking code and a session token — a leaked table hands out nothing usable. */
34
+ export function hashApprovalToken(token) {
35
+ return createHash('sha256').update(normalizeApprovalToken(token)).digest('base64url');
36
+ }
37
+ export function normalizeApprovalToken(token) {
38
+ return token.trim().toUpperCase();
39
+ }
40
+ //# sourceMappingURL=token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/approvals/token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAErD;;;;;;;;;GASG;AACH,MAAM,cAAc,GAAG,iCAAiC,CAAA;AAExD;;;;;;;GAOG;AACH,MAAM,YAAY,GAAG,EAAE,CAAA;AAEvB,6NAA6N;AAC7N,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAEnD,MAAM,UAAU,qBAAqB;IACnC,MAAM,KAAK,GAAG,WAAW,CAAC,YAAY,CAAC,CAAA;IACvC,IAAI,KAAK,GAAG,EAAE,CAAA;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QAC1B,KAAK,IAAI,cAAc,CAAC,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,CAAA;IACvD,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,kHAAkH;AAClH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;AACvF,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;AACnC,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { AlertChannelMessage, ChannelAction, ChannelSeverity } from '../adapter.js';
2
+ export interface BuildAlertInput {
3
+ readonly title: string;
4
+ readonly severity: ChannelSeverity;
5
+ readonly context: string;
6
+ readonly expectedAction: string;
7
+ readonly adminUrl: string;
8
+ readonly actions?: readonly ChannelAction[];
9
+ }
10
+ /**
11
+ * "Alerte — quelque chose demande une action. Titre, gravité, une phrase de
12
+ * contexte, l'action attendue, un lien vers l'admin." The one required
13
+ * constructor for every alert this package (or an agent) dispatches — never
14
+ * hand-assemble the discriminated union literal, so this validation cannot
15
+ * be skipped by a new call site.
16
+ */
17
+ export declare function buildAlert(input: BuildAlertInput): AlertChannelMessage;
18
+ //# sourceMappingURL=alert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alert.d.ts","sourceRoot":"","sources":["../../src/formats/alert.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAExF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAA;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,CAAA;CAC5C;AA2BD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,mBAAmB,CAetE"}