@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
@@ -19,10 +19,7 @@ import { z } from "zod";
19
19
  import { getConfig } from "../../config/loader.js";
20
20
  import { readNowScratchpad } from "../../daemon/conversation-runtime-assembly.js";
21
21
  import { getConversationByKey } from "../../memory/conversation-key-store.js";
22
- import {
23
- resolveChannelPersona,
24
- resolveGuardianPersona,
25
- } from "../../prompts/persona-resolver.js";
22
+ import { resolvePersonaContext } from "../../prompts/persona-resolver.js";
26
23
  import { getLogger } from "../../util/logger.js";
27
24
  import { getWorkspacePromptPath } from "../../util/platform.js";
28
25
  import type { AuthContext } from "../auth/types.js";
@@ -168,14 +165,19 @@ async function handleBtw(
168
165
  try {
169
166
  const isIntroRequest = conversationKey === IDENTITY_INTRO_KEY;
170
167
  const isGreeting = conversationKey === GREETING_KEY;
171
- const userPersona = resolveGuardianPersona();
172
- const channelPersona = resolveChannelPersona(undefined);
168
+ // Resolve guardian persona context (undefined trustContext triggers
169
+ // the guardian lookup path in persona-resolver). Thread userSlug
170
+ // through so buildSystemPrompt's BOOTSTRAP.md placeholder never
171
+ // falls back to "default.md" if excludeBootstrap is ever flipped.
172
+ const { userPersona, userSlug, channelPersona } =
173
+ resolvePersonaContext(undefined, undefined);
173
174
  const result = await runBtwSidechain({
174
175
  content: effectiveContent,
175
176
  conversation,
176
177
  signal: req.signal,
177
178
  userPersona,
178
179
  channelPersona,
180
+ userSlug,
179
181
  ...(isGreeting
180
182
  ? { modelIntent: getConfig().ui.greetingModelIntent }
181
183
  : {}),
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Tests for POST /v1/contacts/guardian/channel endpoint.
3
+ */
4
+ import { beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
5
+
6
+ mock.module("../../util/logger.js", () => ({
7
+ getLogger: () =>
8
+ new Proxy({} as Record<string, unknown>, {
9
+ get: () => () => {},
10
+ }),
11
+ }));
12
+
13
+ mock.module("../../config/loader.js", () => ({
14
+ loadConfig: () => ({}),
15
+ getConfig: () => ({}),
16
+ invalidateConfigCache: () => {},
17
+ }));
18
+
19
+ mock.module("../../config/env.js", () => ({
20
+ isHttpAuthDisabled: () => true,
21
+ getAssistantDomain: () => "vellum.me",
22
+ }));
23
+
24
+ import { and, eq } from "drizzle-orm";
25
+
26
+ import { getDb, initializeDb } from "../../memory/db.js";
27
+ import { contactChannels, contacts } from "../../memory/schema.js";
28
+ import type { AuthContext } from "../auth/types.js";
29
+ import { handleAddGuardianChannel } from "./contact-routes.js";
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Helpers
33
+ // ---------------------------------------------------------------------------
34
+
35
+ function makeRequest(body: unknown): Request {
36
+ return new Request("http://localhost/v1/contacts/guardian/channel", {
37
+ method: "POST",
38
+ headers: { "Content-Type": "application/json" },
39
+ body: JSON.stringify(body),
40
+ });
41
+ }
42
+
43
+ function makeServiceAuthContext(
44
+ overrides: Partial<AuthContext> = {},
45
+ ): AuthContext {
46
+ return {
47
+ subject: "platform-service",
48
+ principalType: "svc_gateway",
49
+ assistantId: "test-assistant",
50
+ actorPrincipalId: undefined,
51
+ scopeProfile: "gateway_service_v1",
52
+ scopes: new Set(),
53
+ policyEpoch: 0,
54
+ ...overrides,
55
+ } as AuthContext;
56
+ }
57
+
58
+ function makeActorAuthContext(
59
+ overrides: Partial<AuthContext> = {},
60
+ ): AuthContext {
61
+ return {
62
+ subject: "test-subject",
63
+ principalType: "actor",
64
+ assistantId: "test-assistant",
65
+ actorPrincipalId: "guardian-principal-001",
66
+ scopeProfile: "actor_client_v1",
67
+ scopes: new Set(),
68
+ policyEpoch: 0,
69
+ ...overrides,
70
+ } as AuthContext;
71
+ }
72
+
73
+ function seedGuardian(
74
+ displayName = "Test Guardian",
75
+ principalId = "guardian-principal-001",
76
+ ): { contactId: string } {
77
+ const db = getDb();
78
+ const contactId = "guardian-001";
79
+ db.insert(contacts)
80
+ .values({
81
+ id: contactId,
82
+ displayName,
83
+ role: "guardian",
84
+ contactType: "human",
85
+ principalId,
86
+ createdAt: Date.now(),
87
+ updatedAt: Date.now(),
88
+ })
89
+ .run();
90
+
91
+ // Add a pre-existing channel (e.g. Telegram) so the guardian is verified
92
+ db.insert(contactChannels)
93
+ .values({
94
+ id: "ch-telegram-001",
95
+ contactId,
96
+ type: "telegram",
97
+ address: "@testguardian",
98
+ externalUserId: principalId,
99
+ status: "active",
100
+ createdAt: Date.now(),
101
+ updatedAt: Date.now(),
102
+ })
103
+ .run();
104
+
105
+ return { contactId };
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Tests
110
+ // ---------------------------------------------------------------------------
111
+
112
+ describe("POST /v1/contacts/guardian/channel", () => {
113
+ beforeAll(() => {
114
+ initializeDb();
115
+ });
116
+
117
+ beforeEach(() => {
118
+ const db = getDb();
119
+ db.delete(contactChannels).run();
120
+ db.delete(contacts).run();
121
+ });
122
+
123
+ // ── Service token (platform) calls — the only permitted path ────────────
124
+
125
+ test("adds an email channel to an existing guardian (service auth)", async () => {
126
+ const { contactId } = seedGuardian();
127
+
128
+ const res = await handleAddGuardianChannel(
129
+ makeRequest({
130
+ type: "email",
131
+ address: "owner@example.com",
132
+ externalUserId: "owner@example.com",
133
+ }),
134
+ makeServiceAuthContext(),
135
+ );
136
+
137
+ expect(res.status).toBe(200);
138
+ const body = (await res.json()) as {
139
+ ok: boolean;
140
+ contact: { id: string };
141
+ };
142
+ expect(body.ok).toBe(true);
143
+ expect(body.contact.id).toBe(contactId);
144
+
145
+ // Verify channel was persisted
146
+ const db = getDb();
147
+ const rows = db
148
+ .select()
149
+ .from(contactChannels)
150
+ .where(
151
+ and(
152
+ eq(contactChannels.contactId, contactId),
153
+ eq(contactChannels.type, "email"),
154
+ ),
155
+ )
156
+ .all();
157
+ expect(rows.length).toBe(1);
158
+ expect(rows[0].address).toBe("owner@example.com");
159
+ expect(rows[0].status).toBe("active");
160
+ });
161
+
162
+ test("returns 404 when no guardian exists (service auth)", async () => {
163
+ const res = await handleAddGuardianChannel(
164
+ makeRequest({
165
+ type: "email",
166
+ address: "owner@example.com",
167
+ externalUserId: "owner@example.com",
168
+ }),
169
+ makeServiceAuthContext(),
170
+ );
171
+
172
+ expect(res.status).toBe(404);
173
+ const body = (await res.json()) as {
174
+ error: { code: string; message: string };
175
+ };
176
+ expect(body.error.message).toContain("No guardian contact exists");
177
+ });
178
+
179
+ test("returns 400 when type is missing (service auth)", async () => {
180
+ seedGuardian();
181
+
182
+ const res = await handleAddGuardianChannel(
183
+ makeRequest({ address: "owner@example.com" }),
184
+ makeServiceAuthContext(),
185
+ );
186
+ expect(res.status).toBe(400);
187
+ });
188
+
189
+ test("returns 400 when address is missing (service auth)", async () => {
190
+ seedGuardian();
191
+
192
+ const res = await handleAddGuardianChannel(
193
+ makeRequest({ type: "email", externalUserId: "owner@example.com" }),
194
+ makeServiceAuthContext(),
195
+ );
196
+ expect(res.status).toBe(400);
197
+ });
198
+
199
+ test("returns 400 when externalUserId is missing (service auth)", async () => {
200
+ seedGuardian();
201
+
202
+ const res = await handleAddGuardianChannel(
203
+ makeRequest({ type: "email", address: "owner@example.com" }),
204
+ makeServiceAuthContext(),
205
+ );
206
+ expect(res.status).toBe(400);
207
+ const body = (await res.json()) as {
208
+ error: { code: string; message: string };
209
+ };
210
+ expect(body.error.message).toContain("externalUserId is required");
211
+ });
212
+
213
+ test("preserves existing channels when adding new ones (service auth)", async () => {
214
+ const { contactId } = seedGuardian();
215
+
216
+ await handleAddGuardianChannel(
217
+ makeRequest({
218
+ type: "email",
219
+ address: "owner@example.com",
220
+ externalUserId: "owner@example.com",
221
+ }),
222
+ makeServiceAuthContext(),
223
+ );
224
+
225
+ // Guardian should still have the telegram channel + new email channel
226
+ const db = getDb();
227
+ const rows = db
228
+ .select()
229
+ .from(contactChannels)
230
+ .where(eq(contactChannels.contactId, contactId))
231
+ .all();
232
+
233
+ const types = rows.map((r) => r.type).sort();
234
+ expect(types).toEqual(["email", "telegram"]);
235
+ });
236
+
237
+ test("defaults channel status to active (service auth)", async () => {
238
+ seedGuardian();
239
+
240
+ const res = await handleAddGuardianChannel(
241
+ makeRequest({
242
+ type: "email",
243
+ address: "owner@example.com",
244
+ externalUserId: "owner@example.com",
245
+ }),
246
+ makeServiceAuthContext(),
247
+ );
248
+
249
+ expect(res.status).toBe(200);
250
+ const body = (await res.json()) as {
251
+ ok: boolean;
252
+ contact: { channels: { type: string; status: string }[] };
253
+ };
254
+ const emailChannel = body.contact.channels.find(
255
+ (ch) => ch.type === "email",
256
+ );
257
+ expect(emailChannel?.status).toBe("active");
258
+ });
259
+
260
+ // ── Actor calls — all rejected (security fix ATL-102) ──────────────────
261
+
262
+ test("rejects actor calls with 403 (guardian takeover prevention)", async () => {
263
+ seedGuardian();
264
+
265
+ const res = await handleAddGuardianChannel(
266
+ makeRequest({
267
+ type: "email",
268
+ address: "owner@example.com",
269
+ externalUserId: "owner@example.com",
270
+ }),
271
+ makeActorAuthContext(),
272
+ );
273
+
274
+ expect(res.status).toBe(403);
275
+ const body = (await res.json()) as {
276
+ error: { code: string; message: string };
277
+ };
278
+ expect(body.error.code).toBe("FORBIDDEN");
279
+ expect(body.error.message).toContain("restricted to platform service");
280
+ });
281
+
282
+ test("rejects actor calls even from the bound guardian", async () => {
283
+ const guardianPrincipalId = "guardian-principal-001";
284
+ seedGuardian("Guardian", guardianPrincipalId);
285
+
286
+ // Caller IS the guardian — but actor calls are still rejected
287
+ const res = await handleAddGuardianChannel(
288
+ makeRequest({
289
+ type: "email",
290
+ address: "guardian@example.com",
291
+ externalUserId: "guardian@example.com",
292
+ }),
293
+ makeActorAuthContext({ actorPrincipalId: guardianPrincipalId }),
294
+ );
295
+
296
+ expect(res.status).toBe(403);
297
+ });
298
+ });
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Route handlers for contact management endpoints.
3
3
  *
4
- * GET /v1/contacts — list contacts
5
- * POST /v1/contacts — create or update a contact
6
- * GET /v1/contacts/:id — get a contact by ID
7
- * DELETE /v1/contacts/:id — delete a contact
8
- * POST /v1/contacts/merge — merge two contacts
4
+ * GET /v1/contacts — list contacts
5
+ * POST /v1/contacts — create or update a contact
6
+ * GET /v1/contacts/:id — get a contact by ID
7
+ * DELETE /v1/contacts/:id — delete a contact
8
+ * POST /v1/contacts/merge — merge two contacts
9
+ * POST /v1/contacts/guardian/channel — add a channel to the guardian contact
9
10
  * PATCH /v1/contact-channels/:contactChannelId — update a contact channel's status/policy
10
11
  */
11
12
 
@@ -13,6 +14,7 @@ import { z } from "zod";
13
14
 
14
15
  import {
15
16
  deleteContact,
17
+ findGuardianContact,
16
18
  getAssistantContactMetadata,
17
19
  getChannelById,
18
20
  getContact,
@@ -32,6 +34,7 @@ import type {
32
34
  ContactType,
33
35
  } from "../../contacts/types.js";
34
36
  import { resolveGuardianName } from "../../prompts/user-reference.js";
37
+ import type { AuthContext } from "../auth/types.js";
35
38
  import { httpError } from "../http-errors.js";
36
39
  import type { RouteDefinition } from "../http-router.js";
37
40
 
@@ -410,6 +413,100 @@ export async function handleUpdateContactChannel(
410
413
  });
411
414
  }
412
415
 
416
+ /**
417
+ * POST /v1/contacts/guardian/channel
418
+ *
419
+ * Add a single channel to the existing guardian contact.
420
+ * If no guardian contact exists, returns 404.
421
+ * If the caller is not the guardian, returns 403.
422
+ *
423
+ * Used by the guardian to auto-verify their own channel.
424
+ */
425
+ export async function handleAddGuardianChannel(
426
+ req: Request,
427
+ authContext: AuthContext,
428
+ ): Promise<Response> {
429
+ // This endpoint is restricted to gateway service tokens only — the
430
+ // platform calls it during email registration to auto-verify the owner's
431
+ // email as a guardian channel. Direct actor/local calls are not permitted
432
+ // because the endpoint bypasses normal channel verification (no code sent,
433
+ // no confirmation) and would allow guardian channel takeover (ATL-102).
434
+ if (authContext.principalType !== "svc_gateway") {
435
+ return httpError(
436
+ "FORBIDDEN",
437
+ "This endpoint is restricted to platform service calls",
438
+ 403,
439
+ );
440
+ }
441
+
442
+ const body = (await req.json()) as {
443
+ type: string;
444
+ address: string;
445
+ externalUserId?: string;
446
+ externalChatId?: string;
447
+ status?: string;
448
+ policy?: string;
449
+ };
450
+
451
+ if (!body.type || !body.address) {
452
+ return httpError("BAD_REQUEST", "type and address are required", 400);
453
+ }
454
+
455
+ if (!body.externalUserId) {
456
+ return httpError(
457
+ "BAD_REQUEST",
458
+ "externalUserId is required for trust resolution",
459
+ 400,
460
+ );
461
+ }
462
+
463
+ if (body.status !== undefined && !isChannelStatus(body.status)) {
464
+ return httpError(
465
+ "BAD_REQUEST",
466
+ `Invalid channel status "${body.status}". Must be one of: ${VALID_CHANNEL_STATUSES.join(", ")}`,
467
+ 400,
468
+ );
469
+ }
470
+
471
+ if (body.policy !== undefined && !isChannelPolicy(body.policy)) {
472
+ return httpError(
473
+ "BAD_REQUEST",
474
+ `Invalid channel policy "${body.policy}". Must be one of: ${VALID_CHANNEL_POLICIES.join(", ")}`,
475
+ 400,
476
+ );
477
+ }
478
+
479
+ const guardian = findGuardianContact();
480
+ if (!guardian) {
481
+ return httpError(
482
+ "NOT_FOUND",
483
+ "No guardian contact exists. The guardian must be verified on at least one channel first.",
484
+ 404,
485
+ );
486
+ }
487
+
488
+ // Upsert the guardian with the new channel added.
489
+ const updated = upsertContact({
490
+ id: guardian.id,
491
+ displayName: guardian.displayName,
492
+ role: "guardian",
493
+ channels: [
494
+ {
495
+ ...body,
496
+ status: (body.status as ChannelStatus) ?? "active",
497
+ policy: body.policy as ChannelPolicy | undefined,
498
+ verifiedAt: Date.now(),
499
+ verifiedVia: "guardian_channel_endpoint",
500
+ },
501
+ ],
502
+ });
503
+
504
+ return Response.json(
505
+ { ok: true, contact: withGuardianNameOverride(updated) },
506
+ { status: 200 },
507
+ );
508
+ }
509
+
413
510
  // ---------------------------------------------------------------------------
414
511
  // Route definitions
415
512
  // ---------------------------------------------------------------------------
@@ -476,6 +573,36 @@ export function contactRouteDefinitions(): RouteDefinition[] {
476
573
  }),
477
574
  handler: async ({ req }) => handleMergeContacts(req),
478
575
  },
576
+ {
577
+ endpoint: "contacts/guardian/channel",
578
+ method: "POST",
579
+ policyKey: "contacts",
580
+ summary: "Add a channel to the guardian contact",
581
+ description: "Used by the guardian to auto-verify their own channel.",
582
+ tags: ["contacts"],
583
+ requestBody: z.object({
584
+ type: z.string().describe("Channel type, e.g. 'email'"),
585
+ address: z
586
+ .string()
587
+ .describe("Channel address, e.g. 'user@example.com'"),
588
+ externalUserId: z
589
+ .string()
590
+ .describe("External user ID for trust resolution"),
591
+ status: z
592
+ .string()
593
+ .optional()
594
+ .describe("Channel status (default: active)"),
595
+ }),
596
+ responseBody: z.object({
597
+ ok: z.boolean(),
598
+ contact: z
599
+ .object({})
600
+ .passthrough()
601
+ .describe("Updated guardian contact"),
602
+ }),
603
+ handler: async ({ req, authContext }) =>
604
+ handleAddGuardianChannel(req, authContext),
605
+ },
479
606
  {
480
607
  endpoint: "contact-channels/:contactChannelId",
481
608
  method: "PATCH",
@@ -3,35 +3,22 @@
3
3
  *
4
4
  * POST /v1/conversations/:id/analyze — analyze a conversation via a new
5
5
  * agent loop that produces a structured self-assessment.
6
+ *
7
+ * The heavy lifting lives in `services/analyze-conversation.ts`. This module
8
+ * is thin glue: map the route params to the service, translate service
9
+ * errors into HTTP errors, and build the success response.
6
10
  */
7
11
 
8
- import type { ServerMessage } from "../../daemon/message-protocol.js";
9
- import {
10
- addMessage,
11
- createConversation,
12
- getConversation,
13
- getMessages,
14
- } from "../../memory/conversation-crud.js";
15
- import { resolveConversationId } from "../../memory/conversation-key-store.js";
16
- import { getLogger } from "../../util/logger.js";
17
- import { buildAssistantEvent } from "../assistant-event.js";
18
- import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
19
- import { httpError } from "../http-errors.js";
12
+ import { httpError, type HttpErrorCode } from "../http-errors.js";
20
13
  import type { RouteDefinition } from "../http-router.js";
21
- import type { SendMessageDeps } from "../http-types.js";
22
-
23
- const log = getLogger("conversation-analysis-routes");
24
-
25
- // ---------------------------------------------------------------------------
26
- // Dependency types — injected by the daemon at wiring time
27
- // ---------------------------------------------------------------------------
14
+ import {
15
+ analyzeConversation,
16
+ type ConversationAnalysisDeps,
17
+ } from "../services/analyze-conversation.js";
28
18
 
29
- export interface ConversationAnalysisDeps {
30
- sendMessageDeps: SendMessageDeps;
31
- buildConversationDetailResponse: (
32
- id: string,
33
- ) => Record<string, unknown> | null;
34
- }
19
+ // Re-export the dependency type so existing callers can continue importing it
20
+ // from this module.
21
+ export type { ConversationAnalysisDeps };
35
22
 
36
23
  // ---------------------------------------------------------------------------
37
24
  // Route definitions
@@ -50,132 +37,25 @@ export function conversationAnalysisRouteDefinitions(
50
37
  "Create a new conversation with a structured self-assessment of an existing conversation.",
51
38
  tags: ["conversations"],
52
39
  handler: async ({ params }) => {
53
- // a. Resolve conversation ID
54
- const resolvedId = resolveConversationId(params.id);
55
- if (!resolvedId) {
56
- return httpError(
57
- "NOT_FOUND",
58
- `Conversation ${params.id} not found`,
59
- 404,
60
- );
61
- }
62
-
63
- // b. Load the conversation
64
- const conversation = getConversation(resolvedId);
65
- if (!conversation) {
66
- return httpError(
67
- "NOT_FOUND",
68
- `Conversation ${resolvedId} not found`,
69
- 404,
70
- );
71
- }
72
-
73
- // c. Reject private conversations
74
- if (conversation.conversationType === "private") {
75
- return httpError(
76
- "FORBIDDEN",
77
- "Private conversations cannot be analyzed",
78
- 403,
79
- );
80
- }
40
+ const result = await analyzeConversation(params.id, deps, {
41
+ trigger: "manual",
42
+ });
81
43
 
82
- // d. Check for messages
83
- const existingMessages = getMessages(resolvedId);
84
- if (existingMessages.length === 0) {
44
+ if ("error" in result) {
85
45
  return httpError(
86
- "BAD_REQUEST",
87
- "Conversation has no messages to analyze",
88
- 400,
46
+ result.error.kind as HttpErrorCode,
47
+ result.error.message,
48
+ result.error.status,
89
49
  );
90
50
  }
91
51
 
92
- // e. Build the analysis transcript
93
- const { buildAnalysisTranscript } =
94
- await import("../../export/transcript-formatter.js");
95
- const transcript = buildAnalysisTranscript(resolvedId);
96
-
97
- // f. Create a new conversation for the analysis
98
- const newConv = createConversation({
99
- title: `Analysis: ${conversation.title ?? "Untitled"}`,
100
- });
101
-
102
- // g. Build the analysis prompt
103
- const prompt = `<transcript>
104
- ${transcript}
105
- </transcript>
106
-
107
- Analyze the conversation above. Provide a structured self-assessment:
108
-
109
- 1. **Summary**: What was the user trying to accomplish? What was the outcome?
110
- 2. **What went well**: Effective tool usage, good reasoning, helpful responses, problem-solving patterns.
111
- 3. **What went wrong**: Errors, unnecessary tool calls, incorrect assumptions, wasted turns, misunderstandings.
112
- 4. **Root causes**: Why did failures happen? Missing context? Wrong approach? Tool limitations?
113
- 5. **Recommendations**: Specific, actionable improvements for similar conversations next time.
114
- 6. **Code & tooling changes**: Are there any changes to files you should make based on these learnings? Are there any skills or scripts that are worth creating or modifying? Don't make these changes yet — just provide your analysis.
115
-
116
- Be honest and specific. Reference particular moments in the transcript. Focus on patterns that generalize beyond this specific conversation.
117
-
118
- Do not use tools during analysis. If you identify insights worth remembering for future conversations, include them in the response as explicit memory candidates instead of saving them directly.`;
119
-
120
- // h. Persist the user message
121
- const message = await addMessage(
122
- newConv.id,
123
- "user",
124
- JSON.stringify([{ type: "text", text: prompt }]),
125
- { provenanceTrustClass: "unknown" as const },
52
+ const detail = deps.buildConversationDetailResponse(
53
+ result.analysisConversationId,
126
54
  );
127
- const messageId = message.id;
128
-
129
- // i. Load the conversation into memory with untrusted analysis context
130
- const analysisConversation =
131
- await deps.sendMessageDeps.getOrCreateConversation(newConv.id);
132
- analysisConversation.setTrustContext({
133
- trustClass: "unknown",
134
- sourceChannel: "vellum",
135
- });
136
- await analysisConversation.ensureActorScopedHistory();
137
- // Analysis runs over attacker-influenced transcript content, so do not
138
- // expose any tools, even when a live client is available.
139
- analysisConversation.setSubagentAllowedTools(new Set<string>());
140
-
141
- const hasLiveSubscriber =
142
- deps.sendMessageDeps.assistantEventHub.hasSubscribersForEvent({
143
- assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
144
- conversationId: newConv.id,
145
- });
146
-
147
- // j. Build onEvent using inline hub publisher
148
- const onEvent = (msg: ServerMessage) => {
149
- deps.sendMessageDeps.assistantEventHub.publish(
150
- buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, msg, newConv.id),
151
- );
152
- };
153
- analysisConversation.updateClient(onEvent, !hasLiveSubscriber);
154
-
155
- // k. Set up processing state (required by runAgentLoop guard)
156
- analysisConversation.processing = true;
157
- analysisConversation.abortController = new AbortController();
158
- analysisConversation.currentRequestId = crypto.randomUUID();
159
-
160
- // l. Fire-and-forget the agent loop
161
- analysisConversation
162
- .runAgentLoop(prompt, messageId, onEvent, {
163
- isInteractive: false,
164
- isUserMessage: true,
165
- })
166
- .catch((err) => {
167
- log.error(
168
- { err, conversationId: newConv.id },
169
- "Analysis agent loop failed",
170
- );
171
- });
172
-
173
- // m. Return the new conversation detail
174
- const detail = deps.buildConversationDetailResponse(newConv.id);
175
55
  if (!detail) {
176
56
  return httpError(
177
57
  "INTERNAL_ERROR",
178
- `Analysis conversation ${newConv.id} could not be loaded`,
58
+ `Analysis conversation ${result.analysisConversationId} could not be loaded`,
179
59
  500,
180
60
  );
181
61
  }