@vellumai/assistant 0.4.26 → 0.4.29

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 (1301) hide show
  1. package/.env.example +2 -2
  2. package/AGENTS.md +5 -0
  3. package/ARCHITECTURE.md +169 -69
  4. package/Dockerfile +1 -1
  5. package/README.md +111 -112
  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 +10 -9
  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 +3 -0
  15. package/scripts/test.sh +89 -5
  16. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +46 -0
  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 +36 -23
  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__/approval-routes-http.test.ts +2 -2
  24. package/src/__tests__/asset-materialize-tool.test.ts +7 -7
  25. package/src/__tests__/asset-search-tool.test.ts +7 -7
  26. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  27. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +217 -0
  28. package/src/__tests__/call-controller.test.ts +99 -69
  29. package/src/__tests__/call-start-guardian-guard.test.ts +1 -1
  30. package/src/__tests__/channel-approval-routes.test.ts +113 -70
  31. package/src/__tests__/channel-guardian.test.ts +173 -282
  32. package/src/__tests__/channel-readiness-service.test.ts +6 -2
  33. package/src/__tests__/channel-reply-delivery.test.ts +2 -2
  34. package/src/__tests__/channel-retry-sweep.test.ts +14 -14
  35. package/src/__tests__/checker.test.ts +12 -31
  36. package/src/__tests__/claude-code-tool-profiles.test.ts +1 -1
  37. package/src/__tests__/commit-message-enrichment-service.test.ts +67 -59
  38. package/src/__tests__/compaction.benchmark.test.ts +6 -2
  39. package/src/__tests__/computer-use-tools.test.ts +1 -1
  40. package/src/__tests__/config-schema.test.ts +66 -7
  41. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -29
  42. package/src/__tests__/contacts-tools.test.ts +63 -2
  43. package/src/__tests__/context-overflow-approval.test.ts +141 -0
  44. package/src/__tests__/context-overflow-policy.test.ts +171 -0
  45. package/src/__tests__/context-overflow-reducer.test.ts +533 -0
  46. package/src/__tests__/context-window-manager.test.ts +97 -0
  47. package/src/__tests__/conversation-attention-telegram.test.ts +38 -46
  48. package/src/__tests__/conversation-pairing.test.ts +2 -2
  49. package/src/__tests__/conversation-routes-guardian-reply.test.ts +214 -10
  50. package/src/__tests__/conversation-routes.test.ts +4 -7
  51. package/src/__tests__/credential-broker-browser-fill.test.ts +13 -2
  52. package/src/__tests__/credential-security-e2e.test.ts +1 -1
  53. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  54. package/src/__tests__/credential-vault-unit.test.ts +1 -1
  55. package/src/__tests__/credential-vault.test.ts +11 -8
  56. package/src/__tests__/daemon-lifecycle.test.ts +2 -2
  57. package/src/__tests__/daemon-server-session-init.test.ts +6 -6
  58. package/src/__tests__/delete-managed-skill-tool.test.ts +1 -1
  59. package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -2
  60. package/src/__tests__/emit-signal-routing-intent.test.ts +4 -0
  61. package/src/__tests__/encrypted-store.test.ts +10 -7
  62. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  63. package/src/__tests__/file-edit-tool.test.ts +1 -1
  64. package/src/__tests__/file-read-tool.test.ts +1 -1
  65. package/src/__tests__/file-write-tool.test.ts +1 -1
  66. package/src/__tests__/fixtures/credential-security-fixtures.ts +87 -64
  67. package/src/__tests__/fixtures/media-reuse-fixtures.ts +37 -31
  68. package/src/__tests__/fixtures/mock-signup-server.ts +171 -115
  69. package/src/__tests__/fixtures/proxy-fixtures.ts +39 -39
  70. package/src/__tests__/followup-tools.test.ts +1 -1
  71. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  72. package/src/__tests__/guardian-actions-endpoint.test.ts +543 -1
  73. package/src/__tests__/guardian-control-plane-policy.test.ts +15 -15
  74. package/src/__tests__/guardian-dispatch.test.ts +79 -1
  75. package/src/__tests__/guardian-grant-minting.test.ts +14 -14
  76. package/src/__tests__/guardian-outbound-http.test.ts +1 -2
  77. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -41
  78. package/src/__tests__/guardian-routing-invariants.test.ts +2 -5
  79. package/src/__tests__/guardian-routing-state.test.ts +36 -52
  80. package/src/__tests__/guardian-verification-intent-routing.test.ts +4 -6
  81. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +2 -2
  82. package/src/__tests__/handle-user-message-secret-resume.test.ts +39 -1
  83. package/src/__tests__/handlers-cu-observation-blob.test.ts +21 -10
  84. package/src/__tests__/handlers-telegram-config.test.ts +14 -14
  85. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +23 -2
  86. package/src/__tests__/headless-browser-interactions.test.ts +1 -1
  87. package/src/__tests__/headless-browser-navigate.test.ts +1 -1
  88. package/src/__tests__/headless-browser-read-tools.test.ts +1 -1
  89. package/src/__tests__/headless-browser-snapshot.test.ts +1 -1
  90. package/src/__tests__/heartbeat-service.test.ts +45 -2
  91. package/src/__tests__/host-file-edit-tool.test.ts +1 -1
  92. package/src/__tests__/host-file-read-tool.test.ts +1 -1
  93. package/src/__tests__/host-file-write-tool.test.ts +1 -1
  94. package/src/__tests__/host-shell-tool.test.ts +1 -1
  95. package/src/__tests__/inbound-invite-redemption.test.ts +16 -18
  96. package/src/__tests__/ingress-reconcile.test.ts +2 -2
  97. package/src/__tests__/ingress-routes-http.test.ts +2 -1
  98. package/src/__tests__/integrations-cli.test.ts +256 -0
  99. package/src/__tests__/intent-routing.test.ts +4 -5
  100. package/src/__tests__/invite-redemption-service.test.ts +4 -3
  101. package/src/__tests__/ipc-snapshot.test.ts +28 -0
  102. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
  103. package/src/__tests__/mcp-cli.test.ts +136 -57
  104. package/src/__tests__/mcp-client-auth.test.ts +95 -0
  105. package/src/__tests__/media-generate-image.test.ts +2 -2
  106. package/src/__tests__/media-reuse-story.e2e.test.ts +8 -8
  107. package/src/__tests__/memory-regressions.test.ts +6 -6
  108. package/src/__tests__/messaging-send-tool.test.ts +1 -1
  109. package/src/__tests__/migration-cross-version-compatibility.test.ts +1855 -0
  110. package/src/__tests__/migration-export-http.test.ts +540 -0
  111. package/src/__tests__/migration-import-commit-http.test.ts +823 -0
  112. package/src/__tests__/migration-import-preflight-http.test.ts +755 -0
  113. package/src/__tests__/migration-parity-persistence.test.ts +1854 -0
  114. package/src/__tests__/migration-transport.test.ts +904 -0
  115. package/src/__tests__/migration-validate-http.test.ts +698 -0
  116. package/src/__tests__/migration-wizard.test.ts +1289 -0
  117. package/src/__tests__/non-member-access-request.test.ts +17 -17
  118. package/src/__tests__/notification-decision-strategy.test.ts +110 -2
  119. package/src/__tests__/notification-deep-link.test.ts +18 -0
  120. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  121. package/src/__tests__/oauth2-gateway-transport.test.ts +1 -1
  122. package/src/__tests__/playbook-execution.test.ts +1 -1
  123. package/src/__tests__/playbook-tools.test.ts +1 -1
  124. package/src/__tests__/provider-streaming.benchmark.test.ts +3 -1
  125. package/src/__tests__/proxy-approval-callback.test.ts +1 -1
  126. package/src/__tests__/qdrant-manager.test.ts +40 -11
  127. package/src/__tests__/rebind-secrets-screen.test.ts +839 -0
  128. package/src/__tests__/recording-handler.test.ts +2 -2
  129. package/src/__tests__/recording-intent-handler.test.ts +3 -3
  130. package/src/__tests__/recording-state-machine.test.ts +2 -2
  131. package/src/__tests__/relay-server.test.ts +506 -227
  132. package/src/__tests__/reminder-store.test.ts +8 -0
  133. package/src/__tests__/reminder.test.ts +8 -0
  134. package/src/__tests__/{resolve-guardian-trust-class.test.ts → resolve-trust-class.test.ts} +11 -17
  135. package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
  136. package/src/__tests__/schedule-tools.test.ts +1 -1
  137. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  138. package/src/__tests__/script-proxy-connect-tunnel.test.ts +2 -3
  139. package/src/__tests__/script-proxy-decision-trace.test.ts +2 -2
  140. package/src/__tests__/script-proxy-http-forwarder.test.ts +1 -1
  141. package/src/__tests__/script-proxy-injection-runtime.test.ts +5 -5
  142. package/src/__tests__/script-proxy-mitm-handler.test.ts +4 -4
  143. package/src/__tests__/script-proxy-policy-runtime.test.ts +2 -2
  144. package/src/__tests__/script-proxy-policy.test.ts +2 -2
  145. package/src/__tests__/script-proxy-session-manager.test.ts +4 -7
  146. package/src/__tests__/script-proxy-session-runtime.test.ts +1 -6
  147. package/src/__tests__/secret-onetime-send.test.ts +4 -4
  148. package/src/__tests__/secret-scanner-executor.test.ts +2 -2
  149. package/src/__tests__/send-endpoint-busy.test.ts +11 -9
  150. package/src/__tests__/send-notification-tool.test.ts +2 -2
  151. package/src/__tests__/session-abort-tool-results.test.ts +17 -2
  152. package/src/__tests__/session-agent-loop.test.ts +456 -35
  153. package/src/__tests__/session-confirmation-signals.test.ts +3 -2
  154. package/src/__tests__/session-conflict-gate.test.ts +20 -3
  155. package/src/__tests__/session-init.benchmark.test.ts +2 -2
  156. package/src/__tests__/session-load-history-repair.test.ts +7 -7
  157. package/src/__tests__/session-pre-run-repair.test.ts +17 -2
  158. package/src/__tests__/session-profile-injection.test.ts +20 -3
  159. package/src/__tests__/session-provider-retry-repair.test.ts +86 -6
  160. package/src/__tests__/session-queue.test.ts +33 -18
  161. package/src/__tests__/session-runtime-assembly.test.ts +147 -1
  162. package/src/__tests__/session-runtime-workspace.test.ts +40 -0
  163. package/src/__tests__/session-slash-known.test.ts +21 -3
  164. package/src/__tests__/session-slash-queue.test.ts +17 -2
  165. package/src/__tests__/session-slash-unknown.test.ts +17 -2
  166. package/src/__tests__/session-surfaces-deselection.test.ts +208 -0
  167. package/src/__tests__/session-workspace-cache-state.test.ts +2 -2
  168. package/src/__tests__/session-workspace-injection.test.ts +17 -2
  169. package/src/__tests__/session-workspace-tool-tracking.test.ts +17 -2
  170. package/src/__tests__/shell-credential-ref.test.ts +1 -1
  171. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
  172. package/src/__tests__/skill-load-feature-flag.test.ts +1 -1
  173. package/src/__tests__/skill-load-tool.test.ts +1 -1
  174. package/src/__tests__/skill-script-runner-host.test.ts +1 -1
  175. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -1
  176. package/src/__tests__/skill-script-runner.test.ts +1 -1
  177. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  178. package/src/__tests__/slack-skill.test.ts +3 -2
  179. package/src/__tests__/subagent-tools.test.ts +3 -3
  180. package/src/__tests__/swarm-recursion.test.ts +1 -1
  181. package/src/__tests__/swarm-session-integration.test.ts +1 -1
  182. package/src/__tests__/swarm-tool.test.ts +1 -1
  183. package/src/__tests__/task-management-tools.test.ts +1 -1
  184. package/src/__tests__/task-tools.test.ts +1 -1
  185. package/src/__tests__/terminal-tools.test.ts +1 -1
  186. package/src/__tests__/test-support/browser-skill-harness.ts +39 -27
  187. package/src/__tests__/test-support/computer-use-skill-harness.ts +14 -14
  188. package/src/__tests__/tool-approval-handler.test.ts +15 -15
  189. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
  190. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  191. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
  192. package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
  193. package/src/__tests__/tool-executor.test.ts +23 -182
  194. package/src/__tests__/tool-grant-request-escalation.test.ts +11 -11
  195. package/src/__tests__/tool-permission-simulate-handler.test.ts +4 -4
  196. package/src/__tests__/transfer-progress-screen.test.ts +1180 -0
  197. package/src/__tests__/trust-context-guards.test.ts +25 -29
  198. package/src/__tests__/trusted-contact-approval-notifier.test.ts +23 -21
  199. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +37 -40
  200. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +29 -25
  201. package/src/__tests__/trusted-contact-multichannel.test.ts +25 -24
  202. package/src/__tests__/trusted-contact-verification.test.ts +63 -77
  203. package/src/__tests__/turn-commit.test.ts +18 -18
  204. package/src/__tests__/twilio-provider.test.ts +7 -7
  205. package/src/__tests__/validation-results-screen.test.ts +1107 -0
  206. package/src/__tests__/view-image-tool.test.ts +1 -1
  207. package/src/__tests__/voice-invite-redemption.test.ts +3 -2
  208. package/src/__tests__/voice-scoped-grant-consumer.test.ts +12 -12
  209. package/src/__tests__/voice-session-bridge.test.ts +24 -24
  210. package/src/agent/attachments.ts +3 -1
  211. package/src/agent/loop.ts +13 -13
  212. package/src/agent/message-types.ts +13 -7
  213. package/src/amazon/cart.ts +59 -32
  214. package/src/amazon/checkout.ts +25 -14
  215. package/src/amazon/client.ts +68 -48
  216. package/src/amazon/product-details.ts +3 -3
  217. package/src/amazon/request-extractor.ts +46 -31
  218. package/src/amazon/search.ts +6 -4
  219. package/src/amazon/session.ts +33 -24
  220. package/src/approvals/AGENTS.md +26 -0
  221. package/src/approvals/approval-primitive.ts +87 -64
  222. package/src/approvals/guardian-decision-primitive.ts +172 -81
  223. package/src/approvals/guardian-request-resolvers.ts +262 -155
  224. package/src/autonomy/autonomy-resolver.ts +7 -5
  225. package/src/autonomy/autonomy-store.ts +34 -19
  226. package/src/autonomy/disposition-mapper.ts +5 -5
  227. package/src/autonomy/index.ts +6 -6
  228. package/src/autonomy/types.ts +7 -3
  229. package/src/browser-extension-relay/client.ts +50 -19
  230. package/src/browser-extension-relay/protocol.ts +11 -11
  231. package/src/browser-extension-relay/server.ts +45 -20
  232. package/src/bundler/app-bundler.ts +75 -50
  233. package/src/bundler/bundle-scanner.ts +145 -41
  234. package/src/bundler/bundle-signer.ts +16 -14
  235. package/src/bundler/signature-verifier.ts +36 -33
  236. package/src/calls/call-constants.ts +10 -3
  237. package/src/calls/call-controller.ts +473 -214
  238. package/src/calls/call-conversation-messages.ts +25 -15
  239. package/src/calls/call-domain.ts +401 -148
  240. package/src/calls/call-pointer-message-composer.ts +26 -21
  241. package/src/calls/call-pointer-messages.ts +52 -28
  242. package/src/calls/call-recovery.ts +53 -37
  243. package/src/calls/call-state-machine.ts +37 -7
  244. package/src/calls/call-state.ts +35 -13
  245. package/src/calls/call-store.ts +165 -77
  246. package/src/calls/elevenlabs-client.ts +39 -20
  247. package/src/calls/guardian-action-sweep.ts +42 -24
  248. package/src/calls/guardian-dispatch.ts +79 -56
  249. package/src/calls/guardian-question-copy.ts +28 -23
  250. package/src/calls/relay-server.ts +1121 -532
  251. package/src/calls/speaker-identification.ts +21 -15
  252. package/src/calls/twilio-config.ts +34 -17
  253. package/src/calls/twilio-provider.ts +108 -55
  254. package/src/calls/twilio-rest.ts +212 -100
  255. package/src/calls/twilio-routes.ts +165 -92
  256. package/src/calls/types.ts +55 -7
  257. package/src/calls/voice-quality.ts +6 -4
  258. package/src/calls/voice-session-bridge.ts +181 -133
  259. package/src/channels/config.ts +17 -13
  260. package/src/channels/types.ts +38 -10
  261. package/src/cli/amazon.ts +333 -227
  262. package/src/cli/config-commands.ts +236 -146
  263. package/src/cli/core-commands.ts +403 -329
  264. package/src/cli/email-guardrails.ts +38 -19
  265. package/src/cli/email.ts +207 -153
  266. package/src/cli/influencer.ts +58 -56
  267. package/src/cli/integrations.ts +362 -0
  268. package/src/cli/ipc-client.ts +24 -19
  269. package/src/cli/map.ts +176 -129
  270. package/src/cli/mcp.ts +260 -152
  271. package/src/cli/sequence.ts +165 -107
  272. package/src/cli/twitter.ts +302 -218
  273. package/src/cli.ts +418 -279
  274. package/src/commands/cc-command-registry.ts +52 -27
  275. package/src/config/agent-schema.ts +217 -134
  276. package/src/config/assistant-feature-flags.ts +23 -18
  277. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +19 -0
  278. package/src/config/bundled-skills/app-builder/tools/app-create.ts +7 -4
  279. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +6 -3
  280. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +7 -4
  281. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +6 -3
  282. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +6 -3
  283. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +7 -4
  284. package/src/config/bundled-skills/app-builder/tools/app-list.ts +6 -3
  285. package/src/config/bundled-skills/app-builder/tools/app-query.ts +6 -3
  286. package/src/config/bundled-skills/app-builder/tools/app-update.ts +6 -3
  287. package/src/config/bundled-skills/browser/tools/browser-click.ts +5 -2
  288. package/src/config/bundled-skills/browser/tools/browser-close.ts +5 -2
  289. package/src/config/bundled-skills/browser/tools/browser-extract.ts +5 -2
  290. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +5 -2
  291. package/src/config/bundled-skills/browser/tools/browser-hover.ts +5 -2
  292. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +5 -2
  293. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +5 -2
  294. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +5 -2
  295. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +5 -2
  296. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +5 -2
  297. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +5 -2
  298. package/src/config/bundled-skills/browser/tools/browser-type.ts +5 -2
  299. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +13 -6
  300. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +5 -2
  301. package/src/config/bundled-skills/claude-code/TOOLS.json +4 -0
  302. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +5 -2
  303. package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
  304. package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +6 -3
  305. package/src/config/bundled-skills/computer-use/tools/computer-use-done.ts +6 -3
  306. package/src/config/bundled-skills/computer-use/tools/computer-use-double-click.ts +10 -3
  307. package/src/config/bundled-skills/computer-use/tools/computer-use-drag.ts +6 -3
  308. package/src/config/bundled-skills/computer-use/tools/computer-use-key.ts +6 -3
  309. package/src/config/bundled-skills/computer-use/tools/computer-use-open-app.ts +6 -3
  310. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +10 -3
  311. package/src/config/bundled-skills/computer-use/tools/computer-use-respond.ts +6 -3
  312. package/src/config/bundled-skills/computer-use/tools/computer-use-right-click.ts +10 -3
  313. package/src/config/bundled-skills/computer-use/tools/computer-use-run-applescript.ts +10 -3
  314. package/src/config/bundled-skills/computer-use/tools/computer-use-scroll.ts +6 -3
  315. package/src/config/bundled-skills/computer-use/tools/computer-use-type-text.ts +6 -3
  316. package/src/config/bundled-skills/computer-use/tools/computer-use-wait.ts +6 -3
  317. package/src/config/bundled-skills/configure-settings/SKILL.md +28 -14
  318. package/src/config/bundled-skills/contacts/SKILL.md +446 -15
  319. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +99 -20
  320. package/src/config/bundled-skills/contacts/tools/contact-search.ts +74 -17
  321. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +89 -26
  322. package/src/config/bundled-skills/document/tools/document-create.ts +5 -2
  323. package/src/config/bundled-skills/document/tools/document-update.ts +5 -2
  324. package/src/config/bundled-skills/doordash/doordash-cli.ts +17 -7
  325. package/src/config/bundled-skills/email-setup/SKILL.md +9 -9
  326. package/src/config/bundled-skills/followups/tools/followup-create.ts +5 -2
  327. package/src/config/bundled-skills/followups/tools/followup-list.ts +5 -2
  328. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +5 -2
  329. package/src/config/bundled-skills/google-calendar/calendar-client.ts +44 -32
  330. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +11 -5
  331. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +13 -7
  332. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +11 -5
  333. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +13 -7
  334. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +28 -12
  335. package/src/config/bundled-skills/google-calendar/tools/shared.ts +6 -4
  336. package/src/config/bundled-skills/google-calendar/types.ts +3 -3
  337. package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +46 -24
  338. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +36 -19
  339. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +60 -35
  340. package/src/config/bundled-skills/mcp-setup/SKILL.md +75 -0
  341. package/src/config/bundled-skills/media-processing/SKILL.md +55 -15
  342. package/src/config/bundled-skills/media-processing/TOOLS.json +20 -2
  343. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +12 -10
  344. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +34 -19
  345. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +82 -66
  346. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +148 -0
  347. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +1 -1
  348. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +8 -3
  349. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +117 -53
  350. package/src/config/bundled-skills/media-processing/services/gemini-video.ts +273 -0
  351. package/src/config/bundled-skills/media-processing/services/preprocess.ts +185 -97
  352. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +32 -27
  353. package/src/config/bundled-skills/media-processing/services/reduce.ts +101 -24
  354. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +121 -55
  355. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +58 -24
  356. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +177 -91
  357. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +98 -70
  358. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +59 -19
  359. package/src/config/bundled-skills/media-processing/tools/media-status.ts +26 -10
  360. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +29 -14
  361. package/src/config/bundled-skills/messaging/SKILL.md +7 -5
  362. package/src/config/bundled-skills/messaging/TOOLS.json +7 -7
  363. package/src/config/bundled-skills/messaging/tools/gmail-archive-by-query.ts +31 -13
  364. package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +16 -10
  365. package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +18 -9
  366. package/src/config/bundled-skills/messaging/tools/gmail-download-attachment.ts +23 -16
  367. package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +28 -12
  368. package/src/config/bundled-skills/messaging/tools/gmail-filters.ts +41 -21
  369. package/src/config/bundled-skills/messaging/tools/gmail-follow-up.ts +44 -23
  370. package/src/config/bundled-skills/messaging/tools/gmail-forward.ts +73 -33
  371. package/src/config/bundled-skills/messaging/tools/gmail-label.ts +15 -9
  372. package/src/config/bundled-skills/messaging/tools/gmail-list-attachments.ts +22 -14
  373. package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +99 -50
  374. package/src/config/bundled-skills/messaging/tools/gmail-send-draft.ts +14 -8
  375. package/src/config/bundled-skills/messaging/tools/gmail-send-with-attachments.ts +63 -44
  376. package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +90 -46
  377. package/src/config/bundled-skills/messaging/tools/gmail-summarize-thread.ts +43 -22
  378. package/src/config/bundled-skills/messaging/tools/gmail-trash.ts +15 -9
  379. package/src/config/bundled-skills/messaging/tools/gmail-triage.ts +51 -22
  380. package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +62 -26
  381. package/src/config/bundled-skills/messaging/tools/gmail-vacation.ts +34 -19
  382. package/src/config/bundled-skills/messaging/tools/google-contacts.ts +32 -16
  383. package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +10 -4
  384. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +91 -47
  385. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +21 -9
  386. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +9 -3
  387. package/src/config/bundled-skills/messaging/tools/messaging-draft.ts +30 -17
  388. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +10 -4
  389. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +14 -6
  390. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +16 -5
  391. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +63 -36
  392. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +10 -4
  393. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +30 -12
  394. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +48 -29
  395. package/src/config/bundled-skills/messaging/tools/scan-result-store.ts +20 -6
  396. package/src/config/bundled-skills/messaging/tools/send-notification.ts +1 -1
  397. package/src/config/bundled-skills/messaging/tools/sequence-analytics.ts +59 -22
  398. package/src/config/bundled-skills/messaging/tools/sequence-cancel.ts +13 -7
  399. package/src/config/bundled-skills/messaging/tools/sequence-create.ts +27 -12
  400. package/src/config/bundled-skills/messaging/tools/sequence-delete.ts +14 -6
  401. package/src/config/bundled-skills/messaging/tools/sequence-enroll.ts +30 -11
  402. package/src/config/bundled-skills/messaging/tools/sequence-enrollment-list.ts +16 -8
  403. package/src/config/bundled-skills/messaging/tools/sequence-get.ts +31 -13
  404. package/src/config/bundled-skills/messaging/tools/sequence-import.ts +38 -22
  405. package/src/config/bundled-skills/messaging/tools/sequence-list.ts +16 -7
  406. package/src/config/bundled-skills/messaging/tools/sequence-pause.ts +29 -10
  407. package/src/config/bundled-skills/messaging/tools/sequence-resume.ts +16 -8
  408. package/src/config/bundled-skills/messaging/tools/sequence-update.ts +35 -16
  409. package/src/config/bundled-skills/messaging/tools/shared.ts +26 -12
  410. package/src/config/bundled-skills/notifications/tools/send-notification.ts +69 -34
  411. package/src/config/bundled-skills/notifications/tools/shared.ts +1 -1
  412. package/src/config/bundled-skills/phone-calls/SKILL.md +46 -48
  413. package/src/config/bundled-skills/phone-calls/tools/call-end.ts +1 -1
  414. package/src/config/bundled-skills/phone-calls/tools/call-start.ts +1 -1
  415. package/src/config/bundled-skills/phone-calls/tools/call-status.ts +1 -1
  416. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +91 -51
  417. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +30 -16
  418. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +66 -27
  419. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +89 -42
  420. package/src/config/bundled-skills/public-ingress/SKILL.md +26 -19
  421. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +5 -2
  422. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +5 -2
  423. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +5 -2
  424. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +5 -2
  425. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +5 -2
  426. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +5 -2
  427. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +5 -2
  428. package/src/config/bundled-skills/screen-recording/SKILL.md +11 -3
  429. package/src/config/bundled-skills/self-upgrade/SKILL.md +9 -8
  430. package/src/config/bundled-skills/slack/TOOLS.json +33 -15
  431. package/src/config/bundled-skills/slack/tools/shared.ts +7 -5
  432. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +11 -5
  433. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +11 -5
  434. package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +46 -16
  435. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +11 -5
  436. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +28 -0
  437. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +12 -6
  438. package/src/config/bundled-skills/sms-setup/SKILL.md +5 -8
  439. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +5 -2
  440. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +5 -2
  441. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +5 -2
  442. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +5 -2
  443. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +5 -2
  444. package/src/config/bundled-skills/tasks/tools/task-delete.ts +5 -2
  445. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +5 -2
  446. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +5 -2
  447. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +5 -2
  448. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +5 -2
  449. package/src/config/bundled-skills/tasks/tools/task-list.ts +5 -2
  450. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +5 -2
  451. package/src/config/bundled-skills/tasks/tools/task-run.ts +5 -2
  452. package/src/config/bundled-skills/tasks/tools/task-save.ts +5 -2
  453. package/src/config/bundled-skills/telegram-setup/SKILL.md +7 -8
  454. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +232 -127
  455. package/src/config/bundled-skills/twilio-setup/SKILL.md +7 -12
  456. package/src/config/bundled-skills/twitter/SKILL.md +19 -2
  457. package/src/config/bundled-skills/voice-setup/SKILL.md +5 -5
  458. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +5 -2
  459. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +5 -2
  460. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +5 -2
  461. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +5 -2
  462. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +5 -2
  463. package/src/config/bundled-skills/weather/tools/get-weather.ts +5 -2
  464. package/src/config/calls-schema.ts +108 -63
  465. package/src/config/computer-use-prompt.ts +7 -7
  466. package/src/config/core-schema.ts +239 -155
  467. package/src/config/defaults.ts +2 -2
  468. package/src/config/elevenlabs-schema.ts +15 -15
  469. package/src/config/env-registry.ts +33 -33
  470. package/src/config/feature-flag-registry.json +31 -7
  471. package/src/config/loader.ts +118 -58
  472. package/src/config/mcp-schema.ts +29 -15
  473. package/src/config/memory-schema.ts +434 -229
  474. package/src/config/notifications-schema.ts +4 -4
  475. package/src/config/sandbox-schema.ts +2 -2
  476. package/src/config/schema.ts +12 -2
  477. package/src/config/skill-state.ts +27 -15
  478. package/src/config/skills-schema.ts +72 -23
  479. package/src/config/skills.ts +303 -143
  480. package/src/config/system-prompt.ts +25 -6
  481. package/src/config/types.ts +1 -1
  482. package/src/config/update-bulletin-format.ts +3 -3
  483. package/src/config/update-bulletin-state.ts +15 -6
  484. package/src/config/update-bulletin-template-path.ts +8 -4
  485. package/src/config/update-bulletin.ts +33 -14
  486. package/src/config/user-reference.ts +8 -8
  487. package/src/contacts/contact-events.ts +21 -0
  488. package/src/contacts/contact-store.ts +622 -100
  489. package/src/contacts/contacts-write.ts +287 -0
  490. package/src/contacts/index.ts +13 -4
  491. package/src/contacts/startup-migration.ts +21 -0
  492. package/src/contacts/types.ts +47 -2
  493. package/src/context/token-estimator.ts +54 -31
  494. package/src/context/tool-result-truncation.ts +41 -7
  495. package/src/context/window-manager.ts +225 -120
  496. package/src/daemon/approval-generators.ts +83 -55
  497. package/src/daemon/approved-devices-store.ts +33 -20
  498. package/src/daemon/assistant-attachments.ts +134 -98
  499. package/src/daemon/auth-manager.ts +17 -15
  500. package/src/daemon/classifier.ts +117 -46
  501. package/src/daemon/computer-use-session.ts +316 -187
  502. package/src/daemon/config-watcher.ts +91 -44
  503. package/src/daemon/connection-policy.ts +18 -10
  504. package/src/daemon/context-overflow-approval.ts +48 -0
  505. package/src/daemon/context-overflow-policy.ts +50 -0
  506. package/src/daemon/context-overflow-reducer.ts +300 -0
  507. package/src/daemon/daemon-control.ts +79 -51
  508. package/src/daemon/date-context.ts +119 -69
  509. package/src/daemon/dictation-profile-store.ts +94 -48
  510. package/src/daemon/dictation-text-processing.ts +33 -12
  511. package/src/daemon/doordash-steps.ts +92 -49
  512. package/src/daemon/guardian-action-generators.ts +62 -46
  513. package/src/daemon/guardian-verification-intent.ts +31 -18
  514. package/src/daemon/handlers/apps.ts +257 -111
  515. package/src/daemon/handlers/avatar.ts +20 -15
  516. package/src/daemon/handlers/computer-use.ts +82 -39
  517. package/src/daemon/handlers/config-channels.ts +146 -69
  518. package/src/daemon/handlers/config-heartbeat.ts +114 -59
  519. package/src/daemon/handlers/config-inbox.ts +277 -106
  520. package/src/daemon/handlers/config-ingress.ts +127 -55
  521. package/src/daemon/handlers/config-integrations.ts +145 -88
  522. package/src/daemon/handlers/config-model.ts +58 -22
  523. package/src/daemon/handlers/config-platform.ts +40 -16
  524. package/src/daemon/handlers/config-scheduling.ts +109 -48
  525. package/src/daemon/handlers/config-slack-channel.ts +67 -35
  526. package/src/daemon/handlers/config-slack.ts +21 -20
  527. package/src/daemon/handlers/config-telegram.ts +100 -70
  528. package/src/daemon/handlers/config-tools.ts +103 -55
  529. package/src/daemon/handlers/config-trust.ts +50 -20
  530. package/src/daemon/handlers/config.ts +72 -24
  531. package/src/daemon/handlers/contacts.ts +163 -0
  532. package/src/daemon/handlers/diagnostics.ts +90 -48
  533. package/src/daemon/handlers/documents.ts +74 -46
  534. package/src/daemon/handlers/guardian-actions.ts +118 -71
  535. package/src/daemon/handlers/home-base.ts +19 -16
  536. package/src/daemon/handlers/identity.ts +65 -45
  537. package/src/daemon/handlers/index.ts +78 -54
  538. package/src/daemon/handlers/misc.ts +664 -234
  539. package/src/daemon/handlers/navigate-settings.ts +14 -11
  540. package/src/daemon/handlers/oauth-connect.ts +48 -35
  541. package/src/daemon/handlers/open-bundle-handler.ts +31 -24
  542. package/src/daemon/handlers/pairing.ts +51 -25
  543. package/src/daemon/handlers/publish.ts +55 -33
  544. package/src/daemon/handlers/recording.ts +378 -162
  545. package/src/daemon/handlers/sessions.ts +923 -423
  546. package/src/daemon/handlers/shared.ts +202 -117
  547. package/src/daemon/handlers/signing.ts +25 -6
  548. package/src/daemon/handlers/subagents.ts +117 -56
  549. package/src/daemon/handlers/twitter-auth.ts +70 -49
  550. package/src/daemon/handlers/work-items.ts +264 -112
  551. package/src/daemon/handlers/workspace-files.ts +27 -20
  552. package/src/daemon/handlers.ts +2 -2
  553. package/src/daemon/history-repair.ts +16 -15
  554. package/src/daemon/identity-helpers.ts +4 -4
  555. package/src/daemon/install-cli-launchers.ts +33 -22
  556. package/src/daemon/ipc-blob-store.ts +38 -24
  557. package/src/daemon/ipc-contract/apps.ts +61 -49
  558. package/src/daemon/ipc-contract/computer-use.ts +47 -37
  559. package/src/daemon/ipc-contract/contacts.ts +69 -0
  560. package/src/daemon/ipc-contract/diagnostics.ts +14 -14
  561. package/src/daemon/ipc-contract/documents.ts +8 -8
  562. package/src/daemon/ipc-contract/guardian-actions.ts +4 -4
  563. package/src/daemon/ipc-contract/inbox.ts +16 -16
  564. package/src/daemon/ipc-contract/integrations.ts +57 -44
  565. package/src/daemon/ipc-contract/memory.ts +3 -5
  566. package/src/daemon/ipc-contract/messages.ts +95 -69
  567. package/src/daemon/ipc-contract/notifications.ts +10 -6
  568. package/src/daemon/ipc-contract/pairing.ts +8 -8
  569. package/src/daemon/ipc-contract/schedules.ts +20 -20
  570. package/src/daemon/ipc-contract/sessions.ts +88 -57
  571. package/src/daemon/ipc-contract/settings.ts +12 -7
  572. package/src/daemon/ipc-contract/shared.ts +9 -7
  573. package/src/daemon/ipc-contract/skills.ts +46 -26
  574. package/src/daemon/ipc-contract/subagents.ts +9 -9
  575. package/src/daemon/ipc-contract/trust.ts +11 -11
  576. package/src/daemon/ipc-contract/work-items.ts +33 -28
  577. package/src/daemon/ipc-contract/workspace.ts +28 -21
  578. package/src/daemon/ipc-contract-inventory.json +8 -0
  579. package/src/daemon/ipc-contract-inventory.ts +29 -26
  580. package/src/daemon/ipc-contract.ts +111 -44
  581. package/src/daemon/ipc-handler.ts +27 -19
  582. package/src/daemon/ipc-protocol.ts +22 -12
  583. package/src/daemon/ipc-validate.ts +91 -46
  584. package/src/daemon/lifecycle.ts +25 -1
  585. package/src/daemon/main.ts +10 -8
  586. package/src/daemon/media-visibility-policy.ts +3 -1
  587. package/src/daemon/pairing-store.ts +72 -40
  588. package/src/daemon/providers-setup.ts +35 -25
  589. package/src/daemon/recording-executor.ts +37 -30
  590. package/src/daemon/recording-intent-fallback.ts +58 -28
  591. package/src/daemon/recording-intent.ts +71 -61
  592. package/src/daemon/ride-shotgun-handler.ts +201 -121
  593. package/src/daemon/seed-files.ts +28 -17
  594. package/src/daemon/server.ts +23 -14
  595. package/src/daemon/session-agent-loop-handlers.ts +261 -135
  596. package/src/daemon/session-agent-loop.ts +795 -253
  597. package/src/daemon/session-attachments.ts +104 -39
  598. package/src/daemon/session-conflict-gate.ts +72 -28
  599. package/src/daemon/session-dynamic-profile.ts +36 -22
  600. package/src/daemon/session-error.ts +50 -45
  601. package/src/daemon/session-evictor.ts +17 -10
  602. package/src/daemon/session-history.ts +201 -89
  603. package/src/daemon/session-lifecycle.ts +79 -42
  604. package/src/daemon/session-media-retry.ts +89 -41
  605. package/src/daemon/session-memory.ts +77 -55
  606. package/src/daemon/session-messaging.ts +261 -111
  607. package/src/daemon/session-notifiers.ts +57 -45
  608. package/src/daemon/session-process.ts +370 -154
  609. package/src/daemon/session-queue-manager.ts +30 -13
  610. package/src/daemon/session-runtime-assembly.ts +61 -15
  611. package/src/daemon/session-skill-tools.ts +84 -36
  612. package/src/daemon/session-slash.ts +178 -113
  613. package/src/daemon/session-surfaces.ts +498 -211
  614. package/src/daemon/session-tool-setup.ts +22 -17
  615. package/src/daemon/session-usage.ts +26 -13
  616. package/src/daemon/session-workspace.ts +7 -4
  617. package/src/daemon/session.ts +18 -19
  618. package/src/daemon/shutdown-handlers.ts +36 -33
  619. package/src/daemon/tls-certs.ts +90 -57
  620. package/src/daemon/tool-side-effects.ts +97 -65
  621. package/src/daemon/trace-emitter.ts +8 -7
  622. package/src/daemon/video-thumbnail.ts +55 -25
  623. package/src/daemon/watch-handler.ts +164 -86
  624. package/src/email/provider.ts +1 -1
  625. package/src/email/providers/agentmail.ts +87 -45
  626. package/src/email/providers/index.ts +19 -14
  627. package/src/email/service.ts +52 -24
  628. package/src/email/types.ts +2 -2
  629. package/src/errors.ts +1 -1
  630. package/src/events/bus.ts +30 -10
  631. package/src/events/domain-events.ts +19 -13
  632. package/src/events/index.ts +6 -6
  633. package/src/events/tool-audit-listener.ts +34 -20
  634. package/src/events/tool-domain-event-publisher.ts +22 -20
  635. package/src/events/tool-metrics-listener.ts +26 -21
  636. package/src/events/tool-notification-listener.ts +5 -5
  637. package/src/events/tool-profiling-listener.ts +33 -23
  638. package/src/events/tool-trace-listener.ts +70 -46
  639. package/src/export/formatter.ts +38 -32
  640. package/src/followups/followup-store.ts +43 -36
  641. package/src/followups/index.ts +2 -2
  642. package/src/followups/types.ts +1 -1
  643. package/src/gallery/default-gallery.ts +37 -34
  644. package/src/gallery/gallery-manifest.ts +9 -9
  645. package/src/heartbeat/heartbeat-service.ts +59 -37
  646. package/src/home-base/app-link-store.ts +14 -12
  647. package/src/home-base/bootstrap.ts +14 -8
  648. package/src/home-base/prebuilt/seed.ts +35 -26
  649. package/src/home-base/prebuilt-home-base-updater.ts +14 -8
  650. package/src/hooks/cli.ts +56 -43
  651. package/src/hooks/config.ts +27 -14
  652. package/src/hooks/discovery.ts +53 -33
  653. package/src/hooks/manager.ts +50 -26
  654. package/src/hooks/runner.ts +35 -29
  655. package/src/hooks/templates.ts +38 -15
  656. package/src/hooks/types.ts +13 -13
  657. package/src/inbound/platform-callback-registration.ts +21 -15
  658. package/src/inbound/public-ingress-urls.ts +9 -6
  659. package/src/index.ts +20 -19
  660. package/src/influencer/client.ts +269 -108
  661. package/src/instrument.ts +3 -1
  662. package/src/logfire.ts +64 -39
  663. package/src/mcp/client.ts +107 -55
  664. package/src/mcp/manager.ts +45 -18
  665. package/src/mcp/mcp-oauth-provider.ts +114 -62
  666. package/src/media/gemini-image-service.ts +28 -21
  667. package/src/memory/account-store.ts +16 -9
  668. package/src/memory/admin.ts +87 -57
  669. package/src/memory/app-git-service.ts +77 -47
  670. package/src/memory/app-store.ts +151 -77
  671. package/src/memory/attachments-store.ts +123 -53
  672. package/src/memory/canonical-guardian-store.ts +190 -48
  673. package/src/memory/channel-delivery-store.ts +5 -5
  674. package/src/memory/channel-guardian-store.ts +31 -16
  675. package/src/memory/checkpoints.ts +14 -7
  676. package/src/memory/clarification-resolver.ts +219 -104
  677. package/src/memory/conflict-intent.ts +74 -23
  678. package/src/memory/conflict-policy.ts +20 -7
  679. package/src/memory/conflict-store.ts +144 -94
  680. package/src/memory/contradiction-checker.ts +257 -132
  681. package/src/memory/conversation-attention-store.ts +72 -32
  682. package/src/memory/conversation-bootstrap.ts +28 -0
  683. package/src/memory/conversation-crud.ts +12 -5
  684. package/src/memory/conversation-display-order-migration.ts +7 -7
  685. package/src/memory/conversation-key-store.ts +18 -13
  686. package/src/memory/conversation-queries.ts +130 -52
  687. package/src/memory/conversation-store.ts +43 -26
  688. package/src/memory/conversation-title-service.ts +89 -66
  689. package/src/memory/db-init.ts +90 -2
  690. package/src/memory/db.ts +10 -3
  691. package/src/memory/delivery-channels.ts +12 -6
  692. package/src/memory/delivery-crud.ts +26 -12
  693. package/src/memory/delivery-status.ts +19 -16
  694. package/src/memory/embedding-backend.ts +205 -77
  695. package/src/memory/embedding-gemini.ts +23 -10
  696. package/src/memory/embedding-local.ts +89 -44
  697. package/src/memory/embedding-ollama.ts +25 -13
  698. package/src/memory/embedding-openai.ts +20 -11
  699. package/src/memory/embedding-runtime-manager.ts +163 -90
  700. package/src/memory/entity-extractor.ts +185 -123
  701. package/src/memory/external-conversation-store.ts +30 -12
  702. package/src/memory/fingerprint.ts +2 -2
  703. package/src/memory/fts-reconciler.ts +57 -28
  704. package/src/memory/guardian-action-store.ts +162 -100
  705. package/src/memory/guardian-approvals.ts +63 -129
  706. package/src/memory/guardian-rate-limits.ts +20 -9
  707. package/src/memory/guardian-verification.ts +82 -35
  708. package/src/memory/indexer.ts +96 -55
  709. package/src/memory/ingress-invite-store.ts +28 -169
  710. package/src/memory/items-extractor.ts +313 -157
  711. package/src/memory/job-handlers/backfill.ts +116 -63
  712. package/src/memory/job-handlers/cleanup.ts +64 -41
  713. package/src/memory/job-handlers/conflict.ts +90 -49
  714. package/src/memory/job-handlers/embedding.ts +32 -17
  715. package/src/memory/job-handlers/extraction.ts +58 -33
  716. package/src/memory/job-handlers/index-maintenance.ts +31 -17
  717. package/src/memory/job-handlers/media-processing.ts +65 -24
  718. package/src/memory/job-handlers/summarization.ts +186 -128
  719. package/src/memory/job-utils.ts +100 -57
  720. package/src/memory/jobs-store.ts +235 -142
  721. package/src/memory/jobs-worker.ts +167 -83
  722. package/src/memory/llm-request-log-store.ts +13 -11
  723. package/src/memory/llm-usage-store.ts +35 -26
  724. package/src/memory/media-store.ts +151 -44
  725. package/src/memory/message-content.ts +28 -18
  726. package/src/memory/migrations/001-job-deferrals.ts +11 -5
  727. package/src/memory/migrations/002-tool-invocations-fk.ts +14 -6
  728. package/src/memory/migrations/003-memory-fts-backfill.ts +11 -5
  729. package/src/memory/migrations/004-entity-relation-dedup.ts +17 -11
  730. package/src/memory/migrations/005-fingerprint-scope-unique.ts +36 -21
  731. package/src/memory/migrations/006-scope-salted-fingerprints.ts +35 -20
  732. package/src/memory/migrations/007-assistant-id-to-self.ts +40 -27
  733. package/src/memory/migrations/008-remove-assistant-id-columns.ts +58 -36
  734. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +36 -22
  735. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +21 -11
  736. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +30 -15
  737. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +4 -2
  738. package/src/memory/migrations/013-guardian-action-tables.ts +29 -11
  739. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +35 -21
  740. package/src/memory/migrations/015-drop-active-search-index.ts +17 -11
  741. package/src/memory/migrations/016-memory-segments-indexes.ts +7 -3
  742. package/src/memory/migrations/017-memory-items-indexes.ts +4 -2
  743. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -5
  744. package/src/memory/migrations/019-notification-tables-schema-migration.ts +34 -20
  745. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +87 -53
  746. package/src/memory/migrations/021-conversation-status-indexes.ts +7 -3
  747. package/src/memory/migrations/022-add-origin-interface.ts +4 -2
  748. package/src/memory/migrations/023-memory-item-sources-indexes.ts +4 -2
  749. package/src/memory/migrations/024-embedding-vector-blob.ts +34 -18
  750. package/src/memory/migrations/025-messages-fts-backfill.ts +11 -5
  751. package/src/memory/migrations/026-guardian-verification-sessions.ts +80 -14
  752. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +42 -26
  753. package/src/memory/migrations/027-notification-delivery-pairing-columns.ts +22 -8
  754. package/src/memory/migrations/027a-guardian-bootstrap-token.ts +11 -3
  755. package/src/memory/migrations/028-call-session-mode.ts +13 -3
  756. package/src/memory/migrations/028-notification-delivery-client-ack.ts +22 -8
  757. package/src/memory/migrations/029-channel-inbound-delivered-segments.ts +7 -3
  758. package/src/memory/migrations/030-guardian-action-followup.ts +46 -8
  759. package/src/memory/migrations/030-guardian-verification-purpose.ts +4 -2
  760. package/src/memory/migrations/031-conversations-thread-type-index.ts +4 -2
  761. package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +4 -2
  762. package/src/memory/migrations/032-notification-delivery-thread-decision.ts +22 -8
  763. package/src/memory/migrations/033-scoped-approval-grants.ts +1 -1
  764. package/src/memory/migrations/034-guardian-action-tool-metadata.ts +15 -3
  765. package/src/memory/migrations/035-guardian-action-supersession.ts +15 -3
  766. package/src/memory/migrations/036-normalize-phone-identities.ts +101 -87
  767. package/src/memory/migrations/037-voice-invite-columns.ts +22 -4
  768. package/src/memory/migrations/038-actor-token-records.ts +5 -9
  769. package/src/memory/migrations/039-actor-refresh-token-records.ts +7 -13
  770. package/src/memory/migrations/100-core-tables.ts +1 -1
  771. package/src/memory/migrations/101-watchers-and-logs.ts +1 -1
  772. package/src/memory/migrations/103-complex-migrations.ts +9 -9
  773. package/src/memory/migrations/104-core-indexes.ts +188 -64
  774. package/src/memory/migrations/105-contacts-and-triage.ts +28 -10
  775. package/src/memory/migrations/106-call-sessions.ts +58 -16
  776. package/src/memory/migrations/107-followups.ts +16 -6
  777. package/src/memory/migrations/108-tasks-and-work-items.ts +43 -11
  778. package/src/memory/migrations/109-external-conversation-bindings.ts +11 -5
  779. package/src/memory/migrations/110-channel-guardian.ts +48 -10
  780. package/src/memory/migrations/111-media-assets.ts +52 -18
  781. package/src/memory/migrations/112-assistant-inbox.ts +32 -12
  782. package/src/memory/migrations/113-late-migrations.ts +12 -12
  783. package/src/memory/migrations/114-notifications.ts +28 -12
  784. package/src/memory/migrations/115-sequences.ts +10 -4
  785. package/src/memory/migrations/116-messages-fts.ts +1 -1
  786. package/src/memory/migrations/117-conversation-attention.ts +16 -6
  787. package/src/memory/migrations/118-reminder-routing-intent.ts +7 -3
  788. package/src/memory/migrations/119-schema-indexes-and-columns.ts +35 -15
  789. package/src/memory/migrations/120-fk-cascade-rebuilds.ts +36 -17
  790. package/src/memory/migrations/121-canonical-guardian-requests.ts +25 -9
  791. package/src/memory/migrations/122-canonical-guardian-requester-chat-id.ts +11 -3
  792. package/src/memory/migrations/123-canonical-guardian-deliveries-destination-index.ts +4 -2
  793. package/src/memory/migrations/124-voice-invite-display-metadata.ts +15 -3
  794. package/src/memory/migrations/125-guardian-principal-id-columns.ts +22 -4
  795. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +174 -126
  796. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +58 -42
  797. package/src/memory/migrations/128-contacts-role-principal.ts +26 -0
  798. package/src/memory/migrations/129-contact-channels-access-fields.ts +105 -0
  799. package/src/memory/migrations/130-contact-channels-type-ext-chat-id-index.ts +15 -0
  800. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +134 -0
  801. package/src/memory/migrations/132-contacts-assistant-id.ts +21 -0
  802. package/src/memory/migrations/index.ts +82 -73
  803. package/src/memory/migrations/registry.ts +53 -37
  804. package/src/memory/migrations/validate-migration-state.ts +73 -46
  805. package/src/memory/profile-compiler.ts +58 -24
  806. package/src/memory/published-pages-store.ts +12 -16
  807. package/src/memory/qdrant-circuit-breaker.ts +28 -20
  808. package/src/memory/qdrant-client.ts +99 -63
  809. package/src/memory/qdrant-manager.ts +89 -57
  810. package/src/memory/query-builder.ts +9 -7
  811. package/src/memory/raw-query.ts +63 -14
  812. package/src/memory/recall-cache.ts +15 -8
  813. package/src/memory/retrieval-budget.ts +0 -1
  814. package/src/memory/retriever.ts +385 -192
  815. package/src/memory/schema-migration.ts +1 -1
  816. package/src/memory/schema.ts +44 -56
  817. package/src/memory/scoped-approval-grants.ts +99 -45
  818. package/src/memory/search/entity.ts +102 -40
  819. package/src/memory/search/formatting.ts +70 -52
  820. package/src/memory/search/lexical.ts +82 -43
  821. package/src/memory/search/ranking.ts +103 -39
  822. package/src/memory/search/semantic.ts +59 -35
  823. package/src/memory/search/types.ts +8 -8
  824. package/src/memory/segmenter.ts +20 -12
  825. package/src/memory/shared-app-links-store.ts +21 -16
  826. package/src/memory/task-memory-cleanup.ts +18 -8
  827. package/src/memory/tool-usage-store.ts +27 -19
  828. package/src/memory/validation.ts +4 -2
  829. package/src/messaging/activity-analyzer.ts +7 -7
  830. package/src/messaging/draft-store.ts +13 -10
  831. package/src/messaging/email-classifier.ts +73 -37
  832. package/src/messaging/index.ts +3 -3
  833. package/src/messaging/outreach-classifier.ts +76 -38
  834. package/src/messaging/provider-types.ts +2 -4
  835. package/src/messaging/provider.ts +37 -8
  836. package/src/messaging/providers/gmail/adapter.ts +183 -66
  837. package/src/messaging/providers/gmail/client.ts +3 -1
  838. package/src/messaging/providers/gmail/mime-builder.ts +21 -19
  839. package/src/messaging/providers/gmail/people-client.ts +22 -9
  840. package/src/messaging/providers/gmail/types.ts +6 -6
  841. package/src/messaging/providers/slack/adapter.ts +93 -43
  842. package/src/messaging/providers/slack/client.ts +100 -41
  843. package/src/messaging/providers/slack/types.ts +6 -0
  844. package/src/messaging/providers/sms/adapter.ts +76 -40
  845. package/src/messaging/providers/sms/client.ts +4 -4
  846. package/src/messaging/providers/telegram-bot/adapter.ts +52 -30
  847. package/src/messaging/providers/telegram-bot/client.ts +7 -7
  848. package/src/messaging/providers/whatsapp/adapter.ts +58 -31
  849. package/src/messaging/providers/whatsapp/client.ts +4 -4
  850. package/src/messaging/registry.ts +9 -5
  851. package/src/messaging/style-analyzer.ts +69 -39
  852. package/src/messaging/thread-summarizer.ts +101 -53
  853. package/src/messaging/triage-engine.ts +111 -82
  854. package/src/messaging/types.ts +10 -10
  855. package/src/migrations/config-merge.ts +18 -10
  856. package/src/migrations/data-layout.ts +35 -22
  857. package/src/migrations/data-merge.ts +17 -7
  858. package/src/migrations/hooks-merge.ts +43 -16
  859. package/src/migrations/index.ts +6 -6
  860. package/src/migrations/log.ts +9 -5
  861. package/src/migrations/skills-merge.ts +17 -7
  862. package/src/migrations/workspace-layout.ts +39 -25
  863. package/src/notifications/AGENTS.md +5 -0
  864. package/src/notifications/adapters/macos.ts +21 -14
  865. package/src/notifications/adapters/sms.ts +28 -15
  866. package/src/notifications/adapters/telegram.ts +24 -15
  867. package/src/notifications/broadcaster.ts +108 -52
  868. package/src/notifications/conversation-pairing.ts +64 -29
  869. package/src/notifications/copy-composer.ts +165 -95
  870. package/src/notifications/decision-engine.ts +353 -147
  871. package/src/notifications/decisions-store.ts +26 -10
  872. package/src/notifications/deliveries-store.ts +23 -13
  873. package/src/notifications/destination-resolver.ts +42 -24
  874. package/src/notifications/deterministic-checks.ts +78 -27
  875. package/src/notifications/emit-signal.ts +83 -45
  876. package/src/notifications/events-store.ts +13 -7
  877. package/src/notifications/guardian-question-mode.ts +125 -75
  878. package/src/notifications/preference-extractor.ts +85 -53
  879. package/src/notifications/preference-summary.ts +31 -18
  880. package/src/notifications/preferences-store.ts +29 -18
  881. package/src/notifications/runtime-dispatch.ts +22 -12
  882. package/src/notifications/signal.ts +4 -4
  883. package/src/notifications/thread-candidates.ts +59 -23
  884. package/src/notifications/thread-seed-composer.ts +45 -27
  885. package/src/notifications/types.ts +19 -10
  886. package/src/oauth/connect-orchestrator.ts +105 -54
  887. package/src/oauth/connect-types.ts +3 -3
  888. package/src/oauth/provider-profiles.ts +80 -59
  889. package/src/oauth/scope-policy.ts +5 -2
  890. package/src/oauth/token-persistence.ts +58 -24
  891. package/src/outbound-proxy/certs.ts +284 -0
  892. package/src/outbound-proxy/config.ts +94 -0
  893. package/src/outbound-proxy/connect-tunnel.ts +84 -0
  894. package/src/outbound-proxy/health.ts +62 -0
  895. package/src/outbound-proxy/host-pattern-match.ts +67 -0
  896. package/src/outbound-proxy/http-forwarder.ts +162 -0
  897. package/src/outbound-proxy/index.ts +80 -0
  898. package/src/outbound-proxy/logging.ts +193 -0
  899. package/src/outbound-proxy/mitm-handler.ts +292 -0
  900. package/src/outbound-proxy/policy.ts +172 -0
  901. package/src/outbound-proxy/router.ts +64 -0
  902. package/src/outbound-proxy/server.ts +145 -0
  903. package/src/outbound-proxy/types.ts +150 -0
  904. package/src/permissions/checker.ts +481 -189
  905. package/src/permissions/defaults.ts +135 -108
  906. package/src/permissions/prompter.ts +53 -27
  907. package/src/permissions/secret-prompter.ts +21 -15
  908. package/src/permissions/shell-identity.ts +47 -16
  909. package/src/permissions/trust-store.ts +185 -73
  910. package/src/permissions/types.ts +22 -12
  911. package/src/permissions/workspace-policy.ts +47 -38
  912. package/src/playbooks/index.ts +10 -2
  913. package/src/playbooks/playbook-compiler.ts +30 -24
  914. package/src/playbooks/types.ts +11 -8
  915. package/src/providers/anthropic/client.ts +325 -168
  916. package/src/providers/failover.ts +57 -22
  917. package/src/providers/fireworks/client.ts +9 -5
  918. package/src/providers/gemini/client.ts +61 -39
  919. package/src/providers/model-intents.ts +40 -33
  920. package/src/providers/ollama/client.ts +7 -7
  921. package/src/providers/openai/client.ts +106 -68
  922. package/src/providers/openrouter/client.ts +9 -5
  923. package/src/providers/provider-send-message.ts +59 -27
  924. package/src/providers/ratelimit.ts +25 -8
  925. package/src/providers/registry.ts +86 -38
  926. package/src/providers/retry.ts +84 -36
  927. package/src/providers/stream-timeout.ts +5 -3
  928. package/src/providers/types.ts +7 -6
  929. package/src/runtime/AGENTS.md +42 -0
  930. package/src/runtime/access-request-helper.ts +118 -68
  931. package/src/runtime/actor-refresh-token-store.ts +21 -16
  932. package/src/runtime/actor-token-store.ts +25 -18
  933. package/src/runtime/actor-trust-resolver.ts +183 -80
  934. package/src/runtime/approval-conversation-turn.ts +39 -26
  935. package/src/runtime/approval-message-composer.ts +116 -84
  936. package/src/runtime/assistant-event-hub.ts +25 -6
  937. package/src/runtime/assistant-event.ts +4 -4
  938. package/src/runtime/assistant-scope.ts +1 -1
  939. package/src/runtime/auth/__tests__/guard-tests.test.ts +36 -14
  940. package/src/runtime/auth/context.ts +8 -7
  941. package/src/runtime/auth/credential-service.ts +60 -38
  942. package/src/runtime/auth/external-assistant-id.ts +16 -8
  943. package/src/runtime/auth/index.ts +23 -16
  944. package/src/runtime/auth/route-policy.ts +170 -104
  945. package/src/runtime/auth/scopes.ts +22 -29
  946. package/src/runtime/auth/subject.ts +19 -13
  947. package/src/runtime/auth/token-service.ts +3 -3
  948. package/src/runtime/auth/types.ts +23 -23
  949. package/src/runtime/channel-approval-parser.ts +37 -14
  950. package/src/runtime/channel-approval-types.ts +12 -4
  951. package/src/runtime/channel-approvals.ts +41 -23
  952. package/src/runtime/channel-guardian-service.ts +144 -103
  953. package/src/runtime/channel-invite-transport.ts +4 -2
  954. package/src/runtime/channel-invite-transports/telegram.ts +16 -10
  955. package/src/runtime/channel-invite-transports/voice.ts +7 -7
  956. package/src/runtime/channel-readiness-service.ts +139 -90
  957. package/src/runtime/channel-readiness-types.ts +4 -2
  958. package/src/runtime/channel-reply-delivery.ts +21 -11
  959. package/src/runtime/channel-retry-sweep.ts +111 -62
  960. package/src/runtime/confirmation-request-guardian-bridge.ts +73 -54
  961. package/src/runtime/gateway-client.ts +86 -53
  962. package/src/runtime/guardian-action-conversation-turn.ts +34 -18
  963. package/src/runtime/guardian-action-followup-executor.ts +115 -45
  964. package/src/runtime/guardian-action-grant-minter.ts +40 -24
  965. package/src/runtime/guardian-action-message-composer.ts +105 -84
  966. package/src/runtime/guardian-decision-types.ts +28 -13
  967. package/src/runtime/guardian-outbound-actions.ts +9 -0
  968. package/src/runtime/guardian-reply-router.ts +274 -145
  969. package/src/runtime/guardian-vellum-migration.ts +38 -24
  970. package/src/runtime/guardian-verification-templates.ts +8 -11
  971. package/src/runtime/http-router.ts +175 -0
  972. package/src/runtime/http-server.ts +931 -669
  973. package/src/runtime/http-types.ts +2 -2
  974. package/src/runtime/ingress-service.ts +182 -89
  975. package/src/runtime/invite-redemption-service.ts +211 -134
  976. package/src/runtime/invite-redemption-templates.ts +18 -11
  977. package/src/runtime/local-actor-identity.ts +73 -55
  978. package/src/runtime/middleware/auth.ts +25 -14
  979. package/src/runtime/middleware/error-handler.ts +15 -11
  980. package/src/runtime/middleware/rate-limiter.ts +23 -17
  981. package/src/runtime/middleware/request-logger.ts +4 -4
  982. package/src/runtime/middleware/twilio-validation.ts +29 -20
  983. package/src/runtime/migrations/migration-transport.ts +575 -0
  984. package/src/runtime/migrations/migration-wizard.ts +715 -0
  985. package/src/runtime/migrations/rebind-secrets-screen.ts +351 -0
  986. package/src/runtime/migrations/transfer-progress-screen.ts +321 -0
  987. package/src/runtime/migrations/validation-results-screen.ts +467 -0
  988. package/src/runtime/migrations/vbundle-builder.ts +295 -0
  989. package/src/runtime/migrations/vbundle-import-analyzer.ts +212 -0
  990. package/src/runtime/migrations/vbundle-importer.ts +339 -0
  991. package/src/runtime/migrations/vbundle-validator.ts +356 -0
  992. package/src/runtime/pending-interactions.ts +16 -7
  993. package/src/runtime/routes/access-request-decision.ts +73 -52
  994. package/src/runtime/routes/app-routes.ts +56 -38
  995. package/src/runtime/routes/approval-routes.ts +165 -74
  996. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +930 -0
  997. package/src/runtime/routes/approval-strategies/guardian-legacy-fallback-strategy.ts +82 -0
  998. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +151 -0
  999. package/src/runtime/routes/attachment-routes.ts +59 -48
  1000. package/src/runtime/routes/brain-graph-routes.ts +85 -69
  1001. package/src/runtime/routes/call-routes.ts +79 -38
  1002. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +10 -10
  1003. package/src/runtime/routes/channel-delivery-routes.ts +19 -14
  1004. package/src/runtime/routes/channel-guardian-routes.ts +3 -3
  1005. package/src/runtime/routes/channel-inbound-routes.ts +2 -2
  1006. package/src/runtime/routes/channel-readiness-routes.ts +12 -6
  1007. package/src/runtime/routes/channel-route-shared.ts +33 -25
  1008. package/src/runtime/routes/channel-routes.ts +4 -6
  1009. package/src/runtime/routes/contact-routes.ts +205 -16
  1010. package/src/runtime/routes/conversation-attention-routes.ts +57 -28
  1011. package/src/runtime/routes/conversation-routes.ts +321 -174
  1012. package/src/runtime/routes/debug-routes.ts +14 -10
  1013. package/src/runtime/routes/events-routes.ts +90 -57
  1014. package/src/runtime/routes/global-search-routes.ts +266 -0
  1015. package/src/runtime/routes/guardian-action-routes.ts +147 -56
  1016. package/src/runtime/routes/guardian-approval-interception.ts +255 -880
  1017. package/src/runtime/routes/guardian-approval-prompt.ts +40 -24
  1018. package/src/runtime/routes/guardian-approval-reply-helpers.ts +135 -0
  1019. package/src/runtime/routes/guardian-bootstrap-routes.ts +55 -36
  1020. package/src/runtime/routes/guardian-expiry-sweep.ts +63 -37
  1021. package/src/runtime/routes/guardian-refresh-routes.ts +40 -19
  1022. package/src/runtime/routes/identity-routes.ts +71 -42
  1023. package/src/runtime/routes/inbound-conversation.ts +17 -11
  1024. package/src/runtime/routes/inbound-message-handler.ts +278 -1460
  1025. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +658 -0
  1026. package/src/runtime/routes/inbound-stages/background-dispatch.ts +492 -0
  1027. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +214 -0
  1028. package/src/runtime/routes/inbound-stages/edit-intercept.ts +116 -0
  1029. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +167 -0
  1030. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +185 -0
  1031. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +132 -0
  1032. package/src/runtime/routes/inbound-stages/verification-intercept.ts +340 -0
  1033. package/src/runtime/routes/ingress-routes.ts +34 -23
  1034. package/src/runtime/routes/integration-routes.ts +60 -21
  1035. package/src/runtime/routes/migration-routes.ts +434 -0
  1036. package/src/runtime/routes/pairing-routes.ts +157 -79
  1037. package/src/runtime/routes/secret-routes.ts +6 -2
  1038. package/src/runtime/routes/twilio-routes.ts +443 -249
  1039. package/src/runtime/tool-grant-request-helper.ts +36 -27
  1040. package/src/runtime/{guardian-context-resolver.ts → trust-context-resolver.ts} +29 -41
  1041. package/src/schedule/integration-status.ts +44 -9
  1042. package/src/schedule/recurrence-engine.ts +47 -24
  1043. package/src/schedule/recurrence-types.ts +12 -7
  1044. package/src/schedule/schedule-store.ts +166 -83
  1045. package/src/schedule/scheduler.ts +26 -22
  1046. package/src/security/encrypted-store.ts +68 -38
  1047. package/src/security/keychain.ts +183 -120
  1048. package/src/security/oauth-callback-registry.ts +3 -3
  1049. package/src/security/oauth2.ts +226 -138
  1050. package/src/security/redaction.ts +24 -24
  1051. package/src/security/secret-allowlist.ts +46 -21
  1052. package/src/security/secret-ingress.ts +15 -7
  1053. package/src/security/secret-scanner.ts +193 -104
  1054. package/src/security/secure-keys.ts +9 -3
  1055. package/src/security/token-manager.ts +99 -40
  1056. package/src/security/tool-approval-digest.ts +3 -3
  1057. package/src/sequence/analytics.ts +52 -27
  1058. package/src/sequence/engine.ts +135 -72
  1059. package/src/sequence/guardrails.ts +32 -20
  1060. package/src/sequence/importer.ts +75 -37
  1061. package/src/sequence/reply-matcher.ts +36 -18
  1062. package/src/sequence/store.ts +137 -75
  1063. package/src/sequence/types.ts +30 -16
  1064. package/src/services/published-app-updater.ts +26 -16
  1065. package/src/services/vercel-deploy.ts +19 -15
  1066. package/src/skills/active-skill-tools.ts +3 -3
  1067. package/src/skills/clawhub.ts +178 -90
  1068. package/src/skills/include-graph.ts +24 -17
  1069. package/src/skills/managed-store.ts +89 -42
  1070. package/src/skills/path-classifier.ts +10 -10
  1071. package/src/skills/remote-skill-policy.ts +31 -22
  1072. package/src/skills/slash-commands.ts +36 -30
  1073. package/src/skills/tool-manifest.ts +60 -31
  1074. package/src/skills/version-hash.ts +25 -15
  1075. package/src/slack/slack-webhook.ts +19 -15
  1076. package/src/subagent/index.ts +4 -8
  1077. package/src/subagent/manager.ts +119 -69
  1078. package/src/subagent/types.ts +9 -12
  1079. package/src/swarm/backend-claude-code.ts +124 -45
  1080. package/src/swarm/checkpoint.ts +36 -16
  1081. package/src/swarm/graph-utils.ts +1 -3
  1082. package/src/swarm/index.ts +38 -19
  1083. package/src/swarm/limits.ts +13 -4
  1084. package/src/swarm/orchestrator.ts +108 -57
  1085. package/src/swarm/plan-validator.ts +23 -17
  1086. package/src/swarm/router-planner.ts +51 -22
  1087. package/src/swarm/router-prompts.ts +4 -1
  1088. package/src/swarm/synthesizer.ts +26 -18
  1089. package/src/swarm/types.ts +14 -4
  1090. package/src/swarm/worker-backend.ts +36 -26
  1091. package/src/swarm/worker-prompts.ts +13 -9
  1092. package/src/swarm/worker-runner.ts +40 -34
  1093. package/src/tasks/candidate-store.ts +14 -6
  1094. package/src/tasks/ephemeral-permissions.ts +9 -5
  1095. package/src/tasks/task-compiler.ts +41 -38
  1096. package/src/tasks/task-runner.ts +54 -26
  1097. package/src/tasks/task-scheduler.ts +1 -1
  1098. package/src/tasks/task-store.ts +20 -7
  1099. package/src/tasks/tool-sanitizer.ts +3 -3
  1100. package/src/tools/apps/definitions.ts +23 -15
  1101. package/src/tools/apps/executors.ts +118 -37
  1102. package/src/tools/apps/open-proxy.ts +5 -5
  1103. package/src/tools/apps/registry.ts +2 -2
  1104. package/src/tools/assets/materialize.ts +59 -41
  1105. package/src/tools/assets/search.ts +86 -48
  1106. package/src/tools/browser/api-map.ts +52 -36
  1107. package/src/tools/browser/auth-cache.ts +21 -18
  1108. package/src/tools/browser/auth-detector.ts +43 -28
  1109. package/src/tools/browser/auto-navigate.ts +149 -68
  1110. package/src/tools/browser/browser-execution.ts +9 -3
  1111. package/src/tools/browser/headless-browser.ts +287 -150
  1112. package/src/tools/browser/jit-auth.ts +37 -21
  1113. package/src/tools/browser/network-recorder.ts +138 -56
  1114. package/src/tools/browser/recording-store.ts +22 -15
  1115. package/src/tools/browser/runtime-check.ts +8 -5
  1116. package/src/tools/browser/x-auto-navigate.ts +88 -47
  1117. package/src/tools/calls/call-end.ts +9 -6
  1118. package/src/tools/calls/call-start.ts +30 -20
  1119. package/src/tools/calls/call-status.ts +8 -5
  1120. package/src/tools/claude-code/claude-code.ts +301 -165
  1121. package/src/tools/computer-use/definitions.ts +159 -130
  1122. package/src/tools/computer-use/registry.ts +2 -2
  1123. package/src/tools/computer-use/request-computer-control.ts +21 -13
  1124. package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
  1125. package/src/tools/credentials/account-registry.ts +52 -35
  1126. package/src/tools/credentials/broker-types.ts +1 -1
  1127. package/src/tools/credentials/broker.ts +97 -55
  1128. package/src/tools/credentials/domain-policy.ts +5 -2
  1129. package/src/tools/credentials/host-pattern-match.ts +15 -8
  1130. package/src/tools/credentials/metadata-store.ts +93 -43
  1131. package/src/tools/credentials/policy-types.ts +5 -2
  1132. package/src/tools/credentials/policy-validate.ts +21 -14
  1133. package/src/tools/credentials/post-connect-hooks.ts +18 -7
  1134. package/src/tools/credentials/resolve.ts +11 -10
  1135. package/src/tools/credentials/selection.ts +30 -25
  1136. package/src/tools/credentials/tool-policy.ts +5 -2
  1137. package/src/tools/credentials/vault.ts +452 -183
  1138. package/src/tools/document/document-tool.ts +23 -17
  1139. package/src/tools/document/editor-template.ts +12 -7
  1140. package/src/tools/execution-target.ts +13 -10
  1141. package/src/tools/execution-timeout.ts +6 -5
  1142. package/src/tools/executor.ts +141 -74
  1143. package/src/tools/filesystem/edit.ts +82 -45
  1144. package/src/tools/filesystem/fuzzy-match.ts +70 -32
  1145. package/src/tools/filesystem/read.ts +46 -28
  1146. package/src/tools/filesystem/view-image.ts +86 -42
  1147. package/src/tools/filesystem/write.ts +53 -32
  1148. package/src/tools/followups/followup_create.ts +43 -17
  1149. package/src/tools/followups/followup_list.ts +28 -13
  1150. package/src/tools/followups/followup_resolve.ts +9 -6
  1151. package/src/tools/guardian-control-plane-policy.ts +15 -14
  1152. package/src/tools/host-filesystem/edit.ts +77 -42
  1153. package/src/tools/host-filesystem/read.ts +52 -33
  1154. package/src/tools/host-filesystem/write.ts +50 -29
  1155. package/src/tools/host-terminal/host-shell.ts +97 -61
  1156. package/src/tools/mcp/mcp-tool-factory.ts +21 -14
  1157. package/src/tools/memory/definitions.ts +60 -28
  1158. package/src/tools/memory/handlers.ts +149 -77
  1159. package/src/tools/memory/register.ts +39 -16
  1160. package/src/tools/network/__tests__/web-search.test.ts +236 -177
  1161. package/src/tools/network/domain-normalize.ts +13 -9
  1162. package/src/tools/network/script-proxy/__tests__/logging.test.ts +193 -123
  1163. package/src/tools/network/script-proxy/__tests__/policy.test.ts +225 -127
  1164. package/src/tools/network/script-proxy/index.ts +1 -17
  1165. package/src/tools/network/script-proxy/session-manager.ts +151 -84
  1166. package/src/tools/network/url-safety.ts +56 -34
  1167. package/src/tools/network/web-fetch.ts +273 -155
  1168. package/src/tools/network/web-search.ts +166 -81
  1169. package/src/tools/permission-checker.ts +6 -25
  1170. package/src/tools/policy-context.ts +8 -5
  1171. package/src/tools/registry.ts +73 -46
  1172. package/src/tools/reminder/reminder-store.ts +65 -44
  1173. package/src/tools/reminder/reminder.ts +76 -35
  1174. package/src/tools/schedule/create.ts +44 -21
  1175. package/src/tools/schedule/delete.ts +8 -5
  1176. package/src/tools/schedule/list.ts +39 -19
  1177. package/src/tools/schedule/update.ts +49 -26
  1178. package/src/tools/secret-detection-handler.ts +130 -49
  1179. package/src/tools/sensitive-output-placeholders.ts +15 -8
  1180. package/src/tools/shared/filesystem/edit-engine.ts +45 -14
  1181. package/src/tools/shared/filesystem/errors.ts +18 -18
  1182. package/src/tools/shared/filesystem/file-ops-service.ts +59 -32
  1183. package/src/tools/shared/filesystem/format-diff.ts +21 -11
  1184. package/src/tools/shared/filesystem/path-policy.ts +17 -13
  1185. package/src/tools/shared/filesystem/size-guard.ts +8 -4
  1186. package/src/tools/shared/filesystem/types.ts +2 -2
  1187. package/src/tools/shared/shell-output.ts +4 -3
  1188. package/src/tools/side-effects.ts +36 -28
  1189. package/src/tools/skills/delete-managed.ts +30 -17
  1190. package/src/tools/skills/load.ts +88 -46
  1191. package/src/tools/skills/sandbox-runner.ts +62 -46
  1192. package/src/tools/skills/scaffold-managed.ts +98 -48
  1193. package/src/tools/skills/script-contract.ts +5 -2
  1194. package/src/tools/skills/skill-script-runner.ts +29 -13
  1195. package/src/tools/skills/skill-tool-factory.ts +20 -10
  1196. package/src/tools/subagent/abort.ts +10 -4
  1197. package/src/tools/subagent/message.ts +14 -8
  1198. package/src/tools/subagent/read.ts +20 -11
  1199. package/src/tools/subagent/spawn.ts +14 -6
  1200. package/src/tools/subagent/status.ts +7 -4
  1201. package/src/tools/swarm/delegate.ts +75 -49
  1202. package/src/tools/system/avatar-generator.ts +46 -33
  1203. package/src/tools/system/navigate-settings.ts +29 -19
  1204. package/src/tools/system/open-system-settings.ts +30 -20
  1205. package/src/tools/system/request-permission.ts +59 -44
  1206. package/src/tools/system/version.ts +27 -16
  1207. package/src/tools/system/voice-config.ts +116 -53
  1208. package/src/tools/tasks/index.ts +8 -8
  1209. package/src/tools/tasks/task-delete.ts +61 -22
  1210. package/src/tools/tasks/task-list.ts +23 -11
  1211. package/src/tools/tasks/task-run.ts +41 -16
  1212. package/src/tools/tasks/task-save.ts +27 -10
  1213. package/src/tools/tasks/work-item-enqueue.ts +114 -48
  1214. package/src/tools/tasks/work-item-list.ts +20 -10
  1215. package/src/tools/tasks/work-item-remove.ts +49 -15
  1216. package/src/tools/tasks/work-item-run.ts +34 -13
  1217. package/src/tools/tasks/work-item-update.ts +84 -31
  1218. package/src/tools/terminal/backends/native.ts +64 -35
  1219. package/src/tools/terminal/backends/types.ts +6 -2
  1220. package/src/tools/terminal/parser.ts +200 -125
  1221. package/src/tools/terminal/safe-env.ts +27 -21
  1222. package/src/tools/terminal/sandbox-diagnostics.ts +31 -13
  1223. package/src/tools/terminal/sandbox.ts +10 -6
  1224. package/src/tools/terminal/shell.ts +124 -68
  1225. package/src/tools/tool-approval-handler.ts +193 -138
  1226. package/src/tools/types.ts +43 -23
  1227. package/src/tools/ui-surface/definitions.ts +124 -89
  1228. package/src/tools/ui-surface/registry.ts +2 -2
  1229. package/src/tools/watch/screen-watch.ts +50 -32
  1230. package/src/tools/watch/watch-state.ts +41 -15
  1231. package/src/tools/watcher/create.ts +37 -15
  1232. package/src/tools/watcher/delete.ts +9 -6
  1233. package/src/tools/watcher/digest.ts +10 -6
  1234. package/src/tools/watcher/list.ts +37 -14
  1235. package/src/tools/watcher/update.ts +33 -18
  1236. package/src/tools/weather/service.ts +331 -174
  1237. package/src/twitter/client.ts +261 -138
  1238. package/src/twitter/oauth-client.ts +17 -13
  1239. package/src/twitter/router.ts +51 -23
  1240. package/src/twitter/session.ts +27 -18
  1241. package/src/types/qrcode.d.ts +6 -3
  1242. package/src/usage/actors.ts +16 -16
  1243. package/src/usage/types.ts +3 -3
  1244. package/src/util/bundled-asset.ts +10 -6
  1245. package/src/util/canonicalize-identity.ts +11 -4
  1246. package/src/util/clipboard.ts +7 -7
  1247. package/src/util/content-id.ts +3 -3
  1248. package/src/util/debounce.ts +3 -2
  1249. package/src/util/diff.ts +55 -33
  1250. package/src/util/errors.ts +26 -26
  1251. package/src/util/fs.ts +8 -2
  1252. package/src/util/log-redact.ts +12 -12
  1253. package/src/util/logger.ts +112 -51
  1254. package/src/util/network-info.ts +13 -5
  1255. package/src/util/object.ts +4 -2
  1256. package/src/util/phone.ts +4 -4
  1257. package/src/util/platform.ts +80 -58
  1258. package/src/util/pricing.ts +49 -31
  1259. package/src/util/retry.ts +18 -7
  1260. package/src/util/row-mapper.ts +7 -4
  1261. package/src/util/silently.ts +7 -4
  1262. package/src/util/spawn.ts +48 -0
  1263. package/src/util/spinner.ts +9 -7
  1264. package/src/util/time.ts +16 -3
  1265. package/src/util/truncate.ts +1 -1
  1266. package/src/util/voice-code.ts +6 -4
  1267. package/src/util/xml.ts +5 -1
  1268. package/src/version.ts +12 -8
  1269. package/src/watcher/engine.ts +71 -44
  1270. package/src/watcher/provider-registry.ts +1 -1
  1271. package/src/watcher/providers/github.ts +40 -23
  1272. package/src/watcher/providers/gmail.ts +59 -38
  1273. package/src/watcher/providers/google-calendar.ts +62 -48
  1274. package/src/watcher/providers/linear.ts +219 -150
  1275. package/src/watcher/providers/slack.ts +93 -27
  1276. package/src/watcher/watcher-store.ts +75 -55
  1277. package/src/work-items/work-item-runner.ts +62 -29
  1278. package/src/work-items/work-item-store.ts +137 -47
  1279. package/src/workspace/commit-message-enrichment-service.ts +65 -25
  1280. package/src/workspace/commit-message-provider.ts +14 -12
  1281. package/src/workspace/git-service.ts +355 -239
  1282. package/src/workspace/heartbeat-service.ts +74 -37
  1283. package/src/workspace/provider-commit-message-generator.ts +95 -70
  1284. package/src/workspace/top-level-renderer.ts +10 -8
  1285. package/src/workspace/top-level-scanner.ts +9 -3
  1286. package/src/workspace/turn-commit.ts +63 -36
  1287. package/src/__tests__/ingress-member-store.test.ts +0 -294
  1288. package/src/__tests__/script-proxy-router.test.ts +0 -215
  1289. package/src/config/bundled-skills/trusted-contacts/SKILL.md +0 -372
  1290. package/src/memory/guardian-bindings.ts +0 -158
  1291. package/src/memory/ingress-member-store.ts +0 -352
  1292. package/src/tools/network/script-proxy/__tests__/router.test.ts +0 -77
  1293. package/src/tools/network/script-proxy/certs.ts +0 -7
  1294. package/src/tools/network/script-proxy/connect-tunnel.ts +0 -1
  1295. package/src/tools/network/script-proxy/http-forwarder.ts +0 -2
  1296. package/src/tools/network/script-proxy/logging.ts +0 -12
  1297. package/src/tools/network/script-proxy/mitm-handler.ts +0 -2
  1298. package/src/tools/network/script-proxy/policy.ts +0 -4
  1299. package/src/tools/network/script-proxy/router.ts +0 -2
  1300. package/src/tools/network/script-proxy/server.ts +0 -5
  1301. package/src/tools/network/script-proxy/types.ts +0 -19
