@vellumai/assistant 0.6.3 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1114) hide show
  1. package/.prettierignore +5 -0
  2. package/ARCHITECTURE.md +298 -39
  3. package/Dockerfile +14 -3
  4. package/README.md +3 -4
  5. package/bun.lock +13 -16
  6. package/docs/architecture/integrations.md +1 -20
  7. package/docs/architecture/security.md +16 -16
  8. package/docs/backup-troubleshooting.md +52 -0
  9. package/docs/browser-use-architecture-phase2.md +174 -0
  10. package/docs/error-handling.md +111 -0
  11. package/docs/skills.md +10 -10
  12. package/docs/stt-provider-onboarding.md +121 -0
  13. package/knip.json +20 -3
  14. package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
  15. package/node_modules/@vellumai/ces-contracts/package.json +5 -4
  16. package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
  17. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
  18. package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
  19. package/node_modules/@vellumai/credential-storage/package.json +2 -2
  20. package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
  21. package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
  22. package/node_modules/@vellumai/egress-proxy/package.json +2 -2
  23. package/openapi.yaml +1094 -72
  24. package/package.json +9 -8
  25. package/scripts/generate-openapi.ts +50 -12
  26. package/scripts/test.sh +73 -18
  27. package/src/__tests__/agent-image-optimize.test.ts +28 -0
  28. package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
  29. package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
  30. package/src/__tests__/agent-loop.test.ts +235 -1
  31. package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
  32. package/src/__tests__/anthropic-provider.test.ts +434 -12
  33. package/src/__tests__/approval-cascade.test.ts +31 -10
  34. package/src/__tests__/approval-routes-http.test.ts +134 -10
  35. package/src/__tests__/assistant-attachments.test.ts +44 -0
  36. package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
  37. package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
  38. package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
  39. package/src/__tests__/browser-fill-credential.test.ts +12 -1
  40. package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
  41. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
  42. package/src/__tests__/browser-skill-endstate.test.ts +52 -159
  43. package/src/__tests__/btw-routes.test.ts +54 -1
  44. package/src/__tests__/call-controller.test.ts +582 -22
  45. package/src/__tests__/call-site-routing-provider.test.ts +214 -0
  46. package/src/__tests__/catalog-cache.test.ts +27 -4
  47. package/src/__tests__/catalog-files.test.ts +138 -0
  48. package/src/__tests__/channel-approval-routes.test.ts +4 -4
  49. package/src/__tests__/channel-invite-transport.test.ts +2 -2
  50. package/src/__tests__/channel-readiness-routes.test.ts +16 -20
  51. package/src/__tests__/channel-readiness-service.test.ts +12 -7
  52. package/src/__tests__/channel-reply-delivery.test.ts +300 -2
  53. package/src/__tests__/checker.test.ts +576 -502
  54. package/src/__tests__/clawhub-files.test.ts +347 -0
  55. package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
  56. package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
  57. package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
  58. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  59. package/src/__tests__/config-analysis.test.ts +83 -0
  60. package/src/__tests__/config-loader-backfill.test.ts +174 -0
  61. package/src/__tests__/config-loader-corrupt.test.ts +183 -0
  62. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
  63. package/src/__tests__/config-schema-cmd.test.ts +11 -5
  64. package/src/__tests__/config-schema.test.ts +1458 -198
  65. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
  66. package/src/__tests__/config-watcher.test.ts +45 -10
  67. package/src/__tests__/contact-store-user-file.test.ts +511 -0
  68. package/src/__tests__/contacts-write.test.ts +197 -0
  69. package/src/__tests__/context-token-estimator.test.ts +191 -1
  70. package/src/__tests__/context-window-manager.test.ts +618 -2
  71. package/src/__tests__/conversation-abort-tool-results.test.ts +32 -16
  72. package/src/__tests__/conversation-agent-loop-overflow.test.ts +62 -17
  73. package/src/__tests__/conversation-agent-loop.test.ts +510 -84
  74. package/src/__tests__/conversation-attachments.test.ts +1 -1
  75. package/src/__tests__/conversation-confirmation-signals.test.ts +165 -9
  76. package/src/__tests__/conversation-error.test.ts +102 -1
  77. package/src/__tests__/conversation-history-web-search.test.ts +17 -4
  78. package/src/__tests__/conversation-init.benchmark.test.ts +42 -1
  79. package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
  80. package/src/__tests__/conversation-lifecycle.test.ts +336 -0
  81. package/src/__tests__/conversation-list-source.test.ts +145 -0
  82. package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
  83. package/src/__tests__/conversation-pre-run-repair.test.ts +32 -16
  84. package/src/__tests__/conversation-process-callsite.test.ts +306 -0
  85. package/src/__tests__/conversation-provider-retry-repair.test.ts +32 -16
  86. package/src/__tests__/conversation-queue.test.ts +932 -76
  87. package/src/__tests__/conversation-routes-disk-view.test.ts +299 -1
  88. package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
  89. package/src/__tests__/conversation-runtime-assembly.test.ts +2790 -55
  90. package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
  91. package/src/__tests__/conversation-skill-tools.test.ts +12 -143
  92. package/src/__tests__/conversation-slash-commands.test.ts +33 -0
  93. package/src/__tests__/conversation-slash-queue.test.ts +120 -34
  94. package/src/__tests__/conversation-slash-unknown.test.ts +32 -16
  95. package/src/__tests__/conversation-speed-override.test.ts +30 -11
  96. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
  97. package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
  98. package/src/__tests__/conversation-title-service.test.ts +2 -2
  99. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
  100. package/src/__tests__/conversation-unread-route.test.ts +2 -2
  101. package/src/__tests__/conversation-usage.test.ts +3 -1
  102. package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
  103. package/src/__tests__/conversation-workspace-injection.test.ts +45 -15
  104. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +46 -16
  105. package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
  106. package/src/__tests__/credential-health-service.test.ts +352 -0
  107. package/src/__tests__/credential-security-invariants.test.ts +8 -3
  108. package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
  109. package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
  110. package/src/__tests__/credential-vault-unit.test.ts +495 -3
  111. package/src/__tests__/credentials-cli.test.ts +32 -16
  112. package/src/__tests__/cross-provider-web-search.test.ts +230 -35
  113. package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
  114. package/src/__tests__/delete-propagation.test.ts +437 -0
  115. package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
  116. package/src/__tests__/device-id.test.ts +112 -0
  117. package/src/__tests__/dm-backfill.test.ts +417 -0
  118. package/src/__tests__/dm-persistence.test.ts +227 -0
  119. package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
  120. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
  121. package/src/__tests__/edit-propagation.test.ts +280 -0
  122. package/src/__tests__/email-html-renderer.test.ts +71 -0
  123. package/src/__tests__/email-invite-adapter.test.ts +36 -32
  124. package/src/__tests__/emit-event-signal.test.ts +71 -0
  125. package/src/__tests__/ephemeral-permissions.test.ts +93 -3
  126. package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
  127. package/src/__tests__/estimator-calibration.test.ts +213 -0
  128. package/src/__tests__/extension-id-sync-guard.test.ts +101 -15
  129. package/src/__tests__/file-write-tool.test.ts +151 -1
  130. package/src/__tests__/filing-service.test.ts +255 -0
  131. package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
  132. package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
  133. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  134. package/src/__tests__/gemini-provider.test.ts +64 -3
  135. package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
  136. package/src/__tests__/guardian-grant-minting.test.ts +8 -0
  137. package/src/__tests__/headless-browser-interactions.test.ts +44 -1
  138. package/src/__tests__/headless-browser-mode.test.ts +614 -0
  139. package/src/__tests__/headless-browser-navigate.test.ts +142 -5
  140. package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
  141. package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
  142. package/src/__tests__/heartbeat-service.test.ts +166 -32
  143. package/src/__tests__/home-state-routes.test.ts +162 -0
  144. package/src/__tests__/host-bash-proxy.test.ts +0 -5
  145. package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
  146. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
  147. package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
  148. package/src/__tests__/host-cu-proxy.test.ts +0 -5
  149. package/src/__tests__/host-shell-tool.test.ts +124 -18
  150. package/src/__tests__/http-user-message-parity.test.ts +29 -1
  151. package/src/__tests__/identity-intro-cache.test.ts +40 -10
  152. package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
  153. package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
  154. package/src/__tests__/intent-routing.test.ts +1 -40
  155. package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
  156. package/src/__tests__/llm-catalog-parity.test.ts +174 -0
  157. package/src/__tests__/llm-context-normalization.test.ts +609 -0
  158. package/src/__tests__/llm-context-route-provider.test.ts +86 -5
  159. package/src/__tests__/llm-resolver.test.ts +214 -0
  160. package/src/__tests__/llm-schema.test.ts +223 -0
  161. package/src/__tests__/llm-usage-store.test.ts +363 -0
  162. package/src/__tests__/managed-proxy-context.test.ts +6 -2
  163. package/src/__tests__/media-stream-output.test.ts +555 -0
  164. package/src/__tests__/media-stream-parser.test.ts +374 -0
  165. package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
  166. package/src/__tests__/media-stream-stt-session.test.ts +588 -0
  167. package/src/__tests__/media-turn-detector.test.ts +440 -0
  168. package/src/__tests__/message-queue.test.ts +125 -0
  169. package/src/__tests__/messaging-skill-split.test.ts +3 -34
  170. package/src/__tests__/migration-export-http.test.ts +6 -6
  171. package/src/__tests__/migration-import-commit-http.test.ts +8 -6
  172. package/src/__tests__/migration-import-from-url.test.ts +684 -0
  173. package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
  174. package/src/__tests__/migration-validate-http.test.ts +3 -3
  175. package/src/__tests__/mock-gateway-ipc.ts +151 -0
  176. package/src/__tests__/model-intents.test.ts +10 -84
  177. package/src/__tests__/notification-decision-fallback.test.ts +0 -10
  178. package/src/__tests__/notification-decision-identity.test.ts +0 -9
  179. package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
  180. package/src/__tests__/oauth-apps-routes.test.ts +1 -0
  181. package/src/__tests__/oauth-cli.test.ts +2 -0
  182. package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
  183. package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
  184. package/src/__tests__/oauth-providers-routes.test.ts +2 -0
  185. package/src/__tests__/oauth-store.test.ts +95 -7
  186. package/src/__tests__/oauth2-gateway-transport.test.ts +257 -9
  187. package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
  188. package/src/__tests__/onboarding-template-contract.test.ts +6 -13
  189. package/src/__tests__/openai-provider.test.ts +183 -0
  190. package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
  191. package/src/__tests__/openai-responses-provider.test.ts +1501 -0
  192. package/src/__tests__/openrouter-provider-only.test.ts +135 -0
  193. package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
  194. package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
  195. package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
  196. package/src/__tests__/permission-mode.test.ts +16 -0
  197. package/src/__tests__/permission-types.test.ts +0 -1
  198. package/src/__tests__/persona-resolver.test.ts +251 -0
  199. package/src/__tests__/pkb-autoinject.test.ts +37 -1
  200. package/src/__tests__/platform-bash-auto-approve.test.ts +5 -1
  201. package/src/__tests__/platform.test.ts +92 -1
  202. package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
  203. package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
  204. package/src/__tests__/pricing.test.ts +224 -3
  205. package/src/__tests__/profiler-routes.test.ts +1 -1
  206. package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
  207. package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
  208. package/src/__tests__/provider-error-scenarios.test.ts +135 -6
  209. package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
  210. package/src/__tests__/provider-registry-ollama.test.ts +1 -2
  211. package/src/__tests__/proxy-approval-callback.test.ts +0 -1
  212. package/src/__tests__/qdrant-manager.test.ts +29 -8
  213. package/src/__tests__/reaction-persistence.test.ts +560 -0
  214. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
  215. package/src/__tests__/relationship-state-contract.test.ts +175 -0
  216. package/src/__tests__/relay-server.test.ts +424 -6
  217. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  218. package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
  219. package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
  220. package/src/__tests__/risk-classifier-parity.test.ts +230 -0
  221. package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
  222. package/src/__tests__/search-skills-unified.test.ts +118 -0
  223. package/src/__tests__/secret-ingress-http.test.ts +28 -0
  224. package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
  225. package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
  226. package/src/__tests__/secret-scanner-executor.test.ts +5 -1
  227. package/src/__tests__/secure-keys.test.ts +107 -0
  228. package/src/__tests__/send-endpoint-busy.test.ts +34 -2
  229. package/src/__tests__/sequence-store.test.ts +1 -1
  230. package/src/__tests__/server-history-render.test.ts +80 -0
  231. package/src/__tests__/settings-routes.test.ts +201 -0
  232. package/src/__tests__/shell-parser-property.test.ts +13 -13
  233. package/src/__tests__/skill-cache-store.test.ts +182 -0
  234. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  235. package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
  236. package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
  237. package/src/__tests__/skills.test.ts +19 -30
  238. package/src/__tests__/skillssh-files.test.ts +446 -0
  239. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  240. package/src/__tests__/slack-block-formatting.test.ts +110 -0
  241. package/src/__tests__/slack-channel-config.test.ts +564 -1
  242. package/src/__tests__/slack-skill.test.ts +3 -8
  243. package/src/__tests__/starter-bundle.test.ts +35 -0
  244. package/src/__tests__/stt-catalog-parity.test.ts +282 -0
  245. package/src/__tests__/stt-stream-session.test.ts +535 -0
  246. package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
  247. package/src/__tests__/suggestion-routes.test.ts +160 -3
  248. package/src/__tests__/system-prompt.test.ts +126 -53
  249. package/src/__tests__/task-runner.test.ts +3 -1
  250. package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
  251. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  252. package/src/__tests__/terminal-tools.test.ts +26 -7
  253. package/src/__tests__/test-preload.ts +18 -0
  254. package/src/__tests__/test-support/browser-skill-harness.ts +2 -49
  255. package/src/__tests__/thread-backfill.test.ts +941 -0
  256. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
  257. package/src/__tests__/tool-executor-lifecycle-events.test.ts +10 -6
  258. package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
  259. package/src/__tests__/tool-executor.test.ts +88 -113
  260. package/src/__tests__/tool-result-truncation.test.ts +36 -0
  261. package/src/__tests__/trust-store.test.ts +442 -103
  262. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
  263. package/src/__tests__/tts-catalog-parity.test.ts +345 -0
  264. package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
  265. package/src/__tests__/twilio-routes.test.ts +376 -0
  266. package/src/__tests__/unicode.test.ts +293 -0
  267. package/src/__tests__/update-bulletin-job.test.ts +389 -0
  268. package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
  269. package/src/__tests__/usage-routes.test.ts +25 -4
  270. package/src/__tests__/user-reference.test.ts +46 -61
  271. package/src/__tests__/verification-control-plane-policy.test.ts +5 -22
  272. package/src/__tests__/voice-config-update.test.ts +403 -0
  273. package/src/__tests__/voice-quality.test.ts +434 -19
  274. package/src/__tests__/voice-session-bridge.test.ts +39 -0
  275. package/src/__tests__/volume-security-guard.test.ts +3 -2
  276. package/src/__tests__/web-search-history.test.ts +337 -0
  277. package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
  278. package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
  279. package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
  280. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
  281. package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
  282. package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
  283. package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
  284. package/src/__tests__/workspace-migration-meets.test.ts +244 -0
  285. package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
  286. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
  287. package/src/__tests__/workspace-policy.test.ts +1 -11
  288. package/src/acp/client-handler.ts +1 -2
  289. package/src/agent/image-optimize.ts +24 -12
  290. package/src/agent/loop.ts +251 -19
  291. package/src/avatar/resvg-lazy.test.ts +136 -0
  292. package/src/avatar/resvg-lazy.ts +82 -9
  293. package/src/avatar/traits-png-sync.ts +21 -1
  294. package/src/backup/__tests__/backup-key.test.ts +152 -0
  295. package/src/backup/__tests__/backup-worker.test.ts +767 -0
  296. package/src/backup/__tests__/list-snapshots.test.ts +87 -0
  297. package/src/backup/__tests__/local-writer.test.ts +218 -0
  298. package/src/backup/__tests__/offsite-writer.test.ts +641 -0
  299. package/src/backup/__tests__/paths.test.ts +300 -0
  300. package/src/backup/__tests__/restore.test.ts +498 -0
  301. package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
  302. package/src/backup/__tests__/stream-crypt.test.ts +228 -0
  303. package/src/backup/backup-key.ts +137 -0
  304. package/src/backup/backup-worker.ts +459 -0
  305. package/src/backup/list-snapshots.ts +147 -0
  306. package/src/backup/local-writer.ts +133 -0
  307. package/src/backup/offsite-writer.ts +222 -0
  308. package/src/backup/paths.ts +226 -0
  309. package/src/backup/restore.ts +322 -0
  310. package/src/backup/snapshot-lock.ts +431 -0
  311. package/src/backup/stream-crypt.ts +263 -0
  312. package/src/browser/__tests__/operations.test.ts +163 -0
  313. package/src/browser/identifiers.ts +51 -0
  314. package/src/browser/operations.ts +660 -0
  315. package/src/browser/types.ts +81 -0
  316. package/src/bundler/package-resolver.ts +4 -0
  317. package/src/calls/audio-store.ts +11 -5
  318. package/src/calls/call-controller.ts +226 -71
  319. package/src/calls/call-domain.ts +9 -0
  320. package/src/calls/call-speech-output.ts +190 -0
  321. package/src/calls/call-transport.ts +77 -0
  322. package/src/calls/guardian-question-copy.ts +2 -2
  323. package/src/calls/media-stream-audio-transcode.ts +173 -0
  324. package/src/calls/media-stream-output.ts +660 -0
  325. package/src/calls/media-stream-parser.ts +300 -0
  326. package/src/calls/media-stream-protocol.ts +166 -0
  327. package/src/calls/media-stream-server.ts +592 -0
  328. package/src/calls/media-stream-stt-session.ts +460 -0
  329. package/src/calls/media-turn-detector.ts +230 -0
  330. package/src/calls/relay-server.ts +90 -75
  331. package/src/calls/resolve-call-tts-provider.ts +136 -0
  332. package/src/calls/telephony-stt-routing.ts +145 -0
  333. package/src/calls/tts-call-strategy.ts +161 -0
  334. package/src/calls/tts-text-sanitizer.ts +32 -16
  335. package/src/calls/twilio-routes.ts +281 -17
  336. package/src/calls/voice-quality.ts +78 -35
  337. package/src/calls/voice-session-bridge.ts +9 -1
  338. package/src/channels/types.ts +16 -0
  339. package/src/cli/AGENTS.md +1 -1
  340. package/src/cli/__tests__/run-assistant-command.ts +11 -1
  341. package/src/cli/commands/__tests__/attachment.test.ts +438 -0
  342. package/src/cli/commands/__tests__/backup.test.ts +1165 -0
  343. package/src/cli/commands/__tests__/browser.test.ts +554 -0
  344. package/src/cli/commands/__tests__/cache.test.ts +623 -0
  345. package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
  346. package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
  347. package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
  348. package/src/cli/commands/__tests__/email-download.test.ts +16 -1
  349. package/src/cli/commands/__tests__/email-list.test.ts +28 -4
  350. package/src/cli/commands/__tests__/email-register.test.ts +4 -4
  351. package/src/cli/commands/__tests__/email-send.test.ts +130 -5
  352. package/src/cli/commands/__tests__/email-status.test.ts +5 -1
  353. package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
  354. package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
  355. package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
  356. package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
  357. package/src/cli/commands/__tests__/task.test.ts +913 -0
  358. package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
  359. package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
  360. package/src/cli/commands/__tests__/ui.test.ts +1215 -0
  361. package/src/cli/commands/__tests__/watchers.test.ts +716 -0
  362. package/src/cli/commands/attachment.ts +182 -0
  363. package/src/cli/commands/backup.ts +993 -0
  364. package/src/cli/commands/browser.ts +350 -0
  365. package/src/cli/commands/cache.ts +341 -0
  366. package/src/cli/commands/completions.ts +0 -3
  367. package/src/cli/commands/config.ts +6 -6
  368. package/src/cli/commands/conversations-import.ts +347 -0
  369. package/src/cli/commands/conversations.ts +90 -0
  370. package/src/cli/commands/credentials.ts +0 -1
  371. package/src/cli/commands/domain.ts +210 -0
  372. package/src/cli/commands/email.ts +308 -16
  373. package/src/cli/commands/image-generation.ts +300 -0
  374. package/src/cli/commands/inference.ts +200 -0
  375. package/src/cli/commands/memory.ts +127 -17
  376. package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
  377. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
  378. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
  379. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
  380. package/src/cli/commands/oauth/mode.ts +12 -3
  381. package/src/cli/commands/oauth/providers.ts +15 -0
  382. package/src/cli/commands/oauth/shared.ts +2 -1
  383. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -10
  384. package/src/cli/commands/platform/__tests__/connect.test.ts +6 -1
  385. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -2
  386. package/src/cli/commands/platform/__tests__/status.test.ts +6 -1
  387. package/src/cli/commands/stt.ts +339 -0
  388. package/src/cli/commands/task.ts +795 -0
  389. package/src/cli/commands/trust.ts +50 -19
  390. package/src/cli/commands/tts.ts +273 -0
  391. package/src/cli/commands/ui.ts +670 -0
  392. package/src/cli/commands/watchers.ts +509 -0
  393. package/src/cli/lib/daemon-credential-client.ts +0 -19
  394. package/src/cli/program.ts +53 -8
  395. package/src/cli.ts +0 -37
  396. package/src/config/__tests__/backup-schema.test.ts +134 -0
  397. package/src/config/assistant-feature-flags.ts +61 -62
  398. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
  399. package/src/config/bundled-skills/contacts/SKILL.md +2 -2
  400. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
  401. package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
  402. package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
  403. package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
  404. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
  405. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
  406. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
  407. package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
  408. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  409. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
  410. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  411. package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
  412. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -2
  413. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
  414. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
  415. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
  416. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  417. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +28 -18
  418. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  419. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  420. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
  421. package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
  422. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
  423. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
  424. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
  425. package/src/config/bundled-tool-registry.ts +0 -167
  426. package/src/config/env-registry.ts +24 -0
  427. package/src/config/env.ts +39 -10
  428. package/src/config/feature-flag-registry.json +63 -15
  429. package/src/config/llm-resolver.ts +128 -0
  430. package/src/config/loader.ts +220 -22
  431. package/src/config/raw-config-utils.ts +30 -2
  432. package/src/config/sanitize-for-transfer.ts +35 -0
  433. package/src/config/schema.ts +65 -51
  434. package/src/config/schemas/__tests__/stt.test.ts +43 -0
  435. package/src/config/schemas/analysis.ts +32 -0
  436. package/src/config/schemas/backup.ts +72 -0
  437. package/src/config/schemas/calls.ts +1 -30
  438. package/src/config/schemas/elevenlabs.ts +0 -59
  439. package/src/config/schemas/filing.ts +49 -14
  440. package/src/config/schemas/heartbeat.ts +27 -10
  441. package/src/config/schemas/host-browser.ts +47 -1
  442. package/src/config/schemas/inference.ts +3 -23
  443. package/src/config/schemas/llm.ts +318 -0
  444. package/src/config/schemas/memory-lifecycle.ts +14 -2
  445. package/src/config/schemas/memory-processing.ts +1 -9
  446. package/src/config/schemas/notifications.ts +4 -11
  447. package/src/config/schemas/platform.ts +3 -9
  448. package/src/config/schemas/security.ts +33 -0
  449. package/src/config/schemas/services.ts +53 -4
  450. package/src/config/schemas/stt.ts +60 -0
  451. package/src/config/schemas/tts.ts +283 -0
  452. package/src/config/schemas/updates.ts +14 -0
  453. package/src/config/schemas/workspace-git.ts +3 -40
  454. package/src/config/skills.ts +6 -2
  455. package/src/config/types.ts +4 -0
  456. package/src/contacts/contact-store.ts +56 -11
  457. package/src/contacts/contacts-write.ts +38 -1
  458. package/src/context/__tests__/compact-prompt.test.ts +45 -0
  459. package/src/context/__tests__/microcompact.test.ts +805 -0
  460. package/src/context/estimator-calibration.ts +136 -0
  461. package/src/context/microcompact.ts +443 -0
  462. package/src/context/post-turn-tool-result-truncation.ts +3 -2
  463. package/src/context/prompts/compact.md +12 -0
  464. package/src/context/token-estimator.ts +61 -3
  465. package/src/context/tool-result-truncation.ts +2 -1
  466. package/src/context/window-manager.ts +272 -35
  467. package/src/credential-execution/approval-bridge.ts +0 -1
  468. package/src/credential-execution/executable-discovery.ts +23 -2
  469. package/src/credential-execution/process-manager.test.ts +109 -0
  470. package/src/credential-execution/process-manager.ts +96 -2
  471. package/src/credential-health/credential-health-service.ts +366 -0
  472. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
  473. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
  474. package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
  475. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
  476. package/src/daemon/approval-generators.ts +29 -4
  477. package/src/daemon/assistant-attachments.ts +24 -13
  478. package/src/daemon/classifier.ts +2 -2
  479. package/src/daemon/config-watcher.ts +99 -6
  480. package/src/daemon/context-overflow-reducer.ts +4 -1
  481. package/src/daemon/conversation-agent-loop-handlers.ts +85 -12
  482. package/src/daemon/conversation-agent-loop.ts +563 -104
  483. package/src/daemon/conversation-attachments.ts +2 -6
  484. package/src/daemon/conversation-error.ts +46 -0
  485. package/src/daemon/conversation-history.ts +40 -6
  486. package/src/daemon/conversation-launch.ts +220 -0
  487. package/src/daemon/conversation-lifecycle.ts +85 -11
  488. package/src/daemon/conversation-messaging.ts +110 -7
  489. package/src/daemon/conversation-notifiers.ts +5 -0
  490. package/src/daemon/conversation-process.ts +591 -23
  491. package/src/daemon/conversation-queue-manager.ts +27 -0
  492. package/src/daemon/conversation-runtime-assembly.ts +769 -28
  493. package/src/daemon/conversation-slash.ts +38 -2
  494. package/src/daemon/conversation-surfaces.ts +483 -5
  495. package/src/daemon/conversation-tool-setup.ts +35 -5
  496. package/src/daemon/conversation-usage.ts +8 -5
  497. package/src/daemon/conversation.ts +193 -47
  498. package/src/daemon/external-skills-bootstrap.ts +41 -0
  499. package/src/daemon/guardian-action-generators.ts +34 -14
  500. package/src/daemon/handlers/config-model.test.ts +86 -0
  501. package/src/daemon/handlers/config-model.ts +54 -12
  502. package/src/daemon/handlers/config-slack-channel.ts +269 -94
  503. package/src/daemon/handlers/conversations.ts +13 -3
  504. package/src/daemon/handlers/shared.ts +51 -1
  505. package/src/daemon/handlers/skills.ts +323 -79
  506. package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
  507. package/src/daemon/host-browser-proxy.ts +2 -1
  508. package/src/daemon/lifecycle.ts +185 -26
  509. package/src/daemon/message-protocol.ts +6 -0
  510. package/src/daemon/message-types/conversations.ts +48 -1
  511. package/src/daemon/message-types/home.ts +40 -0
  512. package/src/daemon/message-types/meet.ts +143 -0
  513. package/src/daemon/message-types/messages.ts +23 -1
  514. package/src/daemon/message-types/schedules.ts +34 -2
  515. package/src/daemon/message-types/skills.ts +16 -0
  516. package/src/daemon/message-types/surfaces.ts +2 -0
  517. package/src/daemon/message-types/trust.ts +0 -2
  518. package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
  519. package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
  520. package/src/daemon/pkb-context-tracker.test.ts +169 -0
  521. package/src/daemon/pkb-context-tracker.ts +125 -0
  522. package/src/daemon/pkb-reminder-builder.test.ts +70 -0
  523. package/src/daemon/pkb-reminder-builder.ts +31 -0
  524. package/src/daemon/providers-setup.ts +6 -0
  525. package/src/daemon/server.ts +463 -10
  526. package/src/daemon/shutdown-handlers.ts +32 -4
  527. package/src/daemon/shutdown-registry.ts +40 -0
  528. package/src/daemon/tool-side-effects.ts +9 -9
  529. package/src/daemon/watch-handler.ts +4 -4
  530. package/src/daemon/web-search-history.ts +126 -0
  531. package/src/email/html-renderer.ts +76 -0
  532. package/src/events/domain-events.ts +0 -1
  533. package/src/filing/filing-service.ts +9 -10
  534. package/src/heartbeat/heartbeat-service.ts +156 -22
  535. package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
  536. package/src/home/__tests__/emit-feed-event.test.ts +169 -0
  537. package/src/home/__tests__/feed-scheduler.test.ts +222 -0
  538. package/src/home/__tests__/feed-types.test.ts +275 -0
  539. package/src/home/__tests__/feed-writer.test.ts +688 -0
  540. package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
  541. package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
  542. package/src/home/__tests__/progress-formula.test.ts +213 -0
  543. package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
  544. package/src/home/__tests__/rollup-producer.test.ts +442 -0
  545. package/src/home/assistant-feed-authoring.ts +128 -0
  546. package/src/home/emit-feed-event.ts +162 -0
  547. package/src/home/feed-scheduler.ts +263 -0
  548. package/src/home/feed-types.ts +235 -0
  549. package/src/home/feed-writer.ts +469 -0
  550. package/src/home/platform-gmail-digest.ts +163 -0
  551. package/src/home/progress-formula.ts +86 -0
  552. package/src/home/relationship-state-writer.ts +824 -0
  553. package/src/home/relationship-state.ts +143 -0
  554. package/src/home/rollup-producer.ts +413 -0
  555. package/src/home/suggested-prompts.ts +101 -0
  556. package/src/hooks/runner.ts +7 -0
  557. package/src/inbound/platform-callback-registration.ts +12 -3
  558. package/src/inbound/public-ingress-urls.ts +12 -0
  559. package/src/instrument.ts +1 -1
  560. package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
  561. package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
  562. package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
  563. package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
  564. package/src/ipc/__tests__/socket-path.test.ts +73 -0
  565. package/src/ipc/__tests__/task-ipc.test.ts +577 -0
  566. package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
  567. package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
  568. package/src/ipc/cli-client.ts +152 -0
  569. package/src/ipc/cli-server.ts +252 -0
  570. package/src/ipc/gateway-client.ts +180 -0
  571. package/src/ipc/routes/attachment.ts +114 -0
  572. package/src/ipc/routes/browser-context.ts +61 -0
  573. package/src/ipc/routes/browser.ts +96 -0
  574. package/src/ipc/routes/cache.ts +96 -0
  575. package/src/ipc/routes/index.ts +21 -0
  576. package/src/ipc/routes/task-queue.ts +226 -0
  577. package/src/ipc/routes/task.ts +173 -0
  578. package/src/ipc/routes/ui-request.ts +50 -0
  579. package/src/ipc/routes/wake-conversation.ts +19 -0
  580. package/src/ipc/routes/watcher.ts +203 -0
  581. package/src/ipc/socket-path.ts +100 -0
  582. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
  583. package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
  584. package/src/memory/__tests__/conversation-analyze-job.test.ts +233 -0
  585. package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
  586. package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
  587. package/src/memory/admin.ts +18 -0
  588. package/src/memory/app-store.ts +1 -1
  589. package/src/memory/attachments-store.ts +70 -0
  590. package/src/memory/auto-analysis-enqueue.ts +127 -0
  591. package/src/memory/auto-analysis-guard.ts +27 -0
  592. package/src/memory/cleanup-schedule-state.ts +37 -0
  593. package/src/memory/conversation-analyze-job.ts +74 -0
  594. package/src/memory/conversation-attention-store.ts +13 -6
  595. package/src/memory/conversation-crud.ts +199 -0
  596. package/src/memory/conversation-disk-view.ts +7 -0
  597. package/src/memory/conversation-group-migration.ts +65 -1
  598. package/src/memory/conversation-queries.ts +6 -5
  599. package/src/memory/conversation-title-service.ts +7 -4
  600. package/src/memory/db-init.ts +8 -0
  601. package/src/memory/db-maintenance.ts +108 -0
  602. package/src/memory/db.ts +1 -0
  603. package/src/memory/embedding-backend.ts +1 -1
  604. package/src/memory/graph/compaction.ts +299 -0
  605. package/src/memory/graph/consolidation.ts +4 -4
  606. package/src/memory/graph/conversation-graph-memory.ts +104 -29
  607. package/src/memory/graph/extraction.test.ts +295 -2
  608. package/src/memory/graph/extraction.ts +181 -51
  609. package/src/memory/graph/graph-search.test.ts +92 -0
  610. package/src/memory/graph/graph-search.ts +4 -1
  611. package/src/memory/graph/narrative.ts +2 -2
  612. package/src/memory/graph/pattern-scan.ts +2 -2
  613. package/src/memory/graph/retriever.test.ts +459 -0
  614. package/src/memory/graph/retriever.ts +257 -66
  615. package/src/memory/graph/scoring.test.ts +186 -0
  616. package/src/memory/graph/scoring.ts +31 -1
  617. package/src/memory/graph/store.ts +41 -0
  618. package/src/memory/graph/tool-handlers.ts +27 -0
  619. package/src/memory/graph/tools.ts +6 -1
  620. package/src/memory/group-crud.ts +6 -1
  621. package/src/memory/indexer.ts +95 -16
  622. package/src/memory/job-handlers/cleanup.ts +11 -8
  623. package/src/memory/job-handlers/conversation-starters.ts +39 -30
  624. package/src/memory/job-handlers/summarization.ts +2 -2
  625. package/src/memory/job-utils.ts +7 -1
  626. package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
  627. package/src/memory/jobs/embed-pkb-file.ts +54 -0
  628. package/src/memory/jobs-store.ts +106 -5
  629. package/src/memory/jobs-worker.ts +26 -9
  630. package/src/memory/llm-usage-store.ts +92 -56
  631. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
  632. package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
  633. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
  634. package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
  635. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
  636. package/src/memory/migrations/index.ts +7 -0
  637. package/src/memory/migrations/registry.ts +8 -0
  638. package/src/memory/pkb/pkb-index.test.ts +368 -0
  639. package/src/memory/pkb/pkb-index.ts +255 -0
  640. package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
  641. package/src/memory/pkb/pkb-reconcile.ts +148 -0
  642. package/src/memory/pkb/pkb-search.test.ts +438 -0
  643. package/src/memory/pkb/pkb-search.ts +137 -0
  644. package/src/memory/pkb/types.ts +53 -0
  645. package/src/memory/qdrant-client.ts +122 -1
  646. package/src/memory/qdrant-manager.ts +43 -16
  647. package/src/memory/schema/conversations.ts +2 -0
  648. package/src/memory/schema/oauth.ts +3 -0
  649. package/src/memory/slack-thread-store.ts +37 -0
  650. package/src/memory/usage-buckets.ts +396 -0
  651. package/src/messaging/providers/gmail/adapter.ts +6 -16
  652. package/src/messaging/providers/gmail/client.ts +79 -6
  653. package/src/messaging/providers/gmail/types.ts +7 -0
  654. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
  655. package/src/messaging/providers/slack/adapter.ts +155 -38
  656. package/src/messaging/providers/slack/backfill.test.ts +257 -0
  657. package/src/messaging/providers/slack/backfill.ts +101 -0
  658. package/src/messaging/providers/slack/client.ts +16 -0
  659. package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
  660. package/src/messaging/providers/slack/message-metadata.ts +123 -0
  661. package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
  662. package/src/messaging/providers/slack/render-transcript.ts +443 -0
  663. package/src/messaging/providers/slack/types.ts +4 -0
  664. package/src/messaging/style-analyzer.ts +5 -2
  665. package/src/notifications/README.md +9 -5
  666. package/src/notifications/decision-engine.ts +6 -12
  667. package/src/notifications/preference-extractor.ts +2 -6
  668. package/src/notifications/signal.ts +5 -0
  669. package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
  670. package/src/oauth/byo-connection.test.ts +18 -1
  671. package/src/oauth/byo-connection.ts +3 -1
  672. package/src/oauth/connect-orchestrator.ts +2 -0
  673. package/src/oauth/connection-resolver.ts +6 -2
  674. package/src/oauth/connection.ts +2 -0
  675. package/src/oauth/oauth-store.ts +10 -0
  676. package/src/oauth/platform-connection.test.ts +145 -0
  677. package/src/oauth/platform-connection.ts +62 -31
  678. package/src/oauth/seed-providers.ts +10 -1
  679. package/src/permissions/approval-policy.test.ts +948 -0
  680. package/src/permissions/approval-policy.ts +257 -0
  681. package/src/permissions/bash-risk-classifier.test.ts +1208 -0
  682. package/src/permissions/bash-risk-classifier.ts +707 -0
  683. package/src/permissions/checker.ts +218 -699
  684. package/src/permissions/command-registry.test.ts +535 -0
  685. package/src/permissions/command-registry.ts +825 -0
  686. package/src/permissions/defaults.ts +71 -75
  687. package/src/permissions/file-risk-classifier.test.ts +535 -0
  688. package/src/permissions/file-risk-classifier.ts +274 -0
  689. package/src/permissions/risk-types.ts +205 -0
  690. package/src/permissions/secret-prompter.ts +53 -2
  691. package/src/permissions/skill-risk-classifier.test.ts +311 -0
  692. package/src/permissions/skill-risk-classifier.ts +214 -0
  693. package/src/permissions/trust-client.ts +52 -25
  694. package/src/permissions/trust-store-interface.ts +1 -6
  695. package/src/permissions/trust-store.ts +164 -65
  696. package/src/permissions/types.ts +23 -14
  697. package/src/permissions/web-risk-classifier.test.ts +170 -0
  698. package/src/permissions/web-risk-classifier.ts +89 -0
  699. package/src/permissions/workspace-policy.ts +1 -13
  700. package/src/platform/client.test.ts +10 -0
  701. package/src/platform/client.ts +19 -1
  702. package/src/platform/sync-identity.ts +129 -0
  703. package/src/prompts/persona-resolver.ts +127 -3
  704. package/src/prompts/system-prompt.ts +78 -38
  705. package/src/prompts/templates/BOOTSTRAP.md +5 -5
  706. package/src/prompts/templates/SOUL.md +5 -3
  707. package/src/prompts/templates/channels/slack.md +20 -0
  708. package/src/prompts/update-bulletin-job.ts +190 -0
  709. package/src/prompts/user-reference.ts +20 -17
  710. package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
  711. package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
  712. package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
  713. package/src/providers/__tests__/retry-callsite.test.ts +424 -0
  714. package/src/providers/anthropic/client.ts +335 -70
  715. package/src/providers/call-site-routing.ts +71 -0
  716. package/src/providers/fireworks/client.ts +2 -2
  717. package/src/providers/gemini/client.ts +74 -3
  718. package/src/providers/managed-proxy/constants.ts +2 -1
  719. package/src/providers/model-catalog.ts +502 -28
  720. package/src/providers/model-intents.ts +8 -8
  721. package/src/providers/ollama/client.ts +2 -2
  722. package/src/providers/openai/chat-completions-provider.ts +530 -0
  723. package/src/providers/openai/client.ts +25 -440
  724. package/src/providers/openai/responses-provider.ts +579 -0
  725. package/src/providers/openrouter/client.ts +168 -4
  726. package/src/providers/provider-env-vars.ts +56 -0
  727. package/src/providers/provider-secret-catalog.ts +139 -0
  728. package/src/providers/provider-send-message.ts +22 -5
  729. package/src/providers/ratelimit.ts +4 -0
  730. package/src/providers/registry.ts +21 -10
  731. package/src/providers/retry.ts +185 -39
  732. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
  733. package/src/providers/speech-to-text/__tests__/resolve.test.ts +883 -0
  734. package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
  735. package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
  736. package/src/providers/speech-to-text/deepgram.test.ts +332 -0
  737. package/src/providers/speech-to-text/deepgram.ts +115 -0
  738. package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
  739. package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
  740. package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
  741. package/src/providers/speech-to-text/google-gemini.ts +101 -0
  742. package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
  743. package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
  744. package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
  745. package/src/providers/speech-to-text/openai-whisper.ts +63 -33
  746. package/src/providers/speech-to-text/provider-catalog.ts +323 -0
  747. package/src/providers/speech-to-text/resolve.ts +393 -6
  748. package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
  749. package/src/providers/speech-to-text/xai-realtime.ts +796 -0
  750. package/src/providers/speech-to-text/xai.test.ts +155 -0
  751. package/src/providers/speech-to-text/xai.ts +97 -0
  752. package/src/providers/types.ts +102 -3
  753. package/src/runtime/AGENTS.md +45 -3
  754. package/src/runtime/__tests__/agent-wake.test.ts +872 -0
  755. package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
  756. package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
  757. package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
  758. package/src/runtime/agent-wake.ts +553 -0
  759. package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
  760. package/src/runtime/auth/route-policy.ts +34 -5
  761. package/src/runtime/auth/token-service.ts +56 -1
  762. package/src/runtime/btw-sidechain.ts +15 -3
  763. package/src/runtime/capability-tokens.ts +10 -10
  764. package/src/runtime/channel-invite-transport.ts +1 -1
  765. package/src/runtime/channel-invite-transports/email.ts +14 -6
  766. package/src/runtime/channel-readiness-service.ts +12 -22
  767. package/src/runtime/channel-reply-delivery.ts +106 -2
  768. package/src/runtime/chrome-extension-registry.ts +38 -2
  769. package/src/runtime/decision-token.ts +116 -0
  770. package/src/runtime/gateway-client.ts +2 -2
  771. package/src/runtime/http-router.ts +32 -0
  772. package/src/runtime/http-server.ts +447 -11
  773. package/src/runtime/http-types.ts +29 -3
  774. package/src/runtime/interactive-ui.ts +362 -0
  775. package/src/runtime/invite-instruction-generator.ts +2 -2
  776. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
  777. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
  778. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
  779. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
  780. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
  781. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
  782. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
  783. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
  784. package/src/runtime/migrations/gcs-signed-url.ts +162 -0
  785. package/src/runtime/migrations/migration-transport.ts +1 -0
  786. package/src/runtime/migrations/migration-wizard.ts +1 -0
  787. package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
  788. package/src/runtime/migrations/vbundle-importer.ts +187 -8
  789. package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
  790. package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
  791. package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
  792. package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
  793. package/src/runtime/migrations/vbundle-validator.ts +15 -6
  794. package/src/runtime/pending-interactions.ts +0 -11
  795. package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
  796. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +618 -0
  797. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +247 -0
  798. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
  799. package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
  800. package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
  801. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
  802. package/src/runtime/routes/app-management-routes.ts +12 -18
  803. package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
  804. package/src/runtime/routes/approval-routes.ts +12 -17
  805. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
  806. package/src/runtime/routes/attachment-routes.test.ts +9 -3
  807. package/src/runtime/routes/attachment-routes.ts +216 -17
  808. package/src/runtime/routes/avatar-routes.ts +20 -4
  809. package/src/runtime/routes/backup-routes.ts +519 -0
  810. package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
  811. package/src/runtime/routes/btw-routes.ts +9 -10
  812. package/src/runtime/routes/contact-routes.test.ts +298 -0
  813. package/src/runtime/routes/contact-routes.ts +132 -5
  814. package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
  815. package/src/runtime/routes/conversation-management-routes.ts +133 -0
  816. package/src/runtime/routes/conversation-routes.ts +487 -160
  817. package/src/runtime/routes/debug-routes.ts +1 -1
  818. package/src/runtime/routes/diagnostics-routes.ts +6 -4
  819. package/src/runtime/routes/events-routes.ts +16 -0
  820. package/src/runtime/routes/filing-routes.ts +93 -0
  821. package/src/runtime/routes/guardian-approval-interception.ts +33 -3
  822. package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
  823. package/src/runtime/routes/home-feed-routes.ts +452 -0
  824. package/src/runtime/routes/home-state-routes.ts +138 -0
  825. package/src/runtime/routes/host-browser-routes.ts +3 -14
  826. package/src/runtime/routes/identity-intro-cache.ts +7 -3
  827. package/src/runtime/routes/identity-routes.ts +3 -17
  828. package/src/runtime/routes/inbound-message-handler.ts +912 -2
  829. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
  830. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
  831. package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
  832. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
  833. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
  834. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
  835. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
  836. package/src/runtime/routes/integrations/slack/channel.ts +36 -6
  837. package/src/runtime/routes/integrations/slack/share.ts +45 -7
  838. package/src/runtime/routes/llm-context-normalization.ts +325 -0
  839. package/src/runtime/routes/memory-item-routes.test.ts +3 -2
  840. package/src/runtime/routes/migration-routes.ts +722 -91
  841. package/src/runtime/routes/settings-routes.ts +26 -7
  842. package/src/runtime/routes/skills-routes.ts +76 -7
  843. package/src/runtime/routes/stt-routes.ts +233 -0
  844. package/src/runtime/routes/surface-action-routes.ts +41 -2
  845. package/src/runtime/routes/trust-rules-routes.ts +30 -14
  846. package/src/runtime/routes/tts-routes.ts +108 -24
  847. package/src/runtime/routes/usage-routes.ts +30 -2
  848. package/src/runtime/routes/user-route-dispatcher.ts +50 -5
  849. package/src/runtime/routes/user-routes.ts +13 -1
  850. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  851. package/src/runtime/routes/work-items-routes.ts +11 -3
  852. package/src/runtime/runtime-mode.ts +33 -0
  853. package/src/runtime/services/__tests__/analyze-conversation.test.ts +426 -0
  854. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
  855. package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
  856. package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
  857. package/src/runtime/services/analyze-conversation.ts +340 -0
  858. package/src/runtime/services/analyze-deps-singleton.ts +32 -0
  859. package/src/runtime/services/auto-analysis-prompt.ts +55 -0
  860. package/src/runtime/skill-route-registry.ts +71 -0
  861. package/src/runtime/slack-block-formatting.ts +437 -10
  862. package/src/schedule/scheduler.ts +58 -0
  863. package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
  864. package/src/security/__tests__/untrusted-content.test.ts +109 -0
  865. package/src/security/oauth2.ts +122 -37
  866. package/src/security/secure-keys.ts +32 -10
  867. package/src/security/token-manager.ts +35 -13
  868. package/src/security/untrusted-content.ts +102 -0
  869. package/src/sequence/engine.ts +23 -0
  870. package/src/sequence/types.ts +1 -1
  871. package/src/skills/catalog-cache.ts +26 -7
  872. package/src/skills/catalog-files.ts +64 -2
  873. package/src/skills/catalog-install.ts +31 -3
  874. package/src/skills/category-inference.ts +122 -0
  875. package/src/skills/clawhub-files.ts +213 -0
  876. package/src/skills/clawhub.ts +84 -23
  877. package/src/skills/skill-cache-store.ts +97 -0
  878. package/src/skills/skill-file-provider.ts +40 -0
  879. package/src/skills/skillssh-files.ts +395 -0
  880. package/src/skills/skillssh-registry.ts +4 -4
  881. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +468 -0
  882. package/src/stt/__tests__/types.test.ts +89 -0
  883. package/src/stt/daemon-batch-transcriber.ts +228 -0
  884. package/src/stt/stt-stream-session.ts +506 -0
  885. package/src/stt/types.ts +334 -0
  886. package/src/stt/wav-encoder.test.ts +373 -0
  887. package/src/stt/wav-encoder.ts +175 -0
  888. package/src/subagent/manager.ts +79 -27
  889. package/src/tasks/ephemeral-permissions.ts +9 -4
  890. package/src/telemetry/usage-telemetry-reporter.ts +27 -5
  891. package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
  892. package/src/tools/browser/__tests__/browser-status.test.ts +166 -0
  893. package/src/tools/browser/browser-execution.ts +1208 -41
  894. package/src/tools/browser/browser-manager.ts +45 -0
  895. package/src/tools/browser/browser-mode-constants.ts +12 -0
  896. package/src/tools/browser/browser-mode.ts +92 -0
  897. package/src/tools/browser/browser-status-constants.ts +33 -0
  898. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
  899. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
  900. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
  901. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
  902. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +205 -17
  903. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
  904. package/src/tools/browser/cdp-client/errors.ts +15 -0
  905. package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
  906. package/src/tools/browser/cdp-client/factory.ts +797 -87
  907. package/src/tools/browser/cdp-client/index.ts +16 -2
  908. package/src/tools/browser/cdp-client/types.ts +68 -0
  909. package/src/tools/credentials/tool-policy.ts +39 -5
  910. package/src/tools/credentials/vault.ts +41 -7
  911. package/src/tools/executor.ts +4 -0
  912. package/src/tools/filesystem/write.ts +52 -0
  913. package/src/tools/host-terminal/host-shell.ts +45 -5
  914. package/src/tools/memory/register.test.ts +185 -0
  915. package/src/tools/memory/register.ts +3 -1
  916. package/src/tools/network/web-fetch.ts +25 -12
  917. package/src/tools/network/web-search.ts +20 -2
  918. package/src/tools/permission-checker.ts +36 -15
  919. package/src/tools/policy-context.ts +25 -8
  920. package/src/tools/registry.ts +55 -3
  921. package/src/tools/shared/shell-output.ts +3 -1
  922. package/src/tools/side-effects.ts +0 -9
  923. package/src/tools/skills/execute.ts +2 -2
  924. package/src/tools/skills/sandbox-runner.ts +6 -2
  925. package/src/tools/terminal/backends/native.ts +51 -2
  926. package/src/tools/terminal/safe-env.ts +11 -2
  927. package/src/tools/terminal/shell.ts +16 -4
  928. package/src/tools/tool-manifest.ts +6 -0
  929. package/src/tools/types.ts +29 -3
  930. package/src/tools/ui-surface/definitions.ts +6 -1
  931. package/src/tools/verification-control-plane-policy.ts +1 -1
  932. package/src/tts/__tests__/provider-adapters.test.ts +1061 -0
  933. package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
  934. package/src/tts/__tests__/provider-catalog.test.ts +183 -0
  935. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  936. package/src/tts/provider-catalog.ts +219 -0
  937. package/src/tts/provider-registry.ts +73 -0
  938. package/src/tts/providers/deepgram-provider.ts +219 -0
  939. package/src/tts/providers/elevenlabs-provider.ts +211 -0
  940. package/src/tts/providers/fish-audio-provider.ts +183 -0
  941. package/src/tts/providers/index.ts +44 -0
  942. package/src/tts/providers/register-builtins.ts +130 -0
  943. package/src/tts/providers/xai-provider.ts +224 -0
  944. package/src/tts/synthesize-text.ts +110 -0
  945. package/src/tts/tts-config-resolver.ts +78 -0
  946. package/src/tts/types.ts +199 -0
  947. package/src/types/onboarding-context.ts +7 -0
  948. package/src/types/tar-stream.d.ts +66 -0
  949. package/src/util/abort-reasons.ts +58 -0
  950. package/src/util/device-id.ts +32 -16
  951. package/src/util/errors.ts +9 -1
  952. package/src/util/json.ts +17 -0
  953. package/src/util/platform.ts +56 -12
  954. package/src/util/pricing.ts +78 -5
  955. package/src/util/spawn.ts +1 -1
  956. package/src/util/truncate.ts +4 -2
  957. package/src/util/unicode.ts +201 -0
  958. package/src/version.ts +19 -24
  959. package/src/watcher/engine.ts +24 -1
  960. package/src/watcher/providers/google-calendar.ts +134 -8
  961. package/src/watcher/providers/outlook-calendar.ts +42 -2
  962. package/src/watcher/watcher-store.ts +31 -0
  963. package/src/workspace/git-service.ts +23 -4
  964. package/src/workspace/migrations/003-seed-device-id.ts +9 -3
  965. package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
  966. package/src/workspace/migrations/029-seed-pkb.ts +1 -1
  967. package/src/workspace/migrations/031-drop-user-md.ts +317 -0
  968. package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
  969. package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
  970. package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
  971. package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
  972. package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
  973. package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
  974. package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
  975. package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
  976. package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
  977. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
  978. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
  979. package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
  980. package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
  981. package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
  982. package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
  983. package/src/workspace/migrations/AGENTS.md +1 -1
  984. package/src/workspace/migrations/registry.ts +32 -0
  985. package/src/workspace/provider-commit-message-generator.ts +19 -38
  986. package/src/workspace/top-level-renderer.ts +13 -1
  987. package/src/workspace/turn-commit.ts +31 -0
  988. package/src/__tests__/email-cli.test.ts +0 -297
  989. package/src/__tests__/email-service-config-fallback.test.ts +0 -102
  990. package/src/__tests__/outlook-attachments.test.ts +0 -301
  991. package/src/__tests__/outlook-automation-tools.test.ts +0 -425
  992. package/src/__tests__/outlook-categories.test.ts +0 -212
  993. package/src/__tests__/outlook-compose-tools.test.ts +0 -325
  994. package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
  995. package/src/__tests__/outlook-follow-up.test.ts +0 -196
  996. package/src/__tests__/outlook-trash.test.ts +0 -77
  997. package/src/__tests__/outlook-unsubscribe.test.ts +0 -250
  998. package/src/__tests__/update-bulletin-format.test.ts +0 -122
  999. package/src/__tests__/update-bulletin-state.test.ts +0 -135
  1000. package/src/__tests__/update-bulletin.test.ts +0 -277
  1001. package/src/__tests__/update-template-contract.test.ts +0 -29
  1002. package/src/cli/commands/browser-relay.ts +0 -466
  1003. package/src/cli/commands/doctor.ts +0 -341
  1004. package/src/config/bundled-skills/browser/SKILL.md +0 -63
  1005. package/src/config/bundled-skills/browser/TOOLS.json +0 -393
  1006. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
  1007. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
  1008. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
  1009. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
  1010. package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
  1011. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
  1012. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
  1013. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
  1014. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
  1015. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
  1016. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
  1017. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
  1018. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -32
  1019. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
  1020. package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
  1021. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
  1022. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
  1023. package/src/config/bundled-skills/gmail/SKILL.md +0 -175
  1024. package/src/config/bundled-skills/gmail/TOOLS.json +0 -558
  1025. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -149
  1026. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
  1027. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
  1028. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
  1029. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
  1030. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
  1031. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
  1032. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -220
  1033. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
  1034. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -251
  1035. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
  1036. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
  1037. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
  1038. package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
  1039. package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
  1040. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  1041. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
  1042. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
  1043. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
  1044. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
  1045. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
  1046. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
  1047. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
  1048. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
  1049. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  1050. package/src/config/bundled-skills/outlook/SKILL.md +0 -196
  1051. package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
  1052. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
  1053. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
  1054. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
  1055. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
  1056. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
  1057. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
  1058. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
  1059. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
  1060. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
  1061. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
  1062. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
  1063. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
  1064. package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
  1065. package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
  1066. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
  1067. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
  1068. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
  1069. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
  1070. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
  1071. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
  1072. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
  1073. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
  1074. package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
  1075. package/src/config/bundled-skills/slack/SKILL.md +0 -107
  1076. package/src/config/bundled-skills/tasks/SKILL.md +0 -37
  1077. package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
  1078. package/src/config/bundled-skills/tasks/icon.svg +0 -34
  1079. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
  1080. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
  1081. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
  1082. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
  1083. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
  1084. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
  1085. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
  1086. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
  1087. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
  1088. package/src/config/bundled-skills/watcher/SKILL.md +0 -31
  1089. package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
  1090. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
  1091. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
  1092. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
  1093. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
  1094. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
  1095. package/src/email/guardrails.ts +0 -221
  1096. package/src/email/provider.ts +0 -117
  1097. package/src/email/providers/agentmail.ts +0 -361
  1098. package/src/email/providers/index.ts +0 -65
  1099. package/src/email/service.ts +0 -384
  1100. package/src/email/types.ts +0 -126
  1101. package/src/prompts/templates/UPDATES.md +0 -38
  1102. package/src/prompts/templates/USER.md +0 -13
  1103. package/src/prompts/update-bulletin-format.ts +0 -68
  1104. package/src/prompts/update-bulletin-state.ts +0 -58
  1105. package/src/prompts/update-bulletin-template-path.ts +0 -13
  1106. package/src/prompts/update-bulletin.ts +0 -128
  1107. package/src/providers/speech-to-text/types.ts +0 -17
  1108. package/src/runtime/routes/browser-cdp-routes.ts +0 -229
  1109. package/src/shared/provider-env-vars.ts +0 -19
  1110. package/src/tools/watcher/create.ts +0 -86
  1111. package/src/tools/watcher/delete.ts +0 -36
  1112. package/src/tools/watcher/digest.ts +0 -54
  1113. package/src/tools/watcher/list.ts +0 -83
  1114. package/src/tools/watcher/update.ts +0 -71
