@spinabot/brigade 1.3.2 → 1.4.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 (225) hide show
  1. package/README.md +54 -10
  2. package/convex/config.d.ts +2 -2
  3. package/convex/extensions.d.ts +2 -2
  4. package/convex/logs.d.ts +2 -2
  5. package/convex/memory.d.ts +7 -7
  6. package/convex/messages.d.ts +4 -4
  7. package/convex/schema.d.ts +17 -17
  8. package/convex/subagents.d.ts +12 -12
  9. package/dist/agents/agent-loop.d.ts +1 -0
  10. package/dist/agents/agent-loop.d.ts.map +1 -1
  11. package/dist/agents/agent-loop.js +1 -1
  12. package/dist/agents/agent-loop.js.map +1 -1
  13. package/dist/agents/channels/access-control/format-allow-from.d.ts +50 -0
  14. package/dist/agents/channels/access-control/format-allow-from.d.ts.map +1 -0
  15. package/dist/agents/channels/access-control/format-allow-from.js +64 -0
  16. package/dist/agents/channels/access-control/format-allow-from.js.map +1 -0
  17. package/dist/agents/channels/access-control/index.d.ts +2 -1
  18. package/dist/agents/channels/access-control/index.d.ts.map +1 -1
  19. package/dist/agents/channels/access-control/index.js +2 -1
  20. package/dist/agents/channels/access-control/index.js.map +1 -1
  21. package/dist/agents/channels/access-control/store.d.ts +15 -0
  22. package/dist/agents/channels/access-control/store.d.ts.map +1 -1
  23. package/dist/agents/channels/access-control/store.js +44 -1
  24. package/dist/agents/channels/access-control/store.js.map +1 -1
  25. package/dist/agents/channels/bundled-channel-metas.d.ts +26 -0
  26. package/dist/agents/channels/bundled-channel-metas.d.ts.map +1 -0
  27. package/dist/agents/channels/bundled-channel-metas.js +44 -0
  28. package/dist/agents/channels/bundled-channel-metas.js.map +1 -0
  29. package/dist/agents/channels/channel-messaging-registry.d.ts +130 -0
  30. package/dist/agents/channels/channel-messaging-registry.d.ts.map +1 -0
  31. package/dist/agents/channels/channel-messaging-registry.js +211 -0
  32. package/dist/agents/channels/channel-messaging-registry.js.map +1 -0
  33. package/dist/agents/channels/channel-meta-registry.d.ts +60 -0
  34. package/dist/agents/channels/channel-meta-registry.d.ts.map +1 -0
  35. package/dist/agents/channels/channel-meta-registry.js +128 -0
  36. package/dist/agents/channels/channel-meta-registry.js.map +1 -0
  37. package/dist/agents/channels/channel-security-registry.d.ts +138 -0
  38. package/dist/agents/channels/channel-security-registry.d.ts.map +1 -0
  39. package/dist/agents/channels/channel-security-registry.js +265 -0
  40. package/dist/agents/channels/channel-security-registry.js.map +1 -0
  41. package/dist/agents/channels/exposure.d.ts +44 -0
  42. package/dist/agents/channels/exposure.d.ts.map +1 -0
  43. package/dist/agents/channels/exposure.js +48 -0
  44. package/dist/agents/channels/exposure.js.map +1 -0
  45. package/dist/agents/channels/general-callback.d.ts +25 -0
  46. package/dist/agents/channels/general-callback.d.ts.map +1 -0
  47. package/dist/agents/channels/general-callback.js +31 -0
  48. package/dist/agents/channels/general-callback.js.map +1 -0
  49. package/dist/agents/channels/inbound-pipeline.d.ts +9 -0
  50. package/dist/agents/channels/inbound-pipeline.d.ts.map +1 -1
  51. package/dist/agents/channels/inbound-pipeline.js +429 -39
  52. package/dist/agents/channels/inbound-pipeline.js.map +1 -1
  53. package/dist/agents/channels/markdown-capability.d.ts +44 -0
  54. package/dist/agents/channels/markdown-capability.d.ts.map +1 -0
  55. package/dist/agents/channels/markdown-capability.js +66 -0
  56. package/dist/agents/channels/markdown-capability.js.map +1 -0
  57. package/dist/agents/channels/sdk.d.ts +170 -10
  58. package/dist/agents/channels/sdk.d.ts.map +1 -1
  59. package/dist/agents/channels/sdk.js +138 -6
  60. package/dist/agents/channels/sdk.js.map +1 -1
  61. package/dist/agents/channels/telegram/account-config.d.ts +41 -0
  62. package/dist/agents/channels/telegram/account-config.d.ts.map +1 -1
  63. package/dist/agents/channels/telegram/account-config.js +79 -0
  64. package/dist/agents/channels/telegram/account-config.js.map +1 -1
  65. package/dist/agents/channels/telegram/adapter.d.ts +6 -0
  66. package/dist/agents/channels/telegram/adapter.d.ts.map +1 -1
  67. package/dist/agents/channels/telegram/adapter.js +178 -6
  68. package/dist/agents/channels/telegram/adapter.js.map +1 -1
  69. package/dist/agents/channels/telegram/allowed-updates.d.ts +14 -5
  70. package/dist/agents/channels/telegram/allowed-updates.d.ts.map +1 -1
  71. package/dist/agents/channels/telegram/allowed-updates.js +8 -4
  72. package/dist/agents/channels/telegram/allowed-updates.js.map +1 -1
  73. package/dist/agents/channels/telegram/connection.d.ts +108 -1
  74. package/dist/agents/channels/telegram/connection.d.ts.map +1 -1
  75. package/dist/agents/channels/telegram/connection.js +219 -3
  76. package/dist/agents/channels/telegram/connection.js.map +1 -1
  77. package/dist/agents/channels/telegram/draft-stream.d.ts +98 -0
  78. package/dist/agents/channels/telegram/draft-stream.d.ts.map +1 -0
  79. package/dist/agents/channels/telegram/draft-stream.js +222 -0
  80. package/dist/agents/channels/telegram/draft-stream.js.map +1 -0
  81. package/dist/agents/channels/telegram/inbound-extras.d.ts +10 -1
  82. package/dist/agents/channels/telegram/inbound-extras.d.ts.map +1 -1
  83. package/dist/agents/channels/telegram/inbound-extras.js +66 -0
  84. package/dist/agents/channels/telegram/inbound-extras.js.map +1 -1
  85. package/dist/agents/channels/telegram/inline-keyboard.d.ts +36 -0
  86. package/dist/agents/channels/telegram/inline-keyboard.d.ts.map +1 -0
  87. package/dist/agents/channels/telegram/inline-keyboard.js +62 -0
  88. package/dist/agents/channels/telegram/inline-keyboard.js.map +1 -0
  89. package/dist/agents/channels/telegram/plugin.d.ts.map +1 -1
  90. package/dist/agents/channels/telegram/plugin.js +7 -11
  91. package/dist/agents/channels/telegram/plugin.js.map +1 -1
  92. package/dist/agents/channels/telegram/reasoning-lane.d.ts +41 -0
  93. package/dist/agents/channels/telegram/reasoning-lane.d.ts.map +1 -0
  94. package/dist/agents/channels/telegram/reasoning-lane.js +67 -0
  95. package/dist/agents/channels/telegram/reasoning-lane.js.map +1 -0
  96. package/dist/agents/channels/telegram/socks-dispatcher.d.ts +32 -0
  97. package/dist/agents/channels/telegram/socks-dispatcher.d.ts.map +1 -0
  98. package/dist/agents/channels/telegram/socks-dispatcher.js +97 -0
  99. package/dist/agents/channels/telegram/socks-dispatcher.js.map +1 -0
  100. package/dist/agents/channels/types.adapters.d.ts +137 -4
  101. package/dist/agents/channels/types.adapters.d.ts.map +1 -1
  102. package/dist/agents/channels/types.adapters.js +2 -2
  103. package/dist/agents/channels/types.core.d.ts +26 -2
  104. package/dist/agents/channels/types.core.d.ts.map +1 -1
  105. package/dist/agents/channels/types.plugin.d.ts +25 -7
  106. package/dist/agents/channels/types.plugin.d.ts.map +1 -1
  107. package/dist/agents/channels/types.plugin.js +6 -5
  108. package/dist/agents/channels/types.plugin.js.map +1 -1
  109. package/dist/agents/channels/whatsapp/adapter.d.ts +8 -0
  110. package/dist/agents/channels/whatsapp/adapter.d.ts.map +1 -1
  111. package/dist/agents/channels/whatsapp/adapter.js +7 -4
  112. package/dist/agents/channels/whatsapp/adapter.js.map +1 -1
  113. package/dist/agents/channels/whatsapp/connection.d.ts +24 -2
  114. package/dist/agents/channels/whatsapp/connection.d.ts.map +1 -1
  115. package/dist/agents/channels/whatsapp/connection.js +26 -5
  116. package/dist/agents/channels/whatsapp/connection.js.map +1 -1
  117. package/dist/agents/channels/whatsapp/plugin.d.ts.map +1 -1
  118. package/dist/agents/channels/whatsapp/plugin.js +6 -10
  119. package/dist/agents/channels/whatsapp/plugin.js.map +1 -1
  120. package/dist/agents/extensions/activation-planner.d.ts +125 -0
  121. package/dist/agents/extensions/activation-planner.d.ts.map +1 -0
  122. package/dist/agents/extensions/activation-planner.js +221 -0
  123. package/dist/agents/extensions/activation-planner.js.map +1 -0
  124. package/dist/agents/extensions/diagnose.d.ts +84 -0
  125. package/dist/agents/extensions/diagnose.d.ts.map +1 -0
  126. package/dist/agents/extensions/diagnose.js +123 -0
  127. package/dist/agents/extensions/diagnose.js.map +1 -0
  128. package/dist/agents/extensions/discovery.d.ts +85 -7
  129. package/dist/agents/extensions/discovery.d.ts.map +1 -1
  130. package/dist/agents/extensions/discovery.js +200 -15
  131. package/dist/agents/extensions/discovery.js.map +1 -1
  132. package/dist/agents/extensions/index.d.ts +3 -2
  133. package/dist/agents/extensions/index.d.ts.map +1 -1
  134. package/dist/agents/extensions/index.js +3 -2
  135. package/dist/agents/extensions/index.js.map +1 -1
  136. package/dist/agents/extensions/install-scan.d.ts +63 -0
  137. package/dist/agents/extensions/install-scan.d.ts.map +1 -0
  138. package/dist/agents/extensions/install-scan.js +201 -0
  139. package/dist/agents/extensions/install-scan.js.map +1 -0
  140. package/dist/agents/extensions/install.d.ts +135 -0
  141. package/dist/agents/extensions/install.d.ts.map +1 -0
  142. package/dist/agents/extensions/install.js +414 -0
  143. package/dist/agents/extensions/install.js.map +1 -0
  144. package/dist/agents/extensions/loader.d.ts +13 -2
  145. package/dist/agents/extensions/loader.d.ts.map +1 -1
  146. package/dist/agents/extensions/loader.js +126 -13
  147. package/dist/agents/extensions/loader.js.map +1 -1
  148. package/dist/agents/extensions/registry.d.ts +109 -0
  149. package/dist/agents/extensions/registry.d.ts.map +1 -1
  150. package/dist/agents/extensions/registry.js +172 -0
  151. package/dist/agents/extensions/registry.js.map +1 -1
  152. package/dist/agents/extensions/sdk-alias.d.ts +45 -0
  153. package/dist/agents/extensions/sdk-alias.d.ts.map +1 -0
  154. package/dist/agents/extensions/sdk-alias.js +94 -0
  155. package/dist/agents/extensions/sdk-alias.js.map +1 -0
  156. package/dist/agents/extensions/types.d.ts +155 -1
  157. package/dist/agents/extensions/types.d.ts.map +1 -1
  158. package/dist/agents/extensions/types.js.map +1 -1
  159. package/dist/agents/tools/composio-tool.d.ts +9 -1
  160. package/dist/agents/tools/composio-tool.d.ts.map +1 -1
  161. package/dist/agents/tools/composio-tool.js +68 -4
  162. package/dist/agents/tools/composio-tool.js.map +1 -1
  163. package/dist/agents/tools/message-action-tool.d.ts +6 -1
  164. package/dist/agents/tools/message-action-tool.d.ts.map +1 -1
  165. package/dist/agents/tools/message-action-tool.js +52 -2
  166. package/dist/agents/tools/message-action-tool.js.map +1 -1
  167. package/dist/agents/tools/send-message-tool.d.ts +1 -0
  168. package/dist/agents/tools/send-message-tool.d.ts.map +1 -1
  169. package/dist/agents/tools/send-message-tool.js +56 -1
  170. package/dist/agents/tools/send-message-tool.js.map +1 -1
  171. package/dist/buildstamp.json +1 -1
  172. package/dist/channel-sdk.d.ts +28 -0
  173. package/dist/channel-sdk.d.ts.map +1 -0
  174. package/dist/channel-sdk.js +28 -0
  175. package/dist/channel-sdk.js.map +1 -0
  176. package/dist/cli/commands/channels.d.ts.map +1 -1
  177. package/dist/cli/commands/channels.js +8 -8
  178. package/dist/cli/commands/channels.js.map +1 -1
  179. package/dist/cli/commands/connect.d.ts +8 -11
  180. package/dist/cli/commands/connect.d.ts.map +1 -1
  181. package/dist/cli/commands/connect.js +157 -17
  182. package/dist/cli/commands/connect.js.map +1 -1
  183. package/dist/cli/commands/doctor.d.ts.map +1 -1
  184. package/dist/cli/commands/doctor.js +64 -0
  185. package/dist/cli/commands/doctor.js.map +1 -1
  186. package/dist/cli/commands/extensions.d.ts +46 -0
  187. package/dist/cli/commands/extensions.d.ts.map +1 -0
  188. package/dist/cli/commands/extensions.js +578 -0
  189. package/dist/cli/commands/extensions.js.map +1 -0
  190. package/dist/cli/commands/pairing.d.ts.map +1 -1
  191. package/dist/cli/commands/pairing.js +16 -2
  192. package/dist/cli/commands/pairing.js.map +1 -1
  193. package/dist/cli/commands/update.d.ts +17 -0
  194. package/dist/cli/commands/update.d.ts.map +1 -0
  195. package/dist/cli/commands/update.js +104 -0
  196. package/dist/cli/commands/update.js.map +1 -0
  197. package/dist/cli/program/build-program.d.ts.map +1 -1
  198. package/dist/cli/program/build-program.js +113 -0
  199. package/dist/cli/program/build-program.js.map +1 -1
  200. package/dist/config/paths.d.ts +1 -0
  201. package/dist/config/paths.d.ts.map +1 -1
  202. package/dist/config/paths.js +9 -0
  203. package/dist/config/paths.js.map +1 -1
  204. package/dist/core/server.d.ts.map +1 -1
  205. package/dist/core/server.js +134 -2
  206. package/dist/core/server.js.map +1 -1
  207. package/dist/protocol.d.ts +25 -0
  208. package/dist/protocol.d.ts.map +1 -1
  209. package/dist/protocol.js.map +1 -1
  210. package/dist/system-prompt/assembler.d.ts.map +1 -1
  211. package/dist/system-prompt/assembler.js +17 -0
  212. package/dist/system-prompt/assembler.js.map +1 -1
  213. package/dist/system-prompt/identity-defaults.d.ts +28 -0
  214. package/dist/system-prompt/identity-defaults.d.ts.map +1 -1
  215. package/dist/system-prompt/identity-defaults.js +47 -0
  216. package/dist/system-prompt/identity-defaults.js.map +1 -1
  217. package/dist/ui/editor.d.ts.map +1 -1
  218. package/dist/ui/editor.js +1 -0
  219. package/dist/ui/editor.js.map +1 -1
  220. package/dist/version.d.ts +4 -3
  221. package/dist/version.d.ts.map +1 -1
  222. package/dist/version.js +27 -5
  223. package/dist/version.js.map +1 -1
  224. package/package.json +21 -4
  225. package/scripts/build-done.mjs +11 -2
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Channel-security registry — the process-wide lookup behind "does channel
3
+ * <id> ship a SUPPLEMENTARY DM-policy / audit opinion?".
4
+ *
5
+ * Mirrors `channel-messaging-registry.ts` exactly: a dynamic registration seam
6
+ * keyed by lowercased channel id, resolved through one process-global singleton
7
+ * so a hot reload (or CLI + gateway in one process) shares a single slot.
8
+ *
9
+ * ─────────────────────────────────────────────────────────────────────────────
10
+ * WHAT THIS IS — and what it is NOT
11
+ * ─────────────────────────────────────────────────────────────────────────────
12
+ *
13
+ * The AUTHORITATIVE access-control engine lives in `access-control/*` +
14
+ * `inbound-pipeline.ts` (store+config allow-from merge, owner bootstrap,
15
+ * `/start`/`/pending`/`/approve`/`/deny`, the per-message `evaluateAccess`
16
+ * decision). That engine is NOT replaced by anything here.
17
+ *
18
+ * This registry adds the OPTIONAL author-facing `ChannelSecurityAdapter`
19
+ * surface on TOP of that engine:
20
+ *
21
+ * 1. `security.resolveDmPolicy` — a channel plugin MAY register a
22
+ * supplementary DM-policy opinion. The pipeline consults it right AFTER
23
+ * its own local `resolveDmPolicy(cfg, adapter.id)` read. PRECEDENCE is
24
+ * strict: the central config stays authoritative, and a registered
25
+ * adapter may only ever TIGHTEN the effective policy (owner-only > allow-
26
+ * list > open), NEVER loosen it. See {@link reconcileDmPolicy}.
27
+ * 2. `security.collectWarnings` / `security.collectAuditFindings` — surfaced
28
+ * by {@link collectChannelSecurityAudit} for `brigade doctor`.
29
+ *
30
+ * A channel with NO `security` adapter simply never registers; the pipeline's
31
+ * local policy then stands UNCHANGED, so back-compat is preserved by
32
+ * construction.
33
+ */
34
+ import type { DmPolicy } from "./access-control/types.js";
35
+ import type { ChannelSecurityAdapter, ChannelSecurityAuditFinding, ChannelSecurityContext, ChannelSecurityDmPolicy } from "./types.adapters.js";
36
+ /**
37
+ * Register (or replace) a channel's SUPPLEMENTARY security adapter. The plugin
38
+ * engine calls this when a channel module that declares `plugin.security`
39
+ * registers, so the inbound pipeline can consult it as an optional consult on
40
+ * top of the central access-control engine. Last registration per id wins.
41
+ * No-ops on an empty/unusable id.
42
+ */
43
+ export declare function registerChannelSecurityAdapter(channelId: string | null | undefined, adapter: ChannelSecurityAdapter): void;
44
+ /**
45
+ * Bulk-register every security adapter declared on a plugin list (skipping
46
+ * plugins that omit the slot). The gateway bootstrap calls this once with its
47
+ * `bundledChannelPlugins` — parallel to `syncChannelMessagingAdaptersFromPlugins`
48
+ * — so the pipeline can consult a per-channel security opinion. Plugins WITHOUT
49
+ * a `security` adapter are simply skipped, leaving the central policy unchanged.
50
+ */
51
+ export declare function syncChannelSecurityAdaptersFromPlugins(plugins: ReadonlyArray<{
52
+ id: string;
53
+ security?: ChannelSecurityAdapter;
54
+ }>): void;
55
+ /**
56
+ * Drop every dynamically-registered security adapter. PUBLIC — the gateway's
57
+ * `stopExtensions()` calls this during a `system.reload` teardown so the
58
+ * registry starts clean and `startExtensions()` re-syncs ONLY the currently-
59
+ * loaded channels (the sync seam is `.set()`-only and never removes a slot, so
60
+ * without this a removed/edited channel's security adapter would leak across the
61
+ * reload and keep TIGHTENING DM policy — a security-relevant stale state).
62
+ * Idempotent.
63
+ */
64
+ export declare function clearChannelSecurityRegistry(): void;
65
+ /** Test-only alias of {@link clearChannelSecurityRegistry}. Kept so existing tests don't break. */
66
+ export declare function resetChannelSecurityRegistryForTests(): void;
67
+ /**
68
+ * Look up a channel's registered security adapter by id (or alias the caller
69
+ * already normalized). Returns `undefined` when the channel registered none —
70
+ * the caller then leaves its local policy untouched. Case-insensitive.
71
+ */
72
+ export declare function getChannelSecurityAdapter(channelId: string | null | undefined): ChannelSecurityAdapter | undefined;
73
+ /** Snapshot the registered (channelId) keys — diagnostics only. */
74
+ export declare function listChannelSecurityAdapters(): string[];
75
+ /**
76
+ * Tightness rank of a pipeline {@link DmPolicy}: higher = more restrictive.
77
+ * The ladder the precedence rule compares on.
78
+ */
79
+ export declare function dmPolicyTightness(policy: DmPolicy): number;
80
+ /**
81
+ * Translate an author-facing {@link ChannelSecurityDmPolicy} into the pipeline's
82
+ * {@link DmPolicy} vocabulary. Total over the enum; the mapping is the single
83
+ * source of truth documented above.
84
+ */
85
+ export declare function securityDmPolicyToDmPolicy(policy: ChannelSecurityDmPolicy): DmPolicy;
86
+ /**
87
+ * Reconcile a channel's supplementary security verdict with the central policy.
88
+ *
89
+ * PRECEDENCE (the whole point): the central `base` policy is AUTHORITATIVE. A
90
+ * registered security adapter may only ever TIGHTEN — it returns the more-
91
+ * restrictive of (base, adapter-opinion) on the tightness ladder. It can NEVER
92
+ * loosen: an adapter that says "open" while the config says "pairing" is
93
+ * ignored (the config wins). A `null`/absent opinion ("channel takes no
94
+ * opinion; defer to the gateway default") leaves `base` exactly as-is.
95
+ *
96
+ * @returns the effective `DmPolicy` (always === `base` or strictly tighter).
97
+ */
98
+ export declare function reconcileDmPolicy(base: DmPolicy, opinion: ChannelSecurityDmPolicy | null | undefined): DmPolicy;
99
+ /**
100
+ * Consult the channel's registered security adapter (if any) for a DM-policy
101
+ * opinion and reconcile it with the central `base` policy under the strict
102
+ * TIGHTEN-ONLY precedence rule. Returns `base` UNCHANGED when no adapter is
103
+ * registered, the adapter omits `resolveDmPolicy`, it returns `null`, or it
104
+ * throws — so a channel that doesn't opt in (or a buggy one) leaves the
105
+ * authoritative policy byte-identical to today. NEVER throws.
106
+ */
107
+ export declare function consultChannelDmPolicy(params: {
108
+ channelId: string;
109
+ base: DmPolicy;
110
+ ctx: ChannelSecurityContext;
111
+ }): DmPolicy;
112
+ /** One channel's findings, grouped under its id, from {@link collectChannelSecurityAudit}. */
113
+ export interface ChannelSecurityAuditGroup {
114
+ channelId: string;
115
+ findings: ChannelSecurityAuditFinding[];
116
+ }
117
+ /**
118
+ * Iterate every registered security adapter and collect its structured audit
119
+ * findings (`checkId`/`severity`/`title`/`detail`/`remediation`) plus its
120
+ * free-text warnings (folded into `info` findings so a single rendering path
121
+ * covers both). Used by `brigade doctor`'s per-channel security section.
122
+ *
123
+ * Total + defensive: an adapter that omits both methods contributes nothing; an
124
+ * adapter that throws is skipped (its failure can never break `doctor`). The
125
+ * per-call {@link ChannelSecurityContext} is built from the supplied config +
126
+ * the ordered account ids the caller resolved.
127
+ */
128
+ export declare function collectChannelSecurityAudit(params: {
129
+ /** The Brigade super-config the audit reads (passed through verbatim). */
130
+ cfg: ChannelSecurityContext["cfg"];
131
+ /**
132
+ * Resolve the ordered account ids to audit for a channel. When omitted, the
133
+ * audit runs once per channel with an empty account scope (`accountId: ""`)
134
+ * — enough for channels whose findings are config-global.
135
+ */
136
+ resolveAccountIds?: (channelId: string) => string[];
137
+ }): Promise<ChannelSecurityAuditGroup[]>;
138
+ //# sourceMappingURL=channel-security-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-security-registry.d.ts","sourceRoot":"","sources":["../../../src/agents/channels/channel-security-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,EACX,sBAAsB,EACtB,2BAA2B,EAC3B,sBAAsB,EACtB,uBAAuB,EACvB,MAAM,qBAAqB,CAAC;AAkB7B;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC7C,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,OAAO,EAAE,sBAAsB,GAC7B,IAAI,CAIN;AAED;;;;;;GAMG;AACH,wBAAgB,sCAAsC,CACrD,OAAO,EAAE,aAAa,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,sBAAsB,CAAA;CAAE,CAAC,GACvE,IAAI,CAIN;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,IAAI,IAAI,CAEnD;AAED,mGAAmG;AACnG,wBAAgB,oCAAoC,IAAI,IAAI,CAE3D;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACxC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAClC,sBAAsB,GAAG,SAAS,CAIpC;AAED,mEAAmE;AACnE,wBAAgB,2BAA2B,IAAI,MAAM,EAAE,CAEtD;AA0BD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAW1D;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,uBAAuB,GAAG,QAAQ,CAWpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAChC,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,uBAAuB,GAAG,IAAI,GAAG,SAAS,GACjD,QAAQ,CAKV;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,QAAQ,CAAC;IACf,GAAG,EAAE,sBAAsB,CAAC;CAC5B,GAAG,QAAQ,CAYX;AAMD,8FAA8F;AAC9F,MAAM,WAAW,yBAAyB;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,2BAA2B,EAAE,CAAC;CACxC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,2BAA2B,CAAC,MAAM,EAAE;IACzD,0EAA0E;IAC1E,GAAG,EAAE,sBAAsB,CAAC,KAAK,CAAC,CAAC;IACnC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;CACpD,GAAG,OAAO,CAAC,yBAAyB,EAAE,CAAC,CAiDvC"}
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Channel-security registry — the process-wide lookup behind "does channel
3
+ * <id> ship a SUPPLEMENTARY DM-policy / audit opinion?".
4
+ *
5
+ * Mirrors `channel-messaging-registry.ts` exactly: a dynamic registration seam
6
+ * keyed by lowercased channel id, resolved through one process-global singleton
7
+ * so a hot reload (or CLI + gateway in one process) shares a single slot.
8
+ *
9
+ * ─────────────────────────────────────────────────────────────────────────────
10
+ * WHAT THIS IS — and what it is NOT
11
+ * ─────────────────────────────────────────────────────────────────────────────
12
+ *
13
+ * The AUTHORITATIVE access-control engine lives in `access-control/*` +
14
+ * `inbound-pipeline.ts` (store+config allow-from merge, owner bootstrap,
15
+ * `/start`/`/pending`/`/approve`/`/deny`, the per-message `evaluateAccess`
16
+ * decision). That engine is NOT replaced by anything here.
17
+ *
18
+ * This registry adds the OPTIONAL author-facing `ChannelSecurityAdapter`
19
+ * surface on TOP of that engine:
20
+ *
21
+ * 1. `security.resolveDmPolicy` — a channel plugin MAY register a
22
+ * supplementary DM-policy opinion. The pipeline consults it right AFTER
23
+ * its own local `resolveDmPolicy(cfg, adapter.id)` read. PRECEDENCE is
24
+ * strict: the central config stays authoritative, and a registered
25
+ * adapter may only ever TIGHTEN the effective policy (owner-only > allow-
26
+ * list > open), NEVER loosen it. See {@link reconcileDmPolicy}.
27
+ * 2. `security.collectWarnings` / `security.collectAuditFindings` — surfaced
28
+ * by {@link collectChannelSecurityAudit} for `brigade doctor`.
29
+ *
30
+ * A channel with NO `security` adapter simply never registers; the pipeline's
31
+ * local policy then stands UNCHANGED, so back-compat is preserved by
32
+ * construction.
33
+ */
34
+ import { resolveGlobalSingleton } from "../../shared/global-singleton.js";
35
+ import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js";
36
+ /** Process-global slot so a hot reload (or CLI+gateway in one process) shares one registry. */
37
+ const REGISTRY_STATE_KEY = Symbol.for("brigade.channelSecurityRegistry.state");
38
+ function createState() {
39
+ return { byChannelId: new Map() };
40
+ }
41
+ function getState() {
42
+ return resolveGlobalSingleton(REGISTRY_STATE_KEY, createState);
43
+ }
44
+ /**
45
+ * Register (or replace) a channel's SUPPLEMENTARY security adapter. The plugin
46
+ * engine calls this when a channel module that declares `plugin.security`
47
+ * registers, so the inbound pipeline can consult it as an optional consult on
48
+ * top of the central access-control engine. Last registration per id wins.
49
+ * No-ops on an empty/unusable id.
50
+ */
51
+ export function registerChannelSecurityAdapter(channelId, adapter) {
52
+ const id = normalizeOptionalLowercaseString(channelId);
53
+ if (!id)
54
+ return;
55
+ getState().byChannelId.set(id, adapter);
56
+ }
57
+ /**
58
+ * Bulk-register every security adapter declared on a plugin list (skipping
59
+ * plugins that omit the slot). The gateway bootstrap calls this once with its
60
+ * `bundledChannelPlugins` — parallel to `syncChannelMessagingAdaptersFromPlugins`
61
+ * — so the pipeline can consult a per-channel security opinion. Plugins WITHOUT
62
+ * a `security` adapter are simply skipped, leaving the central policy unchanged.
63
+ */
64
+ export function syncChannelSecurityAdaptersFromPlugins(plugins) {
65
+ for (const plugin of plugins) {
66
+ if (plugin.security)
67
+ registerChannelSecurityAdapter(plugin.id, plugin.security);
68
+ }
69
+ }
70
+ /**
71
+ * Drop every dynamically-registered security adapter. PUBLIC — the gateway's
72
+ * `stopExtensions()` calls this during a `system.reload` teardown so the
73
+ * registry starts clean and `startExtensions()` re-syncs ONLY the currently-
74
+ * loaded channels (the sync seam is `.set()`-only and never removes a slot, so
75
+ * without this a removed/edited channel's security adapter would leak across the
76
+ * reload and keep TIGHTENING DM policy — a security-relevant stale state).
77
+ * Idempotent.
78
+ */
79
+ export function clearChannelSecurityRegistry() {
80
+ getState().byChannelId.clear();
81
+ }
82
+ /** Test-only alias of {@link clearChannelSecurityRegistry}. Kept so existing tests don't break. */
83
+ export function resetChannelSecurityRegistryForTests() {
84
+ clearChannelSecurityRegistry();
85
+ }
86
+ /**
87
+ * Look up a channel's registered security adapter by id (or alias the caller
88
+ * already normalized). Returns `undefined` when the channel registered none —
89
+ * the caller then leaves its local policy untouched. Case-insensitive.
90
+ */
91
+ export function getChannelSecurityAdapter(channelId) {
92
+ const key = normalizeOptionalLowercaseString(channelId);
93
+ if (!key)
94
+ return undefined;
95
+ return getState().byChannelId.get(key);
96
+ }
97
+ /** Snapshot the registered (channelId) keys — diagnostics only. */
98
+ export function listChannelSecurityAdapters() {
99
+ return [...getState().byChannelId.keys()].sort();
100
+ }
101
+ /* -------------------------------------------------------------------------
102
+ * DM-policy vocabulary reconciliation (the adapter enum ⇄ the pipeline enum)
103
+ *
104
+ * The adapter's `ChannelSecurityDmPolicy` is the AUTHOR-FACING vocabulary
105
+ * ("who may DM this account"); the pipeline's `DmPolicy` is the engine's
106
+ * vocabulary. They are DIFFERENT alphabets on purpose — a channel author thinks
107
+ * in "owner / allow-from / all", the engine thinks in "pairing / allowlist /
108
+ * open / disabled". This module owns the ONE total, documented mapping between
109
+ * them so neither side has to know the other's words.
110
+ *
111
+ * The mapping rides a single TIGHTNESS LADDER (0 = loosest, 3 = tightest):
112
+ *
113
+ * adapter "all" ⇄ pipeline "open" — rank 0 (anyone may DM)
114
+ * adapter "allow-from" ⇄ pipeline "allowlist" — rank 1 (only listed senders)
115
+ * adapter "owner" ⇄ pipeline "pairing" — rank 2 (owner + approved; strangers must pair)
116
+ * adapter "disabled" ⇄ pipeline "disabled" — rank 3 (every DM dropped)
117
+ *
118
+ * `"owner"` maps to `"pairing"` because pairing IS the owner-bootstrap state:
119
+ * the owner (and anyone they approve) gets in, every stranger is challenged —
120
+ * i.e. "owner-gated". It is strictly TIGHTER than a static `allowlist` (an
121
+ * un-approved stranger is challenged, not silently let in by a pre-seeded list)
122
+ * yet looser than a full `disabled` lockdown.
123
+ * --------------------------------------------------------------------- */
124
+ /**
125
+ * Tightness rank of a pipeline {@link DmPolicy}: higher = more restrictive.
126
+ * The ladder the precedence rule compares on.
127
+ */
128
+ export function dmPolicyTightness(policy) {
129
+ switch (policy) {
130
+ case "open":
131
+ return 0;
132
+ case "allowlist":
133
+ return 1;
134
+ case "pairing":
135
+ return 2;
136
+ case "disabled":
137
+ return 3;
138
+ }
139
+ }
140
+ /**
141
+ * Translate an author-facing {@link ChannelSecurityDmPolicy} into the pipeline's
142
+ * {@link DmPolicy} vocabulary. Total over the enum; the mapping is the single
143
+ * source of truth documented above.
144
+ */
145
+ export function securityDmPolicyToDmPolicy(policy) {
146
+ switch (policy) {
147
+ case "all":
148
+ return "open";
149
+ case "allow-from":
150
+ return "allowlist";
151
+ case "owner":
152
+ return "pairing";
153
+ case "disabled":
154
+ return "disabled";
155
+ }
156
+ }
157
+ /**
158
+ * Reconcile a channel's supplementary security verdict with the central policy.
159
+ *
160
+ * PRECEDENCE (the whole point): the central `base` policy is AUTHORITATIVE. A
161
+ * registered security adapter may only ever TIGHTEN — it returns the more-
162
+ * restrictive of (base, adapter-opinion) on the tightness ladder. It can NEVER
163
+ * loosen: an adapter that says "open" while the config says "pairing" is
164
+ * ignored (the config wins). A `null`/absent opinion ("channel takes no
165
+ * opinion; defer to the gateway default") leaves `base` exactly as-is.
166
+ *
167
+ * @returns the effective `DmPolicy` (always === `base` or strictly tighter).
168
+ */
169
+ export function reconcileDmPolicy(base, opinion) {
170
+ if (opinion == null)
171
+ return base;
172
+ const adapterPolicy = securityDmPolicyToDmPolicy(opinion);
173
+ // Tighten-only: keep whichever sits higher on the tightness ladder.
174
+ return dmPolicyTightness(adapterPolicy) > dmPolicyTightness(base) ? adapterPolicy : base;
175
+ }
176
+ /**
177
+ * Consult the channel's registered security adapter (if any) for a DM-policy
178
+ * opinion and reconcile it with the central `base` policy under the strict
179
+ * TIGHTEN-ONLY precedence rule. Returns `base` UNCHANGED when no adapter is
180
+ * registered, the adapter omits `resolveDmPolicy`, it returns `null`, or it
181
+ * throws — so a channel that doesn't opt in (or a buggy one) leaves the
182
+ * authoritative policy byte-identical to today. NEVER throws.
183
+ */
184
+ export function consultChannelDmPolicy(params) {
185
+ const { channelId, base, ctx } = params;
186
+ const adapter = getChannelSecurityAdapter(channelId);
187
+ if (!adapter || typeof adapter.resolveDmPolicy !== "function")
188
+ return base;
189
+ try {
190
+ const opinion = adapter.resolveDmPolicy(ctx);
191
+ return reconcileDmPolicy(base, opinion);
192
+ }
193
+ catch {
194
+ // A misbehaving adapter must never break inbound gating — keep the
195
+ // authoritative central policy.
196
+ return base;
197
+ }
198
+ }
199
+ /**
200
+ * Iterate every registered security adapter and collect its structured audit
201
+ * findings (`checkId`/`severity`/`title`/`detail`/`remediation`) plus its
202
+ * free-text warnings (folded into `info` findings so a single rendering path
203
+ * covers both). Used by `brigade doctor`'s per-channel security section.
204
+ *
205
+ * Total + defensive: an adapter that omits both methods contributes nothing; an
206
+ * adapter that throws is skipped (its failure can never break `doctor`). The
207
+ * per-call {@link ChannelSecurityContext} is built from the supplied config +
208
+ * the ordered account ids the caller resolved.
209
+ */
210
+ export async function collectChannelSecurityAudit(params) {
211
+ const { cfg, resolveAccountIds } = params;
212
+ const out = [];
213
+ for (const channelId of listChannelSecurityAdapters()) {
214
+ const adapter = getChannelSecurityAdapter(channelId);
215
+ if (!adapter)
216
+ continue;
217
+ const findings = [];
218
+ const accountIds = resolveAccountIds?.(channelId) ?? [""];
219
+ const orderedAccountIds = accountIds.length > 0 ? accountIds : [""];
220
+ for (const accountId of orderedAccountIds) {
221
+ const ctx = { account: undefined, accountId, cfg };
222
+ // Structured findings.
223
+ if (typeof adapter.collectAuditFindings === "function") {
224
+ try {
225
+ const got = await adapter.collectAuditFindings({
226
+ ...ctx,
227
+ sourceConfig: cfg,
228
+ orderedAccountIds,
229
+ });
230
+ if (Array.isArray(got))
231
+ findings.push(...got);
232
+ }
233
+ catch {
234
+ /* a broken adapter never breaks the audit */
235
+ }
236
+ }
237
+ // Free-text warnings → folded into `info` findings so doctor renders
238
+ // one shape. Keyed by account so multi-account warnings don't collide.
239
+ if (typeof adapter.collectWarnings === "function") {
240
+ try {
241
+ const warnings = await adapter.collectWarnings(ctx);
242
+ if (Array.isArray(warnings)) {
243
+ for (const w of warnings) {
244
+ if (typeof w === "string" && w.trim()) {
245
+ findings.push({
246
+ checkId: `${channelId}.warning`,
247
+ severity: "warn",
248
+ title: "Channel security warning",
249
+ detail: w.trim(),
250
+ });
251
+ }
252
+ }
253
+ }
254
+ }
255
+ catch {
256
+ /* a broken adapter never breaks the audit */
257
+ }
258
+ }
259
+ }
260
+ if (findings.length > 0)
261
+ out.push({ channelId, findings });
262
+ }
263
+ return out;
264
+ }
265
+ //# sourceMappingURL=channel-security-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-security-registry.js","sourceRoot":"","sources":["../../../src/agents/channels/channel-security-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Channel-exposure resolver — turns a channel's `meta.exposure` (+ the legacy
3
+ * `meta.showConfigured` / `meta.showInSetup` booleans) into a concrete
4
+ * per-surface visibility verdict.
5
+ *
6
+ * Brand-scrubbed analogue of upstream's `src/channels/plugins/exposure.ts`.
7
+ *
8
+ * Three surfaces, each an independent boolean that DEFAULTS TO VISIBLE when the
9
+ * channel says nothing:
10
+ *
11
+ * - `configured` — show in "configured channels" lists (`brigade status`,
12
+ * account pickers). Precedence: `exposure.configured` → `showConfigured`
13
+ * → `true`.
14
+ * - `setup` — offer in the onboarding / setup wizard. Precedence:
15
+ * `exposure.setup` → `showInSetup` → `true`.
16
+ * - `docs` — surface in generated docs / help. Precedence:
17
+ * `exposure.docs` → `true`.
18
+ *
19
+ * The default-true floor matters: a channel plugin authored without ANY
20
+ * exposure metadata stays fully visible, so adding this resolver never hides
21
+ * an existing channel.
22
+ */
23
+ import type { ChannelMeta } from "./types.core.js";
24
+ /** The subset of `ChannelMeta` the resolver reads. */
25
+ export type ChannelExposureInput = Pick<ChannelMeta, "exposure" | "showConfigured" | "showInSetup">;
26
+ /** Concrete per-surface visibility verdict (every key resolved to a boolean). */
27
+ export interface ResolvedChannelExposure {
28
+ configured: boolean;
29
+ setup: boolean;
30
+ docs: boolean;
31
+ }
32
+ /**
33
+ * Resolve a channel's exposure to concrete booleans. Pure; safe to call with a
34
+ * bare `{}` (everything defaults to `true`). Accepts `undefined`/`null` meta
35
+ * as "all visible" so callers needn't null-guard.
36
+ */
37
+ export declare function resolveChannelExposure(meta: ChannelExposureInput | undefined | null): ResolvedChannelExposure;
38
+ /** True when the channel should appear in "configured channels" views. */
39
+ export declare function isChannelVisibleInConfiguredLists(meta: ChannelExposureInput | undefined | null): boolean;
40
+ /** True when the channel should be offered in the setup / onboarding wizard. */
41
+ export declare function isChannelVisibleInSetup(meta: ChannelExposureInput | undefined | null): boolean;
42
+ /** True when the channel should be surfaced in generated docs / help. */
43
+ export declare function isChannelVisibleInDocs(meta: ChannelExposureInput | undefined | null): boolean;
44
+ //# sourceMappingURL=exposure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exposure.d.ts","sourceRoot":"","sources":["../../../src/agents/channels/exposure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,sDAAsD;AACtD,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,gBAAgB,GAAG,aAAa,CAAC,CAAC;AAEpG,iFAAiF;AACjF,MAAM,WAAW,uBAAuB;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACrC,IAAI,EAAE,oBAAoB,GAAG,SAAS,GAAG,IAAI,GAC3C,uBAAuB,CAOzB;AAED,0EAA0E;AAC1E,wBAAgB,iCAAiC,CAChD,IAAI,EAAE,oBAAoB,GAAG,SAAS,GAAG,IAAI,GAC3C,OAAO,CAET;AAED,gFAAgF;AAChF,wBAAgB,uBAAuB,CACtC,IAAI,EAAE,oBAAoB,GAAG,SAAS,GAAG,IAAI,GAC3C,OAAO,CAET;AAED,yEAAyE;AACzE,wBAAgB,sBAAsB,CACrC,IAAI,EAAE,oBAAoB,GAAG,SAAS,GAAG,IAAI,GAC3C,OAAO,CAET"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Channel-exposure resolver — turns a channel's `meta.exposure` (+ the legacy
3
+ * `meta.showConfigured` / `meta.showInSetup` booleans) into a concrete
4
+ * per-surface visibility verdict.
5
+ *
6
+ * Brand-scrubbed analogue of upstream's `src/channels/plugins/exposure.ts`.
7
+ *
8
+ * Three surfaces, each an independent boolean that DEFAULTS TO VISIBLE when the
9
+ * channel says nothing:
10
+ *
11
+ * - `configured` — show in "configured channels" lists (`brigade status`,
12
+ * account pickers). Precedence: `exposure.configured` → `showConfigured`
13
+ * → `true`.
14
+ * - `setup` — offer in the onboarding / setup wizard. Precedence:
15
+ * `exposure.setup` → `showInSetup` → `true`.
16
+ * - `docs` — surface in generated docs / help. Precedence:
17
+ * `exposure.docs` → `true`.
18
+ *
19
+ * The default-true floor matters: a channel plugin authored without ANY
20
+ * exposure metadata stays fully visible, so adding this resolver never hides
21
+ * an existing channel.
22
+ */
23
+ /**
24
+ * Resolve a channel's exposure to concrete booleans. Pure; safe to call with a
25
+ * bare `{}` (everything defaults to `true`). Accepts `undefined`/`null` meta
26
+ * as "all visible" so callers needn't null-guard.
27
+ */
28
+ export function resolveChannelExposure(meta) {
29
+ const exposure = meta?.exposure;
30
+ return {
31
+ configured: exposure?.configured ?? meta?.showConfigured ?? true,
32
+ setup: exposure?.setup ?? meta?.showInSetup ?? true,
33
+ docs: exposure?.docs ?? true,
34
+ };
35
+ }
36
+ /** True when the channel should appear in "configured channels" views. */
37
+ export function isChannelVisibleInConfiguredLists(meta) {
38
+ return resolveChannelExposure(meta).configured;
39
+ }
40
+ /** True when the channel should be offered in the setup / onboarding wizard. */
41
+ export function isChannelVisibleInSetup(meta) {
42
+ return resolveChannelExposure(meta).setup;
43
+ }
44
+ /** True when the channel should be surfaced in generated docs / help. */
45
+ export function isChannelVisibleInDocs(meta) {
46
+ return resolveChannelExposure(meta).docs;
47
+ }
48
+ //# sourceMappingURL=exposure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exposure.js","sourceRoot":"","sources":["../../../src/agents/channels/exposure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAcH;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACrC,IAA6C;IAE7C,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,CAAC;IAChC,OAAO;QACN,UAAU,EAAE,QAAQ,EAAE,UAAU,IAAI,IAAI,EAAE,cAAc,IAAI,IAAI;QAChE,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,IAAI,EAAE,WAAW,IAAI,IAAI;QACnD,IAAI,EAAE,QAAQ,EAAE,IAAI,IAAI,IAAI;KAC5B,CAAC;AACH,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,iCAAiC,CAChD,IAA6C;IAE7C,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC;AAChD,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,uBAAuB,CACtC,IAA6C;IAE7C,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;AAC3C,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,sBAAsB,CACrC,IAA6C;IAE7C,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Shared convention for GENERAL (agent-attached) inline-button callbacks.
3
+ *
4
+ * Approval buttons carry codec payloads the central approval bridge consumes.
5
+ * GENERAL buttons (attached by the agent via the `message_action` `buttons`
6
+ * kind) carry an app-defined token namespaced with {@link GENERAL_CALLBACK_PREFIX}
7
+ * so the inbound pipeline can tell the two apart: it tries the approval bridge
8
+ * first, and a callback that didn't match an approval but DOES carry the general
9
+ * prefix is routed through the pipeline as a synthetic turn instead of being
10
+ * dropped.
11
+ *
12
+ * Channel-agnostic on purpose — both the Telegram keyboard builder (which mints
13
+ * the prefixed `callback_data`) and the pipeline (which decodes it) import from
14
+ * here so the prefix lives in ONE place.
15
+ */
16
+ /** Namespace prefix marking a callback as a general (agent-attached) button. */
17
+ export declare const GENERAL_CALLBACK_PREFIX = "g:";
18
+ /** True when a raw `callback_data` value is a general (agent-attached) button. */
19
+ export declare function isGeneralCallbackData(data: string | undefined): boolean;
20
+ /**
21
+ * Strip the general prefix, returning the app-defined token the agent set.
22
+ * Returns "" when the input is not a general callback.
23
+ */
24
+ export declare function decodeGeneralCallbackData(data: string | undefined): string;
25
+ //# sourceMappingURL=general-callback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"general-callback.d.ts","sourceRoot":"","sources":["../../../src/agents/channels/general-callback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,gFAAgF;AAChF,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,kFAAkF;AAClF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEvE;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG1E"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Shared convention for GENERAL (agent-attached) inline-button callbacks.
3
+ *
4
+ * Approval buttons carry codec payloads the central approval bridge consumes.
5
+ * GENERAL buttons (attached by the agent via the `message_action` `buttons`
6
+ * kind) carry an app-defined token namespaced with {@link GENERAL_CALLBACK_PREFIX}
7
+ * so the inbound pipeline can tell the two apart: it tries the approval bridge
8
+ * first, and a callback that didn't match an approval but DOES carry the general
9
+ * prefix is routed through the pipeline as a synthetic turn instead of being
10
+ * dropped.
11
+ *
12
+ * Channel-agnostic on purpose — both the Telegram keyboard builder (which mints
13
+ * the prefixed `callback_data`) and the pipeline (which decodes it) import from
14
+ * here so the prefix lives in ONE place.
15
+ */
16
+ /** Namespace prefix marking a callback as a general (agent-attached) button. */
17
+ export const GENERAL_CALLBACK_PREFIX = "g:";
18
+ /** True when a raw `callback_data` value is a general (agent-attached) button. */
19
+ export function isGeneralCallbackData(data) {
20
+ return typeof data === "string" && data.startsWith(GENERAL_CALLBACK_PREFIX);
21
+ }
22
+ /**
23
+ * Strip the general prefix, returning the app-defined token the agent set.
24
+ * Returns "" when the input is not a general callback.
25
+ */
26
+ export function decodeGeneralCallbackData(data) {
27
+ if (!isGeneralCallbackData(data))
28
+ return "";
29
+ return data.slice(GENERAL_CALLBACK_PREFIX.length);
30
+ }
31
+ //# sourceMappingURL=general-callback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"general-callback.js","sourceRoot":"","sources":["../../../src/agents/channels/general-callback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,gFAAgF;AAChF,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C,kFAAkF;AAClF,MAAM,UAAU,qBAAqB,CAAC,IAAwB;IAC7D,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,uBAAuB,CAAC,CAAC;AAC7E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAwB;IACjE,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAC5C,OAAQ,IAAe,CAAC,KAAK,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC;AAC/D,CAAC"}
@@ -37,6 +37,15 @@ export type RunChannelTurnFn = (args: {
37
37
  signal?: AbortSignal;
38
38
  senderIsOwner?: boolean;
39
39
  channelApprovalRoute?: ChannelApprovalRoute;
40
+ /**
41
+ * OPTIONAL live-streaming delta sink. When the pipeline wants progressive
42
+ * delivery (the adapter advertises `beginReplyStream` AND the channel is
43
+ * configured to stream), it passes this; the gateway forwards the
44
+ * accumulating answer text on each model update. Undefined → final-only
45
+ * delivery (unchanged). The final reply is ALWAYS still returned, so the
46
+ * non-streaming fallback stays authoritative.
47
+ */
48
+ onReplyDelta?: (accumulatedText: string) => void;
40
49
  }) => Promise<ChannelTurnResult>;