@@ -4,94 +4,46 @@
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';
33
- 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';
8
+ CHANNEL_IDS,
9
+ INTERFACE_IDS,
10
+ isChannelId,
11
+ parseInterfaceId,
12
+ } from "../../channels/types.js";
13
+ import type { TrustContext } from "../../daemon/session-runtime-assembly.js";
14
+ import * as attachmentsStore from "../../memory/attachments-store.js";
15
+ import * as channelDeliveryStore from "../../memory/channel-delivery-store.js";
16
+ import { recordConversationSeenSignal } from "../../memory/conversation-attention-store.js";
17
+ import * as externalConversationStore from "../../memory/external-conversation-store.js";
18
+ import { canonicalizeInboundIdentity } from "../../util/canonicalize-identity.js";
19
+ import { getLogger } from "../../util/logger.js";
20
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
21
+ import { mintDaemonDeliveryToken } from "../auth/token-service.js";
22
+ import { deliverChannelReply } from "../gateway-client.js";
23
+ import { httpError } from "../http-errors.js";
59
24
  import type {
60
25
  ApprovalConversationGenerator,
61
26
  ApprovalCopyGenerator,
62
27
  GuardianActionCopyGenerator,
63
28
  GuardianFollowUpConversationGenerator,
64
29
  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
- }
30
+ } from "../http-types.js";
31
+ import { resolveTrustContext } from "../trust-context-resolver.js";
32
+ import { canonicalChannelAssistantId } from "./channel-route-shared.js";
33
+ import { handleApprovalInterception } from "./guardian-approval-interception.js";
34
+ import { enforceIngressAcl } from "./inbound-stages/acl-enforcement.js";
35
+ import { processChannelMessageInBackground } from "./inbound-stages/background-dispatch.js";
36
+ import { handleBootstrapIntercept } from "./inbound-stages/bootstrap-intercept.js";
37
+ import { handleEditIntercept } from "./inbound-stages/edit-intercept.js";
38
+ import { handleEscalationIntercept } from "./inbound-stages/escalation-intercept.js";
39
+ import { handleGuardianReplyIntercept } from "./inbound-stages/guardian-reply-intercept.js";
40
+ import { runSecretIngressCheck } from "./inbound-stages/secret-ingress-check.js";
41
+ import { handleVerificationIntercept } from "./inbound-stages/verification-intercept.js";
42
+
43
+ import "../channel-invite-transports/telegram.js";
44
+ import "../channel-invite-transports/voice.js";
45
+
46
+ const log = getLogger("runtime-http");
95
47
 
