@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
@@ -0,0 +1,497 @@
1
+ /**
2
+ * Unit tests for the `_action: "launch_conversation"` dispatch branch in
3
+ * `handleSurfaceAction` AND the `launchConversation` helper it calls into.
4
+ *
5
+ * The real `launchConversation` is exercised end-to-end: its DB-hitting
6
+ * dependencies (`conversation-key-store`, `conversation-crud`) and its
7
+ * registered daemon deps are stubbed so the helper runs without a DB.
8
+ * This lets the tests assert the full invariant set in one place:
9
+ *
10
+ * - `handleSurfaceAction` does NOT publish `open_conversation` itself —
11
+ * `launchConversation` is the sole emitter of that event.
12
+ * - Exactly one `open_conversation` is published per launch, carrying
13
+ * the caller-supplied `focus` value (false for fan-out launchers).
14
+ * - The handler returns promptly — the seed turn
15
+ * (`persistAndProcessMessage`) is fire-and-forget.
16
+ * - `originTrustContext` is forwarded to the spawned conversation.
17
+ *
18
+ * These tests guard the single-emitter invariant: exactly one
19
+ * `open_conversation` event is published per launch, with the
20
+ * caller-supplied `focus` value preserved so fan-out launchers do not
21
+ * steal focus from the origin.
22
+ */
23
+
24
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
25
+
26
+ // ── Module-level mocks ─────────────────────────────────────────────
27
+
28
+ // Hub publish capture — used by the single-emit assertions. We spread
29
+ // the real module into each override so unrelated exports (e.g.
30
+ // `formatSseFrame` on assistant-event) stay accessible to other
31
+ // importers loaded indirectly through `conversation-surfaces.ts`.
32
+ const publishCalls: Array<unknown> = [];
33
+ const realHub = await import("../../runtime/assistant-event-hub.js");
34
+ const realEvent = await import("../../runtime/assistant-event.js");
35
+ mock.module("../../runtime/assistant-event-hub.js", () => ({
36
+ ...realHub,
37
+ assistantEventHub: {
38
+ publish: async (event: unknown) => {
39
+ publishCalls.push(event);
40
+ },
41
+ },
42
+ }));
43
+ mock.module("../../runtime/assistant-event.js", () => ({
44
+ ...realEvent,
45
+ // Pass-through so `focus` / `conversationId` can be asserted directly
46
+ // on the captured event's `message` payload.
47
+ buildAssistantEvent: (
48
+ assistantId: string,
49
+ message: unknown,
50
+ conversationId?: string,
51
+ ) => ({ assistantId, message, conversationId }),
52
+ }));
53
+
54
+ // Stub DB helpers so the real `launchConversation` can run without a DB.
55
+ // We spread the real module and override only the specific functions the
56
+ // helper uses — other importers (e.g. conversation-surfaces itself)
57
+ // continue to see `getMessages`, `updateMessageContent`, etc.
58
+ let nextKeyStoreResult: { conversationId: string } = {
59
+ conversationId: "conv-new",
60
+ };
61
+ const updateTitleCalls: Array<{ conversationId: string; title: string }> = [];
62
+ const realKeyStore = await import("../../memory/conversation-key-store.js");
63
+ const realCrud = await import("../../memory/conversation-crud.js");
64
+ mock.module("../../memory/conversation-key-store.js", () => ({
65
+ ...realKeyStore,
66
+ getOrCreateConversation: (_key: string) => nextKeyStoreResult,
67
+ }));
68
+ mock.module("../../memory/conversation-crud.js", () => ({
69
+ ...realCrud,
70
+ updateConversationTitle: (
71
+ conversationId: string,
72
+ title: string,
73
+ _priority: number,
74
+ ) => {
75
+ updateTitleCalls.push({ conversationId, title });
76
+ },
77
+ }));
78
+
79
+ // Dynamic imports after mock.module calls so the stubs take effect
80
+ // before the modules under test are loaded.
81
+ const { createSurfaceMutex, handleSurfaceAction } =
82
+ await import("../conversation-surfaces.js");
83
+ const { registerLaunchConversationDeps, resetLaunchConversationDeps } =
84
+ await import("../conversation-launch.js");
85
+ type SurfaceConversationContext =
86
+ import("../conversation-surfaces.js").SurfaceConversationContext;
87
+ type TrustContext = import("../conversation-runtime-assembly.js").TrustContext;
88
+ type ServerMessage = import("../message-protocol.js").ServerMessage;
89
+ type SurfaceData = import("../message-protocol.js").SurfaceData;
90
+ type SurfaceType = import("../message-protocol.js").SurfaceType;
91
+
92
+ // ── launchConversation deps harness ────────────────────────────────
93
+
94
+ interface DepsHarness {
95
+ getOrCreateCalls: Array<string>;
96
+ processCalls: Array<{ conversationId: string; content: string }>;
97
+ lastTrustContext(): unknown | null;
98
+ resolveProcess: () => void;
99
+ rejectProcess: (err: Error) => void;
100
+ /** Resolves once `persistAndProcessMessage` has actually been invoked. */
101
+ processStarted: Promise<void>;
102
+ }
103
+
104
+ function setupLaunchDeps(): DepsHarness {
105
+ const getOrCreateCalls: Array<string> = [];
106
+ const processCalls: Array<{ conversationId: string; content: string }> = [];
107
+ let trustContextOnConversation: unknown | null = null;
108
+ let resolveProcess = () => {};
109
+ let rejectProcess: (err: Error) => void = () => {};
110
+ let markProcessStarted = () => {};
111
+ const processStarted = new Promise<void>((resolve) => {
112
+ markProcessStarted = resolve;
113
+ });
114
+
115
+ const fakeConversation = {
116
+ setTrustContext: (c: unknown) => {
117
+ trustContextOnConversation = c;
118
+ },
119
+ };
120
+
121
+ registerLaunchConversationDeps({
122
+ getOrCreateConversation: async (id: string) => {
123
+ getOrCreateCalls.push(id);
124
+ return fakeConversation as never;
125
+ },
126
+ persistAndProcessMessage: (conversationId: string, content: string) => {
127
+ processCalls.push({ conversationId, content });
128
+ markProcessStarted();
129
+ return new Promise((resolve, reject) => {
130
+ resolveProcess = () => resolve({ messageId: "msg-1" });
131
+ rejectProcess = (err) => reject(err);
132
+ });
133
+ },
134
+ publishAssistantEvent: () => {},
135
+ getAssistantId: () => "assistant-test-id",
136
+ });
137
+
138
+ return {
139
+ getOrCreateCalls,
140
+ processCalls,
141
+ lastTrustContext: () => trustContextOnConversation,
142
+ resolveProcess: () => resolveProcess(),
143
+ rejectProcess: (err: Error) => rejectProcess(err),
144
+ processStarted,
145
+ };
146
+ }
147
+
148
+ // ── Surface-context harness ────────────────────────────────────────
149
+
150
+ interface HarnessContext extends SurfaceConversationContext {
151
+ sent: ServerMessage[];
152
+ enqueueCalls: Array<{ content: string }>;
153
+ processCalls: Array<{ content: string }>;
154
+ }
155
+
156
+ function makeContext(
157
+ overrides?: Partial<SurfaceConversationContext>,
158
+ ): HarnessContext {
159
+ const sent: ServerMessage[] = [];
160
+ const enqueueCalls: Array<{ content: string }> = [];
161
+ const processCalls: Array<{ content: string }> = [];
162
+
163
+ const base: SurfaceConversationContext = {
164
+ conversationId: "origin-conv-id",
165
+ traceEmitter: { emit: () => {} },
166
+ sendToClient: (msg) => sent.push(msg),
167
+ pendingSurfaceActions: new Map<string, { surfaceType: SurfaceType }>(),
168
+ lastSurfaceAction: new Map<
169
+ string,
170
+ { actionId: string; data?: Record<string, unknown> }
171
+ >(),
172
+ surfaceState: new Map<
173
+ string,
174
+ { surfaceType: SurfaceType; data: SurfaceData; title?: string }
175
+ >(),
176
+ surfaceUndoStacks: new Map<string, string[]>(),
177
+ accumulatedSurfaceState: new Map<string, Record<string, unknown>>(),
178
+ surfaceActionRequestIds: new Set<string>(),
179
+ currentTurnSurfaces: [],
180
+ isProcessing: () => false,
181
+ enqueueMessage: (content: string) => {
182
+ enqueueCalls.push({ content });
183
+ return { queued: false, requestId: "enq-req" };
184
+ },
185
+ getQueueDepth: () => 0,
186
+ processMessage: async (content: string) => {
187
+ processCalls.push({ content });
188
+ return "ok";
189
+ },
190
+ withSurface: createSurfaceMutex(),
191
+ ...overrides,
192
+ };
193
+
194
+ return Object.assign(base, {
195
+ sent,
196
+ enqueueCalls,
197
+ processCalls,
198
+ }) as HarnessContext;
199
+ }
200
+
201
+ /**
202
+ * Register a surface on `ctx`. Launcher cards arrive as history-restored
203
+ * surfaces (no `pendingSurfaceActions` entry) — matching how the card
204
+ * actually reaches `handleSurfaceAction` after reconstruction.
205
+ */
206
+ function registerCardSurface(
207
+ ctx: SurfaceConversationContext,
208
+ surfaceId: string,
209
+ ): void {
210
+ ctx.surfaceState.set(surfaceId, {
211
+ surfaceType: "card",
212
+ data: { title: "Launch" } as unknown as SurfaceData,
213
+ });
214
+ }
215
+
216
+ // Helper: filter captured publish calls down to `open_conversation`
217
+ // events. Typed so assertions can reach the inner `message` payload.
218
+ function openConversationEvents(): Array<{
219
+ assistantId: string;
220
+ conversationId?: string;
221
+ message: {
222
+ type: "open_conversation";
223
+ conversationId: string;
224
+ title?: string;
225
+ anchorMessageId?: string;
226
+ focus?: boolean;
227
+ };
228
+ }> {
229
+ return publishCalls
230
+ .filter((e): e is { message: { type: "open_conversation" } } => {
231
+ const ev = e as { message?: { type?: string } };
232
+ return ev.message?.type === "open_conversation";
233
+ })
234
+ .map(
235
+ (e) =>
236
+ e as unknown as {
237
+ assistantId: string;
238
+ conversationId?: string;
239
+ message: {
240
+ type: "open_conversation";
241
+ conversationId: string;
242
+ title?: string;
243
+ anchorMessageId?: string;
244
+ focus?: boolean;
245
+ };
246
+ },
247
+ );
248
+ }
249
+
250
+ // ── Tests ──────────────────────────────────────────────────────────
251
+
252
+ describe("handleSurfaceAction — launch_conversation dispatch", () => {
253
+ beforeEach(() => {
254
+ publishCalls.length = 0;
255
+ updateTitleCalls.length = 0;
256
+ nextKeyStoreResult = { conversationId: "conv-new" };
257
+ // Reset module-level `_deps` so a test that forgets to call
258
+ // `setupLaunchDeps()` cannot accidentally piggy-back on deps left
259
+ // registered by a previous test. Each test that exercises the launch
260
+ // helper must call `setupLaunchDeps()` explicitly.
261
+ resetLaunchConversationDeps();
262
+ });
263
+
264
+ test("launches new conversation with inherited trust context and no chat message", async () => {
265
+ nextKeyStoreResult = { conversationId: "conv-launched-1" };
266
+ const harness = setupLaunchDeps();
267
+ const originTrustContext: TrustContext = {
268
+ sourceChannel: "vellum",
269
+ trustClass: "guardian",
270
+ guardianChatId: "chat-guardian",
271
+ guardianPrincipalId: "principal-guardian",
272
+ };
273
+ const ctx = makeContext({ trustContext: originTrustContext });
274
+ registerCardSurface(ctx, "surface-1");
275
+
276
+ const result = await handleSurfaceAction(ctx, "surface-1", "launch", {
277
+ _action: "launch_conversation",
278
+ title: "New Thread",
279
+ seedPrompt: "S",
280
+ });
281
+
282
+ // 1. Response shape.
283
+ expect(result).toEqual({
284
+ accepted: true,
285
+ conversationId: "conv-launched-1",
286
+ });
287
+
288
+ // 2. Exactly ONE `open_conversation` event was published for the new
289
+ // id, with focus: false. `launchConversation` is the sole emitter;
290
+ // `handleSurfaceAction` delegates entirely to it.
291
+ const openEvents = openConversationEvents();
292
+ expect(openEvents).toHaveLength(1);
293
+ expect(openEvents[0].message.conversationId).toBe("conv-launched-1");
294
+ expect(openEvents[0].message.focus).toBe(false);
295
+ expect(openEvents[0].message.title).toBe("New Thread");
296
+
297
+ // 3. The spawned conversation inherited the origin's trust context.
298
+ expect(harness.lastTrustContext()).toEqual(originTrustContext);
299
+
300
+ // 4. Seed turn was kicked off fire-and-forget — resolve it to clean
301
+ // up the pending promise the harness stubbed.
302
+ await harness.processStarted;
303
+ expect(harness.processCalls).toHaveLength(1);
304
+ expect(harness.processCalls[0].content).toBe("S");
305
+ harness.resolveProcess();
306
+
307
+ // 5. No chat message side effect on the origin conversation — neither
308
+ // the LLM pipeline nor the `[User action on app: ...]` text echo.
309
+ expect(ctx.enqueueCalls).toHaveLength(0);
310
+ expect(ctx.processCalls).toHaveLength(0);
311
+ const anyUserActionEcho = ctx.sent.some(
312
+ (msg) =>
313
+ "text" in msg &&
314
+ typeof msg.text === "string" &&
315
+ msg.text.includes("[User action on app:"),
316
+ );
317
+ expect(anyUserActionEcho).toBe(false);
318
+ });
319
+
320
+ test("returns error when title or seedPrompt is missing", async () => {
321
+ const ctx = makeContext();
322
+ registerCardSurface(ctx, "surface-2");
323
+
324
+ // Missing seedPrompt.
325
+ const missingSeed = await handleSurfaceAction(ctx, "surface-2", "launch", {
326
+ _action: "launch_conversation",
327
+ title: "T",
328
+ });
329
+ expect(missingSeed).toEqual({
330
+ accepted: false,
331
+ error: "missing_title_or_seedPrompt",
332
+ });
333
+
334
+ // Missing title.
335
+ const missingTitle = await handleSurfaceAction(ctx, "surface-2", "launch", {
336
+ _action: "launch_conversation",
337
+ seedPrompt: "S",
338
+ });
339
+ expect(missingTitle).toEqual({
340
+ accepted: false,
341
+ error: "missing_title_or_seedPrompt",
342
+ });
343
+
344
+ // Neither field: still the same validation error.
345
+ const missingBoth = await handleSurfaceAction(ctx, "surface-2", "launch", {
346
+ _action: "launch_conversation",
347
+ });
348
+ expect(missingBoth).toEqual({
349
+ accepted: false,
350
+ error: "missing_title_or_seedPrompt",
351
+ });
352
+
353
+ // No launch-side effects in any of the failed validations — no events,
354
+ // no queued origin-conversation messages.
355
+ expect(publishCalls).toHaveLength(0);
356
+ expect(ctx.enqueueCalls).toHaveLength(0);
357
+ });
358
+
359
+ test("omits originTrustContext when origin conversation has none", async () => {
360
+ nextKeyStoreResult = { conversationId: "conv-launched-3" };
361
+ const harness = setupLaunchDeps();
362
+ // No `trustContext` on the origin context — simulating the
363
+ // no-inherited-guardian path.
364
+ const ctx = makeContext();
365
+ registerCardSurface(ctx, "surface-3");
366
+
367
+ const result = await handleSurfaceAction(ctx, "surface-3", "launch", {
368
+ _action: "launch_conversation",
369
+ title: "T",
370
+ seedPrompt: "S",
371
+ });
372
+
373
+ expect(result).toEqual({
374
+ accepted: true,
375
+ conversationId: "conv-launched-3",
376
+ });
377
+
378
+ // Trust context was never applied to the spawned conversation.
379
+ expect(harness.lastTrustContext()).toBeNull();
380
+
381
+ // Still exactly one open_conversation event with focus: false.
382
+ const openEvents = openConversationEvents();
383
+ expect(openEvents).toHaveLength(1);
384
+ expect(openEvents[0].message.focus).toBe(false);
385
+
386
+ await harness.processStarted;
387
+ harness.resolveProcess();
388
+ });
389
+
390
+ test("handler returns before the seed turn resolves (fire-and-forget)", async () => {
391
+ nextKeyStoreResult = { conversationId: "conv-nonblocking" };
392
+ const harness = setupLaunchDeps();
393
+ const ctx = makeContext();
394
+ registerCardSurface(ctx, "surface-4");
395
+
396
+ // The harness's `persistAndProcessMessage` returns a pending Promise
397
+ // that only resolves when we call `resolveProcess()`. If the helper
398
+ // (or handler) awaited it, `await handleSurfaceAction(...)` below
399
+ // would hang. The fact that it resolves while the seed turn is still
400
+ // pending proves the fire-and-forget behavior that the HTTP route
401
+ // relies on for the fan-out multi-launch UX.
402
+ const result = await handleSurfaceAction(ctx, "surface-4", "launch", {
403
+ _action: "launch_conversation",
404
+ title: "T",
405
+ seedPrompt: "S",
406
+ });
407
+
408
+ expect(result).toEqual({
409
+ accepted: true,
410
+ conversationId: "conv-nonblocking",
411
+ });
412
+
413
+ // Seed turn is in-flight but not yet resolved. Prove the helper
414
+ // actually invoked it (so we know fire-and-forget is wired), then
415
+ // resolve it to clean up.
416
+ await harness.processStarted;
417
+ expect(harness.processCalls).toHaveLength(1);
418
+ harness.resolveProcess();
419
+ });
420
+
421
+ test("seed turn rejection is swallowed by the helper's .catch()", async () => {
422
+ nextKeyStoreResult = { conversationId: "conv-seed-fails" };
423
+ const harness = setupLaunchDeps();
424
+ const ctx = makeContext();
425
+ registerCardSurface(ctx, "surface-5");
426
+
427
+ const result = await handleSurfaceAction(ctx, "surface-5", "launch", {
428
+ _action: "launch_conversation",
429
+ title: "T",
430
+ seedPrompt: "boom",
431
+ });
432
+
433
+ expect(result).toEqual({
434
+ accepted: true,
435
+ conversationId: "conv-seed-fails",
436
+ });
437
+
438
+ // Reject the pending seed turn — the helper's `.catch()` handler
439
+ // must swallow it. If it didn't, Bun would surface the unhandled
440
+ // rejection at test-end and this test would fail.
441
+ await harness.processStarted;
442
+ harness.rejectProcess(new Error("seed-turn-failed"));
443
+ // Give the microtask queue a tick so the `.catch()` runs before
444
+ // the test completes.
445
+ await Promise.resolve();
446
+ await Promise.resolve();
447
+ });
448
+
449
+ test("dispatches launch even when pendingSurfaceActions has an entry for the surface (first-click case)", async () => {
450
+ // Regression for the gap that left the launch branch unreachable on the
451
+ // FIRST click of a freshly-rendered persistent launcher card. `ui_show`
452
+ // unconditionally sets `pendingSurfaceActions` for any interactive card
453
+ // (regardless of `persistent`), so without this fix `handleSurfaceAction`
454
+ // saw `pending` truthy, skipped the launch dispatch, and fell through to
455
+ // the pending path — emitting the `[User action on card surface: ...]`
456
+ // message and triggering a full LLM round-trip on every click. The plan
457
+ // claimed to eliminate that round-trip; this test enforces it.
458
+ nextKeyStoreResult = { conversationId: "conv-pending-set" };
459
+ const harness = setupLaunchDeps();
460
+ const ctx = makeContext();
461
+ registerCardSurface(ctx, "surface-pending");
462
+ // Simulate `ui_show` having stamped a pending entry for this surface
463
+ // (which it does for any interactive card, including persistent ones).
464
+ ctx.pendingSurfaceActions.set("surface-pending", { surfaceType: "card" });
465
+
466
+ const result = await handleSurfaceAction(ctx, "surface-pending", "launch", {
467
+ _action: "launch_conversation",
468
+ title: "T",
469
+ seedPrompt: "S",
470
+ });
471
+
472
+ expect(result).toEqual({
473
+ accepted: true,
474
+ conversationId: "conv-pending-set",
475
+ });
476
+
477
+ // Exactly one open_conversation event with focus: false — the launch
478
+ // branch ran, not the pending fallthrough.
479
+ const openEvents = openConversationEvents();
480
+ expect(openEvents).toHaveLength(1);
481
+ expect(openEvents[0].message.conversationId).toBe("conv-pending-set");
482
+ expect(openEvents[0].message.focus).toBe(false);
483
+
484
+ // Critical: NO message was enqueued onto the origin conversation. If the
485
+ // launch dispatch had fallen through to the pending path, the
486
+ // `[User action on card surface: ...]` text would have been enqueued and
487
+ // an LLM turn would have started.
488
+ expect(ctx.enqueueCalls).toHaveLength(0);
489
+
490
+ // Pending entry was deleted so subsequent sibling clicks on the same
491
+ // persistent card aren't blocked behind a stale "owes-an-answer" flag.
492
+ expect(ctx.pendingSurfaceActions.has("surface-pending")).toBe(false);
493
+
494
+ await harness.processStarted;
495
+ harness.resolveProcess();
496
+ });
497
+ });
@@ -1,19 +1,23 @@
1
1
  /**
2
2
  * Tests for `isToolActiveForContext` host-tool capability gating.
3
3
  *
4
- * Two scenarios are verified:
4
+ * Scenarios verified:
5
5
  * - chrome-extension is its own executor and is exempt from the hasNoClient
6
6
  * gate (the extension's own popup UI gates commands; there is no SSE
7
7
  * interactive approval channel, and chrome-extension turns intentionally
8
8
  * run with `hasNoClient: true` because chrome-extension is not in
9
9
  * `INTERACTIVE_INTERFACES`).
10
- * - macos still requires a connected SSE client for interactive approval, so
11
- * `hasNoClient: true` continues to deny all host tools on macos.
10
+ * - macos requires a connected SSE client for host tools that flow through
11
+ * the proxy (e.g. host_bash, host_file_*), so `hasNoClient: true` denies
12
+ * those on macos.
13
+ * - host_browser is NOT in the macos capability set because the proxy path
14
+ * requires a Chrome extension that isn't guaranteed to be attached; macos
15
+ * browser tools fall back to local Playwright Chromium.
12
16
  *
13
17
  * The per-capability check (`supportsHostProxy(transport, capability)`) runs
14
18
  * first and is authoritative for structural support, so host_bash and
15
19
  * host_file_* are filtered out for chrome-extension regardless of the
16
- * hasNoClient flag.
20
+ * hasNoClient flag, and host_browser is filtered out for macos.
17
21
  */
