@vellumai/assistant 0.7.0 → 0.7.2

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 (989) hide show
  1. package/ARCHITECTURE.md +38 -56
  2. package/Dockerfile +2 -0
  3. package/README.md +3 -4
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +88 -142
  5. package/bun.lock +29 -26
  6. package/docs/architecture/security.md +38 -16
  7. package/docs/plugins.md +7 -9
  8. package/knip.json +2 -0
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
  10. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +39 -1
  11. package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
  12. package/node_modules/@vellumai/service-contracts/package.json +2 -0
  13. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
  14. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
  15. package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
  16. package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
  17. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
  18. package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +1 -5
  19. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +9 -5
  20. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -16
  21. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +1 -9
  22. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +12 -12
  23. package/node_modules/@vellumai/slack-text/bun.lock +24 -0
  24. package/node_modules/@vellumai/slack-text/package.json +18 -0
  25. package/node_modules/@vellumai/slack-text/src/index.test.ts +153 -0
  26. package/node_modules/@vellumai/slack-text/src/index.ts +235 -0
  27. package/node_modules/@vellumai/slack-text/tsconfig.json +20 -0
  28. package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
  29. package/node_modules/@vellumai/twilio-client/package.json +18 -0
  30. package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
  31. package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
  32. package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
  33. package/openapi.yaml +869 -129
  34. package/package.json +8 -3
  35. package/scripts/generate-openapi.ts +16 -111
  36. package/src/__tests__/agent-wake-override-profile.test.ts +23 -1
  37. package/src/__tests__/anthropic-provider.test.ts +56 -13
  38. package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
  39. package/src/__tests__/app-bundler.test.ts +170 -1
  40. package/src/__tests__/app-control-flow.test.ts +374 -0
  41. package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
  42. package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
  43. package/src/__tests__/app-conversation-ids-backfill.test.ts +278 -0
  44. package/src/__tests__/app-conversation-ids.test.ts +151 -0
  45. package/src/__tests__/app-executors.test.ts +30 -43
  46. package/src/__tests__/approval-cascade.test.ts +0 -15
  47. package/src/__tests__/approval-routes-http.test.ts +29 -23
  48. package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
  49. package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
  50. package/src/__tests__/assistant-event-hub.test.ts +235 -79
  51. package/src/__tests__/assistant-event.test.ts +10 -5
  52. package/src/__tests__/assistant-events-sse-hardening.test.ts +44 -17
  53. package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -36
  54. package/src/__tests__/background-shell-host-bash.test.ts +46 -56
  55. package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
  56. package/src/__tests__/btw-routes.test.ts +13 -4
  57. package/src/__tests__/call-controller.test.ts +50 -2
  58. package/src/__tests__/call-domain.test.ts +0 -2
  59. package/src/__tests__/call-routes-http.test.ts +0 -2
  60. package/src/__tests__/call-site-routing-provider.test.ts +193 -0
  61. package/src/__tests__/channel-approval-routes.test.ts +10 -296
  62. package/src/__tests__/channel-approvals.test.ts +25 -17
  63. package/src/__tests__/channel-guardian.test.ts +100 -146
  64. package/src/__tests__/channel-readiness-service.test.ts +59 -1
  65. package/src/__tests__/checker.test.ts +23 -38
  66. package/src/__tests__/compact-event-conversation-id-guard.test.ts +50 -0
  67. package/src/__tests__/compaction-events.test.ts +2 -0
  68. package/src/__tests__/config-loader-backfill.test.ts +90 -155
  69. package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
  70. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  71. package/src/__tests__/config-schema.test.ts +6 -48
  72. package/src/__tests__/config-set-platform-guard.test.ts +48 -4
  73. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
  74. package/src/__tests__/config-watcher.test.ts +14 -2
  75. package/src/__tests__/connection-policy.test.ts +1 -52
  76. package/src/__tests__/contacts-write.test.ts +2 -64
  77. package/src/__tests__/context-image-dimensions.test.ts +1 -1
  78. package/src/__tests__/context-search-memory-source.test.ts +120 -1
  79. package/src/__tests__/context-search-memory-v2-source.test.ts +383 -0
  80. package/src/__tests__/context-search-pkb-source.test.ts +49 -0
  81. package/src/__tests__/context-search-workspace-source.test.ts +9 -22
  82. package/src/__tests__/context-window-manager.test.ts +46 -0
  83. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  84. package/src/__tests__/conversation-agent-loop-overflow.test.ts +102 -29
  85. package/src/__tests__/conversation-agent-loop.test.ts +980 -13
  86. package/src/__tests__/conversation-analysis-routes.test.ts +12 -10
  87. package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
  88. package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
  89. package/src/__tests__/conversation-attention-telegram.test.ts +11 -3
  90. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -291
  91. package/src/__tests__/conversation-history-web-search.test.ts +4 -3
  92. package/src/__tests__/conversation-inference-profile-route.test.ts +12 -23
  93. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  94. package/src/__tests__/conversation-lifecycle.test.ts +40 -4
  95. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
  96. package/src/__tests__/conversation-process-callsite.test.ts +79 -2
  97. package/src/__tests__/conversation-queue.test.ts +3 -8
  98. package/src/__tests__/conversation-routes-disk-view.test.ts +7 -161
  99. package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -104
  100. package/src/__tests__/conversation-routes-slash-commands.test.ts +76 -66
  101. package/src/__tests__/conversation-runtime-assembly.test.ts +257 -3
  102. package/src/__tests__/conversation-slash-commands.test.ts +24 -8
  103. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  104. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  105. package/src/__tests__/conversation-starter-routes.test.ts +79 -2
  106. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
  107. package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -0
  108. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -5
  109. package/src/__tests__/conversation-surfaces-standalone.test.ts +18 -14
  110. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -2
  111. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +8 -46
  112. package/src/__tests__/conversation-usage.test.ts +253 -3
  113. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
  114. package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
  115. package/src/__tests__/credential-execution-shell-lockdown.test.ts +0 -39
  116. package/src/__tests__/credential-health-service.test.ts +68 -0
  117. package/src/__tests__/credential-security-e2e.test.ts +4 -3
  118. package/src/__tests__/credential-security-invariants.test.ts +1 -5
  119. package/src/__tests__/credential-token-resolver.test.ts +180 -0
  120. package/src/__tests__/credentials-cli.test.ts +5 -12
  121. package/src/__tests__/cu-unified-flow.test.ts +206 -27
  122. package/src/__tests__/daemon-assistant-events.test.ts +34 -21
  123. package/src/__tests__/daemon-credential-client.test.ts +102 -17
  124. package/src/__tests__/db-connection-isolation.test.ts +125 -0
  125. package/src/__tests__/db-migration-rollback.test.ts +101 -0
  126. package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
  127. package/src/__tests__/db-slack-compaction-watermark-migration.test.ts +169 -0
  128. package/src/__tests__/deterministic-verification-control-plane.test.ts +7 -80
  129. package/src/__tests__/document-conversations.test.ts +332 -0
  130. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  131. package/src/__tests__/embedding-managed-proxy-selection.test.ts +2 -2
  132. package/src/__tests__/emit-event-signal.test.ts +4 -6
  133. package/src/__tests__/events-client-registration.test.ts +193 -49
  134. package/src/__tests__/filing-service.test.ts +58 -7
  135. package/src/__tests__/first-greeting.test.ts +156 -150
  136. package/src/__tests__/fixtures/mock-chrome-extension.ts +108 -66
  137. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  138. package/src/__tests__/get-skill-detail-audit.test.ts +3 -8
  139. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  140. package/src/__tests__/guardian-dispatch.test.ts +1 -1
  141. package/src/__tests__/guardian-grant-minting.test.ts +7 -2
  142. package/src/__tests__/guardian-routing-invariants.test.ts +7 -2
  143. package/src/__tests__/guardian-routing-state.test.ts +1 -1
  144. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
  145. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +30 -11
  146. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +2 -84
  147. package/src/__tests__/headless-browser-mode.test.ts +4 -9
  148. package/src/__tests__/headless-browser-navigate.test.ts +21 -20
  149. package/src/__tests__/heartbeat-service.test.ts +1007 -8
  150. package/src/__tests__/helpers/call-route-handler.ts +7 -1
  151. package/src/__tests__/helpers/channel-test-adapter.ts +2 -2
  152. package/src/__tests__/helpers/create-guardian-binding.ts +91 -0
  153. package/src/__tests__/host-app-control-proxy.test.ts +602 -0
  154. package/src/__tests__/host-app-control-routes.test.ts +263 -0
  155. package/src/__tests__/host-bash-proxy.test.ts +270 -147
  156. package/src/__tests__/host-bash-routes.test.ts +294 -0
  157. package/src/__tests__/host-browser-proxy.test.ts +126 -198
  158. package/src/__tests__/host-browser-routes.test.ts +50 -54
  159. package/src/__tests__/host-cu-proxy.test.ts +78 -144
  160. package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
  161. package/src/__tests__/host-file-edit-tool.test.ts +47 -1
  162. package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
  163. package/src/__tests__/host-file-proxy.test.ts +62 -122
  164. package/src/__tests__/host-file-read-tool.test.ts +59 -21
  165. package/src/__tests__/host-file-routes-targeted.test.ts +262 -0
  166. package/src/__tests__/host-file-write-tool.test.ts +42 -1
  167. package/src/__tests__/host-proxy-base.test.ts +312 -0
  168. package/src/__tests__/host-shell-tool.test.ts +53 -70
  169. package/src/__tests__/host-transfer-pending-interactions.test.ts +2 -18
  170. package/src/__tests__/host-transfer-proxy-targeted.test.ts +583 -0
  171. package/src/__tests__/host-transfer-proxy.test.ts +145 -56
  172. package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
  173. package/src/__tests__/http-user-message-parity.test.ts +1 -6
  174. package/src/__tests__/identity-intro-cache.test.ts +29 -0
  175. package/src/__tests__/identity-routes.test.ts +103 -1
  176. package/src/__tests__/inbound-slack-persistence.test.ts +31 -0
  177. package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
  178. package/src/__tests__/injector-chain.test.ts +10 -5
  179. package/src/__tests__/injector-pkb-v2-silenced.test.ts +124 -0
  180. package/src/__tests__/inline-command-runner.test.ts +0 -67
  181. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -13
  182. package/src/__tests__/install-skill-routing.test.ts +1 -13
  183. package/src/__tests__/integration-status.test.ts +85 -5
  184. package/src/__tests__/intent-routing.test.ts +0 -1
  185. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
  186. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
  187. package/src/__tests__/llm-callsite-catalog.test.ts +34 -0
  188. package/src/__tests__/llm-catalog-parity.test.ts +90 -0
  189. package/src/__tests__/llm-context-resolution.test.ts +180 -0
  190. package/src/__tests__/llm-resolver.test.ts +80 -12
  191. package/src/__tests__/llm-usage-store.test.ts +269 -4
  192. package/src/__tests__/log-export-routes.test.ts +89 -0
  193. package/src/__tests__/managed-profile-guard.test.ts +225 -0
  194. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -11
  195. package/src/__tests__/manual-token-reconciliation.test.ts +334 -0
  196. package/src/__tests__/mcp-auth-routes.test.ts +197 -0
  197. package/src/__tests__/mcp-cli.test.ts +338 -2
  198. package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
  199. package/src/__tests__/memory-v2-static-injector.test.ts +95 -0
  200. package/src/__tests__/migration-cross-version-compatibility.test.ts +197 -291
  201. package/src/__tests__/migration-export-http.test.ts +33 -26
  202. package/src/__tests__/migration-export-streaming.test.ts +18 -10
  203. package/src/__tests__/migration-export-to-gcs.test.ts +49 -9
  204. package/src/__tests__/migration-import-commit-http.test.ts +172 -21
  205. package/src/__tests__/migration-import-from-gcs.test.ts +50 -9
  206. package/src/__tests__/migration-import-from-url.test.ts +20 -6
  207. package/src/__tests__/migration-import-preflight-http.test.ts +95 -95
  208. package/src/__tests__/migration-parity-persistence.test.ts +62 -25
  209. package/src/__tests__/migration-transport.test.ts +115 -23
  210. package/src/__tests__/migration-validate-http.test.ts +105 -80
  211. package/src/__tests__/migration-wizard.test.ts +133 -27
  212. package/src/__tests__/mock-gateway-ipc.ts +1 -0
  213. package/src/__tests__/non-member-access-request.test.ts +1 -1
  214. package/src/__tests__/notification-guardian-path.test.ts +1 -1
  215. package/src/__tests__/oauth-cli.test.ts +0 -2
  216. package/src/__tests__/oauth-store.test.ts +19 -0
  217. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
  218. package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
  219. package/src/__tests__/platform-bash-auto-approve.test.ts +26 -21
  220. package/src/__tests__/prechat-onboarding-contract.test.ts +34 -8
  221. package/src/__tests__/pricing.test.ts +68 -4
  222. package/src/__tests__/process-message-background-slack.test.ts +333 -0
  223. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  224. package/src/__tests__/provider-managed-proxy-integration.test.ts +153 -17
  225. package/src/__tests__/provider-send-message-override-profile.test.ts +50 -0
  226. package/src/__tests__/provider-usage-tracking.test.ts +208 -0
  227. package/src/__tests__/public-ingress-urls.test.ts +97 -0
  228. package/src/__tests__/reaction-persistence.test.ts +9 -6
  229. package/src/__tests__/rebind-secrets-screen.test.ts +53 -16
  230. package/src/__tests__/recording-handler.test.ts +64 -81
  231. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +4 -3
  232. package/src/__tests__/relay-server.test.ts +18 -13
  233. package/src/__tests__/require-fresh-approval.test.ts +13 -23
  234. package/src/__tests__/retry-backoff.test.ts +87 -0
  235. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
  236. package/src/__tests__/runtime-events-sse-parity.test.ts +3 -4
  237. package/src/__tests__/runtime-events-sse.test.ts +13 -18
  238. package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
  239. package/src/__tests__/schedule-retry.test.ts +715 -0
  240. package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
  241. package/src/__tests__/search-skills-unified.test.ts +9 -15
  242. package/src/__tests__/secret-ingress-cli.test.ts +2 -5
  243. package/src/__tests__/secret-ingress-http.test.ts +1 -4
  244. package/src/__tests__/secret-onetime-send.test.ts +4 -2
  245. package/src/__tests__/secret-prompt-log-hygiene.test.ts +24 -7
  246. package/src/__tests__/secret-prompter-channel-fallback.test.ts +42 -47
  247. package/src/__tests__/secret-response-routing.test.ts +29 -15
  248. package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -1
  249. package/src/__tests__/secret-scanner.test.ts +2 -545
  250. package/src/__tests__/send-endpoint-busy.test.ts +12 -24
  251. package/src/__tests__/settings-routes.test.ts +1 -1
  252. package/src/__tests__/shell-credential-ref.test.ts +0 -8
  253. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -57
  254. package/src/__tests__/skill-feature-flags.test.ts +43 -41
  255. package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
  256. package/src/__tests__/skill-load-inline-command.test.ts +0 -51
  257. package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
  258. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  259. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -12
  260. package/src/__tests__/skill-tool-factory.test.ts +97 -0
  261. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -30
  262. package/src/__tests__/skills-files-catalog-fallback.test.ts +11 -17
  263. package/src/__tests__/slack-channel-config.test.ts +9 -14
  264. package/src/__tests__/slack-inbound-verification.test.ts +1 -62
  265. package/src/__tests__/subagent-fork-notifications.test.ts +57 -47
  266. package/src/__tests__/subagent-manager-notify.test.ts +70 -70
  267. package/src/__tests__/subagent-notify-parent.test.ts +80 -83
  268. package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
  269. package/src/__tests__/system-prompt.test.ts +115 -14
  270. package/src/__tests__/telegram-config.test.ts +0 -1
  271. package/src/__tests__/terminal-tools.test.ts +0 -89
  272. package/src/__tests__/test-preload.ts +8 -0
  273. package/src/__tests__/thread-backfill.test.ts +945 -31
  274. package/src/__tests__/tool-approval-handler.test.ts +3 -4
  275. package/src/__tests__/tool-audit-listener.test.ts +48 -0
  276. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -36
  277. package/src/__tests__/tool-execute-pipeline.test.ts +0 -7
  278. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -17
  279. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +9 -19
  280. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -8
  281. package/src/__tests__/tool-executor.test.ts +12 -20
  282. package/src/__tests__/tool-metrics-listener.test.ts +0 -35
  283. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  284. package/src/__tests__/tool-trace-listener.test.ts +0 -17
  285. package/src/__tests__/transfer-progress-screen.test.ts +63 -26
  286. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -149
  287. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -4
  288. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  289. package/src/__tests__/tts-catalog-parity.test.ts +16 -5
  290. package/src/__tests__/twilio-config.test.ts +3 -16
  291. package/src/__tests__/twilio-routes.test.ts +3 -5
  292. package/src/__tests__/twilio-validation.test.ts +93 -0
  293. package/src/__tests__/usage-attribution.test.ts +247 -0
  294. package/src/__tests__/usage-cli.test.ts +143 -0
  295. package/src/__tests__/usage-grouped-buckets.test.ts +155 -0
  296. package/src/__tests__/usage-routes.test.ts +150 -0
  297. package/src/__tests__/validation-results-screen.test.ts +39 -16
  298. package/src/__tests__/vbundle-pax-and-symlink.test.ts +12 -3
  299. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +47 -138
  300. package/src/__tests__/verification-control-plane-policy.test.ts +6 -11
  301. package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
  302. package/src/__tests__/voice-session-bridge.test.ts +5 -5
  303. package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
  304. package/src/__tests__/workspace-migration-062-drop-memory-v2-edges-json.test.ts +103 -0
  305. package/src/__tests__/workspace-migration-063-release-notes-dynamic-model-context.test.ts +77 -0
  306. package/src/__tests__/workspace-migration-064-unwind-main-agent-opus-seed.test.ts +225 -0
  307. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
  308. package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
  309. package/src/__tests__/workspace-migration-memory-v2-init.test.ts +8 -30
  310. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
  311. package/src/acp/index.ts +0 -15
  312. package/src/acp/session-manager.ts +37 -34
  313. package/src/agent/loop.ts +16 -1
  314. package/src/approvals/AGENTS.md +4 -0
  315. package/src/approvals/__tests__/guardian-feed-event.test.ts +10 -3
  316. package/src/approvals/guardian-request-resolvers.ts +10 -2
  317. package/src/backup/__tests__/paths.test.ts +0 -22
  318. package/src/backup/__tests__/restore.test.ts +94 -177
  319. package/src/backup/paths.ts +2 -15
  320. package/src/backup/restore.ts +107 -231
  321. package/src/browser-session/events.ts +0 -9
  322. package/src/bundler/app-bundler.ts +51 -3
  323. package/src/calls/call-store.ts +1 -34
  324. package/src/calls/guardian-question-copy.ts +0 -108
  325. package/src/calls/relay-server.ts +4 -68
  326. package/src/calls/twilio-config.ts +2 -17
  327. package/src/calls/twilio-rest.ts +31 -141
  328. package/src/calls/twilio-routes.ts +12 -13
  329. package/src/calls/voice-session-bridge.ts +7 -38
  330. package/src/channels/types.ts +8 -42
  331. package/src/cli/commands/__tests__/backup.test.ts +6 -277
  332. package/src/cli/commands/__tests__/cache.test.ts +152 -5
  333. package/src/cli/commands/__tests__/gateway.test.ts +288 -0
  334. package/src/cli/commands/__tests__/memory-v2.test.ts +18 -28
  335. package/src/cli/commands/__tests__/trust.test.ts +21 -387
  336. package/src/cli/commands/__tests__/webhooks.test.ts +0 -1
  337. package/src/cli/commands/backup.ts +6 -331
  338. package/src/cli/commands/cache-fs.ts +8 -0
  339. package/src/cli/commands/cache.ts +153 -82
  340. package/src/cli/commands/clients.ts +64 -7
  341. package/src/cli/commands/completions.ts +3 -3
  342. package/src/cli/commands/contacts.ts +304 -76
  343. package/src/cli/commands/conversations.ts +2 -5
  344. package/src/cli/commands/credentials.ts +15 -7
  345. package/src/cli/commands/domain.ts +66 -15
  346. package/src/cli/commands/gateway.ts +183 -0
  347. package/src/cli/commands/keys.ts +13 -7
  348. package/src/cli/commands/mcp.ts +116 -156
  349. package/src/cli/commands/memory-v2.ts +320 -53
  350. package/src/cli/commands/oauth/shared.ts +2 -29
  351. package/src/cli/commands/pending.ts +102 -0
  352. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  353. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
  354. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
  355. package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
  356. package/src/cli/commands/platform/disconnect.ts +5 -4
  357. package/src/cli/commands/platform/index.ts +0 -18
  358. package/src/cli/commands/skills.ts +77 -35
  359. package/src/cli/commands/trust.ts +70 -430
  360. package/src/cli/commands/usage.ts +25 -16
  361. package/src/cli/lib/daemon-credential-client.ts +115 -19
  362. package/src/cli/program.ts +4 -0
  363. package/src/cli.ts +0 -21
  364. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  365. package/src/config/assistant-feature-flags.ts +67 -10
  366. package/src/config/bundled-skills/acp/SKILL.md +6 -0
  367. package/src/config/bundled-skills/acp/TOOLS.json +1 -22
  368. package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
  369. package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
  370. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
  371. package/src/config/bundled-skills/app-control/SKILL.md +75 -0
  372. package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
  373. package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
  374. package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
  375. package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
  376. package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
  377. package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
  378. package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
  379. package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
  380. package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
  381. package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
  382. package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
  383. package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
  384. package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
  385. package/src/config/bundled-skills/document/TOOLS.json +0 -8
  386. package/src/config/bundled-skills/followups/TOOLS.json +0 -12
  387. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  388. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
  389. package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
  390. package/src/config/bundled-skills/messaging/TOOLS.json +14 -44
  391. package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
  392. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
  393. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
  394. package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
  395. package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
  396. package/src/config/bundled-skills/settings/SKILL.md +4 -0
  397. package/src/config/bundled-skills/settings/TOOLS.json +0 -12
  398. package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
  399. package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
  400. package/src/config/bundled-skills/subagent/SKILL.md +6 -2
  401. package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
  402. package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
  403. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
  404. package/src/config/bundled-tool-registry.ts +21 -0
  405. package/src/config/env-registry.ts +12 -4
  406. package/src/config/env.ts +22 -26
  407. package/src/config/feature-flag-registry.json +40 -152
  408. package/src/config/llm-callsite-catalog.ts +12 -0
  409. package/src/config/llm-context-resolution.ts +80 -0
  410. package/src/config/llm-resolver.ts +58 -22
  411. package/src/config/loader.ts +76 -102
  412. package/src/config/sanitize-for-transfer.ts +2 -0
  413. package/src/config/schema.ts +2 -158
  414. package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
  415. package/src/config/schemas/__tests__/memory-v2.test.ts +8 -4
  416. package/src/config/schemas/call-site-catalog.ts +271 -0
  417. package/src/config/schemas/calls.ts +5 -14
  418. package/src/config/schemas/heartbeat.ts +63 -0
  419. package/src/config/schemas/inference.ts +1 -1
  420. package/src/config/schemas/ingress.ts +11 -7
  421. package/src/config/schemas/llm.ts +34 -11
  422. package/src/config/schemas/memory-lifecycle.ts +77 -24
  423. package/src/config/schemas/memory-retrieval.ts +2 -2
  424. package/src/config/schemas/memory-v2.ts +57 -4
  425. package/src/config/schemas/platform.ts +6 -0
  426. package/src/config/schemas/security.ts +1 -42
  427. package/src/config/schemas/services.ts +7 -21
  428. package/src/config/schemas/skills.ts +5 -11
  429. package/src/config/schemas/tts.ts +1 -1
  430. package/src/config/seed-inference-profiles.ts +117 -0
  431. package/src/config/skills.ts +0 -90
  432. package/src/config/types.ts +3 -6
  433. package/src/contacts/contact-store.ts +0 -47
  434. package/src/contacts/contacts-write.ts +1 -132
  435. package/src/context/window-manager.ts +43 -5
  436. package/src/credential-execution/feature-gates.ts +10 -10
  437. package/src/credential-execution/process-manager.ts +46 -51
  438. package/src/credential-health/credential-health-service.ts +21 -16
  439. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +75 -82
  440. package/src/daemon/__tests__/conversation-tool-setup.test.ts +126 -5
  441. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -9
  442. package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
  443. package/src/daemon/config-watcher.ts +4 -3
  444. package/src/daemon/connection-policy.ts +1 -26
  445. package/src/daemon/conversation-agent-loop-handlers.ts +74 -7
  446. package/src/daemon/conversation-agent-loop.ts +309 -64
  447. package/src/daemon/conversation-history.ts +8 -8
  448. package/src/daemon/conversation-launch.ts +20 -135
  449. package/src/daemon/conversation-lifecycle.ts +8 -1
  450. package/src/daemon/conversation-messaging.ts +1 -0
  451. package/src/daemon/conversation-process.ts +97 -172
  452. package/src/daemon/conversation-runtime-assembly.ts +219 -76
  453. package/src/daemon/conversation-slash.ts +47 -5
  454. package/src/daemon/conversation-store.ts +7 -31
  455. package/src/daemon/conversation-surfaces.ts +144 -29
  456. package/src/daemon/conversation-tool-setup.ts +18 -87
  457. package/src/daemon/conversation-usage.ts +36 -0
  458. package/src/daemon/conversation.ts +134 -231
  459. package/src/daemon/daemon-control.ts +3 -71
  460. package/src/daemon/daemon-skill-host.ts +8 -11
  461. package/src/daemon/dictation-profile-store.ts +2 -26
  462. package/src/daemon/doordash-steps.ts +1 -1
  463. package/src/daemon/first-greeting.ts +44 -156
  464. package/src/daemon/handlers/config-channels.ts +12 -12
  465. package/src/daemon/handlers/config-ingress.ts +4 -165
  466. package/src/daemon/handlers/config-model.ts +1 -1
  467. package/src/daemon/handlers/config-voice.ts +0 -42
  468. package/src/daemon/handlers/conversations.ts +11 -190
  469. package/src/daemon/handlers/recording.ts +26 -158
  470. package/src/daemon/handlers/shared.ts +27 -72
  471. package/src/daemon/handlers/skills.ts +42 -93
  472. package/src/daemon/host-app-control-proxy.ts +293 -0
  473. package/src/daemon/host-bash-proxy.ts +124 -92
  474. package/src/daemon/host-browser-proxy.ts +111 -88
  475. package/src/daemon/host-cu-proxy.ts +100 -104
  476. package/src/daemon/host-file-proxy.ts +136 -91
  477. package/src/daemon/host-proxy-base.ts +294 -0
  478. package/src/daemon/host-proxy-preactivation.ts +82 -0
  479. package/src/daemon/host-transfer-proxy.ts +303 -147
  480. package/src/daemon/lifecycle.ts +164 -132
  481. package/src/daemon/message-protocol.ts +3 -8
  482. package/src/daemon/message-types/contacts.ts +23 -1
  483. package/src/daemon/message-types/conversations.ts +18 -8
  484. package/src/daemon/message-types/host-app-control.ts +150 -0
  485. package/src/daemon/message-types/host-bash.ts +5 -0
  486. package/src/daemon/message-types/host-cu.ts +3 -0
  487. package/src/daemon/message-types/host-file.ts +5 -0
  488. package/src/daemon/message-types/host-transfer.ts +4 -0
  489. package/src/daemon/message-types/messages.ts +10 -9
  490. package/src/daemon/message-types/schedules.ts +8 -3
  491. package/src/daemon/message-types/skills.ts +2 -2
  492. package/src/daemon/message-types/workspace.ts +1 -1
  493. package/src/daemon/process-message.ts +119 -239
  494. package/src/daemon/server.ts +13 -462
  495. package/src/daemon/shutdown-handlers.ts +2 -5
  496. package/src/daemon/tool-setup-types.ts +51 -0
  497. package/src/daemon/tool-side-effects.ts +126 -108
  498. package/src/daemon/trust-context.ts +13 -0
  499. package/src/daemon/wake-target-adapter.ts +4 -9
  500. package/src/events/domain-events.ts +0 -8
  501. package/src/events/tool-audit-listener.ts +5 -2
  502. package/src/events/tool-domain-event-publisher.ts +0 -10
  503. package/src/events/tool-metrics-listener.ts +0 -17
  504. package/src/events/tool-trace-listener.ts +0 -14
  505. package/src/filing/filing-service.ts +13 -1
  506. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +21 -9
  507. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
  508. package/src/heartbeat/heartbeat-run-store.ts +236 -0
  509. package/src/heartbeat/heartbeat-service.ts +303 -54
  510. package/src/home/__tests__/feed-writer.test.ts +0 -4
  511. package/src/home/__tests__/post-connect-feed.test.ts +99 -0
  512. package/src/home/__tests__/relationship-state-writer.test.ts +41 -9
  513. package/src/home/__tests__/suggested-prompts.test.ts +89 -0
  514. package/src/home/feed-writer.ts +1 -2
  515. package/src/home/post-connect-feed.ts +68 -0
  516. package/src/home/relationship-state-writer.ts +33 -95
  517. package/src/home/suggested-prompts.ts +46 -10
  518. package/src/inbound/public-ingress-urls.ts +32 -34
  519. package/src/ipc/__tests__/browser-ipc.test.ts +2 -12
  520. package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
  521. package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -1
  522. package/src/ipc/assistant-server.ts +17 -11
  523. package/src/ipc/cli-client.ts +32 -1
  524. package/src/ipc/routes/__tests__/memory-v2-backfill.test.ts +39 -20
  525. package/src/ipc/routes/route-adapter.ts +1 -1
  526. package/src/ipc/routes/trust-rules.test.ts +0 -95
  527. package/src/ipc/skill-ipc-types.ts +41 -0
  528. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +13 -27
  529. package/src/ipc/skill-routes/__tests__/identity.test.ts +4 -23
  530. package/src/ipc/skill-routes/events.ts +12 -23
  531. package/src/ipc/skill-routes/identity.ts +4 -17
  532. package/src/ipc/skill-routes/index.ts +1 -1
  533. package/src/ipc/skill-server.ts +6 -39
  534. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +0 -8
  535. package/src/live-voice/live-voice-metrics.ts +10 -10
  536. package/src/live-voice/protocol.ts +4 -13
  537. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
  538. package/src/mcp/manager.ts +0 -5
  539. package/src/mcp/mcp-auth-orchestrator.ts +213 -0
  540. package/src/mcp/mcp-auth-state.ts +133 -0
  541. package/src/mcp/mcp-oauth-provider.ts +19 -0
  542. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +55 -0
  543. package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
  544. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +127 -0
  545. package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
  546. package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
  547. package/src/memory/anisotropy.test.ts +247 -0
  548. package/src/memory/anisotropy.ts +443 -0
  549. package/src/memory/app-git-service.ts +0 -32
  550. package/src/memory/app-store.ts +154 -0
  551. package/src/memory/attachments-store.ts +6 -0
  552. package/src/memory/auto-analysis-constants.ts +17 -0
  553. package/src/memory/auto-analysis-guard.ts +5 -15
  554. package/src/memory/canonical-guardian-store.ts +7 -7
  555. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
  556. package/src/memory/context-search/agent-protocol.ts +6 -6
  557. package/src/memory/context-search/agent-runner.ts +32 -7
  558. package/src/memory/context-search/sources/memory-v2.ts +590 -0
  559. package/src/memory/context-search/sources/memory.ts +5 -0
  560. package/src/memory/context-search/sources/pkb.ts +10 -1
  561. package/src/memory/context-search/sources/workspace.ts +3 -2
  562. package/src/memory/conversation-crud.ts +30 -5
  563. package/src/memory/conversation-disk-view.ts +1 -5
  564. package/src/memory/conversation-key-store.ts +2 -15
  565. package/src/memory/conversation-starter-checkpoints.ts +63 -0
  566. package/src/memory/db-connection.ts +62 -0
  567. package/src/memory/db-init.ts +18 -0
  568. package/src/memory/embedding-backend.ts +12 -42
  569. package/src/memory/embedding-gemini.ts +0 -2
  570. package/src/memory/embedding-local.ts +6 -6
  571. package/src/memory/embedding-ollama.ts +6 -6
  572. package/src/memory/embedding-openai.ts +6 -6
  573. package/src/memory/embedding-types.ts +21 -0
  574. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -8
  575. package/src/memory/graph/conversation-graph-memory.ts +35 -36
  576. package/src/memory/graph/graph-search.ts +8 -0
  577. package/src/memory/graph/injection.test.ts +2 -2
  578. package/src/memory/graph/injection.ts +1 -1
  579. package/src/memory/graph/retriever.ts +28 -0
  580. package/src/memory/graph/tools.ts +1 -1
  581. package/src/memory/guardian-action-store.ts +0 -83
  582. package/src/memory/guardian-approvals.ts +0 -48
  583. package/src/memory/indexer.ts +1 -15
  584. package/src/memory/job-handlers/conversation-starters.ts +36 -53
  585. package/src/memory/job-utils.ts +0 -6
  586. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
  587. package/src/memory/jobs/embed-concept-page.ts +28 -2
  588. package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
  589. package/src/memory/jobs-store.ts +66 -23
  590. package/src/memory/jobs-worker.ts +114 -79
  591. package/src/memory/llm-request-log-store.ts +0 -41
  592. package/src/memory/llm-usage-store.ts +129 -43
  593. package/src/memory/memory-v2-activation-log-store.ts +115 -0
  594. package/src/memory/migrations/233-document-conversations.ts +54 -0
  595. package/src/memory/migrations/234-memory-v2-activation-logs.ts +55 -0
  596. package/src/memory/migrations/235-llm-usage-attribution.ts +31 -0
  597. package/src/memory/migrations/235-slack-compaction-watermark.ts +44 -0
  598. package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +26 -0
  599. package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
  600. package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
  601. package/src/memory/migrations/__tests__/234-memory-v2-activation-logs.test.ts +182 -0
  602. package/src/memory/migrations/index.ts +19 -0
  603. package/src/memory/migrations/registry.ts +32 -0
  604. package/src/memory/pkb/pkb-search.ts +7 -0
  605. package/src/memory/qdrant-client.ts +50 -20
  606. package/src/memory/raw-query.ts +2 -68
  607. package/src/memory/schema/conversations.ts +7 -0
  608. package/src/memory/schema/infrastructure.ts +40 -0
  609. package/src/memory/search/semantic.ts +12 -16
  610. package/src/memory/sparse-tokenize.ts +49 -0
  611. package/src/memory/tool-usage-store.ts +2 -0
  612. package/src/memory/usage-buckets.ts +40 -1
  613. package/src/memory/usage-grouped-buckets.ts +127 -0
  614. package/src/memory/v2/__tests__/activation.test.ts +361 -180
  615. package/src/memory/v2/__tests__/backfill-jobs.test.ts +2 -129
  616. package/src/memory/v2/__tests__/consolidation-job.test.ts +28 -11
  617. package/src/memory/v2/__tests__/edge-index.test.ts +278 -0
  618. package/src/memory/v2/__tests__/injection.test.ts +424 -33
  619. package/src/memory/v2/__tests__/migration.test.ts +64 -36
  620. package/src/memory/v2/__tests__/page-store.test.ts +191 -8
  621. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +181 -0
  622. package/src/memory/v2/__tests__/sim.test.ts +166 -6
  623. package/src/memory/v2/__tests__/skill-store.test.ts +115 -3
  624. package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
  625. package/src/memory/v2/__tests__/static-context.test.ts +152 -0
  626. package/src/memory/v2/activation.ts +215 -163
  627. package/src/memory/v2/backfill-jobs.ts +15 -100
  628. package/src/memory/v2/consolidation-job.ts +17 -17
  629. package/src/memory/v2/constants.ts +7 -0
  630. package/src/memory/v2/edge-index.ts +191 -0
  631. package/src/memory/v2/injection.ts +241 -84
  632. package/src/memory/v2/migration.ts +57 -64
  633. package/src/memory/v2/now-text.ts +2 -3
  634. package/src/memory/v2/page-store.ts +168 -31
  635. package/src/memory/v2/prompts/consolidation.ts +385 -88
  636. package/src/memory/v2/prompts/sweep.ts +3 -3
  637. package/src/memory/v2/qdrant.ts +99 -1
  638. package/src/memory/v2/sim.ts +126 -16
  639. package/src/memory/v2/skill-qdrant.ts +12 -3
  640. package/src/memory/v2/skill-store.ts +71 -8
  641. package/src/memory/v2/sparse-bm25.ts +245 -0
  642. package/src/memory/v2/static-context.ts +63 -0
  643. package/src/memory/v2/types.ts +10 -20
  644. package/src/memory/validation.ts +0 -11
  645. package/src/messaging/draft-store.ts +0 -6
  646. package/src/messaging/provider-types.ts +8 -0
  647. package/src/messaging/provider.ts +7 -0
  648. package/src/messaging/providers/gmail/client.ts +1 -121
  649. package/src/messaging/providers/gmail/types.ts +0 -49
  650. package/src/messaging/providers/outlook/client.ts +0 -73
  651. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +226 -0
  652. package/src/messaging/providers/slack/adapter.ts +123 -52
  653. package/src/messaging/providers/slack/backfill.test.ts +95 -6
  654. package/src/messaging/providers/slack/backfill.ts +89 -11
  655. package/src/messaging/providers/slack/client.ts +10 -124
  656. package/src/messaging/providers/slack/message-metadata.ts +12 -2
  657. package/src/messaging/providers/slack/render-transcript.test.ts +56 -0
  658. package/src/messaging/providers/slack/render-transcript.ts +126 -25
  659. package/src/messaging/providers/slack/types.ts +1 -32
  660. package/src/notifications/README.md +10 -10
  661. package/src/notifications/broadcaster.ts +1 -1
  662. package/src/notifications/guardian-question-mode.ts +5 -5
  663. package/src/oauth/connect-orchestrator.ts +4 -0
  664. package/src/oauth/connection-resolver.test.ts +8 -0
  665. package/src/oauth/connection-resolver.ts +8 -16
  666. package/src/oauth/credential-token-resolver.ts +95 -0
  667. package/src/oauth/manual-token-connection.ts +26 -34
  668. package/src/oauth/oauth-store.ts +6 -4
  669. package/src/outbound-proxy/certs.ts +0 -7
  670. package/src/outbound-proxy/index.ts +1 -59
  671. package/src/outbound-proxy/logging.ts +1 -1
  672. package/src/outbound-proxy/policy.ts +6 -5
  673. package/src/outbound-proxy/router.ts +2 -1
  674. package/src/permissions/approval-policy.test.ts +6 -275
  675. package/src/permissions/approval-policy.ts +0 -51
  676. package/src/permissions/approval-provenance.test.ts +184 -0
  677. package/src/permissions/approval-provenance.ts +70 -0
  678. package/src/permissions/checker.test.ts +0 -1
  679. package/src/permissions/checker.ts +7 -18
  680. package/src/permissions/gateway-threshold-reader.ts +6 -1
  681. package/src/permissions/prompter.ts +43 -3
  682. package/src/permissions/secret-prompter.ts +25 -48
  683. package/src/permissions/types.ts +33 -0
  684. package/src/permissions/workspace-policy.ts +0 -5
  685. package/src/platform/sync-identity.ts +0 -8
  686. package/src/plugins/defaults/injectors.ts +69 -2
  687. package/src/plugins/defaults/overflow-reduce.ts +3 -2
  688. package/src/plugins/types.ts +8 -0
  689. package/src/prompts/bootstrap-cleanup.ts +27 -0
  690. package/src/prompts/system-prompt.ts +37 -88
  691. package/src/prompts/templates/BOOTSTRAP.md +52 -6
  692. package/src/prompts/templates/SOUL.md +13 -1
  693. package/src/prompts/update-bulletin-job.ts +2 -0
  694. package/src/providers/__tests__/retry-callsite.test.ts +138 -1
  695. package/src/providers/anthropic/client.ts +72 -33
  696. package/src/providers/call-site-routing.ts +42 -3
  697. package/src/providers/gemini/client.ts +18 -2
  698. package/src/providers/managed-proxy/context.ts +0 -5
  699. package/src/providers/model-catalog.ts +105 -19
  700. package/src/providers/openai/chat-completions-provider.ts +6 -0
  701. package/src/providers/openai/responses-provider.ts +7 -1
  702. package/src/providers/provider-send-message.ts +45 -2
  703. package/src/providers/ratelimit.ts +7 -2
  704. package/src/providers/registry.ts +14 -9
  705. package/src/providers/retry.ts +96 -8
  706. package/src/providers/speech-to-text/provider-catalog.ts +7 -8
  707. package/src/providers/types.ts +13 -0
  708. package/src/providers/usage-tracking.ts +96 -0
  709. package/src/runtime/AGENTS.md +10 -6
  710. package/src/runtime/__tests__/agent-wake.test.ts +89 -0
  711. package/src/runtime/agent-wake.ts +39 -2
  712. package/src/runtime/assistant-event-hub.ts +570 -52
  713. package/src/runtime/assistant-event.ts +2 -6
  714. package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
  715. package/src/runtime/auth/context.ts +0 -9
  716. package/src/runtime/auth/middleware.ts +1 -97
  717. package/src/runtime/auth/route-policy.ts +30 -9
  718. package/src/runtime/auth/token-service.ts +0 -11
  719. package/src/runtime/btw-sidechain.ts +2 -3
  720. package/src/runtime/channel-approvals.ts +6 -2
  721. package/src/runtime/channel-invite-transport.ts +2 -48
  722. package/src/runtime/channel-invite-transports/email.ts +1 -1
  723. package/src/runtime/channel-invite-transports/slack.ts +1 -1
  724. package/src/runtime/channel-invite-transports/telegram.ts +1 -1
  725. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  726. package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
  727. package/src/runtime/channel-invite-types.ts +54 -0
  728. package/src/runtime/channel-readiness-service.ts +32 -13
  729. package/src/runtime/channel-verification-service.ts +3 -5
  730. package/src/runtime/http-errors.ts +0 -34
  731. package/src/runtime/http-router.ts +6 -3
  732. package/src/runtime/http-server.ts +16 -402
  733. package/src/runtime/http-types.ts +5 -5
  734. package/src/runtime/interactive-ui.ts +0 -1
  735. package/src/runtime/middleware/auth.ts +0 -20
  736. package/src/runtime/migrations/__tests__/v1-test-helpers.ts +112 -0
  737. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +11 -4
  738. package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +253 -0
  739. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +19 -6
  740. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
  741. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
  742. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
  743. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +71 -27
  744. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +41 -2
  745. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +296 -80
  746. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +143 -23
  747. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
  748. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  749. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
  750. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
  751. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
  752. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +2 -2
  753. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +421 -0
  754. package/src/runtime/migrations/migration-transport.ts +49 -16
  755. package/src/runtime/migrations/migration-wizard.ts +2 -2
  756. package/src/runtime/migrations/origin-mode.ts +40 -0
  757. package/src/runtime/migrations/vbundle-builder.ts +457 -136
  758. package/src/runtime/migrations/vbundle-import-analyzer.ts +13 -11
  759. package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
  760. package/src/runtime/migrations/vbundle-importer.ts +251 -74
  761. package/src/runtime/migrations/vbundle-metadata-merge.ts +1 -1
  762. package/src/runtime/migrations/vbundle-streaming-importer.ts +329 -38
  763. package/src/runtime/migrations/vbundle-streaming-validator.ts +203 -28
  764. package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
  765. package/src/runtime/migrations/vbundle-validator.ts +328 -41
  766. package/src/runtime/pending-interactions.ts +48 -13
  767. package/src/runtime/routes/__tests__/acp-routes.test.ts +0 -1
  768. package/src/runtime/routes/__tests__/backup-routes.test.ts +49 -168
  769. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +333 -0
  770. package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
  771. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
  772. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +58 -0
  773. package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +54 -0
  774. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +19 -6
  775. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +7 -7
  776. package/src/runtime/routes/acp-routes.test.ts +0 -3
  777. package/src/runtime/routes/acp-routes.ts +3 -7
  778. package/src/runtime/routes/app-management-routes.ts +18 -9
  779. package/src/runtime/routes/approval-interception-types.ts +13 -0
  780. package/src/runtime/routes/approval-routes.ts +55 -14
  781. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
  782. package/src/runtime/routes/avatar-routes.ts +3 -5
  783. package/src/runtime/routes/backup-routes.ts +15 -38
  784. package/src/runtime/routes/browser-routes.ts +1 -15
  785. package/src/runtime/routes/btw-routes.ts +14 -37
  786. package/src/runtime/routes/channel-guardian-routes.ts +1 -5
  787. package/src/runtime/routes/channel-readiness-routes.ts +3 -7
  788. package/src/runtime/routes/channel-route-shared.ts +2 -28
  789. package/src/runtime/routes/client-routes.ts +46 -12
  790. package/src/runtime/routes/consolidation-routes.ts +115 -0
  791. package/src/runtime/routes/contact-prompt-routes.ts +183 -0
  792. package/src/runtime/routes/conversation-list-routes.ts +12 -29
  793. package/src/runtime/routes/conversation-management-routes.ts +14 -51
  794. package/src/runtime/routes/conversation-query-routes.ts +156 -9
  795. package/src/runtime/routes/conversation-routes.ts +72 -539
  796. package/src/runtime/routes/conversation-starter-routes.ts +19 -40
  797. package/src/runtime/routes/document-pdf-renderer.ts +165 -0
  798. package/src/runtime/routes/documents-routes.ts +83 -18
  799. package/src/runtime/routes/errors.ts +19 -4
  800. package/src/runtime/routes/events-routes.ts +68 -94
  801. package/src/runtime/routes/filing-routes.ts +18 -1
  802. package/src/runtime/routes/gateway-log-routes.ts +79 -0
  803. package/src/runtime/routes/guardian-action-routes.ts +4 -9
  804. package/src/runtime/routes/guardian-approval-interception.ts +2 -8
  805. package/src/runtime/routes/heartbeat-routes.ts +103 -38
  806. package/src/runtime/routes/host-app-control-routes.ts +134 -0
  807. package/src/runtime/routes/host-bash-routes.ts +37 -6
  808. package/src/runtime/routes/host-browser-routes.ts +96 -25
  809. package/src/runtime/routes/host-cu-routes.ts +48 -13
  810. package/src/runtime/routes/host-file-routes.ts +35 -11
  811. package/src/runtime/routes/host-transfer-routes.ts +73 -37
  812. package/src/runtime/routes/http-adapter.ts +1 -0
  813. package/src/runtime/routes/identity-intro-cache.ts +30 -0
  814. package/src/runtime/routes/identity-routes.ts +93 -49
  815. package/src/runtime/routes/inbound-message-handler.ts +581 -146
  816. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -95
  817. package/src/runtime/routes/inbound-stages/background-dispatch.ts +3 -0
  818. package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
  819. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
  820. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
  821. package/src/runtime/routes/index.ts +12 -0
  822. package/src/runtime/routes/integrations/slack/channel.ts +0 -24
  823. package/src/runtime/routes/llm-call-sites-routes.ts +22 -0
  824. package/src/runtime/routes/mcp-auth-routes.ts +132 -0
  825. package/src/runtime/routes/memory-item-routes.ts +10 -12
  826. package/src/runtime/routes/memory-v2-routes.ts +451 -16
  827. package/src/runtime/routes/migration-routes.ts +284 -31
  828. package/src/runtime/routes/playground/guard.ts +1 -1
  829. package/src/runtime/routes/playground/index.ts +0 -2
  830. package/src/runtime/routes/recording-routes.ts +4 -24
  831. package/src/runtime/routes/rename-conversation-routes.ts +2 -6
  832. package/src/runtime/routes/schedule-routes.ts +10 -6
  833. package/src/runtime/routes/secret-routes.ts +87 -18
  834. package/src/runtime/routes/settings-routes.ts +29 -28
  835. package/src/runtime/routes/skills-routes.ts +12 -31
  836. package/src/runtime/routes/suggest-trust-rule-routes.ts +32 -1
  837. package/src/runtime/routes/task-routes.ts +6 -6
  838. package/src/runtime/routes/trust-rules-routes.ts +3 -94
  839. package/src/runtime/routes/types.ts +4 -4
  840. package/src/runtime/routes/upgrade-broadcast-routes.ts +3 -10
  841. package/src/runtime/routes/usage-routes.ts +87 -10
  842. package/src/runtime/routes/user-routes.ts +17 -31
  843. package/src/runtime/routes/work-items-routes.ts +1 -4
  844. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -2
  845. package/src/runtime/services/analyze-conversation.ts +7 -17
  846. package/src/runtime/services/conversation-serializer.ts +2 -4
  847. package/src/runtime/verification-outbound-actions.ts +1 -1
  848. package/src/runtime/verification-rate-limiter.ts +1 -1
  849. package/src/runtime/verification-templates.ts +4 -7
  850. package/src/schedule/integration-status.ts +66 -2
  851. package/src/schedule/recurrence-engine.ts +4 -1
  852. package/src/schedule/retry-backoff.ts +18 -0
  853. package/src/schedule/retry-policy.ts +82 -0
  854. package/src/schedule/schedule-recovery.ts +64 -0
  855. package/src/schedule/schedule-store.ts +106 -18
  856. package/src/schedule/scheduler-types.ts +25 -0
  857. package/src/schedule/scheduler.ts +63 -38
  858. package/src/security/oauth-callback-registry.ts +8 -0
  859. package/src/security/secret-scanner.ts +14 -547
  860. package/src/security/secure-keys.ts +31 -11
  861. package/src/security/token-manager.ts +7 -3
  862. package/src/sequence/analytics.ts +5 -5
  863. package/src/sequence/engine.ts +1 -1
  864. package/src/signals/cancel.ts +16 -25
  865. package/src/signals/conversation-undo.ts +2 -27
  866. package/src/signals/emit-event.ts +1 -2
  867. package/src/signals/user-message.ts +108 -22
  868. package/src/skills/catalog-files.ts +2 -8
  869. package/src/skills/catalog-install.ts +1 -0
  870. package/src/skills/clawhub.ts +2 -2
  871. package/src/skills/include-graph.ts +5 -5
  872. package/src/skills/inline-command-runner.ts +1 -7
  873. package/src/skills/remote-skill-policy.ts +5 -5
  874. package/src/skills/skill-file-provider.ts +1 -1
  875. package/src/skills/skill-file-types.ts +13 -0
  876. package/src/skills/skillssh-audit-types.ts +28 -0
  877. package/src/skills/skillssh-registry.ts +8 -21
  878. package/src/subagent/manager.ts +67 -84
  879. package/src/tasks/task-store.ts +1 -28
  880. package/src/telemetry/types.ts +8 -0
  881. package/src/telemetry/usage-telemetry-reporter.test.ts +59 -15
  882. package/src/telemetry/usage-telemetry-reporter.ts +4 -5
  883. package/src/tools/acp/spawn.test.ts +1 -2
  884. package/src/tools/acp/steer.test.ts +1 -2
  885. package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
  886. package/src/tools/apps/executors.ts +56 -69
  887. package/src/tools/browser/__tests__/browser-status.test.ts +55 -135
  888. package/src/tools/browser/browser-execution.ts +31 -147
  889. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +145 -70
  890. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
  891. package/src/tools/browser/cdp-client/factory.ts +62 -91
  892. package/src/tools/browser/cdp-client/index.ts +1 -27
  893. package/src/tools/computer-use/definitions.ts +42 -20
  894. package/src/tools/executor.ts +46 -31
  895. package/src/tools/host-filesystem/edit.ts +29 -2
  896. package/src/tools/host-filesystem/read.ts +29 -2
  897. package/src/tools/host-filesystem/transfer.test.ts +45 -42
  898. package/src/tools/host-filesystem/transfer.ts +35 -4
  899. package/src/tools/host-filesystem/write.ts +29 -2
  900. package/src/tools/host-terminal/host-shell.ts +62 -3
  901. package/src/tools/network/script-proxy/index.ts +1 -10
  902. package/src/tools/permission-checker.ts +66 -1
  903. package/src/tools/schedule/create.ts +6 -0
  904. package/src/tools/schedule/list.ts +2 -0
  905. package/src/tools/schedule/update.ts +10 -0
  906. package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
  907. package/src/tools/shared/filesystem/path-policy.ts +25 -1
  908. package/src/tools/skills/load.ts +0 -32
  909. package/src/tools/skills/sandbox-runner.ts +1 -6
  910. package/src/tools/skills/skill-tool-factory.ts +32 -0
  911. package/src/tools/terminal/safe-env.ts +1 -0
  912. package/src/tools/terminal/shell.ts +2 -78
  913. package/src/tools/tool-approval-handler.ts +1 -5
  914. package/src/tools/types.ts +16 -39
  915. package/src/tts/__tests__/provider-catalog.test.ts +2 -2
  916. package/src/tts/provider-catalog.ts +1 -1
  917. package/src/usage/actors.ts +2 -1
  918. package/src/usage/attribution.ts +185 -0
  919. package/src/usage/pricing.ts +166 -0
  920. package/src/usage/types.ts +14 -0
  921. package/src/util/json.ts +13 -0
  922. package/src/util/logger.ts +3 -3
  923. package/src/util/pricing.ts +50 -3
  924. package/src/work-items/work-item-runner.ts +15 -42
  925. package/src/workspace/hatched-date.ts +86 -0
  926. package/src/workspace/migrations/003-seed-device-id.ts +1 -1
  927. package/src/workspace/migrations/006-services-config.ts +8 -5
  928. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
  929. package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
  930. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
  931. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
  932. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
  933. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
  934. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +6 -4
  935. package/src/workspace/migrations/052-seed-default-inference-profiles.ts +3 -3
  936. package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
  937. package/src/workspace/migrations/060-memory-v2-init.ts +2 -18
  938. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +54 -0
  939. package/src/workspace/migrations/062-drop-memory-v2-edges-json.ts +27 -0
  940. package/src/workspace/migrations/063-release-notes-dynamic-model-context.ts +70 -0
  941. package/src/workspace/migrations/064-unwind-main-agent-opus-seed.ts +64 -0
  942. package/src/workspace/migrations/AGENTS.md +1 -1
  943. package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
  944. package/src/workspace/migrations/registry.ts +8 -0
  945. package/src/workspace/migrations/utils.ts +21 -0
  946. package/src/workspace/provider-commit-message-generator.ts +3 -3
  947. package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -904
  948. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -296
  949. package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -431
  950. package/src/__tests__/sandbox-diagnostics.test.ts +0 -138
  951. package/src/__tests__/sandbox-host-parity.test.ts +0 -1024
  952. package/src/__tests__/secret-detection-handler.test.ts +0 -67
  953. package/src/__tests__/secret-scanner-executor.test.ts +0 -450
  954. package/src/__tests__/tcc-sandbox-deny.test.ts +0 -198
  955. package/src/__tests__/terminal-sandbox.test.ts +0 -374
  956. package/src/__tests__/tool-notification-listener.test.ts +0 -65
  957. package/src/__tests__/twilio-rest.test.ts +0 -34
  958. package/src/backup/__tests__/backup-key.test.ts +0 -152
  959. package/src/backup/__tests__/backup-worker.test.ts +0 -754
  960. package/src/backup/__tests__/offsite-writer.test.ts +0 -641
  961. package/src/backup/__tests__/stream-crypt.test.ts +0 -228
  962. package/src/backup/backup-key.ts +0 -137
  963. package/src/backup/backup-worker.ts +0 -438
  964. package/src/backup/offsite-writer.ts +0 -222
  965. package/src/backup/stream-crypt.ts +0 -263
  966. package/src/context/__tests__/microcompact.test.ts +0 -805
  967. package/src/context/microcompact.ts +0 -443
  968. package/src/daemon/handlers/slack-channel-oauth-install.ts +0 -197
  969. package/src/daemon/message-types/pairing.ts +0 -58
  970. package/src/events/tool-notification-listener.ts +0 -17
  971. package/src/ipc/routes/__tests__/memory-v2-validate.test.ts +0 -219
  972. package/src/memory/v2/__tests__/edges.test.ts +0 -435
  973. package/src/memory/v2/edges.ts +0 -217
  974. package/src/outbound-proxy/config.ts +0 -94
  975. package/src/outbound-proxy/health.ts +0 -62
  976. package/src/outbound-proxy/types.ts +0 -150
  977. package/src/prompts/__tests__/system-prompt-memory-v2.test.ts +0 -197
  978. package/src/runtime/__tests__/chrome-extension-registry.test.ts +0 -518
  979. package/src/runtime/__tests__/client-registry.test.ts +0 -271
  980. package/src/runtime/capability-tokens.ts +0 -190
  981. package/src/runtime/chrome-extension-registry.ts +0 -368
  982. package/src/runtime/client-registry.ts +0 -254
  983. package/src/runtime/routes/inbound-stages/verification-intercept.ts +0 -329
  984. package/src/signals/mcp-reload.ts +0 -18
  985. package/src/tools/secret-detection-handler.ts +0 -269
  986. package/src/tools/terminal/backends/native.ts +0 -327
  987. package/src/tools/terminal/backends/types.ts +0 -37
  988. package/src/tools/terminal/sandbox-diagnostics.ts +0 -87
  989. package/src/tools/terminal/sandbox.ts +0 -40
