@vellumai/assistant 0.6.3 → 0.6.4

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 (667) hide show
  1. package/ARCHITECTURE.md +273 -10
  2. package/Dockerfile +2 -3
  3. package/bun.lock +5 -13
  4. package/docs/backup-troubleshooting.md +52 -0
  5. package/docs/browser-use-architecture-phase2.md +174 -0
  6. package/docs/stt-provider-onboarding.md +120 -0
  7. package/knip.json +12 -2
  8. package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
  9. package/node_modules/@vellumai/ces-contracts/package.json +3 -3
  10. package/openapi.yaml +982 -72
  11. package/package.json +4 -6
  12. package/scripts/generate-openapi.ts +0 -1
  13. package/scripts/test.sh +73 -18
  14. package/src/__tests__/agent-image-optimize.test.ts +28 -0
  15. package/src/__tests__/agent-loop.test.ts +123 -0
  16. package/src/__tests__/anthropic-provider.test.ts +263 -10
  17. package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
  18. package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
  19. package/src/__tests__/browser-fill-credential.test.ts +11 -0
  20. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  21. package/src/__tests__/browser-skill-endstate.test.ts +31 -7
  22. package/src/__tests__/btw-routes.test.ts +7 -0
  23. package/src/__tests__/call-controller.test.ts +581 -20
  24. package/src/__tests__/catalog-files.test.ts +138 -0
  25. package/src/__tests__/channel-invite-transport.test.ts +2 -2
  26. package/src/__tests__/channel-readiness-routes.test.ts +16 -20
  27. package/src/__tests__/channel-readiness-service.test.ts +12 -7
  28. package/src/__tests__/checker.test.ts +157 -10
  29. package/src/__tests__/clawhub-files.test.ts +347 -0
  30. package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
  31. package/src/__tests__/config-analysis.test.ts +100 -0
  32. package/src/__tests__/config-schema.test.ts +1013 -66
  33. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
  34. package/src/__tests__/config-watcher.test.ts +43 -8
  35. package/src/__tests__/contact-store-user-file.test.ts +512 -0
  36. package/src/__tests__/contacts-write.test.ts +197 -0
  37. package/src/__tests__/context-window-manager.test.ts +88 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +98 -2
  41. package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
  42. package/src/__tests__/conversation-error.test.ts +70 -0
  43. package/src/__tests__/conversation-history-web-search.test.ts +11 -4
  44. package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
  45. package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
  46. package/src/__tests__/conversation-list-source.test.ts +145 -0
  47. package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  49. package/src/__tests__/conversation-queue.test.ts +901 -60
  50. package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
  51. package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
  52. package/src/__tests__/conversation-skill-tools.test.ts +7 -4
  53. package/src/__tests__/conversation-slash-commands.test.ts +33 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +89 -18
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  59. package/src/__tests__/credential-health-service.test.ts +352 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  61. package/src/__tests__/credential-vault-unit.test.ts +379 -3
  62. package/src/__tests__/credentials-cli.test.ts +40 -16
  63. package/src/__tests__/cross-provider-web-search.test.ts +146 -35
  64. package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
  65. package/src/__tests__/device-id.test.ts +112 -0
  66. package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
  67. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
  68. package/src/__tests__/email-html-renderer.test.ts +71 -0
  69. package/src/__tests__/email-invite-adapter.test.ts +36 -32
  70. package/src/__tests__/emit-event-signal.test.ts +71 -0
  71. package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
  72. package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
  73. package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
  74. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  75. package/src/__tests__/gemini-provider.test.ts +64 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
  77. package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
  78. package/src/__tests__/gmail-archive-gate.test.ts +246 -0
  79. package/src/__tests__/gmail-preferences.test.ts +117 -0
  80. package/src/__tests__/headless-browser-interactions.test.ts +43 -0
  81. package/src/__tests__/headless-browser-mode.test.ts +614 -0
  82. package/src/__tests__/headless-browser-navigate.test.ts +142 -5
  83. package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
  84. package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
  85. package/src/__tests__/heartbeat-service.test.ts +70 -17
  86. package/src/__tests__/home-state-routes.test.ts +162 -0
  87. package/src/__tests__/host-bash-proxy.test.ts +0 -5
  88. package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
  89. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
  90. package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
  91. package/src/__tests__/host-cu-proxy.test.ts +0 -5
  92. package/src/__tests__/identity-intro-cache.test.ts +40 -10
  93. package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
  94. package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
  95. package/src/__tests__/llm-context-normalization.test.ts +488 -0
  96. package/src/__tests__/llm-context-route-provider.test.ts +86 -5
  97. package/src/__tests__/llm-usage-store.test.ts +363 -0
  98. package/src/__tests__/media-stream-output.test.ts +555 -0
  99. package/src/__tests__/media-stream-parser.test.ts +374 -0
  100. package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
  101. package/src/__tests__/media-stream-stt-session.test.ts +588 -0
  102. package/src/__tests__/media-turn-detector.test.ts +440 -0
  103. package/src/__tests__/message-queue.test.ts +125 -0
  104. package/src/__tests__/migration-export-http.test.ts +6 -6
  105. package/src/__tests__/migration-import-commit-http.test.ts +8 -6
  106. package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
  107. package/src/__tests__/migration-validate-http.test.ts +3 -3
  108. package/src/__tests__/mock-gateway-ipc.ts +151 -0
  109. package/src/__tests__/model-intents.test.ts +2 -2
  110. package/src/__tests__/oauth-apps-routes.test.ts +1 -0
  111. package/src/__tests__/oauth-cli.test.ts +2 -0
  112. package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
  113. package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
  114. package/src/__tests__/oauth-providers-routes.test.ts +2 -0
  115. package/src/__tests__/oauth-store.test.ts +85 -0
  116. package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
  117. package/src/__tests__/onboarding-template-contract.test.ts +6 -13
  118. package/src/__tests__/openai-provider.test.ts +176 -0
  119. package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
  120. package/src/__tests__/openai-responses-provider.test.ts +1105 -0
  121. package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
  122. package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
  123. package/src/__tests__/persona-resolver.test.ts +251 -0
  124. package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
  125. package/src/__tests__/platform.test.ts +92 -1
  126. package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
  127. package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
  128. package/src/__tests__/pricing.test.ts +174 -0
  129. package/src/__tests__/qdrant-manager.test.ts +29 -8
  130. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
  131. package/src/__tests__/relationship-state-contract.test.ts +175 -0
  132. package/src/__tests__/relay-server.test.ts +423 -5
  133. package/src/__tests__/search-skills-unified.test.ts +118 -0
  134. package/src/__tests__/secret-scanner-executor.test.ts +4 -0
  135. package/src/__tests__/secure-keys.test.ts +107 -0
  136. package/src/__tests__/send-endpoint-busy.test.ts +5 -1
  137. package/src/__tests__/sequence-store.test.ts +1 -1
  138. package/src/__tests__/server-history-render.test.ts +49 -0
  139. package/src/__tests__/settings-routes.test.ts +201 -0
  140. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  141. package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
  142. package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
  143. package/src/__tests__/skills.test.ts +5 -2
  144. package/src/__tests__/skillssh-files.test.ts +446 -0
  145. package/src/__tests__/slack-block-formatting.test.ts +110 -0
  146. package/src/__tests__/slack-channel-config.test.ts +564 -1
  147. package/src/__tests__/stt-catalog-parity.test.ts +282 -0
  148. package/src/__tests__/stt-stream-session.test.ts +535 -0
  149. package/src/__tests__/system-prompt.test.ts +112 -26
  150. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  151. package/src/__tests__/terminal-tools.test.ts +18 -7
  152. package/src/__tests__/test-preload.ts +18 -0
  153. package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
  154. package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
  155. package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
  156. package/src/__tests__/tool-executor.test.ts +33 -24
  157. package/src/__tests__/tool-result-truncation.test.ts +36 -0
  158. package/src/__tests__/trust-store.test.ts +7 -1
  159. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
  160. package/src/__tests__/tts-catalog-parity.test.ts +345 -0
  161. package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
  162. package/src/__tests__/twilio-routes.test.ts +376 -0
  163. package/src/__tests__/unicode.test.ts +293 -0
  164. package/src/__tests__/update-bulletin-format.test.ts +59 -0
  165. package/src/__tests__/update-bulletin.test.ts +206 -5
  166. package/src/__tests__/usage-routes.test.ts +25 -4
  167. package/src/__tests__/user-reference.test.ts +46 -61
  168. package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
  169. package/src/__tests__/voice-config-update.test.ts +403 -0
  170. package/src/__tests__/voice-quality.test.ts +434 -19
  171. package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
  172. package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
  173. package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
  174. package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
  175. package/src/__tests__/workspace-migration-meets.test.ts +244 -0
  176. package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
  177. package/src/__tests__/workspace-policy.test.ts +2 -0
  178. package/src/agent/image-optimize.ts +24 -12
  179. package/src/agent/loop.ts +43 -3
  180. package/src/backup/__tests__/backup-key.test.ts +152 -0
  181. package/src/backup/__tests__/backup-worker.test.ts +767 -0
  182. package/src/backup/__tests__/list-snapshots.test.ts +87 -0
  183. package/src/backup/__tests__/local-writer.test.ts +218 -0
  184. package/src/backup/__tests__/offsite-writer.test.ts +641 -0
  185. package/src/backup/__tests__/paths.test.ts +300 -0
  186. package/src/backup/__tests__/restore.test.ts +498 -0
  187. package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
  188. package/src/backup/__tests__/stream-crypt.test.ts +228 -0
  189. package/src/backup/backup-key.ts +137 -0
  190. package/src/backup/backup-worker.ts +459 -0
  191. package/src/backup/list-snapshots.ts +147 -0
  192. package/src/backup/local-writer.ts +133 -0
  193. package/src/backup/offsite-writer.ts +222 -0
  194. package/src/backup/paths.ts +226 -0
  195. package/src/backup/restore.ts +322 -0
  196. package/src/backup/snapshot-lock.ts +431 -0
  197. package/src/backup/stream-crypt.ts +263 -0
  198. package/src/bundler/package-resolver.ts +4 -0
  199. package/src/calls/audio-store.ts +11 -5
  200. package/src/calls/call-controller.ts +226 -71
  201. package/src/calls/call-domain.ts +9 -0
  202. package/src/calls/call-speech-output.ts +190 -0
  203. package/src/calls/call-transport.ts +77 -0
  204. package/src/calls/media-stream-audio-transcode.ts +173 -0
  205. package/src/calls/media-stream-output.ts +660 -0
  206. package/src/calls/media-stream-parser.ts +300 -0
  207. package/src/calls/media-stream-protocol.ts +166 -0
  208. package/src/calls/media-stream-server.ts +592 -0
  209. package/src/calls/media-stream-stt-session.ts +460 -0
  210. package/src/calls/media-turn-detector.ts +230 -0
  211. package/src/calls/relay-server.ts +90 -75
  212. package/src/calls/resolve-call-tts-provider.ts +136 -0
  213. package/src/calls/telephony-stt-routing.ts +145 -0
  214. package/src/calls/tts-call-strategy.ts +161 -0
  215. package/src/calls/tts-text-sanitizer.ts +32 -16
  216. package/src/calls/twilio-routes.ts +281 -17
  217. package/src/calls/voice-quality.ts +78 -35
  218. package/src/calls/voice-session-bridge.ts +8 -1
  219. package/src/channels/types.ts +16 -0
  220. package/src/cli/__tests__/run-assistant-command.ts +11 -1
  221. package/src/cli/commands/__tests__/backup.test.ts +1165 -0
  222. package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
  223. package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
  224. package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
  225. package/src/cli/commands/__tests__/email-download.test.ts +16 -1
  226. package/src/cli/commands/__tests__/email-list.test.ts +22 -4
  227. package/src/cli/commands/__tests__/email-register.test.ts +4 -4
  228. package/src/cli/commands/__tests__/email-send.test.ts +37 -4
  229. package/src/cli/commands/__tests__/email-status.test.ts +5 -1
  230. package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
  231. package/src/cli/commands/backup.ts +993 -0
  232. package/src/cli/commands/conversations.ts +77 -0
  233. package/src/cli/commands/credentials.ts +0 -1
  234. package/src/cli/commands/domain.ts +210 -0
  235. package/src/cli/commands/email.ts +255 -3
  236. package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
  237. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
  238. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
  239. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
  240. package/src/cli/commands/oauth/mode.ts +12 -3
  241. package/src/cli/commands/oauth/providers.ts +15 -0
  242. package/src/cli/commands/oauth/shared.ts +2 -1
  243. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
  244. package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
  245. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  246. package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
  247. package/src/cli/program.ts +30 -4
  248. package/src/config/__tests__/backup-schema.test.ts +134 -0
  249. package/src/config/assistant-feature-flags.ts +61 -62
  250. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
  251. package/src/config/bundled-skills/browser/SKILL.md +30 -5
  252. package/src/config/bundled-skills/browser/TOOLS.json +123 -0
  253. package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
  254. package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
  255. package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
  256. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +2 -2
  258. package/src/config/bundled-skills/gmail/SKILL.md +53 -7
  259. package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
  260. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
  261. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
  262. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
  263. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
  264. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
  265. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
  266. package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
  267. package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
  268. package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
  269. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
  270. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
  271. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
  272. package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
  273. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
  274. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  275. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  276. package/src/config/bundled-skills/outlook/SKILL.md +2 -2
  277. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
  278. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  279. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
  280. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  281. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  282. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
  283. package/src/config/bundled-skills/slack/SKILL.md +1 -0
  284. package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
  285. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
  286. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
  287. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
  288. package/src/config/bundled-tool-registry.ts +8 -0
  289. package/src/config/env-registry.ts +24 -0
  290. package/src/config/env.ts +34 -10
  291. package/src/config/feature-flag-registry.json +46 -14
  292. package/src/config/loader.ts +26 -12
  293. package/src/config/schema.ts +35 -10
  294. package/src/config/schemas/__tests__/stt.test.ts +43 -0
  295. package/src/config/schemas/analysis.ts +51 -0
  296. package/src/config/schemas/backup.ts +72 -0
  297. package/src/config/schemas/calls.ts +1 -26
  298. package/src/config/schemas/elevenlabs.ts +0 -59
  299. package/src/config/schemas/filing.ts +47 -7
  300. package/src/config/schemas/heartbeat.ts +27 -5
  301. package/src/config/schemas/host-browser.ts +47 -1
  302. package/src/config/schemas/inference.ts +1 -1
  303. package/src/config/schemas/memory-lifecycle.ts +14 -2
  304. package/src/config/schemas/services.ts +44 -0
  305. package/src/config/schemas/stt.ts +59 -0
  306. package/src/config/schemas/tts.ts +230 -0
  307. package/src/config/schemas/updates.ts +14 -0
  308. package/src/config/skills.ts +4 -0
  309. package/src/config/types.ts +4 -0
  310. package/src/contacts/contact-store.ts +56 -11
  311. package/src/contacts/contacts-write.ts +38 -1
  312. package/src/context/post-turn-tool-result-truncation.ts +3 -2
  313. package/src/context/tool-result-truncation.ts +2 -1
  314. package/src/context/window-manager.ts +45 -12
  315. package/src/credential-execution/executable-discovery.ts +12 -2
  316. package/src/credential-execution/process-manager.ts +33 -2
  317. package/src/credential-health/credential-health-service.ts +366 -0
  318. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
  319. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
  320. package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
  321. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
  322. package/src/daemon/config-watcher.ts +99 -5
  323. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  324. package/src/daemon/conversation-agent-loop.ts +101 -24
  325. package/src/daemon/conversation-error.ts +11 -0
  326. package/src/daemon/conversation-history.ts +40 -6
  327. package/src/daemon/conversation-launch.ts +220 -0
  328. package/src/daemon/conversation-lifecycle.ts +59 -9
  329. package/src/daemon/conversation-messaging.ts +37 -3
  330. package/src/daemon/conversation-notifiers.ts +5 -0
  331. package/src/daemon/conversation-process.ts +581 -19
  332. package/src/daemon/conversation-queue-manager.ts +24 -0
  333. package/src/daemon/conversation-runtime-assembly.ts +11 -1
  334. package/src/daemon/conversation-slash.ts +36 -0
  335. package/src/daemon/conversation-surfaces.ts +94 -4
  336. package/src/daemon/conversation-tool-setup.ts +25 -0
  337. package/src/daemon/conversation-usage.ts +7 -4
  338. package/src/daemon/conversation.ts +86 -28
  339. package/src/daemon/handlers/config-slack-channel.ts +269 -94
  340. package/src/daemon/handlers/conversations.ts +4 -1
  341. package/src/daemon/handlers/shared.ts +22 -0
  342. package/src/daemon/handlers/skills.ts +321 -77
  343. package/src/daemon/host-browser-proxy.ts +2 -1
  344. package/src/daemon/lifecycle.ts +122 -25
  345. package/src/daemon/message-protocol.ts +6 -0
  346. package/src/daemon/message-types/conversations.ts +34 -1
  347. package/src/daemon/message-types/home.ts +40 -0
  348. package/src/daemon/message-types/meet.ts +143 -0
  349. package/src/daemon/message-types/messages.ts +14 -0
  350. package/src/daemon/message-types/schedules.ts +34 -2
  351. package/src/daemon/message-types/skills.ts +16 -0
  352. package/src/daemon/message-types/surfaces.ts +2 -0
  353. package/src/daemon/server.ts +347 -2
  354. package/src/daemon/shutdown-handlers.ts +32 -4
  355. package/src/daemon/shutdown-registry.ts +40 -0
  356. package/src/daemon/tool-side-effects.ts +9 -0
  357. package/src/email/html-renderer.ts +76 -0
  358. package/src/heartbeat/heartbeat-service.ts +93 -7
  359. package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
  360. package/src/home/__tests__/emit-feed-event.test.ts +169 -0
  361. package/src/home/__tests__/feed-scheduler.test.ts +194 -0
  362. package/src/home/__tests__/feed-types.test.ts +275 -0
  363. package/src/home/__tests__/feed-writer.test.ts +688 -0
  364. package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
  365. package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
  366. package/src/home/__tests__/progress-formula.test.ts +213 -0
  367. package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
  368. package/src/home/__tests__/rollup-producer.test.ts +398 -0
  369. package/src/home/assistant-feed-authoring.ts +124 -0
  370. package/src/home/emit-feed-event.ts +158 -0
  371. package/src/home/feed-scheduler.ts +247 -0
  372. package/src/home/feed-types.ts +181 -0
  373. package/src/home/feed-writer.ts +469 -0
  374. package/src/home/platform-gmail-digest.ts +163 -0
  375. package/src/home/progress-formula.ts +86 -0
  376. package/src/home/relationship-state-writer.ts +824 -0
  377. package/src/home/relationship-state.ts +143 -0
  378. package/src/home/rollup-producer.ts +384 -0
  379. package/src/hooks/runner.ts +7 -0
  380. package/src/inbound/platform-callback-registration.ts +12 -3
  381. package/src/inbound/public-ingress-urls.ts +12 -0
  382. package/src/instrument.ts +1 -1
  383. package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
  384. package/src/ipc/cli-client.ts +151 -0
  385. package/src/ipc/cli-server.ts +234 -0
  386. package/src/ipc/gateway-client.ts +180 -0
  387. package/src/ipc/routes/index.ts +5 -0
  388. package/src/ipc/routes/wake-conversation.ts +19 -0
  389. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
  390. package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
  391. package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
  392. package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
  393. package/src/memory/app-store.ts +1 -1
  394. package/src/memory/attachments-store.ts +70 -0
  395. package/src/memory/auto-analysis-enqueue.ts +127 -0
  396. package/src/memory/auto-analysis-guard.ts +27 -0
  397. package/src/memory/cleanup-schedule-state.ts +37 -0
  398. package/src/memory/conversation-analyze-job.ts +73 -0
  399. package/src/memory/conversation-crud.ts +99 -0
  400. package/src/memory/conversation-disk-view.ts +7 -0
  401. package/src/memory/conversation-group-migration.ts +34 -2
  402. package/src/memory/conversation-queries.ts +6 -5
  403. package/src/memory/db-init.ts +6 -0
  404. package/src/memory/db-maintenance.ts +108 -0
  405. package/src/memory/db.ts +1 -0
  406. package/src/memory/graph/conversation-graph-memory.ts +15 -0
  407. package/src/memory/graph/extraction.test.ts +23 -0
  408. package/src/memory/graph/extraction.ts +8 -0
  409. package/src/memory/graph/retriever.ts +27 -18
  410. package/src/memory/graph/scoring.test.ts +186 -0
  411. package/src/memory/graph/scoring.ts +31 -1
  412. package/src/memory/graph/tools.ts +1 -1
  413. package/src/memory/group-crud.ts +6 -1
  414. package/src/memory/indexer.ts +95 -16
  415. package/src/memory/job-handlers/cleanup.ts +11 -8
  416. package/src/memory/job-handlers/conversation-starters.ts +16 -10
  417. package/src/memory/jobs-store.ts +64 -4
  418. package/src/memory/jobs-worker.ts +22 -9
  419. package/src/memory/llm-usage-store.ts +92 -56
  420. package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
  421. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
  422. package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
  423. package/src/memory/migrations/index.ts +6 -0
  424. package/src/memory/migrations/registry.ts +8 -0
  425. package/src/memory/qdrant-manager.ts +43 -16
  426. package/src/memory/schema/conversations.ts +2 -0
  427. package/src/memory/schema/oauth.ts +3 -0
  428. package/src/memory/usage-buckets.ts +396 -0
  429. package/src/messaging/providers/gmail/client.ts +57 -6
  430. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
  431. package/src/messaging/providers/slack/adapter.ts +143 -38
  432. package/src/messaging/providers/slack/client.ts +16 -0
  433. package/src/messaging/providers/slack/types.ts +4 -0
  434. package/src/notifications/decision-engine.ts +3 -3
  435. package/src/notifications/signal.ts +5 -0
  436. package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
  437. package/src/oauth/byo-connection.test.ts +18 -1
  438. package/src/oauth/byo-connection.ts +3 -1
  439. package/src/oauth/connect-orchestrator.ts +2 -0
  440. package/src/oauth/connection-resolver.ts +6 -2
  441. package/src/oauth/connection.ts +2 -0
  442. package/src/oauth/oauth-store.ts +9 -0
  443. package/src/oauth/platform-connection.test.ts +98 -0
  444. package/src/oauth/platform-connection.ts +52 -31
  445. package/src/oauth/seed-providers.ts +7 -0
  446. package/src/permissions/checker.ts +16 -6
  447. package/src/permissions/defaults.ts +49 -1
  448. package/src/permissions/trust-store.ts +3 -3
  449. package/src/permissions/workspace-policy.ts +3 -0
  450. package/src/platform/client.test.ts +10 -0
  451. package/src/platform/sync-identity.ts +129 -0
  452. package/src/prompts/persona-resolver.ts +126 -2
  453. package/src/prompts/system-prompt.ts +59 -18
  454. package/src/prompts/templates/BOOTSTRAP.md +5 -5
  455. package/src/prompts/templates/SOUL.md +3 -1
  456. package/src/prompts/templates/UPDATES.md +12 -0
  457. package/src/prompts/templates/channels/slack.md +20 -0
  458. package/src/prompts/update-bulletin-format.ts +26 -9
  459. package/src/prompts/update-bulletin.ts +34 -23
  460. package/src/prompts/user-reference.ts +20 -17
  461. package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
  462. package/src/providers/anthropic/client.ts +157 -61
  463. package/src/providers/fireworks/client.ts +2 -2
  464. package/src/providers/gemini/client.ts +9 -1
  465. package/src/providers/model-catalog.ts +6 -0
  466. package/src/providers/model-intents.ts +4 -4
  467. package/src/providers/ollama/client.ts +2 -2
  468. package/src/providers/openai/chat-completions-provider.ts +474 -0
  469. package/src/providers/openai/client.ts +25 -440
  470. package/src/providers/openai/responses-provider.ts +502 -0
  471. package/src/providers/openrouter/client.ts +101 -4
  472. package/src/providers/provider-secret-catalog.ts +139 -0
  473. package/src/providers/registry.ts +2 -2
  474. package/src/providers/retry.ts +14 -3
  475. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
  476. package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
  477. package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
  478. package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
  479. package/src/providers/speech-to-text/deepgram.test.ts +332 -0
  480. package/src/providers/speech-to-text/deepgram.ts +115 -0
  481. package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
  482. package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
  483. package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
  484. package/src/providers/speech-to-text/google-gemini.ts +101 -0
  485. package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
  486. package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
  487. package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
  488. package/src/providers/speech-to-text/openai-whisper.ts +63 -33
  489. package/src/providers/speech-to-text/provider-catalog.ts +306 -0
  490. package/src/providers/speech-to-text/resolve.ts +386 -6
  491. package/src/providers/types.ts +9 -0
  492. package/src/runtime/AGENTS.md +43 -1
  493. package/src/runtime/__tests__/agent-wake.test.ts +831 -0
  494. package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
  495. package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
  496. package/src/runtime/agent-wake.ts +512 -0
  497. package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
  498. package/src/runtime/auth/route-policy.ts +30 -5
  499. package/src/runtime/auth/token-service.ts +56 -1
  500. package/src/runtime/btw-sidechain.ts +2 -0
  501. package/src/runtime/capability-tokens.ts +10 -10
  502. package/src/runtime/channel-invite-transport.ts +1 -1
  503. package/src/runtime/channel-invite-transports/email.ts +14 -6
  504. package/src/runtime/channel-readiness-service.ts +12 -22
  505. package/src/runtime/chrome-extension-registry.ts +38 -2
  506. package/src/runtime/http-server.ts +395 -10
  507. package/src/runtime/http-types.ts +6 -2
  508. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
  509. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
  510. package/src/runtime/migrations/migration-transport.ts +1 -0
  511. package/src/runtime/migrations/migration-wizard.ts +1 -0
  512. package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
  513. package/src/runtime/migrations/vbundle-importer.ts +34 -0
  514. package/src/runtime/pending-interactions.ts +0 -11
  515. package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
  516. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
  517. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
  518. package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
  519. package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
  520. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
  521. package/src/runtime/routes/app-management-routes.ts +12 -18
  522. package/src/runtime/routes/attachment-routes.test.ts +9 -3
  523. package/src/runtime/routes/attachment-routes.ts +216 -17
  524. package/src/runtime/routes/backup-routes.ts +519 -0
  525. package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
  526. package/src/runtime/routes/btw-routes.ts +8 -6
  527. package/src/runtime/routes/contact-routes.test.ts +298 -0
  528. package/src/runtime/routes/contact-routes.ts +132 -5
  529. package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
  530. package/src/runtime/routes/conversation-management-routes.ts +115 -0
  531. package/src/runtime/routes/conversation-routes.ts +367 -146
  532. package/src/runtime/routes/filing-routes.ts +93 -0
  533. package/src/runtime/routes/home-feed-routes.ts +334 -0
  534. package/src/runtime/routes/home-state-routes.ts +138 -0
  535. package/src/runtime/routes/host-browser-routes.ts +3 -14
  536. package/src/runtime/routes/identity-intro-cache.ts +7 -3
  537. package/src/runtime/routes/identity-routes.ts +3 -17
  538. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
  539. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
  540. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
  541. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
  542. package/src/runtime/routes/integrations/slack/channel.ts +11 -3
  543. package/src/runtime/routes/integrations/slack/share.ts +45 -7
  544. package/src/runtime/routes/llm-context-normalization.ts +303 -0
  545. package/src/runtime/routes/memory-item-routes.test.ts +3 -2
  546. package/src/runtime/routes/migration-routes.ts +40 -5
  547. package/src/runtime/routes/settings-routes.ts +22 -5
  548. package/src/runtime/routes/skills-routes.ts +76 -7
  549. package/src/runtime/routes/stt-routes.ts +233 -0
  550. package/src/runtime/routes/surface-action-routes.ts +41 -2
  551. package/src/runtime/routes/tts-routes.ts +108 -24
  552. package/src/runtime/routes/usage-routes.ts +30 -2
  553. package/src/runtime/routes/user-route-dispatcher.ts +50 -5
  554. package/src/runtime/routes/user-routes.ts +13 -1
  555. package/src/runtime/routes/work-items-routes.ts +8 -1
  556. package/src/runtime/runtime-mode.ts +33 -0
  557. package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
  558. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
  559. package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
  560. package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
  561. package/src/runtime/services/analyze-conversation.ts +344 -0
  562. package/src/runtime/services/analyze-deps-singleton.ts +32 -0
  563. package/src/runtime/services/auto-analysis-prompt.ts +55 -0
  564. package/src/runtime/skill-route-registry.ts +49 -0
  565. package/src/runtime/slack-block-formatting.ts +437 -10
  566. package/src/schedule/scheduler.ts +50 -0
  567. package/src/security/oauth2.ts +26 -4
  568. package/src/security/secure-keys.ts +25 -2
  569. package/src/security/token-manager.ts +8 -0
  570. package/src/sequence/engine.ts +23 -0
  571. package/src/sequence/types.ts +1 -1
  572. package/src/skills/catalog-files.ts +64 -2
  573. package/src/skills/category-inference.ts +122 -0
  574. package/src/skills/clawhub-files.ts +213 -0
  575. package/src/skills/clawhub.ts +84 -23
  576. package/src/skills/skill-file-provider.ts +40 -0
  577. package/src/skills/skillssh-files.ts +395 -0
  578. package/src/skills/skillssh-registry.ts +4 -4
  579. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
  580. package/src/stt/__tests__/types.test.ts +89 -0
  581. package/src/stt/daemon-batch-transcriber.ts +195 -0
  582. package/src/stt/stt-stream-session.ts +499 -0
  583. package/src/stt/types.ts +330 -0
  584. package/src/stt/wav-encoder.test.ts +373 -0
  585. package/src/stt/wav-encoder.ts +175 -0
  586. package/src/subagent/manager.ts +38 -14
  587. package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
  588. package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
  589. package/src/tools/browser/browser-execution.ts +1163 -23
  590. package/src/tools/browser/browser-manager.ts +45 -0
  591. package/src/tools/browser/browser-mode-constants.ts +12 -0
  592. package/src/tools/browser/browser-mode.ts +92 -0
  593. package/src/tools/browser/browser-status-constants.ts +33 -0
  594. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
  595. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
  596. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
  597. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
  598. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
  599. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
  600. package/src/tools/browser/cdp-client/errors.ts +15 -0
  601. package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
  602. package/src/tools/browser/cdp-client/factory.ts +797 -87
  603. package/src/tools/browser/cdp-client/index.ts +16 -2
  604. package/src/tools/browser/cdp-client/types.ts +68 -0
  605. package/src/tools/credentials/vault.ts +35 -6
  606. package/src/tools/network/web-fetch.ts +5 -2
  607. package/src/tools/network/web-search.ts +5 -2
  608. package/src/tools/shared/shell-output.ts +3 -1
  609. package/src/tools/side-effects.ts +2 -0
  610. package/src/tools/skills/sandbox-runner.ts +3 -2
  611. package/src/tools/terminal/safe-env.ts +10 -2
  612. package/src/tools/terminal/shell.ts +15 -4
  613. package/src/tools/tool-manifest.ts +21 -0
  614. package/src/tools/types.ts +17 -0
  615. package/src/tools/ui-surface/definitions.ts +6 -1
  616. package/src/tts/__tests__/provider-adapters.test.ts +834 -0
  617. package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
  618. package/src/tts/__tests__/provider-catalog.test.ts +183 -0
  619. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  620. package/src/tts/provider-catalog.ts +201 -0
  621. package/src/tts/provider-registry.ts +73 -0
  622. package/src/tts/providers/deepgram-provider.ts +219 -0
  623. package/src/tts/providers/elevenlabs-provider.ts +211 -0
  624. package/src/tts/providers/fish-audio-provider.ts +183 -0
  625. package/src/tts/providers/index.ts +42 -0
  626. package/src/tts/providers/register-builtins.ts +130 -0
  627. package/src/tts/synthesize-text.ts +110 -0
  628. package/src/tts/tts-config-resolver.ts +78 -0
  629. package/src/tts/types.ts +153 -0
  630. package/src/types/onboarding-context.ts +7 -0
  631. package/src/util/abort-reasons.ts +58 -0
  632. package/src/util/device-id.ts +32 -16
  633. package/src/util/errors.ts +9 -1
  634. package/src/util/platform.ts +54 -10
  635. package/src/util/pricing.ts +66 -3
  636. package/src/util/spawn.ts +1 -1
  637. package/src/util/truncate.ts +4 -2
  638. package/src/util/unicode.ts +201 -0
  639. package/src/version.ts +19 -24
  640. package/src/watcher/engine.ts +23 -0
  641. package/src/watcher/watcher-store.ts +31 -0
  642. package/src/workspace/migrations/003-seed-device-id.ts +9 -3
  643. package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
  644. package/src/workspace/migrations/029-seed-pkb.ts +1 -1
  645. package/src/workspace/migrations/031-drop-user-md.ts +317 -0
  646. package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
  647. package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
  648. package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
  649. package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
  650. package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
  651. package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
  652. package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
  653. package/src/workspace/migrations/registry.ts +16 -0
  654. package/src/workspace/top-level-renderer.ts +13 -1
  655. package/src/workspace/turn-commit.ts +31 -0
  656. package/src/__tests__/email-cli.test.ts +0 -297
  657. package/src/__tests__/email-service-config-fallback.test.ts +0 -102
  658. package/src/cli/commands/browser-relay.ts +0 -466
  659. package/src/email/guardrails.ts +0 -221
  660. package/src/email/provider.ts +0 -117
  661. package/src/email/providers/agentmail.ts +0 -361
  662. package/src/email/providers/index.ts +0 -65
  663. package/src/email/service.ts +0 -384
  664. package/src/email/types.ts +0 -126
  665. package/src/prompts/templates/USER.md +0 -13
  666. package/src/providers/speech-to-text/types.ts +0 -17
  667. package/src/runtime/routes/browser-cdp-routes.ts +0 -229