18
22
 
19
23
  import { describe, expect, test } from "bun:test";
@@ -70,18 +74,23 @@ describe("isToolActiveForContext — host tool capability gating", () => {
70
74
  ).toBe(false);
71
75
  });
72
76
 
73
- test("host_browser is active for macOS with a connected client", () => {
77
+ test("host_browser is NOT active for macOS (uses local Playwright)", () => {
78
+ // host_browser is not in the macos capability set because the proxy path
79
+ // requires a Chrome extension that isn't guaranteed to be attached; macos
80
+ // browser tools fall back to local Playwright Chromium instead. The
81
+ // per-capability check is authoritative, so host_browser is filtered out
82
+ // for macos regardless of client connection state.
74
83
  expect(
75
84
  isToolActiveForContext(
76
85
  "host_browser",
77
86
  makeCtx({ hasNoClient: false, transportInterface: "macos" }),
78
87
  ),
79
- ).toBe(true);
88
+ ).toBe(false);
80
89
  });
81
90
 
82
91
  test("host_browser is NOT active for macOS when hasNoClient is true", () => {
83
- // macOS requires a client for any host tool the SSE interactive
84
- // approval channel must be available regardless of capability.
92
+ // Same capability gate as above: host_browser is unsupported on macos
93
+ // regardless of connection state.
85
94
  expect(
86
95
  isToolActiveForContext(
87
96
  "host_browser",
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Invariant: the analyze-deps singleton must be populated before the memory
3
+ * worker starts, so any `conversation_analyze` job the worker claims on its
4
+ * first poll sees a non-null deps bundle.
5
+ *
6
+ * Assertions:
7
+ * 1. Source-ordering guard: `lifecycle.ts` constructs `RuntimeHttpServer`
8
+ * (which synchronously calls `setAnalysisDeps()` inside
9
+ * `buildRouteTable()`) before invoking `void initializeQdrantAndMemory()`
10
+ * (which kicks off the memory worker).
11
+ * 2. Runtime check: constructing `RuntimeHttpServer` with `sendMessageDeps`
12
+ * populates the analyze-deps singleton synchronously by the time the
13
+ * constructor returns.
14
+ */
15
+
16
+ import { readFileSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
19
+
20
+ mock.module("../../util/logger.js", () => ({
21
+ getLogger: () =>
22
+ new Proxy({} as Record<string, unknown>, {
23
+ get: () => () => {},
24
+ }),
25
+ }));
26
+
27
+ mock.module("../../config/env.js", () => ({
28
+ isHttpAuthDisabled: () => true,
29
+ hasUngatedHttpAuthDisabled: () => false,
30
+ getGatewayInternalBaseUrl: () => "http://127.0.0.1:7830",
31
+ getGatewayPort: () => 7830,
32
+ getRuntimeHttpPort: () => 7821,
33
+ getRuntimeHttpHost: () => "127.0.0.1",
34
+ getRuntimeGatewayOriginSecret: () => undefined,
35
+ getIngressPublicBaseUrl: () => undefined,
36
+ setIngressPublicBaseUrl: () => {},
37
+ }));
38
+
39
+ mock.module("../../config/loader.js", () => ({
40
+ getConfig: () => ({
41
+ ui: {},
42
+ model: "test",
43
+ provider: "test",
44
+ memory: { enabled: false },
45
+ rateLimit: { maxRequestsPerMinute: 0 },
46
+ secretDetection: { enabled: false },
47
+ }),
48
+ }));
49
+
50
+ import { initializeDb, resetDb } from "../../memory/db.js";
51
+ import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
52
+ import { RuntimeHttpServer } from "../../runtime/http-server.js";
53
+ import { getAnalysisDeps } from "../../runtime/services/analyze-deps-singleton.js";
54
+
55
+ initializeDb();
56
+
57
+ describe("daemon lifecycle startup ordering", () => {
58
+ let server: RuntimeHttpServer | null = null;
59
+
60
+ beforeEach(async () => {
61
+ await server?.stop();
62
+ server = null;
63
+ });
64
+
65
+ afterAll(async () => {
66
+ await server?.stop();
67
+ resetDb();
68
+ });
69
+
70
+ test("lifecycle.ts constructs RuntimeHttpServer before kicking off initializeQdrantAndMemory", () => {
71
+ // Source-level guard: read lifecycle.ts and assert the RuntimeHttpServer
72
+ // constructor call appears before the fire-and-forget memory init.
73
+ const lifecyclePath = join(
74
+ import.meta.dir,
75
+ "..",
76
+ "lifecycle.ts",
77
+ );
78
+ const content = readFileSync(lifecyclePath, "utf-8");
79
+
80
+ const httpServerCtorIdx = content.indexOf("new RuntimeHttpServer(");
81
+ const initQdrantIdx = content.indexOf("void initializeQdrantAndMemory(");
82
+
83
+ expect(
84
+ httpServerCtorIdx,
85
+ "Expected to find `new RuntimeHttpServer(` in lifecycle.ts",
86
+ ).toBeGreaterThan(-1);
87
+ expect(
88
+ initQdrantIdx,
89
+ "Expected to find `void initializeQdrantAndMemory(` in lifecycle.ts",
90
+ ).toBeGreaterThan(-1);
91
+
92
+ expect(
93
+ httpServerCtorIdx,
94
+ "lifecycle.ts must construct RuntimeHttpServer (which synchronously " +
95
+ "populates the analyze-deps singleton via buildRouteTable → " +
96
+ "setAnalysisDeps) BEFORE invoking `void initializeQdrantAndMemory()`. " +
97
+ "Otherwise the memory worker can claim a leftover " +
98
+ "`conversation_analyze` job before the deps singleton is populated, " +
99
+ "the handler throws, and the worker classifies the plain Error as " +
100
+ "fatal and drops the job.",
101
+ ).toBeLessThan(initQdrantIdx);
102
+ });
103
+
104
+ test("constructing RuntimeHttpServer with sendMessageDeps populates the analyze-deps singleton synchronously", async () => {
105
+ // Runtime guard: confirms the wiring inside buildRouteTable calls
106
+ // setAnalysisDeps when sendMessageDeps is provided. If that call ever
107
+ // moves out of buildRouteTable without an equivalent call site in
108
+ // lifecycle.ts, this assertion fires.
109
+ server = new RuntimeHttpServer({
110
+ port: 0,
111
+ bearerToken: "test-bearer-token",
112
+ sendMessageDeps: {
113
+ getOrCreateConversation: async () => {
114
+ throw new Error("not used in this test");
115
+ },
116
+ assistantEventHub,
117
+ resolveAttachments: () => [],
118
+ },
119
+ });
120
+
121
+ // The constructor populates the singleton synchronously — no start()
122
+ // call required. The memory worker's first tick runs as a microtask
123
+ // after lifecycle.ts kicks it off, so the singleton must be ready by
124
+ // the time the constructor returns.
125
+ expect(getAnalysisDeps()).not.toBeNull();
126
+ });
127
+ });