@vellumai/assistant 0.4.26 → 0.4.30

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 (1360) hide show
  1. package/.env.example +2 -2
  2. package/AGENTS.md +5 -0
  3. package/ARCHITECTURE.md +207 -105
  4. package/Dockerfile +1 -1
  5. package/README.md +111 -113
  6. package/bun.lock +0 -3
  7. package/docs/architecture/integrations.md +0 -1
  8. package/docs/architecture/memory.md +100 -63
  9. package/docs/error-handling.md +71 -0
  10. package/docs/runbook-trusted-contacts.md +89 -52
  11. package/docs/trusted-contact-access.md +48 -46
  12. package/package.json +3 -3
  13. package/scripts/compare-benchmarks.sh +12 -5
  14. package/scripts/ipc/check-swift-decoder-drift.ts +5 -3
  15. package/scripts/test.sh +89 -5
  16. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +50 -37
  17. package/src/__tests__/access-request-decision.test.ts +0 -1
  18. package/src/__tests__/account-registry.test.ts +1 -1
  19. package/src/__tests__/actor-token-service.test.ts +40 -26
  20. package/src/__tests__/agent-loop-thinking.test.ts +29 -13
  21. package/src/__tests__/agent-loop.test.ts +2 -1
  22. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -1
  23. package/src/__tests__/app-executors.test.ts +7 -17
  24. package/src/__tests__/approval-routes-http.test.ts +2 -2
  25. package/src/__tests__/asset-materialize-tool.test.ts +7 -7
  26. package/src/__tests__/asset-search-tool.test.ts +7 -7
  27. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -10
  28. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  29. package/src/__tests__/browser-skill-endstate.test.ts +10 -1
  30. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +218 -0
  31. package/src/__tests__/call-controller.test.ts +99 -69
  32. package/src/__tests__/call-start-guardian-guard.test.ts +1 -1
  33. package/src/__tests__/channel-approval-routes.test.ts +157 -114
  34. package/src/__tests__/channel-approval.test.ts +8 -0
  35. package/src/__tests__/channel-approvals.test.ts +39 -1
  36. package/src/__tests__/channel-guardian.test.ts +176 -275
  37. package/src/__tests__/channel-readiness-service.test.ts +6 -2
  38. package/src/__tests__/channel-reply-delivery.test.ts +33 -2
  39. package/src/__tests__/channel-retry-sweep.test.ts +14 -14
  40. package/src/__tests__/checker.test.ts +12 -31
  41. package/src/__tests__/claude-code-tool-profiles.test.ts +1 -1
  42. package/src/__tests__/commit-message-enrichment-service.test.ts +71 -59
  43. package/src/__tests__/compaction.benchmark.test.ts +6 -2
  44. package/src/__tests__/computer-use-tools.test.ts +1 -1
  45. package/src/__tests__/config-schema.test.ts +66 -7
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -29
  47. package/src/__tests__/contacts-tools.test.ts +63 -2
  48. package/src/__tests__/context-overflow-approval.test.ts +141 -0
  49. package/src/__tests__/context-overflow-policy.test.ts +171 -0
  50. package/src/__tests__/context-overflow-reducer.test.ts +533 -0
  51. package/src/__tests__/context-window-manager.test.ts +97 -0
  52. package/src/__tests__/conversation-attention-telegram.test.ts +38 -46
  53. package/src/__tests__/conversation-pairing.test.ts +2 -2
  54. package/src/__tests__/conversation-routes-guardian-reply.test.ts +214 -10
  55. package/src/__tests__/conversation-routes.test.ts +4 -7
  56. package/src/__tests__/credential-broker-browser-fill.test.ts +13 -2
  57. package/src/__tests__/credential-security-e2e.test.ts +1 -1
  58. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  59. package/src/__tests__/credential-vault-unit.test.ts +1 -1
  60. package/src/__tests__/credential-vault.test.ts +11 -8
  61. package/src/__tests__/daemon-lifecycle.test.ts +2 -2
  62. package/src/__tests__/daemon-server-session-init.test.ts +6 -6
  63. package/src/__tests__/delete-managed-skill-tool.test.ts +1 -1
  64. package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -2
  65. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +9 -0
  66. package/src/__tests__/emit-signal-routing-intent.test.ts +4 -0
  67. package/src/__tests__/encrypted-store.test.ts +10 -7
  68. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  69. package/src/__tests__/file-edit-tool.test.ts +1 -1
  70. package/src/__tests__/file-read-tool.test.ts +1 -1
  71. package/src/__tests__/file-write-tool.test.ts +1 -1
  72. package/src/__tests__/fixtures/credential-security-fixtures.ts +87 -64
  73. package/src/__tests__/fixtures/media-reuse-fixtures.ts +37 -31
  74. package/src/__tests__/fixtures/mock-signup-server.ts +171 -115
  75. package/src/__tests__/fixtures/proxy-fixtures.ts +39 -39
  76. package/src/__tests__/followup-tools.test.ts +1 -1
  77. package/src/__tests__/gateway-only-guard.test.ts +4 -0
  78. package/src/__tests__/gemini-image-service.test.ts +2 -2
  79. package/src/__tests__/guardian-actions-endpoint.test.ts +543 -1
  80. package/src/__tests__/guardian-control-plane-policy.test.ts +15 -15
  81. package/src/__tests__/guardian-dispatch.test.ts +79 -1
  82. package/src/__tests__/guardian-grant-minting.test.ts +20 -20
  83. package/src/__tests__/guardian-outbound-http.test.ts +1 -2
  84. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -41
  85. package/src/__tests__/guardian-routing-invariants.test.ts +36 -16
  86. package/src/__tests__/guardian-routing-state.test.ts +36 -52
  87. package/src/__tests__/guardian-verification-intent-routing.test.ts +4 -6
  88. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +6 -8
  89. package/src/__tests__/handle-user-message-secret-resume.test.ts +39 -1
  90. package/src/__tests__/handlers-cu-observation-blob.test.ts +21 -10
  91. package/src/__tests__/handlers-telegram-config.test.ts +14 -14
  92. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +23 -2
  93. package/src/__tests__/headless-browser-interactions.test.ts +1 -1
  94. package/src/__tests__/headless-browser-navigate.test.ts +1 -1
  95. package/src/__tests__/headless-browser-read-tools.test.ts +1 -1
  96. package/src/__tests__/headless-browser-snapshot.test.ts +1 -1
  97. package/src/__tests__/heartbeat-service.test.ts +45 -2
  98. package/src/__tests__/host-file-edit-tool.test.ts +1 -1
  99. package/src/__tests__/host-file-read-tool.test.ts +1 -1
  100. package/src/__tests__/host-file-write-tool.test.ts +1 -1
  101. package/src/__tests__/host-shell-tool.test.ts +1 -1
  102. package/src/__tests__/inbound-invite-redemption.test.ts +17 -19
  103. package/src/__tests__/ingress-reconcile.test.ts +2 -2
  104. package/src/__tests__/integrations-cli.test.ts +232 -0
  105. package/src/__tests__/intent-routing.test.ts +7 -5
  106. package/src/__tests__/invite-redemption-service.test.ts +5 -4
  107. package/src/__tests__/{ingress-routes-http.test.ts → invite-routes-http.test.ts} +42 -321
  108. package/src/__tests__/ipc-snapshot.test.ts +32 -31
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
  110. package/src/__tests__/mcp-cli.test.ts +136 -57
  111. package/src/__tests__/mcp-client-auth.test.ts +95 -0
  112. package/src/__tests__/media-generate-image.test.ts +2 -2
  113. package/src/__tests__/media-reuse-story.e2e.test.ts +8 -8
  114. package/src/__tests__/memory-regressions.test.ts +6 -6
  115. package/src/__tests__/messaging-send-tool.test.ts +1 -1
  116. package/src/__tests__/migration-cross-version-compatibility.test.ts +1855 -0
  117. package/src/__tests__/migration-export-http.test.ts +540 -0
  118. package/src/__tests__/migration-import-commit-http.test.ts +823 -0
  119. package/src/__tests__/migration-import-preflight-http.test.ts +755 -0
  120. package/src/__tests__/migration-parity-persistence.test.ts +1854 -0
  121. package/src/__tests__/migration-transport.test.ts +904 -0
  122. package/src/__tests__/migration-validate-http.test.ts +698 -0
  123. package/src/__tests__/migration-wizard.test.ts +1289 -0
  124. package/src/__tests__/nl-approval-parser.test.ts +305 -0
  125. package/src/__tests__/non-member-access-request.test.ts +17 -17
  126. package/src/__tests__/notification-decision-strategy.test.ts +110 -2
  127. package/src/__tests__/notification-deep-link.test.ts +18 -0
  128. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  129. package/src/__tests__/oauth-provider-profiles.test.ts +34 -0
  130. package/src/__tests__/oauth2-gateway-transport.test.ts +1 -1
  131. package/src/__tests__/playbook-execution.test.ts +1 -1
  132. package/src/__tests__/playbook-tools.test.ts +1 -1
  133. package/src/__tests__/provider-error-scenarios.test.ts +68 -0
  134. package/src/__tests__/provider-streaming.benchmark.test.ts +3 -1
  135. package/src/__tests__/proxy-approval-callback.test.ts +1 -1
  136. package/src/__tests__/qdrant-manager.test.ts +40 -11
  137. package/src/__tests__/rebind-secrets-screen.test.ts +839 -0
  138. package/src/__tests__/recording-handler.test.ts +2 -2
  139. package/src/__tests__/recording-intent-handler.test.ts +3 -3
  140. package/src/__tests__/recording-state-machine.test.ts +2 -2
  141. package/src/__tests__/relay-server.test.ts +507 -228
  142. package/src/__tests__/reminder-store.test.ts +8 -0
  143. package/src/__tests__/reminder.test.ts +8 -0
  144. package/src/__tests__/{resolve-guardian-trust-class.test.ts → resolve-trust-class.test.ts} +11 -17
  145. package/src/__tests__/retry-after-extraction.test.ts +111 -0
  146. package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
  147. package/src/__tests__/schedule-tools.test.ts +1 -1
  148. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  149. package/src/__tests__/script-proxy-connect-tunnel.test.ts +2 -3
  150. package/src/__tests__/script-proxy-decision-trace.test.ts +2 -2
  151. package/src/__tests__/script-proxy-http-forwarder.test.ts +1 -1
  152. package/src/__tests__/script-proxy-injection-runtime.test.ts +5 -5
  153. package/src/__tests__/script-proxy-mitm-handler.test.ts +4 -4
  154. package/src/__tests__/script-proxy-policy-runtime.test.ts +2 -2
  155. package/src/__tests__/script-proxy-policy.test.ts +2 -2
  156. package/src/__tests__/script-proxy-profile-template-fallback.test.ts +127 -0
  157. package/src/__tests__/script-proxy-session-manager.test.ts +4 -7
  158. package/src/__tests__/script-proxy-session-runtime.test.ts +1 -6
  159. package/src/__tests__/secret-onetime-send.test.ts +4 -4
  160. package/src/__tests__/secret-scanner-executor.test.ts +2 -2
  161. package/src/__tests__/send-endpoint-busy.test.ts +11 -9
  162. package/src/__tests__/send-notification-tool.test.ts +2 -2
  163. package/src/__tests__/session-abort-tool-results.test.ts +17 -2
  164. package/src/__tests__/session-agent-loop.test.ts +456 -35
  165. package/src/__tests__/session-confirmation-signals.test.ts +3 -2
  166. package/src/__tests__/session-conflict-gate.test.ts +20 -3
  167. package/src/__tests__/session-init.benchmark.test.ts +2 -2
  168. package/src/__tests__/session-load-history-repair.test.ts +7 -7
  169. package/src/__tests__/session-media-retry.test.ts +147 -0
  170. package/src/__tests__/session-pre-run-repair.test.ts +17 -2
  171. package/src/__tests__/session-profile-injection.test.ts +20 -3
  172. package/src/__tests__/session-provider-retry-repair.test.ts +86 -6
  173. package/src/__tests__/session-queue.test.ts +33 -18
  174. package/src/__tests__/session-runtime-assembly.test.ts +147 -1
  175. package/src/__tests__/session-runtime-workspace.test.ts +40 -0
  176. package/src/__tests__/session-slash-known.test.ts +21 -3
  177. package/src/__tests__/session-slash-queue.test.ts +17 -2
  178. package/src/__tests__/session-slash-unknown.test.ts +17 -2
  179. package/src/__tests__/session-surfaces-deselection.test.ts +208 -0
  180. package/src/__tests__/session-workspace-cache-state.test.ts +2 -2
  181. package/src/__tests__/session-workspace-injection.test.ts +17 -2
  182. package/src/__tests__/session-workspace-tool-tracking.test.ts +17 -2
  183. package/src/__tests__/shell-credential-ref.test.ts +1 -1
  184. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
  185. package/src/__tests__/skill-feature-flags-integration.test.ts +9 -5
  186. package/src/__tests__/skill-feature-flags.test.ts +18 -12
  187. package/src/__tests__/skill-load-feature-flag.test.ts +5 -4
  188. package/src/__tests__/skill-load-tool.test.ts +1 -1
  189. package/src/__tests__/skill-script-runner-host.test.ts +1 -1
  190. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -1
  191. package/src/__tests__/skill-script-runner.test.ts +1 -1
  192. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  193. package/src/__tests__/slack-block-formatting.test.ts +100 -0
  194. package/src/__tests__/slack-inbound-verification.test.ts +346 -0
  195. package/src/__tests__/slack-reaction-approvals.test.ts +77 -0
  196. package/src/__tests__/slack-skill.test.ts +4 -2
  197. package/src/__tests__/starter-task-flow.test.ts +0 -1
  198. package/src/__tests__/subagent-tools.test.ts +3 -3
  199. package/src/__tests__/swarm-recursion.test.ts +1 -1
  200. package/src/__tests__/swarm-session-integration.test.ts +1 -1
  201. package/src/__tests__/swarm-tool.test.ts +1 -1
  202. package/src/__tests__/task-management-tools.test.ts +1 -1
  203. package/src/__tests__/task-tools.test.ts +1 -1
  204. package/src/__tests__/terminal-tools.test.ts +1 -1
  205. package/src/__tests__/test-support/browser-skill-harness.ts +39 -27
  206. package/src/__tests__/test-support/computer-use-skill-harness.ts +14 -14
  207. package/src/__tests__/tool-approval-handler.test.ts +15 -15
  208. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
  209. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  210. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
  211. package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
  212. package/src/__tests__/tool-executor.test.ts +23 -182
  213. package/src/__tests__/tool-grant-request-escalation.test.ts +11 -11
  214. package/src/__tests__/tool-permission-simulate-handler.test.ts +4 -4
  215. package/src/__tests__/transfer-progress-screen.test.ts +1180 -0
  216. package/src/__tests__/trust-context-guards.test.ts +25 -29
  217. package/src/__tests__/trusted-contact-approval-notifier.test.ts +23 -21
  218. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +37 -40
  219. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +29 -25
  220. package/src/__tests__/trusted-contact-multichannel.test.ts +25 -24
  221. package/src/__tests__/trusted-contact-verification.test.ts +64 -76
  222. package/src/__tests__/turn-commit.test.ts +18 -18
  223. package/src/__tests__/twilio-provider.test.ts +7 -7
  224. package/src/__tests__/validation-results-screen.test.ts +1107 -0
  225. package/src/__tests__/view-image-tool.test.ts +1 -1
  226. package/src/__tests__/voice-invite-redemption.test.ts +4 -3
  227. package/src/__tests__/voice-scoped-grant-consumer.test.ts +12 -12
  228. package/src/__tests__/voice-session-bridge.test.ts +24 -24
  229. package/src/agent/attachments.ts +3 -1
  230. package/src/agent/loop.ts +13 -13
  231. package/src/agent/message-types.ts +13 -7
  232. package/src/amazon/cart.ts +59 -32
  233. package/src/amazon/checkout.ts +25 -14
  234. package/src/amazon/client.ts +61 -58
  235. package/src/amazon/product-details.ts +3 -3
  236. package/src/amazon/request-extractor.ts +46 -31
  237. package/src/amazon/search.ts +6 -4
  238. package/src/amazon/session.ts +33 -24
  239. package/src/approvals/AGENTS.md +26 -0
  240. package/src/approvals/approval-primitive.ts +87 -64
  241. package/src/approvals/guardian-decision-primitive.ts +172 -81
  242. package/src/approvals/guardian-request-resolvers.ts +262 -155
  243. package/src/autonomy/autonomy-resolver.ts +7 -5
  244. package/src/autonomy/autonomy-store.ts +34 -19
  245. package/src/autonomy/disposition-mapper.ts +5 -5
  246. package/src/autonomy/index.ts +6 -6
  247. package/src/autonomy/types.ts +7 -3
  248. package/src/browser-extension-relay/client.ts +50 -19
  249. package/src/browser-extension-relay/protocol.ts +11 -11
  250. package/src/browser-extension-relay/server.ts +45 -20
  251. package/src/bundler/app-bundler.ts +75 -50
  252. package/src/bundler/bundle-scanner.ts +145 -41
  253. package/src/bundler/bundle-signer.ts +16 -14
  254. package/src/bundler/signature-verifier.ts +36 -33
  255. package/src/calls/call-constants.ts +10 -3
  256. package/src/calls/call-controller.ts +473 -214
  257. package/src/calls/call-conversation-messages.ts +25 -15
  258. package/src/calls/call-domain.ts +401 -148
  259. package/src/calls/call-pointer-message-composer.ts +26 -21
  260. package/src/calls/call-pointer-messages.ts +52 -28
  261. package/src/calls/call-recovery.ts +53 -37
  262. package/src/calls/call-state-machine.ts +37 -7
  263. package/src/calls/call-state.ts +35 -13
  264. package/src/calls/call-store.ts +165 -77
  265. package/src/calls/elevenlabs-client.ts +39 -20
  266. package/src/calls/guardian-action-sweep.ts +42 -24
  267. package/src/calls/guardian-dispatch.ts +79 -56
  268. package/src/calls/guardian-question-copy.ts +28 -23
  269. package/src/calls/relay-server.ts +1149 -532
  270. package/src/calls/speaker-identification.ts +21 -15
  271. package/src/calls/twilio-config.ts +34 -17
  272. package/src/calls/twilio-provider.ts +108 -55
  273. package/src/calls/twilio-rest.ts +212 -100
  274. package/src/calls/twilio-routes.ts +165 -92
  275. package/src/calls/types.ts +55 -7
  276. package/src/calls/voice-quality.ts +6 -4
  277. package/src/calls/voice-session-bridge.ts +181 -133
  278. package/src/channels/config.ts +18 -14
  279. package/src/channels/types.ts +38 -10
  280. package/src/cli/amazon.ts +333 -227
  281. package/src/cli/config-commands.ts +236 -146
  282. package/src/cli/core-commands.ts +403 -329
  283. package/src/cli/email-guardrails.ts +38 -19
  284. package/src/cli/email.ts +207 -153
  285. package/src/cli/influencer.ts +58 -56
  286. package/src/cli/integrations.ts +306 -0
  287. package/src/cli/ipc-client.ts +24 -19
  288. package/src/cli/map.ts +176 -129
  289. package/src/cli/mcp.ts +260 -152
  290. package/src/cli/sequence.ts +165 -107
  291. package/src/cli/twitter.ts +302 -218
  292. package/src/cli.ts +418 -279
  293. package/src/commands/cc-command-registry.ts +52 -27
  294. package/src/config/agent-schema.ts +217 -134
  295. package/src/config/assistant-feature-flags.ts +23 -18
  296. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +19 -0
  297. package/src/config/bundled-skills/app-builder/SKILL.md +193 -1500
  298. package/src/config/bundled-skills/app-builder/TOOLS.json +70 -18
  299. package/src/config/bundled-skills/app-builder/tools/app-create.ts +7 -4
  300. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +6 -3
  301. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +7 -4
  302. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +6 -3
  303. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +6 -3
  304. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +7 -4
  305. package/src/config/bundled-skills/app-builder/tools/app-list.ts +6 -3
  306. package/src/config/bundled-skills/app-builder/tools/app-query.ts +6 -3
  307. package/src/config/bundled-skills/app-builder/tools/app-update.ts +6 -3
  308. package/src/config/bundled-skills/browser/TOOLS.json +59 -2
  309. package/src/config/bundled-skills/browser/tools/browser-click.ts +5 -2
  310. package/src/config/bundled-skills/browser/tools/browser-close.ts +5 -2
  311. package/src/config/bundled-skills/browser/tools/browser-extract.ts +5 -2
  312. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +5 -2
  313. package/src/config/bundled-skills/browser/tools/browser-hover.ts +5 -2
  314. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +5 -2
  315. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +5 -2
  316. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +5 -2
  317. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +5 -2
  318. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +5 -2
  319. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +5 -2
  320. package/src/config/bundled-skills/browser/tools/browser-type.ts +5 -2
  321. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +13 -6
  322. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +5 -2
  323. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +4 -0
  324. package/src/config/bundled-skills/claude-code/TOOLS.json +4 -0
  325. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +5 -2
  326. package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
  327. package/src/config/bundled-skills/computer-use/TOOLS.json +50 -2
  328. package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +6 -3
  329. package/src/config/bundled-skills/computer-use/tools/computer-use-done.ts +6 -3
  330. package/src/config/bundled-skills/computer-use/tools/computer-use-double-click.ts +10 -3
  331. package/src/config/bundled-skills/computer-use/tools/computer-use-drag.ts +6 -3
  332. package/src/config/bundled-skills/computer-use/tools/computer-use-key.ts +6 -3
  333. package/src/config/bundled-skills/computer-use/tools/computer-use-open-app.ts +6 -3
  334. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +10 -3
  335. package/src/config/bundled-skills/computer-use/tools/computer-use-respond.ts +6 -3
  336. package/src/config/bundled-skills/computer-use/tools/computer-use-right-click.ts +10 -3
  337. package/src/config/bundled-skills/computer-use/tools/computer-use-run-applescript.ts +10 -3
  338. package/src/config/bundled-skills/computer-use/tools/computer-use-scroll.ts +6 -3
  339. package/src/config/bundled-skills/computer-use/tools/computer-use-type-text.ts +6 -3
  340. package/src/config/bundled-skills/computer-use/tools/computer-use-wait.ts +6 -3
  341. package/src/config/bundled-skills/configure-settings/SKILL.md +28 -14
  342. package/src/config/bundled-skills/contacts/SKILL.md +453 -15
  343. package/src/config/bundled-skills/contacts/TOOLS.json +22 -2
  344. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +79 -20
  345. package/src/config/bundled-skills/contacts/tools/contact-search.ts +55 -18
  346. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +64 -19
  347. package/src/config/bundled-skills/document/TOOLS.json +8 -0
  348. package/src/config/bundled-skills/document/tools/document-create.ts +5 -2
  349. package/src/config/bundled-skills/document/tools/document-update.ts +5 -2
  350. package/src/config/bundled-skills/doordash/doordash-cli.ts +17 -7
  351. package/src/config/bundled-skills/email-setup/SKILL.md +12 -9
  352. package/src/config/bundled-skills/followups/TOOLS.json +12 -0
  353. package/src/config/bundled-skills/followups/tools/followup-create.ts +5 -2
  354. package/src/config/bundled-skills/followups/tools/followup-list.ts +5 -2
  355. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +5 -2
  356. package/src/config/bundled-skills/google-calendar/TOOLS.json +124 -26
  357. package/src/config/bundled-skills/google-calendar/calendar-client.ts +44 -32
  358. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +11 -5
  359. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +13 -7
  360. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +11 -5
  361. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +13 -7
  362. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +28 -12
  363. package/src/config/bundled-skills/google-calendar/tools/shared.ts +6 -4
  364. package/src/config/bundled-skills/google-calendar/types.ts +3 -3
  365. package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +88 -33
  366. package/src/config/bundled-skills/image-studio/TOOLS.json +12 -2
  367. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +48 -25
  368. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +13 -3
  369. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +60 -35
  370. package/src/config/bundled-skills/mcp-setup/SKILL.md +75 -0
  371. package/src/config/bundled-skills/media-processing/SKILL.md +55 -15
  372. package/src/config/bundled-skills/media-processing/TOOLS.json +48 -2
  373. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +12 -10
  374. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +34 -19
  375. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +82 -66
  376. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +148 -0
  377. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +1 -1
  378. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +8 -3
  379. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +117 -53
  380. package/src/config/bundled-skills/media-processing/services/gemini-video.ts +273 -0
  381. package/src/config/bundled-skills/media-processing/services/preprocess.ts +185 -97
  382. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +32 -27
  383. package/src/config/bundled-skills/media-processing/services/reduce.ts +101 -24
  384. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +121 -55
  385. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +58 -24
  386. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +198 -92
  387. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +98 -70
  388. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +59 -19
  389. package/src/config/bundled-skills/media-processing/tools/media-status.ts +26 -10
  390. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +29 -14
  391. package/src/config/bundled-skills/messaging/SKILL.md +7 -5
  392. package/src/config/bundled-skills/messaging/TOOLS.json +232 -186
  393. package/src/config/bundled-skills/messaging/tools/gmail-archive-by-query.ts +31 -13
  394. package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +16 -10
  395. package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +18 -9
  396. package/src/config/bundled-skills/messaging/tools/gmail-download-attachment.ts +23 -16
  397. package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +28 -12
  398. package/src/config/bundled-skills/messaging/tools/gmail-filters.ts +41 -21
  399. package/src/config/bundled-skills/messaging/tools/gmail-follow-up.ts +44 -23
  400. package/src/config/bundled-skills/messaging/tools/gmail-forward.ts +73 -33
  401. package/src/config/bundled-skills/messaging/tools/gmail-label.ts +15 -9
  402. package/src/config/bundled-skills/messaging/tools/gmail-list-attachments.ts +22 -14
  403. package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +99 -50
  404. package/src/config/bundled-skills/messaging/tools/gmail-send-draft.ts +14 -8
  405. package/src/config/bundled-skills/messaging/tools/gmail-send-with-attachments.ts +63 -44
  406. package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +90 -46
  407. package/src/config/bundled-skills/messaging/tools/gmail-summarize-thread.ts +43 -22
  408. package/src/config/bundled-skills/messaging/tools/gmail-trash.ts +15 -9
  409. package/src/config/bundled-skills/messaging/tools/gmail-triage.ts +51 -22
  410. package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +62 -26
  411. package/src/config/bundled-skills/messaging/tools/gmail-vacation.ts +34 -19
  412. package/src/config/bundled-skills/messaging/tools/google-contacts.ts +32 -16
  413. package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +10 -4
  414. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +91 -47
  415. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +21 -9
  416. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +9 -3
  417. package/src/config/bundled-skills/messaging/tools/messaging-draft.ts +30 -17
  418. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +10 -4
  419. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +14 -6
  420. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +16 -5
  421. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +63 -36
  422. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +10 -4
  423. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +30 -12
  424. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +48 -29
  425. package/src/config/bundled-skills/messaging/tools/scan-result-store.ts +20 -6
  426. package/src/config/bundled-skills/messaging/tools/send-notification.ts +1 -1
  427. package/src/config/bundled-skills/messaging/tools/sequence-analytics.ts +59 -22
  428. package/src/config/bundled-skills/messaging/tools/sequence-cancel.ts +13 -7
  429. package/src/config/bundled-skills/messaging/tools/sequence-create.ts +27 -12
  430. package/src/config/bundled-skills/messaging/tools/sequence-delete.ts +14 -6
  431. package/src/config/bundled-skills/messaging/tools/sequence-enroll.ts +30 -11
  432. package/src/config/bundled-skills/messaging/tools/sequence-enrollment-list.ts +16 -8
  433. package/src/config/bundled-skills/messaging/tools/sequence-get.ts +31 -13
  434. package/src/config/bundled-skills/messaging/tools/sequence-import.ts +38 -22
  435. package/src/config/bundled-skills/messaging/tools/sequence-list.ts +16 -7
  436. package/src/config/bundled-skills/messaging/tools/sequence-pause.ts +29 -10
  437. package/src/config/bundled-skills/messaging/tools/sequence-resume.ts +16 -8
  438. package/src/config/bundled-skills/messaging/tools/sequence-update.ts +35 -16
  439. package/src/config/bundled-skills/messaging/tools/shared.ts +26 -12
  440. package/src/config/bundled-skills/notifications/SKILL.md +3 -2
  441. package/src/config/bundled-skills/notifications/TOOLS.json +7 -13
  442. package/src/config/bundled-skills/notifications/tools/send-notification.ts +69 -34
  443. package/src/config/bundled-skills/notifications/tools/shared.ts +1 -1
  444. package/src/config/bundled-skills/phone-calls/SKILL.md +46 -48
  445. package/src/config/bundled-skills/phone-calls/TOOLS.json +13 -1
  446. package/src/config/bundled-skills/phone-calls/tools/call-end.ts +1 -1
  447. package/src/config/bundled-skills/phone-calls/tools/call-start.ts +1 -1
  448. package/src/config/bundled-skills/phone-calls/tools/call-status.ts +1 -1
  449. package/src/config/bundled-skills/playbooks/TOOLS.json +16 -0
  450. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +91 -51
  451. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +30 -16
  452. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +66 -27
  453. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +89 -42
  454. package/src/config/bundled-skills/public-ingress/SKILL.md +26 -19
  455. package/src/config/bundled-skills/reminder/TOOLS.json +15 -2
  456. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +5 -2
  457. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +5 -2
  458. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +5 -2
  459. package/src/config/bundled-skills/schedule/SKILL.md +33 -15
  460. package/src/config/bundled-skills/schedule/TOOLS.json +17 -1
  461. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +5 -2
  462. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +5 -2
  463. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +5 -2
  464. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +5 -2
  465. package/src/config/bundled-skills/screen-recording/SKILL.md +11 -3
  466. package/src/config/bundled-skills/self-upgrade/SKILL.md +9 -8
  467. package/src/config/bundled-skills/slack/SKILL.md +30 -1
  468. package/src/config/bundled-skills/slack/TOOLS.json +122 -17
  469. package/src/config/bundled-skills/slack/tools/shared.ts +7 -5
  470. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +11 -5
  471. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +11 -5
  472. package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +146 -0
  473. package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +46 -16
  474. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +11 -5
  475. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +28 -0
  476. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +12 -6
  477. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +120 -0
  478. package/src/config/bundled-skills/slack-app-setup/SKILL.md +200 -0
  479. package/src/config/bundled-skills/sms-setup/SKILL.md +5 -8
  480. package/src/config/bundled-skills/subagent/TOOLS.json +22 -2
  481. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +5 -2
  482. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +5 -2
  483. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +5 -2
  484. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +5 -2
  485. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +5 -2
  486. package/src/config/bundled-skills/tasks/TOOLS.json +86 -14
  487. package/src/config/bundled-skills/tasks/tools/task-delete.ts +5 -2
  488. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +5 -2
  489. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +5 -2
  490. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +5 -2
  491. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +5 -2
  492. package/src/config/bundled-skills/tasks/tools/task-list.ts +5 -2
  493. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +5 -2
  494. package/src/config/bundled-skills/tasks/tools/task-run.ts +5 -2
  495. package/src/config/bundled-skills/tasks/tools/task-save.ts +5 -2
  496. package/src/config/bundled-skills/telegram-setup/SKILL.md +7 -8
  497. package/src/config/bundled-skills/transcribe/TOOLS.json +4 -0
  498. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +232 -127
  499. package/src/config/bundled-skills/twilio-setup/SKILL.md +7 -12
  500. package/src/config/bundled-skills/twitter/SKILL.md +19 -2
  501. package/src/config/bundled-skills/voice-setup/SKILL.md +5 -5
  502. package/src/config/bundled-skills/watcher/TOOLS.json +20 -0
  503. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +5 -2
  504. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +5 -2
  505. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +5 -2
  506. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +5 -2
  507. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +5 -2
  508. package/src/config/bundled-skills/weather/TOOLS.json +4 -0
  509. package/src/config/bundled-skills/weather/tools/get-weather.ts +5 -2
  510. package/src/config/bundled-tool-registry.ts +2 -0
  511. package/src/config/calls-schema.ts +108 -63
  512. package/src/config/channel-permission-profiles.ts +155 -0
  513. package/src/config/computer-use-prompt.ts +7 -7
  514. package/src/config/core-schema.ts +239 -155
  515. package/src/config/defaults.ts +2 -2
  516. package/src/config/elevenlabs-schema.ts +15 -15
  517. package/src/config/env-registry.ts +33 -33
  518. package/src/config/env.ts +4 -1
  519. package/src/config/feature-flag-registry.json +31 -7
  520. package/src/config/loader.ts +118 -58
  521. package/src/config/mcp-schema.ts +29 -15
  522. package/src/config/memory-schema.ts +434 -229
  523. package/src/config/notifications-schema.ts +4 -4
  524. package/src/config/sandbox-schema.ts +2 -2
  525. package/src/config/schema.ts +12 -2
  526. package/src/config/skill-state.ts +27 -15
  527. package/src/config/skills-schema.ts +72 -23
  528. package/src/config/skills.ts +303 -143
  529. package/src/config/system-prompt.ts +25 -6
  530. package/src/config/types.ts +1 -1
  531. package/src/config/update-bulletin-format.ts +3 -3
  532. package/src/config/update-bulletin-state.ts +15 -6
  533. package/src/config/update-bulletin-template-path.ts +8 -4
  534. package/src/config/update-bulletin.ts +33 -14
  535. package/src/config/user-reference.ts +8 -8
  536. package/src/contacts/contact-events.ts +21 -0
  537. package/src/contacts/contact-store.ts +813 -100
  538. package/src/contacts/contacts-write.ts +287 -0
  539. package/src/contacts/index.ts +13 -4
  540. package/src/contacts/startup-migration.ts +21 -0
  541. package/src/contacts/types.ts +73 -2
  542. package/src/context/token-estimator.ts +54 -31
  543. package/src/context/tool-result-truncation.ts +41 -7
  544. package/src/context/window-manager.ts +225 -120
  545. package/src/daemon/approval-generators.ts +83 -55
  546. package/src/daemon/approved-devices-store.ts +33 -20
  547. package/src/daemon/assistant-attachments.ts +157 -101
  548. package/src/daemon/auth-manager.ts +17 -15
  549. package/src/daemon/classifier.ts +117 -46
  550. package/src/daemon/computer-use-session.ts +316 -187
  551. package/src/daemon/config-watcher.ts +91 -44
  552. package/src/daemon/connection-policy.ts +18 -10
  553. package/src/daemon/context-overflow-approval.ts +48 -0
  554. package/src/daemon/context-overflow-policy.ts +50 -0
  555. package/src/daemon/context-overflow-reducer.ts +300 -0
  556. package/src/daemon/daemon-control.ts +79 -51
  557. package/src/daemon/date-context.ts +119 -69
  558. package/src/daemon/dictation-profile-store.ts +94 -48
  559. package/src/daemon/dictation-text-processing.ts +33 -12
  560. package/src/daemon/doordash-steps.ts +92 -49
  561. package/src/daemon/guardian-action-generators.ts +62 -46
  562. package/src/daemon/guardian-verification-intent.ts +35 -19
  563. package/src/daemon/handlers/apps.ts +258 -113
  564. package/src/daemon/handlers/avatar.ts +20 -15
  565. package/src/daemon/handlers/computer-use.ts +82 -39
  566. package/src/daemon/handlers/config-channels.ts +146 -69
  567. package/src/daemon/handlers/config-heartbeat.ts +114 -59
  568. package/src/daemon/handlers/config-inbox.ts +213 -160
  569. package/src/daemon/handlers/config-ingress.ts +127 -55
  570. package/src/daemon/handlers/config-integrations.ts +145 -88
  571. package/src/daemon/handlers/config-model.ts +58 -22
  572. package/src/daemon/handlers/config-platform.ts +40 -16
  573. package/src/daemon/handlers/config-scheduling.ts +109 -48
  574. package/src/daemon/handlers/config-slack-channel.ts +67 -35
  575. package/src/daemon/handlers/config-slack.ts +21 -20
  576. package/src/daemon/handlers/config-telegram.ts +100 -70
  577. package/src/daemon/handlers/config-tools.ts +103 -55
  578. package/src/daemon/handlers/config-trust.ts +50 -20
  579. package/src/daemon/handlers/config.ts +72 -24
  580. package/src/daemon/handlers/contacts.ts +163 -0
  581. package/src/daemon/handlers/diagnostics.ts +90 -48
  582. package/src/daemon/handlers/documents.ts +74 -46
  583. package/src/daemon/handlers/guardian-actions.ts +57 -77
  584. package/src/daemon/handlers/home-base.ts +19 -16
  585. package/src/daemon/handlers/identity.ts +65 -45
  586. package/src/daemon/handlers/index.ts +78 -54
  587. package/src/daemon/handlers/misc.ts +664 -234
  588. package/src/daemon/handlers/navigate-settings.ts +14 -11
  589. package/src/daemon/handlers/oauth-connect.ts +48 -35
  590. package/src/daemon/handlers/open-bundle-handler.ts +31 -24
  591. package/src/daemon/handlers/pairing.ts +51 -25
  592. package/src/daemon/handlers/publish.ts +55 -33
  593. package/src/daemon/handlers/recording.ts +378 -162
  594. package/src/daemon/handlers/sessions.ts +922 -423
  595. package/src/daemon/handlers/shared.ts +202 -117
  596. package/src/daemon/handlers/signing.ts +25 -6
  597. package/src/daemon/handlers/subagents.ts +117 -56
  598. package/src/daemon/handlers/twitter-auth.ts +70 -49
  599. package/src/daemon/handlers/work-items.ts +264 -112
  600. package/src/daemon/handlers/workspace-files.ts +27 -20
  601. package/src/daemon/handlers.ts +2 -2
  602. package/src/daemon/history-repair.ts +16 -15
  603. package/src/daemon/identity-helpers.ts +4 -4
  604. package/src/daemon/install-cli-launchers.ts +33 -22
  605. package/src/daemon/ipc-blob-store.ts +38 -24
  606. package/src/daemon/ipc-contract/apps.ts +61 -50
  607. package/src/daemon/ipc-contract/computer-use.ts +47 -37
  608. package/src/daemon/ipc-contract/contacts.ts +69 -0
  609. package/src/daemon/ipc-contract/diagnostics.ts +14 -14
  610. package/src/daemon/ipc-contract/documents.ts +8 -8
  611. package/src/daemon/ipc-contract/guardian-actions.ts +4 -4
  612. package/src/daemon/ipc-contract/inbox.ts +12 -71
  613. package/src/daemon/ipc-contract/integrations.ts +57 -44
  614. package/src/daemon/ipc-contract/memory.ts +3 -5
  615. package/src/daemon/ipc-contract/messages.ts +95 -69
  616. package/src/daemon/ipc-contract/notifications.ts +10 -6
  617. package/src/daemon/ipc-contract/pairing.ts +8 -8
  618. package/src/daemon/ipc-contract/schedules.ts +20 -20
  619. package/src/daemon/ipc-contract/sessions.ts +89 -57
  620. package/src/daemon/ipc-contract/settings.ts +12 -7
  621. package/src/daemon/ipc-contract/shared.ts +9 -7
  622. package/src/daemon/ipc-contract/skills.ts +46 -26
  623. package/src/daemon/ipc-contract/subagents.ts +9 -9
  624. package/src/daemon/ipc-contract/surfaces.ts +0 -1
  625. package/src/daemon/ipc-contract/trust.ts +11 -11
  626. package/src/daemon/ipc-contract/work-items.ts +33 -28
  627. package/src/daemon/ipc-contract/workspace.ts +28 -21
  628. package/src/daemon/ipc-contract-inventory.json +10 -4
  629. package/src/daemon/ipc-contract-inventory.ts +29 -26
  630. package/src/daemon/ipc-contract.ts +111 -44
  631. package/src/daemon/ipc-handler.ts +27 -19
  632. package/src/daemon/ipc-protocol.ts +22 -12
  633. package/src/daemon/ipc-validate.ts +91 -46
  634. package/src/daemon/lifecycle.ts +39 -3
  635. package/src/daemon/main.ts +10 -8
  636. package/src/daemon/media-visibility-policy.ts +3 -1
  637. package/src/daemon/pairing-store.ts +72 -40
  638. package/src/daemon/providers-setup.ts +35 -25
  639. package/src/daemon/recording-executor.ts +37 -30
  640. package/src/daemon/recording-intent-fallback.ts +58 -28
  641. package/src/daemon/recording-intent.ts +71 -61
  642. package/src/daemon/ride-shotgun-handler.ts +201 -121
  643. package/src/daemon/seed-files.ts +28 -17
  644. package/src/daemon/server.ts +23 -14
  645. package/src/daemon/session-agent-loop-handlers.ts +270 -135
  646. package/src/daemon/session-agent-loop.ts +796 -253
  647. package/src/daemon/session-attachments.ts +109 -40
  648. package/src/daemon/session-conflict-gate.ts +72 -28
  649. package/src/daemon/session-dynamic-profile.ts +36 -22
  650. package/src/daemon/session-error.ts +68 -45
  651. package/src/daemon/session-evictor.ts +17 -10
  652. package/src/daemon/session-history.ts +201 -89
  653. package/src/daemon/session-lifecycle.ts +80 -44
  654. package/src/daemon/session-media-retry.ts +104 -42
  655. package/src/daemon/session-memory.ts +77 -55
  656. package/src/daemon/session-messaging.ts +261 -111
  657. package/src/daemon/session-notifiers.ts +57 -45
  658. package/src/daemon/session-process.ts +370 -154
  659. package/src/daemon/session-queue-manager.ts +30 -13
  660. package/src/daemon/session-runtime-assembly.ts +61 -15
  661. package/src/daemon/session-skill-tools.ts +84 -36
  662. package/src/daemon/session-slash.ts +178 -113
  663. package/src/daemon/session-surfaces.ts +498 -212
  664. package/src/daemon/session-tool-setup.ts +24 -16
  665. package/src/daemon/session-usage.ts +26 -13
  666. package/src/daemon/session-workspace.ts +7 -4
  667. package/src/daemon/session.ts +18 -19
  668. package/src/daemon/shutdown-handlers.ts +36 -33
  669. package/src/daemon/tls-certs.ts +90 -57
  670. package/src/daemon/tool-side-effects.ts +97 -65
  671. package/src/daemon/trace-emitter.ts +8 -7
  672. package/src/daemon/video-thumbnail.ts +55 -25
  673. package/src/daemon/watch-handler.ts +164 -86
  674. package/src/email/provider.ts +1 -1
  675. package/src/email/providers/agentmail.ts +87 -45
  676. package/src/email/providers/index.ts +19 -14
  677. package/src/email/service.ts +52 -24
  678. package/src/email/types.ts +2 -2
  679. package/src/errors.ts +1 -1
  680. package/src/events/bus.ts +30 -10
  681. package/src/events/domain-events.ts +20 -13
  682. package/src/events/index.ts +6 -6
  683. package/src/events/tool-audit-listener.ts +34 -20
  684. package/src/events/tool-domain-event-publisher.ts +22 -20
  685. package/src/events/tool-metrics-listener.ts +26 -21
  686. package/src/events/tool-notification-listener.ts +5 -5
  687. package/src/events/tool-profiling-listener.ts +33 -23
  688. package/src/events/tool-trace-listener.ts +70 -46
  689. package/src/export/formatter.ts +38 -32
  690. package/src/followups/followup-store.ts +43 -36
  691. package/src/followups/index.ts +2 -2
  692. package/src/followups/types.ts +1 -1
  693. package/src/gallery/default-gallery.ts +37 -34
  694. package/src/gallery/gallery-manifest.ts +9 -9
  695. package/src/heartbeat/heartbeat-service.ts +59 -37
  696. package/src/home-base/app-link-store.ts +14 -12
  697. package/src/home-base/bootstrap.ts +14 -8
  698. package/src/home-base/prebuilt/seed.ts +34 -26
  699. package/src/home-base/prebuilt-home-base-updater.ts +14 -8
  700. package/src/hooks/cli.ts +56 -43
  701. package/src/hooks/config.ts +27 -14
  702. package/src/hooks/discovery.ts +53 -33
  703. package/src/hooks/manager.ts +50 -26
  704. package/src/hooks/runner.ts +35 -29
  705. package/src/hooks/templates.ts +38 -15
  706. package/src/hooks/types.ts +13 -13
  707. package/src/inbound/platform-callback-registration.ts +21 -15
  708. package/src/inbound/public-ingress-urls.ts +9 -6
  709. package/src/index.ts +20 -19
  710. package/src/influencer/client.ts +261 -117
  711. package/src/instrument.ts +3 -1
  712. package/src/logfire.ts +64 -39
  713. package/src/mcp/client.ts +107 -55
  714. package/src/mcp/manager.ts +45 -18
  715. package/src/mcp/mcp-oauth-provider.ts +114 -62
  716. package/src/media/gemini-image-service.ts +75 -23
  717. package/src/memory/account-store.ts +16 -9
  718. package/src/memory/admin.ts +87 -57
  719. package/src/memory/app-git-service.ts +77 -47
  720. package/src/memory/app-store.ts +148 -78
  721. package/src/memory/attachments-store.ts +123 -53
  722. package/src/memory/canonical-guardian-store.ts +190 -48
  723. package/src/memory/channel-delivery-store.ts +5 -5
  724. package/src/memory/channel-guardian-store.ts +31 -16
  725. package/src/memory/checkpoints.ts +14 -7
  726. package/src/memory/clarification-resolver.ts +219 -104
  727. package/src/memory/conflict-intent.ts +74 -23
  728. package/src/memory/conflict-policy.ts +20 -7
  729. package/src/memory/conflict-store.ts +144 -94
  730. package/src/memory/contradiction-checker.ts +257 -132
  731. package/src/memory/conversation-attention-store.ts +74 -32
  732. package/src/memory/conversation-bootstrap.ts +28 -0
  733. package/src/memory/conversation-crud.ts +12 -5
  734. package/src/memory/conversation-display-order-migration.ts +7 -7
  735. package/src/memory/conversation-key-store.ts +18 -13
  736. package/src/memory/conversation-queries.ts +130 -52
  737. package/src/memory/conversation-store.ts +43 -26
  738. package/src/memory/conversation-title-service.ts +89 -66
  739. package/src/memory/db-init.ts +94 -2
  740. package/src/memory/db.ts +10 -3
  741. package/src/memory/delivery-channels.ts +12 -6
  742. package/src/memory/delivery-crud.ts +26 -12
  743. package/src/memory/delivery-status.ts +19 -16
  744. package/src/memory/embedding-backend.ts +205 -77
  745. package/src/memory/embedding-gemini.ts +23 -10
  746. package/src/memory/embedding-local.ts +89 -44
  747. package/src/memory/embedding-ollama.ts +25 -13
  748. package/src/memory/embedding-openai.ts +20 -11
  749. package/src/memory/embedding-runtime-manager.ts +163 -90
  750. package/src/memory/entity-extractor.ts +185 -123
  751. package/src/memory/external-conversation-store.ts +30 -12
  752. package/src/memory/fingerprint.ts +2 -2
  753. package/src/memory/fts-reconciler.ts +57 -28
  754. package/src/memory/guardian-action-store.ts +162 -100
  755. package/src/memory/guardian-approvals.ts +63 -129
  756. package/src/memory/guardian-rate-limits.ts +20 -9
  757. package/src/memory/guardian-verification.ts +82 -35
  758. package/src/memory/indexer.ts +96 -55
  759. package/src/memory/{ingress-invite-store.ts → invite-store.ts} +28 -169
  760. package/src/memory/items-extractor.ts +313 -157
  761. package/src/memory/job-handlers/backfill.ts +116 -63
  762. package/src/memory/job-handlers/cleanup.ts +64 -41
  763. package/src/memory/job-handlers/conflict.ts +90 -49
  764. package/src/memory/job-handlers/embedding.ts +32 -17
  765. package/src/memory/job-handlers/extraction.ts +58 -33
  766. package/src/memory/job-handlers/index-maintenance.ts +31 -17
  767. package/src/memory/job-handlers/media-processing.ts +65 -24
  768. package/src/memory/job-handlers/summarization.ts +186 -128
  769. package/src/memory/job-utils.ts +100 -57
  770. package/src/memory/jobs-store.ts +235 -142
  771. package/src/memory/jobs-worker.ts +167 -83
  772. package/src/memory/llm-request-log-store.ts +13 -11
  773. package/src/memory/llm-usage-store.ts +35 -26
  774. package/src/memory/media-store.ts +151 -44
  775. package/src/memory/message-content.ts +28 -18
  776. package/src/memory/migrations/001-job-deferrals.ts +11 -5
  777. package/src/memory/migrations/002-tool-invocations-fk.ts +14 -6
  778. package/src/memory/migrations/003-memory-fts-backfill.ts +11 -5
  779. package/src/memory/migrations/004-entity-relation-dedup.ts +17 -11
  780. package/src/memory/migrations/005-fingerprint-scope-unique.ts +36 -21
  781. package/src/memory/migrations/006-scope-salted-fingerprints.ts +35 -20
  782. package/src/memory/migrations/007-assistant-id-to-self.ts +40 -27
  783. package/src/memory/migrations/008-remove-assistant-id-columns.ts +58 -36
  784. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +36 -22
  785. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +21 -11
  786. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +30 -15
  787. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +4 -2
  788. package/src/memory/migrations/013-guardian-action-tables.ts +29 -11
  789. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +35 -21
  790. package/src/memory/migrations/015-drop-active-search-index.ts +17 -11
  791. package/src/memory/migrations/016-memory-segments-indexes.ts +7 -3
  792. package/src/memory/migrations/017-memory-items-indexes.ts +4 -2
  793. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -5
  794. package/src/memory/migrations/019-notification-tables-schema-migration.ts +34 -20
  795. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +87 -53
  796. package/src/memory/migrations/021-conversation-status-indexes.ts +7 -3
  797. package/src/memory/migrations/022-add-origin-interface.ts +4 -2
  798. package/src/memory/migrations/023-memory-item-sources-indexes.ts +4 -2
  799. package/src/memory/migrations/024-embedding-vector-blob.ts +34 -18
  800. package/src/memory/migrations/025-messages-fts-backfill.ts +11 -5
  801. package/src/memory/migrations/026-guardian-verification-sessions.ts +80 -14
  802. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +42 -26
  803. package/src/memory/migrations/027-notification-delivery-pairing-columns.ts +22 -8
  804. package/src/memory/migrations/027a-guardian-bootstrap-token.ts +11 -3
  805. package/src/memory/migrations/028-call-session-mode.ts +13 -3
  806. package/src/memory/migrations/028-notification-delivery-client-ack.ts +22 -8
  807. package/src/memory/migrations/029-channel-inbound-delivered-segments.ts +7 -3
  808. package/src/memory/migrations/030-guardian-action-followup.ts +46 -8
  809. package/src/memory/migrations/030-guardian-verification-purpose.ts +4 -2
  810. package/src/memory/migrations/031-conversations-thread-type-index.ts +4 -2
  811. package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +4 -2
  812. package/src/memory/migrations/032-notification-delivery-thread-decision.ts +22 -8
  813. package/src/memory/migrations/033-scoped-approval-grants.ts +1 -1
  814. package/src/memory/migrations/034-guardian-action-tool-metadata.ts +15 -3
  815. package/src/memory/migrations/035-guardian-action-supersession.ts +15 -3
  816. package/src/memory/migrations/036-normalize-phone-identities.ts +101 -87
  817. package/src/memory/migrations/037-voice-invite-columns.ts +22 -4
  818. package/src/memory/migrations/038-actor-token-records.ts +5 -9
  819. package/src/memory/migrations/039-actor-refresh-token-records.ts +7 -13
  820. package/src/memory/migrations/100-core-tables.ts +1 -1
  821. package/src/memory/migrations/101-watchers-and-logs.ts +1 -1
  822. package/src/memory/migrations/103-complex-migrations.ts +9 -9
  823. package/src/memory/migrations/104-core-indexes.ts +188 -64
  824. package/src/memory/migrations/105-contacts-and-triage.ts +28 -10
  825. package/src/memory/migrations/106-call-sessions.ts +58 -16
  826. package/src/memory/migrations/107-followups.ts +16 -6
  827. package/src/memory/migrations/108-tasks-and-work-items.ts +43 -11
  828. package/src/memory/migrations/109-external-conversation-bindings.ts +11 -5
  829. package/src/memory/migrations/110-channel-guardian.ts +48 -10
  830. package/src/memory/migrations/111-media-assets.ts +52 -18
  831. package/src/memory/migrations/112-assistant-inbox.ts +32 -12
  832. package/src/memory/migrations/113-late-migrations.ts +12 -12
  833. package/src/memory/migrations/114-notifications.ts +28 -12
  834. package/src/memory/migrations/115-sequences.ts +10 -4
  835. package/src/memory/migrations/116-messages-fts.ts +1 -1
  836. package/src/memory/migrations/117-conversation-attention.ts +16 -6
  837. package/src/memory/migrations/118-reminder-routing-intent.ts +7 -3
  838. package/src/memory/migrations/119-schema-indexes-and-columns.ts +35 -15
  839. package/src/memory/migrations/120-fk-cascade-rebuilds.ts +36 -17
  840. package/src/memory/migrations/121-canonical-guardian-requests.ts +25 -9
  841. package/src/memory/migrations/122-canonical-guardian-requester-chat-id.ts +11 -3
  842. package/src/memory/migrations/123-canonical-guardian-deliveries-destination-index.ts +4 -2
  843. package/src/memory/migrations/124-voice-invite-display-metadata.ts +15 -3
  844. package/src/memory/migrations/125-guardian-principal-id-columns.ts +22 -4
  845. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +174 -126
  846. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +58 -42
  847. package/src/memory/migrations/128-contacts-role-principal.ts +26 -0
  848. package/src/memory/migrations/129-contact-channels-access-fields.ts +105 -0
  849. package/src/memory/migrations/130-contact-channels-type-ext-chat-id-index.ts +15 -0
  850. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +134 -0
  851. package/src/memory/migrations/132-contacts-assistant-id.ts +21 -0
  852. package/src/memory/migrations/133-assistant-contact-metadata.ts +21 -0
  853. package/src/memory/migrations/index.ts +83 -73
  854. package/src/memory/migrations/registry.ts +53 -37
  855. package/src/memory/migrations/validate-migration-state.ts +73 -46
  856. package/src/memory/profile-compiler.ts +58 -24
  857. package/src/memory/published-pages-store.ts +12 -16
  858. package/src/memory/qdrant-circuit-breaker.ts +28 -20
  859. package/src/memory/qdrant-client.ts +99 -63
  860. package/src/memory/qdrant-manager.ts +89 -57
  861. package/src/memory/query-builder.ts +9 -7
  862. package/src/memory/raw-query.ts +63 -14
  863. package/src/memory/recall-cache.ts +15 -8
  864. package/src/memory/retrieval-budget.ts +0 -1
  865. package/src/memory/retriever.ts +385 -192
  866. package/src/memory/schema-migration.ts +1 -1
  867. package/src/memory/schema.ts +56 -56
  868. package/src/memory/scoped-approval-grants.ts +99 -45
  869. package/src/memory/search/entity.ts +102 -40
  870. package/src/memory/search/formatting.ts +70 -52
  871. package/src/memory/search/lexical.ts +82 -43
  872. package/src/memory/search/ranking.ts +103 -39
  873. package/src/memory/search/semantic.ts +59 -35
  874. package/src/memory/search/types.ts +8 -8
  875. package/src/memory/segmenter.ts +20 -12
  876. package/src/memory/shared-app-links-store.ts +21 -16
  877. package/src/memory/slack-thread-store.ts +187 -0
  878. package/src/memory/task-memory-cleanup.ts +18 -8
  879. package/src/memory/tool-usage-store.ts +27 -19
  880. package/src/memory/validation.ts +4 -2
  881. package/src/messaging/activity-analyzer.ts +7 -7
  882. package/src/messaging/draft-store.ts +13 -10
  883. package/src/messaging/email-classifier.ts +73 -37
  884. package/src/messaging/index.ts +3 -3
  885. package/src/messaging/outreach-classifier.ts +76 -38
  886. package/src/messaging/provider-types.ts +2 -4
  887. package/src/messaging/provider.ts +37 -8
  888. package/src/messaging/providers/gmail/adapter.ts +183 -66
  889. package/src/messaging/providers/gmail/client.ts +3 -1
  890. package/src/messaging/providers/gmail/mime-builder.ts +21 -19
  891. package/src/messaging/providers/gmail/people-client.ts +22 -9
  892. package/src/messaging/providers/gmail/types.ts +6 -6
  893. package/src/messaging/providers/slack/adapter.ts +93 -43
  894. package/src/messaging/providers/slack/client.ts +165 -48
  895. package/src/messaging/providers/slack/types.ts +10 -0
  896. package/src/messaging/providers/sms/adapter.ts +76 -40
  897. package/src/messaging/providers/sms/client.ts +4 -4
  898. package/src/messaging/providers/telegram-bot/adapter.ts +52 -30
  899. package/src/messaging/providers/telegram-bot/client.ts +7 -7
  900. package/src/messaging/providers/whatsapp/adapter.ts +58 -31
  901. package/src/messaging/providers/whatsapp/client.ts +4 -4
  902. package/src/messaging/registry.ts +9 -5
  903. package/src/messaging/style-analyzer.ts +69 -39
  904. package/src/messaging/thread-summarizer.ts +101 -53
  905. package/src/messaging/triage-engine.ts +111 -82
  906. package/src/messaging/types.ts +10 -10
  907. package/src/migrations/config-merge.ts +18 -10
  908. package/src/migrations/data-layout.ts +35 -22
  909. package/src/migrations/data-merge.ts +17 -7
  910. package/src/migrations/hooks-merge.ts +43 -16
  911. package/src/migrations/index.ts +6 -6
  912. package/src/migrations/log.ts +9 -5
  913. package/src/migrations/skills-merge.ts +17 -7
  914. package/src/migrations/workspace-layout.ts +39 -25
  915. package/src/notifications/AGENTS.md +5 -0
  916. package/src/notifications/adapters/macos.ts +21 -14
  917. package/src/notifications/adapters/slack.ts +90 -0
  918. package/src/notifications/adapters/sms.ts +28 -15
  919. package/src/notifications/adapters/telegram.ts +24 -15
  920. package/src/notifications/broadcaster.ts +108 -52
  921. package/src/notifications/conversation-pairing.ts +64 -29
  922. package/src/notifications/copy-composer.ts +165 -95
  923. package/src/notifications/decision-engine.ts +353 -147
  924. package/src/notifications/decisions-store.ts +26 -10
  925. package/src/notifications/deliveries-store.ts +23 -13
  926. package/src/notifications/destination-resolver.ts +83 -24
  927. package/src/notifications/deterministic-checks.ts +78 -27
  928. package/src/notifications/emit-signal.ts +95 -41
  929. package/src/notifications/events-store.ts +13 -7
  930. package/src/notifications/guardian-question-mode.ts +125 -75
  931. package/src/notifications/preference-extractor.ts +85 -53
  932. package/src/notifications/preference-summary.ts +31 -18
  933. package/src/notifications/preferences-store.ts +29 -18
  934. package/src/notifications/runtime-dispatch.ts +22 -12
  935. package/src/notifications/signal.ts +4 -4
  936. package/src/notifications/thread-candidates.ts +59 -23
  937. package/src/notifications/thread-seed-composer.ts +45 -27
  938. package/src/notifications/types.ts +19 -10
  939. package/src/oauth/connect-orchestrator.ts +105 -54
  940. package/src/oauth/connect-types.ts +3 -3
  941. package/src/oauth/provider-profiles.ts +102 -59
  942. package/src/oauth/scope-policy.ts +5 -2
  943. package/src/oauth/token-persistence.ts +58 -24
  944. package/src/outbound-proxy/certs.ts +284 -0
  945. package/src/outbound-proxy/config.ts +94 -0
  946. package/src/outbound-proxy/connect-tunnel.ts +84 -0
  947. package/src/outbound-proxy/health.ts +62 -0
  948. package/src/outbound-proxy/host-pattern-match.ts +67 -0
  949. package/src/outbound-proxy/http-forwarder.ts +162 -0
  950. package/src/outbound-proxy/index.ts +80 -0
  951. package/src/outbound-proxy/logging.ts +193 -0
  952. package/src/outbound-proxy/mitm-handler.ts +292 -0
  953. package/src/outbound-proxy/policy.ts +172 -0
  954. package/src/outbound-proxy/router.ts +64 -0
  955. package/src/outbound-proxy/server.ts +145 -0
  956. package/src/outbound-proxy/types.ts +150 -0
  957. package/src/permissions/checker.ts +481 -189
  958. package/src/permissions/defaults.ts +135 -108
  959. package/src/permissions/prompter.ts +53 -27
  960. package/src/permissions/secret-prompter.ts +21 -15
  961. package/src/permissions/shell-identity.ts +47 -16
  962. package/src/permissions/trust-store.ts +185 -73
  963. package/src/permissions/types.ts +22 -12
  964. package/src/permissions/workspace-policy.ts +47 -38
  965. package/src/playbooks/index.ts +10 -2
  966. package/src/playbooks/playbook-compiler.ts +30 -24
  967. package/src/playbooks/types.ts +11 -8
  968. package/src/providers/anthropic/client.ts +328 -168
  969. package/src/providers/failover.ts +57 -22
  970. package/src/providers/fireworks/client.ts +9 -5
  971. package/src/providers/gemini/client.ts +61 -39
  972. package/src/providers/model-intents.ts +40 -33
  973. package/src/providers/ollama/client.ts +7 -7
  974. package/src/providers/openai/client.ts +109 -68
  975. package/src/providers/openrouter/client.ts +9 -5
  976. package/src/providers/provider-send-message.ts +59 -27
  977. package/src/providers/ratelimit.ts +25 -8
  978. package/src/providers/registry.ts +86 -38
  979. package/src/providers/retry.ts +93 -37
  980. package/src/providers/stream-timeout.ts +5 -3
  981. package/src/providers/types.ts +7 -6
  982. package/src/runtime/AGENTS.md +42 -0
  983. package/src/runtime/access-request-helper.ts +118 -68
  984. package/src/runtime/actor-refresh-token-store.ts +21 -16
  985. package/src/runtime/actor-token-store.ts +25 -18
  986. package/src/runtime/actor-trust-resolver.ts +191 -80
  987. package/src/runtime/approval-conversation-turn.ts +39 -26
  988. package/src/runtime/approval-message-composer.ts +116 -84
  989. package/src/runtime/assistant-event-hub.ts +25 -6
  990. package/src/runtime/assistant-event.ts +4 -4
  991. package/src/runtime/assistant-scope.ts +1 -1
  992. package/src/runtime/auth/__tests__/guard-tests.test.ts +36 -14
  993. package/src/runtime/auth/context.ts +8 -7
  994. package/src/runtime/auth/credential-service.ts +60 -38
  995. package/src/runtime/auth/external-assistant-id.ts +16 -8
  996. package/src/runtime/auth/index.ts +23 -16
  997. package/src/runtime/auth/require-bound-guardian.ts +44 -0
  998. package/src/runtime/auth/route-policy.ts +166 -104
  999. package/src/runtime/auth/scopes.ts +22 -29
  1000. package/src/runtime/auth/subject.ts +19 -13
  1001. package/src/runtime/auth/token-service.ts +3 -3
  1002. package/src/runtime/auth/types.ts +23 -23
  1003. package/src/runtime/channel-approval-parser.ts +37 -14
  1004. package/src/runtime/channel-approval-types.ts +30 -4
  1005. package/src/runtime/channel-approvals.ts +49 -23
  1006. package/src/runtime/channel-guardian-service.ts +144 -103
  1007. package/src/runtime/channel-invite-transport.ts +5 -3
  1008. package/src/runtime/channel-invite-transports/telegram.ts +16 -10
  1009. package/src/runtime/channel-invite-transports/voice.ts +7 -7
  1010. package/src/runtime/channel-readiness-service.ts +139 -90
  1011. package/src/runtime/channel-readiness-types.ts +4 -2
  1012. package/src/runtime/channel-reply-delivery.ts +83 -14
  1013. package/src/runtime/channel-retry-sweep.ts +111 -62
  1014. package/src/runtime/confirmation-request-guardian-bridge.ts +73 -54
  1015. package/src/runtime/gateway-client.ts +122 -55
  1016. package/src/runtime/gateway-internal-client.ts +86 -0
  1017. package/src/runtime/guardian-action-conversation-turn.ts +34 -18
  1018. package/src/runtime/guardian-action-followup-executor.ts +115 -45
  1019. package/src/runtime/guardian-action-grant-minter.ts +40 -24
  1020. package/src/runtime/guardian-action-message-composer.ts +105 -84
  1021. package/src/runtime/guardian-action-service.ts +127 -0
  1022. package/src/runtime/guardian-decision-types.ts +28 -13
  1023. package/src/runtime/guardian-outbound-actions.ts +9 -0
  1024. package/src/runtime/guardian-reply-router.ts +274 -145
  1025. package/src/runtime/guardian-vellum-migration.ts +38 -24
  1026. package/src/runtime/guardian-verification-templates.ts +24 -12
  1027. package/src/runtime/http-router.ts +175 -0
  1028. package/src/runtime/http-server.ts +913 -680
  1029. package/src/runtime/http-types.ts +2 -2
  1030. package/src/runtime/invite-redemption-service.ts +211 -134
  1031. package/src/runtime/invite-redemption-templates.ts +18 -11
  1032. package/src/runtime/{ingress-service.ts → invite-service.ts} +92 -151
  1033. package/src/runtime/local-actor-identity.ts +73 -55
  1034. package/src/runtime/middleware/auth.ts +25 -14
  1035. package/src/runtime/middleware/error-handler.ts +15 -11
  1036. package/src/runtime/middleware/rate-limiter.ts +23 -17
  1037. package/src/runtime/middleware/request-logger.ts +4 -4
  1038. package/src/runtime/middleware/twilio-validation.ts +29 -20
  1039. package/src/runtime/migrations/migration-transport.ts +575 -0
  1040. package/src/runtime/migrations/migration-wizard.ts +715 -0
  1041. package/src/runtime/migrations/rebind-secrets-screen.ts +351 -0
  1042. package/src/runtime/migrations/transfer-progress-screen.ts +321 -0
  1043. package/src/runtime/migrations/validation-results-screen.ts +467 -0
  1044. package/src/runtime/migrations/vbundle-builder.ts +295 -0
  1045. package/src/runtime/migrations/vbundle-import-analyzer.ts +212 -0
  1046. package/src/runtime/migrations/vbundle-importer.ts +339 -0
  1047. package/src/runtime/migrations/vbundle-validator.ts +356 -0
  1048. package/src/runtime/nl-approval-parser.ts +138 -0
  1049. package/src/runtime/pending-interactions.ts +16 -7
  1050. package/src/runtime/routes/access-request-decision.ts +73 -52
  1051. package/src/runtime/routes/app-routes.ts +56 -38
  1052. package/src/runtime/routes/approval-routes.ts +144 -92
  1053. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +930 -0
  1054. package/src/runtime/routes/approval-strategies/guardian-legacy-fallback-strategy.ts +82 -0
  1055. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +151 -0
  1056. package/src/runtime/routes/attachment-routes.ts +59 -48
  1057. package/src/runtime/routes/brain-graph-routes.ts +85 -69
  1058. package/src/runtime/routes/call-routes.ts +79 -38
  1059. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +10 -10
  1060. package/src/runtime/routes/channel-delivery-routes.ts +19 -14
  1061. package/src/runtime/routes/channel-guardian-routes.ts +3 -3
  1062. package/src/runtime/routes/channel-inbound-routes.ts +2 -2
  1063. package/src/runtime/routes/channel-readiness-routes.ts +12 -6
  1064. package/src/runtime/routes/channel-route-shared.ts +67 -25
  1065. package/src/runtime/routes/channel-routes.ts +4 -6
  1066. package/src/runtime/routes/contact-routes.ts +374 -17
  1067. package/src/runtime/routes/conversation-attention-routes.ts +57 -28
  1068. package/src/runtime/routes/conversation-routes.ts +321 -174
  1069. package/src/runtime/routes/debug-routes.ts +14 -10
  1070. package/src/runtime/routes/events-routes.ts +90 -57
  1071. package/src/runtime/routes/global-search-routes.ts +266 -0
  1072. package/src/runtime/routes/guardian-action-routes.ts +112 -113
  1073. package/src/runtime/routes/guardian-approval-interception.ts +325 -874
  1074. package/src/runtime/routes/guardian-approval-prompt.ts +40 -24
  1075. package/src/runtime/routes/guardian-approval-reply-helpers.ts +135 -0
  1076. package/src/runtime/routes/guardian-bootstrap-routes.ts +55 -36
  1077. package/src/runtime/routes/guardian-expiry-sweep.ts +63 -37
  1078. package/src/runtime/routes/guardian-refresh-routes.ts +40 -19
  1079. package/src/runtime/routes/identity-routes.ts +71 -42
  1080. package/src/runtime/routes/inbound-conversation.ts +17 -11
  1081. package/src/runtime/routes/inbound-message-handler.ts +305 -1459
  1082. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +880 -0
  1083. package/src/runtime/routes/inbound-stages/background-dispatch.ts +600 -0
  1084. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +214 -0
  1085. package/src/runtime/routes/inbound-stages/edit-intercept.ts +116 -0
  1086. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +167 -0
  1087. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +185 -0
  1088. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +132 -0
  1089. package/src/runtime/routes/inbound-stages/verification-intercept.ts +340 -0
  1090. package/src/runtime/routes/integration-routes.ts +60 -21
  1091. package/src/runtime/routes/invite-routes.ts +140 -0
  1092. package/src/runtime/routes/migration-routes.ts +434 -0
  1093. package/src/runtime/routes/pairing-routes.ts +157 -79
  1094. package/src/runtime/routes/secret-routes.ts +6 -2
  1095. package/src/runtime/routes/twilio-routes.ts +443 -249
  1096. package/src/runtime/slack-block-formatting.ts +176 -0
  1097. package/src/runtime/tool-grant-request-helper.ts +36 -27
  1098. package/src/runtime/{guardian-context-resolver.ts → trust-context-resolver.ts} +29 -41
  1099. package/src/schedule/integration-status.ts +44 -9
  1100. package/src/schedule/recurrence-engine.ts +47 -24
  1101. package/src/schedule/recurrence-types.ts +12 -7
  1102. package/src/schedule/schedule-store.ts +166 -83
  1103. package/src/schedule/scheduler.ts +37 -24
  1104. package/src/security/encrypted-store.ts +68 -38
  1105. package/src/security/keychain.ts +183 -120
  1106. package/src/security/oauth-callback-registry.ts +3 -3
  1107. package/src/security/oauth2.ts +226 -138
  1108. package/src/security/redaction.ts +24 -24
  1109. package/src/security/secret-allowlist.ts +46 -21
  1110. package/src/security/secret-ingress.ts +15 -7
  1111. package/src/security/secret-scanner.ts +193 -104
  1112. package/src/security/secure-keys.ts +9 -3
  1113. package/src/security/token-manager.ts +99 -40
  1114. package/src/security/tool-approval-digest.ts +3 -3
  1115. package/src/sequence/analytics.ts +52 -27
  1116. package/src/sequence/engine.ts +135 -72
  1117. package/src/sequence/guardrails.ts +32 -20
  1118. package/src/sequence/importer.ts +75 -37
  1119. package/src/sequence/reply-matcher.ts +36 -18
  1120. package/src/sequence/store.ts +137 -75
  1121. package/src/sequence/types.ts +30 -16
  1122. package/src/services/published-app-updater.ts +26 -16
  1123. package/src/services/vercel-deploy.ts +19 -15
  1124. package/src/skills/active-skill-tools.ts +3 -3
  1125. package/src/skills/clawhub.ts +178 -90
  1126. package/src/skills/include-graph.ts +24 -17
  1127. package/src/skills/managed-store.ts +89 -42
  1128. package/src/skills/path-classifier.ts +10 -10
  1129. package/src/skills/remote-skill-policy.ts +31 -22
  1130. package/src/skills/slash-commands.ts +36 -30
  1131. package/src/skills/tool-manifest.ts +60 -31
  1132. package/src/skills/version-hash.ts +25 -15
  1133. package/src/slack/slack-webhook.ts +19 -15
  1134. package/src/subagent/index.ts +4 -8
  1135. package/src/subagent/manager.ts +119 -69
  1136. package/src/subagent/types.ts +9 -12
  1137. package/src/swarm/backend-claude-code.ts +124 -45
  1138. package/src/swarm/checkpoint.ts +36 -16
  1139. package/src/swarm/graph-utils.ts +1 -3
  1140. package/src/swarm/index.ts +38 -19
  1141. package/src/swarm/limits.ts +13 -4
  1142. package/src/swarm/orchestrator.ts +108 -57
  1143. package/src/swarm/plan-validator.ts +23 -17
  1144. package/src/swarm/router-planner.ts +51 -22
  1145. package/src/swarm/router-prompts.ts +4 -1
  1146. package/src/swarm/synthesizer.ts +26 -18
  1147. package/src/swarm/types.ts +14 -4
  1148. package/src/swarm/worker-backend.ts +36 -26
  1149. package/src/swarm/worker-prompts.ts +13 -9
  1150. package/src/swarm/worker-runner.ts +40 -34
  1151. package/src/tasks/candidate-store.ts +14 -6
  1152. package/src/tasks/ephemeral-permissions.ts +9 -5
  1153. package/src/tasks/task-compiler.ts +41 -38
  1154. package/src/tasks/task-runner.ts +54 -26
  1155. package/src/tasks/task-scheduler.ts +1 -1
  1156. package/src/tasks/task-store.ts +20 -7
  1157. package/src/tasks/tool-sanitizer.ts +3 -3
  1158. package/src/tools/apps/definitions.ts +23 -15
  1159. package/src/tools/apps/executors.ts +122 -40
  1160. package/src/tools/apps/open-proxy.ts +5 -5
  1161. package/src/tools/apps/registry.ts +2 -2
  1162. package/src/tools/assets/materialize.ts +59 -41
  1163. package/src/tools/assets/search.ts +86 -48
  1164. package/src/tools/browser/api-map.ts +52 -36
  1165. package/src/tools/browser/auth-cache.ts +21 -18
  1166. package/src/tools/browser/auth-detector.ts +43 -28
  1167. package/src/tools/browser/auto-navigate.ts +149 -68
  1168. package/src/tools/browser/browser-execution.ts +9 -3
  1169. package/src/tools/browser/headless-browser.ts +287 -150
  1170. package/src/tools/browser/jit-auth.ts +37 -21
  1171. package/src/tools/browser/network-recorder.ts +138 -56
  1172. package/src/tools/browser/recording-store.ts +22 -15
  1173. package/src/tools/browser/runtime-check.ts +8 -5
  1174. package/src/tools/browser/x-auto-navigate.ts +88 -47
  1175. package/src/tools/calls/call-end.ts +10 -7
  1176. package/src/tools/calls/call-start.ts +30 -20
  1177. package/src/tools/calls/call-status.ts +8 -5
  1178. package/src/tools/claude-code/claude-code.ts +301 -165
  1179. package/src/tools/computer-use/definitions.ts +175 -130
  1180. package/src/tools/computer-use/registry.ts +2 -2
  1181. package/src/tools/computer-use/request-computer-control.ts +21 -13
  1182. package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
  1183. package/src/tools/credentials/account-registry.ts +52 -35
  1184. package/src/tools/credentials/broker-types.ts +1 -1
  1185. package/src/tools/credentials/broker.ts +97 -55
  1186. package/src/tools/credentials/domain-policy.ts +5 -2
  1187. package/src/tools/credentials/host-pattern-match.ts +15 -8
  1188. package/src/tools/credentials/metadata-store.ts +93 -43
  1189. package/src/tools/credentials/policy-types.ts +5 -2
  1190. package/src/tools/credentials/policy-validate.ts +21 -14
  1191. package/src/tools/credentials/post-connect-hooks.ts +18 -7
  1192. package/src/tools/credentials/resolve.ts +11 -10
  1193. package/src/tools/credentials/selection.ts +30 -25
  1194. package/src/tools/credentials/tool-policy.ts +5 -2
  1195. package/src/tools/credentials/vault.ts +538 -185
  1196. package/src/tools/document/document-tool.ts +23 -17
  1197. package/src/tools/document/editor-template.ts +12 -7
  1198. package/src/tools/execution-target.ts +13 -10
  1199. package/src/tools/execution-timeout.ts +6 -5
  1200. package/src/tools/executor.ts +141 -74
  1201. package/src/tools/filesystem/edit.ts +82 -45
  1202. package/src/tools/filesystem/fuzzy-match.ts +70 -32
  1203. package/src/tools/filesystem/read.ts +46 -28
  1204. package/src/tools/filesystem/view-image.ts +86 -42
  1205. package/src/tools/filesystem/write.ts +53 -32
  1206. package/src/tools/followups/followup_create.ts +43 -17
  1207. package/src/tools/followups/followup_list.ts +28 -13
  1208. package/src/tools/followups/followup_resolve.ts +9 -6
  1209. package/src/tools/guardian-control-plane-policy.ts +15 -14
  1210. package/src/tools/host-filesystem/edit.ts +77 -42
  1211. package/src/tools/host-filesystem/read.ts +52 -33
  1212. package/src/tools/host-filesystem/write.ts +50 -29
  1213. package/src/tools/host-terminal/host-shell.ts +97 -61
  1214. package/src/tools/mcp/mcp-tool-factory.ts +21 -14
  1215. package/src/tools/memory/definitions.ts +60 -28
  1216. package/src/tools/memory/handlers.ts +149 -77
  1217. package/src/tools/memory/register.ts +39 -16
  1218. package/src/tools/network/__tests__/web-search.test.ts +236 -177
  1219. package/src/tools/network/domain-normalize.ts +13 -9
  1220. package/src/tools/network/script-proxy/__tests__/logging.test.ts +193 -123
  1221. package/src/tools/network/script-proxy/__tests__/policy.test.ts +225 -127
  1222. package/src/tools/network/script-proxy/index.ts +1 -17
  1223. package/src/tools/network/script-proxy/session-manager.ts +178 -86
  1224. package/src/tools/network/url-safety.ts +56 -34
  1225. package/src/tools/network/web-fetch.ts +273 -155
  1226. package/src/tools/network/web-search.ts +166 -81
  1227. package/src/tools/permission-checker.ts +24 -25
  1228. package/src/tools/policy-context.ts +8 -5
  1229. package/src/tools/registry.ts +73 -46
  1230. package/src/tools/reminder/reminder-store.ts +65 -44
  1231. package/src/tools/reminder/reminder.ts +76 -35
  1232. package/src/tools/schedule/create.ts +44 -21
  1233. package/src/tools/schedule/delete.ts +8 -5
  1234. package/src/tools/schedule/list.ts +39 -19
  1235. package/src/tools/schedule/update.ts +49 -26
  1236. package/src/tools/secret-detection-handler.ts +130 -49
  1237. package/src/tools/sensitive-output-placeholders.ts +15 -8
  1238. package/src/tools/shared/filesystem/edit-engine.ts +45 -14
  1239. package/src/tools/shared/filesystem/errors.ts +18 -18
  1240. package/src/tools/shared/filesystem/file-ops-service.ts +59 -32
  1241. package/src/tools/shared/filesystem/format-diff.ts +21 -11
  1242. package/src/tools/shared/filesystem/path-policy.ts +17 -13
  1243. package/src/tools/shared/filesystem/size-guard.ts +8 -4
  1244. package/src/tools/shared/filesystem/types.ts +2 -2
  1245. package/src/tools/shared/shell-output.ts +4 -3
  1246. package/src/tools/side-effects.ts +36 -28
  1247. package/src/tools/skills/delete-managed.ts +30 -17
  1248. package/src/tools/skills/load.ts +88 -46
  1249. package/src/tools/skills/sandbox-runner.ts +62 -46
  1250. package/src/tools/skills/scaffold-managed.ts +98 -48
  1251. package/src/tools/skills/script-contract.ts +5 -2
  1252. package/src/tools/skills/skill-script-runner.ts +29 -13
  1253. package/src/tools/skills/skill-tool-factory.ts +20 -10
  1254. package/src/tools/subagent/abort.ts +10 -4
  1255. package/src/tools/subagent/message.ts +14 -8
  1256. package/src/tools/subagent/read.ts +20 -11
  1257. package/src/tools/subagent/spawn.ts +14 -6
  1258. package/src/tools/subagent/status.ts +7 -4
  1259. package/src/tools/swarm/delegate.ts +75 -49
  1260. package/src/tools/system/avatar-generator.ts +46 -33
  1261. package/src/tools/system/navigate-settings.ts +29 -19
  1262. package/src/tools/system/open-system-settings.ts +30 -20
  1263. package/src/tools/system/request-permission.ts +59 -44
  1264. package/src/tools/system/version.ts +27 -16
  1265. package/src/tools/system/voice-config.ts +116 -53
  1266. package/src/tools/tasks/index.ts +8 -8
  1267. package/src/tools/tasks/task-delete.ts +61 -22
  1268. package/src/tools/tasks/task-list.ts +23 -11
  1269. package/src/tools/tasks/task-run.ts +41 -16
  1270. package/src/tools/tasks/task-save.ts +27 -10
  1271. package/src/tools/tasks/work-item-enqueue.ts +114 -48
  1272. package/src/tools/tasks/work-item-list.ts +20 -10
  1273. package/src/tools/tasks/work-item-remove.ts +49 -15
  1274. package/src/tools/tasks/work-item-run.ts +34 -13
  1275. package/src/tools/tasks/work-item-update.ts +84 -31
  1276. package/src/tools/terminal/backends/native.ts +64 -35
  1277. package/src/tools/terminal/backends/types.ts +6 -2
  1278. package/src/tools/terminal/parser.ts +200 -125
  1279. package/src/tools/terminal/safe-env.ts +27 -21
  1280. package/src/tools/terminal/sandbox-diagnostics.ts +31 -13
  1281. package/src/tools/terminal/sandbox.ts +10 -6
  1282. package/src/tools/terminal/shell.ts +134 -68
  1283. package/src/tools/tool-approval-handler.ts +239 -140
  1284. package/src/tools/types.ts +79 -22
  1285. package/src/tools/ui-surface/definitions.ts +124 -89
  1286. package/src/tools/ui-surface/registry.ts +2 -2
  1287. package/src/tools/watch/screen-watch.ts +50 -32
  1288. package/src/tools/watch/watch-state.ts +41 -15
  1289. package/src/tools/watcher/create.ts +37 -15
  1290. package/src/tools/watcher/delete.ts +9 -6
  1291. package/src/tools/watcher/digest.ts +10 -6
  1292. package/src/tools/watcher/list.ts +37 -14
  1293. package/src/tools/watcher/update.ts +33 -18
  1294. package/src/tools/weather/service.ts +331 -174
  1295. package/src/twitter/client.ts +261 -138
  1296. package/src/twitter/oauth-client.ts +17 -13
  1297. package/src/twitter/router.ts +51 -23
  1298. package/src/twitter/session.ts +27 -18
  1299. package/src/types/qrcode.d.ts +6 -3
  1300. package/src/usage/actors.ts +16 -16
  1301. package/src/usage/types.ts +3 -3
  1302. package/src/util/bundled-asset.ts +10 -6
  1303. package/src/util/canonicalize-identity.ts +11 -4
  1304. package/src/util/clipboard.ts +7 -7
  1305. package/src/util/content-id.ts +3 -3
  1306. package/src/util/debounce.ts +3 -2
  1307. package/src/util/diff.ts +55 -33
  1308. package/src/util/errors.ts +31 -27
  1309. package/src/util/fs.ts +8 -2
  1310. package/src/util/log-redact.ts +12 -12
  1311. package/src/util/logger.ts +112 -51
  1312. package/src/util/network-info.ts +13 -5
  1313. package/src/util/object.ts +4 -2
  1314. package/src/util/phone.ts +4 -4
  1315. package/src/util/platform.ts +80 -58
  1316. package/src/util/pricing.ts +49 -31
  1317. package/src/util/retry.ts +39 -7
  1318. package/src/util/row-mapper.ts +7 -4
  1319. package/src/util/silently.ts +7 -4
  1320. package/src/util/spawn.ts +48 -0
  1321. package/src/util/spinner.ts +9 -7
  1322. package/src/util/time.ts +16 -3
  1323. package/src/util/truncate.ts +1 -1
  1324. package/src/util/voice-code.ts +6 -4
  1325. package/src/util/xml.ts +5 -1
  1326. package/src/version.ts +12 -8
  1327. package/src/watcher/engine.ts +71 -44
  1328. package/src/watcher/provider-registry.ts +1 -1
  1329. package/src/watcher/providers/github.ts +40 -23
  1330. package/src/watcher/providers/gmail.ts +59 -38
  1331. package/src/watcher/providers/google-calendar.ts +62 -48
  1332. package/src/watcher/providers/linear.ts +219 -150
  1333. package/src/watcher/providers/slack.ts +125 -29
  1334. package/src/watcher/watcher-store.ts +75 -55
  1335. package/src/work-items/work-item-runner.ts +62 -29
  1336. package/src/work-items/work-item-store.ts +137 -47
  1337. package/src/workspace/commit-message-enrichment-service.ts +65 -25
  1338. package/src/workspace/commit-message-provider.ts +14 -12
  1339. package/src/workspace/git-service.ts +355 -239
  1340. package/src/workspace/heartbeat-service.ts +74 -37
  1341. package/src/workspace/provider-commit-message-generator.ts +95 -70
  1342. package/src/workspace/top-level-renderer.ts +10 -8
  1343. package/src/workspace/top-level-scanner.ts +9 -3
  1344. package/src/workspace/turn-commit.ts +63 -36
  1345. package/src/__tests__/ingress-member-store.test.ts +0 -294
  1346. package/src/__tests__/script-proxy-router.test.ts +0 -215
  1347. package/src/config/bundled-skills/trusted-contacts/SKILL.md +0 -372
  1348. package/src/memory/guardian-bindings.ts +0 -158
  1349. package/src/memory/ingress-member-store.ts +0 -352
  1350. package/src/runtime/routes/ingress-routes.ts +0 -229
  1351. package/src/tools/network/script-proxy/__tests__/router.test.ts +0 -77
  1352. package/src/tools/network/script-proxy/certs.ts +0 -7
  1353. package/src/tools/network/script-proxy/connect-tunnel.ts +0 -1
  1354. package/src/tools/network/script-proxy/http-forwarder.ts +0 -2
  1355. package/src/tools/network/script-proxy/logging.ts +0 -12
  1356. package/src/tools/network/script-proxy/mitm-handler.ts +0 -2
  1357. package/src/tools/network/script-proxy/policy.ts +0 -4
  1358. package/src/tools/network/script-proxy/router.ts +0 -2
  1359. package/src/tools/network/script-proxy/server.ts +0 -5
  1360. package/src/tools/network/script-proxy/types.ts +0 -19