96
48
  export async function handleChannelInbound(
97
49
  req: Request,
@@ -113,7 +65,7 @@ export async function handleChannelInbound(
113
65
  // a single token from request start.
114
66
  const mintBearerToken = (): string => mintDaemonDeliveryToken();
115
67
 
116
- const body = await req.json() as {
68
+ const body = (await req.json()) as {
117
69
  sourceChannel?: string;
118
70
  interface?: string;
119
71
  conversationExternalId?: string;
@@ -139,63 +91,92 @@ export async function handleChannelInbound(
139
91
  sourceMetadata,
140
92
  } = body;
141
93
 
142
- if (!body.sourceChannel || typeof body.sourceChannel !== 'string') {
143
- return httpError('BAD_REQUEST', 'sourceChannel is required', 400);
94
+ if (!body.sourceChannel || typeof body.sourceChannel !== "string") {
95
+ return httpError("BAD_REQUEST", "sourceChannel is required", 400);
144
96
  }
145
97
  // Validate and narrow to canonical ChannelId at the boundary — the gateway
146
98
  // only sends well-known channel strings, so an unknown value is rejected.
147
99
  if (!isChannelId(body.sourceChannel)) {
148
- return httpError('BAD_REQUEST', `Invalid sourceChannel: ${body.sourceChannel}. Valid values: ${CHANNEL_IDS.join(', ')}`, 400);
100
+ return httpError(
101
+ "BAD_REQUEST",
102
+ `Invalid sourceChannel: ${
103
+ body.sourceChannel
104
+ }. Valid values: ${CHANNEL_IDS.join(", ")}`,
105
+ 400,
106
+ );
149
107
  }
150
108
 
151
109
  const sourceChannel = body.sourceChannel;
152
110
 
153
- if (!body.interface || typeof body.interface !== 'string') {
154
- return httpError('BAD_REQUEST', 'interface is required', 400);
111
+ if (!body.interface || typeof body.interface !== "string") {
112
+ return httpError("BAD_REQUEST", "interface is required", 400);
155
113
  }
156
114
  const sourceInterface = parseInterfaceId(body.interface);
157
115
  if (!sourceInterface) {
158
- return httpError('BAD_REQUEST', `Invalid interface: ${body.interface}. Valid values: ${INTERFACE_IDS.join(', ')}`, 400);
116
+ return httpError(
117
+ "BAD_REQUEST",
118
+ `Invalid interface: ${body.interface}. Valid values: ${INTERFACE_IDS.join(
119
+ ", ",
120
+ )}`,
121
+ 400,
122
+ );
159
123
  }
160
124
 
161
- if (!conversationExternalId || typeof conversationExternalId !== 'string') {
162
- return httpError('BAD_REQUEST', 'conversationExternalId is required', 400);
125
+ if (!conversationExternalId || typeof conversationExternalId !== "string") {
126
+ return httpError("BAD_REQUEST", "conversationExternalId is required", 400);
163
127
  }
164
- if (!body.actorExternalId || typeof body.actorExternalId !== 'string' || !body.actorExternalId.trim()) {
165
- return httpError('BAD_REQUEST', 'actorExternalId is required', 400);
128
+ if (
129
+ !body.actorExternalId ||
130
+ typeof body.actorExternalId !== "string" ||
131
+ !body.actorExternalId.trim()
132
+ ) {
133
+ return httpError("BAD_REQUEST", "actorExternalId is required", 400);
166
134
  }
167
- if (!externalMessageId || typeof externalMessageId !== 'string') {
168
- return httpError('BAD_REQUEST', 'externalMessageId is required', 400);
135
+ if (!externalMessageId || typeof externalMessageId !== "string") {
136
+ return httpError("BAD_REQUEST", "externalMessageId is required", 400);
169
137
  }
170
138
 
171
139
  // 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);
140
+ if (content != null && typeof content !== "string") {
141
+ return httpError("BAD_REQUEST", "content must be a string", 400);
174
142
  }
175
143
 
176
- const trimmedContent = typeof content === 'string' ? content.trim() : '';
177
- const hasAttachments = Array.isArray(attachmentIds) && attachmentIds.length > 0;
144
+ const trimmedContent = typeof content === "string" ? content.trim() : "";
145
+ const hasAttachments =
146
+ Array.isArray(attachmentIds) && attachmentIds.length > 0;
178
147
 
179
- const hasCallbackData = typeof body.callbackData === 'string' && body.callbackData.length > 0;
148
+ const hasCallbackData =
149
+ typeof body.callbackData === "string" && body.callbackData.length > 0;
180
150
 
181
- if (trimmedContent.length === 0 && !hasAttachments && !isEdit && !hasCallbackData) {
182
- return httpError('BAD_REQUEST', 'content or attachmentIds is required', 400);
151
+ if (
152
+ trimmedContent.length === 0 &&
153
+ !hasAttachments &&
154
+ !isEdit &&
155
+ !hasCallbackData
156
+ ) {
157
+ return httpError(
158
+ "BAD_REQUEST",
159
+ "content or attachmentIds is required",
160
+ 400,
161
+ );
183
162
  }
184
163
 
185
164
  // Canonicalize the assistant ID so all DB-facing operations use the
186
165
  // consistent 'self' key regardless of what the gateway sent.
187
166
  const canonicalAssistantId = canonicalChannelAssistantId(assistantId);
188
167
  if (canonicalAssistantId !== assistantId) {
189
- log.debug({ raw: assistantId, canonical: canonicalAssistantId }, 'Canonicalized channel assistant ID');
168
+ log.debug(
169
+ { raw: assistantId, canonical: canonicalAssistantId },
170
+ "Canonicalized channel assistant ID",
171
+ );
190
172
  }
191
173
 
192
174
  // Coerce actorExternalId to a string at the boundary — the field
193
175
  // comes from unvalidated JSON and may be a number, object, or other
194
176
  // non-string type. Non-string truthy values would throw inside
195
177
  // canonicalizeInboundIdentity when it calls .trim().
196
- const rawSenderId = body.actorExternalId != null
197
- ? String(body.actorExternalId)
198
- : undefined;
178
+ const rawSenderId =
179
+ body.actorExternalId != null ? String(body.actorExternalId) : undefined;
199
180
 
200
181
  // Canonicalize the sender identity so all trust lookups, member matching,
201
182
  // and guardian binding comparisons use a normalized form. Phone-like
@@ -212,255 +193,24 @@ export async function handleChannelInbound(
212
193
  const hasSenderIdentityClaim = rawSenderId !== undefined;
213
194
 
214
195
  // ── 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,
196
+ const aclResult = await enforceIngressAcl({
197
+ canonicalSenderId,
198
+ hasSenderIdentityClaim,
199
+ rawSenderId,
200
+ sourceChannel,
201
+ conversationExternalId,
202
+ canonicalAssistantId,
203
+ trimmedContent,
244
204
  sourceMetadata: body.sourceMetadata,
205
+ actorDisplayName: body.actorDisplayName,
206
+ actorUsername: body.actorUsername,
207
+ replyCallbackUrl: body.replyCallbackUrl,
208
+ mintBearerToken,
209
+ assistantId,
210
+ externalMessageId,
245
211
  });
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
- }
212
+ if (aclResult.earlyResponse) return aclResult.earlyResponse;
213
+ const { resolvedMember, guardianVerifyCode } = aclResult;
464
214
 
465
215
  if (hasAttachments) {
466
216
  const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds);
@@ -468,78 +218,35 @@ export async function handleChannelInbound(
468
218
  const resolvedIds = new Set(resolved.map((a) => a.id));
469
219
  const missing = attachmentIds.filter((id) => !resolvedIds.has(id));
470
220
  return Response.json(
471
- { error: `Attachment IDs not found: ${missing.join(', ')}` },
221
+ { error: `Attachment IDs not found: ${missing.join(", ")}` },
472
222
  { status: 400 },
473
223
  );
474
224
  }
475
225
  }
476
226
 
477
- const sourceMessageId = typeof sourceMetadata?.messageId === 'string'
478
- ? sourceMetadata.messageId
479
- : undefined;
227
+ const sourceMessageId =
228
+ typeof sourceMetadata?.messageId === "string"
229
+ ? sourceMetadata.messageId
230
+ : undefined;
480
231
 
481
232
  if (isEdit && !sourceMessageId) {
482
- return httpError('BAD_REQUEST', 'sourceMetadata.messageId is required for edits', 400);
233
+ return httpError(
234
+ "BAD_REQUEST",
235
+ "sourceMetadata.messageId is required for edits",
236
+ 400,
237
+ );
483
238
  }
484
239
 
485
240
  // ── Edit path: update existing message content, no new agent loop ──
486
241
  if (isEdit && sourceMessageId) {
487
- // Dedup the edit event itself (retried edited_message webhooks)
488
- const editResult = channelDeliveryStore.recordInbound(
242
+ return handleEditIntercept({
489
243
  sourceChannel,
490
244
  conversationExternalId,
491
245
  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,
246
+ sourceMessageId,
247
+ canonicalAssistantId,
248
+ assistantId,
249
+ content,
543
250
  });
544
251
  }
545
252
 
@@ -558,18 +265,30 @@ export async function handleChannelInbound(
558
265
  // gateway retries (duplicates) re-attempt delivery here. On success the
559
266
  // pending marker is cleared so further duplicates short-circuit normally.
560
267
  if (result.duplicate && replyCallbackUrl) {
561
- const pendingReply = channelDeliveryStore.getPendingVerificationReply(result.eventId);
268
+ const pendingReply = channelDeliveryStore.getPendingVerificationReply(
269
+ result.eventId,
270
+ );
562
271
  if (pendingReply) {
563
272
  try {
564
- await deliverChannelReply(replyCallbackUrl, {
565
- chatId: pendingReply.chatId,
566
- text: pendingReply.text,
567
- assistantId: pendingReply.assistantId,
568
- }, mintBearerToken());
273
+ await deliverChannelReply(
274
+ replyCallbackUrl,
275
+ {
276
+ chatId: pendingReply.chatId,
277
+ text: pendingReply.text,
278
+ assistantId: pendingReply.assistantId,
279
+ },
280
+ mintBearerToken(),
281
+ );
569
282
  channelDeliveryStore.clearPendingVerificationReply(result.eventId);
570
- log.info({ eventId: result.eventId }, 'Retried pending verification reply: delivered');
283
+ log.info(
284
+ { eventId: result.eventId },
285
+ "Retried pending verification reply: delivered",
286
+ );
571
287
  } catch (retryErr) {
572
- log.error({ err: retryErr, eventId: result.eventId }, 'Retry of pending verification reply failed; will retry on next duplicate');
288
+ log.error(
289
+ { err: retryErr, eventId: result.eventId },
290
+ "Retry of pending verification reply failed; will retry on next duplicate",
291
+ );
573
292
  }
574
293
  return Response.json({
575
294
  accepted: true,
@@ -594,322 +313,86 @@ export async function handleChannelInbound(
594
313
  }
595
314
 
596
315
  // ── 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
- }
607
-
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
- });
619
-
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
- );
638
- }
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
- }
316
+ const escalationResponse = handleEscalationIntercept({
317
+ resolvedMember,
318
+ canonicalAssistantId,
319
+ sourceChannel,
320
+ sourceInterface,
321
+ conversationExternalId,
322
+ externalMessageId,
323
+ conversationId: result.conversationId,
324
+ eventId: result.eventId,
325
+ content,
326
+ attachmentIds,
327
+ sourceMetadata: body.sourceMetadata,
328
+ actorDisplayName: body.actorDisplayName,
329
+ actorExternalId: body.actorExternalId,
330
+ actorUsername: body.actorUsername,
331
+ replyCallbackUrl: body.replyCallbackUrl,
332
+ canonicalSenderId,
333
+ rawSenderId,
334
+ });
335
+ if (escalationResponse) return escalationResponse;
673
336
 
674
337
  const metadataHintsRaw = sourceMetadata?.hints;
675
338
  const metadataHints = Array.isArray(metadataHintsRaw)
676
- ? metadataHintsRaw.filter((hint): hint is string => typeof hint === 'string' && hint.trim().length > 0)
339
+ ? metadataHintsRaw.filter(
340
+ (hint): hint is string =>
341
+ typeof hint === "string" && hint.trim().length > 0,
342
+ )
677
343
  : [];
678
- const metadataUxBrief = typeof sourceMetadata?.uxBrief === 'string' && sourceMetadata.uxBrief.trim().length > 0
679
- ? sourceMetadata.uxBrief.trim()
680
- : undefined;
344
+ const metadataUxBrief =
345
+ typeof sourceMetadata?.uxBrief === "string" &&
346
+ sourceMetadata.uxBrief.trim().length > 0
347
+ ? sourceMetadata.uxBrief.trim()
348
+ : undefined;
681
349
 
682
350
  // Extract channel command intent (e.g. /start from Telegram)
683
351
  const rawCommandIntent = sourceMetadata?.commandIntent;
684
- const commandIntent = rawCommandIntent && typeof rawCommandIntent === 'object' && !Array.isArray(rawCommandIntent)
685
- ? rawCommandIntent as Record<string, unknown>
686
- : undefined;
352
+ const commandIntent =
353
+ rawCommandIntent &&
354
+ typeof rawCommandIntent === "object" &&
355
+ !Array.isArray(rawCommandIntent)
356
+ ? (rawCommandIntent as Record<string, unknown>)
357
+ : undefined;
687
358
 
688
359
  // 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;
360
+ const sourceLanguageCode =
361
+ typeof sourceMetadata?.languageCode === "string" &&
362
+ sourceMetadata.languageCode.trim().length > 0
363
+ ? sourceMetadata.languageCode.trim()
364
+ : undefined;
692
365
 
693
366
  // ── 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
- }
367
+ const bootstrapResponse = await handleBootstrapIntercept({
368
+ isDuplicate: result.duplicate,
369
+ commandIntent,
370
+ rawSenderId,
371
+ canonicalAssistantId,
372
+ sourceChannel,
373
+ conversationExternalId,
374
+ eventId: result.eventId,
375
+ });
376
+ if (bootstrapResponse) return bootstrapResponse;
752
377
 
753
378
  // ── 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
- }
379
+ const verificationResponse = await handleVerificationIntercept({
380
+ isDuplicate: result.duplicate,
381
+ guardianVerifyCode,
382
+ rawSenderId,
383
+ canonicalSenderId,
384
+ canonicalAssistantId,
385
+ sourceChannel,
386
+ conversationExternalId,
387
+ conversationId: result.conversationId,
388
+ eventId: result.eventId,
389
+ replyCallbackUrl,
390
+ mintBearerToken,
391
+ assistantId,
392
+ actorDisplayName: body.actorDisplayName,
393
+ actorUsername: body.actorUsername,
394
+ });
395
+ if (verificationResponse) return verificationResponse;
913
396
 
914
397
  // Legacy voice guardian action interception removed — all guardian reply
915
398
  // routing now flows through the canonical router below (routeGuardianReply),
@@ -919,7 +402,7 @@ export async function handleChannelInbound(
919
402
  // ── Actor role resolution ──
920
403
  // Uses shared channel-agnostic resolution so all ingress paths classify
921
404
  // guardian vs non-guardian actors the same way.
922
- const guardianCtx: GuardianContext = resolveGuardianContext({
405
+ const trustCtx: TrustContext = resolveTrustContext({
923
406
  assistantId: canonicalAssistantId,
924
407
  sourceChannel,
925
408
  conversationExternalId,
@@ -928,99 +411,26 @@ export async function handleChannelInbound(
928
411
  actorDisplayName: body.actorDisplayName,
929
412
  });
930
413
 
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
414
  // ── 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
- }
415
+ const guardianReplyResult = await handleGuardianReplyIntercept({
416
+ isDuplicate: result.duplicate,
417
+ trimmedContent,
418
+ hasCallbackData,
419
+ callbackData: body.callbackData,
420
+ rawSenderId,
421
+ canonicalSenderId,
422
+ canonicalAssistantId,
423
+ sourceChannel,
424
+ conversationExternalId,
425
+ conversationId: result.conversationId,
426
+ eventId: result.eventId,
427
+ replyCallbackUrl,
428
+ mintBearerToken,
429
+ trustClass: trustCtx.trustClass,
430
+ guardianPrincipalId: trustCtx.guardianPrincipalId,
431
+ approvalConversationGenerator,
432
+ });
433
+ if (guardianReplyResult.response) return guardianReplyResult.response;
1024
434
 
1025
435
  // ── Approval interception ──
1026
436
  // Keep this active whenever callback context is available.
@@ -1030,7 +440,7 @@ export async function handleChannelInbound(
1030
440
  if (
1031
441
  replyCallbackUrl &&
1032
442
  !result.duplicate &&
1033
- !skipApprovalInterception
443
+ !guardianReplyResult.skipApprovalInterception
1034
444
  ) {
1035
445
  const approvalResult = await handleApprovalInterception({
1036
446
  conversationId: result.conversationId,
@@ -1041,7 +451,7 @@ export async function handleChannelInbound(
1041
451
  actorExternalId: canonicalSenderId ?? rawSenderId,
1042
452
  replyCallbackUrl,
1043
453
  bearerToken: mintBearerToken(),
1044
- guardianCtx,
454
+ trustCtx,
1045
455
  assistantId: canonicalAssistantId,
1046
456
  approvalCopyGenerator,
1047
457
  approvalConversationGenerator,
@@ -1049,37 +459,42 @@ export async function handleChannelInbound(
1049
459
 
1050
460
  if (approvalResult.handled) {
1051
461
  // Record inferred seen signal for all handled Telegram approval interactions
1052
- if (sourceChannel === 'telegram') {
462
+ if (sourceChannel === "telegram") {
1053
463
  try {
1054
464
  if (hasCallbackData) {
1055
- const cbPreview = body.callbackData!.length > 80
1056
- ? body.callbackData!.slice(0, 80) + '...'
1057
- : body.callbackData!;
465
+ const cbPreview =
466
+ body.callbackData!.length > 80
467
+ ? body.callbackData!.slice(0, 80) + "..."
468
+ : body.callbackData!;
1058
469
  recordConversationSeenSignal({
1059
470
  conversationId: result.conversationId,
1060
471
  assistantId: canonicalAssistantId,
1061
- signalType: 'telegram_callback',
1062
- confidence: 'inferred',
1063
- sourceChannel: 'telegram',
1064
- source: 'inbound-message-handler',
472
+ signalType: "telegram_callback",
473
+ confidence: "inferred",
474
+ sourceChannel: "telegram",
475
+ source: "inbound-message-handler",
1065
476
  evidenceText: `User tapped callback: '${cbPreview}'`,
1066
477
  });
1067
478
  } else {
1068
- const msgPreview = trimmedContent.length > 80
1069
- ? trimmedContent.slice(0, 80) + '...'
1070
- : trimmedContent;
479
+ const msgPreview =
480
+ trimmedContent.length > 80
481
+ ? trimmedContent.slice(0, 80) + "..."
482
+ : trimmedContent;
1071
483
  recordConversationSeenSignal({
1072
484
  conversationId: result.conversationId,
1073
485
  assistantId: canonicalAssistantId,
1074
- signalType: 'telegram_inbound_message',
1075
- confidence: 'inferred',
1076
- sourceChannel: 'telegram',
1077
- source: 'inbound-message-handler',
486
+ signalType: "telegram_inbound_message",
487
+ confidence: "inferred",
488
+ sourceChannel: "telegram",
489
+ source: "inbound-message-handler",
1078
490
  evidenceText: `User sent plain-text approval reply: '${msgPreview}'`,
1079
491
  });
1080
492
  }
1081
493
  } catch (err) {
1082
- log.warn({ err, conversationId: result.conversationId }, 'Failed to record seen signal for Telegram approval interaction');
494
+ log.warn(
495
+ { err, conversationId: result.conversationId },
496
+ "Failed to record seen signal for Telegram approval interaction",
497
+ );
1083
498
  }
1084
499
  }
1085
500
 
@@ -1098,22 +513,26 @@ export async function handleChannelInbound(
1098
513
  // so checking for empty content alone would miss stale callbacks.
1099
514
  if (hasCallbackData) {
1100
515
  // Record seen signal even for stale callbacks — the user still interacted
1101
- if (sourceChannel === 'telegram') {
516
+ if (sourceChannel === "telegram") {
1102
517
  try {
1103
- const cbPreview = body.callbackData!.length > 80
1104
- ? body.callbackData!.slice(0, 80) + '...'
1105
- : body.callbackData!;
518
+ const cbPreview =
519
+ body.callbackData!.length > 80
520
+ ? body.callbackData!.slice(0, 80) + "..."
521
+ : body.callbackData!;
1106
522
  recordConversationSeenSignal({
1107
523
  conversationId: result.conversationId,
1108
524
  assistantId: canonicalAssistantId,
1109
- signalType: 'telegram_callback',
1110
- confidence: 'inferred',
1111
- sourceChannel: 'telegram',
1112
- source: 'inbound-message-handler',
525
+ signalType: "telegram_callback",
526
+ confidence: "inferred",
527
+ sourceChannel: "telegram",
528
+ source: "inbound-message-handler",
1113
529
  evidenceText: `User tapped stale callback: '${cbPreview}'`,
1114
530
  });
1115
531
  } catch (err) {
1116
- log.warn({ err, conversationId: result.conversationId }, 'Failed to record seen signal for stale Telegram callback');
532
+ log.warn(
533
+ { err, conversationId: result.conversationId },
534
+ "Failed to record seen signal for stale Telegram callback",
535
+ );
1117
536
  }
1118
537
  }
1119
538
 
@@ -1121,7 +540,7 @@ export async function handleChannelInbound(
1121
540
  accepted: true,
1122
541
  duplicate: false,
1123
542
  eventId: result.eventId,
1124
- approval: 'stale_ignored',
543
+ approval: "stale_ignored",
1125
544
  });
1126
545
  }
1127
546
  }
@@ -1129,56 +548,24 @@ export async function handleChannelInbound(
1129
548
  // For new (non-duplicate) messages, run the secret ingress check
1130
549
  // synchronously, then fire off the agent loop in the background.
1131
550
  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,
551
+ runSecretIngressCheck({
552
+ eventId: result.eventId,
553
+ sourceChannel,
554
+ conversationExternalId,
555
+ externalMessageId,
556
+ conversationId: result.conversationId,
557
+ content,
558
+ trimmedContent,
559
+ attachmentIds,
560
+ sourceMetadata: body.sourceMetadata,
561
+ actorDisplayName: body.actorDisplayName,
562
+ actorExternalId: body.actorExternalId,
563
+ actorUsername: body.actorUsername,
564
+ trustCtx,
1142
565
  replyCallbackUrl,
1143
- assistantId: canonicalAssistantId,
566
+ canonicalAssistantId,
1144
567
  });
1145
568
 
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
569
  // Fire-and-forget: process the message and deliver the reply in the background.
1183
570
  // The HTTP response returns immediately so the gateway webhook is not blocked.
1184
571
  // The onEvent callback in processMessage registers pending interactions, and
@@ -1187,12 +574,12 @@ export async function handleChannelInbound(
1187
574
  processMessage,
1188
575
  conversationId: result.conversationId,
1189
576
  eventId: result.eventId,
1190
- content: content ?? '',
577
+ content: content ?? "",
1191
578
  attachmentIds: hasAttachments ? attachmentIds : undefined,
1192
579
  sourceChannel,
1193
580
  sourceInterface,
1194
581
  externalChatId: conversationExternalId,
1195
- guardianCtx,
582
+ trustCtx,
1196
583
  metadataHints,
1197
584
  metadataUxBrief,
1198
585
  commandIntent,
@@ -1210,572 +597,3 @@ export async function handleChannelInbound(
1210
597
  eventId: result.eventId,
1211
598
  });
1212
599
  }
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
- }