@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
@@ -3,13 +3,13 @@
3
3
  * backfill of the missing thread ancestors when the conversation has no
4
4
  * record of the parent message, persists each backfilled message with a
5
5
  * derived `slackMeta` envelope, de-dupes against rows already stored, and
6
- * gates re-triggers behind a 10-minute idempotency cache so bursts of
7
- * replies in the same thread do not flood the Slack API.
6
+ * gates exact-window re-triggers behind a 10-minute idempotency cache so
7
+ * bursts of retries for the same gap do not flood the Slack API.
8
8
  *
9
9
  * Tests exercise the helper {@link triggerSlackThreadBackfillIfNeeded}
10
10
  * directly against the real database (via the test-preload temp workspace).
11
- * Only `backfillThread` is mocked, since the contract under test is "given
12
- * what Slack returns, what does the daemon write to the DB".
11
+ * Only the Slack backfill read is mocked, since the contract under test is
12
+ * "given what Slack returns, what does the daemon write to the DB".
13
13
  */
14
14
  import {
15
15
  afterAll,
@@ -23,7 +23,7 @@ import {
23
23
  } from "bun:test";
24
24
 
25
25
  // ---------------------------------------------------------------------------
26
- // Mocks (must precede module imports under test). Note: backfillThread is
26
+ // Mocks (must precede module imports under test). Note: backfillThreadWindow is
27
27
  // stubbed via spyOn (below) rather than mock.module so the stub does not leak
28
28
  // into other test files (e.g. backfill.test.ts) that import the same module.
29
29
  // ---------------------------------------------------------------------------
@@ -58,6 +58,11 @@ mock.module("../runtime/gateway-client.js", () => ({
58
58
  import { v4 as uuid } from "uuid";
59
59
 
60
60
  import { upsertContactChannel } from "../contacts/contacts-write.js";
61
+ import {
62
+ type ChannelCapabilities,
63
+ loadSlackChronologicalContext,
64
+ } from "../daemon/conversation-runtime-assembly.js";
65
+ import type { MessageRow } from "../memory/conversation-crud.js";
61
66
  import { getDb } from "../memory/db-connection.js";
62
67
  import { initializeDb } from "../memory/db-init.js";
63
68
  import type { Message as MessagingMessage } from "../messaging/provider-types.js";
@@ -66,22 +71,39 @@ import {
66
71
  readSlackMetadata,
67
72
  writeSlackMetadata,
68
73
  } from "../messaging/providers/slack/message-metadata.js";
74
+ import type { Message } from "../providers/types.js";
69
75
  import {
70
76
  _backfillTriggerCache,
71
77
  triggerSlackThreadBackfillIfNeeded,
72
78
  } from "../runtime/routes/inbound-message-handler.js";
73
- import { handleChannelInbound } from "./helpers/channel-test-adapter.js";
79
+ import {
80
+ handleChannelInbound,
81
+ setAdapterProcessMessage,
82
+ } from "./helpers/channel-test-adapter.js";
74
83
 
75
84
  initializeDb();
76
85
 
77
- // Spy on backfillThread so the stub is scoped to this test file only.
78
- // Restoring after the file's tests run keeps cross-file leakage to zero —
79
- // other tests (e.g. backfill.test.ts) keep seeing the real implementation.
80
- const backfillThreadMock = spyOn(slackBackfill, "backfillThread");
86
+ // Spy on backfillThreadWindowPage so the stub is scoped to this test file
87
+ // only. Existing tests drive the message array through `backfillThreadMock`;
88
+ // page metadata defaults to "complete" unless a test overrides the page spy.
89
+ const backfillThreadMock = mock<typeof slackBackfill.backfillThreadWindow>(
90
+ async () => [],
91
+ );
92
+ const backfillThreadPageMock = spyOn(slackBackfill, "backfillThreadWindowPage");
93
+ function installDefaultThreadPageMock(): void {
94
+ backfillThreadPageMock.mockImplementation(async (...args) => ({
95
+ messages: await backfillThreadMock(...args),
96
+ hasMore: false,
97
+ }));
98
+ }
99
+ installDefaultThreadPageMock();
81
100
  backfillThreadMock.mockResolvedValue([]);
101
+ const backfillDmMock = spyOn(slackBackfill, "backfillDm");
102
+ backfillDmMock.mockResolvedValue([]);
82
103
 
83
104
  afterAll(() => {
84
- backfillThreadMock.mockRestore();
105
+ backfillThreadPageMock.mockRestore();
106
+ backfillDmMock.mockRestore();
85
107
  });
86
108
 
87
109
  // ---------------------------------------------------------------------------
@@ -105,6 +127,8 @@ function resetState(): void {
105
127
  _backfillTriggerCache.clear();
106
128
  backfillThreadMock.mockReset();
107
129
  backfillThreadMock.mockImplementation(async () => []);
130
+ backfillDmMock.mockReset();
131
+ backfillDmMock.mockImplementation(async () => []);
108
132
  }
109
133
 
110
134
  let convCounter = 0;
@@ -137,7 +161,7 @@ function insertMessage(
137
161
  role: string,
138
162
  content: string,
139
163
  metadata?: Record<string, unknown>,
140
- ): void {
164
+ ): string {
141
165
  const db = getDb();
142
166
  const id = uuid();
143
167
  // Use a strictly increasing timestamp so the ORDER BY in
@@ -152,6 +176,7 @@ function insertMessage(
152
176
  VALUES (?, ?, ?, ?, ?, ?)`,
153
177
  )
154
178
  .run(id, conversationId, role, content, now, metadataStr);
179
+ return id;
155
180
  }
156
181
 
157
182
  interface RawMessageRow {
@@ -169,6 +194,19 @@ function readMessagesByConversation(conversationId: string): RawMessageRow[] {
169
194
  .all(conversationId) as RawMessageRow[];
170
195
  }
171
196
 
197
+ function readMessageRowsByConversation(conversationId: string): MessageRow[] {
198
+ const db = getDb();
199
+ return db.$client
200
+ .prepare(
201
+ `SELECT id, conversation_id AS conversationId, role, content,
202
+ created_at AS createdAt, metadata
203
+ FROM messages
204
+ WHERE conversation_id = ?
205
+ ORDER BY created_at ASC`,
206
+ )
207
+ .all(conversationId) as MessageRow[];
208
+ }
209
+
172
210
  function makeBackfillMessage(
173
211
  overrides: Partial<MessagingMessage> = {},
174
212
  ): MessagingMessage {
@@ -190,6 +228,7 @@ interface PersistedRow {
190
228
  channelTs: string | undefined;
191
229
  threadTs: string | undefined;
192
230
  displayName: string | undefined;
231
+ slackFiles: Array<{ name: string; mimetype?: string }> | undefined;
193
232
  }
194
233
 
195
234
  function readPersistedSlackRows(conversationId: string): PersistedRow[] {
@@ -202,6 +241,7 @@ function readPersistedSlackRows(conversationId: string): PersistedRow[] {
202
241
  channelTs: undefined,
203
242
  threadTs: undefined,
204
243
  displayName: undefined,
244
+ slackFiles: undefined,
205
245
  };
206
246
  if (!row.metadata) {
207
247
  out.push(blank);
@@ -235,6 +275,10 @@ function readPersistedSlackRows(conversationId: string): PersistedRow[] {
235
275
  channelTs: slackMeta?.channelTs,
236
276
  threadTs: slackMeta?.threadTs,
237
277
  displayName: slackMeta?.displayName,
278
+ slackFiles: slackMeta?.slackFiles?.map((file) => ({
279
+ name: file.name,
280
+ ...(file.mimetype ? { mimetype: file.mimetype } : {}),
281
+ })),
238
282
  });
239
283
  }
240
284
  return out;
@@ -268,6 +312,7 @@ describe("triggerSlackThreadBackfillIfNeeded — gap detection and persistence",
268
312
 
269
313
  afterEach(() => {
270
314
  backfillThreadMock.mockReset();
315
+ installDefaultThreadPageMock();
271
316
  _backfillTriggerCache.clear();
272
317
  });
273
318
 
@@ -325,11 +370,297 @@ describe("triggerSlackThreadBackfillIfNeeded — gap detection and persistence",
325
370
  expect(byChannelTs.get("1234.2")?.displayName).toBe("Reply Two");
326
371
  });
327
372
 
328
- test("backfill is NOT triggered when the parent is already persisted", async () => {
373
+ test("initial late-join backfill keeps the newest bounded page before the inbound mention", async () => {
374
+ const conv = createTestConversation();
375
+ const ts = (n: number) => `1700000000.${String(n).padStart(6, "0")}`;
376
+ const inboundTs = ts(500000);
377
+
378
+ backfillThreadPageMock.mockImplementation(async (...args) => {
379
+ const messages = await backfillThreadMock(...args);
380
+ const opts = args[2];
381
+ if (opts?.limit === 25) {
382
+ return { messages, hasMore: true, nextCursor: "early-page-2" };
383
+ }
384
+ return { messages, hasMore: false };
385
+ });
386
+ backfillThreadMock.mockImplementation(async (_channel, _thread, opts) => {
387
+ if (opts?.limit === 25) {
388
+ return Array.from({ length: 25 }, (_, i) =>
389
+ makeBackfillMessage({
390
+ id: ts(i),
391
+ text: i === 0 ? "root context" : `early ${i}`,
392
+ threadId: i === 0 ? undefined : ts(0),
393
+ }),
394
+ );
395
+ }
396
+ if (opts?.before === inboundTs && opts.after !== undefined) {
397
+ return [
398
+ ...Array.from({ length: 50 }, (_, i) => {
399
+ const n = 499950 + i;
400
+ return makeBackfillMessage({
401
+ id: ts(n),
402
+ text: n === 499999 ? "newest file share" : `recent ${n}`,
403
+ threadId: ts(0),
404
+ ...(n === 499999
405
+ ? {
406
+ metadata: {
407
+ slackFiles: [
408
+ {
409
+ id: "F123",
410
+ name: "requirements.txt",
411
+ mimetype: "text/plain",
412
+ },
413
+ ],
414
+ },
415
+ }
416
+ : {}),
417
+ });
418
+ }),
419
+ makeBackfillMessage({
420
+ id: ts(499960),
421
+ text: "duplicate recent row",
422
+ threadId: ts(0),
423
+ }),
424
+ ];
425
+ }
426
+ return [];
427
+ });
428
+
429
+ const result = await triggerSlackThreadBackfillIfNeeded({
430
+ conversationId: conv.id,
431
+ channelId: SLACK_CHANNEL_ID,
432
+ threadTs: ts(0),
433
+ excludeChannelTs: inboundTs,
434
+ });
435
+
436
+ expect(backfillThreadMock).toHaveBeenCalledTimes(2);
437
+ expect(backfillThreadMock.mock.calls[0][2]?.limit).toBe(25);
438
+ expect(backfillThreadMock.mock.calls[0][2]?.before).toBeUndefined();
439
+ expect(backfillThreadMock.mock.calls[1][2]?.limit).toBe(50);
440
+ expect(backfillThreadMock.mock.calls[1][2]?.before).toBe(inboundTs);
441
+ expect(backfillThreadMock.mock.calls[1][2]?.after).toBeDefined();
442
+
443
+ expect(result.reason).toBe("thread_late_join");
444
+ expect(result.omittedMiddle).toBe(true);
445
+
446
+ const persisted = readPersistedSlackRows(conv.id);
447
+ expect(persisted.length).toBe(75);
448
+ expect(persisted.find((p) => p.channelTs === ts(0))?.content).toBe(
449
+ "root context",
450
+ );
451
+ expect(persisted.find((p) => p.channelTs === ts(250000))).toBeUndefined();
452
+ expect(persisted.find((p) => p.channelTs === ts(499999))?.content).toBe(
453
+ "newest file share",
454
+ );
455
+ expect(
456
+ persisted.filter((p) => p.channelTs === ts(499960)).map((p) => p.content),
457
+ ).toEqual(["recent 499960"]);
458
+ expect(
459
+ persisted.find((p) => p.channelTs === ts(499999))?.slackFiles,
460
+ ).toEqual([{ name: "requirements.txt", mimetype: "text/plain" }]);
461
+ });
462
+
463
+ test("high-throughput initial backfill keeps shrinking after a truncated probe and persists newest pre-mention rows", async () => {
464
+ const conv = createTestConversation();
465
+ const ts = (seconds: number, micros = 0) =>
466
+ `${seconds}.${String(micros).padStart(6, "0")}`;
467
+ const threadTs = ts(1700000000);
468
+ const inboundTs = ts(1700001000);
469
+ const fiveMinuteAfter = ts(1700000700);
470
+ const sixtySecondAfter = ts(1700000940);
471
+ const tenSecondAfter = ts(1700000990);
472
+ const newestPreMention = [
473
+ makeBackfillMessage({
474
+ id: ts(1700000997, 100000),
475
+ text: "newest context 1",
476
+ threadId: threadTs,
477
+ }),
478
+ makeBackfillMessage({
479
+ id: ts(1700000998, 200000),
480
+ text: "newest context 2",
481
+ threadId: threadTs,
482
+ }),
483
+ makeBackfillMessage({
484
+ id: ts(1700000999, 300000),
485
+ text: "newest context 3",
486
+ threadId: threadTs,
487
+ }),
488
+ ];
489
+
490
+ backfillThreadPageMock.mockImplementation(
491
+ async (_channel, _thread, opts) => {
492
+ if (opts?.limit === 25 && opts.before === undefined) {
493
+ return {
494
+ messages: [
495
+ makeBackfillMessage({
496
+ id: threadTs,
497
+ text: "thread parent",
498
+ threadId: undefined,
499
+ }),
500
+ ],
501
+ hasMore: true,
502
+ };
503
+ }
504
+
505
+ if (opts?.limit === 50 && opts.before === inboundTs) {
506
+ if (
507
+ opts.after === fiveMinuteAfter ||
508
+ opts.after === sixtySecondAfter
509
+ ) {
510
+ return {
511
+ messages: Array.from({ length: 50 }, (_, i) =>
512
+ makeBackfillMessage({
513
+ id: ts(1700000940 + i, i),
514
+ text: `truncated high-throughput ${i}`,
515
+ threadId: threadTs,
516
+ }),
517
+ ),
518
+ hasMore: true,
519
+ nextCursor: "still-truncated",
520
+ };
521
+ }
522
+
523
+ if (opts.after === tenSecondAfter) {
524
+ return { messages: newestPreMention, hasMore: false };
525
+ }
526
+ }
527
+
528
+ return { messages: [], hasMore: false };
529
+ },
530
+ );
531
+
532
+ const pageCallOffset = backfillThreadPageMock.mock.calls.length;
533
+
534
+ const result = await triggerSlackThreadBackfillIfNeeded({
535
+ conversationId: conv.id,
536
+ channelId: SLACK_CHANNEL_ID,
537
+ threadTs,
538
+ excludeChannelTs: inboundTs,
539
+ });
540
+
541
+ const afterAttempts = backfillThreadPageMock.mock.calls
542
+ .slice(pageCallOffset)
543
+ .map((call) => call[2]?.after)
544
+ .filter((after): after is string => after !== undefined);
545
+ expect(afterAttempts).toContain(sixtySecondAfter);
546
+ expect(afterAttempts).toContain(tenSecondAfter);
547
+ expect(afterAttempts.indexOf(tenSecondAfter)).toBeGreaterThan(
548
+ afterAttempts.indexOf(sixtySecondAfter),
549
+ );
550
+
551
+ expect(result.reason).toBe("thread_late_join");
552
+ expect(result.omittedMiddle).toBe(true);
553
+
554
+ const persisted = readPersistedSlackRows(conv.id);
555
+ expect(
556
+ persisted.filter((p) => p.threadTs === threadTs).map((p) => p.content),
557
+ ).toEqual(["newest context 1", "newest context 2", "newest context 3"]);
558
+ expect(
559
+ persisted.some((p) => p.content.startsWith("truncated high-throughput")),
560
+ ).toBe(false);
561
+ expect(persisted.find((p) => p.channelTs === inboundTs)).toBeUndefined();
562
+ });
563
+
564
+ test("high-throughput initial backfill still runs near-upper fallback after shrinking attempts are exhausted", async () => {
565
+ const conv = createTestConversation();
566
+ const ts = (seconds: number, micros = 0) =>
567
+ `${seconds}.${String(micros).padStart(6, "0")}`;
568
+ const threadTs = ts(1700000000);
569
+ const inboundTs = ts(1700001000);
570
+ const fiveMinuteAfter = ts(1700000700);
571
+ const sixtySecondAfter = ts(1700000940);
572
+ const tenSecondAfter = ts(1700000990);
573
+ const oneSecondAfter = ts(1700000999);
574
+ const hundredMillisecondAfter = ts(1700000999, 900000);
575
+ const nearUpperFallbackAfter = ts(1700000999, 999998);
576
+
577
+ backfillThreadPageMock.mockImplementation(
578
+ async (_channel, _thread, opts) => {
579
+ if (opts?.limit === 25 && opts.before === undefined) {
580
+ return {
581
+ messages: [
582
+ makeBackfillMessage({
583
+ id: threadTs,
584
+ text: "thread parent",
585
+ threadId: undefined,
586
+ }),
587
+ ],
588
+ hasMore: true,
589
+ };
590
+ }
591
+
592
+ if (opts?.limit === 50 && opts.before === inboundTs) {
593
+ if (opts.after === nearUpperFallbackAfter) {
594
+ return {
595
+ messages: [
596
+ makeBackfillMessage({
597
+ id: ts(1700000999, 999999),
598
+ text: "newest context after exhausted probes",
599
+ threadId: threadTs,
600
+ }),
601
+ ],
602
+ hasMore: false,
603
+ };
604
+ }
605
+
606
+ return {
607
+ messages: Array.from({ length: 50 }, (_, i) =>
608
+ makeBackfillMessage({
609
+ id: ts(1700000999, 900000 + i),
610
+ text: `truncated exhausted probe ${i}`,
611
+ threadId: threadTs,
612
+ }),
613
+ ),
614
+ hasMore: true,
615
+ nextCursor: "still-truncated",
616
+ };
617
+ }
618
+
619
+ return { messages: [], hasMore: false };
620
+ },
621
+ );
622
+
623
+ const exhaustedPageCallOffset = backfillThreadPageMock.mock.calls.length;
624
+
625
+ const result = await triggerSlackThreadBackfillIfNeeded({
626
+ conversationId: conv.id,
627
+ channelId: SLACK_CHANNEL_ID,
628
+ threadTs,
629
+ excludeChannelTs: inboundTs,
630
+ });
631
+
632
+ const afterAttempts = backfillThreadPageMock.mock.calls
633
+ .slice(exhaustedPageCallOffset)
634
+ .map((call) => call[2]?.after)
635
+ .filter((after): after is string => after !== undefined);
636
+ expect(afterAttempts).toEqual([
637
+ fiveMinuteAfter,
638
+ sixtySecondAfter,
639
+ tenSecondAfter,
640
+ oneSecondAfter,
641
+ hundredMillisecondAfter,
642
+ nearUpperFallbackAfter,
643
+ ]);
644
+
645
+ expect(result.reason).toBe("thread_late_join");
646
+ expect(result.omittedMiddle).toBe(true);
647
+
648
+ const persisted = readPersistedSlackRows(conv.id);
649
+ expect(
650
+ persisted.find((p) => p.channelTs === ts(1700000999, 999999))?.content,
651
+ ).toBe("newest context after exhausted probes");
652
+ expect(
653
+ persisted.some((p) => p.content.startsWith("truncated exhausted probe")),
654
+ ).toBe(false);
655
+ expect(persisted.find((p) => p.channelTs === inboundTs)).toBeUndefined();
656
+ });
657
+
658
+ test("backfill is NOT triggered when the parent is already persisted and no upper-bound gap is known", async () => {
329
659
  const conv = createTestConversation();
330
660
 
331
661
  // Seed the parent message before the trigger runs — simulates a
332
- // conversation where the daemon has already seen the thread parent.
662
+ // conversation where the daemon has already seen the thread parent but
663
+ // the caller did not provide the inbound Slack ts needed to bound a gap.
333
664
  seedSlackRow(conv.id, "1234.0", undefined, "already here");
334
665
 
335
666
  await triggerSlackThreadBackfillIfNeeded({
@@ -345,6 +676,175 @@ describe("triggerSlackThreadBackfillIfNeeded — gap detection and persistence",
345
676
  expect(persisted[0].channelTs).toBe("1234.0");
346
677
  });
347
678
 
679
+ test("parent already persisted but later replies are missing triggers a bounded delta backfill", async () => {
680
+ const conv = createTestConversation();
681
+
682
+ seedSlackRow(conv.id, "1234.0", undefined, "parent already here");
683
+
684
+ backfillThreadMock.mockImplementation(async () => [
685
+ makeBackfillMessage({
686
+ id: "1234.0",
687
+ text: "duplicate parent",
688
+ threadId: undefined,
689
+ }),
690
+ makeBackfillMessage({
691
+ id: "1234.1",
692
+ text: "unseen earlier reply",
693
+ threadId: "1234.0",
694
+ }),
695
+ makeBackfillMessage({
696
+ id: "1234.5",
697
+ text: "live inbound reply",
698
+ threadId: "1234.0",
699
+ }),
700
+ ]);
701
+
702
+ await triggerSlackThreadBackfillIfNeeded({
703
+ conversationId: conv.id,
704
+ channelId: SLACK_CHANNEL_ID,
705
+ threadTs: "1234.0",
706
+ excludeChannelTs: "1234.5",
707
+ });
708
+
709
+ expect(backfillThreadMock).toHaveBeenCalledTimes(1);
710
+ const [, , opts] = backfillThreadMock.mock.calls[0];
711
+ expect(opts?.after).toBe("1234.0");
712
+ expect(opts?.before).toBe("1234.5");
713
+
714
+ const persisted = readPersistedSlackRows(conv.id);
715
+ expect(persisted.length).toBe(2);
716
+ expect(persisted.find((p) => p.channelTs === "1234.0")?.content).toBe(
717
+ "parent already here",
718
+ );
719
+ expect(persisted.find((p) => p.channelTs === "1234.1")?.content).toBe(
720
+ "unseen earlier reply",
721
+ );
722
+ expect(persisted.find((p) => p.channelTs === "1234.5")).toBeUndefined();
723
+ });
724
+
725
+ test("multi-page delta backfill keeps the newest rows before the inbound mention", async () => {
726
+ const conv = createTestConversation();
727
+ const parentTs = "1699990000.000000";
728
+ const inboundTs = "1700000000.500000";
729
+ const ts = (n: number) => `1700000000.${String(n).padStart(6, "0")}`;
730
+
731
+ seedSlackRow(conv.id, parentTs, undefined, "parent already here");
732
+
733
+ backfillThreadPageMock.mockImplementation(async (...args) => {
734
+ const messages = await backfillThreadMock(...args);
735
+ const opts = args[2];
736
+ if (opts?.limit === 1) {
737
+ return { messages, hasMore: messages.length > 0 };
738
+ }
739
+ return { messages, hasMore: false };
740
+ });
741
+ backfillThreadMock.mockImplementation(async (_channel, _thread, opts) => {
742
+ if (opts?.limit === 1) {
743
+ return [
744
+ makeBackfillMessage({
745
+ id: ts(100000),
746
+ text: "omitted earlier delta",
747
+ threadId: parentTs,
748
+ }),
749
+ ];
750
+ }
751
+ if (opts?.before === inboundTs && opts.after !== parentTs) {
752
+ return Array.from({ length: 50 }, (_, i) => {
753
+ const n = 499950 + i;
754
+ return makeBackfillMessage({
755
+ id: ts(n),
756
+ text: `newest delta ${n}`,
757
+ threadId: parentTs,
758
+ });
759
+ });
760
+ }
761
+ return [];
762
+ });
763
+
764
+ const result = await triggerSlackThreadBackfillIfNeeded({
765
+ conversationId: conv.id,
766
+ channelId: SLACK_CHANNEL_ID,
767
+ threadTs: parentTs,
768
+ excludeChannelTs: inboundTs,
769
+ });
770
+
771
+ expect(result.reason).toBe("thread_delta");
772
+ expect(result.omittedMiddle).toBe(true);
773
+ expect(backfillThreadMock.mock.calls[0][2]?.before).toBe(inboundTs);
774
+ expect(backfillThreadMock.mock.calls[0][2]?.after).not.toBe(parentTs);
775
+
776
+ const persisted = readPersistedSlackRows(conv.id);
777
+ expect(persisted.find((p) => p.channelTs === parentTs)?.content).toBe(
778
+ "parent already here",
779
+ );
780
+ expect(persisted.find((p) => p.channelTs === ts(100000))).toBeUndefined();
781
+ expect(persisted.find((p) => p.channelTs === ts(499999))?.content).toBe(
782
+ "newest delta 499999",
783
+ );
784
+ expect(persisted.find((p) => p.channelTs === inboundTs)).toBeUndefined();
785
+ });
786
+
787
+ test("file-bearing backfill renders a Slack file marker without binary hydration", async () => {
788
+ const conv = createTestConversation();
789
+
790
+ seedSlackRow(conv.id, "1234.0", undefined, "parent already here");
791
+
792
+ backfillThreadMock.mockImplementation(async () => [
793
+ makeBackfillMessage({
794
+ id: "1234.1",
795
+ text: "uploaded the draft",
796
+ threadId: "1234.0",
797
+ sender: { id: "U_FILE", name: "File Sharer" },
798
+ metadata: {
799
+ slackFiles: [
800
+ {
801
+ id: "F-DRAFT",
802
+ name: "project-plan.pdf",
803
+ mimetype: "application/pdf",
804
+ },
805
+ ],
806
+ },
807
+ }),
808
+ ]);
809
+
810
+ await triggerSlackThreadBackfillIfNeeded({
811
+ conversationId: conv.id,
812
+ channelId: SLACK_CHANNEL_ID,
813
+ threadTs: "1234.0",
814
+ excludeChannelTs: "1234.2",
815
+ });
816
+
817
+ const context = loadSlackChronologicalContext(conv.id, SLACK_CHANNEL_CAPS, {
818
+ loader: readMessageRowsByConversation,
819
+ trustClass: "guardian",
820
+ });
821
+
822
+ expect(context).not.toBeNull();
823
+ const rendered = flattenText(context!.messages);
824
+ expect(rendered).toContain("uploaded the draft");
825
+ expect(rendered).toContain(
826
+ "[attached file: project-plan.pdf, application/pdf]",
827
+ );
828
+ expect(rendered).not.toContain("F-DRAFT");
829
+ });
830
+
831
+ test("latest stored thread message at or after inbound ts skips backfill using parsed Slack timestamps", async () => {
832
+ const conv = createTestConversation();
833
+
834
+ seedSlackRow(conv.id, "1234.0", undefined, "parent");
835
+ seedSlackRow(conv.id, "1234.10", "1234.0", "newer stored reply");
836
+
837
+ await triggerSlackThreadBackfillIfNeeded({
838
+ conversationId: conv.id,
839
+ channelId: SLACK_CHANNEL_ID,
840
+ threadTs: "1234.0",
841
+ excludeChannelTs: "1234.2",
842
+ });
843
+
844
+ expect(backfillThreadMock).not.toHaveBeenCalled();
845
+ expect(readPersistedSlackRows(conv.id).length).toBe(2);
846
+ });
847
+
348
848
  test("idempotency cache: a second call inside the TTL window does not re-fetch", async () => {
349
849
  const conv = createTestConversation();
350
850
 
@@ -358,8 +858,8 @@ describe("triggerSlackThreadBackfillIfNeeded — gap detection and persistence",
358
858
  threadTs: "1234.0",
359
859
  });
360
860
 
361
- // Second call for the same conversation+thread — must short-circuit on
362
- // the in-memory cache without hitting backfillThread again.
861
+ // Second call for the same unbounded window — must short-circuit on the
862
+ // in-memory cache without hitting backfillThreadWindow again.
363
863
  await triggerSlackThreadBackfillIfNeeded({
364
864
  conversationId: conv.id,
365
865
  channelId: SLACK_CHANNEL_ID,
@@ -394,17 +894,13 @@ describe("triggerSlackThreadBackfillIfNeeded — gap detection and persistence",
394
894
  test("backfill returns duplicates that are already stored — only new rows are inserted", async () => {
395
895
  const conv = createTestConversation();
396
896
 
397
- // Pre-seed sibling 1234.1 so the backfill response includes one row that
398
- // already exists (and must not be re-inserted) plus two genuinely new
399
- // ones (parent 1234.0 and sibling 1234.2).
897
+ // Pre-seed parent and sibling 1234.1 so the bounded delta response
898
+ // includes one row that already exists (and must not be re-inserted)
899
+ // plus one genuinely new sibling.
900
+ seedSlackRow(conv.id, "1234.0", undefined, "parent");
400
901
  seedSlackRow(conv.id, "1234.1", "1234.0", "already here");
401
902
 
402
903
  backfillThreadMock.mockImplementation(async () => [
403
- makeBackfillMessage({
404
- id: "1234.0",
405
- text: "parent",
406
- threadId: undefined,
407
- }),
408
904
  makeBackfillMessage({
409
905
  id: "1234.1",
410
906
  text: "duplicate sibling — must be skipped",
@@ -421,8 +917,13 @@ describe("triggerSlackThreadBackfillIfNeeded — gap detection and persistence",
421
917
  conversationId: conv.id,
422
918
  channelId: SLACK_CHANNEL_ID,
423
919
  threadTs: "1234.0",
920
+ excludeChannelTs: "1234.3",
424
921
  });
425
922
 
923
+ expect(backfillThreadMock).toHaveBeenCalledTimes(1);
924
+ expect(backfillThreadMock.mock.calls[0][2]?.after).toBe("1234.1");
925
+ expect(backfillThreadMock.mock.calls[0][2]?.before).toBe("1234.3");
926
+
426
927
  const persisted = readPersistedSlackRows(conv.id);
427
928
  expect(persisted.length).toBe(3);
428
929
 
@@ -453,7 +954,7 @@ describe("triggerSlackThreadBackfillIfNeeded — gap detection and persistence",
453
954
  expect(backfillThreadMock).toHaveBeenCalledTimes(1);
454
955
  expect(readPersistedSlackRows(conv.id).length).toBe(0);
455
956
 
456
- // Cache should now be populated for this conversation+thread, so an
957
+ // Cache should now be populated for this exact unbounded window, so an
457
958
  // immediate retry must not re-run the API call.
458
959
  await triggerSlackThreadBackfillIfNeeded({
459
960
  conversationId: conv.id,
@@ -463,6 +964,124 @@ describe("triggerSlackThreadBackfillIfNeeded — gap detection and persistence",
463
964
  expect(backfillThreadMock).toHaveBeenCalledTimes(1);
464
965
  });
465
966
 
967
+ test("TTL cache suppresses the same bounded window but not a newer upper-bound window", async () => {
968
+ const conv = createTestConversation();
969
+
970
+ backfillThreadMock.mockImplementation(async () => []);
971
+
972
+ await triggerSlackThreadBackfillIfNeeded({
973
+ conversationId: conv.id,
974
+ channelId: SLACK_CHANNEL_ID,
975
+ threadTs: "1234.0",
976
+ excludeChannelTs: "1234.5",
977
+ });
978
+ await triggerSlackThreadBackfillIfNeeded({
979
+ conversationId: conv.id,
980
+ channelId: SLACK_CHANNEL_ID,
981
+ threadTs: "1234.0",
982
+ excludeChannelTs: "1234.5",
983
+ });
984
+ await triggerSlackThreadBackfillIfNeeded({
985
+ conversationId: conv.id,
986
+ channelId: SLACK_CHANNEL_ID,
987
+ threadTs: "1234.0",
988
+ excludeChannelTs: "1234.6",
989
+ });
990
+
991
+ expect(
992
+ backfillThreadMock.mock.calls.some(
993
+ (call) => call[2]?.before === "1234.5",
994
+ ),
995
+ ).toBe(true);
996
+ expect(
997
+ backfillThreadMock.mock.calls.some(
998
+ (call) => call[2]?.before === "1234.6",
999
+ ),
1000
+ ).toBe(true);
1001
+ });
1002
+
1003
+ test("rapid consecutive replies can fetch a newer gap even when the prior inbound reply was only excluded", async () => {
1004
+ const conv = createTestConversation();
1005
+
1006
+ backfillThreadMock.mockImplementation(async (_channel, _thread, opts) => {
1007
+ if (opts?.limit === 25) {
1008
+ return [
1009
+ makeBackfillMessage({
1010
+ id: "1234.0",
1011
+ text: "parent",
1012
+ threadId: undefined,
1013
+ }),
1014
+ ];
1015
+ }
1016
+ if (opts?.before === "1234.5") {
1017
+ return [
1018
+ makeBackfillMessage({
1019
+ id: "1234.0",
1020
+ text: "parent",
1021
+ threadId: undefined,
1022
+ }),
1023
+ makeBackfillMessage({
1024
+ id: "1234.4",
1025
+ text: "reply before first live inbound",
1026
+ threadId: "1234.0",
1027
+ }),
1028
+ makeBackfillMessage({
1029
+ id: "1234.5",
1030
+ text: "first live inbound",
1031
+ threadId: "1234.0",
1032
+ }),
1033
+ ];
1034
+ }
1035
+ return [
1036
+ makeBackfillMessage({
1037
+ id: "1234.5",
1038
+ text: "first live inbound recovered by newer window",
1039
+ threadId: "1234.0",
1040
+ }),
1041
+ makeBackfillMessage({
1042
+ id: "1234.6",
1043
+ text: "second live inbound",
1044
+ threadId: "1234.0",
1045
+ }),
1046
+ ];
1047
+ });
1048
+
1049
+ await triggerSlackThreadBackfillIfNeeded({
1050
+ conversationId: conv.id,
1051
+ channelId: SLACK_CHANNEL_ID,
1052
+ threadTs: "1234.0",
1053
+ excludeChannelTs: "1234.5",
1054
+ });
1055
+ await triggerSlackThreadBackfillIfNeeded({
1056
+ conversationId: conv.id,
1057
+ channelId: SLACK_CHANNEL_ID,
1058
+ threadTs: "1234.0",
1059
+ excludeChannelTs: "1234.6",
1060
+ });
1061
+
1062
+ expect(backfillThreadMock.mock.calls.length).toBeGreaterThanOrEqual(3);
1063
+ expect(backfillThreadMock.mock.calls[0][2]?.after).toBeUndefined();
1064
+ expect(backfillThreadMock.mock.calls[0][2]?.before).toBeUndefined();
1065
+ expect(
1066
+ backfillThreadMock.mock.calls.some(
1067
+ (call) => call[2]?.before === "1234.5",
1068
+ ),
1069
+ ).toBe(true);
1070
+ expect(
1071
+ backfillThreadMock.mock.calls.some(
1072
+ (call) => call[2]?.before === "1234.6",
1073
+ ),
1074
+ ).toBe(true);
1075
+
1076
+ const persisted = readPersistedSlackRows(conv.id);
1077
+ expect(persisted.map((p) => p.channelTs).sort()).toEqual([
1078
+ "1234.0",
1079
+ "1234.4",
1080
+ "1234.5",
1081
+ ]);
1082
+ expect(persisted.find((p) => p.channelTs === "1234.6")).toBeUndefined();
1083
+ });
1084
+
466
1085
  test("two distinct threads in the same conversation each trigger their own backfill", async () => {
467
1086
  const conv = createTestConversation();
468
1087
 
@@ -678,13 +1297,16 @@ function resetHttpState(): void {
678
1297
  _backfillTriggerCache.clear();
679
1298
  backfillThreadMock.mockReset();
680
1299
  backfillThreadMock.mockImplementation(async () => []);
1300
+ backfillDmMock.mockReset();
1301
+ backfillDmMock.mockImplementation(async () => []);
1302
+ setAdapterProcessMessage(undefined);
681
1303
  }
682
1304
 
683
- function seedHttpActiveMember(): void {
1305
+ function seedHttpActiveMember(chatId = HTTP_SLACK_CHANNEL_ID): void {
684
1306
  upsertContactChannel({
685
1307
  sourceChannel: "slack",
686
1308
  externalUserId: HTTP_SLACK_USER_ID,
687
- externalChatId: HTTP_SLACK_CHANNEL_ID,
1309
+ externalChatId: chatId,
688
1310
  status: "active",
689
1311
  policy: "allow",
690
1312
  displayName: HTTP_SLACK_DISPLAY_NAME,
@@ -727,6 +1349,93 @@ function buildThreadReplyRequest(
727
1349
  });
728
1350
  }
729
1351
 
1352
+ function buildSlackDmRequest(
1353
+ channelId: string,
1354
+ messageId: string,
1355
+ overrides: Record<string, unknown> = {},
1356
+ ): Request {
1357
+ httpMsgCounter++;
1358
+ const body: Record<string, unknown> = {
1359
+ sourceChannel: "slack",
1360
+ interface: "slack",
1361
+ conversationExternalId: channelId,
1362
+ externalMessageId: `${channelId}:${messageId}:${httpMsgCounter}`,
1363
+ content: "DM text",
1364
+ actorExternalId: HTTP_SLACK_USER_ID,
1365
+ actorDisplayName: HTTP_SLACK_DISPLAY_NAME,
1366
+ actorUsername: "charlie",
1367
+ replyCallbackUrl: "http://localhost:7830/deliver/slack",
1368
+ sourceMetadata: {
1369
+ messageId,
1370
+ chatType: "im",
1371
+ },
1372
+ ...overrides,
1373
+ };
1374
+
1375
+ return new Request("http://localhost:8080/channels/inbound", {
1376
+ method: "POST",
1377
+ headers: {
1378
+ "Content-Type": "application/json",
1379
+ "X-Gateway-Origin": TEST_BEARER_TOKEN,
1380
+ },
1381
+ body: JSON.stringify(body),
1382
+ });
1383
+ }
1384
+
1385
+ interface SlackInboundProcessOptions {
1386
+ slackRuntimeContextNotice?: string;
1387
+ slackInbound?: {
1388
+ channelId: string;
1389
+ channelTs: string;
1390
+ threadTs?: string;
1391
+ displayName?: string;
1392
+ };
1393
+ }
1394
+
1395
+ function persistSlackInboundFromProcessMessage(
1396
+ conversationId: string,
1397
+ content: string,
1398
+ options?: SlackInboundProcessOptions,
1399
+ ): string {
1400
+ const slackInbound = options?.slackInbound;
1401
+ return insertMessage(conversationId, "user", content, {
1402
+ ...(slackInbound
1403
+ ? {
1404
+ slackMeta: writeSlackMetadata({
1405
+ source: "slack",
1406
+ channelId: slackInbound.channelId,
1407
+ channelTs: slackInbound.channelTs,
1408
+ ...(slackInbound.threadTs
1409
+ ? { threadTs: slackInbound.threadTs }
1410
+ : {}),
1411
+ ...(slackInbound.displayName
1412
+ ? { displayName: slackInbound.displayName }
1413
+ : {}),
1414
+ eventKind: "message",
1415
+ }),
1416
+ }
1417
+ : {}),
1418
+ });
1419
+ }
1420
+
1421
+ const SLACK_CHANNEL_CAPS: ChannelCapabilities = {
1422
+ channel: "slack",
1423
+ dashboardCapable: false,
1424
+ supportsDynamicUi: false,
1425
+ supportsVoiceInput: false,
1426
+ chatType: "channel",
1427
+ };
1428
+
1429
+ function flattenText(messages: Message[]): string {
1430
+ return messages
1431
+ .flatMap((message) => message.content)
1432
+ .filter((block): block is { type: "text"; text: string } => {
1433
+ return block.type === "text";
1434
+ })
1435
+ .map((block) => block.text)
1436
+ .join("\n");
1437
+ }
1438
+
730
1439
  describe("handleChannelInbound — Slack thread backfill wiring", () => {
731
1440
  beforeEach(() => {
732
1441
  resetHttpState();
@@ -736,6 +1445,7 @@ describe("handleChannelInbound — Slack thread backfill wiring", () => {
736
1445
 
737
1446
  afterEach(() => {
738
1447
  backfillThreadMock.mockReset();
1448
+ installDefaultThreadPageMock();
739
1449
  _backfillTriggerCache.clear();
740
1450
  });
741
1451
 
@@ -755,9 +1465,22 @@ describe("handleChannelInbound — Slack thread backfill wiring", () => {
755
1465
  }),
756
1466
  ]);
757
1467
 
758
- const processMessage = async (): Promise<{ messageId: string }> => {
1468
+ let capturedHints: string[] | undefined;
1469
+ let capturedSlackNotice: string | undefined;
1470
+ const processMessage = async (
1471
+ _conversationId: string,
1472
+ _content: string,
1473
+ _attachmentIds?: string[],
1474
+ options?: {
1475
+ transport?: { hints?: string[] };
1476
+ slackRuntimeContextNotice?: string;
1477
+ },
1478
+ ): Promise<{ messageId: string }> => {
1479
+ capturedHints = options?.transport?.hints;
1480
+ capturedSlackNotice = options?.slackRuntimeContextNotice;
759
1481
  return { messageId: "agent-result-id" };
760
1482
  };
1483
+ setAdapterProcessMessage(processMessage);
761
1484
 
762
1485
  const req = buildThreadReplyRequest("1234.0", "1234.3");
763
1486
  const resp = await handleChannelInbound(
@@ -774,7 +1497,7 @@ describe("handleChannelInbound — Slack thread backfill wiring", () => {
774
1497
  // void-promise has time to write to the DB before we assert.
775
1498
  await new Promise((resolve) => setTimeout(resolve, 100));
776
1499
 
777
- expect(backfillThreadMock).toHaveBeenCalledTimes(1);
1500
+ expect(backfillThreadMock.mock.calls.length).toBeGreaterThanOrEqual(2);
778
1501
  const [calledChannel, calledThread] = backfillThreadMock.mock.calls[0];
779
1502
  expect(calledChannel).toBe(HTTP_SLACK_CHANNEL_ID);
780
1503
  expect(calledThread).toBe("1234.0");
@@ -800,9 +1523,190 @@ describe("handleChannelInbound — Slack thread backfill wiring", () => {
800
1523
 
801
1524
  expect(channelTimestamps.has("1234.0")).toBe(true);
802
1525
  expect(channelTimestamps.has("1234.1")).toBe(true);
1526
+
1527
+ expect(
1528
+ capturedHints?.some((hint) => hint.includes("joined an existing thread")),
1529
+ ).not.toBe(true);
1530
+ expect(capturedSlackNotice).toContain("joined an existing thread");
1531
+ const contents = db.$client
1532
+ .prepare("SELECT content FROM messages")
1533
+ .all() as Array<{ content: string }>;
1534
+ expect(
1535
+ contents.some((row) => row.content.includes("Slack context note")),
1536
+ ).toBe(false);
803
1537
  });
804
1538
 
805
- test("second thread reply within the TTL window does not re-trigger backfill", async () => {
1539
+ test("late app mention sees unseen backfilled replies before the mention", async () => {
1540
+ let capturedTranscript = "";
1541
+ let parentTurnSeen = false;
1542
+ let resolveParentTurn: (() => void) | undefined;
1543
+ let secondTurnSeen = false;
1544
+ const parentTurnProcessed = new Promise<void>((resolve) => {
1545
+ resolveParentTurn = resolve;
1546
+ });
1547
+ let resolveSecondTurn: (() => void) | undefined;
1548
+ const secondTurnProcessed = new Promise<void>((resolve) => {
1549
+ resolveSecondTurn = resolve;
1550
+ });
1551
+
1552
+ const processMessage = async (
1553
+ conversationId: string,
1554
+ content: string,
1555
+ _attachmentIds?: string[],
1556
+ options?: SlackInboundProcessOptions,
1557
+ ): Promise<{ messageId: string }> => {
1558
+ const messageId = persistSlackInboundFromProcessMessage(
1559
+ conversationId,
1560
+ content,
1561
+ options,
1562
+ );
1563
+ if (options?.slackInbound?.channelTs === "1234.0") {
1564
+ parentTurnSeen = true;
1565
+ resolveParentTurn?.();
1566
+ }
1567
+ if (options?.slackInbound?.channelTs === "1234.5") {
1568
+ const context = loadSlackChronologicalContext(
1569
+ conversationId,
1570
+ SLACK_CHANNEL_CAPS,
1571
+ {
1572
+ loader: readMessageRowsByConversation,
1573
+ trustClass: "guardian",
1574
+ },
1575
+ );
1576
+ capturedTranscript = context ? flattenText(context.messages) : "";
1577
+ secondTurnSeen = true;
1578
+ resolveSecondTurn?.();
1579
+ }
1580
+ return { messageId };
1581
+ };
1582
+ setAdapterProcessMessage(processMessage);
1583
+
1584
+ const parentResp = await handleChannelInbound(
1585
+ buildThreadReplyRequest("1234.0", "1234.0", {
1586
+ content: "parent already stored",
1587
+ sourceMetadata: {
1588
+ messageId: "1234.0",
1589
+ chatType: "channel",
1590
+ },
1591
+ }),
1592
+ processMessage,
1593
+ TEST_BEARER_TOKEN,
1594
+ );
1595
+ expect(parentResp.status).toBe(200);
1596
+ await Promise.race([
1597
+ parentTurnProcessed,
1598
+ new Promise((resolve) => setTimeout(resolve, 250)),
1599
+ ]);
1600
+ expect(parentTurnSeen).toBe(true);
1601
+
1602
+ backfillThreadMock.mockReset();
1603
+ backfillThreadMock.mockImplementation(async () => [
1604
+ makeBackfillMessage({
1605
+ id: "1234.1",
1606
+ text: "unseen first reply",
1607
+ threadId: "1234.0",
1608
+ sender: { id: "U_ONE", name: "Reply One" },
1609
+ }),
1610
+ makeBackfillMessage({
1611
+ id: "1234.2",
1612
+ text: "unseen second reply",
1613
+ threadId: "1234.0",
1614
+ sender: { id: "U_TWO", name: "Reply Two" },
1615
+ }),
1616
+ makeBackfillMessage({
1617
+ id: "1234.5",
1618
+ text: "live app mention should not be duplicated by backfill",
1619
+ threadId: "1234.0",
1620
+ sender: { id: HTTP_SLACK_USER_ID, name: HTTP_SLACK_DISPLAY_NAME },
1621
+ }),
1622
+ ]);
1623
+
1624
+ const mentionResp = await handleChannelInbound(
1625
+ buildThreadReplyRequest("1234.0", "1234.5", {
1626
+ content: "<@U_ASSISTANT> please answer with the missing context",
1627
+ sourceMetadata: {
1628
+ messageId: "1234.5",
1629
+ threadId: "1234.0",
1630
+ chatType: "channel",
1631
+ eventType: "app_mention",
1632
+ },
1633
+ }),
1634
+ processMessage,
1635
+ TEST_BEARER_TOKEN,
1636
+ );
1637
+ expect(mentionResp.status).toBe(200);
1638
+
1639
+ await Promise.race([
1640
+ secondTurnProcessed,
1641
+ new Promise((resolve) => setTimeout(resolve, 250)),
1642
+ ]);
1643
+
1644
+ expect(secondTurnSeen).toBe(true);
1645
+ expect(backfillThreadMock).toHaveBeenCalledTimes(1);
1646
+ expect(backfillThreadMock.mock.calls[0][2]?.after).toBe("1234.0");
1647
+ expect(backfillThreadMock.mock.calls[0][2]?.before).toBe("1234.5");
1648
+
1649
+ const parentIndex = capturedTranscript.indexOf("parent already stored");
1650
+ const firstReplyIndex = capturedTranscript.indexOf("unseen first reply");
1651
+ const secondReplyIndex = capturedTranscript.indexOf("unseen second reply");
1652
+ const mentionIndex = capturedTranscript.indexOf(
1653
+ "please answer with the missing context",
1654
+ );
1655
+
1656
+ expect(parentIndex).toBeGreaterThanOrEqual(0);
1657
+ expect(firstReplyIndex).toBeGreaterThan(parentIndex);
1658
+ expect(secondReplyIndex).toBeGreaterThan(firstReplyIndex);
1659
+ expect(mentionIndex).toBeGreaterThan(secondReplyIndex);
1660
+ expect(
1661
+ capturedTranscript.match(/live app mention should not be duplicated/g),
1662
+ ).toBeNull();
1663
+ });
1664
+
1665
+ test("cold-start Slack DMs still use backfillDm without thread backfill", async () => {
1666
+ const dmChannelId = "D0HTTPDM";
1667
+ seedHttpActiveMember(dmChannelId);
1668
+ backfillDmMock.mockImplementation(async () => [
1669
+ makeBackfillMessage({
1670
+ id: "1700000000.000100",
1671
+ conversationId: dmChannelId,
1672
+ text: "earlier DM context",
1673
+ sender: { id: HTTP_SLACK_USER_ID, name: HTTP_SLACK_DISPLAY_NAME },
1674
+ }),
1675
+ ]);
1676
+
1677
+ const processMessage = async (
1678
+ conversationId: string,
1679
+ content: string,
1680
+ _attachmentIds?: string[],
1681
+ options?: SlackInboundProcessOptions,
1682
+ ): Promise<{ messageId: string }> => ({
1683
+ messageId: persistSlackInboundFromProcessMessage(
1684
+ conversationId,
1685
+ content,
1686
+ options,
1687
+ ),
1688
+ });
1689
+ setAdapterProcessMessage(processMessage);
1690
+
1691
+ const resp = await handleChannelInbound(
1692
+ buildSlackDmRequest(dmChannelId, "1700000000.000200"),
1693
+ processMessage,
1694
+ TEST_BEARER_TOKEN,
1695
+ );
1696
+
1697
+ expect(resp.status).toBe(200);
1698
+ await new Promise((resolve) => setTimeout(resolve, 100));
1699
+
1700
+ expect(backfillDmMock).toHaveBeenCalledTimes(1);
1701
+ expect(backfillDmMock.mock.calls[0][0]).toBe(dmChannelId);
1702
+ expect(backfillDmMock.mock.calls[0][1]).toMatchObject({
1703
+ limit: 50,
1704
+ before: "1700000000.000200",
1705
+ });
1706
+ expect(backfillThreadMock).not.toHaveBeenCalled();
1707
+ });
1708
+
1709
+ test("second thread reply within the TTL window can fetch a newer bounded gap", async () => {
806
1710
  backfillThreadMock.mockImplementation(async () => [
807
1711
  makeBackfillMessage({ id: "5678.0", text: "parent" }),
808
1712
  ]);
@@ -827,7 +1731,17 @@ describe("handleChannelInbound — Slack thread backfill wiring", () => {
827
1731
  expect(r2.status).toBe(200);
828
1732
  await new Promise((resolve) => setTimeout(resolve, 100));
829
1733
 
830
- expect(backfillThreadMock).toHaveBeenCalledTimes(1);
1734
+ expect(backfillThreadMock.mock.calls[0][2]?.before).toBeUndefined();
1735
+ expect(
1736
+ backfillThreadMock.mock.calls.some(
1737
+ (call) => call[2]?.before === "5678.1",
1738
+ ),
1739
+ ).toBe(true);
1740
+ expect(
1741
+ backfillThreadMock.mock.calls.some(
1742
+ (call) => call[2]?.before === "5678.2",
1743
+ ),
1744
+ ).toBe(true);
831
1745
  });
832
1746
 
833
1747
  test("backfill error from the HTTP path does not crash the request", async () => {