@@ -21,6 +21,12 @@ import {
21
21
  import { getDb, initializeDb } from "../memory/db.js";
22
22
  import { AssistantEventHub } from "../runtime/assistant-event-hub.js";
23
23
  import type { AuthContext } from "../runtime/auth/types.js";
24
+ import {
25
+ __resetChromeExtensionRegistryForTests,
26
+ type ChromeExtensionConnection,
27
+ getChromeExtensionRegistry,
28
+ } from "../runtime/chrome-extension-registry.js";
29
+ import * as pendingInteractions from "../runtime/pending-interactions.js";
24
30
  import { handleSendMessage } from "../runtime/routes/conversation-routes.js";
25
31
 
26
32
  const testDir = process.env.VELLUM_WORKSPACE_DIR!;
@@ -329,6 +335,270 @@ beforeEach(() => {
329
335
  resetTables();
330
336
  resetConversationsDir();
331
337
  conversationInstances.clear();
338
+ pendingInteractions.clear();
339
+ __resetChromeExtensionRegistryForTests();
340
+ });
341
+
342
+ // ── macOS browser backend fallback regression ─────────────────────────
343
+ //
344
+ // These tests verify the route-level wiring that enables the CDP factory's
345
+ // fallback chain on macOS-originated turns:
346
+ //
347
+ // 1. Extension connected → extension backend (tested in host-browser-e2e-cloud.test.ts)
348
+ // 2. Extension absent, cdp-inspect unavailable → local Playwright fallback
349
+ //
350
+ // Specifically, we verify that when a macOS message enters through
351
+ // handleSendMessage with `interface: "macos"` and NO extension is connected,
352
+ // the conversation's turnInterfaceContext is set correctly so the CDP factory
353
+ // builds the right candidate list (cdp-inspect → local). If cdp-inspect
354
+ // is also unreachable (the common case when Chrome is not launched with
355
+ // --remote-debugging-port), the factory falls through to local.
356
+ //
357
+ // This is the regression guard for backend preference order step 2:
358
+ // macOS + no extension + cdp-inspect unavailable → local backend
359
+ //
360
+ // If the interface propagation or factory candidate list construction
361
+ // regresses, these tests will fail.
362
+
363
+ describe("macOS browser backend fallback (no extension, no cdp-inspect)", () => {
364
+ test("macOS turn without extension sets turnInterfaceContext to macos, enabling local fallback", async () => {
365
+ const conversationKey = `macos-fallback-${crypto.randomUUID()}`;
366
+ const content = "Test macOS fallback path.";
367
+ let capturedConversation: Conversation | undefined;
368
+
369
+ const response = await handleSendMessage(
370
+ new Request("http://localhost/v1/messages", {
371
+ method: "POST",
372
+ headers: { "Content-Type": "application/json" },
373
+ body: JSON.stringify({
374
+ conversationKey,
375
+ content,
376
+ sourceChannel: "vellum",
377
+ interface: "macos",
378
+ }),
379
+ }),
380
+ {
381
+ sendMessageDeps: {
382
+ getOrCreateConversation: async (conversationId: string) => {
383
+ const conv = getOrCreateFakeConversation(conversationId);
384
+ capturedConversation = conv;
385
+ return conv;
386
+ },
387
+ assistantEventHub: new AssistantEventHub(),
388
+ resolveAttachments: () => [],
389
+ },
390
+ },
391
+ authContext,
392
+ );
393
+
394
+ expect(response.status).toBe(202);
395
+
396
+ // The conversation instance should have its turnInterfaceContext set
397
+ // to "macos" by handleSendMessage. This is the value the CDP factory
398
+ // reads (via ToolContext.transportInterface) to decide whether to
399
+ // include cdp-inspect as a desktop-auto candidate and ultimately fall
400
+ // back to local Playwright when cdp-inspect is unavailable.
401
+ expect(capturedConversation).toBeDefined();
402
+ const interfaceCtx = capturedConversation!.getTurnInterfaceContext();
403
+ expect(interfaceCtx).not.toBeNull();
404
+ expect(interfaceCtx!.userMessageInterface).toBe("macos");
405
+ expect(interfaceCtx!.assistantMessageInterface).toBe("macos");
406
+
407
+ // With no extension in the ChromeExtensionRegistry, the conversation's
408
+ // hostBrowserProxy should NOT be provisioned through the registry.
409
+ // The absence of a hostBrowserProxy means the CDP factory will skip
410
+ // the extension candidate entirely and fall through:
411
+ // cdp-inspect (desktop-auto) → local.
412
+ expect(
413
+ (capturedConversation as unknown as Record<string, unknown>)
414
+ .hostBrowserProxy,
415
+ ).toBeUndefined();
416
+ expect(
417
+ (capturedConversation as unknown as Record<string, unknown>)
418
+ .hostBrowserSenderOverride,
419
+ ).toBeUndefined();
420
+ });
421
+
422
+ test("macOS turn correctly persists interface metadata through the agent loop", async () => {
423
+ const conversationKey = `macos-metadata-${crypto.randomUUID()}`;
424
+ const content = "Verify interface metadata persistence.";
425
+
426
+ const response = await handleSendMessage(
427
+ new Request("http://localhost/v1/messages", {
428
+ method: "POST",
429
+ headers: { "Content-Type": "application/json" },
430
+ body: JSON.stringify({
431
+ conversationKey,
432
+ content,
433
+ sourceChannel: "vellum",
434
+ interface: "macos",
435
+ }),
436
+ }),
437
+ {
438
+ sendMessageDeps: {
439
+ getOrCreateConversation: async (conversationId: string) =>
440
+ getOrCreateFakeConversation(conversationId),
441
+ assistantEventHub: new AssistantEventHub(),
442
+ resolveAttachments: () => [],
443
+ },
444
+ },
445
+ authContext,
446
+ );
447
+
448
+ expect(response.status).toBe(202);
449
+ const body = (await response.json()) as {
450
+ accepted: boolean;
451
+ conversationId: string;
452
+ };
453
+
454
+ // Wait for the agent loop to persist the assistant reply (the fake
455
+ // runAgentLoop persists interface metadata into message records).
456
+ const conversationRow = getConversation(body.conversationId);
457
+ expect(conversationRow).not.toBeNull();
458
+ const conversationDir = getConversationDirPath(
459
+ body.conversationId,
460
+ conversationRow!.createdAt,
461
+ );
462
+ const messagesPath = join(conversationDir, "messages.jsonl");
463
+
464
+ const lines = await waitFor(() => {
465
+ if (!existsSync(messagesPath)) return undefined;
466
+ const raw = readFileSync(messagesPath, "utf-8").trim();
467
+ if (!raw) return undefined;
468
+ const parsed = raw.split("\n").map(
469
+ (line) =>
470
+ JSON.parse(line) as {
471
+ role: string;
472
+ metadata?: Record<string, unknown>;
473
+ },
474
+ );
475
+ return parsed.length >= 2 ? parsed : undefined;
476
+ });
477
+
478
+ // The assistant reply (second line) should carry the interface metadata
479
+ // set by setTurnInterfaceContext during the macOS turn setup.
480
+ const assistantLine = lines[1];
481
+ expect(assistantLine?.role).toBe("assistant");
482
+ expect(assistantLine?.metadata?.userMessageInterface).toBe("macos");
483
+ expect(assistantLine?.metadata?.assistantMessageInterface).toBe("macos");
484
+ });
485
+ });
486
+
487
+ describe("chrome-extension host_browser pending registration", () => {
488
+ test("registers pending interaction before registry-routed host_browser send", async () => {
489
+ const guardianId = `guardian-${crypto.randomUUID()}`;
490
+ let pendingRegisteredAtSend: boolean | undefined;
491
+ let observedRequestId: string | undefined;
492
+
493
+ const connection: ChromeExtensionConnection = {
494
+ id: `conn-${crypto.randomUUID()}`,
495
+ guardianId,
496
+ ws: {
497
+ send(payload: string) {
498
+ const frame = JSON.parse(payload) as {
499
+ type?: string;
500
+ requestId?: string;
501
+ };
502
+ if (
503
+ frame.type === "host_browser_request" &&
504
+ typeof frame.requestId === "string"
505
+ ) {
506
+ observedRequestId = frame.requestId;
507
+ pendingRegisteredAtSend =
508
+ pendingInteractions.get(frame.requestId)?.kind === "host_browser";
509
+ }
510
+ },
511
+ close() {},
512
+ } as unknown as ChromeExtensionConnection["ws"],
513
+ connectedAt: Date.now(),
514
+ lastActiveAt: Date.now(),
515
+ };
516
+ getChromeExtensionRegistry().register(connection);
517
+
518
+ const actorAuthContext: AuthContext = {
519
+ ...authContext,
520
+ subject: `actor:self:${guardianId}`,
521
+ principalType: "actor",
522
+ scopeProfile: "actor_client_v1",
523
+ actorPrincipalId: guardianId,
524
+ };
525
+
526
+ const conversationKey = `chrome-ext-pending-${crypto.randomUUID()}`;
527
+ const response = await handleSendMessage(
528
+ new Request("http://localhost/v1/messages", {
529
+ method: "POST",
530
+ headers: { "Content-Type": "application/json" },
531
+ body: JSON.stringify({
532
+ conversationKey,
533
+ content: "Trigger host browser request via registry sender.",
534
+ sourceChannel: "vellum",
535
+ interface: "chrome-extension",
536
+ }),
537
+ }),
538
+ {
539
+ sendMessageDeps: {
540
+ getOrCreateConversation: async (conversationId: string) => {
541
+ const conversation = createFakeConversation(
542
+ conversationId,
543
+ ) as unknown as {
544
+ hostBrowserProxy?: {
545
+ request: (
546
+ input: { cdpMethod: string; timeout_seconds?: number },
547
+ conversationId: string,
548
+ ) => Promise<{ content: string; isError: boolean }>;
549
+ };
550
+ processing: boolean;
551
+ abortController: AbortController | null;
552
+ currentRequestId?: string;
553
+ runAgentLoop: (
554
+ content: string,
555
+ userMessageId: string,
556
+ onEvent: (msg: Record<string, unknown>) => void,
557
+ ) => Promise<void>;
558
+ conversationId: string;
559
+ };
560
+
561
+ conversation.runAgentLoop = async function (
562
+ _content: string,
563
+ _userMessageId: string,
564
+ _onEvent: (msg: Record<string, unknown>) => void,
565
+ ): Promise<void> {
566
+ if (!this.hostBrowserProxy) {
567
+ throw new Error(
568
+ "Expected hostBrowserProxy to be provisioned for chrome-extension turn",
569
+ );
570
+ }
571
+ await this.hostBrowserProxy.request(
572
+ {
573
+ cdpMethod: "Browser.getVersion",
574
+ // Keep the test fast while still allowing assertion at send time.
575
+ timeout_seconds: 0.05,
576
+ },
577
+ this.conversationId,
578
+ );
579
+ this.processing = false;
580
+ this.abortController = null;
581
+ this.currentRequestId = undefined;
582
+ };
583
+
584
+ conversationInstances.set(
585
+ conversationId,
586
+ conversation as unknown as Conversation,
587
+ );
588
+ return conversation as unknown as Conversation;
589
+ },
590
+ assistantEventHub: new AssistantEventHub(),
591
+ resolveAttachments: () => [],
592
+ },
593
+ },
594
+ actorAuthContext,
595
+ );
596
+
597
+ expect(response.status).toBe(202);
598
+ const registered = await waitFor(() => pendingRegisteredAtSend, 1000);
599
+ expect(observedRequestId).toBeDefined();
600
+ expect(registered).toBe(true);
601
+ });
332
602
  });
333
603
 
334
604
  describe("conversationKey send path disk-view regression", () => {
@@ -1391,6 +1391,61 @@ describe("buildUnifiedTurnContextBlock", () => {
1391
1391
  expect(text).toContain("contact_notes: Prefers short replies");
1392
1392
  expect(text).toContain("contact_interaction_count: 42");
1393
1393
  });
1394
+
1395
+ test("time_since_last_message: emitted right after current_time when provided", () => {
1396
+ const options: UnifiedTurnContextOptions = {
1397
+ timestamp: "2026-04-02T12:00:00Z",
1398
+ interfaceName: "macos",
1399
+ timeSinceLastMessage: "2d ago",
1400
+ };
1401
+
1402
+ const text = buildUnifiedTurnContextBlock(options);
1403
+ const lines = text.split("\n");
1404
+ expect(lines[0]).toBe("<turn_context>");
1405
+ expect(lines[1]).toBe("current_time: 2026-04-02T12:00:00Z");
1406
+ expect(lines[2]).toBe("time_since_last_message: 2d ago");
1407
+ expect(lines[3]).toBe("interface: macos");
1408
+ expect(lines[4]).toBe("</turn_context>");
1409
+ });
1410
+
1411
+ test("time_since_last_message: omitted when null", () => {
1412
+ const options: UnifiedTurnContextOptions = {
1413
+ timestamp: "2026-04-02T12:00:00Z",
1414
+ interfaceName: "macos",
1415
+ timeSinceLastMessage: null,
1416
+ };
1417
+
1418
+ const text = buildUnifiedTurnContextBlock(options);
1419
+ expect(text).not.toContain("time_since_last_message");
1420
+ });
1421
+
1422
+ test("time_since_last_message: omitted when field absent (backward-compat)", () => {
1423
+ const options: UnifiedTurnContextOptions = {
1424
+ timestamp: "2026-04-02T12:00:00Z",
1425
+ interfaceName: "macos",
1426
+ };
1427
+
1428
+ const text = buildUnifiedTurnContextBlock(options);
1429
+ expect(text).not.toContain("time_since_last_message");
1430
+ });
1431
+
1432
+ test("time_since_last_message: works on non-guardian path", () => {
1433
+ const options: UnifiedTurnContextOptions = {
1434
+ timestamp: "2026-04-02T12:00:00Z",
1435
+ interfaceName: "telegram",
1436
+ channelName: "telegram",
1437
+ timeSinceLastMessage: "yesterday",
1438
+ actorContext: {
1439
+ sourceChannel: "telegram",
1440
+ canonicalActorIdentity: "user-1",
1441
+ trustClass: "trusted_contact",
1442
+ },
1443
+ };
1444
+
1445
+ const text = buildUnifiedTurnContextBlock(options);
1446
+ expect(text).toContain("time_since_last_message: yesterday");
1447
+ expect(text).toContain("canonical_actor_identity: user-1");
1448
+ });
1394
1449
  });
1395
1450
 
1396
1451
  // ---------------------------------------------------------------------------
@@ -1829,7 +1829,7 @@ describe("bundled skill: browser", () => {
1829
1829
  sessionState = new Map<string, string>();
1830
1830
  });
1831
1831
 
1832
- test("browser skill activation via loaded_skill marker registers all 14 tools in allowedToolNames", () => {
1832
+ test("browser skill activation via loaded_skill marker registers all browser tools in allowedToolNames", () => {
1833
1833
  mockCatalog = [makeSkill("browser", "/path/to/bundled-skills/browser")];
1834
1834
  mockManifests = { browser: makeManifest([...BROWSER_TOOL_NAMES]) };
1835
1835
 
@@ -1876,7 +1876,7 @@ describe("bundled skill: browser", () => {
1876
1876
 
1877
1877
  const tools = mockRegisteredTools.get("browser");
1878
1878
  expect(tools).toBeDefined();
1879
- expect(tools!.length).toBe(14);
1879
+ expect(tools!.length).toBe(BROWSER_TOOL_NAMES.length);
1880
1880
 
1881
1881
  for (const tool of tools!) {
1882
1882
  expect(tool.origin).toBe("skill");
@@ -2610,10 +2610,13 @@ describe("browser skill migration harness", () => {
2610
2610
  expect(id1).not.toBe(id2);
2611
2611
  });
2612
2612
 
2613
- test("BROWSER_TOOL_NAMES contains all 14 browser tools", () => {
2614
- expect(BROWSER_TOOL_NAMES).toHaveLength(14);
2613
+ test("BROWSER_TOOL_NAMES contains all browser tools", () => {
2614
+ expect(BROWSER_TOOL_NAMES).toHaveLength(17);
2615
2615
  expect(BROWSER_TOOL_NAMES).toContain("browser_navigate");
2616
+ expect(BROWSER_TOOL_NAMES).toContain("browser_attach");
2617
+ expect(BROWSER_TOOL_NAMES).toContain("browser_detach");
2616
2618
  expect(BROWSER_TOOL_NAMES).toContain("browser_fill_credential");
2619
+ expect(BROWSER_TOOL_NAMES).toContain("browser_status");
2617
2620
  });
2618
2621
 
2619
2622
  test("assertBrowserToolsPresent passes when all tools present", () => {
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import {
4
+ classifySlash,
4
5
  resolveSlash,
5
6
  type SlashContext,
6
7
  } from "../daemon/conversation-slash.js";
@@ -148,3 +149,35 @@ describe("resolveSlash command contract", () => {
148
149
  expect(result.message).toContain("Pairing is not available");
149
150
  });
150
151
  });
152
+
153
+ describe("classifySlash is a pure classifier matching resolveSlash kinds", () => {
154
+ // Lookahead in `buildPassthroughBatch` must not run `resolveSlash`'s side
155
+ // effects (/pair registers a pairing request and writes a QR PNG). The
156
+ // pure classifier is synchronous, takes no side-effecting dependencies,
157
+ // and must agree with resolveSlash's `kind`.
158
+ const cases: Array<{ input: string; kind: "passthrough" | "compact" | "unknown" }> = [
159
+ { input: "/pair", kind: "unknown" },
160
+ { input: "/models", kind: "unknown" },
161
+ { input: "/status", kind: "unknown" },
162
+ { input: "/commands", kind: "unknown" },
163
+ { input: "/compact", kind: "compact" },
164
+ { input: "/model", kind: "unknown" },
165
+ { input: "/model foo", kind: "unknown" },
166
+ { input: "/opus", kind: "unknown" },
167
+ { input: "hello", kind: "passthrough" },
168
+ { input: " /compact ", kind: "compact" },
169
+ { input: "/pair foo", kind: "passthrough" },
170
+ { input: "/models foo", kind: "passthrough" },
171
+ ];
172
+
173
+ for (const { input, kind } of cases) {
174
+ test(`classifies ${JSON.stringify(input)} as ${kind}`, async () => {
175
+ expect(classifySlash(input)).toBe(kind);
176
+ const resolved = await resolveSlash(
177
+ input,
178
+ makeSlashContext({ userMessageInterface: "macos" }),
179
+ );
180
+ expect(resolved.kind).toBe(kind);
181
+ });
182
+ }
183
+ });
@@ -90,6 +90,7 @@ mock.module("../security/secret-allowlist.js", () => ({
90
90
  mock.module("../memory/conversation-crud.js", () => ({
91
91
  getConversationType: () => "default",
92
92
  setConversationOriginChannelIfUnset: () => {},
93
+ setConversationOriginInterfaceIfUnset: () => {},
93
94
  updateConversationContextWindow: () => {},
94
95
  deleteMessageById: () => {},
95
96
  provenanceFromTrustContext: () => ({
@@ -113,6 +114,8 @@ mock.module("../memory/conversation-crud.js", () => ({
113
114
  },
114
115
  updateConversationUsage: () => {},
115
116
  updateConversationTitle: () => {},
117
+ getMessageById: () => null,
118
+ getLastUserTimestampBefore: () => 0,
116
119
  }));
117
120
 
118
121
  mock.module("../memory/conversation-queries.js", () => ({
@@ -206,6 +209,24 @@ mock.module("../agent/loop.js", () => ({
206
209
  }
207
210
  },
208
211
  }));
212
+ // Avoid real workspace-git initialization on /tmp — on CI runners,
213
+ // `git add -A` under /tmp hits permission errors on systemd-private dirs,
214
+ // which blocks `runAgentLoopImpl` for long enough to trip the test's
215
+ // `waitForPendingRun` 2s timeout before `AgentLoop.run` is invoked.
216
+ mock.module("../workspace/git-service.js", () => ({
217
+ getWorkspaceGitService: () => ({
218
+ ensureInitialized: async () => {},
219
+ }),
220
+ }));
221
+
222
+ mock.module("../workspace/turn-commit.js", () => ({
223
+ commitTurnChanges: async () => {},
224
+ }));
225
+
226
+ mock.module("../memory/app-git-service.js", () => ({
227
+ commitAppTurnChanges: async () => {},
228
+ }));
229
+
209
230
  mock.module("../memory/canonical-guardian-store.js", () => ({
210
231
  listPendingCanonicalGuardianRequestsByDestinationConversation: () => [],
211
232
  listCanonicalGuardianRequests: () => [],
@@ -232,6 +253,12 @@ mock.module("../memory/canonical-guardian-store.js", () => ({
232
253
 
233
254
  import { Conversation } from "../daemon/conversation.js";
234
255
 
256
+ type ConversationWithWorkspaceDeps = Conversation & {
257
+ getWorkspaceGitService?: (_workspaceDir: string) => {
258
+ ensureInitialized: () => Promise<void>;
259
+ };
260
+ };
261
+
235
262
  function makeConversation(): Conversation {
236
263
  const provider = {
237
264
  name: "mock",
@@ -244,7 +271,7 @@ function makeConversation(): Conversation {
244
271
  };
245
272
  },
246
273
  };
247
- return new Conversation(
274
+ const conversation = new Conversation(
248
275
  "conv-1",
249
276
  provider,
250
277
  "system prompt",
@@ -252,6 +279,13 @@ function makeConversation(): Conversation {
252
279
  () => {},
253
280
  "/tmp",
254
281
  );
282
+ // Bypass real workspace git init: with "/tmp" as the workspace dir, a real
283
+ // ensureInitialized() walks all of /tmp and can exceed the 2s waitForPendingRun
284
+ // budget on CI where parallel tests churn /tmp subdirectories.
285
+ (conversation as ConversationWithWorkspaceDeps).getWorkspaceGitService = () => ({
286
+ ensureInitialized: async () => {},
287
+ });
288
+ return conversation;
255
289
  }
256
290
 
257
291
  async function waitForPendingRun(
@@ -296,7 +330,7 @@ describe("Conversation queue — slash-like messages pass through to agent loop"
296
330
  pendingRuns = [];
297
331
  });
298
332
 
299
- test("queued slash-like input does not stall queue — passes through", async () => {
333
+ test("queued slash-like input does not stall queue — batches with siblings", async () => {
300
334
  const conversation = makeConversation();
301
335
  await conversation.loadFromDb();
302
336
 
@@ -313,7 +347,8 @@ describe("Conversation queue — slash-like messages pass through to agent loop"
313
347
  );
314
348
  await waitForPendingRun(1);
315
349
 
316
- // Enqueue a slash-like message and a normal message after it
350
+ // Enqueue a slash-like passthrough and a normal passthrough after it.
351
+ // Both resolve to passthrough, so the batch builder groups them into one run.
317
352
  conversation.enqueueMessage(
318
353
  "/not-a-skill",
319
354
  [],
@@ -323,30 +358,19 @@ describe("Conversation queue — slash-like messages pass through to agent loop"
323
358
  conversation.enqueueMessage("msg-3", [], (e) => events3.push(e), "req-3");
324
359
  expect(conversation.getQueueDepth()).toBe(2);
325
360
 
326
- // Complete first run — triggers drain
361
+ // Complete first run — drain pulls both queued messages into one batched run.
327
362
  resolveRun(0);
328
363
  await p1;
329
-
330
- // The slash-like message should go through agent loop (passthrough)
331
364
  await waitForPendingRun(2);
332
365
 
333
- // Slash-like message events: dequeued (agent loop started)
366
+ // Both batched clients see their own message_dequeued.
334
367
  expect(eventsSlash.some((e) => e.type === "message_dequeued")).toBe(true);
368
+ expect(events3.some((e) => e.type === "message_dequeued")).toBe(true);
335
369
 
336
- // It goes through the agent loop — so 2 agent runs total (msg-1 and /not-a-skill)
370
+ // Exactly 2 runs total: msg-1 + batched [/not-a-skill, msg-3].
337
371
  expect(pendingRuns.length).toBe(2);
338
372
 
339
- // Complete the slash run so msg-3 can drain
340
373
  resolveRun(1);
341
- await waitForPendingRun(3);
342
-
343
- // msg-3 events: dequeued (agent loop started)
344
- expect(events3.some((e) => e.type === "message_dequeued")).toBe(true);
345
-
346
- // 3 total agent loop runs: msg-1, /not-a-skill, msg-3
347
- expect(pendingRuns.length).toBe(3);
348
-
349
- resolveRun(2);
350
374
  await new Promise((r) => setTimeout(r, 50));
351
375
  });
352
376
 
@@ -393,4 +417,51 @@ describe("Conversation queue — slash-like messages pass through to agent loop"
393
417
  resolveRun(1);
394
418
  await new Promise((r) => setTimeout(r, 50));
395
419
  });
420
+
421
+ test("passthrough batch is terminated by a /compact in the middle of the queue", async () => {
422
+ const conversation = makeConversation();
423
+ await conversation.loadFromDb();
424
+
425
+ const eventsHi: ServerMessage[] = [];
426
+ const eventsCompact: ServerMessage[] = [];
427
+ const eventsBye: ServerMessage[] = [];
428
+
429
+ // Start in-flight message
430
+ const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
431
+ await waitForPendingRun(1);
432
+
433
+ // Enqueue ["hi", "/compact", "bye"]. /compact is non-passthrough, so the
434
+ // batch builder stops at "hi" (length-1 batch → drainSingleMessage). Then
435
+ // /compact takes its short-circuit path (no new runAgentLoop), and "bye"
436
+ // drains as its own run.
437
+ conversation.enqueueMessage("hi", [], (e) => eventsHi.push(e), "req-hi");
438
+ conversation.enqueueMessage(
439
+ "/compact",
440
+ [],
441
+ (e) => eventsCompact.push(e),
442
+ "req-compact",
443
+ );
444
+ conversation.enqueueMessage("bye", [], (e) => eventsBye.push(e), "req-bye");
445
+ expect(conversation.getQueueDepth()).toBe(3);
446
+
447
+ // Resolve msg-1 → drain pulls only "hi" (batch builder stops at /compact).
448
+ resolveRun(0);
449
+ await p1;
450
+ await waitForPendingRun(2);
451
+
452
+ expect(pendingRuns.length).toBe(2);
453
+ expect(eventsHi.some((e) => e.type === "message_dequeued")).toBe(true);
454
+
455
+ // Resolve "hi" → /compact short-circuits without a new runAgentLoop, then
456
+ // drains "bye" as its own run.
457
+ resolveRun(1);
458
+ await waitForPendingRun(3);
459
+
460
+ expect(eventsCompact.some((e) => e.type === "message_complete")).toBe(true);
461
+ expect(eventsBye.some((e) => e.type === "message_dequeued")).toBe(true);
462
+ expect(pendingRuns.length).toBe(3);
463
+
464
+ resolveRun(2);
465
+ await new Promise((r) => setTimeout(r, 50));
466
+ });
396
467
  });
@@ -120,6 +120,8 @@ mock.module("../memory/conversation-crud.js", () => ({
120
120
  },
121
121
  updateConversationUsage: () => {},
122
122
  updateConversationTitle: () => {},
123
+ getMessageById: () => null,
124
+ getLastUserTimestampBefore: () => 0,
123
125
  }));
124
126
 
125
127
  mock.module("../memory/conversation-queries.js", () => ({