@vellumai/assistant 0.6.3 → 0.6.5

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 (1114) hide show
  1. package/.prettierignore +5 -0
  2. package/ARCHITECTURE.md +298 -39
  3. package/Dockerfile +14 -3
  4. package/README.md +3 -4
  5. package/bun.lock +13 -16
  6. package/docs/architecture/integrations.md +1 -20
  7. package/docs/architecture/security.md +16 -16
  8. package/docs/backup-troubleshooting.md +52 -0
  9. package/docs/browser-use-architecture-phase2.md +174 -0
  10. package/docs/error-handling.md +111 -0
  11. package/docs/skills.md +10 -10
  12. package/docs/stt-provider-onboarding.md +121 -0
  13. package/knip.json +20 -3
  14. package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
  15. package/node_modules/@vellumai/ces-contracts/package.json +5 -4
  16. package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
  17. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
  18. package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
  19. package/node_modules/@vellumai/credential-storage/package.json +2 -2
  20. package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
  21. package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
  22. package/node_modules/@vellumai/egress-proxy/package.json +2 -2
  23. package/openapi.yaml +1094 -72
  24. package/package.json +9 -8
  25. package/scripts/generate-openapi.ts +50 -12
  26. package/scripts/test.sh +73 -18
  27. package/src/__tests__/agent-image-optimize.test.ts +28 -0
  28. package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
  29. package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
  30. package/src/__tests__/agent-loop.test.ts +235 -1
  31. package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
  32. package/src/__tests__/anthropic-provider.test.ts +434 -12
  33. package/src/__tests__/approval-cascade.test.ts +31 -10
  34. package/src/__tests__/approval-routes-http.test.ts +134 -10
  35. package/src/__tests__/assistant-attachments.test.ts +44 -0
  36. package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
  37. package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
  38. package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
  39. package/src/__tests__/browser-fill-credential.test.ts +12 -1
  40. package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
  41. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
  42. package/src/__tests__/browser-skill-endstate.test.ts +52 -159
  43. package/src/__tests__/btw-routes.test.ts +54 -1
  44. package/src/__tests__/call-controller.test.ts +582 -22
  45. package/src/__tests__/call-site-routing-provider.test.ts +214 -0
  46. package/src/__tests__/catalog-cache.test.ts +27 -4
  47. package/src/__tests__/catalog-files.test.ts +138 -0
  48. package/src/__tests__/channel-approval-routes.test.ts +4 -4
  49. package/src/__tests__/channel-invite-transport.test.ts +2 -2
  50. package/src/__tests__/channel-readiness-routes.test.ts +16 -20
  51. package/src/__tests__/channel-readiness-service.test.ts +12 -7
  52. package/src/__tests__/channel-reply-delivery.test.ts +300 -2
  53. package/src/__tests__/checker.test.ts +576 -502
  54. package/src/__tests__/clawhub-files.test.ts +347 -0
  55. package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
  56. package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
  57. package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
  58. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  59. package/src/__tests__/config-analysis.test.ts +83 -0
  60. package/src/__tests__/config-loader-backfill.test.ts +174 -0
  61. package/src/__tests__/config-loader-corrupt.test.ts +183 -0
  62. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
  63. package/src/__tests__/config-schema-cmd.test.ts +11 -5
  64. package/src/__tests__/config-schema.test.ts +1458 -198
  65. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
  66. package/src/__tests__/config-watcher.test.ts +45 -10
  67. package/src/__tests__/contact-store-user-file.test.ts +511 -0
  68. package/src/__tests__/contacts-write.test.ts +197 -0
  69. package/src/__tests__/context-token-estimator.test.ts +191 -1
  70. package/src/__tests__/context-window-manager.test.ts +618 -2
  71. package/src/__tests__/conversation-abort-tool-results.test.ts +32 -16
  72. package/src/__tests__/conversation-agent-loop-overflow.test.ts +62 -17
  73. package/src/__tests__/conversation-agent-loop.test.ts +510 -84
  74. package/src/__tests__/conversation-attachments.test.ts +1 -1
  75. package/src/__tests__/conversation-confirmation-signals.test.ts +165 -9
  76. package/src/__tests__/conversation-error.test.ts +102 -1
  77. package/src/__tests__/conversation-history-web-search.test.ts +17 -4
  78. package/src/__tests__/conversation-init.benchmark.test.ts +42 -1
  79. package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
  80. package/src/__tests__/conversation-lifecycle.test.ts +336 -0
  81. package/src/__tests__/conversation-list-source.test.ts +145 -0
  82. package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
  83. package/src/__tests__/conversation-pre-run-repair.test.ts +32 -16
  84. package/src/__tests__/conversation-process-callsite.test.ts +306 -0
  85. package/src/__tests__/conversation-provider-retry-repair.test.ts +32 -16
  86. package/src/__tests__/conversation-queue.test.ts +932 -76
  87. package/src/__tests__/conversation-routes-disk-view.test.ts +299 -1
  88. package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
  89. package/src/__tests__/conversation-runtime-assembly.test.ts +2790 -55
  90. package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
  91. package/src/__tests__/conversation-skill-tools.test.ts +12 -143
  92. package/src/__tests__/conversation-slash-commands.test.ts +33 -0
  93. package/src/__tests__/conversation-slash-queue.test.ts +120 -34
  94. package/src/__tests__/conversation-slash-unknown.test.ts +32 -16
  95. package/src/__tests__/conversation-speed-override.test.ts +30 -11
  96. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
  97. package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
  98. package/src/__tests__/conversation-title-service.test.ts +2 -2
  99. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
  100. package/src/__tests__/conversation-unread-route.test.ts +2 -2
  101. package/src/__tests__/conversation-usage.test.ts +3 -1
  102. package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
  103. package/src/__tests__/conversation-workspace-injection.test.ts +45 -15
  104. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +46 -16
  105. package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
  106. package/src/__tests__/credential-health-service.test.ts +352 -0
  107. package/src/__tests__/credential-security-invariants.test.ts +8 -3
  108. package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
  109. package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
  110. package/src/__tests__/credential-vault-unit.test.ts +495 -3
  111. package/src/__tests__/credentials-cli.test.ts +32 -16
  112. package/src/__tests__/cross-provider-web-search.test.ts +230 -35
  113. package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
  114. package/src/__tests__/delete-propagation.test.ts +437 -0
  115. package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
  116. package/src/__tests__/device-id.test.ts +112 -0
  117. package/src/__tests__/dm-backfill.test.ts +417 -0
  118. package/src/__tests__/dm-persistence.test.ts +227 -0
  119. package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
  120. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
  121. package/src/__tests__/edit-propagation.test.ts +280 -0
  122. package/src/__tests__/email-html-renderer.test.ts +71 -0
  123. package/src/__tests__/email-invite-adapter.test.ts +36 -32
  124. package/src/__tests__/emit-event-signal.test.ts +71 -0
  125. package/src/__tests__/ephemeral-permissions.test.ts +93 -3
  126. package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
  127. package/src/__tests__/estimator-calibration.test.ts +213 -0
  128. package/src/__tests__/extension-id-sync-guard.test.ts +101 -15
  129. package/src/__tests__/file-write-tool.test.ts +151 -1
  130. package/src/__tests__/filing-service.test.ts +255 -0
  131. package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
  132. package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
  133. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  134. package/src/__tests__/gemini-provider.test.ts +64 -3
  135. package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
  136. package/src/__tests__/guardian-grant-minting.test.ts +8 -0
  137. package/src/__tests__/headless-browser-interactions.test.ts +44 -1
  138. package/src/__tests__/headless-browser-mode.test.ts +614 -0
  139. package/src/__tests__/headless-browser-navigate.test.ts +142 -5
  140. package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
  141. package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
  142. package/src/__tests__/heartbeat-service.test.ts +166 -32
  143. package/src/__tests__/home-state-routes.test.ts +162 -0
  144. package/src/__tests__/host-bash-proxy.test.ts +0 -5
  145. package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
  146. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
  147. package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
  148. package/src/__tests__/host-cu-proxy.test.ts +0 -5
  149. package/src/__tests__/host-shell-tool.test.ts +124 -18
  150. package/src/__tests__/http-user-message-parity.test.ts +29 -1
  151. package/src/__tests__/identity-intro-cache.test.ts +40 -10
  152. package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
  153. package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
  154. package/src/__tests__/intent-routing.test.ts +1 -40
  155. package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
  156. package/src/__tests__/llm-catalog-parity.test.ts +174 -0
  157. package/src/__tests__/llm-context-normalization.test.ts +609 -0
  158. package/src/__tests__/llm-context-route-provider.test.ts +86 -5
  159. package/src/__tests__/llm-resolver.test.ts +214 -0
  160. package/src/__tests__/llm-schema.test.ts +223 -0
  161. package/src/__tests__/llm-usage-store.test.ts +363 -0
  162. package/src/__tests__/managed-proxy-context.test.ts +6 -2
  163. package/src/__tests__/media-stream-output.test.ts +555 -0
  164. package/src/__tests__/media-stream-parser.test.ts +374 -0
  165. package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
  166. package/src/__tests__/media-stream-stt-session.test.ts +588 -0
  167. package/src/__tests__/media-turn-detector.test.ts +440 -0
  168. package/src/__tests__/message-queue.test.ts +125 -0
  169. package/src/__tests__/messaging-skill-split.test.ts +3 -34
  170. package/src/__tests__/migration-export-http.test.ts +6 -6
  171. package/src/__tests__/migration-import-commit-http.test.ts +8 -6
  172. package/src/__tests__/migration-import-from-url.test.ts +684 -0
  173. package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
  174. package/src/__tests__/migration-validate-http.test.ts +3 -3
  175. package/src/__tests__/mock-gateway-ipc.ts +151 -0
  176. package/src/__tests__/model-intents.test.ts +10 -84
  177. package/src/__tests__/notification-decision-fallback.test.ts +0 -10
  178. package/src/__tests__/notification-decision-identity.test.ts +0 -9
  179. package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
  180. package/src/__tests__/oauth-apps-routes.test.ts +1 -0
  181. package/src/__tests__/oauth-cli.test.ts +2 -0
  182. package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
  183. package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
  184. package/src/__tests__/oauth-providers-routes.test.ts +2 -0
  185. package/src/__tests__/oauth-store.test.ts +95 -7
  186. package/src/__tests__/oauth2-gateway-transport.test.ts +257 -9
  187. package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
  188. package/src/__tests__/onboarding-template-contract.test.ts +6 -13
  189. package/src/__tests__/openai-provider.test.ts +183 -0
  190. package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
  191. package/src/__tests__/openai-responses-provider.test.ts +1501 -0
  192. package/src/__tests__/openrouter-provider-only.test.ts +135 -0
  193. package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
  194. package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
  195. package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
  196. package/src/__tests__/permission-mode.test.ts +16 -0
  197. package/src/__tests__/permission-types.test.ts +0 -1
  198. package/src/__tests__/persona-resolver.test.ts +251 -0
  199. package/src/__tests__/pkb-autoinject.test.ts +37 -1
  200. package/src/__tests__/platform-bash-auto-approve.test.ts +5 -1
  201. package/src/__tests__/platform.test.ts +92 -1
  202. package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
  203. package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
  204. package/src/__tests__/pricing.test.ts +224 -3
  205. package/src/__tests__/profiler-routes.test.ts +1 -1
  206. package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
  207. package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
  208. package/src/__tests__/provider-error-scenarios.test.ts +135 -6
  209. package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
  210. package/src/__tests__/provider-registry-ollama.test.ts +1 -2
  211. package/src/__tests__/proxy-approval-callback.test.ts +0 -1
  212. package/src/__tests__/qdrant-manager.test.ts +29 -8
  213. package/src/__tests__/reaction-persistence.test.ts +560 -0
  214. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
  215. package/src/__tests__/relationship-state-contract.test.ts +175 -0
  216. package/src/__tests__/relay-server.test.ts +424 -6
  217. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  218. package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
  219. package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
  220. package/src/__tests__/risk-classifier-parity.test.ts +230 -0
  221. package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
  222. package/src/__tests__/search-skills-unified.test.ts +118 -0
  223. package/src/__tests__/secret-ingress-http.test.ts +28 -0
  224. package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
  225. package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
  226. package/src/__tests__/secret-scanner-executor.test.ts +5 -1
  227. package/src/__tests__/secure-keys.test.ts +107 -0
  228. package/src/__tests__/send-endpoint-busy.test.ts +34 -2
  229. package/src/__tests__/sequence-store.test.ts +1 -1
  230. package/src/__tests__/server-history-render.test.ts +80 -0
  231. package/src/__tests__/settings-routes.test.ts +201 -0
  232. package/src/__tests__/shell-parser-property.test.ts +13 -13
  233. package/src/__tests__/skill-cache-store.test.ts +182 -0
  234. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  235. package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
  236. package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
  237. package/src/__tests__/skills.test.ts +19 -30
  238. package/src/__tests__/skillssh-files.test.ts +446 -0
  239. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  240. package/src/__tests__/slack-block-formatting.test.ts +110 -0
  241. package/src/__tests__/slack-channel-config.test.ts +564 -1
  242. package/src/__tests__/slack-skill.test.ts +3 -8
  243. package/src/__tests__/starter-bundle.test.ts +35 -0
  244. package/src/__tests__/stt-catalog-parity.test.ts +282 -0
  245. package/src/__tests__/stt-stream-session.test.ts +535 -0
  246. package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
  247. package/src/__tests__/suggestion-routes.test.ts +160 -3
  248. package/src/__tests__/system-prompt.test.ts +126 -53
  249. package/src/__tests__/task-runner.test.ts +3 -1
  250. package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
  251. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  252. package/src/__tests__/terminal-tools.test.ts +26 -7
  253. package/src/__tests__/test-preload.ts +18 -0
  254. package/src/__tests__/test-support/browser-skill-harness.ts +2 -49
  255. package/src/__tests__/thread-backfill.test.ts +941 -0
  256. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
  257. package/src/__tests__/tool-executor-lifecycle-events.test.ts +10 -6
  258. package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
  259. package/src/__tests__/tool-executor.test.ts +88 -113
  260. package/src/__tests__/tool-result-truncation.test.ts +36 -0
  261. package/src/__tests__/trust-store.test.ts +442 -103
  262. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
  263. package/src/__tests__/tts-catalog-parity.test.ts +345 -0
  264. package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
  265. package/src/__tests__/twilio-routes.test.ts +376 -0
  266. package/src/__tests__/unicode.test.ts +293 -0
  267. package/src/__tests__/update-bulletin-job.test.ts +389 -0
  268. package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
  269. package/src/__tests__/usage-routes.test.ts +25 -4
  270. package/src/__tests__/user-reference.test.ts +46 -61
  271. package/src/__tests__/verification-control-plane-policy.test.ts +5 -22
  272. package/src/__tests__/voice-config-update.test.ts +403 -0
  273. package/src/__tests__/voice-quality.test.ts +434 -19
  274. package/src/__tests__/voice-session-bridge.test.ts +39 -0
  275. package/src/__tests__/volume-security-guard.test.ts +3 -2
  276. package/src/__tests__/web-search-history.test.ts +337 -0
  277. package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
  278. package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
  279. package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
  280. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
  281. package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
  282. package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
  283. package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
  284. package/src/__tests__/workspace-migration-meets.test.ts +244 -0
  285. package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
  286. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
  287. package/src/__tests__/workspace-policy.test.ts +1 -11
  288. package/src/acp/client-handler.ts +1 -2
  289. package/src/agent/image-optimize.ts +24 -12
  290. package/src/agent/loop.ts +251 -19
  291. package/src/avatar/resvg-lazy.test.ts +136 -0
  292. package/src/avatar/resvg-lazy.ts +82 -9
  293. package/src/avatar/traits-png-sync.ts +21 -1
  294. package/src/backup/__tests__/backup-key.test.ts +152 -0
  295. package/src/backup/__tests__/backup-worker.test.ts +767 -0
  296. package/src/backup/__tests__/list-snapshots.test.ts +87 -0
  297. package/src/backup/__tests__/local-writer.test.ts +218 -0
  298. package/src/backup/__tests__/offsite-writer.test.ts +641 -0
  299. package/src/backup/__tests__/paths.test.ts +300 -0
  300. package/src/backup/__tests__/restore.test.ts +498 -0
  301. package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
  302. package/src/backup/__tests__/stream-crypt.test.ts +228 -0
  303. package/src/backup/backup-key.ts +137 -0
  304. package/src/backup/backup-worker.ts +459 -0
  305. package/src/backup/list-snapshots.ts +147 -0
  306. package/src/backup/local-writer.ts +133 -0
  307. package/src/backup/offsite-writer.ts +222 -0
  308. package/src/backup/paths.ts +226 -0
  309. package/src/backup/restore.ts +322 -0
  310. package/src/backup/snapshot-lock.ts +431 -0
  311. package/src/backup/stream-crypt.ts +263 -0
  312. package/src/browser/__tests__/operations.test.ts +163 -0
  313. package/src/browser/identifiers.ts +51 -0
  314. package/src/browser/operations.ts +660 -0
  315. package/src/browser/types.ts +81 -0
  316. package/src/bundler/package-resolver.ts +4 -0
  317. package/src/calls/audio-store.ts +11 -5
  318. package/src/calls/call-controller.ts +226 -71
  319. package/src/calls/call-domain.ts +9 -0
  320. package/src/calls/call-speech-output.ts +190 -0
  321. package/src/calls/call-transport.ts +77 -0
  322. package/src/calls/guardian-question-copy.ts +2 -2
  323. package/src/calls/media-stream-audio-transcode.ts +173 -0
  324. package/src/calls/media-stream-output.ts +660 -0
  325. package/src/calls/media-stream-parser.ts +300 -0
  326. package/src/calls/media-stream-protocol.ts +166 -0
  327. package/src/calls/media-stream-server.ts +592 -0
  328. package/src/calls/media-stream-stt-session.ts +460 -0
  329. package/src/calls/media-turn-detector.ts +230 -0
  330. package/src/calls/relay-server.ts +90 -75
  331. package/src/calls/resolve-call-tts-provider.ts +136 -0
  332. package/src/calls/telephony-stt-routing.ts +145 -0
  333. package/src/calls/tts-call-strategy.ts +161 -0
  334. package/src/calls/tts-text-sanitizer.ts +32 -16
  335. package/src/calls/twilio-routes.ts +281 -17
  336. package/src/calls/voice-quality.ts +78 -35
  337. package/src/calls/voice-session-bridge.ts +9 -1
  338. package/src/channels/types.ts +16 -0
  339. package/src/cli/AGENTS.md +1 -1
  340. package/src/cli/__tests__/run-assistant-command.ts +11 -1
  341. package/src/cli/commands/__tests__/attachment.test.ts +438 -0
  342. package/src/cli/commands/__tests__/backup.test.ts +1165 -0
  343. package/src/cli/commands/__tests__/browser.test.ts +554 -0
  344. package/src/cli/commands/__tests__/cache.test.ts +623 -0
  345. package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
  346. package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
  347. package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
  348. package/src/cli/commands/__tests__/email-download.test.ts +16 -1
  349. package/src/cli/commands/__tests__/email-list.test.ts +28 -4
  350. package/src/cli/commands/__tests__/email-register.test.ts +4 -4
  351. package/src/cli/commands/__tests__/email-send.test.ts +130 -5
  352. package/src/cli/commands/__tests__/email-status.test.ts +5 -1
  353. package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
  354. package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
  355. package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
  356. package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
  357. package/src/cli/commands/__tests__/task.test.ts +913 -0
  358. package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
  359. package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
  360. package/src/cli/commands/__tests__/ui.test.ts +1215 -0
  361. package/src/cli/commands/__tests__/watchers.test.ts +716 -0
  362. package/src/cli/commands/attachment.ts +182 -0
  363. package/src/cli/commands/backup.ts +993 -0
  364. package/src/cli/commands/browser.ts +350 -0
  365. package/src/cli/commands/cache.ts +341 -0
  366. package/src/cli/commands/completions.ts +0 -3
  367. package/src/cli/commands/config.ts +6 -6
  368. package/src/cli/commands/conversations-import.ts +347 -0
  369. package/src/cli/commands/conversations.ts +90 -0
  370. package/src/cli/commands/credentials.ts +0 -1
  371. package/src/cli/commands/domain.ts +210 -0
  372. package/src/cli/commands/email.ts +308 -16
  373. package/src/cli/commands/image-generation.ts +300 -0
  374. package/src/cli/commands/inference.ts +200 -0
  375. package/src/cli/commands/memory.ts +127 -17
  376. package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
  377. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
  378. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
  379. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
  380. package/src/cli/commands/oauth/mode.ts +12 -3
  381. package/src/cli/commands/oauth/providers.ts +15 -0
  382. package/src/cli/commands/oauth/shared.ts +2 -1
  383. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -10
  384. package/src/cli/commands/platform/__tests__/connect.test.ts +6 -1
  385. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -2
  386. package/src/cli/commands/platform/__tests__/status.test.ts +6 -1
  387. package/src/cli/commands/stt.ts +339 -0
  388. package/src/cli/commands/task.ts +795 -0
  389. package/src/cli/commands/trust.ts +50 -19
  390. package/src/cli/commands/tts.ts +273 -0
  391. package/src/cli/commands/ui.ts +670 -0
  392. package/src/cli/commands/watchers.ts +509 -0
  393. package/src/cli/lib/daemon-credential-client.ts +0 -19
  394. package/src/cli/program.ts +53 -8
  395. package/src/cli.ts +0 -37
  396. package/src/config/__tests__/backup-schema.test.ts +134 -0
  397. package/src/config/assistant-feature-flags.ts +61 -62
  398. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
  399. package/src/config/bundled-skills/contacts/SKILL.md +2 -2
  400. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
  401. package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
  402. package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
  403. package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
  404. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
  405. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
  406. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
  407. package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
  408. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  409. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
  410. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  411. package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
  412. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -2
  413. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
  414. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
  415. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
  416. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  417. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +28 -18
  418. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  419. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  420. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
  421. package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
  422. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
  423. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
  424. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
  425. package/src/config/bundled-tool-registry.ts +0 -167
  426. package/src/config/env-registry.ts +24 -0
  427. package/src/config/env.ts +39 -10
  428. package/src/config/feature-flag-registry.json +63 -15
  429. package/src/config/llm-resolver.ts +128 -0
  430. package/src/config/loader.ts +220 -22
  431. package/src/config/raw-config-utils.ts +30 -2
  432. package/src/config/sanitize-for-transfer.ts +35 -0
  433. package/src/config/schema.ts +65 -51
  434. package/src/config/schemas/__tests__/stt.test.ts +43 -0
  435. package/src/config/schemas/analysis.ts +32 -0
  436. package/src/config/schemas/backup.ts +72 -0
  437. package/src/config/schemas/calls.ts +1 -30
  438. package/src/config/schemas/elevenlabs.ts +0 -59
  439. package/src/config/schemas/filing.ts +49 -14
  440. package/src/config/schemas/heartbeat.ts +27 -10
  441. package/src/config/schemas/host-browser.ts +47 -1
  442. package/src/config/schemas/inference.ts +3 -23
  443. package/src/config/schemas/llm.ts +318 -0
  444. package/src/config/schemas/memory-lifecycle.ts +14 -2
  445. package/src/config/schemas/memory-processing.ts +1 -9
  446. package/src/config/schemas/notifications.ts +4 -11
  447. package/src/config/schemas/platform.ts +3 -9
  448. package/src/config/schemas/security.ts +33 -0
  449. package/src/config/schemas/services.ts +53 -4
  450. package/src/config/schemas/stt.ts +60 -0
  451. package/src/config/schemas/tts.ts +283 -0
  452. package/src/config/schemas/updates.ts +14 -0
  453. package/src/config/schemas/workspace-git.ts +3 -40
  454. package/src/config/skills.ts +6 -2
  455. package/src/config/types.ts +4 -0
  456. package/src/contacts/contact-store.ts +56 -11
  457. package/src/contacts/contacts-write.ts +38 -1
  458. package/src/context/__tests__/compact-prompt.test.ts +45 -0
  459. package/src/context/__tests__/microcompact.test.ts +805 -0
  460. package/src/context/estimator-calibration.ts +136 -0
  461. package/src/context/microcompact.ts +443 -0
  462. package/src/context/post-turn-tool-result-truncation.ts +3 -2
  463. package/src/context/prompts/compact.md +12 -0
  464. package/src/context/token-estimator.ts +61 -3
  465. package/src/context/tool-result-truncation.ts +2 -1
  466. package/src/context/window-manager.ts +272 -35
  467. package/src/credential-execution/approval-bridge.ts +0 -1
  468. package/src/credential-execution/executable-discovery.ts +23 -2
  469. package/src/credential-execution/process-manager.test.ts +109 -0
  470. package/src/credential-execution/process-manager.ts +96 -2
  471. package/src/credential-health/credential-health-service.ts +366 -0
  472. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
  473. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
  474. package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
  475. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
  476. package/src/daemon/approval-generators.ts +29 -4
  477. package/src/daemon/assistant-attachments.ts +24 -13
  478. package/src/daemon/classifier.ts +2 -2
  479. package/src/daemon/config-watcher.ts +99 -6
  480. package/src/daemon/context-overflow-reducer.ts +4 -1
  481. package/src/daemon/conversation-agent-loop-handlers.ts +85 -12
  482. package/src/daemon/conversation-agent-loop.ts +563 -104
  483. package/src/daemon/conversation-attachments.ts +2 -6
  484. package/src/daemon/conversation-error.ts +46 -0
  485. package/src/daemon/conversation-history.ts +40 -6
  486. package/src/daemon/conversation-launch.ts +220 -0
  487. package/src/daemon/conversation-lifecycle.ts +85 -11
  488. package/src/daemon/conversation-messaging.ts +110 -7
  489. package/src/daemon/conversation-notifiers.ts +5 -0
  490. package/src/daemon/conversation-process.ts +591 -23
  491. package/src/daemon/conversation-queue-manager.ts +27 -0
  492. package/src/daemon/conversation-runtime-assembly.ts +769 -28
  493. package/src/daemon/conversation-slash.ts +38 -2
  494. package/src/daemon/conversation-surfaces.ts +483 -5
  495. package/src/daemon/conversation-tool-setup.ts +35 -5
  496. package/src/daemon/conversation-usage.ts +8 -5
  497. package/src/daemon/conversation.ts +193 -47
  498. package/src/daemon/external-skills-bootstrap.ts +41 -0
  499. package/src/daemon/guardian-action-generators.ts +34 -14
  500. package/src/daemon/handlers/config-model.test.ts +86 -0
  501. package/src/daemon/handlers/config-model.ts +54 -12
  502. package/src/daemon/handlers/config-slack-channel.ts +269 -94
  503. package/src/daemon/handlers/conversations.ts +13 -3
  504. package/src/daemon/handlers/shared.ts +51 -1
  505. package/src/daemon/handlers/skills.ts +323 -79
  506. package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
  507. package/src/daemon/host-browser-proxy.ts +2 -1
  508. package/src/daemon/lifecycle.ts +185 -26
  509. package/src/daemon/message-protocol.ts +6 -0
  510. package/src/daemon/message-types/conversations.ts +48 -1
  511. package/src/daemon/message-types/home.ts +40 -0
  512. package/src/daemon/message-types/meet.ts +143 -0
  513. package/src/daemon/message-types/messages.ts +23 -1
  514. package/src/daemon/message-types/schedules.ts +34 -2
  515. package/src/daemon/message-types/skills.ts +16 -0
  516. package/src/daemon/message-types/surfaces.ts +2 -0
  517. package/src/daemon/message-types/trust.ts +0 -2
  518. package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
  519. package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
  520. package/src/daemon/pkb-context-tracker.test.ts +169 -0
  521. package/src/daemon/pkb-context-tracker.ts +125 -0
  522. package/src/daemon/pkb-reminder-builder.test.ts +70 -0
  523. package/src/daemon/pkb-reminder-builder.ts +31 -0
  524. package/src/daemon/providers-setup.ts +6 -0
  525. package/src/daemon/server.ts +463 -10
  526. package/src/daemon/shutdown-handlers.ts +32 -4
  527. package/src/daemon/shutdown-registry.ts +40 -0
  528. package/src/daemon/tool-side-effects.ts +9 -9
  529. package/src/daemon/watch-handler.ts +4 -4
  530. package/src/daemon/web-search-history.ts +126 -0
  531. package/src/email/html-renderer.ts +76 -0
  532. package/src/events/domain-events.ts +0 -1
  533. package/src/filing/filing-service.ts +9 -10
  534. package/src/heartbeat/heartbeat-service.ts +156 -22
  535. package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
  536. package/src/home/__tests__/emit-feed-event.test.ts +169 -0
  537. package/src/home/__tests__/feed-scheduler.test.ts +222 -0
  538. package/src/home/__tests__/feed-types.test.ts +275 -0
  539. package/src/home/__tests__/feed-writer.test.ts +688 -0
  540. package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
  541. package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
  542. package/src/home/__tests__/progress-formula.test.ts +213 -0
  543. package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
  544. package/src/home/__tests__/rollup-producer.test.ts +442 -0
  545. package/src/home/assistant-feed-authoring.ts +128 -0
  546. package/src/home/emit-feed-event.ts +162 -0
  547. package/src/home/feed-scheduler.ts +263 -0
  548. package/src/home/feed-types.ts +235 -0
  549. package/src/home/feed-writer.ts +469 -0
  550. package/src/home/platform-gmail-digest.ts +163 -0
  551. package/src/home/progress-formula.ts +86 -0
  552. package/src/home/relationship-state-writer.ts +824 -0
  553. package/src/home/relationship-state.ts +143 -0
  554. package/src/home/rollup-producer.ts +413 -0
  555. package/src/home/suggested-prompts.ts +101 -0
  556. package/src/hooks/runner.ts +7 -0
  557. package/src/inbound/platform-callback-registration.ts +12 -3
  558. package/src/inbound/public-ingress-urls.ts +12 -0
  559. package/src/instrument.ts +1 -1
  560. package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
  561. package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
  562. package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
  563. package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
  564. package/src/ipc/__tests__/socket-path.test.ts +73 -0
  565. package/src/ipc/__tests__/task-ipc.test.ts +577 -0
  566. package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
  567. package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
  568. package/src/ipc/cli-client.ts +152 -0
  569. package/src/ipc/cli-server.ts +252 -0
  570. package/src/ipc/gateway-client.ts +180 -0
  571. package/src/ipc/routes/attachment.ts +114 -0
  572. package/src/ipc/routes/browser-context.ts +61 -0
  573. package/src/ipc/routes/browser.ts +96 -0
  574. package/src/ipc/routes/cache.ts +96 -0
  575. package/src/ipc/routes/index.ts +21 -0
  576. package/src/ipc/routes/task-queue.ts +226 -0
  577. package/src/ipc/routes/task.ts +173 -0
  578. package/src/ipc/routes/ui-request.ts +50 -0
  579. package/src/ipc/routes/wake-conversation.ts +19 -0
  580. package/src/ipc/routes/watcher.ts +203 -0
  581. package/src/ipc/socket-path.ts +100 -0
  582. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
  583. package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
  584. package/src/memory/__tests__/conversation-analyze-job.test.ts +233 -0
  585. package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
  586. package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
  587. package/src/memory/admin.ts +18 -0
  588. package/src/memory/app-store.ts +1 -1
  589. package/src/memory/attachments-store.ts +70 -0
  590. package/src/memory/auto-analysis-enqueue.ts +127 -0
  591. package/src/memory/auto-analysis-guard.ts +27 -0
  592. package/src/memory/cleanup-schedule-state.ts +37 -0
  593. package/src/memory/conversation-analyze-job.ts +74 -0
  594. package/src/memory/conversation-attention-store.ts +13 -6
  595. package/src/memory/conversation-crud.ts +199 -0
  596. package/src/memory/conversation-disk-view.ts +7 -0
  597. package/src/memory/conversation-group-migration.ts +65 -1
  598. package/src/memory/conversation-queries.ts +6 -5
  599. package/src/memory/conversation-title-service.ts +7 -4
  600. package/src/memory/db-init.ts +8 -0
  601. package/src/memory/db-maintenance.ts +108 -0
  602. package/src/memory/db.ts +1 -0
  603. package/src/memory/embedding-backend.ts +1 -1
  604. package/src/memory/graph/compaction.ts +299 -0
  605. package/src/memory/graph/consolidation.ts +4 -4
  606. package/src/memory/graph/conversation-graph-memory.ts +104 -29
  607. package/src/memory/graph/extraction.test.ts +295 -2
  608. package/src/memory/graph/extraction.ts +181 -51
  609. package/src/memory/graph/graph-search.test.ts +92 -0
  610. package/src/memory/graph/graph-search.ts +4 -1
  611. package/src/memory/graph/narrative.ts +2 -2
  612. package/src/memory/graph/pattern-scan.ts +2 -2
  613. package/src/memory/graph/retriever.test.ts +459 -0
  614. package/src/memory/graph/retriever.ts +257 -66
  615. package/src/memory/graph/scoring.test.ts +186 -0
  616. package/src/memory/graph/scoring.ts +31 -1
  617. package/src/memory/graph/store.ts +41 -0
  618. package/src/memory/graph/tool-handlers.ts +27 -0
  619. package/src/memory/graph/tools.ts +6 -1
  620. package/src/memory/group-crud.ts +6 -1
  621. package/src/memory/indexer.ts +95 -16
  622. package/src/memory/job-handlers/cleanup.ts +11 -8
  623. package/src/memory/job-handlers/conversation-starters.ts +39 -30
  624. package/src/memory/job-handlers/summarization.ts +2 -2
  625. package/src/memory/job-utils.ts +7 -1
  626. package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
  627. package/src/memory/jobs/embed-pkb-file.ts +54 -0
  628. package/src/memory/jobs-store.ts +106 -5
  629. package/src/memory/jobs-worker.ts +26 -9
  630. package/src/memory/llm-usage-store.ts +92 -56
  631. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
  632. package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
  633. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
  634. package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
  635. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
  636. package/src/memory/migrations/index.ts +7 -0
  637. package/src/memory/migrations/registry.ts +8 -0
  638. package/src/memory/pkb/pkb-index.test.ts +368 -0
  639. package/src/memory/pkb/pkb-index.ts +255 -0
  640. package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
  641. package/src/memory/pkb/pkb-reconcile.ts +148 -0
  642. package/src/memory/pkb/pkb-search.test.ts +438 -0
  643. package/src/memory/pkb/pkb-search.ts +137 -0
  644. package/src/memory/pkb/types.ts +53 -0
  645. package/src/memory/qdrant-client.ts +122 -1
  646. package/src/memory/qdrant-manager.ts +43 -16
  647. package/src/memory/schema/conversations.ts +2 -0
  648. package/src/memory/schema/oauth.ts +3 -0
  649. package/src/memory/slack-thread-store.ts +37 -0
  650. package/src/memory/usage-buckets.ts +396 -0
  651. package/src/messaging/providers/gmail/adapter.ts +6 -16
  652. package/src/messaging/providers/gmail/client.ts +79 -6
  653. package/src/messaging/providers/gmail/types.ts +7 -0
  654. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
  655. package/src/messaging/providers/slack/adapter.ts +155 -38
  656. package/src/messaging/providers/slack/backfill.test.ts +257 -0
  657. package/src/messaging/providers/slack/backfill.ts +101 -0
  658. package/src/messaging/providers/slack/client.ts +16 -0
  659. package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
  660. package/src/messaging/providers/slack/message-metadata.ts +123 -0
  661. package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
  662. package/src/messaging/providers/slack/render-transcript.ts +443 -0
  663. package/src/messaging/providers/slack/types.ts +4 -0
  664. package/src/messaging/style-analyzer.ts +5 -2
  665. package/src/notifications/README.md +9 -5
  666. package/src/notifications/decision-engine.ts +6 -12
  667. package/src/notifications/preference-extractor.ts +2 -6
  668. package/src/notifications/signal.ts +5 -0
  669. package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
  670. package/src/oauth/byo-connection.test.ts +18 -1
  671. package/src/oauth/byo-connection.ts +3 -1
  672. package/src/oauth/connect-orchestrator.ts +2 -0
  673. package/src/oauth/connection-resolver.ts +6 -2
  674. package/src/oauth/connection.ts +2 -0
  675. package/src/oauth/oauth-store.ts +10 -0
  676. package/src/oauth/platform-connection.test.ts +145 -0
  677. package/src/oauth/platform-connection.ts +62 -31
  678. package/src/oauth/seed-providers.ts +10 -1
  679. package/src/permissions/approval-policy.test.ts +948 -0
  680. package/src/permissions/approval-policy.ts +257 -0
  681. package/src/permissions/bash-risk-classifier.test.ts +1208 -0
  682. package/src/permissions/bash-risk-classifier.ts +707 -0
  683. package/src/permissions/checker.ts +218 -699
  684. package/src/permissions/command-registry.test.ts +535 -0
  685. package/src/permissions/command-registry.ts +825 -0
  686. package/src/permissions/defaults.ts +71 -75
  687. package/src/permissions/file-risk-classifier.test.ts +535 -0
  688. package/src/permissions/file-risk-classifier.ts +274 -0
  689. package/src/permissions/risk-types.ts +205 -0
  690. package/src/permissions/secret-prompter.ts +53 -2
  691. package/src/permissions/skill-risk-classifier.test.ts +311 -0
  692. package/src/permissions/skill-risk-classifier.ts +214 -0
  693. package/src/permissions/trust-client.ts +52 -25
  694. package/src/permissions/trust-store-interface.ts +1 -6
  695. package/src/permissions/trust-store.ts +164 -65
  696. package/src/permissions/types.ts +23 -14
  697. package/src/permissions/web-risk-classifier.test.ts +170 -0
  698. package/src/permissions/web-risk-classifier.ts +89 -0
  699. package/src/permissions/workspace-policy.ts +1 -13
  700. package/src/platform/client.test.ts +10 -0
  701. package/src/platform/client.ts +19 -1
  702. package/src/platform/sync-identity.ts +129 -0
  703. package/src/prompts/persona-resolver.ts +127 -3
  704. package/src/prompts/system-prompt.ts +78 -38
  705. package/src/prompts/templates/BOOTSTRAP.md +5 -5
  706. package/src/prompts/templates/SOUL.md +5 -3
  707. package/src/prompts/templates/channels/slack.md +20 -0
  708. package/src/prompts/update-bulletin-job.ts +190 -0
  709. package/src/prompts/user-reference.ts +20 -17
  710. package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
  711. package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
  712. package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
  713. package/src/providers/__tests__/retry-callsite.test.ts +424 -0
  714. package/src/providers/anthropic/client.ts +335 -70
  715. package/src/providers/call-site-routing.ts +71 -0
  716. package/src/providers/fireworks/client.ts +2 -2
  717. package/src/providers/gemini/client.ts +74 -3
  718. package/src/providers/managed-proxy/constants.ts +2 -1
  719. package/src/providers/model-catalog.ts +502 -28
  720. package/src/providers/model-intents.ts +8 -8
  721. package/src/providers/ollama/client.ts +2 -2
  722. package/src/providers/openai/chat-completions-provider.ts +530 -0
  723. package/src/providers/openai/client.ts +25 -440
  724. package/src/providers/openai/responses-provider.ts +579 -0
  725. package/src/providers/openrouter/client.ts +168 -4
  726. package/src/providers/provider-env-vars.ts +56 -0
  727. package/src/providers/provider-secret-catalog.ts +139 -0
  728. package/src/providers/provider-send-message.ts +22 -5
  729. package/src/providers/ratelimit.ts +4 -0
  730. package/src/providers/registry.ts +21 -10
  731. package/src/providers/retry.ts +185 -39
  732. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
  733. package/src/providers/speech-to-text/__tests__/resolve.test.ts +883 -0
  734. package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
  735. package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
  736. package/src/providers/speech-to-text/deepgram.test.ts +332 -0
  737. package/src/providers/speech-to-text/deepgram.ts +115 -0
  738. package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
  739. package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
  740. package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
  741. package/src/providers/speech-to-text/google-gemini.ts +101 -0
  742. package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
  743. package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
  744. package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
  745. package/src/providers/speech-to-text/openai-whisper.ts +63 -33
  746. package/src/providers/speech-to-text/provider-catalog.ts +323 -0
  747. package/src/providers/speech-to-text/resolve.ts +393 -6
  748. package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
  749. package/src/providers/speech-to-text/xai-realtime.ts +796 -0
  750. package/src/providers/speech-to-text/xai.test.ts +155 -0
  751. package/src/providers/speech-to-text/xai.ts +97 -0
  752. package/src/providers/types.ts +102 -3
  753. package/src/runtime/AGENTS.md +45 -3
  754. package/src/runtime/__tests__/agent-wake.test.ts +872 -0
  755. package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
  756. package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
  757. package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
  758. package/src/runtime/agent-wake.ts +553 -0
  759. package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
  760. package/src/runtime/auth/route-policy.ts +34 -5
  761. package/src/runtime/auth/token-service.ts +56 -1
  762. package/src/runtime/btw-sidechain.ts +15 -3
  763. package/src/runtime/capability-tokens.ts +10 -10
  764. package/src/runtime/channel-invite-transport.ts +1 -1
  765. package/src/runtime/channel-invite-transports/email.ts +14 -6
  766. package/src/runtime/channel-readiness-service.ts +12 -22
  767. package/src/runtime/channel-reply-delivery.ts +106 -2
  768. package/src/runtime/chrome-extension-registry.ts +38 -2
  769. package/src/runtime/decision-token.ts +116 -0
  770. package/src/runtime/gateway-client.ts +2 -2
  771. package/src/runtime/http-router.ts +32 -0
  772. package/src/runtime/http-server.ts +447 -11
  773. package/src/runtime/http-types.ts +29 -3
  774. package/src/runtime/interactive-ui.ts +362 -0
  775. package/src/runtime/invite-instruction-generator.ts +2 -2
  776. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
  777. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
  778. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
  779. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
  780. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
  781. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
  782. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
  783. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
  784. package/src/runtime/migrations/gcs-signed-url.ts +162 -0
  785. package/src/runtime/migrations/migration-transport.ts +1 -0
  786. package/src/runtime/migrations/migration-wizard.ts +1 -0
  787. package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
  788. package/src/runtime/migrations/vbundle-importer.ts +187 -8
  789. package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
  790. package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
  791. package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
  792. package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
  793. package/src/runtime/migrations/vbundle-validator.ts +15 -6
  794. package/src/runtime/pending-interactions.ts +0 -11
  795. package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
  796. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +618 -0
  797. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +247 -0
  798. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
  799. package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
  800. package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
  801. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
  802. package/src/runtime/routes/app-management-routes.ts +12 -18
  803. package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
  804. package/src/runtime/routes/approval-routes.ts +12 -17
  805. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
  806. package/src/runtime/routes/attachment-routes.test.ts +9 -3
  807. package/src/runtime/routes/attachment-routes.ts +216 -17
  808. package/src/runtime/routes/avatar-routes.ts +20 -4
  809. package/src/runtime/routes/backup-routes.ts +519 -0
  810. package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
  811. package/src/runtime/routes/btw-routes.ts +9 -10
  812. package/src/runtime/routes/contact-routes.test.ts +298 -0
  813. package/src/runtime/routes/contact-routes.ts +132 -5
  814. package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
  815. package/src/runtime/routes/conversation-management-routes.ts +133 -0
  816. package/src/runtime/routes/conversation-routes.ts +487 -160
  817. package/src/runtime/routes/debug-routes.ts +1 -1
  818. package/src/runtime/routes/diagnostics-routes.ts +6 -4
  819. package/src/runtime/routes/events-routes.ts +16 -0
  820. package/src/runtime/routes/filing-routes.ts +93 -0
  821. package/src/runtime/routes/guardian-approval-interception.ts +33 -3
  822. package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
  823. package/src/runtime/routes/home-feed-routes.ts +452 -0
  824. package/src/runtime/routes/home-state-routes.ts +138 -0
  825. package/src/runtime/routes/host-browser-routes.ts +3 -14
  826. package/src/runtime/routes/identity-intro-cache.ts +7 -3
  827. package/src/runtime/routes/identity-routes.ts +3 -17
  828. package/src/runtime/routes/inbound-message-handler.ts +912 -2
  829. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
  830. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
  831. package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
  832. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
  833. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
  834. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
  835. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
  836. package/src/runtime/routes/integrations/slack/channel.ts +36 -6
  837. package/src/runtime/routes/integrations/slack/share.ts +45 -7
  838. package/src/runtime/routes/llm-context-normalization.ts +325 -0
  839. package/src/runtime/routes/memory-item-routes.test.ts +3 -2
  840. package/src/runtime/routes/migration-routes.ts +722 -91
  841. package/src/runtime/routes/settings-routes.ts +26 -7
  842. package/src/runtime/routes/skills-routes.ts +76 -7
  843. package/src/runtime/routes/stt-routes.ts +233 -0
  844. package/src/runtime/routes/surface-action-routes.ts +41 -2
  845. package/src/runtime/routes/trust-rules-routes.ts +30 -14
  846. package/src/runtime/routes/tts-routes.ts +108 -24
  847. package/src/runtime/routes/usage-routes.ts +30 -2
  848. package/src/runtime/routes/user-route-dispatcher.ts +50 -5
  849. package/src/runtime/routes/user-routes.ts +13 -1
  850. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  851. package/src/runtime/routes/work-items-routes.ts +11 -3
  852. package/src/runtime/runtime-mode.ts +33 -0
  853. package/src/runtime/services/__tests__/analyze-conversation.test.ts +426 -0
  854. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
  855. package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
  856. package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
  857. package/src/runtime/services/analyze-conversation.ts +340 -0
  858. package/src/runtime/services/analyze-deps-singleton.ts +32 -0
  859. package/src/runtime/services/auto-analysis-prompt.ts +55 -0
  860. package/src/runtime/skill-route-registry.ts +71 -0
  861. package/src/runtime/slack-block-formatting.ts +437 -10
  862. package/src/schedule/scheduler.ts +58 -0
  863. package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
  864. package/src/security/__tests__/untrusted-content.test.ts +109 -0
  865. package/src/security/oauth2.ts +122 -37
  866. package/src/security/secure-keys.ts +32 -10
  867. package/src/security/token-manager.ts +35 -13
  868. package/src/security/untrusted-content.ts +102 -0
  869. package/src/sequence/engine.ts +23 -0
  870. package/src/sequence/types.ts +1 -1
  871. package/src/skills/catalog-cache.ts +26 -7
  872. package/src/skills/catalog-files.ts +64 -2
  873. package/src/skills/catalog-install.ts +31 -3
  874. package/src/skills/category-inference.ts +122 -0
  875. package/src/skills/clawhub-files.ts +213 -0
  876. package/src/skills/clawhub.ts +84 -23
  877. package/src/skills/skill-cache-store.ts +97 -0
  878. package/src/skills/skill-file-provider.ts +40 -0
  879. package/src/skills/skillssh-files.ts +395 -0
  880. package/src/skills/skillssh-registry.ts +4 -4
  881. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +468 -0
  882. package/src/stt/__tests__/types.test.ts +89 -0
  883. package/src/stt/daemon-batch-transcriber.ts +228 -0
  884. package/src/stt/stt-stream-session.ts +506 -0
  885. package/src/stt/types.ts +334 -0
  886. package/src/stt/wav-encoder.test.ts +373 -0
  887. package/src/stt/wav-encoder.ts +175 -0
  888. package/src/subagent/manager.ts +79 -27
  889. package/src/tasks/ephemeral-permissions.ts +9 -4
  890. package/src/telemetry/usage-telemetry-reporter.ts +27 -5
  891. package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
  892. package/src/tools/browser/__tests__/browser-status.test.ts +166 -0
  893. package/src/tools/browser/browser-execution.ts +1208 -41
  894. package/src/tools/browser/browser-manager.ts +45 -0
  895. package/src/tools/browser/browser-mode-constants.ts +12 -0
  896. package/src/tools/browser/browser-mode.ts +92 -0
  897. package/src/tools/browser/browser-status-constants.ts +33 -0
  898. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
  899. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
  900. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
  901. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
  902. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +205 -17
  903. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
  904. package/src/tools/browser/cdp-client/errors.ts +15 -0
  905. package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
  906. package/src/tools/browser/cdp-client/factory.ts +797 -87
  907. package/src/tools/browser/cdp-client/index.ts +16 -2
  908. package/src/tools/browser/cdp-client/types.ts +68 -0
  909. package/src/tools/credentials/tool-policy.ts +39 -5
  910. package/src/tools/credentials/vault.ts +41 -7
  911. package/src/tools/executor.ts +4 -0
  912. package/src/tools/filesystem/write.ts +52 -0
  913. package/src/tools/host-terminal/host-shell.ts +45 -5
  914. package/src/tools/memory/register.test.ts +185 -0
  915. package/src/tools/memory/register.ts +3 -1
  916. package/src/tools/network/web-fetch.ts +25 -12
  917. package/src/tools/network/web-search.ts +20 -2
  918. package/src/tools/permission-checker.ts +36 -15
  919. package/src/tools/policy-context.ts +25 -8
  920. package/src/tools/registry.ts +55 -3
  921. package/src/tools/shared/shell-output.ts +3 -1
  922. package/src/tools/side-effects.ts +0 -9
  923. package/src/tools/skills/execute.ts +2 -2
  924. package/src/tools/skills/sandbox-runner.ts +6 -2
  925. package/src/tools/terminal/backends/native.ts +51 -2
  926. package/src/tools/terminal/safe-env.ts +11 -2
  927. package/src/tools/terminal/shell.ts +16 -4
  928. package/src/tools/tool-manifest.ts +6 -0
  929. package/src/tools/types.ts +29 -3
  930. package/src/tools/ui-surface/definitions.ts +6 -1
  931. package/src/tools/verification-control-plane-policy.ts +1 -1
  932. package/src/tts/__tests__/provider-adapters.test.ts +1061 -0
  933. package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
  934. package/src/tts/__tests__/provider-catalog.test.ts +183 -0
  935. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  936. package/src/tts/provider-catalog.ts +219 -0
  937. package/src/tts/provider-registry.ts +73 -0
  938. package/src/tts/providers/deepgram-provider.ts +219 -0
  939. package/src/tts/providers/elevenlabs-provider.ts +211 -0
  940. package/src/tts/providers/fish-audio-provider.ts +183 -0
  941. package/src/tts/providers/index.ts +44 -0
  942. package/src/tts/providers/register-builtins.ts +130 -0
  943. package/src/tts/providers/xai-provider.ts +224 -0
  944. package/src/tts/synthesize-text.ts +110 -0
  945. package/src/tts/tts-config-resolver.ts +78 -0
  946. package/src/tts/types.ts +199 -0
  947. package/src/types/onboarding-context.ts +7 -0
  948. package/src/types/tar-stream.d.ts +66 -0
  949. package/src/util/abort-reasons.ts +58 -0
  950. package/src/util/device-id.ts +32 -16
  951. package/src/util/errors.ts +9 -1
  952. package/src/util/json.ts +17 -0
  953. package/src/util/platform.ts +56 -12
  954. package/src/util/pricing.ts +78 -5
  955. package/src/util/spawn.ts +1 -1
  956. package/src/util/truncate.ts +4 -2
  957. package/src/util/unicode.ts +201 -0
  958. package/src/version.ts +19 -24
  959. package/src/watcher/engine.ts +24 -1
  960. package/src/watcher/providers/google-calendar.ts +134 -8
  961. package/src/watcher/providers/outlook-calendar.ts +42 -2
  962. package/src/watcher/watcher-store.ts +31 -0
  963. package/src/workspace/git-service.ts +23 -4
  964. package/src/workspace/migrations/003-seed-device-id.ts +9 -3
  965. package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
  966. package/src/workspace/migrations/029-seed-pkb.ts +1 -1
  967. package/src/workspace/migrations/031-drop-user-md.ts +317 -0
  968. package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
  969. package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
  970. package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
  971. package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
  972. package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
  973. package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
  974. package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
  975. package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
  976. package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
  977. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
  978. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
  979. package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
  980. package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
  981. package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
  982. package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
  983. package/src/workspace/migrations/AGENTS.md +1 -1
  984. package/src/workspace/migrations/registry.ts +32 -0
  985. package/src/workspace/provider-commit-message-generator.ts +19 -38
  986. package/src/workspace/top-level-renderer.ts +13 -1
  987. package/src/workspace/turn-commit.ts +31 -0
  988. package/src/__tests__/email-cli.test.ts +0 -297
  989. package/src/__tests__/email-service-config-fallback.test.ts +0 -102
  990. package/src/__tests__/outlook-attachments.test.ts +0 -301
  991. package/src/__tests__/outlook-automation-tools.test.ts +0 -425
  992. package/src/__tests__/outlook-categories.test.ts +0 -212
  993. package/src/__tests__/outlook-compose-tools.test.ts +0 -325
  994. package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
  995. package/src/__tests__/outlook-follow-up.test.ts +0 -196
  996. package/src/__tests__/outlook-trash.test.ts +0 -77
  997. package/src/__tests__/outlook-unsubscribe.test.ts +0 -250
  998. package/src/__tests__/update-bulletin-format.test.ts +0 -122
  999. package/src/__tests__/update-bulletin-state.test.ts +0 -135
  1000. package/src/__tests__/update-bulletin.test.ts +0 -277
  1001. package/src/__tests__/update-template-contract.test.ts +0 -29
  1002. package/src/cli/commands/browser-relay.ts +0 -466
  1003. package/src/cli/commands/doctor.ts +0 -341
  1004. package/src/config/bundled-skills/browser/SKILL.md +0 -63
  1005. package/src/config/bundled-skills/browser/TOOLS.json +0 -393
  1006. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
  1007. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
  1008. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
  1009. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
  1010. package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
  1011. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
  1012. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
  1013. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
  1014. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
  1015. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
  1016. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
  1017. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
  1018. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -32
  1019. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
  1020. package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
  1021. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
  1022. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
  1023. package/src/config/bundled-skills/gmail/SKILL.md +0 -175
  1024. package/src/config/bundled-skills/gmail/TOOLS.json +0 -558
  1025. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -149
  1026. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
  1027. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
  1028. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
  1029. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
  1030. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
  1031. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
  1032. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -220
  1033. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
  1034. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -251
  1035. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
  1036. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
  1037. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
  1038. package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
  1039. package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
  1040. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  1041. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
  1042. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
  1043. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
  1044. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
  1045. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
  1046. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
  1047. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
  1048. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
  1049. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  1050. package/src/config/bundled-skills/outlook/SKILL.md +0 -196
  1051. package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
  1052. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
  1053. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
  1054. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
  1055. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
  1056. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
  1057. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
  1058. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
  1059. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
  1060. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
  1061. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
  1062. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
  1063. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
  1064. package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
  1065. package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
  1066. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
  1067. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
  1068. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
  1069. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
  1070. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
  1071. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
  1072. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
  1073. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
  1074. package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
  1075. package/src/config/bundled-skills/slack/SKILL.md +0 -107
  1076. package/src/config/bundled-skills/tasks/SKILL.md +0 -37
  1077. package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
  1078. package/src/config/bundled-skills/tasks/icon.svg +0 -34
  1079. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
  1080. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
  1081. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
  1082. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
  1083. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
  1084. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
  1085. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
  1086. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
  1087. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
  1088. package/src/config/bundled-skills/watcher/SKILL.md +0 -31
  1089. package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
  1090. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
  1091. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
  1092. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
  1093. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
  1094. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
  1095. package/src/email/guardrails.ts +0 -221
  1096. package/src/email/provider.ts +0 -117
  1097. package/src/email/providers/agentmail.ts +0 -361
  1098. package/src/email/providers/index.ts +0 -65
  1099. package/src/email/service.ts +0 -384
  1100. package/src/email/types.ts +0 -126
  1101. package/src/prompts/templates/UPDATES.md +0 -38
  1102. package/src/prompts/templates/USER.md +0 -13
  1103. package/src/prompts/update-bulletin-format.ts +0 -68
  1104. package/src/prompts/update-bulletin-state.ts +0 -58
  1105. package/src/prompts/update-bulletin-template-path.ts +0 -13
  1106. package/src/prompts/update-bulletin.ts +0 -128
  1107. package/src/providers/speech-to-text/types.ts +0 -17
  1108. package/src/runtime/routes/browser-cdp-routes.ts +0 -229
  1109. package/src/shared/provider-env-vars.ts +0 -19
  1110. package/src/tools/watcher/create.ts +0 -86
  1111. package/src/tools/watcher/delete.ts +0 -36
  1112. package/src/tools/watcher/digest.ts +0 -54
  1113. package/src/tools/watcher/list.ts +0 -83
  1114. package/src/tools/watcher/update.ts +0 -71
