@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
@@ -6,6 +6,7 @@ import type {
6
6
  Provider,
7
7
  } from "../providers/types.js";
8
8
  import { getLogger } from "../util/logger.js";
9
+ import { safeStringSlice } from "../util/unicode.js";
9
10
  import {
10
11
  estimateContentBlockTokens,
11
12
  estimatePromptTokens,
@@ -110,6 +111,16 @@ export class ContextWindowManager {
110
111
  * after a successful compaction pass.
111
112
  */
112
113
  nonPersistedPrefixCount = 0;
114
+ /**
115
+ * True when the message at index 0 is a context summary that was inherited
116
+ * from a parent fork (i.e. injected as part of the non-persisted prefix),
117
+ * rather than produced by this conversation's own compaction. The parent
118
+ * summary sits at index 0 but is excluded from `compactableMessages` by
119
+ * `summaryOffset`, so its slot in `nonPersistedPrefixCount` must be
120
+ * accounted for separately. Cleared after the first compaction replaces
121
+ * the parent summary with a child-owned one.
122
+ */
123
+ summaryIsInjected = false;
113
124
  /**
114
125
  * Cached resolved system prompt. Lazily populated on first access via the
115
126
  * `systemPrompt` getter and cleared after each compaction pass so the next
@@ -124,6 +135,16 @@ export class ContextWindowManager {
124
135
  this.toolTokenBudget = options.toolTokenBudget ?? 0;
125
136
  }
126
137
 
138
+ /**
139
+ * Provider key for the local token estimator. Wrapper providers (e.g.
140
+ * OpenRouter routing to `anthropic/*`) override `tokenEstimationProvider`
141
+ * so image/PDF sizing uses the same rules as the upstream API instead of
142
+ * the generic `base64/4` fallback.
143
+ */
144
+ private get estimationProviderName(): string {
145
+ return this.provider.tokenEstimationProvider ?? this.provider.name;
146
+ }
147
+
127
148
  /** Lazily resolve and cache the system prompt for the duration of a compaction pass. */
128
149
  private get systemPrompt(): string {
129
150
  if (this._resolvedSystemPrompt !== undefined) {
@@ -151,7 +172,7 @@ export class ContextWindowManager {
151
172
  if (!this.config.enabled) return { needed: false, estimatedTokens: 0 };
152
173
  try {
153
174
  const estimated = estimatePromptTokens(messages, this.systemPrompt, {
154
- providerName: this.provider.name,
175
+ providerName: this.estimationProviderName,
155
176
  toolTokenBudget: this.toolTokenBudget,
156
177
  });
157
178
  const threshold = Math.floor(
@@ -183,7 +204,7 @@ export class ContextWindowManager {
183
204
  const previousEstimatedInputTokens =
184
205
  options?.precomputedEstimate ??
185
206
  estimatePromptTokens(messages, this.systemPrompt, {
186
- providerName: this.provider.name,
207
+ providerName: this.estimationProviderName,
187
208
  toolTokenBudget: this.toolTokenBudget,
188
209
  });
189
210
  const thresholdTokens = Math.floor(
@@ -266,7 +287,7 @@ export class ContextWindowManager {
266
287
  const didTruncate = truncatedCount > 0;
267
288
  const estimatedAfterTruncation = didTruncate
268
289
  ? estimatePromptTokens(truncatedMessages, this.systemPrompt, {
269
- providerName: this.provider.name,
290
+ providerName: this.estimationProviderName,
270
291
  toolTokenBudget: this.toolTokenBudget,
271
292
  })
272
293
  : previousEstimatedInputTokens;
@@ -313,8 +334,15 @@ export class ContextWindowManager {
313
334
  };
314
335
  }
315
336
 
337
+ // When the summary at index 0 was injected from a parent fork, it
338
+ // contributes 1 to `nonPersistedPrefixCount` but is excluded from
339
+ // `compactableMessages` by `summaryOffset`; subtract it here so the
340
+ // remaining injected count lines up with compactableMessages. A summary
341
+ // produced by this conversation's own prior compaction is not part of
342
+ // `nonPersistedPrefixCount` (already decremented), so no subtraction.
343
+ const injectedSummaryOffset = this.summaryIsInjected ? summaryOffset : 0;
316
344
  const injectedInCompactable = Math.min(
317
- Math.max(0, this.nonPersistedPrefixCount - summaryOffset),
345
+ Math.max(0, this.nonPersistedPrefixCount - injectedSummaryOffset),
318
346
  compactableMessages.length,
319
347
  );
320
348
  const compactedPersistedMessages =
@@ -331,7 +359,7 @@ export class ContextWindowManager {
331
359
  projectedMessages,
332
360
  this.systemPrompt,
333
361
  {
334
- providerName: this.provider.name,
362
+ providerName: this.estimationProviderName,
335
363
  toolTokenBudget: this.toolTokenBudget,
336
364
  },
337
365
  );
@@ -460,15 +488,20 @@ export class ContextWindowManager {
460
488
  compactedMessages,
461
489
  this.systemPrompt,
462
490
  {
463
- providerName: this.provider.name,
491
+ providerName: this.estimationProviderName,
464
492
  toolTokenBudget: this.toolTokenBudget,
465
493
  },
466
494
  );
467
- // Consume the injected prefix messages that were compacted away.
495
+ // Consume the injected prefix messages that were compacted away. When the
496
+ // parent-injected summary was replaced by a freshly produced child summary,
497
+ // also consume its slot (it was excluded from injectedInCompactable via
498
+ // injectedSummaryOffset) and clear the flag so subsequent compactions treat
499
+ // the summary at index 0 as child-owned.
468
500
  this.nonPersistedPrefixCount = Math.max(
469
501
  0,
470
- this.nonPersistedPrefixCount - injectedInCompactable,
502
+ this.nonPersistedPrefixCount - injectedInCompactable - injectedSummaryOffset,
471
503
  );
504
+ this.summaryIsInjected = false;
472
505
 
473
506
  log.info(
474
507
  {
@@ -541,7 +574,7 @@ export class ContextWindowManager {
541
574
  COMPACTION_TOOL_RESULT_MAX_CHARS,
542
575
  );
543
576
  return estimatePromptTokens(projectedMessages, this.systemPrompt, {
544
- providerName: this.provider.name,
577
+ providerName: this.estimationProviderName,
545
578
  toolTokenBudget: this.toolTokenBudget,
546
579
  });
547
580
  };
@@ -610,7 +643,7 @@ export class ContextWindowManager {
610
643
  );
611
644
 
612
645
  const estimateBlockTokens = (b: ContentBlock): number =>
613
- estimateContentBlockTokens(b, { providerName: this.provider.name });
646
+ estimateContentBlockTokens(b, { providerName: this.estimationProviderName });
614
647
 
615
648
  let totalTokens = 0;
616
649
  for (const block of blocks) {
@@ -752,7 +785,7 @@ export class ContextWindowManager {
752
785
  // Budget in tokens → approximate char limit (4 chars ≈ 1 token).
753
786
  const maxChars = this.summaryMaxTokens * 4;
754
787
  if (summary.length <= maxChars) return summary;
755
- return `${summary.slice(0, maxChars)}...`;
788
+ return `${safeStringSlice(summary, 0, maxChars)}...`;
756
789
  }
757
790
  }
758
791
 
@@ -1025,7 +1058,7 @@ function serializeBlock(block: ContentBlock): string {
1025
1058
 
1026
1059
  function clampText(text: string): string {
1027
1060
  if (text.length <= MAX_BLOCK_PREVIEW_CHARS) return text;
1028
- return `${text.slice(0, MAX_BLOCK_PREVIEW_CHARS)}... [truncated ${
1061
+ return `${safeStringSlice(text, 0, MAX_BLOCK_PREVIEW_CHARS)}... [truncated ${
1029
1062
  text.length - MAX_BLOCK_PREVIEW_CHARS
1030
1063
  } chars]`;
1031
1064
  }
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  import { existsSync } from "node:fs";
20
- import { join } from "node:path";
20
+ import { dirname, join } from "node:path";
21
21
 
22
22
  import { getIsContainerized } from "../config/env-registry.js";
23
23
  import { getLogger } from "../util/logger.js";
@@ -65,9 +65,19 @@ function getManagedBootstrapSocketPath(): string {
65
65
  * `getDataDir()` (under `~/.vellum/workspace/data`) was previously included
66
66
  * but is inside the sandbox write boundary, so a sandboxed tool could plant
67
67
  * a malicious binary there. Removed to close the sandbox-escape vector.
68
+ *
69
+ * Search order:
70
+ * 1. Alongside the running executable (packaged macOS app:
71
+ * `<App>.app/Contents/MacOS/credential-executor`). When running from
72
+ * source via `bun run`, `process.execPath` points at the bun binary
73
+ * itself, so this path won't exist and the search falls through.
74
+ * 2. `<binDir>/credential-executor` — user-installed override (dev flow).
68
75
  */
69
76
  function getLocalBinarySearchPaths(): string[] {
70
- return [join(getBinDir(), "credential-executor")];
77
+ return [
78
+ join(dirname(process.execPath), "credential-executor"),
79
+ join(getBinDir(), "credential-executor"),
80
+ ];
71
81
  }
72
82
 
73
83
  // ---------------------------------------------------------------------------
@@ -240,7 +240,7 @@ export function createCesProcessManager(
240
240
  cmd: [discovery.executablePath],
241
241
  stdin: "pipe",
242
242
  stdout: "pipe",
243
- stderr: "ignore",
243
+ stderr: "pipe",
244
244
  env: {
245
245
  ...process.env,
246
246
  // Signal to CES that it was launched by the assistant
@@ -251,6 +251,7 @@ export function createCesProcessManager(
251
251
  childProcess = proc;
252
252
 
253
253
  log.info({ pid: proc.pid }, "CES child process started");
254
+ forwardStderrToLogger(proc);
254
255
 
255
256
  return createStdioTransport(proc);
256
257
  }
@@ -272,7 +273,7 @@ export function createCesProcessManager(
272
273
  cmd: [bunPath, "run", discovery.sourcePath],
273
274
  stdin: "pipe",
274
275
  stdout: "pipe",
275
- stderr: "ignore",
276
+ stderr: "pipe",
276
277
  env: {
277
278
  ...process.env,
278
279
  // Signal to CES that it was launched by the assistant
@@ -283,6 +284,7 @@ export function createCesProcessManager(
283
284
  childProcess = proc;
284
285
 
285
286
  log.info({ pid: proc.pid }, "CES child process started (from source)");
287
+ forwardStderrToLogger(proc);
286
288
 
287
289
  return createStdioTransport(proc);
288
290
  }
@@ -442,6 +444,35 @@ function createSocketTransport(socket: Socket): CesTransport {
442
444
  // Helpers
443
445
  // ---------------------------------------------------------------------------
444
446
 
447
+ function forwardStderrToLogger(proc: Subprocess): void {
448
+ if (!proc.stderr || typeof proc.stderr === "number") return;
449
+
450
+ const reader = proc.stderr.getReader();
451
+ const decoder = new TextDecoder();
452
+ let buffer = "";
453
+
454
+ void (async () => {
455
+ try {
456
+ while (true) {
457
+ const { value, done } = await reader.read();
458
+ if (done) break;
459
+
460
+ buffer += decoder.decode(value, { stream: true });
461
+ let newlineIdx: number;
462
+ while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
463
+ const line = buffer.slice(0, newlineIdx).trimEnd();
464
+ buffer = buffer.slice(newlineIdx + 1);
465
+ if (line) log.error({ pid: proc.pid }, `[ces-stderr] ${line}`);
466
+ }
467
+ }
468
+ const trailing = buffer.trimEnd();
469
+ if (trailing) log.error({ pid: proc.pid }, `[ces-stderr] ${trailing}`);
470
+ } catch {
471
+ // Process ended or stream closed; nothing to forward.
472
+ }
473
+ })();
474
+ }
475
+
445
476
  async function stopLocalProcess(proc: Subprocess): Promise<void> {
446
477
  log.info({ pid: proc.pid }, "Stopping CES child process");
447
478
  proc.kill("SIGTERM");
@@ -0,0 +1,366 @@
1
+ /**
2
+ * Proactive credential health monitoring.
3
+ *
4
+ * Enumerates all active OAuth connections and validates each one for:
5
+ * - Token presence in secure storage
6
+ * - Token expiry (expired or expiring within the warning window)
7
+ * - Scope coverage (grantedScopes vs provider defaultScopes)
8
+ * - Liveness ping (for providers with a pingUrl)
9
+ *
10
+ * Designed to run during the heartbeat cycle. All checks are diagnostic —
11
+ * no token refresh or recovery is attempted.
12
+ */
13
+
14
+ import {
15
+ isTokenExpired,
16
+ oauthConnectionAccessTokenPath,
17
+ } from "@vellumai/credential-storage";
18
+
19
+ import {
20
+ getProvider,
21
+ listActiveConnectionsByProvider,
22
+ listProviders,
23
+ } from "../oauth/oauth-store.js";
24
+ import { getSecureKeyAsync } from "../security/secure-keys.js";
25
+ import { getLogger } from "../util/logger.js";
26
+
27
+ const log = getLogger("credential-health");
28
+
29
+ /** 7 days in milliseconds — warn if token expires within this window. */
30
+ const EXPIRY_WARNING_MS = 7 * 24 * 60 * 60 * 1000;
31
+
32
+ /** Timeout for liveness pings. */
33
+ const PING_TIMEOUT_MS = 5_000;
34
+
35
+ // ── Types ─────────────────────────────────────────────────────────────
36
+
37
+ export type CredentialHealthStatus =
38
+ | "healthy"
39
+ | "expiring"
40
+ | "expired"
41
+ | "missing_token"
42
+ | "missing_scopes"
43
+ | "revoked"
44
+ | "ping_failed";
45
+
46
+ export interface CredentialHealthResult {
47
+ connectionId: string;
48
+ provider: string;
49
+ accountInfo: string | null;
50
+ status: CredentialHealthStatus;
51
+ details: string;
52
+ missingScopes: string[];
53
+ canAutoRecover: boolean;
54
+ }
55
+
56
+ export interface CredentialHealthReport {
57
+ checkedAt: number;
58
+ results: CredentialHealthResult[];
59
+ unhealthy: CredentialHealthResult[];
60
+ }
61
+
62
+ // ── Helpers ───────────────────────────────────────────────────────────
63
+
64
+ function safeJsonParse<T>(raw: string | null | undefined, fallback: T): T {
65
+ if (!raw) return fallback;
66
+ try {
67
+ return JSON.parse(raw) as T;
68
+ } catch {
69
+ return fallback;
70
+ }
71
+ }
72
+
73
+ function scopeDifference(required: string[], granted: string[]): string[] {
74
+ const grantedSet = new Set(granted);
75
+ return required.filter((s) => !grantedSet.has(s));
76
+ }
77
+
78
+ // ── Liveness ping ─────────────────────────────────────────────────────
79
+
80
+ /** @internal Exposed for test injection. */
81
+ export let _fetchFn: typeof fetch = fetch;
82
+
83
+ /** @internal Test-only: override the fetch function used for pings. */
84
+ export function _setFetchFn(fn: typeof fetch): void {
85
+ _fetchFn = fn;
86
+ }
87
+
88
+ async function pingProvider(
89
+ token: string,
90
+ pingUrl: string,
91
+ pingMethod: string | null,
92
+ pingHeaders: string | null,
93
+ pingBody: string | null,
94
+ ): Promise<{ ok: boolean; authError: boolean }> {
95
+ const method = pingMethod ?? "GET";
96
+ const headers: Record<string, string> = {
97
+ Authorization: `Bearer ${token}`,
98
+ ...safeJsonParse<Record<string, string>>(pingHeaders, {}),
99
+ };
100
+
101
+ const body =
102
+ method !== "GET" && pingBody
103
+ ? (typeof pingBody === "string" ? pingBody : JSON.stringify(pingBody))
104
+ : undefined;
105
+
106
+ try {
107
+ const response = await _fetchFn(pingUrl, {
108
+ method,
109
+ headers,
110
+ body,
111
+ signal: AbortSignal.timeout(PING_TIMEOUT_MS),
112
+ });
113
+
114
+ if (response.ok) return { ok: true, authError: false };
115
+ if (response.status === 401 || response.status === 403) {
116
+ return { ok: false, authError: true };
117
+ }
118
+ return { ok: false, authError: false };
119
+ } catch {
120
+ // Network error or timeout — treat as non-auth failure
121
+ return { ok: false, authError: false };
122
+ }
123
+ }
124
+
125
+ // ── Core check ────────────────────────────────────────────────────────
126
+
127
+ interface CheckConnectionOpts {
128
+ connectionId: string;
129
+ provider: string;
130
+ accountInfo: string | null;
131
+ expiresAt: number | null;
132
+ hasRefreshToken: boolean;
133
+ grantedScopesRaw: string;
134
+ defaultScopesRaw: string;
135
+ pingUrl: string | null;
136
+ pingMethod: string | null;
137
+ pingHeaders: string | null;
138
+ pingBody: string | null;
139
+ }
140
+
141
+ async function checkConnection(
142
+ opts: CheckConnectionOpts,
143
+ ): Promise<CredentialHealthResult> {
144
+ const {
145
+ connectionId,
146
+ provider,
147
+ accountInfo,
148
+ expiresAt,
149
+ hasRefreshToken,
150
+ grantedScopesRaw,
151
+ defaultScopesRaw,
152
+ pingUrl,
153
+ pingMethod,
154
+ pingHeaders,
155
+ pingBody,
156
+ } = opts;
157
+
158
+ const base = { connectionId, provider, accountInfo, missingScopes: [] as string[] };
159
+
160
+ // 1. Check token presence
161
+ const token = await getSecureKeyAsync(
162
+ oauthConnectionAccessTokenPath(connectionId),
163
+ );
164
+ if (!token) {
165
+ return {
166
+ ...base,
167
+ status: "missing_token",
168
+ details: `No access token found for ${provider}. Re-authorization required.`,
169
+ canAutoRecover: false,
170
+ };
171
+ }
172
+
173
+ // 2. Check token expiry
174
+ if (isTokenExpired(expiresAt)) {
175
+ return {
176
+ ...base,
177
+ status: hasRefreshToken ? "expiring" : "expired",
178
+ details: hasRefreshToken
179
+ ? `Token for ${provider} is expired but has a refresh token — auto-recovery may work.`
180
+ : `Token for ${provider} is expired with no refresh token. Re-authorization required.`,
181
+ canAutoRecover: hasRefreshToken,
182
+ };
183
+ }
184
+
185
+ // Check if expiring within warning window (but not yet expired by the 5-min buffer)
186
+ if (expiresAt && Date.now() >= expiresAt - EXPIRY_WARNING_MS) {
187
+ // Token works now but will expire soon
188
+ if (!hasRefreshToken) {
189
+ return {
190
+ ...base,
191
+ status: "expiring",
192
+ details: `Token for ${provider} expires within 7 days and has no refresh token. Re-authorization will be needed soon.`,
193
+ canAutoRecover: false,
194
+ };
195
+ }
196
+ // Has refresh token — not an issue, auto-refresh will handle it
197
+ }
198
+
199
+ // 3. Check scope coverage
200
+ const grantedScopes = safeJsonParse<string[]>(grantedScopesRaw, []);
201
+ const defaultScopes = safeJsonParse<string[]>(defaultScopesRaw, []);
202
+ if (defaultScopes.length > 0 && grantedScopes.length > 0) {
203
+ const missing = scopeDifference(defaultScopes, grantedScopes);
204
+ if (missing.length > 0) {
205
+ return {
206
+ ...base,
207
+ status: "missing_scopes",
208
+ details: `${provider} is missing required scopes: ${missing.join(", ")}. Features may not work correctly.`,
209
+ missingScopes: missing,
210
+ canAutoRecover: false,
211
+ };
212
+ }
213
+ }
214
+
215
+ // 4. Liveness ping
216
+ if (pingUrl) {
217
+ const pingResult = await pingProvider(
218
+ token,
219
+ pingUrl,
220
+ pingMethod,
221
+ pingHeaders,
222
+ pingBody,
223
+ );
224
+ if (!pingResult.ok) {
225
+ if (pingResult.authError) {
226
+ return {
227
+ ...base,
228
+ status: "revoked",
229
+ details: `${provider} token was rejected (401/403). The token may have been revoked. Re-authorization required.`,
230
+ canAutoRecover: false,
231
+ };
232
+ }
233
+ // Non-auth ping failure — log but don't mark as critical.
234
+ // Could be a transient API issue.
235
+ log.debug(
236
+ { provider, connectionId },
237
+ "Credential ping failed with non-auth error",
238
+ );
239
+ return {
240
+ ...base,
241
+ status: "ping_failed",
242
+ details: `${provider} liveness check failed (non-auth error). This may be transient.`,
243
+ canAutoRecover: false,
244
+ };
245
+ }
246
+ }
247
+
248
+ return {
249
+ ...base,
250
+ status: "healthy",
251
+ details: `${provider} credential is healthy.`,
252
+ canAutoRecover: hasRefreshToken,
253
+ };
254
+ }
255
+
256
+ // ── Public API ────────────────────────────────────────────────────────
257
+
258
+ /**
259
+ * Check the health of all active OAuth connections.
260
+ *
261
+ * Iterates every registered provider, looks up active connections, and
262
+ * validates each one. Returns a structured report with overall results
263
+ * and a filtered list of unhealthy credentials.
264
+ */
265
+ export async function checkAllCredentials(): Promise<CredentialHealthReport> {
266
+ const checkedAt = Date.now();
267
+ const results: CredentialHealthResult[] = [];
268
+
269
+ let providers;
270
+ try {
271
+ providers = listProviders();
272
+ } catch (err) {
273
+ log.warn({ err }, "Failed to list OAuth providers");
274
+ return { checkedAt, results, unhealthy: [] };
275
+ }
276
+
277
+ for (const providerRow of providers) {
278
+ let connections;
279
+ try {
280
+ connections = listActiveConnectionsByProvider(providerRow.provider);
281
+ } catch (err) {
282
+ log.warn(
283
+ { err, provider: providerRow.provider },
284
+ "Failed to list connections for provider",
285
+ );
286
+ continue;
287
+ }
288
+
289
+ for (const conn of connections) {
290
+ try {
291
+ const result = await checkConnection({
292
+ connectionId: conn.id,
293
+ provider: conn.provider,
294
+ accountInfo: conn.accountInfo,
295
+ expiresAt: conn.expiresAt,
296
+ hasRefreshToken: !!conn.hasRefreshToken,
297
+ grantedScopesRaw: conn.grantedScopes,
298
+ defaultScopesRaw: providerRow.defaultScopes,
299
+ pingUrl: providerRow.pingUrl,
300
+ pingMethod: providerRow.pingMethod,
301
+ pingHeaders: providerRow.pingHeaders,
302
+ pingBody: providerRow.pingBody,
303
+ });
304
+ results.push(result);
305
+ } catch (err) {
306
+ log.warn(
307
+ { err, provider: conn.provider, connectionId: conn.id },
308
+ "Failed to check credential health",
309
+ );
310
+ }
311
+ }
312
+ }
313
+
314
+ const unhealthy = results.filter((r) => r.status !== "healthy");
315
+ if (unhealthy.length > 0) {
316
+ log.info(
317
+ {
318
+ total: results.length,
319
+ unhealthy: unhealthy.length,
320
+ providers: [...new Set(unhealthy.map((r) => r.provider))],
321
+ },
322
+ "Credential health check found issues",
323
+ );
324
+ } else {
325
+ log.debug({ total: results.length }, "All credentials healthy");
326
+ }
327
+
328
+ return { checkedAt, results, unhealthy };
329
+ }
330
+
331
+ /**
332
+ * Check credential health for a single provider. Returns the health
333
+ * result for the most recent active connection, or null if no connection
334
+ * exists.
335
+ *
336
+ * Used by the watcher engine for pre-poll gating.
337
+ */
338
+ export async function checkCredentialForProvider(
339
+ provider: string,
340
+ ): Promise<CredentialHealthResult | null> {
341
+ let connections;
342
+ try {
343
+ connections = listActiveConnectionsByProvider(provider);
344
+ } catch {
345
+ return null;
346
+ }
347
+ if (connections.length === 0) return null;
348
+
349
+ const conn = connections[0]!;
350
+ const providerRow = getProvider(conn.provider);
351
+ if (!providerRow) return null;
352
+
353
+ return checkConnection({
354
+ connectionId: conn.id,
355
+ provider: conn.provider,
356
+ accountInfo: conn.accountInfo,
357
+ expiresAt: conn.expiresAt,
358
+ hasRefreshToken: !!conn.hasRefreshToken,
359
+ grantedScopesRaw: conn.grantedScopes,
360
+ defaultScopesRaw: providerRow.defaultScopes,
361
+ pingUrl: providerRow.pingUrl,
362
+ pingMethod: providerRow.pingMethod,
363
+ pingHeaders: providerRow.pingHeaders,
364
+ pingBody: providerRow.pingBody,
365
+ });
366
+ }