@@ -11,7 +11,6 @@ import {
11
11
  isChannelId,
12
12
  parseInterfaceId,
13
13
  } from "../../channels/types.js";
14
- import { touchContactInteraction } from "../../contacts/contacts-write.js";
15
14
  import {
16
15
  createApprovalConversationGenerator,
17
16
  createApprovalCopyGenerator,
@@ -45,11 +44,13 @@ import { upsertBinding } from "../../memory/external-conversation-store.js";
45
44
  import type { Message as ProviderMessage } from "../../messaging/provider-types.js";
46
45
  import {
47
46
  backfillDm,
48
- backfillThread,
47
+ backfillThreadWindowPage,
48
+ type SlackBackfillWindowPage,
49
49
  } from "../../messaging/providers/slack/backfill.js";
50
50
  import {
51
51
  mergeSlackMetadata,
52
52
  readSlackMetadata,
53
+ type SlackFileMetadata,
53
54
  type SlackMessageMetadata,
54
55
  writeSlackMetadata,
55
56
  } from "../../messaging/providers/slack/message-metadata.js";
@@ -71,7 +72,6 @@ import { handleGuardianActivationIntercept } from "./inbound-stages/guardian-act
71
72
  import { handleGuardianReplyIntercept } from "./inbound-stages/guardian-reply-intercept.js";
72
73
  import { runSecretIngressCheck } from "./inbound-stages/secret-ingress-check.js";
73
74
  import { tryTranscribeAudioAttachments } from "./inbound-stages/transcribe-audio.js";
74
- import { handleVerificationIntercept } from "./inbound-stages/verification-intercept.js";
75
75
  import type { RouteHandlerArgs } from "./types.js";
76
76
 
77
77
  const log = getLogger("runtime-http");
@@ -263,7 +263,7 @@ export async function handleChannelInbound({
263
263
  externalMessageId,
264
264
  });
265
265
  if (aclResult.earlyResponse) return aclResult.earlyResponse;
266
- const { resolvedMember, guardianVerifyCode } = aclResult;
266
+ const { resolvedMember } = aclResult;
267
267
 
268
268
  // ── Slack delete propagation ──
269
269
  // Slack message_deleted events are forwarded by the gateway with the
@@ -286,7 +286,7 @@ export async function handleChannelInbound({
286
286
  { conversationExternalId },
287
287
  "Slack message_deleted event missing sourceMetadata.messageId; ignoring",
288
288
  );
289
- return ({ accepted: true, deleted: false });
289
+ return { accepted: true, deleted: false };
290
290
  }
291
291
 
292
292
  // Look up the stored message via the existing channel-event lookup.
@@ -327,7 +327,7 @@ export async function handleChannelInbound({
327
327
  { conversationExternalId, deletedMessageTs },
328
328
  "No stored message found for Slack delete after retries; ignoring",
329
329
  );
330
- return ({ accepted: true, deleted: false });
330
+ return { accepted: true, deleted: false };
331
331
  }
332
332
 
333
333
  // Merge deletedAt into the existing slackMeta sub-key. If the row has
@@ -345,7 +345,7 @@ export async function handleChannelInbound({
345
345
  },
346
346
  "Stored Slack message has no metadata; skipping delete marker",
347
347
  );
348
- return ({ accepted: true, deleted: false });
348
+ return { accepted: true, deleted: false };
349
349
  }