@@ -19,10 +19,29 @@ import {
19
19
  recordConversationSeenSignal,
20
20
  type SignalType,
21
21
  } from "../../memory/conversation-attention-store.js";
22
+ import {
23
+ addMessage,
24
+ countMessagesWithSlackMeta,
25
+ getMessageById,
26
+ getMessages,
27
+ updateMessageMetadata,
28
+ } from "../../memory/conversation-crud.js";
22
29
  import * as deliveryChannels from "../../memory/delivery-channels.js";
23
30
  import * as deliveryCrud from "../../memory/delivery-crud.js";
24
31
  import * as deliveryStatus from "../../memory/delivery-status.js";
25
32
  import * as externalConversationStore from "../../memory/external-conversation-store.js";
33
+ import type { Message as ProviderMessage } from "../../messaging/provider-types.js";
34
+ import {
35
+ backfillDm,
36
+ backfillThread,
37
+ } from "../../messaging/providers/slack/backfill.js";
38
+ import {
39
+ mergeSlackMetadata,
40
+ readSlackMetadata,
41
+ type SlackMessageMetadata,
42
+ writeSlackMetadata,
43
+ } from "../../messaging/providers/slack/message-metadata.js";
44
+ import { wrapUntrustedContent } from "../../security/untrusted-content.js";
26
45
  import { canonicalizeInboundIdentity } from "../../util/canonicalize-identity.js";