@@ -4,94 +4,50 @@
4
4
  * verification, guardian action answers, approval interception, and
5
5
  * invite token redemption.
6
6
  */
7
- // Side-effect import: registers the Telegram invite transport adapter so
8
- // getTransport('telegram') resolves at runtime.
9
- import type { ChannelId, InterfaceId } from '../../channels/types.js';
10
- import { CHANNEL_IDS, INTERFACE_IDS, isChannelId, parseInterfaceId } from '../../channels/types.js';
11
- import { getGatewayInternalBaseUrl } from '../../config/env.js';
12
- import { resolveUserReference } from '../../config/user-reference.js';
13
- import { RESEND_COOLDOWN_MS } from '../../daemon/handlers/config-channels.js';
14
- import * as attachmentsStore from '../../memory/attachments-store.js';
15
7
  import {
16
- createCanonicalGuardianRequest,
17
- listCanonicalGuardianRequests,
18
- listPendingCanonicalGuardianRequestsByDestinationChat,
19
- } from '../../memory/canonical-guardian-store.js';
20
- import * as channelDeliveryStore from '../../memory/channel-delivery-store.js';
21
- import { recordConversationSeenSignal } from '../../memory/conversation-attention-store.js';
22
- import * as conversationStore from '../../memory/conversation-store.js';
23
- import * as externalConversationStore from '../../memory/external-conversation-store.js';
24
- import { findMember, updateLastSeen, upsertMember } from '../../memory/ingress-member-store.js';
25
- import { emitNotificationSignal } from '../../notifications/emit-signal.js';
26
- import { checkIngressForSecrets } from '../../security/secret-ingress.js';
27
- import { canonicalizeInboundIdentity } from '../../util/canonicalize-identity.js';
28
- import { IngressBlockedError } from '../../util/errors.js';
29
- import { getLogger } from '../../util/logger.js';
30
- import { notifyGuardianOfAccessRequest } from '../access-request-helper.js';
31
- import { DAEMON_INTERNAL_ASSISTANT_ID } from '../assistant-scope.js';
32
- import { mintDaemonDeliveryToken } from '../auth/token-service.js';
8
+ CHANNEL_IDS,
9
+ INTERFACE_IDS,
10
+ isChannelId,
11
+ parseInterfaceId,
12
+ } from "../../channels/types.js";
13
+ import { getChannelPermissionProfile } from "../../config/channel-permission-profiles.js";
14
+ import type { TrustContext } from "../../daemon/session-runtime-assembly.js";
15
+ import * as attachmentsStore from "../../memory/attachments-store.js";
16
+ import * as channelDeliveryStore from "../../memory/channel-delivery-store.js";
33
17
  import {
34
- buildApprovalUIMetadata,
35
- getApprovalInfoByConversation,
36
- getChannelApprovalPrompt,
37
- } from '../channel-approvals.js';
38
- import {
39
- bindSessionIdentity,
40
- createOutboundSession,
41
- findActiveSession,
42
- getGuardianBinding,
43
- getPendingChallenge,
44
- resolveBootstrapToken,
45
- updateSessionDelivery,
46
- updateSessionStatus,
47
- validateAndConsumeChallenge,
48
- } from '../channel-guardian-service.js';
49
- import { getTransport } from '../channel-invite-transport.js';
50
- import { deliverChannelReply } from '../gateway-client.js';
51
- import { resolveGuardianContext, resolveRoutingState } from '../guardian-context-resolver.js';
52
- import { routeGuardianReply } from '../guardian-reply-router.js';
53
- import {
54
- composeChannelVerifyReply,
55
- composeVerificationTelegram,
56
- GUARDIAN_VERIFY_TEMPLATE_KEYS,
57
- } from '../guardian-verification-templates.js';
58
- import { httpError } from '../http-errors.js';
18
+ recordConversationSeenSignal,
19
+ type SignalType,
20
+ } from "../../memory/conversation-attention-store.js";
21
+ import * as externalConversationStore from "../../memory/external-conversation-store.js";
22
+ import { canonicalizeInboundIdentity } from "../../util/canonicalize-identity.js";
23
+ import { getLogger } from "../../util/logger.js";
24
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
25
+ import { mintDaemonDeliveryToken } from "../auth/token-service.js";
26
+ import { deliverChannelReply } from "../gateway-client.js";
27
+ import { httpError } from "../http-errors.js";
59
28
  import type {
60
29
  ApprovalConversationGenerator,
61
30
  ApprovalCopyGenerator,
62
31
  GuardianActionCopyGenerator,
63
32
  GuardianFollowUpConversationGenerator,
64
33
  MessageProcessor,
65
- } from '../http-types.js';
66
- import { redeemInvite } from '../invite-redemption-service.js';
67
- import { getInviteRedemptionReply } from '../invite-redemption-templates.js';
68
- import { deliverReplyViaCallback } from './channel-delivery-routes.js';
69
- import {
70
- canonicalChannelAssistantId,
71
- GUARDIAN_APPROVAL_TTL_MS,
72
- type GuardianContext,
73
- stripVerificationFailurePrefix,
74
- } from './channel-route-shared.js';
75
- import { handleApprovalInterception } from './guardian-approval-interception.js';
76
- import { deliverGeneratedApprovalPrompt } from './guardian-approval-prompt.js';
77
-
78
- import '../channel-invite-transports/telegram.js';
79
- import '../channel-invite-transports/voice.js';
80
-
81
- const log = getLogger('runtime-http');
82
-
83
- /**
84
- * Parse a guardian verification code from message content.
85
- * Accepts a bare code as the entire message: 6-digit numeric OR 64-char hex
86
- * (hex is retained for compatibility with unbound inbound/bootstrap sessions
87
- * that intentionally use high-entropy secrets).
88
- */
89
- function parseGuardianVerifyCode(content: string): string | undefined {
90
- const bareMatch = content.match(/^([0-9a-fA-F]{64}|\d{6})$/);
91
- if (bareMatch) return bareMatch[1];
92
-
93
- return undefined;
94
- }
34
+ } from "../http-types.js";
35
+ import { resolveTrustContext } from "../trust-context-resolver.js";
36
+ import { canonicalChannelAssistantId } from "./channel-route-shared.js";
37
+ import { handleApprovalInterception } from "./guardian-approval-interception.js";
38
+ import { enforceIngressAcl } from "./inbound-stages/acl-enforcement.js";
39
+ import { processChannelMessageInBackground } from "./inbound-stages/background-dispatch.js";
40
+ import { handleBootstrapIntercept } from "./inbound-stages/bootstrap-intercept.js";
41
+ import { handleEditIntercept } from "./inbound-stages/edit-intercept.js";
42
+ import { handleEscalationIntercept } from "./inbound-stages/escalation-intercept.js";
43
+ import { handleGuardianReplyIntercept } from "./inbound-stages/guardian-reply-intercept.js";
44
+ import { runSecretIngressCheck } from "./inbound-stages/secret-ingress-check.js";
45
+ import { handleVerificationIntercept } from "./inbound-stages/verification-intercept.js";
46
+
47
+ import "../channel-invite-transports/telegram.js";
48
+ import "../channel-invite-transports/voice.js";
49
+
50
+ const log = getLogger("runtime-http");
95
51
 