@@ -55,23 +55,34 @@ mock.module("../providers/registry.js", () => ({
55
55
  mock.module("../config/loader.js", () => ({
56
56
  getConfig: () => ({
57
57
  ui: {},
58
-
59
- provider: "mock-provider",
60
- maxTokens: 4096,
61
- thinking: false,
62
- contextWindow: {
63
- maxInputTokens: 100000,
64
- thresholdTokens: 80000,
65
- preserveRecentMessages: 6,
66
- summaryModel: "mock-model",
67
- maxSummaryTokens: 512,
68
- overflowRecovery: {
69
- enabled: true,
70
- safetyMarginRatio: 0.05,
71
- maxAttempts: 3,
72
- interactiveLatestTurnCompression: "summarize",
73
- nonInteractiveLatestTurnCompression: "truncate",
58
+
59
+ llm: {
60
+ default: {
61
+ provider: "mock-provider",
62
+ model: "mock-model",
63
+ maxTokens: 4096,
64
+ effort: "max" as const,
65
+ speed: "standard" as const,
66
+ temperature: null,
67
+ thinking: { enabled: false, streamThinking: true },
68
+ contextWindow: {
69
+ enabled: true,
70
+ maxInputTokens: 100000,
71
+ targetBudgetRatio: 0.3,
72
+ compactThreshold: 0.8,
73
+ summaryBudgetRatio: 0.05,
74
+ overflowRecovery: {
75
+ enabled: true,
76
+ safetyMarginRatio: 0.05,
77
+ maxAttempts: 3,
78
+ interactiveLatestTurnCompression: "summarize",
79
+ nonInteractiveLatestTurnCompression: "truncate",
80
+ },
81
+ },
74
82
  },
83
+ profiles: {},
84
+ callSites: {},
85
+ pricingOverrides: [],
75
86
  },
76
87
  rateLimit: { maxRequestsPerMinute: 0 },
77
88
  timeouts: { permissionTimeoutSec: 1 },
@@ -105,6 +116,20 @@ mock.module("../config/loader.js", () => ({
105
116
 
106
117
  const mockedConversationHostAccess = new Map<string, boolean>();
107
118
 
119
+ const capturedAddMessages: Array<{
120
+ id: string;
121
+ role: string;
122
+ content: string;
123
+ metadata?: Record<string, unknown>;
124
+ }> = [];
125
+
126
+ /**
127
+ * Content substrings that should cause `addMessage` to throw — used to
128
+ * simulate a mid-batch persist failure (e.g. a DB error on a specific
129
+ * tail message while its siblings succeed).
130
+ */
131
+ const addMessageShouldThrowForContent = new Set<string>();
132
+
108
133
  mock.module("../prompts/system-prompt.js", () => ({
109
134
  buildSystemPrompt: () => "system prompt",
110
135
  }));
@@ -133,6 +158,7 @@ mock.module("../security/secret-allowlist.js", () => ({
133
158
  mock.module("../memory/conversation-crud.js", () => ({
134
159
  getConversationType: () => "default",
135
160
  setConversationOriginChannelIfUnset: () => {},
161
+ setConversationOriginInterfaceIfUnset: () => {},
136
162
  updateConversationContextWindow: () => {},
137
163
  getConversationHostAccess: (conversationId: string) =>
138
164
  mockedConversationHostAccess.get(conversationId) ?? false,
@@ -159,11 +185,28 @@ mock.module("../memory/conversation-crud.js", () => ({
159
185
  totalEstimatedCost: 0,
160
186
  }),
161
187
  createConversation: () => ({ id: "conv-1" }),
162
- addMessage: (_convId: string, _role: string, _content: string) => {
163
- return { id: `msg-${Date.now()}` };
188
+ addMessage: (
189
+ _convId: string,
190
+ role: string,
191
+ content: string,
192
+ metadata?: Record<string, unknown>,
193
+ ) => {
194
+ // Simulate a persist failure for tests that need to exercise the
195
+ // tail-persist-failed path in drainBatch. Triggered by matching any
196
+ // registered substring against the serialized content payload.
197
+ for (const needle of addMessageShouldThrowForContent) {
198
+ if (content.includes(needle)) {
199
+ throw new Error(`Simulated addMessage failure for content: ${needle}`);
200
+ }
201
+ }
202
+ const id = `msg-${Date.now()}-${capturedAddMessages.length}`;
203
+ capturedAddMessages.push({ id, role, content, metadata });
204
+ return { id };
164
205
  },
165
206
  updateConversationUsage: () => {},
166
207
  updateConversationTitle: () => {},
208
+ getMessageById: () => null,
209
+ getLastUserTimestampBefore: () => 0,
167
210
  }));
168
211
 
169
212
  mock.module("../memory/conversation-queries.js", () => ({
@@ -298,6 +341,9 @@ mock.module("../agent/loop.js", () => ({
298
341
  getToolTokenBudget() {
299
342
  return 0;
300
343
  }
344
+ getActiveModel() {
345
+ return undefined;
346
+ }
301
347
  async run(
302
348
  messages: Message[],
303
349
  onEvent: (event: AgentEvent) => void,
@@ -456,6 +502,7 @@ beforeEach(() => {
456
502
  turnCommitCalls.length = 0;
457
503
  turnCommitHangForever = false;
458
504
  linkAttachmentShouldThrow = false;
505
+ addMessageShouldThrowForContent.clear();
459
506
  });
460
507
 
461
508
  afterAll(() => {
@@ -521,44 +568,73 @@ describe("Conversation message queue", () => {
521
568
  await new Promise((r) => setTimeout(r, 10));
522
569
  });
523
570
 
524
- test("[experimental] queued messages are processed in FIFO order", async () => {
571
+ test("[experimental] queued passthrough siblings drain as a single batched run", async () => {
525
572
  const conversation = makeConversation();
526
573
  await conversation.loadFromDb();
527
574
 
528
- const processedOrder: string[] = [];
529
-
530
- const makeHandler = (label: string) => (e: ServerMessage) => {
531
- if (e.type === "message_complete") processedOrder.push(label);
532
- };
575
+ const events1: ServerMessage[] = [];
576
+ const events2: ServerMessage[] = [];
577
+ const events3: ServerMessage[] = [];
533
578
 
534
579
  // Start first message
535
580
  const p1 = conversation.processMessage(
536
581
  "msg-1",
537
582
  [],
538
- makeHandler("msg-1"),
583
+ (e) => events1.push(e),
539
584
  "req-1",
540
585
  );
541
586
  await waitForPendingRun(1);
542
587
 
543
- // Enqueue two more
544
- conversation.enqueueMessage("msg-2", [], makeHandler("msg-2"), "req-2");
545
- conversation.enqueueMessage("msg-3", [], makeHandler("msg-3"), "req-3");
588
+ // Enqueue two more sibling passthrough messages
589
+ conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
590
+ conversation.enqueueMessage("msg-3", [], (e) => events3.push(e), "req-3");
546
591
  expect(conversation.getQueueDepth()).toBe(2);
547
592
 
548
- // Complete firsttriggers second
593
+ // Complete run 0 drain pulls msg-2 and msg-3 into ONE batched run.
549
594
  resolveRun(0);
550
595
  await p1;
551
596
  await waitForPendingRun(2);
552
597
 
553
- // Complete second triggers third
554
- resolveRun(1);
555
- await waitForPendingRun(3);
598
+ // Exactly two runs total (not three): run 0 = msg-1, run 1 = batched [msg-2, msg-3]
599
+ expect(pendingRuns.length).toBe(2);
556
600
 
557
- // Complete third
558
- resolveRun(2);
601
+ // Each batched client saw its own message_dequeued tagged with its own requestId.
602
+ const dequeued2 = events2.filter((e) => e.type === "message_dequeued");
603
+ expect(dequeued2).toHaveLength(1);
604
+ expect(dequeued2[0]).toEqual({
605
+ type: "message_dequeued",
606
+ conversationId: "conv-1",
607
+ requestId: "req-2",
608
+ });
609
+ const dequeued3 = events3.filter((e) => e.type === "message_dequeued");
610
+ expect(dequeued3).toHaveLength(1);
611
+ expect(dequeued3[0]).toEqual({
612
+ type: "message_dequeued",
613
+ conversationId: "conv-1",
614
+ requestId: "req-3",
615
+ });
616
+
617
+ // The batched run's captured history carries both siblings. Either as
618
+ // separate user entries (raw history) or merged into one user entry
619
+ // (after history-repair's alternation enforcement — required by the
620
+ // Anthropic API). Either way, both msg-2 and msg-3 text must appear.
621
+ const batchedHistory = pendingRuns[1].messages;
622
+ const userMessages = batchedHistory.filter((m) => m.role === "user");
623
+ const textOf = (m: Message) =>
624
+ (Array.isArray(m.content) ? m.content : [])
625
+ .filter((b) => b.type === "text")
626
+ .map((b) => (b as { text: string }).text)
627
+ .join("\n");
628
+ const combinedUserText = userMessages.map(textOf).join("\n");
629
+ expect(combinedUserText).toContain("msg-2");
630
+ expect(combinedUserText).toContain("msg-3");
631
+
632
+ // Resolve the batched run; message_complete must fan out to both clients.
633
+ resolveRun(1);
559
634
  await new Promise((r) => setTimeout(r, 10));
560
635
 
561
- expect(processedOrder).toEqual(["msg-1", "msg-2", "msg-3"]);
636
+ expect(events2.some((e) => e.type === "message_complete")).toBe(true);
637
+ expect(events3.some((e) => e.type === "message_complete")).toBe(true);
562
638
  });
563
639
 
564
640
  test("message_queued and message_dequeued events are emitted", async () => {
@@ -699,27 +775,17 @@ describe("Conversation message queue", () => {
699
775
  conversation.enqueueMessage("msg-4", [], () => {}, "req-4");
700
776
  expect(conversation.getQueueDepth()).toBe(3);
701
777
 
702
- // Complete first → drains one from queue
778
+ // Complete first → drain pulls all three same-interface passthroughs
779
+ // into a single batched run (depth → 0, runs → 2 total).
703
780
  resolveRun(0);
704
781
  await p1;
705
782
  await waitForPendingRun(2);
706
783
 
707
- expect(conversation.getQueueDepth()).toBe(2);
708
-
709
- // Complete second → drains another
710
- resolveRun(1);
711
- await waitForPendingRun(3);
712
-
713
- expect(conversation.getQueueDepth()).toBe(1);
714
-
715
- // Complete third → drains last
716
- resolveRun(2);
717
- await waitForPendingRun(4);
718
-
719
784
  expect(conversation.getQueueDepth()).toBe(0);
785
+ expect(pendingRuns.length).toBe(2);
720
786
 
721
- // Complete fourth (final queued message)
722
- resolveRun(3);
787
+ // Complete the batched run; conversation finishes cleanly.
788
+ resolveRun(1);
723
789
  await new Promise((r) => setTimeout(r, 10));
724
790
  });
725
791
 
@@ -773,6 +839,764 @@ describe("Conversation message queue", () => {
773
839
  });
774
840
  });
775
841
 
842
+ // ---------------------------------------------------------------------------
843
+ // Batched drain — mixed-interface, slash-in-middle, attachments, byte budget
844
+ // ---------------------------------------------------------------------------
845
+
846
+ describe("Batched drain", () => {
847
+ beforeEach(() => {
848
+ pendingRuns = [];
849
+ });
850
+
851
+ test("mixed-interface queue splits into multiple batches at each interface boundary", async () => {
852
+ const conversation = makeConversation();
853
+ await conversation.loadFromDb();
854
+
855
+ const events2: ServerMessage[] = [];
856
+ const events3: ServerMessage[] = [];
857
+ const events4: ServerMessage[] = [];
858
+ const events5: ServerMessage[] = [];
859
+
860
+ // Start in-flight message (msg-1)
861
+ const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
862
+ await waitForPendingRun(1);
863
+
864
+ // Enqueue 4 messages with interfaces [macos, macos, cli, macos].
865
+ // Expected drain: [macos batch of 2] → [cli single] → [macos single].
866
+ const meta = (iface: string) => ({
867
+ userMessageInterface: iface,
868
+ assistantMessageInterface: iface,
869
+ });
870
+ conversation.enqueueMessage(
871
+ "msg-2",
872
+ [],
873
+ (e) => events2.push(e),
874
+ "req-2",
875
+ undefined,
876
+ undefined,
877
+ meta("macos"),
878
+ );
879
+ conversation.enqueueMessage(
880
+ "msg-3",
881
+ [],
882
+ (e) => events3.push(e),
883
+ "req-3",
884
+ undefined,
885
+ undefined,
886
+ meta("macos"),
887
+ );
888
+ conversation.enqueueMessage(
889
+ "msg-4",
890
+ [],
891
+ (e) => events4.push(e),
892
+ "req-4",
893
+ undefined,
894
+ undefined,
895
+ meta("cli"),
896
+ );
897
+ conversation.enqueueMessage(
898
+ "msg-5",
899
+ [],
900
+ (e) => events5.push(e),
901
+ "req-5",
902
+ undefined,
903
+ undefined,
904
+ meta("macos"),
905
+ );
906
+ expect(conversation.getQueueDepth()).toBe(4);
907
+
908
+ // Resolve msg-1 → batched run pulls macos msg-2 + msg-3.
909
+ resolveRun(0);
910
+ await p1;
911
+ await waitForPendingRun(2);
912
+
913
+ // Batched run's history must contain both macos messages (either as
914
+ // separate user entries or merged into one after history-repair).
915
+ const macosBatchedHistory = pendingRuns[1].messages;
916
+ const macosUserMessages = macosBatchedHistory.filter(
917
+ (m) => m.role === "user",
918
+ );
919
+ const textOf = (m: Message) =>
920
+ (Array.isArray(m.content) ? m.content : [])
921
+ .filter((b) => b.type === "text")
922
+ .map((b) => (b as { text: string }).text)
923
+ .join("\n");
924
+ const combinedMacosText = macosUserMessages.map(textOf).join("\n");
925
+ expect(combinedMacosText).toContain("msg-2");
926
+ expect(combinedMacosText).toContain("msg-3");
927
+
928
+ // Both msg-2 and msg-3 received their own dequeue event.
929
+ expect(events2.filter((e) => e.type === "message_dequeued")).toHaveLength(
930
+ 1,
931
+ );
932
+ expect(events3.filter((e) => e.type === "message_dequeued")).toHaveLength(
933
+ 1,
934
+ );
935
+
936
+ // Resolve the batched run → drain pulls the cli single-message run.
937
+ resolveRun(1);
938
+ await waitForPendingRun(3);
939
+
940
+ // cli run contains msg-4 as a single-message run.
941
+ const cliHistory = pendingRuns[2].messages;
942
+ const cliUserText = cliHistory
943
+ .filter((m) => m.role === "user")
944
+ .map(textOf)
945
+ .join("\n");
946
+ expect(cliUserText).toContain("msg-4");
947
+ expect(events4.filter((e) => e.type === "message_dequeued")).toHaveLength(
948
+ 1,
949
+ );
950
+
951
+ // Resolve the cli run → drain pulls the final macos single-message run.
952
+ resolveRun(2);
953
+ await waitForPendingRun(4);
954
+ const finalHistory = pendingRuns[3].messages;
955
+ const finalUserText = finalHistory
956
+ .filter((m) => m.role === "user")
957
+ .map(textOf)
958
+ .join("\n");
959
+ expect(finalUserText).toContain("msg-5");
960
+ expect(events5.filter((e) => e.type === "message_dequeued")).toHaveLength(
961
+ 1,
962
+ );
963
+
964
+ // Four total runs: msg-1, batched [msg-2, msg-3], msg-4, msg-5.
965
+ expect(pendingRuns.length).toBe(4);
966
+
967
+ resolveRun(3);
968
+ await new Promise((r) => setTimeout(r, 10));
969
+ });
970
+
971
+ test("slash-in-middle splits the queue at the slash boundary", async () => {
972
+ const conversation = makeConversation();
973
+ await conversation.loadFromDb();
974
+
975
+ const eventsHello: ServerMessage[] = [];
976
+ const eventsSlash: ServerMessage[] = [];
977
+ const eventsWorld: ServerMessage[] = [];
978
+
979
+ // Start in-flight message
980
+ const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
981
+ await waitForPendingRun(1);
982
+
983
+ // Enqueue ["hello", "/compact", "world"]. /compact resolves to a non-
984
+ // passthrough slash, so the batch builder stops at "hello" (length 1),
985
+ // then /compact takes the single-message /compact short-circuit path
986
+ // (no new runAgentLoop invocation), then "world" drains as its own run.
987
+ conversation.enqueueMessage(
988
+ "hello",
989
+ [],
990
+ (e) => eventsHello.push(e),
991
+ "req-hello",
992
+ );
993
+ conversation.enqueueMessage(
994
+ "/compact",
995
+ [],
996
+ (e) => eventsSlash.push(e),
997
+ "req-slash",
998
+ );
999
+ conversation.enqueueMessage(
1000
+ "world",
1001
+ [],
1002
+ (e) => eventsWorld.push(e),
1003
+ "req-world",
1004
+ );
1005
+ expect(conversation.getQueueDepth()).toBe(3);
1006
+
1007
+ // Resolve msg-1 → drain pulls "hello" as its own run (batch stops at
1008
+ // /compact boundary).
1009
+ resolveRun(0);
1010
+ await p1;
1011
+ await waitForPendingRun(2);
1012
+
1013
+ expect(pendingRuns.length).toBe(2);
1014
+ expect(eventsHello.some((e) => e.type === "message_dequeued")).toBe(true);
1015
+ // /compact and "world" are still queued.
1016
+ expect(conversation.getQueueDepth()).toBe(2);
1017
+
1018
+ // Resolve "hello" → drain pops /compact via the builder-rejected path,
1019
+ // runs its short-circuit (no new runAgentLoop), then drains "world".
1020
+ resolveRun(1);
1021
+ await waitForPendingRun(3);
1022
+
1023
+ // /compact should have emitted its own message_complete via the short-
1024
+ // circuit path (not via a runAgentLoop run).
1025
+ expect(eventsSlash.some((e) => e.type === "message_complete")).toBe(true);
1026
+ expect(eventsWorld.some((e) => e.type === "message_dequeued")).toBe(true);
1027
+ expect(pendingRuns.length).toBe(3);
1028
+
1029
+ resolveRun(2);
1030
+ await new Promise((r) => setTimeout(r, 10));
1031
+ });
1032
+
1033
+ test("unknown-slash in middle splits the queue at the unknown-slash boundary", async () => {
1034
+ // Covers the `kind: "unknown"` short-circuit branch in drainSingleMessage
1035
+ // specifically. The sibling /compact-in-middle test covers the `kind:
1036
+ // "compact"` short-circuit (via a different code path), so this test
1037
+ // exists to guarantee the batch builder also stops at unknown-kind
1038
+ // boundaries and that the unknown-slash drain path does NOT invoke a new
1039
+ // runAgentLoop run.
1040
+ //
1041
+ // We use `/status`, which the real `resolveSlash` returns as
1042
+ // `{ kind: "unknown", message: <status report> }` when a SlashContext is
1043
+ // present (always true for queued drains via buildSlashContext).
1044
+ const conversation = makeConversation();
1045
+ await conversation.loadFromDb();
1046
+
1047
+ const eventsPlainA: ServerMessage[] = [];
1048
+ const eventsSlash: ServerMessage[] = [];
1049
+ const eventsPlainB: ServerMessage[] = [];
1050
+
1051
+ // Start in-flight message
1052
+ const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
1053
+ await waitForPendingRun(1);
1054
+
1055
+ // Enqueue ["plain-a", "/status", "plain-b"]. /status resolves to a non-
1056
+ // passthrough slash (kind: "unknown"), so the batch builder stops at
1057
+ // "plain-a" (length-1 batch → drainSingleMessage), then /status takes the
1058
+ // unknown-slash short-circuit path (no new runAgentLoop invocation — it
1059
+ // emits assistant_text_delta + message_complete inline), then "plain-b"
1060
+ // drains as its own run.
1061
+ conversation.enqueueMessage(
1062
+ "plain-a",
1063
+ [],
1064
+ (e) => eventsPlainA.push(e),
1065
+ "req-plain-a",
1066
+ );
1067
+ conversation.enqueueMessage(
1068
+ "/status",
1069
+ [],
1070
+ (e) => eventsSlash.push(e),
1071
+ "req-slash",
1072
+ );
1073
+ conversation.enqueueMessage(
1074
+ "plain-b",
1075
+ [],
1076
+ (e) => eventsPlainB.push(e),
1077
+ "req-plain-b",
1078
+ );
1079
+ expect(conversation.getQueueDepth()).toBe(3);
1080
+
1081
+ // Resolve msg-1 → drain pulls "plain-a" as its own run (batch stops at
1082
+ // the /status boundary).
1083
+ resolveRun(0);
1084
+ await p1;
1085
+ await waitForPendingRun(2);
1086
+
1087
+ expect(pendingRuns.length).toBe(2);
1088
+ expect(eventsPlainA.some((e) => e.type === "message_dequeued")).toBe(true);
1089
+ // /status and "plain-b" are still queued.
1090
+ expect(conversation.getQueueDepth()).toBe(2);
1091
+
1092
+ // Resolve "plain-a" → drain pops /status via the builder-rejected path,
1093
+ // runs its unknown-slash short-circuit (no new runAgentLoop, emits
1094
+ // assistant_text_delta + message_complete inline), then drains "plain-b"
1095
+ // as its own run.
1096
+ resolveRun(1);
1097
+ await waitForPendingRun(3);
1098
+
1099
+ // /status should have emitted its own assistant_text_delta + message_complete
1100
+ // via the unknown-slash short-circuit path (not via a runAgentLoop run).
1101
+ expect(eventsSlash.some((e) => e.type === "assistant_text_delta")).toBe(
1102
+ true,
1103
+ );
1104
+ expect(eventsSlash.some((e) => e.type === "message_complete")).toBe(true);
1105
+ expect(eventsPlainB.some((e) => e.type === "message_dequeued")).toBe(true);
1106
+ // Only three runs total: msg-1, "plain-a", "plain-b". /status short-circuits
1107
+ // without a runAgentLoop invocation.
1108
+ expect(pendingRuns.length).toBe(3);
1109
+
1110
+ resolveRun(2);
1111
+ await new Promise((r) => setTimeout(r, 10));
1112
+ });
1113
+
1114
+ test("attachments are preserved across a batched drain", async () => {
1115
+ capturedAddMessages.length = 0;
1116
+ const conversation = makeConversation();
1117
+ await conversation.loadFromDb();
1118
+
1119
+ // Start in-flight message
1120
+ const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
1121
+ await waitForPendingRun(1);
1122
+
1123
+ // Two sibling messages, each with a distinct image attachment.
1124
+ const attachA = [
1125
+ {
1126
+ id: "att-a",
1127
+ filename: "a.png",
1128
+ mimeType: "image/png",
1129
+ data: Buffer.from("imageA").toString("base64"),
1130
+ filePath: "/tmp/a.png",
1131
+ },
1132
+ ];
1133
+ const attachB = [
1134
+ {
1135
+ id: "att-b",
1136
+ filename: "b.png",
1137
+ mimeType: "image/png",
1138
+ data: Buffer.from("imageB").toString("base64"),
1139
+ filePath: "/tmp/b.png",
1140
+ },
1141
+ ];
1142
+ conversation.enqueueMessage("with-A", attachA, () => {}, "req-A");
1143
+ conversation.enqueueMessage("with-B", attachB, () => {}, "req-B");
1144
+ expect(conversation.getQueueDepth()).toBe(2);
1145
+
1146
+ resolveRun(0);
1147
+ await p1;
1148
+ await waitForPendingRun(2);
1149
+
1150
+ // Two persisted user rows in the DB (one per batched message), each with
1151
+ // its own imageSourcePaths metadata keyed by the right filename.
1152
+ const userRows = capturedAddMessages.filter(
1153
+ (m) => m.role === "user" && m.content.includes('"image"'),
1154
+ );
1155
+ expect(userRows).toHaveLength(2);
1156
+ const pathsA = (userRows[0].metadata as Record<string, unknown>)
1157
+ ?.imageSourcePaths as Record<string, string> | undefined;
1158
+ expect(pathsA).toBeDefined();
1159
+ expect(pathsA!["0:a.png"]).toBe("/tmp/a.png");
1160
+ const pathsB = (userRows[1].metadata as Record<string, unknown>)
1161
+ ?.imageSourcePaths as Record<string, string> | undefined;
1162
+ expect(pathsB).toBeDefined();
1163
+ expect(pathsB!["0:b.png"]).toBe("/tmp/b.png");
1164
+
1165
+ // The batched run's in-memory history also reflects both image sources
1166
+ // (enrichMessageWithSourcePaths injects file:// references for images).
1167
+ const batchedHistory = pendingRuns[1].messages;
1168
+ const userMessages = batchedHistory.filter((m) => m.role === "user");
1169
+ const allText = userMessages
1170
+ .map((m) =>
1171
+ (Array.isArray(m.content) ? m.content : [])
1172
+ .filter((b) => b.type === "text")
1173
+ .map((b) => (b as { text: string }).text)
1174
+ .join("\n"),
1175
+ )
1176
+ .join("\n");
1177
+ expect(allText).toContain("a.png");
1178
+ expect(allText).toContain("b.png");
1179
+
1180
+ resolveRun(1);
1181
+ await new Promise((r) => setTimeout(r, 10));
1182
+ });
1183
+
1184
+ test("byte-budget accounting is unchanged by shiftN-based batching", async () => {
1185
+ // Uses a small budget so we can observe reclamation after drain.
1186
+ // Each ~500-char message ≈ 1512 bytes.
1187
+ const conversation = makeConversation();
1188
+ await conversation.loadFromDb();
1189
+
1190
+ const budget = 4000;
1191
+ (conversation as unknown as { queue: MessageQueue }).queue =
1192
+ new MessageQueue(budget);
1193
+
1194
+ // Start in-flight so subsequent enqueues are queued (not processed).
1195
+ const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
1196
+ await waitForPendingRun(1);
1197
+
1198
+ // Fill to just-under budget: two ~500-char messages (1512+1512 = 3024 bytes).
1199
+ const accepted1 = conversation.enqueueMessage(
1200
+ "x".repeat(500),
1201
+ [],
1202
+ () => {},
1203
+ "req-big-1",
1204
+ );
1205
+ const accepted2 = conversation.enqueueMessage(
1206
+ "y".repeat(500),
1207
+ [],
1208
+ () => {},
1209
+ "req-big-2",
1210
+ );
1211
+ expect(accepted1.queued).toBe(true);
1212
+ expect(accepted2.queued).toBe(true);
1213
+ // A third would push the queue over budget → rejected. Capture its
1214
+ // onEvent callback so we can verify the queue_full error event reaches
1215
+ // the rejected caller (not just the synchronous return value).
1216
+ const rejectedEvents: ServerMessage[] = [];
1217
+ const rejected = conversation.enqueueMessage(
1218
+ "z".repeat(500),
1219
+ [],
1220
+ (e) => rejectedEvents.push(e),
1221
+ "req-over",
1222
+ );
1223
+ expect(rejected.queued).toBe(false);
1224
+ expect(rejected.rejected).toBe(true);
1225
+ expect(conversation.getQueueDepth()).toBe(2);
1226
+
1227
+ // The rejected caller must have received a `queue_full` error event on
1228
+ // its own onEvent callback — event emission is part of the public
1229
+ // contract, not just the return value.
1230
+ const queueFullErr = rejectedEvents.find(
1231
+ (e) => e.type === "error" && e.category === "queue_full",
1232
+ );
1233
+ expect(queueFullErr).toBeDefined();
1234
+ if (queueFullErr && queueFullErr.type === "error") {
1235
+ expect(queueFullErr.category).toBe("queue_full");
1236
+ expect(typeof queueFullErr.message).toBe("string");
1237
+ expect(queueFullErr.message.length).toBeGreaterThan(0);
1238
+ }
1239
+
1240
+ // Complete in-flight → drain pulls both queued passthroughs as ONE batched run.
1241
+ resolveRun(0);
1242
+ await p1;
1243
+ await waitForPendingRun(2);
1244
+ expect(conversation.getQueueDepth()).toBe(0);
1245
+
1246
+ // Resolve the batched run.
1247
+ resolveRun(1);
1248
+ await new Promise((r) => setTimeout(r, 10));
1249
+
1250
+ // After the full drain, the byte budget must be fully reclaimed — a fresh
1251
+ // round of enqueues up to the budget should succeed again. Spin up another
1252
+ // in-flight message to reach the queueing state.
1253
+ const p2 = conversation.processMessage("msg-2", [], () => {}, "req-2");
1254
+ await waitForPendingRun(3);
1255
+ expect(
1256
+ conversation.enqueueMessage("a".repeat(500), [], () => {}, "req-a")
1257
+ .queued,
1258
+ ).toBe(true);
1259
+ expect(
1260
+ conversation.enqueueMessage("b".repeat(500), [], () => {}, "req-b")
1261
+ .queued,
1262
+ ).toBe(true);
1263
+
1264
+ resolveRun(2);
1265
+ await p2;
1266
+ await waitForPendingRun(4);
1267
+ resolveRun(3);
1268
+ await new Promise((r) => setTimeout(r, 10));
1269
+ });
1270
+ });
1271
+
1272
+ // ---------------------------------------------------------------------------
1273
+ // Batched drain — correctness fixes (surface exclusion, abort, last-successful
1274
+ // tracking, single activity-state emission)
1275
+ // ---------------------------------------------------------------------------
1276
+
1277
+ describe("Batched drain correctness fixes", () => {
1278
+ beforeEach(() => {
1279
+ pendingRuns = [];
1280
+ capturedAddMessages.length = 0;
1281
+ });
1282
+
1283
+ test("surface-action messages are not batched with regular passthroughs", async () => {
1284
+ const conversation = makeConversation();
1285
+ await conversation.loadFromDb();
1286
+
1287
+ const eventsSurface: ServerMessage[] = [];
1288
+ const eventsRegular: ServerMessage[] = [];
1289
+
1290
+ // Start in-flight message
1291
+ const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
1292
+ await waitForPendingRun(1);
1293
+
1294
+ // Enqueue a surface-action message (activeSurfaceId set + tracked in
1295
+ // surfaceActionRequestIds) followed by a regular passthrough from the
1296
+ // same interface. The batch builder must reject the surface-action head
1297
+ // so each drains as its own run.
1298
+ conversation.surfaceActionRequestIds.add("req-surface");
1299
+ conversation.enqueueMessage(
1300
+ "surface action response",
1301
+ [],
1302
+ (e) => eventsSurface.push(e),
1303
+ "req-surface",
1304
+ "surface-1", // activeSurfaceId
1305
+ );
1306
+ conversation.enqueueMessage(
1307
+ "regular follow-up",
1308
+ [],
1309
+ (e) => eventsRegular.push(e),
1310
+ "req-regular",
1311
+ );
1312
+ expect(conversation.getQueueDepth()).toBe(2);
1313
+
1314
+ // Complete run 0 → drain must NOT batch the surface-action with the
1315
+ // regular passthrough. Expect the surface-action to drain as a single
1316
+ // run first.
1317
+ resolveRun(0);
1318
+ await p1;
1319
+ await waitForPendingRun(2);
1320
+
1321
+ // The second run is the surface-action single-message run.
1322
+ const surfaceUserRowsAfterRun2 = capturedAddMessages.filter(
1323
+ (m) => m.role === "user" && m.content.includes("surface action response"),
1324
+ );
1325
+ expect(surfaceUserRowsAfterRun2).toHaveLength(1);
1326
+ expect(
1327
+ eventsSurface.filter((e) => e.type === "message_dequeued"),
1328
+ ).toHaveLength(1);
1329
+
1330
+ // Complete the surface-action run; drain pulls the regular passthrough
1331
+ // as its own separate run.
1332
+ resolveRun(1);
1333
+ await waitForPendingRun(3);
1334
+ expect(pendingRuns.length).toBe(3);
1335
+ expect(
1336
+ eventsRegular.filter((e) => e.type === "message_dequeued"),
1337
+ ).toHaveLength(1);
1338
+
1339
+ // Total runs = 3: msg-1, surface-action, regular — NOT 2 (would mean
1340
+ // they were batched).
1341
+ resolveRun(2);
1342
+ await new Promise((r) => setTimeout(r, 10));
1343
+ });
1344
+
1345
+ test("abort mid-batch stops tail persists", async () => {
1346
+ const conversation = makeConversation();
1347
+ await conversation.loadFromDb();
1348
+
1349
+ const events1: ServerMessage[] = [];
1350
+ const events2: ServerMessage[] = [];
1351
+ const events3: ServerMessage[] = [];
1352
+ const events4: ServerMessage[] = [];
1353
+
1354
+ // Start in-flight message
1355
+ const p1 = conversation.processMessage(
1356
+ "msg-1",
1357
+ [],
1358
+ (e) => events1.push(e),
1359
+ "req-1",
1360
+ );
1361
+ await waitForPendingRun(1);
1362
+
1363
+ // Enqueue three sibling passthroughs (msg-2 = head, msg-3 = mid,
1364
+ // msg-4 = tail). We trigger abort from msg-3's dequeue callback —
1365
+ // by the time that fires, msg-2 has already been persisted (which
1366
+ // REPLACED the abortController, since persistUserMessage creates a
1367
+ // fresh one). Calling abort() now aborts that fresh controller, and
1368
+ // the drainBatch loop's abort check after msg-3's persist will break,
1369
+ // so msg-4 never persists.
1370
+ conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
1371
+
1372
+ // Install a one-shot abort trigger on msg-3's dequeue event. We do
1373
+ // this before enqueueing so the wrapped callback is what drainBatch
1374
+ // invokes.
1375
+ let aborted = false;
1376
+ const onMsg3Event = (e: ServerMessage) => {
1377
+ events3.push(e);
1378
+ if (!aborted && e.type === "message_dequeued") {
1379
+ aborted = true;
1380
+ conversation.abort();
1381
+ }
1382
+ };
1383
+ conversation.enqueueMessage("msg-3", [], onMsg3Event, "req-3");
1384
+ conversation.enqueueMessage("msg-4", [], (e) => events4.push(e), "req-4");
1385
+ expect(conversation.getQueueDepth()).toBe(3);
1386
+
1387
+ const persistedUserRowCountBefore = capturedAddMessages.filter(
1388
+ (m) => m.role === "user",
1389
+ ).length;
1390
+
1391
+ // Complete run 0 → drain pulls the sibling batch.
1392
+ resolveRun(0);
1393
+ await p1;
1394
+
1395
+ // Give the drain loop a chance to iterate. Abort happens on msg-3's
1396
+ // dequeue (between msg-2's persist and msg-3's persist), so msg-3 may
1397
+ // still persist before the abort check at the end of its iteration.
1398
+ // Either way, msg-4 must NOT persist.
1399
+ await new Promise((r) => setTimeout(r, 30));
1400
+
1401
+ const userRowsAfter = capturedAddMessages
1402
+ .slice(persistedUserRowCountBefore)
1403
+ .filter((m) => m.role === "user");
1404
+ const contents = userRowsAfter.map((r) => r.content).join("||");
1405
+ expect(contents).toContain("msg-2");
1406
+ expect(contents).not.toContain("msg-4");
1407
+ expect(events4.filter((e) => e.type === "message_dequeued")).toHaveLength(
1408
+ 0,
1409
+ );
1410
+ });
1411
+
1412
+ test("failed tail persist uses last-successful requestId", async () => {
1413
+ const conversation = makeConversation();
1414
+ await conversation.loadFromDb();
1415
+
1416
+ const events1: ServerMessage[] = [];
1417
+ const events2: ServerMessage[] = [];
1418
+ const events3: ServerMessage[] = [];
1419
+ const events4: ServerMessage[] = [];
1420
+
1421
+ // Start in-flight message
1422
+ const p1 = conversation.processMessage(
1423
+ "msg-1",
1424
+ [],
1425
+ (e) => events1.push(e),
1426
+ "req-1",
1427
+ );
1428
+ await waitForPendingRun(1);
1429
+
1430
+ // Enqueue three siblings. Configure addMessage to throw for the second
1431
+ // tail (msg-mid) but succeed for msg-head and msg-tail. This simulates
1432
+ // a middle tail persist failure — currentRequestId should end up as
1433
+ // msg-tail's requestId (the LAST successful persist), not msg-mid's.
1434
+ addMessageShouldThrowForContent.add("msg-mid-unique-marker");
1435
+
1436
+ conversation.enqueueMessage(
1437
+ "msg-head",
1438
+ [],
1439
+ (e) => events2.push(e),
1440
+ "req-head",
1441
+ );
1442
+ conversation.enqueueMessage(
1443
+ "msg-mid-unique-marker",
1444
+ [],
1445
+ (e) => events3.push(e),
1446
+ "req-mid",
1447
+ );
1448
+ conversation.enqueueMessage(
1449
+ "msg-tail",
1450
+ [],
1451
+ (e) => events4.push(e),
1452
+ "req-tail",
1453
+ );
1454
+ expect(conversation.getQueueDepth()).toBe(3);
1455
+
1456
+ // Complete run 0 → batched drain.
1457
+ resolveRun(0);
1458
+ await p1;
1459
+ await waitForPendingRun(2);
1460
+
1461
+ // mid should have emitted an error event via persist failure.
1462
+ const errMid = events3.find((e) => e.type === "error");
1463
+ expect(errMid).toBeDefined();
1464
+
1465
+ // The agent loop should have been invoked with the tail's userMessageId
1466
+ // (last SUCCESSFUL persist), not the mid's. We check via currentRequestId
1467
+ // on the conversation which drainBatch assigns after the loop.
1468
+ expect(
1469
+ (conversation as unknown as { currentRequestId?: string })
1470
+ .currentRequestId,
1471
+ ).toBe("req-tail");
1472
+
1473
+ // Cleanup: resolve the batched run.
1474
+ resolveRun(1);
1475
+ await new Promise((r) => setTimeout(r, 20));
1476
+ });
1477
+
1478
+ test("failed tail persist is excluded from fanOutOnEvent agent events", async () => {
1479
+ const conversation = makeConversation();
1480
+ await conversation.loadFromDb();
1481
+
1482
+ const events1: ServerMessage[] = [];
1483
+ const events2: ServerMessage[] = [];
1484
+ const events3: ServerMessage[] = [];
1485
+ const events4: ServerMessage[] = [];
1486
+
1487
+ const p1 = conversation.processMessage(
1488
+ "msg-1",
1489
+ [],
1490
+ (e) => events1.push(e),
1491
+ "req-1",
1492
+ );
1493
+ await waitForPendingRun(1);
1494
+
1495
+ // Mid tail will fail to persist. After the batched run resolves,
1496
+ // message_complete (broadcast via fanOutOnEvent) must NOT land on the
1497
+ // failed mid tail — it already received an error event and persisting
1498
+ // the assistant reply for a user message that has no DB row would
1499
+ // desync the client.
1500
+ addMessageShouldThrowForContent.add("fanout-mid-marker");
1501
+
1502
+ conversation.enqueueMessage(
1503
+ "fanout-head",
1504
+ [],
1505
+ (e) => events2.push(e),
1506
+ "req-fanout-head",
1507
+ );
1508
+ conversation.enqueueMessage(
1509
+ "fanout-mid-marker",
1510
+ [],
1511
+ (e) => events3.push(e),
1512
+ "req-fanout-mid",
1513
+ );
1514
+ conversation.enqueueMessage(
1515
+ "fanout-tail",
1516
+ [],
1517
+ (e) => events4.push(e),
1518
+ "req-fanout-tail",
1519
+ );
1520
+
1521
+ resolveRun(0);
1522
+ await p1;
1523
+ await waitForPendingRun(2);
1524
+
1525
+ // Drive the batched run to emit message_complete via fanOutOnEvent.
1526
+ resolveRun(1);
1527
+ await new Promise((r) => setTimeout(r, 20));
1528
+
1529
+ expect(events3.find((e) => e.type === "error")).toBeDefined();
1530
+ expect(events3.find((e) => e.type === "message_complete")).toBeUndefined();
1531
+
1532
+ expect(events2.find((e) => e.type === "message_complete")).toBeDefined();
1533
+ expect(events4.find((e) => e.type === "message_complete")).toBeDefined();
1534
+ });
1535
+
1536
+ test("drainBatch emits exactly one activity-state event for the whole batch", async () => {
1537
+ const activityStates: ServerMessage[] = [];
1538
+ const conversation = makeConversation((msg) => {
1539
+ if ("type" in msg && msg.type === "assistant_activity_state") {
1540
+ activityStates.push(msg);
1541
+ }
1542
+ });
1543
+ await conversation.loadFromDb();
1544
+
1545
+ // Start in-flight message
1546
+ const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
1547
+ await waitForPendingRun(1);
1548
+
1549
+ // Snapshot the count before drain so we only compare batch-emitted
1550
+ // transitions (msg-1's processMessage already fired one).
1551
+ const baseline = activityStates.length;
1552
+
1553
+ // Enqueue three sibling passthroughs.
1554
+ conversation.enqueueMessage("msg-2", [], () => {}, "req-2");
1555
+ conversation.enqueueMessage("msg-3", [], () => {}, "req-3");
1556
+ conversation.enqueueMessage("msg-4", [], () => {}, "req-4");
1557
+
1558
+ // Complete run 0 → drain pulls the batched siblings as ONE run.
1559
+ resolveRun(0);
1560
+ await p1;
1561
+ await waitForPendingRun(2);
1562
+
1563
+ // Filter for "message_dequeued" reasons emitted by the batched drain.
1564
+ const batchEmissions = activityStates
1565
+ .slice(baseline)
1566
+ .filter(
1567
+ (m) =>
1568
+ "type" in m &&
1569
+ m.type === "assistant_activity_state" &&
1570
+ (m as { reason?: string }).reason === "message_dequeued",
1571
+ );
1572
+ expect(batchEmissions).toHaveLength(1);
1573
+ expect(batchEmissions[0]).toMatchObject({
1574
+ type: "assistant_activity_state",
1575
+ reason: "message_dequeued",
1576
+ requestId: "req-2", // head's requestId, per the fix
1577
+ });
1578
+
1579
+ resolveRun(1);
1580
+ await new Promise((r) => setTimeout(r, 10));
1581
+ });
1582
+
1583
+ // Defensive recovery path: buildPassthroughBatch is designed to make
1584
+ // the invariant throw unreachable in practice, so neither the head
1585
+ // branch (re-dispatch batch.slice(1) to drainBatch/drainSingleMessage/
1586
+ // drainQueue) nor the tail branch (skip + continue) can fire in normal
1587
+ // operation. Left as a todo so the harness contract is documented
1588
+ // without wedging mainline CI. Covering this would require either
1589
+ // (a) reflecting into drainBatch to short-circuit resolveSlash for a
1590
+ // specific batch entry, or (b) exposing a seam on SlashContext — both
1591
+ // are more invasive than the safety-net value justifies.
1592
+ test.todo(
1593
+ "invariant violation in persist loop triggers error event + recovery, not stranded state",
1594
+ async () => {
1595
+ // no-op: see comment above.
1596
+ },
1597
+ );
1598
+ });
1599
+
776
1600
  // ---------------------------------------------------------------------------
777
1601
  // Queue policy primitives
778
1602
  // ---------------------------------------------------------------------------
@@ -962,32 +1786,31 @@ describe("Conversation checkpoint handoff", () => {
962
1786
  await p1;
963
1787
  });
964
1788
 
965
- test("[experimental] FIFO ordering is preserved through checkpoint handoff", async () => {
1789
+ test("[experimental] checkpoint handoff pulls a batched run for all queued siblings", async () => {
966
1790
  const conversation = makeConversation();
967
1791
  await conversation.loadFromDb();
968
1792
 
969
- const processedOrder: string[] = [];
970
-
971
- const makeHandler = (label: string) => (e: ServerMessage) => {
972
- if (e.type === "message_complete" || e.type === "generation_handoff")
973
- processedOrder.push(label);
974
- };
1793
+ const events1: ServerMessage[] = [];
1794
+ const events2: ServerMessage[] = [];
1795
+ const events3: ServerMessage[] = [];
1796
+ const events4: ServerMessage[] = [];
975
1797
 
976
- // Start first message
1798
+ // Start first message (mid-tool-use — will yield at the next checkpoint)
977
1799
  const p1 = conversation.processMessage(
978
1800
  "msg-1",
979
1801
  [],
980
- makeHandler("msg-1"),
1802
+ (e) => events1.push(e),
981
1803
  "req-1",
982
1804
  );
983
1805
  await waitForPendingRun(1);
984
1806
 
985
- // Enqueue two messages
986
- conversation.enqueueMessage("msg-2", [], makeHandler("msg-2"), "req-2");
987
- conversation.enqueueMessage("msg-3", [], makeHandler("msg-3"), "req-3");
988
- expect(conversation.getQueueDepth()).toBe(2);
1807
+ // Enqueue three sibling passthroughs while msg-1 is mid-turn
1808
+ conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
1809
+ conversation.enqueueMessage("msg-3", [], (e) => events3.push(e), "req-3");
1810
+ conversation.enqueueMessage("msg-4", [], (e) => events4.push(e), "req-4");
1811
+ expect(conversation.getQueueDepth()).toBe(3);
989
1812
 
990
- // Simulate the agent loop yielding at the checkpoint (first run)
1813
+ // Simulate the agent loop yielding at the checkpoint (first run is mid-tool-use)
991
1814
  const run0 = pendingRuns[0];
992
1815
  expect(run0.onCheckpoint).toBeDefined();
993
1816
  const decision = run0.onCheckpoint!({
@@ -1002,19 +1825,23 @@ describe("Conversation checkpoint handoff", () => {
1002
1825
  resolveRun(0);
1003
1826
  await p1;
1004
1827
 
1005
- // msg-2 should be draining next
1828
+ // The yielded drain pulls ALL THREE queued siblings as ONE batched run —
1829
+ // not three separate runs.
1006
1830
  await waitForPendingRun(2);
1831
+ expect(pendingRuns.length).toBe(2);
1007
1832
 
1008
- // Complete second run (msg-2)
1009
- resolveRun(1);
1010
- await waitForPendingRun(3);
1833
+ // Each client saw its own message_dequeued tagged with its own requestId.
1834
+ expect(events2.some((e) => e.type === "message_dequeued")).toBe(true);
1835
+ expect(events3.some((e) => e.type === "message_dequeued")).toBe(true);
1836
+ expect(events4.some((e) => e.type === "message_dequeued")).toBe(true);
1011
1837
 
1012
- // Complete third run (msg-3)
1013
- resolveRun(2);
1838
+ // Resolve the batched run — message_complete fans out to all three clients.
1839
+ resolveRun(1);
1014
1840
  await new Promise((r) => setTimeout(r, 10));
1015
1841
 
1016
- // FIFO order: msg-1 completes first, then msg-2, then msg-3
1017
- expect(processedOrder).toEqual(["msg-1", "msg-2", "msg-3"]);
1842
+ expect(events2.some((e) => e.type === "message_complete")).toBe(true);
1843
+ expect(events3.some((e) => e.type === "message_complete")).toBe(true);
1844
+ expect(events4.some((e) => e.type === "message_complete")).toBe(true);
1018
1845
  });
1019
1846
 
1020
1847
  test("[experimental] active run with repeated tool turns + queued message triggers checkpoint handoff", async () => {
@@ -1100,10 +1927,39 @@ describe("Conversation checkpoint handoff", () => {
1100
1927
  );
1101
1928
  await waitForPendingRun(1);
1102
1929
 
1103
- // Enqueue messages B, C, D
1104
- conversation.enqueueMessage("msg-B", [], makeHandler("B"), "req-B");
1105
- conversation.enqueueMessage("msg-C", [], makeHandler("C"), "req-C");
1106
- conversation.enqueueMessage("msg-D", [], makeHandler("D"), "req-D");
1930
+ // Enqueue messages B, C, D — each on a distinct userMessageInterface so the
1931
+ // batch builder stops at each boundary and we see one run per message.
1932
+ const meta = (iface: string) => ({
1933
+ userMessageInterface: iface,
1934
+ assistantMessageInterface: iface,
1935
+ });
1936
+ conversation.enqueueMessage(
1937
+ "msg-B",
1938
+ [],
1939
+ makeHandler("B"),
1940
+ "req-B",
1941
+ undefined,
1942
+ undefined,
1943
+ meta("macos"),
1944
+ );
1945
+ conversation.enqueueMessage(
1946
+ "msg-C",
1947
+ [],
1948
+ makeHandler("C"),
1949
+ "req-C",
1950
+ undefined,
1951
+ undefined,
1952
+ meta("cli"),
1953
+ );
1954
+ conversation.enqueueMessage(
1955
+ "msg-D",
1956
+ [],
1957
+ makeHandler("D"),
1958
+ "req-D",
1959
+ undefined,
1960
+ undefined,
1961
+ meta("vellum"),
1962
+ );
1107
1963
  expect(conversation.getQueueDepth()).toBe(3);
1108
1964
 
1109
1965
  // Handoff from A -> B