27
46
  import { getLogger } from "../../util/logger.js";
28
47
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
@@ -52,6 +71,28 @@ import { handleVerificationIntercept } from "./inbound-stages/verification-inter
52
71
 
53
72
  const log = getLogger("runtime-http");
54
73
 
74
+ // Delete-lookup retry configuration. Delete webhooks can race ahead of
75
+ // the inbound handler's `linkMessage` call when the original message's
76
+ // agent loop is still running. Retrying buys time for the link to land
77
+ // before we drop the deletion signal. Mirrors the edit-intercept path's
78
+ // EDIT_LOOKUP_RETRIES / EDIT_LOOKUP_DELAY_MS constants.
79
+ let deleteLookupRetries = 5;
80
+ let deleteLookupDelayMs = 2000;
81
+
82
+ /**
83
+ * Test-only override for the delete-lookup retry timings. Used by
84
+ * tests that exercise the "no such message" path without waiting
85
+ * through the full production backoff. Not exported from any barrel
86
+ * file — only the test file imports it directly.
87
+ */
88
+ export function _setDeleteLookupConfigForTests(
89
+ retries: number,
90
+ delayMs: number,
91
+ ): void {
92
+ deleteLookupRetries = retries;
93
+ deleteLookupDelayMs = delayMs;
94
+ }
95
+
55
96
  export async function handleChannelInbound(
56
97
  req: Request,
57
98
  processMessage?: MessageProcessor,
@@ -238,6 +279,152 @@ export async function handleChannelInbound(
238
279
  if (aclResult.earlyResponse) return aclResult.earlyResponse;
239
280
  const { resolvedMember, guardianVerifyCode } = aclResult;
240
281
 
282
+ // ── Slack delete propagation ──
283
+ // Slack message_deleted events are forwarded by the gateway with the
284
+ // sentinel `callbackData = "message_deleted"` and `sourceMetadata.messageId`
285
+ // set to the original (deleted) message's ts. Short-circuit the rest of
286
+ // the pipeline: the agent loop should not run for delete notifications,
287
+ // and routing the event through approval / agent paths would be incorrect.
288
+ // We mark the stored row as deleted in slackMeta but leave `content`
289
+ // untouched for audit purposes — rendering elides based on the deletedAt
290
+ // marker. Gated behind ingress ACL so non-members cannot drive deletes
291
+ // (matches the edit-intercept policy).
292
+ if (sourceChannel === "slack" && body.callbackData === "message_deleted") {
293
+ const deletedMessageTs =
294
+ typeof sourceMetadata?.messageId === "string"
295
+ ? sourceMetadata.messageId
296
+ : undefined;
297
+
298
+ if (!deletedMessageTs) {
299
+ log.debug(
300
+ { conversationExternalId },
301
+ "Slack message_deleted event missing sourceMetadata.messageId; ignoring",
302
+ );
303
+ return Response.json({ accepted: true, deleted: false });
304
+ }
305
+
306
+ // Look up the stored message via the existing channel-event lookup.
307
+ // The original message's externalMessageId may differ from its ts
308
+ // (Slack populates client_msg_id when present), so we join via the
309
+ // sourceMessageId column which records the ts explicitly.
310
+ //
311
+ // Retry with backoff mirrors the edit-intercept path: delete webhooks
312
+ // can race ahead of `linkMessage` when the original message's agent
313
+ // loop is still running. Without retries a delete that arrives in
314
+ // that window is silently dropped and the deletion signal is lost.
315
+ let original: { messageId: string; conversationId: string } | null = null;
316
+ for (let attempt = 0; attempt <= deleteLookupRetries; attempt++) {
317
+ original = deliveryCrud.findMessageBySourceId(
318
+ sourceChannel,
319
+ conversationExternalId,
320
+ deletedMessageTs,
321
+ );
322
+ if (original) break;
323
+ if (attempt < deleteLookupRetries) {
324
+ log.info(
325
+ {
326
+ conversationExternalId,
327
+ deletedMessageTs,
328
+ attempt: attempt + 1,
329
+ maxAttempts: deleteLookupRetries,
330
+ },
331
+ "Original message not linked yet, retrying delete lookup",
332
+ );
333
+ await new Promise((resolve) =>
334
+ setTimeout(resolve, deleteLookupDelayMs),
335
+ );
336
+ }
337
+ }
338
+
339
+ if (!original) {
340
+ log.debug(
341
+ { conversationExternalId, deletedMessageTs },
342
+ "No stored message found for Slack delete after retries; ignoring",
343
+ );
344
+ return Response.json({ accepted: true, deleted: false });
345
+ }
346
+
347
+ // Merge deletedAt into the existing slackMeta sub-key. If the row has
348
+ // no slackMeta (legacy pre-upgrade row), skip — the renderer's flat
349
+ // fallback ignores deletedAt for those rows anyway, and synthesizing
350
+ // a partial slackMeta here would produce metadata that fails
351
+ // readSlackMetadata validation.
352
+ const row = getMessageById(original.messageId);
353
+ if (!row?.metadata) {
354
+ log.debug(
355
+ {
356
+ conversationExternalId,
357
+ deletedMessageTs,
358
+ messageId: original.messageId,
359
+ },
360
+ "Stored Slack message has no metadata; skipping delete marker",
361
+ );
362
+ return Response.json({ accepted: true, deleted: false });
363
+ }
364
+
365
+ let parentMetadata: Record<string, unknown>;
366
+ try {
367
+ const parsed = JSON.parse(row.metadata) as unknown;
368
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
369
+ parentMetadata = parsed as Record<string, unknown>;
370
+ } else {
371
+ parentMetadata = {};
372
+ }
373
+ } catch {
374
+ log.debug(
375
+ {
376
+ conversationExternalId,
377
+ deletedMessageTs,
378
+ messageId: original.messageId,
379
+ },
380
+ "Failed to parse stored metadata; skipping delete marker",
381
+ );
382
+ return Response.json({ accepted: true, deleted: false });
383
+ }
384
+
385
+ const existingSlackMeta =
386
+ typeof parentMetadata.slackMeta === "string"
387
+ ? parentMetadata.slackMeta
388
+ : null;
389
+
390
+ if (!existingSlackMeta) {
391
+ log.debug(
392
+ {
393
+ conversationExternalId,
394
+ deletedMessageTs,
395
+ messageId: original.messageId,
396
+ },
397
+ "Stored Slack message has no slackMeta; skipping delete marker",
398
+ );
399
+ return Response.json({ accepted: true, deleted: false });
400
+ }
401
+
402
+ const updatedSlackMeta = mergeSlackMetadata(existingSlackMeta, {
403
+ deletedAt: Date.now(),
404
+ });
405
+
406
+ // updateMessageMetadata performs a shallow merge over the parent
407
+ // metadata, replacing only `slackMeta` and leaving sibling keys
408
+ // (channel, interface, provenance, etc.) untouched. Content column
409
+ // is intentionally not updated.
410
+ updateMessageMetadata(original.messageId, { slackMeta: updatedSlackMeta });
411
+
412
+ log.info(
413
+ {
414
+ conversationExternalId,
415
+ deletedMessageTs,
416
+ messageId: original.messageId,
417
+ },
418
+ "Marked Slack message as deleted",
419
+ );
420
+
421
+ return Response.json({
422
+ accepted: true,
423
+ deleted: true,
424
+ messageId: original.messageId,
425
+ });
426
+ }
427
+
241
428
  if (hasAttachments) {
242
429
  const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds);
243
430
  if (resolved.length !== attachmentIds.length) {
@@ -369,6 +556,119 @@ export async function handleChannelInbound(
369
556
  });
370
557
  }