96
52
  export async function handleChannelInbound(
97
53
  req: Request,
@@ -113,7 +69,7 @@ export async function handleChannelInbound(
113
69
  // a single token from request start.
114
70
  const mintBearerToken = (): string => mintDaemonDeliveryToken();
115
71
 
116
- const body = await req.json() as {
72
+ const body = (await req.json()) as {
117
73
  sourceChannel?: string;
118
74
  interface?: string;
119
75
  conversationExternalId?: string;
@@ -139,63 +95,92 @@ export async function handleChannelInbound(
139
95
  sourceMetadata,
140
96
  } = body;
141
97
 
142
- if (!body.sourceChannel || typeof body.sourceChannel !== 'string') {
143
- return httpError('BAD_REQUEST', 'sourceChannel is required', 400);
98
+ if (!body.sourceChannel || typeof body.sourceChannel !== "string") {
99
+ return httpError("BAD_REQUEST", "sourceChannel is required", 400);
144
100
  }
145
101
  // Validate and narrow to canonical ChannelId at the boundary — the gateway
146
102
  // only sends well-known channel strings, so an unknown value is rejected.
147
103
  if (!isChannelId(body.sourceChannel)) {
148
- return httpError('BAD_REQUEST', `Invalid sourceChannel: ${body.sourceChannel}. Valid values: ${CHANNEL_IDS.join(', ')}`, 400);
104
+ return httpError(
105
+ "BAD_REQUEST",
106
+ `Invalid sourceChannel: ${
107
+ body.sourceChannel
108
+ }. Valid values: ${CHANNEL_IDS.join(", ")}`,
109
+ 400,
110
+ );
149
111
  }
150
112
 
151
113
  const sourceChannel = body.sourceChannel;
152
114
 
153
- if (!body.interface || typeof body.interface !== 'string') {
154
- return httpError('BAD_REQUEST', 'interface is required', 400);
115
+ if (!body.interface || typeof body.interface !== "string") {
116
+ return httpError("BAD_REQUEST", "interface is required", 400);
155
117
  }
156
118
  const sourceInterface = parseInterfaceId(body.interface);
157
119
  if (!sourceInterface) {
158
- return httpError('BAD_REQUEST', `Invalid interface: ${body.interface}. Valid values: ${INTERFACE_IDS.join(', ')}`, 400);
120
+ return httpError(
121
+ "BAD_REQUEST",
122
+ `Invalid interface: ${body.interface}. Valid values: ${INTERFACE_IDS.join(
123
+ ", ",
124
+ )}`,
125
+ 400,
126
+ );
159
127
  }
160
128
 
161
- if (!conversationExternalId || typeof conversationExternalId !== 'string') {
162
- return httpError('BAD_REQUEST', 'conversationExternalId is required', 400);
129
+ if (!conversationExternalId || typeof conversationExternalId !== "string") {
130
+ return httpError("BAD_REQUEST", "conversationExternalId is required", 400);
163
131
  }
164
- if (!body.actorExternalId || typeof body.actorExternalId !== 'string' || !body.actorExternalId.trim()) {
165
- return httpError('BAD_REQUEST', 'actorExternalId is required', 400);
132
+ if (
133
+ !body.actorExternalId ||
134
+ typeof body.actorExternalId !== "string" ||
135
+ !body.actorExternalId.trim()
136
+ ) {
137
+ return httpError("BAD_REQUEST", "actorExternalId is required", 400);
166
138
  }
167
- if (!externalMessageId || typeof externalMessageId !== 'string') {
168
- return httpError('BAD_REQUEST', 'externalMessageId is required', 400);
139
+ if (!externalMessageId || typeof externalMessageId !== "string") {
140
+ return httpError("BAD_REQUEST", "externalMessageId is required", 400);
169
141
  }
170
142
 
171
143
  // Reject non-string content regardless of whether attachments are present.
172
- if (content != null && typeof content !== 'string') {
173
- return httpError('BAD_REQUEST', 'content must be a string', 400);
144
+ if (content != null && typeof content !== "string") {
145
+ return httpError("BAD_REQUEST", "content must be a string", 400);
174
146
  }
175
147
 
176
- const trimmedContent = typeof content === 'string' ? content.trim() : '';
177
- const hasAttachments = Array.isArray(attachmentIds) && attachmentIds.length > 0;
148
+ const trimmedContent = typeof content === "string" ? content.trim() : "";
149
+ const hasAttachments =
150
+ Array.isArray(attachmentIds) && attachmentIds.length > 0;
178
151
 
179
- const hasCallbackData = typeof body.callbackData === 'string' && body.callbackData.length > 0;
152
+ const hasCallbackData =
153
+ typeof body.callbackData === "string" && body.callbackData.length > 0;
180
154
 
181
- if (trimmedContent.length === 0 && !hasAttachments && !isEdit && !hasCallbackData) {
182
- return httpError('BAD_REQUEST', 'content or attachmentIds is required', 400);
155
+ if (
156
+ trimmedContent.length === 0 &&
157
+ !hasAttachments &&
158
+ !isEdit &&
159
+ !hasCallbackData
160
+ ) {
161
+ return httpError(
162
+ "BAD_REQUEST",
163
+ "content or attachmentIds is required",
164
+ 400,
165
+ );
183
166
  }
184
167
 
185
168
  // Canonicalize the assistant ID so all DB-facing operations use the
186
169
  // consistent 'self' key regardless of what the gateway sent.
187
170
  const canonicalAssistantId = canonicalChannelAssistantId(assistantId);
188
171
  if (canonicalAssistantId !== assistantId) {
189
- log.debug({ raw: assistantId, canonical: canonicalAssistantId }, 'Canonicalized channel assistant ID');
172
+ log.debug(
173
+ { raw: assistantId, canonical: canonicalAssistantId },
174
+ "Canonicalized channel assistant ID",
175
+ );
190
176
  }
191
177
 
192
178
  // Coerce actorExternalId to a string at the boundary — the field
193
179
  // comes from unvalidated JSON and may be a number, object, or other
194
180
  // non-string type. Non-string truthy values would throw inside
195
181
  // canonicalizeInboundIdentity when it calls .trim().
196
- const rawSenderId = body.actorExternalId != null
197
- ? String(body.actorExternalId)
198
- : undefined;
182
+ const rawSenderId =
183
+ body.actorExternalId != null ? String(body.actorExternalId) : undefined;
199
184
 
200
185
  // Canonicalize the sender identity so all trust lookups, member matching,
201
186
  // and guardian binding comparisons use a normalized form. Phone-like
@@ -212,255 +197,24 @@ export async function handleChannelInbound(
212
197
  const hasSenderIdentityClaim = rawSenderId !== undefined;
213
198
 
214
199
  // ── Ingress ACL enforcement ──
215
- // Track the resolved member so the escalate branch can reference it after
216
- // recordInbound (where we have a conversationId).
217
- let resolvedMember: ReturnType<typeof findMember> = null;
218
-
219
- // Verification codes must bypass the ACL membership check — users without a
220
- // member record need to verify before they can be recognized as members.
221
- const guardianVerifyCode = parseGuardianVerifyCode(trimmedContent);
222
- const isGuardianVerifyCode = guardianVerifyCode !== undefined;
223
-
224
- // /start gv_<token> bootstrap commands must also bypass ACL — the user
225
- // hasn't been verified yet and needs to complete the bootstrap handshake.
226
- const rawCommandIntentForAcl = sourceMetadata?.commandIntent;
227
- const isBootstrapCommand = rawCommandIntentForAcl &&
228
- typeof rawCommandIntentForAcl === 'object' &&
229
- !Array.isArray(rawCommandIntentForAcl) &&
230
- (rawCommandIntentForAcl as Record<string, unknown>).type === 'start' &&
231
- typeof (rawCommandIntentForAcl as Record<string, unknown>).payload === 'string' &&
232
- ((rawCommandIntentForAcl as Record<string, unknown>).payload as string).startsWith('gv_');
233
-
234
- // Parse invite token from /start payloads using the channel transport
235
- // adapter. The token is extracted once here so both the ACL bypass and
236
- // the intercept handler can reference it without re-parsing.
237
- const commandIntentForAcl = rawCommandIntentForAcl && typeof rawCommandIntentForAcl === 'object' && !Array.isArray(rawCommandIntentForAcl)
238
- ? rawCommandIntentForAcl as Record<string, unknown>
239
- : undefined;
240
- const inviteTransport = getTransport(sourceChannel);
241
- const inviteToken = inviteTransport?.extractInboundToken({
242
- commandIntent: commandIntentForAcl,
243
- content: trimmedContent,
200
+ const aclResult = await enforceIngressAcl({
201
+ canonicalSenderId,
202
+ hasSenderIdentityClaim,
203
+ rawSenderId,
204
+ sourceChannel,
205
+ conversationExternalId,
206
+ canonicalAssistantId,
207
+ trimmedContent,
244
208
  sourceMetadata: body.sourceMetadata,
209
+ actorDisplayName: body.actorDisplayName,
210
+ actorUsername: body.actorUsername,
211
+ replyCallbackUrl: body.replyCallbackUrl,
212
+ mintBearerToken,
213
+ assistantId,
214
+ externalMessageId,
245
215
  });
246
-
247
- if (canonicalSenderId || hasSenderIdentityClaim) {
248
- // Only perform member lookup when we have a usable canonical ID.
249
- // Whitespace-only senders (hasSenderIdentityClaim=true but
250
- // canonicalSenderId=null) skip the lookup and fall into the deny path.
251
- if (canonicalSenderId) {
252
- resolvedMember = findMember({
253
- assistantId: canonicalAssistantId,
254
- sourceChannel,
255
- externalUserId: canonicalSenderId,
256
- externalChatId: conversationExternalId,
257
- });
258
- }
259
-
260
- if (!resolvedMember) {
261
- // Determine whether a verification-code bypass is warranted: only allow
262
- // when there is a pending (unconsumed, unexpired) challenge AND no
263
- // active guardian binding for this (assistantId, channel).
264
- let denyNonMember = true;
265
- if (isGuardianVerifyCode) {
266
- // Allow bypass when there is any consumable challenge or active
267
- // outbound session. The !hasActiveBinding guard is intentionally
268
- // omitted: rebind sessions create a consumable challenge while a
269
- // binding already exists, and the identity check inside
270
- // validateAndConsumeChallenge prevents unauthorized takeovers.
271
- const hasPendingChallenge = !!getPendingChallenge(canonicalAssistantId, sourceChannel);
272
- const hasActiveOutboundSession = !!findActiveSession(canonicalAssistantId, sourceChannel);
273
- if (hasPendingChallenge || hasActiveOutboundSession) {
274
- denyNonMember = false;
275
- } else {
276
- log.info({ sourceChannel, hasPendingChallenge, hasActiveOutboundSession }, 'Ingress ACL: guardian verification bypass denied');
277
- }
278
- }
279
-
280
- // Bootstrap deep-link commands bypass ACL only when the token
281
- // resolves to a real pending_bootstrap session. Without this check,
282
- // any `/start gv_<garbage>` would bypass the not_a_member gate and
283
- // fall through to normal /start processing.
284
- if (isBootstrapCommand) {
285
- const bootstrapPayload = (rawCommandIntentForAcl as Record<string, unknown>).payload as string;
286
- const bootstrapTokenForAcl = bootstrapPayload.slice(3); // strip 'gv_' prefix
287
- const bootstrapSessionForAcl = resolveBootstrapToken(canonicalAssistantId, sourceChannel, bootstrapTokenForAcl);
288
- if (bootstrapSessionForAcl && bootstrapSessionForAcl.status === 'pending_bootstrap') {
289
- denyNonMember = false;
290
- } else {
291
- log.info({ sourceChannel, hasValidBootstrapSession: false }, 'Ingress ACL: bootstrap command bypass denied — no valid pending_bootstrap session');
292
- }
293
- }
294
-
295
- // ── Invite token intercept (non-member) ──
296
- // /start invite deep links grant access without guardian approval.
297
- // Intercept here — before the deny gate — so valid invites short-circuit
298
- // the ACL rejection and never reach the agent pipeline.
299
- if (inviteToken && denyNonMember) {
300
- const inviteResult = await handleInviteTokenIntercept({
301
- rawToken: inviteToken,
302
- sourceChannel,
303
- externalChatId: conversationExternalId,
304
- externalMessageId,
305
- senderExternalUserId: canonicalSenderId ?? rawSenderId,
306
- senderName: body.actorDisplayName,
307
- senderUsername: body.actorUsername,
308
- replyCallbackUrl: body.replyCallbackUrl,
309
- bearerToken: mintBearerToken(),
310
- assistantId,
311
- canonicalAssistantId,
312
- });
313
- if (inviteResult) return inviteResult;
314
- }
315
-
316
- if (denyNonMember) {
317
- log.info({ sourceChannel, externalUserId: canonicalSenderId }, 'Ingress ACL: no member record, denying');
318
-
319
- // Notify the guardian about the access request so they can approve/deny.
320
- // Uses the shared helper which handles guardian binding lookup,
321
- // deduplication, canonical request creation, and notification emission.
322
- let guardianNotified = false;
323
- try {
324
- const accessResult = notifyGuardianOfAccessRequest({
325
- canonicalAssistantId,
326
- sourceChannel,
327
- conversationExternalId,
328
- actorExternalId: canonicalSenderId ?? rawSenderId,
329
- actorDisplayName: body.actorDisplayName,
330
- actorUsername: body.actorUsername,
331
- });
332
- guardianNotified = accessResult.notified;
333
- } catch (err) {
334
- log.error({ err, sourceChannel, conversationExternalId }, 'Failed to notify guardian of access request');
335
- }
336
-
337
- if (body.replyCallbackUrl) {
338
- const replyText = guardianNotified
339
- ? "Hmm looks like you don't have access to talk to me. I'll let them know you tried talking to me and get back to you."
340
- : "Sorry, you haven't been approved to message this assistant.";
341
- try {
342
- await deliverChannelReply(body.replyCallbackUrl, {
343
- chatId: conversationExternalId,
344
- text: replyText,
345
- assistantId,
346
- }, mintBearerToken());
347
- } catch (err) {
348
- log.error({ err, conversationExternalId }, 'Failed to deliver ACL rejection reply');
349
- }
350
- }
351
-
352
- return Response.json({ accepted: true, denied: true, reason: 'not_a_member' });
353
- }
354
- }
355
-
356
- if (resolvedMember) {
357
- if (resolvedMember.status !== 'active') {
358
- // Same bypass logic as the no-member branch: verification codes and
359
- // bootstrap commands must pass through even when the member record is
360
- // revoked/blocked — otherwise the user can never re-verify.
361
- let denyInactiveMember = true;
362
- if (isGuardianVerifyCode) {
363
- const hasPendingChallenge = !!getPendingChallenge(canonicalAssistantId, sourceChannel);
364
- const hasActiveOutboundSession = !!findActiveSession(canonicalAssistantId, sourceChannel);
365
- if (hasPendingChallenge || hasActiveOutboundSession) {
366
- denyInactiveMember = false;
367
- } else {
368
- log.info({ sourceChannel, memberId: resolvedMember.id, hasPendingChallenge, hasActiveOutboundSession }, 'Ingress ACL: inactive member verification bypass denied');
369
- }
370
- }
371
- if (isBootstrapCommand) {
372
- const bootstrapPayload = (rawCommandIntentForAcl as Record<string, unknown>).payload as string;
373
- const bootstrapTokenForAcl = bootstrapPayload.slice(3);
374
- const bootstrapSessionForAcl = resolveBootstrapToken(canonicalAssistantId, sourceChannel, bootstrapTokenForAcl);
375
- if (bootstrapSessionForAcl && bootstrapSessionForAcl.status === 'pending_bootstrap') {
376
- denyInactiveMember = false;
377
- } else {
378
- log.info({ sourceChannel, memberId: resolvedMember.id, hasValidBootstrapSession: false }, 'Ingress ACL: inactive member bootstrap bypass denied');
379
- }
380
- }
381
-
382
- // ── Invite token intercept (inactive member) ──
383
- // Same as the non-member branch: invite tokens can reactivate
384
- // revoked/pending members without requiring guardian approval.
385
- if (inviteToken && denyInactiveMember) {
386
- const inviteResult = await handleInviteTokenIntercept({
387
- rawToken: inviteToken,
388
- sourceChannel,
389
- externalChatId: conversationExternalId,
390
- externalMessageId,
391
- senderExternalUserId: canonicalSenderId ?? rawSenderId,
392
- senderName: body.actorDisplayName,
393
- senderUsername: body.actorUsername,
394
- replyCallbackUrl: body.replyCallbackUrl,
395
- bearerToken: mintBearerToken(),
396
- assistantId,
397
- canonicalAssistantId,
398
- });
399
- if (inviteResult) return inviteResult;
400
- }
401
-
402
- if (denyInactiveMember) {
403
- log.info({ sourceChannel, memberId: resolvedMember.id, status: resolvedMember.status }, 'Ingress ACL: member not active, denying');
404
-
405
- // For revoked/pending members, notify the guardian so they can
406
- // re-approve. Blocked members are intentionally excluded — the
407
- // guardian already made an explicit decision to block them.
408
- let guardianNotified = false;
409
- if (resolvedMember.status !== 'blocked') {
410
- try {
411
- const accessResult = notifyGuardianOfAccessRequest({
412
- canonicalAssistantId,
413
- sourceChannel,
414
- conversationExternalId,
415
- actorExternalId: canonicalSenderId ?? rawSenderId,
416
- actorDisplayName: body.actorDisplayName,
417
- actorUsername: body.actorUsername,
418
- previousMemberStatus: resolvedMember.status,
419
- });
420
- guardianNotified = accessResult.notified;
421
- } catch (err) {
422
- log.error({ err, sourceChannel, conversationExternalId }, 'Failed to notify guardian of access request');
423
- }
424
- }
425
-
426
- if (body.replyCallbackUrl) {
427
- const replyText = guardianNotified
428
- ? "Hmm looks like you don't have access to talk to me. I'll let them know you tried talking to me and get back to you."
429
- : "Sorry, you haven't been approved to message this assistant.";
430
- try {
431
- await deliverChannelReply(body.replyCallbackUrl, {
432
- chatId: conversationExternalId,
433
- text: replyText,
434
- assistantId,
435
- }, mintBearerToken());
436
- } catch (err) {
437
- log.error({ err, conversationExternalId }, 'Failed to deliver ACL rejection reply');
438
- }
439
- }
440
- return Response.json({ accepted: true, denied: true, reason: `member_${resolvedMember.status}` });
441
- }
442
- }
443
-
444
- if (resolvedMember.policy === 'deny') {
445
- log.info({ sourceChannel, memberId: resolvedMember.id }, 'Ingress ACL: member policy deny');
446
- if (body.replyCallbackUrl) {
447
- try {
448
- await deliverChannelReply(body.replyCallbackUrl, {
449
- chatId: conversationExternalId,
450
- text: "Sorry, you haven't been approved to message this assistant.",
451
- assistantId,
452
- }, mintBearerToken());
453
- } catch (err) {
454
- log.error({ err, conversationExternalId }, 'Failed to deliver ACL rejection reply');
455
- }
456
- }
457
- return Response.json({ accepted: true, denied: true, reason: 'policy_deny' });
458
- }
459
-
460
- // 'allow' or 'escalate' — update last seen and continue
461
- updateLastSeen(resolvedMember.id);
462
- }
463
- }
216
+ if (aclResult.earlyResponse) return aclResult.earlyResponse;
217
+ const { resolvedMember, guardianVerifyCode } = aclResult;
464
218
 
465
219
  if (hasAttachments) {
466
220
  const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds);
@@ -468,78 +222,35 @@ export async function handleChannelInbound(
468
222
  const resolvedIds = new Set(resolved.map((a) => a.id));
469
223
  const missing = attachmentIds.filter((id) => !resolvedIds.has(id));
470
224
  return Response.json(
471
- { error: `Attachment IDs not found: ${missing.join(', ')}` },
225
+ { error: `Attachment IDs not found: ${missing.join(", ")}` },
472
226
  { status: 400 },
473
227
  );
474
228
  }
475
229
  }
476
230
 
477
- const sourceMessageId = typeof sourceMetadata?.messageId === 'string'
478
- ? sourceMetadata.messageId
479
- : undefined;
231
+ const sourceMessageId =
232
+ typeof sourceMetadata?.messageId === "string"
233
+ ? sourceMetadata.messageId
234
+ : undefined;
480
235
 
481
236
  if (isEdit && !sourceMessageId) {
482
- return httpError('BAD_REQUEST', 'sourceMetadata.messageId is required for edits', 400);
237
+ return httpError(
238
+ "BAD_REQUEST",
239
+ "sourceMetadata.messageId is required for edits",
240
+ 400,
241
+ );
483
242
  }
484
243
 
485
244
  // ── Edit path: update existing message content, no new agent loop ──
486
245
  if (isEdit && sourceMessageId) {
487
- // Dedup the edit event itself (retried edited_message webhooks)
488
- const editResult = channelDeliveryStore.recordInbound(
246
+ return handleEditIntercept({
489
247
  sourceChannel,
490
248
  conversationExternalId,
491
249
  externalMessageId,
492
- { sourceMessageId, assistantId: canonicalAssistantId },
493
- );
494
-
495
- if (editResult.duplicate) {
496
- return Response.json({
497
- accepted: true,
498
- duplicate: true,
499
- eventId: editResult.eventId,
500
- });
501
- }
502
-
503
- // Retry lookup a few times — the original message may still be processing
504
- // (linkMessage hasn't been called yet). Short backoff avoids losing edits
505
- // that arrive while the original agent loop is in progress.
506
- const EDIT_LOOKUP_RETRIES = 5;
507
- const EDIT_LOOKUP_DELAY_MS = 2000;
508
-
509
- let original: { messageId: string; conversationId: string } | null = null;
510
- for (let attempt = 0; attempt <= EDIT_LOOKUP_RETRIES; attempt++) {
511
- original = channelDeliveryStore.findMessageBySourceId(
512
- sourceChannel,
513
- conversationExternalId,
514
- sourceMessageId,
515
- );
516
- if (original) break;
517
- if (attempt < EDIT_LOOKUP_RETRIES) {
518
- log.info(
519
- { assistantId, sourceMessageId, attempt: attempt + 1, maxAttempts: EDIT_LOOKUP_RETRIES },
520
- 'Original message not linked yet, retrying edit lookup',
521
- );
522
- await new Promise((resolve) => setTimeout(resolve, EDIT_LOOKUP_DELAY_MS));
523
- }
524
- }
525
-
526
- if (original) {
527
- conversationStore.updateMessageContent(original.messageId, content ?? '');
528
- log.info(
529
- { assistantId, sourceMessageId, messageId: original.messageId },
530
- 'Updated message content from edited_message',
531
- );
532
- } else {
533
- log.warn(
534
- { assistantId, sourceChannel, conversationExternalId, sourceMessageId },
535
- 'Could not find original message for edit after retries, ignoring',
536
- );
537
- }
538
-
539
- return Response.json({
540
- accepted: true,
541
- duplicate: false,
542
- eventId: editResult.eventId,
250
+ sourceMessageId,
251
+ canonicalAssistantId,
252
+ assistantId,
253
+ content,
543
254
  });
544
255
  }
545
256
 
@@ -558,18 +269,30 @@ export async function handleChannelInbound(
558
269
  // gateway retries (duplicates) re-attempt delivery here. On success the
559
270
  // pending marker is cleared so further duplicates short-circuit normally.
560
271
  if (result.duplicate && replyCallbackUrl) {
561
- const pendingReply = channelDeliveryStore.getPendingVerificationReply(result.eventId);
272
+ const pendingReply = channelDeliveryStore.getPendingVerificationReply(
273
+ result.eventId,
274
+ );
562
275
  if (pendingReply) {
563
276
  try {
564
- await deliverChannelReply(replyCallbackUrl, {
565
- chatId: pendingReply.chatId,
566
- text: pendingReply.text,
567
- assistantId: pendingReply.assistantId,
568
- }, mintBearerToken());
277
+ await deliverChannelReply(
278
+ replyCallbackUrl,
279
+ {
280
+ chatId: pendingReply.chatId,
281
+ text: pendingReply.text,
282
+ assistantId: pendingReply.assistantId,
283
+ },
284
+ mintBearerToken(),
285
+ );
569
286
  channelDeliveryStore.clearPendingVerificationReply(result.eventId);
570
- log.info({ eventId: result.eventId }, 'Retried pending verification reply: delivered');
287
+ log.info(
288
+ { eventId: result.eventId },
289
+ "Retried pending verification reply: delivered",
290
+ );
571
291
  } catch (retryErr) {
572
- log.error({ err: retryErr, eventId: result.eventId }, 'Retry of pending verification reply failed; will retry on next duplicate');
292
+ log.error(
293
+ { err: retryErr, eventId: result.eventId },
294
+ "Retry of pending verification reply failed; will retry on next duplicate",
295
+ );
573
296
  }
574
297
  return Response.json({
575
298
  accepted: true,
@@ -594,322 +317,109 @@ export async function handleChannelInbound(
594
317
  }
595
318
 
596
319
  // ── Ingress escalation ──
597
- // When the member's policy is 'escalate', create a pending guardian
598
- // approval request and halt the run. This check runs after recordInbound
599
- // so we have a conversationId for the approval record.
600
- if (resolvedMember?.policy === 'escalate') {
601
- const binding = getGuardianBinding(canonicalAssistantId, sourceChannel);
602
- if (!binding) {
603
- // Fail-closed: can't escalate without a guardian to route to
604
- log.info({ sourceChannel, memberId: resolvedMember.id }, 'Ingress ACL: escalate policy but no guardian binding, denying');
605
- return Response.json({ accepted: true, denied: true, reason: 'escalate_no_guardian' });
606
- }
320
+ const escalationResponse = handleEscalationIntercept({
321
+ resolvedMember,
322
+ canonicalAssistantId,
323
+ sourceChannel,
324
+ sourceInterface,
325
+ conversationExternalId,
326
+ externalMessageId,
327
+ conversationId: result.conversationId,
328
+ eventId: result.eventId,
329
+ content,
330
+ attachmentIds,
331
+ sourceMetadata: body.sourceMetadata,
332
+ actorDisplayName: body.actorDisplayName,
333
+ actorExternalId: body.actorExternalId,
334
+ actorUsername: body.actorUsername,
335
+ replyCallbackUrl: body.replyCallbackUrl,
336
+ canonicalSenderId,
337
+ rawSenderId,
338
+ });
339
+ if (escalationResponse) return escalationResponse;
607
340
 
608
- // Persist the raw payload so the decide handler can recover the original
609
- // message content when the escalation is approved.
610
- channelDeliveryStore.storePayload(result.eventId, {
611
- sourceChannel, interface: sourceInterface, externalChatId: conversationExternalId, externalMessageId, content,
612
- attachmentIds, sourceMetadata: body.sourceMetadata,
613
- senderName: body.actorDisplayName,
614
- senderExternalUserId: body.actorExternalId,
615
- senderUsername: body.actorUsername,
616
- replyCallbackUrl: body.replyCallbackUrl,
617
- assistantId: canonicalAssistantId,
618
- });
341
+ const metadataHintsRaw = sourceMetadata?.hints;
342
+ const metadataHints = Array.isArray(metadataHintsRaw)
343
+ ? metadataHintsRaw.filter(
344
+ (hint): hint is string =>
345
+ typeof hint === "string" && hint.trim().length > 0,
346
+ )
347
+ : [];
619
348
 
620
- try {
621
- createCanonicalGuardianRequest({
622
- kind: 'tool_approval',
623
- sourceType: 'channel',
624
- sourceChannel,
625
- conversationId: result.conversationId,
626
- requesterExternalUserId: canonicalSenderId ?? rawSenderId ?? undefined,
627
- guardianExternalUserId: binding.guardianExternalUserId,
628
- guardianPrincipalId: binding.guardianPrincipalId,
629
- toolName: 'ingress_message',
630
- questionText: 'Ingress policy requires guardian approval',
631
- expiresAt: new Date(Date.now() + GUARDIAN_APPROVAL_TTL_MS).toISOString(),
632
- });
633
- } catch (err) {
634
- log.warn(
635
- { err, conversationId: result.conversationId, sourceChannel },
636
- 'Failed to create canonical guardian request for ingress escalation escalation continues via notification pipeline',
637
- );
349
+ // Inject channel-scoped permission hints for Slack channel messages
350
+ if (sourceChannel === "slack") {
351
+ const channelProfile = getChannelPermissionProfile(conversationExternalId);
352
+ if (channelProfile) {
353
+ if (channelProfile.blockedTools?.length) {
354
+ metadataHints.push(
355
+ `Channel policy: the following tools are blocked in this channel: ${channelProfile.blockedTools.join(", ")}`,
356
+ );
357
+ }
358
+ if (channelProfile.allowedToolCategories?.length) {
359
+ metadataHints.push(
360
+ `Channel policy: only these tool categories are allowed in this channel: ${channelProfile.allowedToolCategories.join(", ")}`,
361
+ );
362
+ }
363
+ if (channelProfile.trustLevel === "restricted") {
364
+ metadataHints.push(
365
+ "Channel policy: this channel has restricted trust level. Exercise caution with tool usage.",
366
+ );
367
+ }
638
368
  }
639
-
640
- // Emit notification signal through the unified pipeline (fire-and-forget).
641
- // This lets the decision engine route escalation alerts to all configured
642
- // channels, supplementing the direct guardian notification below.
643
- void emitNotificationSignal({
644
- sourceEventName: 'ingress.escalation',
645
- sourceChannel: sourceChannel,
646
- sourceSessionId: result.conversationId,
647
- assistantId: canonicalAssistantId,
648
- attentionHints: {
649
- requiresAction: true,
650
- urgency: 'high',
651
- isAsyncBackground: false,
652
- visibleInSourceNow: false,
653
- },
654
- contextPayload: {
655
- conversationId: result.conversationId,
656
- sourceChannel,
657
- conversationExternalId,
658
- senderIdentifier: body.actorDisplayName || body.actorUsername || rawSenderId || 'Unknown sender',
659
- eventId: result.eventId,
660
- },
661
- dedupeKey: `escalation:${result.eventId}`,
662
- });
663
-
664
- // Guardian escalation channel delivery is handled by the notification
665
- // pipeline — no legacy callback dispatch needed.
666
- log.info(
667
- { conversationId: result.conversationId },
668
- 'Guardian escalation created — notification pipeline handles channel delivery',
669
- );
670
-
671
- return Response.json({ accepted: true, escalated: true, reason: 'policy_escalate' });
672
369
  }
673
370
 
674
- const metadataHintsRaw = sourceMetadata?.hints;
675
- const metadataHints = Array.isArray(metadataHintsRaw)
676
- ? metadataHintsRaw.filter((hint): hint is string => typeof hint === 'string' && hint.trim().length > 0)
677
- : [];
678
- const metadataUxBrief = typeof sourceMetadata?.uxBrief === 'string' && sourceMetadata.uxBrief.trim().length > 0
679
- ? sourceMetadata.uxBrief.trim()
680
- : undefined;
371
+ const metadataUxBrief =
372
+ typeof sourceMetadata?.uxBrief === "string" &&
373
+ sourceMetadata.uxBrief.trim().length > 0
374
+ ? sourceMetadata.uxBrief.trim()
375
+ : undefined;
681
376
 
682
377
  // Extract channel command intent (e.g. /start from Telegram)
683
378
  const rawCommandIntent = sourceMetadata?.commandIntent;
684
- const commandIntent = rawCommandIntent && typeof rawCommandIntent === 'object' && !Array.isArray(rawCommandIntent)
685
- ? rawCommandIntent as Record<string, unknown>
686
- : undefined;
379
+ const commandIntent =
380
+ rawCommandIntent &&
381
+ typeof rawCommandIntent === "object" &&
382
+ !Array.isArray(rawCommandIntent)
383
+ ? (rawCommandIntent as Record<string, unknown>)
384
+ : undefined;
687
385
 
688
386
  // Preserve locale from sourceMetadata so the model can greet in the user's language
689
- const sourceLanguageCode = typeof sourceMetadata?.languageCode === 'string' && sourceMetadata.languageCode.trim().length > 0
690
- ? sourceMetadata.languageCode.trim()
691
- : undefined;
387
+ const sourceLanguageCode =
388
+ typeof sourceMetadata?.languageCode === "string" &&
389
+ sourceMetadata.languageCode.trim().length > 0
390
+ ? sourceMetadata.languageCode.trim()
391
+ : undefined;
692
392
 
693
393
  // ── Telegram bootstrap deep-link handling ──
694
- // Intercept /start gv_<token> commands BEFORE the verification-code intercept.
695
- // When a user clicks the deep link, Telegram sends /start gv_<token> which
696
- // the gateway forwards with commandIntent: { type: 'start', payload: 'gv_<token>' }.
697
- // We resolve the bootstrap token, bind the session identity, create a new
698
- // identity-bound session with a fresh verification code, send it, and return.
699
- if (
700
- !result.duplicate &&
701
- commandIntent?.type === 'start' &&
702
- typeof commandIntent.payload === 'string' &&
703
- (commandIntent.payload as string).startsWith('gv_') &&
704
- rawSenderId
705
- ) {
706
- const bootstrapToken = (commandIntent.payload as string).slice(3);
707
- const bootstrapSession = resolveBootstrapToken(canonicalAssistantId, sourceChannel, bootstrapToken);
708
-
709
- if (bootstrapSession && bootstrapSession.status === 'pending_bootstrap') {
710
- // Bind the pending_bootstrap session to the sender's identity
711
- bindSessionIdentity(bootstrapSession.id, rawSenderId!, conversationExternalId);
712
-
713
- // Transition bootstrap session to awaiting_response
714
- updateSessionStatus(bootstrapSession.id, 'awaiting_response');
715
-
716
- // Create a new identity-bound outbound session with a fresh secret.
717
- // The old bootstrap session is auto-revoked by createOutboundSession.
718
- const newSession = createOutboundSession({
719
- assistantId: canonicalAssistantId,
720
- channel: sourceChannel,
721
- expectedExternalUserId: rawSenderId!,
722
- expectedChatId: conversationExternalId,
723
- identityBindingStatus: 'bound',
724
- destinationAddress: conversationExternalId,
725
- });
726
-
727
- // Compose and send the verification prompt via Telegram
728
- const telegramBody = composeVerificationTelegram(
729
- GUARDIAN_VERIFY_TEMPLATE_KEYS.TELEGRAM_CHALLENGE_REQUEST,
730
- {
731
- code: newSession.secret,
732
- expiresInMinutes: Math.floor((newSession.expiresAt - Date.now()) / 60_000),
733
- },
734
- );
735
-
736
- // Deliver verification Telegram message via the gateway (fire-and-forget)
737
- deliverBootstrapVerificationTelegram(conversationExternalId, telegramBody, canonicalAssistantId);
738
-
739
- // Update delivery tracking
740
- const now = Date.now();
741
- updateSessionDelivery(newSession.sessionId, now, 1, now + RESEND_COOLDOWN_MS);
742
-
743
- return Response.json({
744
- accepted: true,
745
- duplicate: false,
746
- eventId: result.eventId,
747
- guardianVerification: 'bootstrap_bound',
748
- });
749
- }
750
- // If not found or expired, fall through to normal /start handling
751
- }
394
+ const bootstrapResponse = await handleBootstrapIntercept({
395
+ isDuplicate: result.duplicate,
396
+ commandIntent,
397
+ rawSenderId,
398
+ canonicalAssistantId,
399
+ sourceChannel,
400
+ conversationExternalId,
401
+ eventId: result.eventId,
402
+ });
403
+ if (bootstrapResponse) return bootstrapResponse;
752
404
 
753
405
  // ── Guardian verification code intercept (deterministic) ──
754
- // Validate/consume the challenge synchronously so side effects (member
755
- // upsert, binding creation) complete before any reply. The reply is
756
- // delivered via template-driven deterministic messages and the code
757
- // is short-circuited — it NEVER enters the agent pipeline. This
758
- // prevents verification code messages from producing agent-generated copy.
759
- //
760
- // Bare 6-digit codes are only intercepted when there is actually a
761
- // pending challenge or active outbound session for this channel.
762
- // Without this guard, normal 6-digit messages (zip codes, PINs, etc.)
763
- // would be swallowed by the verification handler and never reach the
764
- // agent pipeline.
765
- const shouldInterceptVerification = guardianVerifyCode !== undefined &&
766
- (!!getPendingChallenge(canonicalAssistantId, sourceChannel) ||
767
- !!findActiveSession(canonicalAssistantId, sourceChannel));
768
-
769
- if (
770
- !result.duplicate &&
771
- shouldInterceptVerification &&
772
- guardianVerifyCode !== undefined &&
773
- rawSenderId
774
- ) {
775
- const verifyResult = validateAndConsumeChallenge(
776
- canonicalAssistantId,
777
- sourceChannel,
778
- guardianVerifyCode,
779
- canonicalSenderId ?? rawSenderId!,
780
- conversationExternalId,
781
- body.actorUsername,
782
- body.actorDisplayName,
783
- );
784
-
785
- const guardianVerifyOutcome: 'verified' | 'failed' = verifyResult.success ? 'verified' : 'failed';
786
-
787
- if (verifyResult.success) {
788
- const existingMember = (canonicalSenderId ?? rawSenderId)
789
- ? findMember({
790
- assistantId: canonicalAssistantId,
791
- sourceChannel,
792
- externalUserId: canonicalSenderId ?? rawSenderId!,
793
- externalChatId: conversationExternalId,
794
- })
795
- : null;
796
- const memberMatchesSender = existingMember?.externalUserId
797
- ? canonicalizeInboundIdentity(sourceChannel, existingMember.externalUserId) === (canonicalSenderId ?? rawSenderId)
798
- : false;
799
- const preservedDisplayName = memberMatchesSender && existingMember?.displayName?.trim().length
800
- ? existingMember.displayName
801
- : body.actorDisplayName;
802
-
803
- upsertMember({
804
- assistantId: canonicalAssistantId,
805
- sourceChannel,
806
- externalUserId: canonicalSenderId ?? rawSenderId!,
807
- externalChatId: conversationExternalId,
808
- status: 'active',
809
- policy: 'allow',
810
- // Keep guardian-curated member name stable across re-verification.
811
- displayName: preservedDisplayName,
812
- username: body.actorUsername,
813
- });
814
-
815
- const verifyLogLabel = verifyResult.verificationType === 'trusted_contact'
816
- ? 'Trusted contact verified'
817
- : 'Guardian verified';
818
- log.info({ sourceChannel, externalUserId: canonicalSenderId, verificationType: verifyResult.verificationType }, `${verifyLogLabel}: auto-upserted ingress member`);
819
-
820
- // Emit activated signal when a trusted contact completes verification.
821
- // Member record is persisted above before this event fires, satisfying
822
- // the persistence-before-event ordering invariant.
823
- if (verifyResult.verificationType === 'trusted_contact') {
824
- void emitNotificationSignal({
825
- sourceEventName: 'ingress.trusted_contact.activated',
826
- sourceChannel,
827
- sourceSessionId: result.conversationId,
828
- assistantId: canonicalAssistantId,
829
- attentionHints: {
830
- requiresAction: false,
831
- urgency: 'low',
832
- isAsyncBackground: false,
833
- visibleInSourceNow: false,
834
- },
835
- contextPayload: {
836
- sourceChannel,
837
- actorExternalId: canonicalSenderId ?? rawSenderId!,
838
- conversationExternalId,
839
- actorDisplayName: body.actorDisplayName ?? null,
840
- actorUsername: body.actorUsername ?? null,
841
- },
842
- dedupeKey: `trusted-contact:activated:${canonicalAssistantId}:${sourceChannel}:${canonicalSenderId ?? rawSenderId!}`,
843
- });
844
- }
845
- }
846
-
847
- // Deliver a deterministic template-driven reply and short-circuit.
848
- // Verification code messages must never produce agent-generated copy.
849
- if (replyCallbackUrl) {
850
- let replyText: string;
851
- if (!verifyResult.success) {
852
- replyText = composeChannelVerifyReply(GUARDIAN_VERIFY_TEMPLATE_KEYS.CHANNEL_VERIFY_FAILED, {
853
- failureReason: stripVerificationFailurePrefix(verifyResult.reason),
854
- });
855
- } else if (verifyResult.verificationType === 'trusted_contact') {
856
- replyText = composeChannelVerifyReply(GUARDIAN_VERIFY_TEMPLATE_KEYS.CHANNEL_TRUSTED_CONTACT_VERIFY_SUCCESS);
857
- } else {
858
- replyText = composeChannelVerifyReply(GUARDIAN_VERIFY_TEMPLATE_KEYS.CHANNEL_VERIFY_SUCCESS);
859
- }
860
- try {
861
- await deliverChannelReply(replyCallbackUrl, {
862
- chatId: conversationExternalId,
863
- text: replyText,
864
- assistantId,
865
- }, mintBearerToken());
866
- } catch (err) {
867
- // The challenge is already consumed and side effects applied, so
868
- // we cannot simply re-throw and let the gateway retry the full
869
- // flow. Instead, persist the reply so that gateway retries
870
- // (which arrive as duplicates) can re-attempt delivery.
871
- log.error({ err, conversationExternalId }, 'Failed to deliver deterministic verification reply; persisting for retry');
872
- channelDeliveryStore.storePendingVerificationReply(result.eventId, {
873
- chatId: conversationExternalId,
874
- text: replyText,
875
- assistantId,
876
- });
877
-
878
- // Self-retry after a short delay. The gateway deduplicates
879
- // inbound webhooks after a successful forward, so duplicate
880
- // retries may never arrive. This fire-and-forget retry ensures
881
- // delivery is re-attempted even without a gateway duplicate.
882
- setTimeout(async () => {
883
- try {
884
- await deliverChannelReply(replyCallbackUrl, {
885
- chatId: conversationExternalId,
886
- text: replyText,
887
- assistantId,
888
- }, mintBearerToken());
889
- log.info({ eventId: result.eventId }, 'Verification reply delivered on self-retry');
890
- channelDeliveryStore.clearPendingVerificationReply(result.eventId);
891
- } catch (retryErr) {
892
- log.error({ err: retryErr, eventId: result.eventId }, 'Verification reply self-retry also failed; pending reply remains as fallback');
893
- }
894
- }, 3000);
895
-
896
- return Response.json({
897
- accepted: true,
898
- duplicate: false,
899
- eventId: result.eventId,
900
- guardianVerification: guardianVerifyOutcome,
901
- deliveryPending: true,
902
- });
903
- }
904
- }
905
-
906
- return Response.json({
907
- accepted: true,
908
- duplicate: false,
909
- eventId: result.eventId,
910
- guardianVerification: guardianVerifyOutcome,
911
- });
912
- }
406
+ const verificationResponse = await handleVerificationIntercept({
407
+ isDuplicate: result.duplicate,
408
+ guardianVerifyCode,
409
+ rawSenderId,
410
+ canonicalSenderId,
411
+ canonicalAssistantId,
412
+ sourceChannel,
413
+ conversationExternalId,
414
+ conversationId: result.conversationId,
415
+ eventId: result.eventId,
416
+ replyCallbackUrl,
417
+ mintBearerToken,
418
+ assistantId,
419
+ actorDisplayName: body.actorDisplayName,
420
+ actorUsername: body.actorUsername,
421
+ });
422
+ if (verificationResponse) return verificationResponse;
913
423
 
914
424
  // Legacy voice guardian action interception removed — all guardian reply
915
425
  // routing now flows through the canonical router below (routeGuardianReply),
@@ -919,7 +429,7 @@ export async function handleChannelInbound(
919
429
  // ── Actor role resolution ──
920
430
  // Uses shared channel-agnostic resolution so all ingress paths classify
921
431
  // guardian vs non-guardian actors the same way.
922
- const guardianCtx: GuardianContext = resolveGuardianContext({
432
+ const trustCtx: TrustContext = resolveTrustContext({
923
433
  assistantId: canonicalAssistantId,
924
434
  sourceChannel,
925
435
  conversationExternalId,
@@ -928,99 +438,26 @@ export async function handleChannelInbound(
928
438
  actorDisplayName: body.actorDisplayName,
929
439
  });
930
440
 
931
- // Hoisted flag: set by the canonical guardian reply router when the invite
932
- // handoff bypass fires. Prevents legacy approval interception from swallowing
933
- // the message when other approvals are pending in the same chat.
934
- let skipApprovalInterception = false;
935
-
936
441
  // ── Canonical guardian reply router ──
937
- // Attempts to route inbound messages through the canonical decision pipeline
938
- // before falling through to the legacy approval interception. Handles
939
- // deterministic callbacks (button presses), request code prefixes, and
940
- // NL classification via the conversational approval engine.
941
- if (
942
- !result.duplicate &&
943
- replyCallbackUrl &&
944
- (trimmedContent.length > 0 || hasCallbackData) &&
945
- rawSenderId &&
946
- guardianCtx.trustClass === 'guardian'
947
- ) {
948
- // Compute destination-scoped pending request hints so the router can
949
- // discover canonical requests delivered to this chat even when the
950
- // request lacks a guardianExternalUserId (e.g. voice-originated
951
- // pending_question requests).
952
- //
953
- // When delivery-scoped matches exist, union them with any identity-
954
- // based pending requests so that requests without delivery rows (e.g.
955
- // tool_approval requests created inline) are not silently excluded.
956
- // Pass undefined (not []) when there are zero combined results so the
957
- // router's own identity-based fallback stays active.
958
- const deliveryScopedPendingRequests = listPendingCanonicalGuardianRequestsByDestinationChat(
959
- sourceChannel,
960
- conversationExternalId,
961
- );
962
- let pendingRequestIds: string[] | undefined;
963
- if (deliveryScopedPendingRequests.length > 0) {
964
- const deliveryIds = new Set(deliveryScopedPendingRequests.map(r => r.id));
965
- // Also include identity-based pending requests so we don't hide them
966
- const identityId = canonicalSenderId ?? rawSenderId!;
967
- const identityPending = listCanonicalGuardianRequests({
968
- status: 'pending',
969
- guardianExternalUserId: identityId,
970
- });
971
- for (const r of identityPending) {
972
- deliveryIds.add(r.id);
973
- }
974
- pendingRequestIds = [...deliveryIds];
975
- }
976
-
977
- const routerResult = await routeGuardianReply({
978
- messageText: trimmedContent,
979
- channel: sourceChannel,
980
- actor: {
981
- externalUserId: canonicalSenderId ?? rawSenderId!,
982
- channel: sourceChannel,
983
- guardianPrincipalId: guardianCtx.guardianPrincipalId ?? undefined,
984
- },
985
- conversationId: result.conversationId,
986
- callbackData: body.callbackData,
987
- pendingRequestIds,
988
- approvalConversationGenerator,
989
- channelDeliveryContext: {
990
- replyCallbackUrl,
991
- guardianChatId: conversationExternalId,
992
- assistantId: canonicalAssistantId,
993
- bearerToken: mintBearerToken(),
994
- },
995
- });
996
-
997
- if (routerResult.consumed) {
998
- // Deliver reply text if the router produced one
999
- if (routerResult.replyText) {
1000
- try {
1001
- await deliverChannelReply(replyCallbackUrl, {
1002
- chatId: conversationExternalId,
1003
- text: routerResult.replyText,
1004
- assistantId: canonicalAssistantId,
1005
- }, mintBearerToken());
1006
- } catch (err) {
1007
- log.error({ err, conversationExternalId }, 'Failed to deliver canonical router reply');
1008
- }
1009
- }
1010
-
1011
- return Response.json({
1012
- accepted: true,
1013
- duplicate: false,
1014
- eventId: result.eventId,
1015
- canonicalRouter: routerResult.type,
1016
- requestId: routerResult.requestId,
1017
- });
1018
- }
1019
-
1020
- if (routerResult.skipApprovalInterception) {
1021
- skipApprovalInterception = true;
1022
- }
1023
- }
442
+ const guardianReplyResult = await handleGuardianReplyIntercept({
443
+ isDuplicate: result.duplicate,
444
+ trimmedContent,
445
+ hasCallbackData,
446
+ callbackData: body.callbackData,
447
+ rawSenderId,
448
+ canonicalSenderId,
449
+ canonicalAssistantId,
450
+ sourceChannel,
451
+ conversationExternalId,
452
+ conversationId: result.conversationId,
453
+ eventId: result.eventId,
454
+ replyCallbackUrl,
455
+ mintBearerToken,
456
+ trustClass: trustCtx.trustClass,
457
+ guardianPrincipalId: trustCtx.guardianPrincipalId,
458
+ approvalConversationGenerator,
459
+ });
460
+ if (guardianReplyResult.response) return guardianReplyResult.response;
1024
461
 
1025
462
  // ── Approval interception ──
1026
463
  // Keep this active whenever callback context is available.
@@ -1030,7 +467,7 @@ export async function handleChannelInbound(
1030
467
  if (
1031
468
  replyCallbackUrl &&
1032
469
  !result.duplicate &&
1033
- !skipApprovalInterception
470
+ !guardianReplyResult.skipApprovalInterception
1034
471
  ) {
1035
472
  const approvalResult = await handleApprovalInterception({
1036
473
  conversationId: result.conversationId,
@@ -1041,45 +478,50 @@ export async function handleChannelInbound(
1041
478
  actorExternalId: canonicalSenderId ?? rawSenderId,
1042
479
  replyCallbackUrl,
1043
480
  bearerToken: mintBearerToken(),
1044
- guardianCtx,
481
+ trustCtx,
1045
482
  assistantId: canonicalAssistantId,
1046
483
  approvalCopyGenerator,
1047
484
  approvalConversationGenerator,
1048
485
  });
1049
486
 
1050
487
  if (approvalResult.handled) {
1051
- // Record inferred seen signal for all handled Telegram approval interactions
1052
- if (sourceChannel === 'telegram') {
488
+ // Record inferred seen signal for handled approval interactions
489
+ if (sourceChannel === "telegram" || sourceChannel === "slack") {
1053
490
  try {
1054
491
  if (hasCallbackData) {
1055
- const cbPreview = body.callbackData!.length > 80
1056
- ? body.callbackData!.slice(0, 80) + '...'
1057
- : body.callbackData!;
492
+ const cbPreview =
493
+ body.callbackData!.length > 80
494
+ ? body.callbackData!.slice(0, 80) + "..."
495
+ : body.callbackData!;
1058
496
  recordConversationSeenSignal({
1059
497
  conversationId: result.conversationId,
1060
498
  assistantId: canonicalAssistantId,
1061
- signalType: 'telegram_callback',
1062
- confidence: 'inferred',
1063
- sourceChannel: 'telegram',
1064
- source: 'inbound-message-handler',
499
+ signalType: `${sourceChannel}_callback` as SignalType,
500
+ confidence: "inferred",
501
+ sourceChannel,
502
+ source: "inbound-message-handler",
1065
503
  evidenceText: `User tapped callback: '${cbPreview}'`,
1066
504
  });
1067
505
  } else {
1068
- const msgPreview = trimmedContent.length > 80
1069
- ? trimmedContent.slice(0, 80) + '...'
1070
- : trimmedContent;
506
+ const msgPreview =
507
+ trimmedContent.length > 80
508
+ ? trimmedContent.slice(0, 80) + "..."
509
+ : trimmedContent;
1071
510
  recordConversationSeenSignal({
1072
511
  conversationId: result.conversationId,
1073
512
  assistantId: canonicalAssistantId,
1074
- signalType: 'telegram_inbound_message',
1075
- confidence: 'inferred',
1076
- sourceChannel: 'telegram',
1077
- source: 'inbound-message-handler',
513
+ signalType: `${sourceChannel}_inbound_message` as SignalType,
514
+ confidence: "inferred",
515
+ sourceChannel,
516
+ source: "inbound-message-handler",
1078
517
  evidenceText: `User sent plain-text approval reply: '${msgPreview}'`,
1079
518
  });
1080
519
  }
1081
520
  } catch (err) {
1082
- log.warn({ err, conversationId: result.conversationId }, 'Failed to record seen signal for Telegram approval interaction');
521
+ log.warn(
522
+ { err, conversationId: result.conversationId },
523
+ "Failed to record seen signal for approval interaction",
524
+ );
1083
525
  }
1084
526
  }
1085
527
 
@@ -1098,22 +540,26 @@ export async function handleChannelInbound(
1098
540
  // so checking for empty content alone would miss stale callbacks.
1099
541
  if (hasCallbackData) {
1100
542
  // Record seen signal even for stale callbacks — the user still interacted
1101
- if (sourceChannel === 'telegram') {
543
+ if (sourceChannel === "telegram" || sourceChannel === "slack") {
1102
544
  try {
1103
- const cbPreview = body.callbackData!.length > 80
1104
- ? body.callbackData!.slice(0, 80) + '...'
1105
- : body.callbackData!;
545
+ const cbPreview =
546
+ body.callbackData!.length > 80
547
+ ? body.callbackData!.slice(0, 80) + "..."
548
+ : body.callbackData!;
1106
549
  recordConversationSeenSignal({
1107
550
  conversationId: result.conversationId,
1108
551
  assistantId: canonicalAssistantId,
1109
- signalType: 'telegram_callback',
1110
- confidence: 'inferred',
1111
- sourceChannel: 'telegram',
1112
- source: 'inbound-message-handler',
552
+ signalType: `${sourceChannel}_callback` as SignalType,
553
+ confidence: "inferred",
554
+ sourceChannel,
555
+ source: "inbound-message-handler",
1113
556
  evidenceText: `User tapped stale callback: '${cbPreview}'`,
1114
557
  });
1115
558
  } catch (err) {
1116
- log.warn({ err, conversationId: result.conversationId }, 'Failed to record seen signal for stale Telegram callback');
559
+ log.warn(
560
+ { err, conversationId: result.conversationId },
561
+ "Failed to record seen signal for stale callback",
562
+ );
1117
563
  }
1118
564
  }
1119
565
 
@@ -1121,7 +567,7 @@ export async function handleChannelInbound(
1121
567
  accepted: true,
1122
568
  duplicate: false,
1123
569
  eventId: result.eventId,
1124
- approval: 'stale_ignored',
570
+ approval: "stale_ignored",
1125
571
  });
1126
572
  }
1127
573
  }
@@ -1129,56 +575,24 @@ export async function handleChannelInbound(
1129
575
  // For new (non-duplicate) messages, run the secret ingress check
1130
576
  // synchronously, then fire off the agent loop in the background.
1131
577
  if (!result.duplicate && processMessage) {
1132
- // Persist the raw payload first so dead-lettered events can always be
1133
- // replayed. If the ingress check later detects secrets we clear it
1134
- // before throwing, so secret-bearing content is never left on disk.
1135
- channelDeliveryStore.storePayload(result.eventId, {
1136
- sourceChannel, externalChatId: conversationExternalId, externalMessageId, content,
1137
- attachmentIds, sourceMetadata: body.sourceMetadata,
1138
- senderName: body.actorDisplayName,
1139
- senderExternalUserId: body.actorExternalId,
1140
- senderUsername: body.actorUsername,
1141
- guardianCtx,
578
+ runSecretIngressCheck({
579
+ eventId: result.eventId,
580
+ sourceChannel,
581
+ conversationExternalId,
582
+ externalMessageId,
583
+ conversationId: result.conversationId,
584
+ content,
585
+ trimmedContent,
586
+ attachmentIds,
587
+ sourceMetadata: body.sourceMetadata,
588
+ actorDisplayName: body.actorDisplayName,
589
+ actorExternalId: body.actorExternalId,
590
+ actorUsername: body.actorUsername,
591
+ trustCtx,
1142
592
  replyCallbackUrl,
1143
- assistantId: canonicalAssistantId,
593
+ canonicalAssistantId,
1144
594
  });
1145
595
 
1146
- const contentToCheck = content ?? '';
1147
- let ingressCheck: ReturnType<typeof checkIngressForSecrets>;
1148
- try {
1149
- ingressCheck = checkIngressForSecrets(contentToCheck);
1150
- } catch (checkErr) {
1151
- channelDeliveryStore.clearPayload(result.eventId);
1152
- throw checkErr;
1153
- }
1154
- if (ingressCheck.blocked) {
1155
- channelDeliveryStore.clearPayload(result.eventId);
1156
- throw new IngressBlockedError(ingressCheck.userNotice!, ingressCheck.detectedTypes);
1157
- }
1158
-
1159
- // Record inferred seen signal for non-duplicate Telegram inbound messages
1160
- if (sourceChannel === 'telegram') {
1161
- try {
1162
- const msgPreview = trimmedContent.length > 80
1163
- ? trimmedContent.slice(0, 80) + '...'
1164
- : trimmedContent;
1165
- const evidence = trimmedContent.length > 0
1166
- ? `User sent message: '${msgPreview}'`
1167
- : 'User sent media attachment';
1168
- recordConversationSeenSignal({
1169
- conversationId: result.conversationId,
1170
- assistantId: canonicalAssistantId,
1171
- signalType: 'telegram_inbound_message',
1172
- confidence: 'inferred',
1173
- sourceChannel: 'telegram',
1174
- source: 'inbound-message-handler',
1175
- evidenceText: evidence,
1176
- });
1177
- } catch (err) {
1178
- log.warn({ err, conversationId: result.conversationId }, 'Failed to record seen signal for Telegram inbound message');
1179
- }
1180
- }
1181
-
1182
596
  // Fire-and-forget: process the message and deliver the reply in the background.
1183
597
  // The HTTP response returns immediately so the gateway webhook is not blocked.
1184
598
  // The onEvent callback in processMessage registers pending interactions, and
@@ -1187,12 +601,12 @@ export async function handleChannelInbound(
1187
601
  processMessage,
1188
602
  conversationId: result.conversationId,
1189
603
  eventId: result.eventId,
1190
- content: content ?? '',
604
+ content: content ?? "",
1191
605
  attachmentIds: hasAttachments ? attachmentIds : undefined,
1192
606
  sourceChannel,
1193
607
  sourceInterface,
1194
608
  externalChatId: conversationExternalId,
1195
- guardianCtx,
609
+ trustCtx,
1196
610
  metadataHints,
1197
611
  metadataUxBrief,
1198
612
  commandIntent,
@@ -1201,6 +615,7 @@ export async function handleChannelInbound(
1201
615
  mintBearerToken,
1202
616
  assistantId: canonicalAssistantId,
1203
617
  approvalCopyGenerator,
618
+ externalMessageId: sourceMessageId ?? externalMessageId,
1204
619
  });
1205
620
  }
1206
621
 
@@ -1210,572 +625,3 @@ export async function handleChannelInbound(
1210
625
  eventId: result.eventId,
1211
626
  });
1212
627
  }
1213
-
1214
- // ---------------------------------------------------------------------------
1215
- // Invite token intercept
1216
- // ---------------------------------------------------------------------------
1217
-
1218
- /**
1219
- * Handle an inbound invite token for a non-member or inactive member.
1220
- *
1221
- * Redeems the invite, delivers a deterministic reply, and returns a Response
1222
- * to short-circuit the handler. Returns `null` when the intercept should not
1223
- * fire (e.g. already_member outcome — let normal flow handle it).
1224
- */
1225
- async function handleInviteTokenIntercept(params: {
1226
- rawToken: string;
1227
- sourceChannel: ChannelId;
1228
- externalChatId: string;
1229
- externalMessageId: string;
1230
- senderExternalUserId?: string;
1231
- senderName?: string;
1232
- senderUsername?: string;
1233
- replyCallbackUrl?: string;
1234
- bearerToken?: string;
1235
- assistantId?: string;
1236
- canonicalAssistantId: string;
1237
- }): Promise<Response | null> {
1238
- const {
1239
- rawToken,
1240
- sourceChannel,
1241
- externalChatId,
1242
- externalMessageId,
1243
- senderExternalUserId,
1244
- senderName,
1245
- senderUsername,
1246
- replyCallbackUrl,
1247
- bearerToken,
1248
- assistantId,
1249
- canonicalAssistantId,
1250
- } = params;
1251
-
1252
- // Record the inbound event for dedup tracking BEFORE performing redemption.
1253
- // Without this, duplicate webhook deliveries (common with Telegram) would
1254
- // not be tracked: the first delivery redeems the invite and returns early,
1255
- // then the retry finds an active member, passes ACL, and the raw
1256
- // /start iv_<token> message leaks into the agent pipeline.
1257
- const dedupResult = channelDeliveryStore.recordInbound(
1258
- sourceChannel,
1259
- externalChatId,
1260
- externalMessageId,
1261
- { assistantId: canonicalAssistantId },
1262
- );
1263
-
1264
- if (dedupResult.duplicate) {
1265
- return Response.json({
1266
- accepted: true,
1267
- duplicate: true,
1268
- eventId: dedupResult.eventId,
1269
- });
1270
- }
1271
-
1272
- const outcome = redeemInvite({
1273
- rawToken,
1274
- sourceChannel,
1275
- externalUserId: senderExternalUserId,
1276
- externalChatId,
1277
- displayName: senderName,
1278
- username: senderUsername,
1279
- assistantId: canonicalAssistantId,
1280
- });
1281
-
1282
- log.info(
1283
- { sourceChannel, externalChatId: params.externalChatId, ok: outcome.ok, type: outcome.ok ? outcome.type : undefined, reason: !outcome.ok ? outcome.reason : undefined },
1284
- 'Invite token intercept: redemption result',
1285
- );
1286
-
1287
- // already_member means the user has an active record — let the normal
1288
- // flow handle them (they passed ACL or the member is active).
1289
- if (outcome.ok && outcome.type === 'already_member') {
1290
- // Deliver a quick acknowledgement and short-circuit so the user
1291
- // does not trigger the deny gate or a duplicate agent loop.
1292
- const replyText = getInviteRedemptionReply(outcome);
1293
- if (replyCallbackUrl) {
1294
- try {
1295
- await deliverChannelReply(replyCallbackUrl, {
1296
- chatId: externalChatId,
1297
- text: replyText,
1298
- assistantId,
1299
- }, bearerToken);
1300
- } catch (err) {
1301
- log.error({ err, externalChatId }, 'Failed to deliver invite already-member reply');
1302
- }
1303
- }
1304
- channelDeliveryStore.markProcessed(dedupResult.eventId);
1305
- return Response.json({ accepted: true, eventId: dedupResult.eventId, inviteRedemption: 'already_member' });
1306
- }
1307
-
1308
- const replyText = getInviteRedemptionReply(outcome);
1309
-
1310
- if (replyCallbackUrl) {
1311
- try {
1312
- await deliverChannelReply(replyCallbackUrl, {
1313
- chatId: externalChatId,
1314
- text: replyText,
1315
- assistantId,
1316
- }, bearerToken);
1317
- } catch (err) {
1318
- log.error({ err, externalChatId }, 'Failed to deliver invite redemption reply');
1319
- }
1320
- }
1321
-
1322
- if (outcome.ok && outcome.type === 'redeemed') {
1323
- channelDeliveryStore.markProcessed(dedupResult.eventId);
1324
- return Response.json({ accepted: true, eventId: dedupResult.eventId, inviteRedemption: 'redeemed', memberId: outcome.memberId });
1325
- }
1326
-
1327
- // Failed redemption — inform the user and deny
1328
- channelDeliveryStore.markProcessed(dedupResult.eventId);
1329
- return Response.json({ accepted: true, eventId: dedupResult.eventId, denied: true, inviteRedemption: outcome.reason });
1330
- }
1331
-
1332
- // ---------------------------------------------------------------------------
1333
- // Background message processing
1334
- // ---------------------------------------------------------------------------
1335
-
1336
- interface BackgroundProcessingParams {
1337
- processMessage: MessageProcessor;
1338
- conversationId: string;
1339
- eventId: string;
1340
- content: string;
1341
- attachmentIds?: string[];
1342
- sourceChannel: ChannelId;
1343
- sourceInterface: InterfaceId;
1344
- externalChatId: string;
1345
- guardianCtx: GuardianContext;
1346
- metadataHints: string[];
1347
- metadataUxBrief?: string;
1348
- replyCallbackUrl?: string;
1349
- /** Factory that mints a fresh delivery JWT for each HTTP attempt. */
1350
- mintBearerToken: () => string;
1351
- assistantId?: string;
1352
- approvalCopyGenerator?: ApprovalCopyGenerator;
1353
- commandIntent?: Record<string, unknown>;
1354
- sourceLanguageCode?: string;
1355
- }
1356
-
1357
- const TELEGRAM_TYPING_INTERVAL_MS = 4_000;
1358
- const PENDING_APPROVAL_POLL_INTERVAL_MS = 300;
1359
-
1360
- // Module-level map tracking which approval requestIds have already been
1361
- // notified to trusted contacts. Maps requestId -> conversationId so that
1362
- // cleanup can be scoped to the owning conversation's poller, preventing
1363
- // concurrent pollers from different conversations from evicting each
1364
- // other's entries.
1365
- const globalNotifiedApprovalRequestIds = new Map<string, string>();
1366
-
1367
- function delay(ms: number): Promise<void> {
1368
- return new Promise((resolve) => setTimeout(resolve, ms));
1369
- }
1370
-
1371
- function shouldEmitTelegramTyping(
1372
- sourceChannel: ChannelId,
1373
- replyCallbackUrl?: string,
1374
- ): boolean {
1375
- if (sourceChannel !== 'telegram' || !replyCallbackUrl) return false;
1376
- try {
1377
- return new URL(replyCallbackUrl).pathname.endsWith('/deliver/telegram');
1378
- } catch {
1379
- return replyCallbackUrl.endsWith('/deliver/telegram');
1380
- }
1381
- }
1382
-
1383
- function startTelegramTypingHeartbeat(
1384
- callbackUrl: string,
1385
- chatId: string,
1386
- mintBearerToken: () => string,
1387
- assistantId?: string,
1388
- ): () => void {
1389
- let active = true;
1390
- let inFlight = false;
1391
-
1392
- const emitTyping = (): void => {
1393
- if (!active || inFlight) return;
1394
- inFlight = true;
1395
- void deliverChannelReply(
1396
- callbackUrl,
1397
- { chatId, chatAction: 'typing', assistantId },
1398
- mintBearerToken(),
1399
- ).catch((err) => {
1400
- log.debug({ err, chatId }, 'Failed to deliver Telegram typing indicator');
1401
- }).finally(() => {
1402
- inFlight = false;
1403
- });
1404
- };
1405
-
1406
- emitTyping();
1407
-
1408
- const interval = setInterval(emitTyping, TELEGRAM_TYPING_INTERVAL_MS);
1409
- (interval as { unref?: () => void }).unref?.();
1410
-
1411
- return () => {
1412
- active = false;
1413
- clearInterval(interval);
1414
- };
1415
- }
1416
-
1417
- function startPendingApprovalPromptWatcher(params: {
1418
- conversationId: string;
1419
- sourceChannel: ChannelId;
1420
- externalChatId: string;
1421
- guardianTrustClass: GuardianContext['trustClass'];
1422
- guardianExternalUserId?: string;
1423
- requesterExternalUserId?: string;
1424
- replyCallbackUrl: string;
1425
- mintBearerToken: () => string;
1426
- assistantId?: string;
1427
- approvalCopyGenerator?: ApprovalCopyGenerator;
1428
- }): () => void {
1429
- const {
1430
- conversationId,
1431
- sourceChannel,
1432
- externalChatId,
1433
- guardianTrustClass,
1434
- guardianExternalUserId,
1435
- requesterExternalUserId,
1436
- replyCallbackUrl,
1437
- mintBearerToken,
1438
- assistantId,
1439
- approvalCopyGenerator,
1440
- } = params;
1441
-
1442
- // Approval prompt delivery is guardian-only. Non-guardian and unverified
1443
- // actors must never receive approval prompt broadcasts for the conversation.
1444
- // We also require an explicit identity match against the bound guardian to
1445
- // avoid broadcasting prompts when trustClass is stale/mis-scoped.
1446
- const isBoundGuardianActor = guardianTrustClass === 'guardian'
1447
- && !!guardianExternalUserId
1448
- && requesterExternalUserId === guardianExternalUserId;
1449
- if (!isBoundGuardianActor) {
1450
- return () => {};
1451
- }
1452
-
1453
- let active = true;
1454
- const deliveredRequestIds = new Set<string>();
1455
-
1456
- const poll = async (): Promise<void> => {
1457
- while (active) {
1458
- try {
1459
- const prompt = getChannelApprovalPrompt(conversationId);
1460
- const pending = getApprovalInfoByConversation(conversationId);
1461
- const info = pending[0];
1462
- if (prompt && info && !deliveredRequestIds.has(info.requestId)) {
1463
- deliveredRequestIds.add(info.requestId);
1464
- const delivered = await deliverGeneratedApprovalPrompt({
1465
- replyCallbackUrl,
1466
- chatId: externalChatId,
1467
- sourceChannel,
1468
- assistantId: assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
1469
- bearerToken: mintBearerToken(),
1470
- prompt,
1471
- uiMetadata: buildApprovalUIMetadata(prompt, info),
1472
- messageContext: {
1473
- scenario: 'standard_prompt',
1474
- toolName: info.toolName,
1475
- channel: sourceChannel,
1476
- },
1477
- approvalCopyGenerator,
1478
- });
1479
- if (!delivered) {
1480
- // Delivery can fail transiently (network or gateway outage).
1481
- // Keep polling and retry prompt delivery for the same request.
1482
- deliveredRequestIds.delete(info.requestId);
1483
- }
1484
- }
1485
- } catch (err) {
1486
- log.warn({ err, conversationId }, 'Pending approval prompt watcher failed');
1487
- }
1488
- await delay(PENDING_APPROVAL_POLL_INTERVAL_MS);
1489
- }
1490
- };
1491
-
1492
- void poll();
1493
- return () => {
1494
- active = false;
1495
- };
1496
- }
1497
-
1498
- /**
1499
- * Resolve a human-readable guardian name from the guardian binding metadata.
1500
- * Returns the display name, username (prefixed with @), or undefined if
1501
- * no name is available.
1502
- */
1503
- function resolveGuardianDisplayName(
1504
- assistantId: string,
1505
- sourceChannel: ChannelId,
1506
- ): string | undefined {
1507
- const binding = getGuardianBinding(assistantId, sourceChannel);
1508
- if (!binding?.metadataJson) return undefined;
1509
- try {
1510
- const parsed = JSON.parse(binding.metadataJson) as Record<string, unknown>;
1511
- if (typeof parsed.displayName === 'string' && parsed.displayName.trim().length > 0) {
1512
- return parsed.displayName.trim();
1513
- }
1514
- if (typeof parsed.username === 'string' && parsed.username.trim().length > 0) {
1515
- return `@${parsed.username.trim()}`;
1516
- }
1517
- } catch {
1518
- // ignore malformed metadata
1519
- }
1520
- return undefined;
1521
- }
1522
-
1523
- /**
1524
- * Start a poller that sends a one-shot "waiting for guardian approval" message
1525
- * to the trusted contact when a confirmation_request enters guardian approval
1526
- * wait. Deduplicates by requestId so each request only produces one message.
1527
- *
1528
- * Only activates for trusted-contact actors with a resolvable guardian route.
1529
- */
1530
- function startTrustedContactApprovalNotifier(params: {
1531
- conversationId: string;
1532
- sourceChannel: ChannelId;
1533
- externalChatId: string;
1534
- guardianTrustClass: GuardianContext['trustClass'];
1535
- guardianExternalUserId?: string;
1536
- replyCallbackUrl: string;
1537
- mintBearerToken: () => string;
1538
- assistantId?: string;
1539
- }): () => void {
1540
- const {
1541
- conversationId,
1542
- sourceChannel,
1543
- externalChatId,
1544
- guardianTrustClass,
1545
- guardianExternalUserId,
1546
- replyCallbackUrl,
1547
- mintBearerToken,
1548
- assistantId,
1549
- } = params;
1550
-
1551
- // Only notify trusted contacts who have a resolvable guardian route.
1552
- if (guardianTrustClass !== 'trusted_contact' || !guardianExternalUserId) {
1553
- return () => {};
1554
- }
1555
-
1556
- let active = true;
1557
-
1558
- const poll = async (): Promise<void> => {
1559
- while (active) {
1560
- try {
1561
- const pending = getApprovalInfoByConversation(conversationId);
1562
- const info = pending[0];
1563
-
1564
- // Clean up resolved requests from the module-level dedupe map.
1565
- // Only remove entries that belong to THIS conversation — other
1566
- // conversations' pollers own their own entries. Without this
1567
- // scoping, concurrent pollers would evict each other's request
1568
- // IDs and cause duplicate notifications.
1569
- const currentPendingIds = new Set(pending.map(p => p.requestId));
1570
- for (const [rid, cid] of globalNotifiedApprovalRequestIds) {
1571
- if (cid === conversationId && !currentPendingIds.has(rid)) {
1572
- globalNotifiedApprovalRequestIds.delete(rid);
1573
- }
1574
- }
1575
-
1576
- if (info && !globalNotifiedApprovalRequestIds.has(info.requestId)) {
1577
- globalNotifiedApprovalRequestIds.set(info.requestId, conversationId);
1578
- const guardianName = resolveGuardianDisplayName(
1579
- assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
1580
- sourceChannel,
1581
- ) ?? resolveUserReference();
1582
- const waitingText = `Waiting for ${guardianName}'s approval...`;
1583
- try {
1584
- await deliverChannelReply(replyCallbackUrl, {
1585
- chatId: externalChatId,
1586
- text: waitingText,
1587
- assistantId: assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
1588
- }, mintBearerToken());
1589
- } catch (err) {
1590
- log.warn({ err, conversationId }, 'Failed to deliver trusted-contact pending-approval notification');
1591
- // Remove from notified set so delivery is retried on next poll
1592
- globalNotifiedApprovalRequestIds.delete(info.requestId);
1593
- }
1594
- }
1595
- } catch (err) {
1596
- log.warn({ err, conversationId }, 'Trusted-contact approval notifier poll failed');
1597
- }
1598
- await delay(PENDING_APPROVAL_POLL_INTERVAL_MS);
1599
- }
1600
- };
1601
-
1602
- void poll();
1603
- return () => {
1604
- active = false;
1605
-
1606
- // Evict all dedupe entries owned by this conversation so the
1607
- // module-level map doesn't grow unboundedly after the poller stops.
1608
- for (const [rid, cid] of globalNotifiedApprovalRequestIds) {
1609
- if (cid === conversationId) {
1610
- globalNotifiedApprovalRequestIds.delete(rid);
1611
- }
1612
- }
1613
- };
1614
- }
1615
-
1616
- function processChannelMessageInBackground(params: BackgroundProcessingParams): void {
1617
- const {
1618
- processMessage,
1619
- conversationId,
1620
- eventId,
1621
- content,
1622
- attachmentIds,
1623
- sourceChannel,
1624
- sourceInterface,
1625
- externalChatId,
1626
- guardianCtx,
1627
- metadataHints,
1628
- metadataUxBrief,
1629
- replyCallbackUrl,
1630
- mintBearerToken,
1631
- assistantId,
1632
- approvalCopyGenerator,
1633
- commandIntent,
1634
- sourceLanguageCode,
1635
- } = params;
1636
-
1637
- (async () => {
1638
- const typingCallbackUrl = shouldEmitTelegramTyping(sourceChannel, replyCallbackUrl)
1639
- ? replyCallbackUrl
1640
- : undefined;
1641
- const stopTypingHeartbeat = typingCallbackUrl
1642
- ? startTelegramTypingHeartbeat(typingCallbackUrl, externalChatId, mintBearerToken, assistantId)
1643
- : undefined;
1644
- const stopApprovalWatcher = replyCallbackUrl
1645
- ? startPendingApprovalPromptWatcher({
1646
- conversationId,
1647
- sourceChannel,
1648
- externalChatId,
1649
- guardianTrustClass: guardianCtx.trustClass,
1650
- guardianExternalUserId: guardianCtx.guardianExternalUserId,
1651
- requesterExternalUserId: guardianCtx.requesterExternalUserId,
1652
- replyCallbackUrl,
1653
- mintBearerToken,
1654
- assistantId,
1655
- approvalCopyGenerator,
1656
- })
1657
- : undefined;
1658
- const stopTcApprovalNotifier = replyCallbackUrl
1659
- ? startTrustedContactApprovalNotifier({
1660
- conversationId,
1661
- sourceChannel,
1662
- externalChatId,
1663
- guardianTrustClass: guardianCtx.trustClass,
1664
- guardianExternalUserId: guardianCtx.guardianExternalUserId,
1665
- replyCallbackUrl,
1666
- mintBearerToken,
1667
- assistantId,
1668
- })
1669
- : undefined;
1670
-
1671
- try {
1672
- const cmdIntent = commandIntent && typeof commandIntent.type === 'string'
1673
- ? { type: commandIntent.type as string, ...(typeof commandIntent.payload === 'string' ? { payload: commandIntent.payload } : {}), ...(sourceLanguageCode ? { languageCode: sourceLanguageCode } : {}) }
1674
- : undefined;
1675
- const { messageId: userMessageId } = await processMessage(
1676
- conversationId,
1677
- content,
1678
- attachmentIds,
1679
- {
1680
- transport: {
1681
- channelId: sourceChannel,
1682
- hints: metadataHints.length > 0 ? metadataHints : undefined,
1683
- uxBrief: metadataUxBrief,
1684
- },
1685
- assistantId,
1686
- guardianContext: guardianCtx,
1687
- isInteractive: resolveRoutingState(guardianCtx).promptWaitingAllowed,
1688
- ...(cmdIntent ? { commandIntent: cmdIntent } : {}),
1689
- },
1690
- sourceChannel,
1691
- sourceInterface,
1692
- );
1693
- channelDeliveryStore.linkMessage(eventId, userMessageId);
1694
- channelDeliveryStore.markProcessed(eventId);
1695
-
1696
- if (replyCallbackUrl) {
1697
- await deliverReplyViaCallback(
1698
- conversationId,
1699
- externalChatId,
1700
- replyCallbackUrl,
1701
- mintBearerToken(),
1702
- assistantId,
1703
- {
1704
- onSegmentDelivered: (count) =>
1705
- channelDeliveryStore.updateDeliveredSegmentCount(eventId, count),
1706
- },
1707
- );
1708
- }
1709
- } catch (err) {
1710
- log.error({ err, conversationId }, 'Background channel message processing failed');
1711
- channelDeliveryStore.recordProcessingFailure(eventId, err);
1712
- } finally {
1713
- stopTypingHeartbeat?.();
1714
- stopApprovalWatcher?.();
1715
- stopTcApprovalNotifier?.();
1716
- }
1717
- })();
1718
- }
1719
-
1720
- // ---------------------------------------------------------------------------
1721
- // Bootstrap verification Telegram delivery helper
1722
- // ---------------------------------------------------------------------------
1723
-
1724
- /**
1725
- * Deliver a verification Telegram message during bootstrap.
1726
- * Fire-and-forget with error logging and a single self-retry on failure.
1727
- */
1728
- function deliverBootstrapVerificationTelegram(
1729
- chatId: string,
1730
- text: string,
1731
- assistantId: string,
1732
- ): void {
1733
- const attemptDelivery = async (): Promise<boolean> => {
1734
- const gatewayUrl = getGatewayInternalBaseUrl();
1735
- const bearerToken = mintDaemonDeliveryToken();
1736
- const url = `${gatewayUrl}/deliver/telegram`;
1737
- const resp = await fetch(url, {
1738
- method: 'POST',
1739
- headers: {
1740
- 'Content-Type': 'application/json',
1741
- Authorization: `Bearer ${bearerToken}`,
1742
- },
1743
- body: JSON.stringify({ chatId, text, assistantId }),
1744
- });
1745
- if (!resp.ok) {
1746
- const body = await resp.text().catch(() => '<unreadable>');
1747
- log.error({ chatId, assistantId, status: resp.status, body }, 'Gateway /deliver/telegram failed for bootstrap verification');
1748
- return false;
1749
- }
1750
- return true;
1751
- };
1752
-
1753
- (async () => {
1754
- try {
1755
- const delivered = await attemptDelivery();
1756
- if (delivered) {
1757
- log.info({ chatId, assistantId }, 'Bootstrap verification Telegram message delivered');
1758
- return;
1759
- }
1760
- } catch (err) {
1761
- log.error({ err, chatId, assistantId }, 'Failed to deliver bootstrap verification Telegram message');
1762
- }
1763
-
1764
- // Self-retry after a short delay. The gateway deduplicates inbound
1765
- // webhooks after a successful forward, so duplicate retries from the
1766
- // user re-clicking the deep link may never arrive. This ensures
1767
- // delivery is re-attempted even without a gateway duplicate.
1768
- setTimeout(async () => {
1769
- try {
1770
- const delivered = await attemptDelivery();
1771
- if (delivered) {
1772
- log.info({ chatId, assistantId }, 'Bootstrap verification Telegram message delivered on self-retry');
1773
- } else {
1774
- log.error({ chatId, assistantId }, 'Bootstrap verification Telegram self-retry also failed');
1775
- }
1776
- } catch (retryErr) {
1777
- log.error({ err: retryErr, chatId, assistantId }, 'Bootstrap verification Telegram self-retry threw; giving up');
1778
- }
1779
- }, 3000);
1780
- })();
1781
- }