41
50
  /** Pending debounce slot — accumulated text waiting to dispatch. */
42
51
  export interface PendingDispatch {
@@ -1 +1 @@
1
- {"version":3,"file":"inbound-pipeline.d.ts","sourceRoot":"","sources":["../../../src/agents/channels/inbound-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAa7F,OAAO,EACN,KAAK,oBAAoB,EAGzB,MAAM,sBAAsB,CAAC;AA4J9B,2EAA2E;AAC3E,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,MAAM,CAAC;CACd;AAED,0EAA0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;CAC5C,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEjC,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;IACrC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,oBAAoB,EAAE,oBAAoB,CAAC;CAC3C;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,EAAE,CA8E9E;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAIT;AAED,gFAAgF;AAChF,MAAM,WAAW,sBAAsB;IACtC,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACxC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAChD,8EAA8E;IAC9E,WAAW,CAAC,EAAE,WAAW,CAAC;CAC1B;AA4BD;;;GAGG;AACH,wBAAsB,yBAAyB,CAC9C,GAAG,EAAE,sBAAsB,EAC3B,GAAG,EAAE,cAAc,GACjB,OAAO,CAAC,IAAI,CAAC,CA+gBf;AAED,kDAAkD;AAClD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE;IAClD,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACzC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC1B,GAAG,sBAAsB,CAWzB"}
1
+ {"version":3,"file":"inbound-pipeline.d.ts","sourceRoot":"","sources":["../../../src/agents/channels/inbound-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAoB7F,OAAO,EACN,KAAK,oBAAoB,EAGzB,MAAM,sBAAsB,CAAC;AAsK9B,2EAA2E;AAC3E,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,MAAM,CAAC;CACd;AAED,0EAA0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEjC,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;IACrC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,oBAAoB,EAAE,oBAAoB,CAAC;CAC3C;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,EAAE,CA8J9E;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAIT;AAED,gFAAgF;AAChF,MAAM,WAAW,sBAAsB;IACtC,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACxC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAChD,8EAA8E;IAC9E,WAAW,CAAC,EAAE,WAAW,CAAC;CAC1B;AAyCD;;;GAGG;AACH,wBAAsB,yBAAyB,CAC9C,GAAG,EAAE,sBAAsB,EAC3B,GAAG,EAAE,cAAc,GACjB,OAAO,CAAC,IAAI,CAAC,CAwzBf;AAED,kDAAkD;AAClD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE;IAClD,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACzC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC1B,GAAG,sBAAsB,CAWzB"}