371
558
 
559
+ // ── Slack reaction handling ──
560
+ // Reactions arrive as regular `SlackInboundEvent`s with `callbackData`
561
+ // prefixed `reaction:` (added) or `reaction_removed:` (removed).
562
+ //
563
+ // Two paths from here:
564
+ // 1. Guardian approval-by-reaction. A `reaction:` (added) event from
565
+ // the guardian on an active approval prompt is consumed by
566
+ // `handleApprovalInterception` to apply the decision. In that case
567
+ // we do NOT persist the reaction as a transcript line — resolved
568
+ // guardian approval reactions have no transcript representation.
569
+ // 2. All other reactions (non-guardian, no pending approval, stale,
570
+ // and any `reaction_removed:` event regardless of actor) fall
571
+ // through to `persistSlackReactionAsMessage` so the chronological
572
+ // renderer (PR 18) can surface them inline. Reactions never
573
+ // trigger an agent response, so we short-circuit before
574
+ // escalation and agent-loop dispatch in both cases.
575
+ if (isSlackReactionEvent(body)) {
576
+ // Approval interception runs only for reactions (added) — `reaction_removed`
577
+ // never expresses an approval intent, so un-reacting is left as a pure
578
+ // transcript signal. Gated by the same `replyCallbackUrl && !duplicate`
579
+ // preconditions used by the standard approval interception call below.
580
+ const isReactionAdded = body.callbackData?.startsWith("reaction:") === true;
581
+ if (isReactionAdded && replyCallbackUrl && !result.duplicate) {
582
+ const trustCtxForReaction: TrustContext = resolveTrustContext({
583
+ assistantId: canonicalAssistantId,
584
+ sourceChannel,
585
+ conversationExternalId,
586
+ actorExternalId: rawSenderId,
587
+ actorUsername: body.actorUsername,
588
+ actorDisplayName: body.actorDisplayName,
589
+ });
590
+
591
+ const approvalMessageTs =
592
+ typeof sourceMetadata?.messageId === "string"
593
+ ? sourceMetadata.messageId
594
+ : undefined;
595
+
596
+ const reactionApprovalResult = await handleApprovalInterception({
597
+ conversationId: result.conversationId,
598
+ callbackData: body.callbackData,
599
+ content: trimmedContent,
600
+ conversationExternalId,
601
+ sourceChannel,
602
+ actorExternalId: canonicalSenderId ?? rawSenderId,
603
+ replyCallbackUrl,
604
+ bearerToken: mintBearerToken(),
605
+ trustCtx: trustCtxForReaction,
606
+ assistantId: canonicalAssistantId,
607
+ approvalCopyGenerator,
608
+ approvalConversationGenerator,
609
+ approvalMessageTs,
610
+ });
611
+
612
+ // A real guardian decision was applied — short-circuit and skip the
613
+ // reaction-persistence path so we do not double-record it as a
614
+ // transcript line. All other interception outcomes (stale_ignored,
615
+ // non-guardian, no pending approval) fall through to persistence.
616
+ if (reactionApprovalResult.type === "guardian_decision_applied") {
617
+ return Response.json({
618
+ accepted: true,
619
+ duplicate: false,
620
+ eventId: result.eventId,
621
+ approval: reactionApprovalResult.type,
622
+ });
623
+ }
624
+ }
625
+
626
+ const reactedMessageTs =
627
+ typeof sourceMetadata?.messageId === "string"
628
+ ? sourceMetadata.messageId
629
+ : undefined;
630
+ if (!reactedMessageTs) {
631
+ log.debug(
632
+ { conversationId: result.conversationId, eventId: result.eventId },
633
+ "Skipping reaction persistence: missing sourceMetadata.messageId",
634
+ );
635
+ return Response.json({
636
+ accepted: result.accepted,
637
+ duplicate: result.duplicate,
638
+ eventId: result.eventId,
639
+ });
640
+ }
641
+
642
+ const threadTs =
643
+ typeof sourceMetadata?.threadId === "string"
644
+ ? sourceMetadata.threadId
645
+ : undefined;
646
+
647
+ try {
648
+ await persistSlackReactionAsMessage({
649
+ conversationId: result.conversationId,
650
+ conversationExternalId,
651
+ eventId: result.eventId,
652
+ callbackData: body.callbackData!,
653
+ actorDisplayName: body.actorDisplayName,
654
+ threadTs,
655
+ reactedMessageTs,
656
+ duplicate: result.duplicate,
657
+ });
658
+ } catch (err) {
659
+ log.error(
660
+ { err, conversationId: result.conversationId, eventId: result.eventId },
661
+ "Failed to persist Slack reaction event",
662
+ );
663
+ }
664
+
665
+ return Response.json({
666
+ accepted: result.accepted,
667
+ duplicate: result.duplicate,
668
+ eventId: result.eventId,
669
+ });
670
+ }
671
+
372
672
  // ── Ingress escalation ──