350
350
 
351
351
  let parentMetadata: Record<string, unknown>;
@@ -365,7 +365,7 @@ export async function handleChannelInbound({
365
365
  },
366
366
  "Failed to parse stored metadata; skipping delete marker",
367
367
  );
368
- return ({ accepted: true, deleted: false });
368
+ return { accepted: true, deleted: false };
369
369
  }
370
370
 
371
371
  const existingSlackMeta =
@@ -382,7 +382,7 @@ export async function handleChannelInbound({
382
382
  },
383
383
  "Stored Slack message has no slackMeta; skipping delete marker",
384
384
  );
385
- return ({ accepted: true, deleted: false });
385
+ return { accepted: true, deleted: false };
386
386
  }
387
387
 
388
388
  const updatedSlackMeta = mergeSlackMetadata(existingSlackMeta, {
@@ -404,11 +404,11 @@ export async function handleChannelInbound({
404
404
  "Marked Slack message as deleted",
405
405
  );
406
406
 
407
- return ({
407
+ return {
408
408
  accepted: true,
409
409
  deleted: true,
410
410
  messageId: original.messageId,
411
- });
411
+ };
412
412
  }
413
413
 
414
414
  if (hasAttachments) {
@@ -416,7 +416,9 @@ export async function handleChannelInbound({
416
416
  if (resolved.length !== attachmentIds.length) {
417
417
  const resolvedIds = new Set(resolved.map((a) => a.id));
418
418
  const missing = attachmentIds.filter((id) => !resolvedIds.has(id));
419
- throw new BadRequestError(`Attachment IDs not found: ${missing.join(", ")}`);
419
+ throw new BadRequestError(
420
+ `Attachment IDs not found: ${missing.join(", ")}`,
421
+ );
420
422
  }
421
423
  }
422
424
 
@@ -439,7 +441,7 @@ export async function handleChannelInbound({
439
441
  `[Voice message received — ${transcribeResult.reason}]` +
440
442
  (trimmedContent ? `\n\n${trimmedContent}` : "");
441
443
  break;
442
- // "no_audio", "disabled" — no action needed
444
+ // "no_audio" — no action needed
443
445
  }
444
446
  }
445
447
 
@@ -500,21 +502,14 @@ export async function handleChannelInbound({
500
502
  "Retry of pending verification reply failed; will retry on next duplicate",
501
503
  );
502
504
  }
503
- return ({
505
+ return {
504
506
  accepted: true,
505
507
  duplicate: true,
506
508
  eventId: result.eventId,
507
- });
509
+ };
508
510
  }
509
511
  }
510
512
 
511
- // Track contact interaction only for genuinely new messages (not webhook
512
- // retries). This was previously in ACL enforcement which runs before dedup,
513
- // causing retries to inflate interaction counts.
514
- if (!result.duplicate && resolvedMember) {
515
- touchContactInteraction(resolvedMember.channel.id);
516
- }
517
-
518
513
  // external_conversation_bindings is assistant-agnostic. Restrict writes to
519
514
  // self so assistant-scoped legacy routes do not overwrite each other's
520
515
  // channel binding metadata for the same chat.
@@ -541,10 +536,10 @@ export async function handleChannelInbound({
541
536
  // guardian approval reactions have no transcript representation.
542
537
  // 2. All other reactions (non-guardian, no pending approval, stale,
543
538
  // and any `reaction_removed:` event regardless of actor) fall
544
- // through to `persistSlackReactionAsMessage` so the chronological
545
- // renderer (PR 18) can surface them inline. Reactions never
546
- // trigger an agent response, so we short-circuit before
547
- // escalation and agent-loop dispatch in both cases.
539
+ // through to `persistSlackReactionAsMessage` so Slack transcript
540
+ // rendering can surface them inline. Reactions never trigger an
541
+ // agent response, so we short-circuit before escalation and
542
+ // agent-loop dispatch in both cases.
548
543
  if (isSlackReactionEvent(body)) {
549
544
  // Approval interception runs only for reactions (added) — `reaction_removed`
550
545
  // never expresses an approval intent, so un-reacting is left as a pure
@@ -586,12 +581,12 @@ export async function handleChannelInbound({
586
581
  // transcript line. All other interception outcomes (stale_ignored,
587
582
  // non-guardian, no pending approval) fall through to persistence.
588
583
  if (reactionApprovalResult.type === "guardian_decision_applied") {
589
- return ({
584
+ return {
590
585
  accepted: true,
591
586
  duplicate: false,
592
587
  eventId: result.eventId,
593
588
  approval: reactionApprovalResult.type,
594
- });
589
+ };
595
590
  }
596
591
  }
597
592
 
@@ -604,11 +599,11 @@ export async function handleChannelInbound({
604
599
  { conversationId: result.conversationId, eventId: result.eventId },
605
600
  "Skipping reaction persistence: missing sourceMetadata.messageId",
606
601
  );
607
- return ({
602
+ return {
608
603
  accepted: result.accepted,
609
604
  duplicate: result.duplicate,
610
605
  eventId: result.eventId,
611
- });
606
+ };
612
607
  }
613
608
 
614
609
  const threadTs =
@@ -634,11 +629,11 @@ export async function handleChannelInbound({
634
629
  );
635
630
  }
636
631
 
637
- return ({
632
+ return {
638
633
  accepted: result.accepted,
639
634
  duplicate: result.duplicate,
640
635
  eventId: result.eventId,
641
- });
636
+ };
642
637
  }
643
638
 
644
639
  // ── Ingress escalation ──
@@ -670,6 +665,7 @@ export async function handleChannelInbound({
670
665
  typeof hint === "string" && hint.trim().length > 0,
671
666
  )
672
667
  : [];
668
+ let slackRuntimeContextNotice: string | undefined;
673
669
 
674
670
  // Inject channel-scoped permission hints for Slack channel messages
675
671
  if (sourceChannel === "slack") {
@@ -734,24 +730,6 @@ export async function handleChannelInbound({
734
730
  });
735
731
  if (bootstrapResponse) return bootstrapResponse;
736
732
 
737
- // ── Guardian verification code intercept (deterministic) ──
738
- const verificationResponse = await handleVerificationIntercept({
739
- isDuplicate: result.duplicate,
740
- guardianVerifyCode,
741
- rawSenderId,
742
- canonicalSenderId,
743
- canonicalAssistantId,
744
- sourceChannel,
745
- conversationExternalId,
746
- conversationId: result.conversationId,
747
- eventId: result.eventId,
748
- replyCallbackUrl,
749
- assistantId,
750
- actorDisplayName: body.actorDisplayName,
751
- actorUsername: body.actorUsername,
752
- });
753
- if (verificationResponse) return verificationResponse;
754
-
755
733
  // Legacy voice guardian action interception removed — all guardian reply
756
734
  // routing now flows through the canonical router below (routeGuardianReply),
757
735
  // which handles request code matching, callback parsing, and NL classification
@@ -862,12 +840,12 @@ export async function handleChannelInbound({
862
840
  }
863
841
  }
864
842
 
865
- return ({
843
+ return {
866
844
  accepted: true,
867
845
  duplicate: false,
868
846
  eventId: result.eventId,
869
847
  approval: approvalResult.type,
870
- });
848
+ };
871
849
  }
872
850
 
873
851
  // When a callback payload was not handled by approval interception, it's
@@ -922,12 +900,12 @@ export async function handleChannelInbound({
922
900
  });
923
901
  }
924
902
 
925
- return ({
903
+ return {
926
904
  accepted: true,
927
905
  duplicate: false,
928
906
  eventId: result.eventId,
929
907
  approval: "stale_ignored",
930
- });
908
+ };
931
909
  }
932
910
  }
933
911
 
@@ -1032,25 +1010,27 @@ export async function handleChannelInbound({
1032
1010
  });
1033
1011
  }
1034
1012
 
1035
- // ── Thread-ancestor backfill ──
1036
- // When a Slack reply arrives for a thread the daemon never saw the
1037
- // parent of, fetch the thread's recent history from Slack and persist
1038
- // the missing messages so the chronological renderer (PR 18) has the
1039
- // full conversation. Awaited (mirrors the DM cold-start path above)
1040
- // so the agent loop dispatched immediately afterwards observes the
1041
- // backfilled parent without this, `loadSlackChronologicalMessages`
1042
- // can race the persist and miss thread context. Backfill is bounded
1043
- // (parent + ~50 messages) and the agent latency is dominated by the
1044
- // LLM call, so the added latency is negligible. Failures are
1045
- // swallowed inside the helper so they never block dispatch.
1013
+ // ── Thread gap/delta backfill ──
1014
+ // When a Slack thread reply arrives, compare the stored thread state
1015
+ // with the inbound message's ts and fetch only the bounded unseen
1016
+ // window. Initial late-join turns hydrate the earliest thread messages
1017
+ // plus a recent window adjacent to the inbound reply; later turns use
1018
+ // a delta window after the latest stored thread ts and before the
1019
+ // inbound ts. Awaited (mirrors the DM cold-start path above) so the
1020
+ // agent loop dispatched immediately afterwards observes hydrated
1021
+ // context. A late-join notice is added only to the current turn's
1022
+ // runtime context, not persisted as durable Slack metadata. Failures
1023
+ // are swallowed inside the helper so they never block dispatch.
1046
1024
  if (slackThreadTs) {
1047
- await triggerSlackThreadBackfillIfNeeded({
1025
+ const backfillResult = await triggerSlackThreadBackfillIfNeeded({
1048
1026
  conversationId: result.conversationId,
1049
1027
  channelId: conversationExternalId,
1050
1028
  threadTs: slackThreadTs,
1051
1029
  excludeChannelTs: slackInbound?.channelTs,
1052
1030
  account: slackAccount,
1053
1031
  });
1032
+ const lateJoinNotice = buildSlackLateJoinNotice(backfillResult);
1033
+ if (lateJoinNotice) slackRuntimeContextNotice = lateJoinNotice;
1054
1034
  }
1055
1035
 
1056
1036
  // Wrap non-guardian inbound content in external_content boundaries so
@@ -1078,6 +1058,7 @@ export async function handleChannelInbound({
1078
1058
  externalChatId: conversationExternalId,
1079
1059
  trustCtx,
1080
1060
  metadataHints,
1061
+ slackRuntimeContextNotice,
1081
1062
  metadataUxBrief,
1082
1063
  commandIntent,
1083
1064
  sourceLanguageCode,
@@ -1090,11 +1071,11 @@ export async function handleChannelInbound({
1090
1071
  }
1091
1072
  }
1092
1073
 
1093
- return ({
1074
+ return {
1094
1075
  accepted: result.accepted,
1095
1076
  duplicate: result.duplicate,
1096
1077
  eventId: result.eventId,
1097
- });
1078
+ };
1098
1079
  }
1099
1080
 
1100
1081
  /**
@@ -1193,8 +1174,8 @@ async function persistSlackReactionAsMessage(params: {
1193
1174
  },
1194
1175
  };
1195
1176
 
1196
- // Sentinel content — renderers (PR 18) read `slackMeta` to format the
1197
- // reaction line; the literal text is never displayed to the model.
1177
+ // Sentinel content — Slack transcript renderers read `slackMeta` to format
1178
+ // the reaction line; the literal text is never displayed to the model.
1198
1179
  const persisted = await addMessage(
1199
1180
  params.conversationId,
1200
1181
  "user",
@@ -1273,19 +1254,7 @@ function countSlackMetaMessages(conversationId: string): number {
1273
1254
  );
1274
1255
  if (candidates.length === 0) return count;
1275
1256
  for (const raw of candidates) {
1276
- let parent: Record<string, unknown> | null = null;
1277
- try {
1278
- const parsed = JSON.parse(raw) as unknown;
1279
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1280
- parent = parsed as Record<string, unknown>;
1281
- }
1282
- } catch {
1283
- continue;
1284
- }
1285
- if (!parent) continue;
1286
- const inner = parent.slackMeta;
1287
- if (typeof inner !== "string") continue;
1288
- if (readSlackMetadata(inner)) {
1257
+ if (readSlackMetadataFromMessageMetadata(raw)) {
1289
1258
  count++;
1290
1259
  if (count >= SLACK_DM_BACKFILL_WARM_THRESHOLD) return count;
1291
1260
  }
@@ -1296,44 +1265,110 @@ function countSlackMetaMessages(conversationId: string): number {
1296
1265
  return count;
1297
1266
  }
1298
1267
 
1268
+ function readSlackMetadataFromMessageMetadata(
1269
+ metadata: string | null | undefined,
1270
+ ): SlackMessageMetadata | null {
1271
+ if (!metadata) return null;
1272
+ let parent: Record<string, unknown> | null = null;
1273
+ try {
1274
+ const parsed = JSON.parse(metadata) as unknown;
1275
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1276
+ parent = parsed as Record<string, unknown>;
1277
+ }
1278
+ } catch {
1279
+ return null;
1280
+ }
1281
+ if (!parent) return null;
1282
+ const raw = parent.slackMeta;
1283
+ if (typeof raw !== "string") return null;
1284
+ return readSlackMetadata(raw);
1285
+ }
1286
+
1299
1287
  /**
1300
1288
  * Build the set of `slackMeta.channelTs` values already stored on a
1301
- * conversation. Used by both DM cold-start backfill and thread-ancestor
1289
+ * conversation. Used by both DM cold-start backfill and thread gap/delta
1302
1290
  * backfill to dedupe rows so a partial prior backfill (or a single message
1303
1291
  * that was already persisted via the live ingress path) does not double-write.
1304
1292
  */
1305
1293
  function readStoredSlackChannelTs(conversationId: string): Set<string> {
1306
1294
  const seen = new Set<string>();
1307
1295
  for (const row of getMessages(conversationId)) {
1308
- if (!row.metadata) continue;
1309
- let parent: Record<string, unknown> | null = null;
1310
- try {
1311
- const parsed = JSON.parse(row.metadata) as unknown;
1312
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1313
- parent = parsed as Record<string, unknown>;
1314
- }
1315
- } catch {
1316
- continue;
1317
- }
1318
- if (!parent) continue;
1319
- const raw = parent.slackMeta;
1320
- if (typeof raw !== "string") continue;
1321
- const meta = readSlackMetadata(raw);
1296
+ const meta = readSlackMetadataFromMessageMetadata(row.metadata);
1322
1297
  // Only message rows represent stored Slack messages. Reaction rows carry
1323
1298
  // `channelTs` equal to the target message's ts, so including them would
1324
- // make a reaction on a thread parent wrongly short-circuit ancestor
1299
+ // make a reaction on a thread parent wrongly short-circuit thread
1325
1300
  // backfill (the parent itself may still be unseen).
1326
1301
  if (meta && meta.eventKind === "message") seen.add(meta.channelTs);
1327
1302
  }
1328
1303
  return seen;
1329
1304
  }
1330
1305
 
1306
+ interface ParsedSlackTimestamp {
1307
+ seconds: bigint;
1308
+ micros: bigint;
1309
+ }
1310
+
1311
+ function parseSlackTimestamp(
1312
+ ts: string | undefined,
1313
+ ): ParsedSlackTimestamp | null {
1314
+ if (!ts) return null;
1315
+ const match = /^(\d+)\.(\d{1,6})$/.exec(ts);
1316
+ if (!match) return null;
1317
+ const micros = BigInt(match[2]);
1318
+ if (micros > 999_999n) return null;
1319
+ return {
1320
+ seconds: BigInt(match[1]),
1321
+ micros,
1322
+ };
1323
+ }
1324
+
1325
+ function compareSlackTimestamps(left: string, right: string): number | null {
1326
+ const parsedLeft = parseSlackTimestamp(left);
1327
+ const parsedRight = parseSlackTimestamp(right);
1328
+ if (!parsedLeft || !parsedRight) return null;
1329
+ if (parsedLeft.seconds < parsedRight.seconds) return -1;
1330
+ if (parsedLeft.seconds > parsedRight.seconds) return 1;
1331
+ if (parsedLeft.micros < parsedRight.micros) return -1;
1332
+ if (parsedLeft.micros > parsedRight.micros) return 1;
1333
+ return 0;
1334
+ }
1335
+
1336
+ interface StoredSlackThreadState {
1337
+ storedChannelTs: Set<string>;
1338
+ latestStoredThreadTs: string | undefined;
1339
+ }
1340
+
1341
+ function readStoredSlackThreadState(
1342
+ conversationId: string,
1343
+ threadTs: string,
1344
+ ): StoredSlackThreadState {
1345
+ const storedChannelTs = new Set<string>();
1346
+ let latestStoredThreadTs: string | undefined;
1347
+
1348
+ for (const row of getMessages(conversationId)) {
1349
+ const meta = readSlackMetadataFromMessageMetadata(row.metadata);
1350
+ if (!meta || meta.eventKind !== "message") continue;
1351
+ if (meta.channelTs !== threadTs && meta.threadTs !== threadTs) continue;
1352
+
1353
+ storedChannelTs.add(meta.channelTs);
1354
+ if (!parseSlackTimestamp(meta.channelTs)) continue;
1355
+ if (
1356
+ latestStoredThreadTs === undefined ||
1357
+ compareSlackTimestamps(meta.channelTs, latestStoredThreadTs) === 1
1358
+ ) {
1359
+ latestStoredThreadTs = meta.channelTs;
1360
+ }
1361
+ }
1362
+
1363
+ return { storedChannelTs, latestStoredThreadTs };
1364
+ }
1365
+
1331
1366
  /**
1332
1367
  * Persist a single backfilled Slack message as a `messages` row with a
1333
1368
  * `slackMeta` envelope.
1334
1369
  *
1335
1370
  * Shared insertion point for any path that hydrates Slack history lazily
1336
- * (DM cold-start backfill, thread-ancestor backfill, etc.). Role is derived
1371
+ * (DM cold-start backfill, thread gap/delta backfill, etc.). Role is derived
1337
1372
  * from `message.metadata.isBot` — bot-authored rows map to `"assistant"` so
1338
1373
  * our own prior replies (and any other bot traffic) are not rehydrated as
1339
1374
  * user turns, which would otherwise corrupt speaker attribution and make
@@ -1347,6 +1382,7 @@ async function persistBackfilledSlackMessage(params: {
1347
1382
  message: ProviderMessage;
1348
1383
  }): Promise<void> {
1349
1384
  const { message } = params;
1385
+ const slackFiles = readSlackFilesFromProviderMetadata(message.metadata);
1350
1386
  const slackMeta: SlackMessageMetadata = {
1351
1387
  source: "slack",
1352
1388
  channelId: params.channelId,
@@ -1354,6 +1390,7 @@ async function persistBackfilledSlackMessage(params: {
1354
1390
  eventKind: "message",
1355
1391
  ...(message.threadId ? { threadTs: message.threadId } : {}),
1356
1392
  ...(message.sender?.name ? { displayName: message.sender.name } : {}),
1393
+ ...(slackFiles.length > 0 ? { slackFiles } : {}),
1357
1394
  };
1358
1395
  const role = message.metadata?.isBot === true ? "assistant" : "user";
1359
1396
  await addMessage(params.conversationId, role, message.text ?? "", {
@@ -1361,6 +1398,32 @@ async function persistBackfilledSlackMessage(params: {
1361
1398
  });
1362
1399
  }
1363
1400
 
1401
+ function readSlackFilesFromProviderMetadata(
1402
+ metadata: Record<string, unknown> | undefined,
1403
+ ): SlackFileMetadata[] {
1404
+ const raw = metadata?.slackFiles;
1405
+ if (!Array.isArray(raw)) return [];
1406
+ const files: SlackFileMetadata[] = [];
1407
+ for (const item of raw) {
1408
+ if (item === null || typeof item !== "object" || Array.isArray(item)) {
1409
+ continue;
1410
+ }
1411
+ const record = item as Record<string, unknown>;
1412
+ const name = typeof record.name === "string" ? record.name.trim() : "";
1413
+ if (!name) continue;
1414
+ files.push({
1415
+ ...(typeof record.id === "string" && record.id.length > 0
1416
+ ? { id: record.id }
1417
+ : {}),
1418
+ name,
1419
+ ...(typeof record.mimetype === "string" && record.mimetype.length > 0
1420
+ ? { mimetype: record.mimetype }
1421
+ : {}),
1422
+ });
1423
+ }
1424
+ return files;
1425
+ }
1426
+
1364
1427
  /**
1365
1428
  * In-memory map of in-flight DM cold-start backfills keyed by conversationId.
1366
1429
  * Concurrent inbound DMs to the same cold conversation share a single
@@ -1503,13 +1566,11 @@ async function runBackfillSlackDmIfCold(params: {
1503
1566
  // ---------------------------------------------------------------------------
1504
1567
 
1505
1568
  /**
1506
- * In-memory TTL cache keyed by `<conversationId>:<threadTs>`. Tracks recent
1507
- * thread-backfill triggers so a burst of replies inside the same Slack
1508
- * thread (e.g. a guardian rapidly typing several lines) does not re-fetch
1509
- * the same parent messages from Slack repeatedly. Entries naturally fall
1510
- * out after the TTL — if the thread is still active later, a fresh
1511
- * backfill becomes a cheap "are the parents already stored?" DB lookup
1512
- * that short-circuits before the Slack API is touched.
1569
+ * In-memory TTL cache keyed by
1570
+ * `<conversationId>:<threadTs>:<lowerBoundTs>:<upperBoundTs>`. Tracks recent
1571
+ * thread-backfill windows so repeated triggers for the same Slack gap do not
1572
+ * re-fetch identical rows while later replies in the same thread can still
1573
+ * request newer unseen windows.
1513
1574
  *
1514
1575
  * Exported only for tests; production callers should use
1515
1576
  * {@link triggerSlackThreadBackfillIfNeeded}.
@@ -1518,6 +1579,39 @@ export const _backfillTriggerCache = new Map<string, number>();
1518
1579
 
1519
1580
  const BACKFILL_TRIGGER_TTL_MS = 10 * 60 * 1000; // 10 minutes
1520
1581
  const BACKFILL_TRIGGER_CACHE_MAX = 1_000;
1582
+ const SLACK_THREAD_INITIAL_EARLY_LIMIT = 25;
1583
+ const SLACK_THREAD_INITIAL_RECENT_LIMIT = 50;
1584
+ const SLACK_THREAD_INITIAL_RECENT_MAX_PAGES = 5;
1585
+ const SLACK_THREAD_DELTA_LIMIT = 50;
1586
+ const SLACK_THREAD_UPPER_ADJACENT_MAX_ATTEMPTS = 5;
1587
+ const MICROS_PER_SECOND = 1_000_000n;
1588
+ const SLACK_UPPER_ADJACENT_EXPANDING_WINDOWS_MICROS = [
1589
+ 5n * 60n * MICROS_PER_SECOND,
1590
+ 60n * 60n * MICROS_PER_SECOND,
1591
+ 24n * 60n * 60n * MICROS_PER_SECOND,
1592
+ 7n * 24n * 60n * 60n * MICROS_PER_SECOND,
1593
+ 30n * 24n * 60n * 60n * MICROS_PER_SECOND,
1594
+ ];
1595
+ const SLACK_UPPER_ADJACENT_SHRINKING_WINDOWS_MICROS = [
1596
+ 60n * MICROS_PER_SECOND,
1597
+ 10n * MICROS_PER_SECOND,
1598
+ MICROS_PER_SECOND,
1599
+ 100_000n,
1600
+ 1_000n,
1601
+ ];
1602
+
1603
+ export interface SlackThreadBackfillResult {
1604
+ fetched: number;
1605
+ persisted: number;
1606
+ reason?: SlackBackfillReason;
1607
+ omittedMiddle: boolean;
1608
+ }
1609
+
1610
+ type SlackBackfillReason = "thread_late_join" | "thread_delta";
1611
+
1612
+ function emptySlackThreadBackfillResult(): SlackThreadBackfillResult {
1613
+ return { fetched: 0, persisted: 0, omittedMiddle: false };
1614
+ }
1521
1615
 
1522
1616
  function pruneBackfillCacheIfNeeded(): void {
1523
1617
  if (_backfillTriggerCache.size < BACKFILL_TRIGGER_CACHE_MAX) return;
@@ -1552,23 +1646,315 @@ function isBackfillRecentlyTriggered(cacheKey: string): boolean {
1552
1646
  return true;
1553
1647
  }
1554
1648
 
1649
+ interface SlackInitialThreadWindowsResult {
1650
+ messages: ProviderMessage[];
1651
+ omittedMiddle: boolean;
1652
+ }
1653
+
1654
+ interface SlackUpperAdjacentWindowResult {
1655
+ messages: ProviderMessage[];
1656
+ omittedEarlierContent: boolean;
1657
+ truncatedBeforeUpperBound: boolean;
1658
+ }
1659
+
1660
+ function slackPageHasMore(page: SlackBackfillWindowPage): boolean {
1661
+ return page.hasMore || page.nextCursor !== undefined;
1662
+ }
1663
+
1664
+ function minSlackMessageTs(messages: ProviderMessage[]): string | undefined {
1665
+ return sortSlackProviderMessages(messages)[0]?.id;
1666
+ }
1667
+
1668
+ function maxSlackMessageTs(messages: ProviderMessage[]): string | undefined {
1669
+ const sorted = sortSlackProviderMessages(messages);
1670
+ return sorted[sorted.length - 1]?.id;
1671
+ }
1672
+
1673
+ function slackTimestampToMicros(ts: string | undefined): bigint | null {
1674
+ const parsed = parseSlackTimestamp(ts);
1675
+ if (!parsed) return null;
1676
+ return parsed.seconds * MICROS_PER_SECOND + parsed.micros;
1677
+ }
1678
+
1679
+ function slackTimestampFromMicros(totalMicros: bigint): string | undefined {
1680
+ if (totalMicros < 0n) return undefined;
1681
+ const seconds = totalMicros / MICROS_PER_SECOND;
1682
+ const micros = totalMicros % MICROS_PER_SECOND;
1683
+ return `${seconds.toString()}.${micros.toString().padStart(6, "0")}`;
1684
+ }
1685
+
1686
+ function didInitialWindowsLeaveGap(params: {
1687
+ early: SlackBackfillWindowPage;
1688
+ recent: SlackBackfillWindowPage;
1689
+ recentScanTruncated: boolean;
1690
+ }): boolean {
1691
+ if (params.recentScanTruncated) return true;
1692
+ if (!slackPageHasMore(params.early)) return false;
1693
+ const earlyMax = maxSlackMessageTs(params.early.messages);
1694
+ const recentMin = minSlackMessageTs(params.recent.messages);
1695
+ if (!earlyMax || !recentMin) return false;
1696
+ const compared = compareSlackTimestamps(earlyMax, recentMin);
1697
+ return compared !== null && compared < 0;
1698
+ }
1699
+
1700
+ async function fetchSlackThreadUpperAdjacentWindow(params: {
1701
+ channelId: string;
1702
+ threadTs: string;
1703
+ upperBoundTs: string;
1704
+ lowerBoundTs?: string;
1705
+ limit: number;
1706
+ account?: string;
1707
+ maxAttempts?: number;
1708
+ }): Promise<SlackUpperAdjacentWindowResult> {
1709
+ // Slack returns bounded conversations.replies pages earliest-first. To keep
1710
+ // the context closest to the inbound mention, narrow by timestamp instead
1711
+ // of cursoring forward from the oldest page in the bounded range.
1712
+ const upperMicros = slackTimestampToMicros(params.upperBoundTs);
1713
+ if (upperMicros === null) {
1714
+ const page = await backfillThreadWindowPage(
1715
+ params.channelId,
1716
+ params.threadTs,
1717
+ {
1718
+ limit: params.limit,
1719
+ account: params.account,
1720
+ before: params.upperBoundTs,
1721
+ ...(params.lowerBoundTs !== undefined
1722
+ ? { after: params.lowerBoundTs }
1723
+ : {}),
1724
+ },
1725
+ );
1726
+ return {
1727
+ messages: page.messages,
1728
+ omittedEarlierContent: slackPageHasMore(page),
1729
+ truncatedBeforeUpperBound: slackPageHasMore(page),
1730
+ };
1731
+ }
1732
+
1733
+ const lowerMicros = slackTimestampToMicros(params.lowerBoundTs);
1734
+ const maxAttempts =
1735
+ params.maxAttempts ?? SLACK_THREAD_UPPER_ADJACENT_MAX_ATTEMPTS;
1736
+ let attempts = 0;
1737
+ let safePage: SlackBackfillWindowPage | undefined;
1738
+ let safeAfterTs: string | undefined;
1739
+ let truncatedBeforeUpperBound = false;
1740
+
1741
+ const fetchWindow = async (
1742
+ windowMicros: bigint,
1743
+ ): Promise<{
1744
+ page: SlackBackfillWindowPage;
1745
+ after?: string;
1746
+ reachedLowerBound: boolean;
1747
+ }> => {
1748
+ let candidateMicros = upperMicros - windowMicros;
1749
+ let reachedLowerBound = false;
1750
+ if (lowerMicros !== null && candidateMicros <= lowerMicros) {
1751
+ candidateMicros = lowerMicros;
1752
+ reachedLowerBound = true;
1753
+ }
1754
+ const after = reachedLowerBound
1755
+ ? params.lowerBoundTs
1756
+ : slackTimestampFromMicros(candidateMicros);
1757
+ const page = await backfillThreadWindowPage(
1758
+ params.channelId,
1759
+ params.threadTs,
1760
+ {
1761
+ limit: params.limit,
1762
+ account: params.account,
1763
+ before: params.upperBoundTs,
1764
+ ...(after !== undefined ? { after } : {}),
1765
+ },
1766
+ );
1767
+ attempts++;
1768
+ return { page, after, reachedLowerBound };
1769
+ };
1770
+
1771
+ const considerWindow = async (windowMicros: bigint): Promise<boolean> => {
1772
+ const { page, after, reachedLowerBound } = await fetchWindow(windowMicros);
1773
+ if (slackPageHasMore(page)) {
1774
+ truncatedBeforeUpperBound = true;
1775
+ return false;
1776
+ }
1777
+
1778
+ safePage = page;
1779
+ safeAfterTs = after;
1780
+ return page.messages.length < params.limit && !reachedLowerBound;
1781
+ };
1782
+
1783
+ for (const windowMicros of SLACK_UPPER_ADJACENT_EXPANDING_WINDOWS_MICROS) {
1784
+ if (attempts >= maxAttempts) break;
1785
+ const shouldExpand = await considerWindow(windowMicros);
1786
+ if (!shouldExpand) break;
1787
+ }
1788
+
1789
+ if (truncatedBeforeUpperBound && !safePage && attempts < maxAttempts) {
1790
+ for (const windowMicros of SLACK_UPPER_ADJACENT_SHRINKING_WINDOWS_MICROS) {
1791
+ if (attempts >= maxAttempts) break;
1792
+ await considerWindow(windowMicros);
1793
+ if (safePage) break;
1794
+ }
1795
+ }
1796
+
1797
+ if (!safePage) {
1798
+ const after = slackTimestampFromMicros(upperMicros - 2n);
1799
+ const page = await backfillThreadWindowPage(
1800
+ params.channelId,
1801
+ params.threadTs,
1802
+ {
1803
+ limit: params.limit,
1804
+ account: params.account,
1805
+ before: params.upperBoundTs,
1806
+ ...(after !== undefined ? { after } : {}),
1807
+ },
1808
+ );
1809
+ safePage = page;
1810
+ safeAfterTs = after;
1811
+ truncatedBeforeUpperBound =
1812
+ truncatedBeforeUpperBound || slackPageHasMore(page);
1813
+ }
1814
+ if (!safePage) {
1815
+ return {
1816
+ messages: [],
1817
+ omittedEarlierContent: true,
1818
+ truncatedBeforeUpperBound: true,
1819
+ };
1820
+ }
1821
+
1822
+ let omittedEarlierContent = truncatedBeforeUpperBound;
1823
+ if (
1824
+ !omittedEarlierContent &&
1825
+ params.lowerBoundTs !== undefined &&
1826
+ safeAfterTs !== undefined &&
1827
+ compareSlackTimestamps(params.lowerBoundTs, safeAfterTs) === -1
1828
+ ) {
1829
+ const coverageProbe = await backfillThreadWindowPage(
1830
+ params.channelId,
1831
+ params.threadTs,
1832
+ {
1833
+ limit: 1,
1834
+ account: params.account,
1835
+ after: params.lowerBoundTs,
1836
+ before: safeAfterTs,
1837
+ },
1838
+ );
1839
+ omittedEarlierContent =
1840
+ coverageProbe.messages.length > 0 || slackPageHasMore(coverageProbe);
1841
+ }
1842
+
1843
+ return {
1844
+ messages: safePage.messages,
1845
+ omittedEarlierContent,
1846
+ truncatedBeforeUpperBound,
1847
+ };
1848
+ }
1849
+
1850
+ async function fetchInitialSlackThreadWindows(params: {
1851
+ channelId: string;
1852
+ threadTs: string;
1853
+ upperBoundTs?: string;
1854
+ account?: string;
1855
+ }): Promise<SlackInitialThreadWindowsResult> {
1856
+ if (!params.upperBoundTs) {
1857
+ const early = await backfillThreadWindowPage(
1858
+ params.channelId,
1859
+ params.threadTs,
1860
+ {
1861
+ limit: SLACK_THREAD_INITIAL_EARLY_LIMIT,
1862
+ account: params.account,
1863
+ },
1864
+ );
1865
+ return {
1866
+ messages: sortSlackProviderMessages(
1867
+ dedupeSlackProviderMessages(early.messages),
1868
+ ),
1869
+ omittedMiddle: slackPageHasMore(early),
1870
+ };
1871
+ }
1872
+ const [early, recentResult] = await Promise.all([
1873
+ backfillThreadWindowPage(params.channelId, params.threadTs, {
1874
+ limit: SLACK_THREAD_INITIAL_EARLY_LIMIT,
1875
+ account: params.account,
1876
+ }),
1877
+ fetchSlackThreadUpperAdjacentWindow({
1878
+ channelId: params.channelId,
1879
+ threadTs: params.threadTs,
1880
+ account: params.account,
1881
+ upperBoundTs: params.upperBoundTs,
1882
+ limit: SLACK_THREAD_INITIAL_RECENT_LIMIT,
1883
+ maxAttempts: SLACK_THREAD_INITIAL_RECENT_MAX_PAGES,
1884
+ }),
1885
+ ]);
1886
+ const recent: SlackBackfillWindowPage = {
1887
+ messages: recentResult.messages,
1888
+ hasMore: recentResult.truncatedBeforeUpperBound,
1889
+ };
1890
+ return {
1891
+ messages: sortSlackProviderMessages(
1892
+ dedupeSlackProviderMessages([...early.messages, ...recent.messages]),
1893
+ ),
1894
+ omittedMiddle:
1895
+ recentResult.omittedEarlierContent ||
1896
+ didInitialWindowsLeaveGap({
1897
+ early,
1898
+ recent,
1899
+ recentScanTruncated: recentResult.truncatedBeforeUpperBound,
1900
+ }),
1901
+ };
1902
+ }
1903
+
1904
+ function dedupeSlackProviderMessages(
1905
+ messages: ProviderMessage[],
1906
+ ): ProviderMessage[] {
1907
+ const byTs = new Map<string, ProviderMessage>();
1908
+ for (const message of messages) {
1909
+ if (!message.id || byTs.has(message.id)) continue;
1910
+ byTs.set(message.id, message);
1911
+ }
1912
+ return [...byTs.values()];
1913
+ }
1914
+
1915
+ function sortSlackProviderMessages(
1916
+ messages: ProviderMessage[],
1917
+ ): ProviderMessage[] {
1918
+ return [...messages].sort((left, right) => {
1919
+ const compared = compareSlackTimestamps(left.id, right.id);
1920
+ if (compared !== null) return compared;
1921
+ return left.id.localeCompare(right.id);
1922
+ });
1923
+ }
1924
+
1925
+ function buildSlackLateJoinNotice(
1926
+ result: SlackThreadBackfillResult,
1927
+ ): string | null {
1928
+ if (result.reason !== "thread_late_join" || result.persisted === 0) {
1929
+ return null;
1930
+ }
1931
+ const omitted = result.omittedMiddle
1932
+ ? " Some middle thread messages were intentionally omitted from this turn's hydrated context to keep latency bounded."
1933
+ : "";
1934
+ return `Slack context note: this turn joined an existing thread. ${result.persisted} earlier thread message${result.persisted === 1 ? " was" : "s were"} backfilled before the current message.${omitted}`;
1935
+ }
1936
+
1555
1937
  /**
1556
- * Lazily backfill missing Slack thread ancestors for an inbound thread reply.
1938
+ * Lazily backfill Slack thread gaps for an inbound thread reply.
1557
1939
  *
1558
- * When a reply arrives for a thread the daemon has never seen (e.g. the bot
1559
- * was just added to the channel, or the parent message pre-dates the
1560
- * conversation), the daemon fetches the thread's recent history via
1561
- * {@link backfillThread}, persists each unseen message as a `messages` row
1562
- * with a `slackMeta` envelope, and skips duplicates whose `ts` already
1563
- * appears in the conversation.
1940
+ * When a reply arrives for a thread with unseen Slack history, the assistant
1941
+ * fetches bounded `conversations.replies` pages via
1942
+ * {@link backfillThreadWindowPage}, persists each unseen message as a
1943
+ * `messages` row with a `slackMeta` envelope, and skips duplicates whose `ts`
1944
+ * already appears in the conversation.
1564
1945
  *
1565
1946
  * Behavior contracts:
1566
- * - **No-op when the parent is already stored.** Looks up the conversation's
1567
- * messages and short-circuits if any row has `slackMeta.channelTs ===
1568
- * threadTs`. This keeps subsequent replies in the same thread cheap.
1569
- * - **TTL idempotency cache.** A 10-minute in-memory cache prevents bursts
1570
- * of replies in the same thread from re-running the DB lookup or the
1571
- * Slack API call.
1947
+ * - **Thread-state gap detection.** Looks up stored Slack message rows for
1948
+ * the same thread, excluding reactions, then fetches only the unseen
1949
+ * `(latestStoredThreadTs, excludeChannelTs)` window when the inbound Slack
1950
+ * timestamp is newer than local state.
1951
+ * - **Upper-bound windows.** Initial late-join backfill combines an early
1952
+ * thread page with a recent page adjacent to the inbound ts; delta backfill
1953
+ * fetches the page nearest the inbound upper bound so the current turn sees
1954
+ * the most relevant context while keeping latency bounded.
1955
+ * - **Exact-window TTL cache.** A 10-minute in-memory cache prevents repeated
1956
+ * fetches for the same exact lower/upper bounded window, without
1957
+ * suppressing later unseen windows in the same thread.
1572
1958
  * - **Failure-tolerant.** Any error (Slack API failure, DB error, malformed
1573
1959
  * payload) is logged at `warn` and swallowed — the inbound turn must
1574
1960
  * never block on backfill.
@@ -1583,65 +1969,106 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
1583
1969
  * `conversations.replies` returns it in the thread window. Necessary
1584
1970
  * because thread backfill runs concurrently with
1585
1971
  * `processChannelMessageInBackground`, so the inbound row may not yet be
1586
- * in the DB when `readStoredSlackChannelTs` snapshots the conversation.
1972
+ * in the DB when the thread-state scan snapshots the conversation.
1587
1973
  */
1588
1974
  excludeChannelTs?: string;
1589
1975
  /**
1590
1976
  * OAuth account identifier used to disambiguate which Slack workspace the
1591
1977
  * backfill should read from in multi-account setups. Passed through to
1592
- * `backfillThread` `resolveConnection`. Best-effort: if omitted, the
1593
- * resolver falls back to the default-active connection.
1978
+ * `backfillThreadWindowPage` page requests and then `resolveConnection`.
1979
+ * Best-effort: if omitted, the resolver falls back to the default-active
1980
+ * connection.
1594
1981
  */
1595
1982
  account?: string;
1596
- }): Promise<void> {
1983
+ }): Promise<SlackThreadBackfillResult> {
1597
1984
  const { conversationId, channelId, threadTs, excludeChannelTs, account } =
1598
1985
  params;
1599
- const cacheKey = `${conversationId}:${threadTs}`;
1600
1986
 
1601
1987
  try {
1602
- if (isBackfillRecentlyTriggered(cacheKey)) {
1603
- return;
1988
+ const upperBoundTs = parseSlackTimestamp(excludeChannelTs)
1989
+ ? excludeChannelTs
1990
+ : undefined;
1991
+ const threadState = readStoredSlackThreadState(conversationId, threadTs);
1992
+ const lowerBoundTs = threadState.latestStoredThreadTs;
1993
+
1994
+ // Pre-seed only after computing lowerBoundTs. The current inbound row
1995
+ // may not have reached the DB yet, and treating it as stored state would
1996
+ // hide the gap we need to fetch.
1997
+ if (excludeChannelTs) threadState.storedChannelTs.add(excludeChannelTs);
1998
+
1999
+ if (upperBoundTs && lowerBoundTs) {
2000
+ const lowerVsUpper = compareSlackTimestamps(lowerBoundTs, upperBoundTs);
2001
+ if (lowerVsUpper !== null && lowerVsUpper >= 0) {
2002
+ return emptySlackThreadBackfillResult();
2003
+ }
2004
+ } else if (!upperBoundTs && lowerBoundTs) {
2005
+ return emptySlackThreadBackfillResult();
1604
2006
  }
1605
2007
 
1606
- const storedChannelTs = readStoredSlackChannelTs(conversationId);
1607
- if (excludeChannelTs) storedChannelTs.add(excludeChannelTs);
1608
- if (storedChannelTs.has(threadTs)) {
1609
- // Parent is already in the conversation; mark the cache so a burst of
1610
- // replies in this thread does not redo the DB scan for each one.
1611
- _backfillTriggerCache.set(cacheKey, Date.now());
1612
- pruneBackfillCacheIfNeeded();
1613
- return;
2008
+ const cacheKey = `${conversationId}:${threadTs}:${
2009
+ lowerBoundTs ?? "none"
2010
+ }:${upperBoundTs ?? "unbounded"}`;
2011
+ if (isBackfillRecentlyTriggered(cacheKey)) {
2012
+ return emptySlackThreadBackfillResult();
1614
2013
  }
1615
2014
 
1616
2015
  // Mark the trigger before issuing the network call. Doing this first
1617
- // means a second concurrent reply in the same thread short-circuits
1618
- // immediately even while the first call is still awaiting the Slack
1619
- // API. The cost is a slightly larger window where a transient Slack
1620
- // failure suppresses a retry, which the next reply outside the TTL
1621
- // (or a daemon restart) will re-attempt anyway.
2016
+ // means a second concurrent request for the same window short-circuits
2017
+ // immediately even while the first call is still awaiting the Slack API.
2018
+ // The cost is a slightly larger window where a transient Slack failure
2019
+ // suppresses a retry, which the next reply outside the TTL (or a daemon
2020
+ // restart) will re-attempt anyway.
1622
2021
  _backfillTriggerCache.set(cacheKey, Date.now());
1623
2022
  pruneBackfillCacheIfNeeded();
1624
2023
 
1625
- const fetched = await backfillThread(channelId, threadTs, { account });
2024
+ const isInitialLateJoin =
2025
+ lowerBoundTs === undefined &&
2026
+ threadState.storedChannelTs.size === (excludeChannelTs ? 1 : 0);
2027
+ const reason: SlackBackfillReason = isInitialLateJoin
2028
+ ? "thread_late_join"
2029
+ : "thread_delta";
2030
+ let omittedMiddle = false;
2031
+ let fetched: ProviderMessage[];
2032
+ if (isInitialLateJoin) {
2033
+ const initial = await fetchInitialSlackThreadWindows({
2034
+ channelId,
2035
+ threadTs,
2036
+ upperBoundTs,
2037
+ account,
2038
+ });
2039
+ fetched = initial.messages;
2040
+ omittedMiddle = initial.omittedMiddle;
2041
+ } else {
2042
+ const window = await fetchSlackThreadUpperAdjacentWindow({
2043
+ channelId,
2044
+ threadTs,
2045
+ limit: SLACK_THREAD_DELTA_LIMIT,
2046
+ account,
2047
+ ...(lowerBoundTs !== undefined ? { lowerBoundTs } : {}),
2048
+ upperBoundTs: upperBoundTs ?? threadTs,
2049
+ });
2050
+ fetched = window.messages;
2051
+ omittedMiddle = window.omittedEarlierContent;
2052
+ }
1626
2053
  if (fetched.length === 0) {
1627
2054
  log.debug(
1628
2055
  { conversationId, channelId, threadTs },
1629
2056
  "Slack thread backfill returned no messages",
1630
2057
  );
1631
- return;
2058
+ return emptySlackThreadBackfillResult();
1632
2059
  }
1633
2060
 
1634
2061
  let persisted = 0;
1635
2062
  for (const message of fetched) {
1636
2063
  if (!message.id) continue;
1637
- if (storedChannelTs.has(message.id)) continue;
2064
+ if (threadState.storedChannelTs.has(message.id)) continue;
1638
2065
  try {
1639
2066
  await persistBackfilledSlackMessage({
1640
2067
  conversationId,
1641
2068
  channelId,
1642
2069
  message,
1643
2070
  });
1644
- storedChannelTs.add(message.id);
2071
+ threadState.storedChannelTs.add(message.id);
1645
2072
  persisted++;
1646
2073
  } catch (err) {
1647
2074
  log.warn(
@@ -1658,15 +2085,22 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
1658
2085
  threadTs,
1659
2086
  persisted,
1660
2087
  fetched: fetched.length,
2088
+ omittedMiddle,
1661
2089
  },
1662
- "Slack thread backfill persisted ancestor messages",
2090
+ "Slack thread backfill persisted thread messages",
1663
2091
  );
2092
+ return {
2093
+ fetched: fetched.length,
2094
+ persisted,
2095
+ reason,
2096
+ omittedMiddle,
2097
+ };
1664
2098
  } catch (err) {
1665
2099
  // `channel_not_found` almost always means the resolved connection is
1666
2100
  // pointing at the wrong Slack workspace (a real config bug), so log it
1667
2101
  // at ERROR to match backfill's rethrow contract. Other failures
1668
2102
  // (timeout, auth, ratelimited, …) stay at WARN — they're expected
1669
- // transient blips and dispatch proceeds without the ancestors.
2103
+ // transient blips and dispatch proceeds without the backfilled thread rows.
1670
2104
  const channelNotFound =
1671
2105
  err instanceof Error && /channel_not_found/i.test(err.message);
1672
2106
  const payload = { err, conversationId, channelId, threadTs, account };
@@ -1678,5 +2112,6 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
1678
2112
  } else {
1679
2113
  log.warn(payload, "Slack thread backfill failed; proceeding without it");
1680
2114
  }
2115
+ return emptySlackThreadBackfillResult();
1681
2116
  }
1682
2117
  }