373
673
  const escalationResponse = handleEscalationIntercept({
374
674
  resolvedMember,
@@ -606,7 +906,13 @@ export async function handleChannelInbound(
606
906
  // of whether content/attachments are present — callback payloads always
607
907
  // have non-empty content (normalize.ts sets message.content to cbq.data),
608
908
  // so checking for empty content alone would miss stale callbacks.
609
- if (hasCallbackData) {
909
+ //
910
+ // Reaction events (`reaction:` / `reaction_removed:`) are persisted by
911
+ // the earlier `isSlackReactionEvent` branch and never reach here; guard
912
+ // explicitly so a future refactor can't let a reaction ts drive a
913
+ // "This approval request has been resolved." edit that would clobber
914
+ // the user's reacted-to message.
915
+ if (hasCallbackData && !isSlackReactionEvent(body)) {
610
916
  // Record seen signal even for stale callbacks — the user still interacted
611
917
  if (sourceChannel === "telegram" || sourceChannel === "slack") {
612
918
  try {
@@ -698,6 +1004,93 @@ export async function handleChannelInbound(
698
1004
  heartbeatService?.resetTimer();
699
1005
  }
700
1006
 
1007
+ // Slack inbound metadata captured for thread-aware persistence. The
1008
+ // gateway forwards `thread_ts` under `sourceMetadata.threadId` and the
1009
+ // message's own ts under `sourceMetadata.messageId`. Persistence turns
1010
+ // this into a `slackMeta` sub-object in the row's metadata column so
1011
+ // the chronological renderer can reconstruct thread structure without
1012
+ // re-fetching from Slack.
1013
+ const slackThreadTs =
1014
+ sourceChannel === "slack" &&
1015
+ typeof sourceMetadata?.threadId === "string"
1016
+ ? sourceMetadata.threadId
1017
+ : undefined;
1018
+ const slackInbound =
1019
+ sourceChannel === "slack"
1020
+ ? {
1021
+ channelId: conversationExternalId,
1022
+ channelTs: sourceMessageId ?? externalMessageId,
1023
+ ...(slackThreadTs ? { threadTs: slackThreadTs } : {}),
1024
+ ...((body.actorDisplayName ?? body.actorUsername)
1025
+ ? {
1026
+ displayName: body.actorDisplayName ?? body.actorUsername!,
1027
+ }
1028
+ : {}),
1029
+ }
1030
+ : undefined;
1031
+
1032
+ // Account identifier threaded into backfill so `resolveConnection()`
1033
+ // can pick the right workspace in multi-account setups. Best-effort:
1034
+ // the gateway forwards `sourceMetadata.account` when it knows which
1035
+ // Slack workspace the event came from; when absent, both helpers
1036
+ // fall back to the default-active connection.
1037
+ const slackAccount =
1038
+ sourceChannel === "slack" &&
1039
+ typeof sourceMetadata?.account === "string" &&
1040
+ sourceMetadata.account.length > 0
1041
+ ? sourceMetadata.account
1042
+ : undefined;
1043
+
1044
+ // ── DM cold-start backfill ──
1045
+ // First time a Slack DM lands in a conversation that has fewer than
1046
+ // SLACK_DM_BACKFILL_WARM_THRESHOLD stored slackMeta messages, fetch a
1047
+ // window of recent history so the agent sees prior context. One-shot:
1048
+ // once persistence warms up past the threshold, subsequent DMs no
1049
+ // longer trigger backfill. Failures are non-fatal — the new message
1050
+ // proceeds without backfilled history.
1051
+ if (sourceChannel === "slack" && sourceChatType === "im") {
1052
+ await tryBackfillSlackDmIfCold({
1053
+ conversationId: result.conversationId,
1054
+ channelId: conversationExternalId,
1055
+ account: slackAccount,
1056
+ // Exclude the just-arrived webhook message from the history window —
1057
+ // the normal inbound persistence path writes it separately, so
1058
+ // including it here would produce duplicate user turns.
1059
+ latestTs: slackInbound?.channelTs,
1060
+ });
1061
+ }
1062
+
1063
+ // ── Thread-ancestor backfill ──
1064
+ // When a Slack reply arrives for a thread the daemon never saw the
1065
+ // parent of, fetch the thread's recent history from Slack and persist
1066
+ // the missing messages so the chronological renderer (PR 18) has the
1067
+ // full conversation. Awaited (mirrors the DM cold-start path above)
1068
+ // so the agent loop dispatched immediately afterwards observes the
1069
+ // backfilled parent — without this, `loadSlackChronologicalMessages`
1070
+ // can race the persist and miss thread context. Backfill is bounded
1071
+ // (parent + ~50 messages) and the agent latency is dominated by the
1072
+ // LLM call, so the added latency is negligible. Failures are
1073
+ // swallowed inside the helper so they never block dispatch.
1074
+ if (slackThreadTs) {
1075
+ await triggerSlackThreadBackfillIfNeeded({
1076
+ conversationId: result.conversationId,
1077
+ channelId: conversationExternalId,
1078
+ threadTs: slackThreadTs,
1079
+ excludeChannelTs: slackInbound?.channelTs,
1080
+ account: slackAccount,
1081
+ });
1082
+ }
1083
+
1084
+ // Wrap non-guardian inbound content in external_content boundaries so
1085
+ // the model can distinguish external channel messages from instructions.
1086
+ const contentForProcessing =
1087
+ trustCtx.trustClass !== "guardian"
1088
+ ? wrapUntrustedContent(trimmedContent, {
1089
+ source: "webhook",
1090
+ sourceDetail: trustCtx.requesterIdentifier,
1091
+ })
1092
+ : trimmedContent;
1093
+
701
1094
  // Fire-and-forget: process the message and deliver the reply in the background.
702
1095
  // The HTTP response returns immediately so the gateway webhook is not blocked.
703
1096
  // The onEvent callback in processMessage registers pending interactions, and
@@ -706,7 +1099,7 @@ export async function handleChannelInbound(
706
1099
  processMessage,
707
1100
  conversationId: result.conversationId,
708
1101
  eventId: result.eventId,
709
- content: trimmedContent,
1102
+ content: contentForProcessing,
710
1103
  attachmentIds: hasAttachments ? attachmentIds : undefined,
711
1104
  sourceChannel,
712
1105
  sourceInterface,
@@ -721,6 +1114,7 @@ export async function handleChannelInbound(
721
1114
  assistantId: canonicalAssistantId,
722
1115
  approvalCopyGenerator,
723
1116
  chatType: sourceChatType,
1117
+ slackInbound,
724
1118
  });
725
1119
  }
726
1120
  }
@@ -731,3 +1125,519 @@ export async function handleChannelInbound(
731
1125
  eventId: result.eventId,
732
1126
  });
733
1127
  }
1128
+
1129
+ /**
1130
+ * Detect a Slack reaction event by inspecting the inbound payload's
1131
+ * `callbackData` prefix. The gateway encodes reactions as a unified
1132
+ * `SlackInboundEvent` with `callbackData` of the form
1133
+ * `reaction:<emoji>` (added) or `reaction_removed:<emoji>` (removed) —
1134
+ * see `gateway/src/slack/normalize.ts`. This helper centralizes that
1135
+ * convention so the daemon can route reactions to a dedicated persistence
1136
+ * branch instead of the agent-response pipeline.
1137
+ */
1138
+ export function isSlackReactionEvent(body: {
1139
+ sourceChannel?: string;
1140
+ callbackData?: string;
1141
+ }): boolean {
1142
+ if (body.sourceChannel !== "slack") return false;
1143
+ const cb = body.callbackData;
1144
+ if (typeof cb !== "string") return false;
1145
+ return cb.startsWith("reaction:") || cb.startsWith("reaction_removed:");
1146
+ }
1147
+
1148
+ /**
1149
+ * Parse a reaction `callbackData` string into its op (added/removed) and
1150
+ * emoji name. Returns `null` when the input is not a reaction prefix or
1151
+ * when the emoji portion is empty.
1152
+ */
1153
+ export function parseSlackReactionCallbackData(
1154
+ callbackData: string,
1155
+ ): { op: "added" | "removed"; emoji: string } | null {
1156
+ let op: "added" | "removed";
1157
+ let emoji: string;
1158
+ if (callbackData.startsWith("reaction_removed:")) {
1159
+ op = "removed";
1160
+ emoji = callbackData.slice("reaction_removed:".length);
1161
+ } else if (callbackData.startsWith("reaction:")) {
1162
+ op = "added";
1163
+ emoji = callbackData.slice("reaction:".length);
1164
+ } else {
1165
+ return null;
1166
+ }
1167
+ if (emoji.length === 0) return null;
1168
+ return { op, emoji };
1169
+ }
1170
+
1171
+ /**
1172
+ * Persist a Slack reaction event as a `messages` row with `slackMeta`
1173
+ * envelope so the renderer can surface it inline in the chronological
1174
+ * transcript. Reactions do not trigger an agent response — the row is
1175
+ * written and the inbound event is linked, but the agent loop is not
1176
+ * dispatched.
1177
+ *
1178
+ * The caller is expected to have run `recordInbound` already so that
1179
+ * deduplication and conversation resolution have happened. Duplicate
1180
+ * inbound events are skipped here to keep persistence idempotent.
1181
+ */
1182
+ async function persistSlackReactionAsMessage(params: {
1183
+ conversationId: string;
1184
+ conversationExternalId: string;
1185
+ eventId: string;
1186
+ callbackData: string;
1187
+ actorDisplayName?: string;
1188
+ threadTs?: string;
1189
+ reactedMessageTs: string;
1190
+ duplicate: boolean;
1191
+ }): Promise<void> {
1192
+ if (params.duplicate) return;
1193
+
1194
+ const parsed = parseSlackReactionCallbackData(params.callbackData);
1195
+ if (!parsed) {
1196
+ log.debug(
1197
+ {
1198
+ conversationId: params.conversationId,
1199
+ callbackData: params.callbackData,
1200
+ },
1201
+ "Skipping reaction persistence: unparseable callbackData",
1202
+ );
1203
+ return;
1204
+ }
1205
+
1206
+ const slackMeta: SlackMessageMetadata = {
1207
+ source: "slack",
1208
+ channelId: params.conversationExternalId,
1209
+ channelTs: params.reactedMessageTs,
1210
+ eventKind: "reaction",
1211
+ ...(params.threadTs ? { threadTs: params.threadTs } : {}),
1212
+ ...(params.actorDisplayName
1213
+ ? { displayName: params.actorDisplayName }
1214
+ : {}),
1215
+ reaction: {
1216
+ emoji: parsed.emoji,
1217
+ targetChannelTs: params.reactedMessageTs,
1218
+ op: parsed.op,
1219
+ ...(params.actorDisplayName
1220
+ ? { actorDisplayName: params.actorDisplayName }
1221
+ : {}),
1222
+ },
1223
+ };
1224
+
1225
+ // Sentinel content — renderers (PR 18) read `slackMeta` to format the
1226
+ // reaction line; the literal text is never displayed to the model.
1227
+ const persisted = await addMessage(
1228
+ params.conversationId,
1229
+ "user",
1230
+ "[reaction]",
1231
+ { slackMeta: writeSlackMetadata(slackMeta) },
1232
+ { skipIndexing: true },
1233
+ );
1234
+ deliveryCrud.linkMessage(params.eventId, persisted.id);
1235
+ deliveryStatus.markProcessed(params.eventId);
1236
+ }
1237
+
1238
+ /**
1239
+ * Threshold of stored Slack-tagged messages below which a conversation is
1240
+ * considered "cold" and eligible for one-shot backfill. The number is
1241
+ * deliberately small but greater than 1 so a single sentinel row (e.g. a
1242
+ * stale reaction) does not disqualify a conversation that has no real
1243
+ * message history yet.
1244
+ */
1245
+ const SLACK_DM_BACKFILL_WARM_THRESHOLD = 3;
1246
+
1247
+ /**
1248
+ * Count messages in a conversation whose `metadata` carries a `slackMeta`
1249
+ * envelope, capped at the warm threshold. Delegates to the DB helper which
1250
+ * pushes both `LIKE` and `LIMIT` into SQL so a warm DM conversation never
1251
+ * scans beyond the threshold's worth of rows on the webhook critical path.
1252
+ */
1253
+ function countSlackMetaMessages(conversationId: string): number {
1254
+ return countMessagesWithSlackMeta(
1255
+ conversationId,
1256
+ SLACK_DM_BACKFILL_WARM_THRESHOLD,
1257
+ );
1258
+ }
1259
+
1260
+ /**
1261
+ * Build the set of `slackMeta.channelTs` values already stored on a
1262
+ * conversation. Used by both DM cold-start backfill and thread-ancestor
1263
+ * backfill to dedupe rows so a partial prior backfill (or a single message
1264
+ * that was already persisted via the live ingress path) does not double-write.
1265
+ */
1266
+ function readStoredSlackChannelTs(conversationId: string): Set<string> {
1267
+ const seen = new Set<string>();
1268
+ for (const row of getMessages(conversationId)) {
1269
+ if (!row.metadata) continue;
1270
+ let parent: Record<string, unknown> | null = null;
1271
+ try {
1272
+ const parsed = JSON.parse(row.metadata) as unknown;
1273
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1274
+ parent = parsed as Record<string, unknown>;
1275
+ }
1276
+ } catch {
1277
+ continue;
1278
+ }
1279
+ if (!parent) continue;
1280
+ const raw = parent.slackMeta;
1281
+ if (typeof raw !== "string") continue;
1282
+ const meta = readSlackMetadata(raw);
1283
+ // Only message rows represent stored Slack messages. Reaction rows carry
1284
+ // `channelTs` equal to the target message's ts, so including them would
1285
+ // make a reaction on a thread parent wrongly short-circuit ancestor
1286
+ // backfill (the parent itself may still be unseen).
1287
+ if (meta && meta.eventKind === "message") seen.add(meta.channelTs);
1288
+ }
1289
+ return seen;
1290
+ }
1291
+
1292
+ /**
1293
+ * Persist a single backfilled Slack message as a `messages` row with a
1294
+ * `slackMeta` envelope.
1295
+ *
1296
+ * Shared insertion point for any path that hydrates Slack history lazily
1297
+ * (DM cold-start backfill, thread-ancestor backfill, etc.). Role is derived
1298
+ * from `message.metadata.isBot` — bot-authored rows map to `"assistant"` so
1299
+ * our own prior replies (and any other bot traffic) are not rehydrated as
1300
+ * user turns, which would otherwise corrupt speaker attribution and make
1301
+ * the assistant treat its own outputs as new user input on later turns.
1302
+ * Caller is responsible for dedup checks before invoking; this helper
1303
+ * performs no idempotency check itself.
1304
+ */
1305
+ async function persistBackfilledSlackMessage(params: {
1306
+ conversationId: string;
1307
+ channelId: string;
1308
+ message: ProviderMessage;
1309
+ }): Promise<void> {
1310
+ const { message } = params;
1311
+ const slackMeta: SlackMessageMetadata = {
1312
+ source: "slack",
1313
+ channelId: params.channelId,
1314
+ channelTs: message.id,
1315
+ eventKind: "message",
1316
+ ...(message.threadId ? { threadTs: message.threadId } : {}),
1317
+ ...(message.sender?.name ? { displayName: message.sender.name } : {}),
1318
+ };
1319
+ const role = message.metadata?.isBot === true ? "assistant" : "user";
1320
+ await addMessage(params.conversationId, role, message.text ?? "", {
1321
+ slackMeta: writeSlackMetadata(slackMeta),
1322
+ });
1323
+ }
1324
+
1325
+ /**
1326
+ * In-memory map of in-flight DM cold-start backfills keyed by conversationId.
1327
+ * Concurrent inbound DMs to the same cold conversation share a single
1328
+ * backfill promise instead of each issuing their own Slack history fetch and
1329
+ * write — without this, two near-simultaneous DMs would both observe a cold
1330
+ * count, both fetch the same history window, and both insert duplicate rows
1331
+ * (channelTs lives inside a JSON metadata blob, so the DB has no uniqueness
1332
+ * constraint to fall back on).
1333
+ */
1334
+ const _dmBackfillInFlight = new Map<string, Promise<void>>();
1335
+
1336
+ /**
1337
+ * One-shot DM cold-start backfill. When a Slack DM lands in a conversation
1338
+ * with fewer than `SLACK_DM_BACKFILL_WARM_THRESHOLD` stored Slack-tagged
1339
+ * messages, fetch a window of recent history via `backfillDm` and persist
1340
+ * each returned message with a `slackMeta` envelope. Already-stored
1341
+ * messages (matched by `slackMeta.channelTs`) are skipped to keep the
1342
+ * operation idempotent across retries.
1343
+ *
1344
+ * Failure semantics: any error is logged at WARN and swallowed. The caller
1345
+ * proceeds with only the new message.
1346
+ */
1347
+ async function tryBackfillSlackDmIfCold(params: {
1348
+ conversationId: string;
1349
+ channelId: string;
1350
+ account?: string;
1351
+ latestTs?: string;
1352
+ }): Promise<void> {
1353
+ const existing = _dmBackfillInFlight.get(params.conversationId);
1354
+ if (existing) {
1355
+ await existing;
1356
+ return;
1357
+ }
1358
+ const promise = runBackfillSlackDmIfCold(params).finally(() => {
1359
+ _dmBackfillInFlight.delete(params.conversationId);
1360
+ });
1361
+ _dmBackfillInFlight.set(params.conversationId, promise);
1362
+ await promise;
1363
+ }
1364
+
1365
+ async function runBackfillSlackDmIfCold(params: {
1366
+ conversationId: string;
1367
+ channelId: string;
1368
+ account?: string;
1369
+ latestTs?: string;
1370
+ }): Promise<void> {
1371
+ try {
1372
+ const storedCount = countSlackMetaMessages(params.conversationId);
1373
+ if (storedCount >= SLACK_DM_BACKFILL_WARM_THRESHOLD) {
1374
+ return;
1375
+ }
1376
+
1377
+ // Pass the webhook message's ts as `before` (Slack's `latest`,
1378
+ // exclusive) so history never contains the message that's about to be
1379
+ // persisted by the live inbound path. Without this bound the just-arrived
1380
+ // DM would be written twice — once here and once via normal persistence —
1381
+ // producing duplicate user turns.
1382
+ const fetched = await backfillDm(params.channelId, {
1383
+ limit: 50,
1384
+ account: params.account,
1385
+ before: params.latestTs,
1386
+ });
1387
+ if (fetched.length === 0) {
1388
+ log.debug(
1389
+ { conversationId: params.conversationId, channelId: params.channelId },
1390
+ "DM backfill returned no messages",
1391
+ );
1392
+ return;
1393
+ }
1394
+
1395
+ const seen = readStoredSlackChannelTs(params.conversationId);
1396
+ let written = 0;
1397
+ // Slack's conversation.history returns most-recent first. Reverse so
1398
+ // rows insert in chronological order, giving stable createdAt ordering
1399
+ // and a transcript that reads correctly when the renderer joins on
1400
+ // monotonic createdAt.
1401
+ const ordered = [...fetched].reverse();
1402
+ for (const message of ordered) {
1403
+ if (seen.has(message.id)) continue;
1404
+ try {
1405
+ await persistBackfilledSlackMessage({
1406
+ conversationId: params.conversationId,
1407
+ channelId: params.channelId,
1408
+ message,
1409
+ });
1410
+ seen.add(message.id);
1411
+ written++;
1412
+ } catch (perRowErr) {
1413
+ log.warn(
1414
+ {
1415
+ err: perRowErr,
1416
+ conversationId: params.conversationId,
1417
+ channelId: params.channelId,
1418
+ channelTs: message.id,
1419
+ },
1420
+ "Failed to persist backfilled DM row; continuing",
1421
+ );
1422
+ }
1423
+ }
1424
+
1425
+ log.info(
1426
+ {
1427
+ conversationId: params.conversationId,
1428
+ channelId: params.channelId,
1429
+ fetched: fetched.length,
1430
+ written,
1431
+ },
1432
+ "DM cold-start backfill complete",
1433
+ );
1434
+ } catch (err) {
1435
+ // `channel_not_found` almost always means the resolved connection is
1436
+ // pointing at the wrong Slack workspace (a real config bug), so log it
1437
+ // at ERROR to match backfill's rethrow contract. Other failures
1438
+ // (timeout, auth, ratelimited, …) stay at WARN — they're expected
1439
+ // transient blips and the caller proceeds without backfilled history.
1440
+ const channelNotFound =
1441
+ err instanceof Error && /channel_not_found/i.test(err.message);
1442
+ const payload = {
1443
+ err,
1444
+ conversationId: params.conversationId,
1445
+ channelId: params.channelId,
1446
+ account: params.account,
1447
+ };
1448
+ if (channelNotFound) {
1449
+ log.error(
1450
+ payload,
1451
+ "DM cold-start backfill hit channel_not_found — connection likely points at the wrong Slack workspace",
1452
+ );
1453
+ } else {
1454
+ log.warn(
1455
+ payload,
1456
+ "DM cold-start backfill failed; proceeding without history",
1457
+ );
1458
+ }
1459
+ }
1460
+ }
1461
+
1462
+ // ---------------------------------------------------------------------------
1463
+ // Slack thread backfill on gap detection
1464
+ // ---------------------------------------------------------------------------
1465
+
1466
+ /**
1467
+ * In-memory TTL cache keyed by `<conversationId>:<threadTs>`. Tracks recent
1468
+ * thread-backfill triggers so a burst of replies inside the same Slack
1469
+ * thread (e.g. a guardian rapidly typing several lines) does not re-fetch
1470
+ * the same parent messages from Slack repeatedly. Entries naturally fall
1471
+ * out after the TTL — if the thread is still active later, a fresh
1472
+ * backfill becomes a cheap "are the parents already stored?" DB lookup
1473
+ * that short-circuits before the Slack API is touched.
1474
+ *
1475
+ * Exported only for tests; production callers should use
1476
+ * {@link triggerSlackThreadBackfillIfNeeded}.
1477
+ */
1478
+ export const _backfillTriggerCache = new Map<string, number>();
1479
+
1480
+ const BACKFILL_TRIGGER_TTL_MS = 10 * 60 * 1000; // 10 minutes
1481
+ const BACKFILL_TRIGGER_CACHE_MAX = 1_000;
1482
+
1483
+ function pruneBackfillCacheIfNeeded(): void {
1484
+ if (_backfillTriggerCache.size < BACKFILL_TRIGGER_CACHE_MAX) return;
1485
+ const now = Date.now();
1486
+ for (const [key, ts] of _backfillTriggerCache) {
1487
+ if (now - ts >= BACKFILL_TRIGGER_TTL_MS) {
1488
+ _backfillTriggerCache.delete(key);
1489
+ }
1490
+ }
1491
+ // If still over the cap after TTL sweep, drop the oldest entries (LRU-ish).
1492
+ if (_backfillTriggerCache.size >= BACKFILL_TRIGGER_CACHE_MAX) {
1493
+ const entries = [..._backfillTriggerCache.entries()].sort(
1494
+ (a, b) => a[1] - b[1],
1495
+ );
1496
+ const toRemove = entries.slice(
1497
+ 0,
1498
+ entries.length - BACKFILL_TRIGGER_CACHE_MAX + 1,
1499
+ );
1500
+ for (const [key] of toRemove) {
1501
+ _backfillTriggerCache.delete(key);
1502
+ }
1503
+ }
1504
+ }
1505
+
1506
+ function isBackfillRecentlyTriggered(cacheKey: string): boolean {
1507
+ const ts = _backfillTriggerCache.get(cacheKey);
1508
+ if (ts === undefined) return false;
1509
+ if (Date.now() - ts >= BACKFILL_TRIGGER_TTL_MS) {
1510
+ _backfillTriggerCache.delete(cacheKey);
1511
+ return false;
1512
+ }
1513
+ return true;
1514
+ }
1515
+
1516
+ /**
1517
+ * Lazily backfill missing Slack thread ancestors for an inbound thread reply.
1518
+ *
1519
+ * When a reply arrives for a thread the daemon has never seen (e.g. the bot
1520
+ * was just added to the channel, or the parent message pre-dates the
1521
+ * conversation), the daemon fetches the thread's recent history via
1522
+ * {@link backfillThread}, persists each unseen message as a `messages` row
1523
+ * with a `slackMeta` envelope, and skips duplicates whose `ts` already
1524
+ * appears in the conversation.
1525
+ *
1526
+ * Behavior contracts:
1527
+ * - **No-op when the parent is already stored.** Looks up the conversation's
1528
+ * messages and short-circuits if any row has `slackMeta.channelTs ===
1529
+ * threadTs`. This keeps subsequent replies in the same thread cheap.
1530
+ * - **TTL idempotency cache.** A 10-minute in-memory cache prevents bursts
1531
+ * of replies in the same thread from re-running the DB lookup or the
1532
+ * Slack API call.
1533
+ * - **Failure-tolerant.** Any error (Slack API failure, DB error, malformed
1534
+ * payload) is logged at `warn` and swallowed — the inbound turn must
1535
+ * never block on backfill.
1536
+ */
1537
+ export async function triggerSlackThreadBackfillIfNeeded(params: {
1538
+ conversationId: string;
1539
+ channelId: string;
1540
+ threadTs: string;
1541
+ /**
1542
+ * The inbound message's own `channelTs`. Pre-seeded into the dedup set so
1543
+ * this helper does not re-persist the just-received message when Slack's
1544
+ * `conversations.replies` returns it in the thread window. Necessary
1545
+ * because thread backfill runs concurrently with
1546
+ * `processChannelMessageInBackground`, so the inbound row may not yet be
1547
+ * in the DB when `readStoredSlackChannelTs` snapshots the conversation.
1548
+ */
1549
+ excludeChannelTs?: string;
1550
+ /**
1551
+ * OAuth account identifier used to disambiguate which Slack workspace the
1552
+ * backfill should read from in multi-account setups. Passed through to
1553
+ * `backfillThread` → `resolveConnection`. Best-effort: if omitted, the
1554
+ * resolver falls back to the default-active connection.
1555
+ */
1556
+ account?: string;
1557
+ }): Promise<void> {
1558
+ const { conversationId, channelId, threadTs, excludeChannelTs, account } =
1559
+ params;
1560
+ const cacheKey = `${conversationId}:${threadTs}`;
1561
+
1562
+ try {
1563
+ if (isBackfillRecentlyTriggered(cacheKey)) {
1564
+ return;
1565
+ }
1566
+
1567
+ const storedChannelTs = readStoredSlackChannelTs(conversationId);
1568
+ if (excludeChannelTs) storedChannelTs.add(excludeChannelTs);
1569
+ if (storedChannelTs.has(threadTs)) {
1570
+ // Parent is already in the conversation; mark the cache so a burst of
1571
+ // replies in this thread does not redo the DB scan for each one.
1572
+ _backfillTriggerCache.set(cacheKey, Date.now());
1573
+ pruneBackfillCacheIfNeeded();
1574
+ return;
1575
+ }
1576
+
1577
+ // Mark the trigger before issuing the network call. Doing this first
1578
+ // means a second concurrent reply in the same thread short-circuits
1579
+ // immediately even while the first call is still awaiting the Slack
1580
+ // API. The cost is a slightly larger window where a transient Slack
1581
+ // failure suppresses a retry, which the next reply outside the TTL
1582
+ // (or a daemon restart) will re-attempt anyway.
1583
+ _backfillTriggerCache.set(cacheKey, Date.now());
1584
+ pruneBackfillCacheIfNeeded();
1585
+
1586
+ const fetched = await backfillThread(channelId, threadTs, { account });
1587
+ if (fetched.length === 0) {
1588
+ log.debug(
1589
+ { conversationId, channelId, threadTs },
1590
+ "Slack thread backfill returned no messages",
1591
+ );
1592
+ return;
1593
+ }
1594
+
1595
+ let persisted = 0;
1596
+ for (const message of fetched) {
1597
+ if (!message.id) continue;
1598
+ if (storedChannelTs.has(message.id)) continue;
1599
+ try {
1600
+ await persistBackfilledSlackMessage({
1601
+ conversationId,
1602
+ channelId,
1603
+ message,
1604
+ });
1605
+ storedChannelTs.add(message.id);
1606
+ persisted++;
1607
+ } catch (err) {
1608
+ log.warn(
1609
+ { err, conversationId, channelId, threadTs, channelTs: message.id },
1610
+ "Failed to persist backfilled Slack thread message",
1611
+ );
1612
+ }
1613
+ }
1614
+
1615
+ log.info(
1616
+ {
1617
+ conversationId,
1618
+ channelId,
1619
+ threadTs,
1620
+ persisted,
1621
+ fetched: fetched.length,
1622
+ },
1623
+ "Slack thread backfill persisted ancestor messages",
1624
+ );
1625
+ } catch (err) {
1626
+ // `channel_not_found` almost always means the resolved connection is
1627
+ // pointing at the wrong Slack workspace (a real config bug), so log it
1628
+ // at ERROR to match backfill's rethrow contract. Other failures
1629
+ // (timeout, auth, ratelimited, …) stay at WARN — they're expected
1630
+ // transient blips and dispatch proceeds without the ancestors.
1631
+ const channelNotFound =
1632
+ err instanceof Error && /channel_not_found/i.test(err.message);
1633
+ const payload = { err, conversationId, channelId, threadTs, account };
1634
+ if (channelNotFound) {
1635
+ log.error(
1636
+ payload,
1637
+ "Slack thread backfill hit channel_not_found — connection likely points at the wrong Slack workspace",
1638
+ );
1639
+ } else {
1640
+ log.warn(payload, "Slack thread backfill failed; proceeding without it");
1641
+ }
1642
+ }
1643
+ }