@vellumai/assistant 0.8.4 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (438) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/docs/browser-use-architecture-phase2.md +1 -1
  3. package/knip.json +2 -1
  4. package/openapi.yaml +809 -11
  5. package/package.json +1 -1
  6. package/src/__tests__/anthropic-provider.test.ts +34 -37
  7. package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
  8. package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
  9. package/src/__tests__/audit-log-rotation.test.ts +70 -16
  10. package/src/__tests__/background-workers-disk-pressure.test.ts +3 -3
  11. package/src/__tests__/btw-routes.test.ts +2 -3
  12. package/src/__tests__/call-controller.test.ts +0 -1
  13. package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
  14. package/src/__tests__/channel-guardian.test.ts +3 -3
  15. package/src/__tests__/checker.test.ts +6 -15
  16. package/src/__tests__/compaction-events.test.ts +1 -0
  17. package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
  18. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
  19. package/src/__tests__/computer-use-tools.test.ts +2 -4
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  21. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
  22. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop-overflow.test.ts +197 -2
  24. package/src/__tests__/conversation-agent-loop.test.ts +163 -122
  25. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
  26. package/src/__tests__/conversation-clear-safety.test.ts +25 -25
  27. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  29. package/src/__tests__/conversation-error.test.ts +31 -0
  30. package/src/__tests__/conversation-fork-crud.test.ts +178 -15
  31. package/src/__tests__/conversation-lifecycle.test.ts +52 -11
  32. package/src/__tests__/{conversation-load-cleaned-at.test.ts → conversation-load-history-stripped.test.ts} +13 -13
  33. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
  34. package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
  35. package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
  36. package/src/__tests__/conversation-skill-tools.test.ts +2 -5
  37. package/src/__tests__/conversation-store.test.ts +1 -1
  38. package/src/__tests__/conversation-sync-tags.test.ts +99 -32
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  40. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  41. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  42. package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
  43. package/src/__tests__/credential-execution-tools.test.ts +6 -6
  44. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  45. package/src/__tests__/credential-vault-unit.test.ts +2 -2
  46. package/src/__tests__/dynamic-page-surface.test.ts +2 -2
  47. package/src/__tests__/email-html-renderer.test.ts +12 -0
  48. package/src/__tests__/gateway-flag-listener.test.ts +237 -0
  49. package/src/__tests__/gemini-provider.test.ts +78 -0
  50. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  51. package/src/__tests__/guardian-outbound-http.test.ts +7 -5
  52. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  53. package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
  54. package/src/__tests__/heartbeat-service.test.ts +4 -0
  55. package/src/__tests__/host-shell-tool.test.ts +1 -1
  56. package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
  57. package/src/__tests__/list-messages-tool-merge.test.ts +70 -11
  58. package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
  59. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
  60. package/src/__tests__/llm-resolver.test.ts +77 -9
  61. package/src/__tests__/llm-usage-store.test.ts +66 -0
  62. package/src/__tests__/logger.test.ts +89 -0
  63. package/src/__tests__/mcp-abort-signal.test.ts +2 -2
  64. package/src/__tests__/media-generate-image.test.ts +31 -0
  65. package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
  66. package/src/__tests__/model-intents.test.ts +2 -4
  67. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  68. package/src/__tests__/onboarding-template-contract.test.ts +1 -1
  69. package/src/__tests__/openai-provider.test.ts +46 -0
  70. package/src/__tests__/openai-responses-provider.test.ts +114 -12
  71. package/src/__tests__/pending-interactions-resolved-event.test.ts +0 -1
  72. package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
  73. package/src/__tests__/platform.test.ts +2 -2
  74. package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
  75. package/src/__tests__/plugin-bootstrap.test.ts +2 -2
  76. package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
  77. package/src/__tests__/plugin-types.test.ts +3 -2
  78. package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
  79. package/src/__tests__/pricing.test.ts +12 -0
  80. package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
  81. package/src/__tests__/registry.test.ts +2 -8
  82. package/src/__tests__/require-fresh-approval.test.ts +2 -2
  83. package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
  84. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
  85. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  86. package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
  87. package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
  88. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  89. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  90. package/src/__tests__/suggestion-routes.test.ts +1 -0
  91. package/src/__tests__/sync-message-contract.test.ts +59 -0
  92. package/src/__tests__/system-prompt.test.ts +145 -131
  93. package/src/__tests__/terminal-tools.test.ts +1 -1
  94. package/src/__tests__/tool-approval-handler.test.ts +1 -5
  95. package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
  96. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
  98. package/src/__tests__/tool-executor.test.ts +9 -62
  99. package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
  100. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  101. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
  102. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  103. package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
  104. package/src/__tests__/usage-routes.test.ts +3 -0
  105. package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
  106. package/src/__tests__/workspace-git-service.test.ts +6 -5
  107. package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
  108. package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
  109. package/src/acp/prepare-agent-env.ts +78 -0
  110. package/src/acp/session-manager.ts +1 -1
  111. package/src/agent/loop.ts +8 -0
  112. package/src/api/README.md +5 -0
  113. package/src/api/index.ts +4 -0
  114. package/src/api/package.json +10 -0
  115. package/src/background-wake/background-wake-routes.test.ts +233 -0
  116. package/src/background-wake/runtime-registry.ts +24 -0
  117. package/src/cli/commands/__tests__/browser.test.ts +23 -5
  118. package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
  119. package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
  120. package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
  121. package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
  122. package/src/cli/commands/__tests__/memory-v2.test.ts +1 -0
  123. package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
  124. package/src/cli/commands/browser.ts +247 -0
  125. package/src/cli/commands/domain.ts +91 -41
  126. package/src/cli/commands/inference.ts +93 -40
  127. package/src/cli/commands/memory-v2-compare-render.ts +115 -0
  128. package/src/cli/commands/memory-v2.ts +176 -1
  129. package/src/cli/commands/memory-v3-render.ts +344 -0
  130. package/src/cli/commands/memory-v3.ts +316 -0
  131. package/src/cli/program.ts +2 -0
  132. package/src/config/assistant-feature-flags.ts +21 -9
  133. package/src/config/bundled-skills/document-editor/SKILL.md +11 -2
  134. package/src/config/bundled-skills/document-editor/TOOLS.json +18 -0
  135. package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
  136. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  137. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  138. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
  139. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
  140. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
  141. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
  142. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
  143. package/src/config/bundled-tool-registry.ts +2 -0
  144. package/src/config/call-site-defaults.ts +7 -6
  145. package/src/config/feature-flag-registry.json +16 -0
  146. package/src/config/schemas/__tests__/memory-v2.test.ts +213 -1
  147. package/src/config/schemas/call-site-catalog.ts +21 -7
  148. package/src/config/schemas/llm.ts +12 -1
  149. package/src/config/schemas/memory-v2.ts +246 -0
  150. package/src/config/schemas/memory.ts +2 -1
  151. package/src/context/compactor.ts +52 -0
  152. package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
  153. package/src/conversations/message-consolidation.ts +404 -0
  154. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
  155. package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
  156. package/src/daemon/conversation-agent-loop-handlers.ts +2 -13
  157. package/src/daemon/conversation-agent-loop.ts +126 -76
  158. package/src/daemon/conversation-error.ts +31 -1
  159. package/src/daemon/conversation-lifecycle.ts +27 -22
  160. package/src/daemon/conversation-runtime-assembly.ts +10 -9
  161. package/src/daemon/conversation-tool-setup.ts +63 -3
  162. package/src/daemon/conversation-usage.ts +2 -0
  163. package/src/daemon/conversation.ts +14 -29
  164. package/src/daemon/disk-pressure-guard.ts +14 -2
  165. package/src/daemon/handlers/config-model.test.ts +1 -0
  166. package/src/daemon/handlers/conversations.ts +11 -3
  167. package/src/daemon/host-browser-proxy.ts +5 -5
  168. package/src/daemon/host-cu-proxy.ts +4 -4
  169. package/src/daemon/host-file-proxy.ts +4 -4
  170. package/src/daemon/host-proxy-base.ts +4 -4
  171. package/src/daemon/host-transfer-proxy.ts +10 -10
  172. package/src/daemon/lifecycle.ts +23 -20
  173. package/src/daemon/meet-manifest-loader.ts +1 -7
  174. package/src/daemon/message-types/conversations.ts +6 -9
  175. package/src/daemon/message-types/home.ts +1 -13
  176. package/src/daemon/message-types/messages.ts +6 -14
  177. package/src/daemon/message-types/sync.ts +14 -0
  178. package/src/daemon/shutdown-handlers.ts +24 -5
  179. package/src/daemon/switch-inference-profile-tool.ts +52 -0
  180. package/src/daemon/tool-setup-types.ts +13 -0
  181. package/src/events/relationship-state-updated.ts +25 -0
  182. package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
  183. package/src/home/home-greeting.ts +0 -9
  184. package/src/home/suggested-prompts.ts +0 -9
  185. package/src/ipc/gateway-flag-listener.ts +123 -0
  186. package/src/ipc/skill-routes/registries.ts +8 -12
  187. package/src/memory/__tests__/db-async-query.test.ts +165 -0
  188. package/src/memory/__tests__/db-maintenance.test.ts +115 -0
  189. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
  190. package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
  191. package/src/memory/__tests__/memory-retrospective-job.test.ts +7 -0
  192. package/src/memory/auto-analysis-enqueue.ts +5 -1
  193. package/src/memory/conversation-crud.ts +71 -70
  194. package/src/memory/conversation-starters-cadence.ts +3 -1
  195. package/src/memory/conversation-title-service.ts +19 -3
  196. package/src/memory/db-async-query.ts +214 -0
  197. package/src/memory/db-init.ts +10 -0
  198. package/src/memory/db-maintenance.ts +30 -21
  199. package/src/memory/graph/bootstrap.ts +8 -1
  200. package/src/memory/graph/capability-seed.ts +7 -3
  201. package/src/memory/graph/conversation-graph-memory.ts +100 -17
  202. package/src/memory/graph/extraction.ts +1 -5
  203. package/src/memory/graph/graph-search.ts +7 -1
  204. package/src/memory/indexer.ts +28 -18
  205. package/src/memory/job-handlers/cleanup.ts +76 -18
  206. package/src/memory/job-handlers/conversation-starters.ts +1 -4
  207. package/src/memory/jobs/embed-pkb-file.ts +6 -1
  208. package/src/memory/jobs-store.ts +14 -0
  209. package/src/memory/jobs-worker.ts +55 -22
  210. package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
  211. package/src/memory/llm-request-log-source-local.ts +7 -0
  212. package/src/memory/llm-request-log-source.ts +9 -2
  213. package/src/memory/llm-request-log-store.ts +43 -1
  214. package/src/memory/llm-usage-store.ts +24 -0
  215. package/src/memory/memory-retrospective-enqueue.ts +8 -1
  216. package/src/memory/memory-retrospective-job.ts +5 -0
  217. package/src/memory/memory-v2-activation-log-store.ts +15 -6
  218. package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
  219. package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
  220. package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
  221. package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
  222. package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
  223. package/src/memory/migrations/index.ts +17 -0
  224. package/src/memory/migrations/registry.ts +33 -0
  225. package/src/memory/schema/conversations.ts +1 -1
  226. package/src/memory/schema/infrastructure.ts +21 -0
  227. package/src/memory/tool-usage-store.ts +36 -8
  228. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
  229. package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
  230. package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
  231. package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
  232. package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
  233. package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
  234. package/src/memory/v2/__tests__/injection.test.ts +127 -98
  235. package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
  236. package/src/memory/v2/__tests__/router.test.ts +171 -3
  237. package/src/memory/v2/harness/compare.ts +57 -0
  238. package/src/memory/v2/harness/metrics.ts +124 -0
  239. package/src/memory/v2/harness/oracle.ts +145 -0
  240. package/src/memory/v2/harness/replay-input.ts +224 -0
  241. package/src/memory/v2/harness/retriever.ts +74 -0
  242. package/src/memory/v2/harness/router-retriever.ts +43 -0
  243. package/src/memory/v2/harness/runner.ts +106 -0
  244. package/src/memory/v2/harness/trace.ts +58 -0
  245. package/src/memory/v2/injection.ts +21 -15
  246. package/src/memory/v2/prompts/router.ts +26 -1
  247. package/src/memory/v2/qdrant.ts +14 -2
  248. package/src/memory/v2/router.ts +171 -18
  249. package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
  250. package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
  251. package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
  252. package/src/memory/v3/__tests__/edges.test.ts +563 -0
  253. package/src/memory/v3/__tests__/filter.test.ts +512 -0
  254. package/src/memory/v3/__tests__/gate.test.ts +574 -0
  255. package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
  256. package/src/memory/v3/__tests__/loop.test.ts +530 -0
  257. package/src/memory/v3/__tests__/retriever.test.ts +226 -0
  258. package/src/memory/v3/__tests__/scouts.test.ts +440 -0
  259. package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
  260. package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
  261. package/src/memory/v3/__tests__/traversal.test.ts +469 -0
  262. package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
  263. package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
  264. package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
  265. package/src/memory/v3/__tests__/validate.test.ts +245 -0
  266. package/src/memory/v3/auto-edges.ts +223 -0
  267. package/src/memory/v3/coactivation-store.ts +124 -0
  268. package/src/memory/v3/consolidation-job.ts +323 -0
  269. package/src/memory/v3/edge-learning-job.ts +160 -0
  270. package/src/memory/v3/edges.ts +249 -0
  271. package/src/memory/v3/filter.ts +281 -0
  272. package/src/memory/v3/gate.ts +334 -0
  273. package/src/memory/v3/index-composition.ts +113 -0
  274. package/src/memory/v3/llm-capture.ts +46 -0
  275. package/src/memory/v3/loop.ts +382 -0
  276. package/src/memory/v3/maintenance.ts +144 -0
  277. package/src/memory/v3/prompt-context.ts +33 -0
  278. package/src/memory/v3/prompts/consolidation.ts +458 -0
  279. package/src/memory/v3/prompts/system-prompts.ts +196 -0
  280. package/src/memory/v3/retriever.ts +33 -0
  281. package/src/memory/v3/scouts.ts +420 -0
  282. package/src/memory/v3/shadow-middleware.ts +305 -0
  283. package/src/memory/v3/traversal.ts +206 -0
  284. package/src/memory/v3/tree-index.ts +237 -0
  285. package/src/memory/v3/tree-store.ts +394 -0
  286. package/src/memory/v3/tree-walk.ts +351 -0
  287. package/src/memory/v3/types.ts +65 -0
  288. package/src/memory/v3/validate.ts +300 -0
  289. package/src/notifications/adapters/macos.ts +18 -1
  290. package/src/notifications/adapters/platform.ts +1 -1
  291. package/src/notifications/decision-engine.ts +1 -4
  292. package/src/notifications/emit-signal.ts +29 -49
  293. package/src/permissions/prompter.ts +3 -3
  294. package/src/permissions/question-prompter.ts +5 -2
  295. package/src/permissions/secret-prompter.ts +2 -2
  296. package/src/plugin-api/index.ts +4 -0
  297. package/src/plugin-api/types.ts +7 -33
  298. package/src/plugins/defaults/index.ts +6 -0
  299. package/src/plugins/defaults/injectors.ts +18 -11
  300. package/src/plugins/external-plugin-loader.ts +5 -68
  301. package/src/plugins/types.ts +11 -16
  302. package/src/proactive-artifact/aux-message-injector.ts +17 -4
  303. package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
  304. package/src/prompts/persona-resolver.ts +36 -21
  305. package/src/prompts/sections.ts +39 -7
  306. package/src/prompts/system-prompt.ts +50 -185
  307. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  308. package/src/prompts/templates/system-sections.ts +230 -8
  309. package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
  310. package/src/providers/__tests__/retry-callsite.test.ts +85 -5
  311. package/src/providers/anthropic/client.ts +32 -66
  312. package/src/providers/call-site-routing.ts +14 -2
  313. package/src/providers/connection-model-compat.ts +38 -0
  314. package/src/providers/connection-resolution.ts +16 -2
  315. package/src/providers/gemini/client.ts +49 -6
  316. package/src/providers/inference/adapter-factory.ts +3 -0
  317. package/src/providers/minimax/client.ts +106 -0
  318. package/src/providers/model-catalog.ts +43 -0
  319. package/src/providers/model-intents.ts +1 -1
  320. package/src/providers/openai/chat-completions-provider.ts +6 -3
  321. package/src/providers/openai/codex-models.ts +18 -0
  322. package/src/providers/openai/responses-provider.ts +78 -21
  323. package/src/providers/provider-send-message.ts +7 -1
  324. package/src/providers/retry.ts +34 -3
  325. package/src/providers/thinking-config.ts +26 -1
  326. package/src/providers/usage-tracking.ts +2 -0
  327. package/src/runtime/AGENTS.md +2 -2
  328. package/src/runtime/agent-wake.ts +1 -0
  329. package/src/runtime/assistant-event-hub.ts +76 -6
  330. package/src/runtime/auth/route-policy.ts +36 -0
  331. package/src/runtime/btw-sidechain.ts +0 -6
  332. package/src/runtime/http-types.ts +0 -2
  333. package/src/runtime/migrations/vbundle-builder.ts +10 -3
  334. package/src/runtime/pending-interactions.ts +0 -1
  335. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +106 -0
  336. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +25 -6
  337. package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
  338. package/src/runtime/routes/acp-routes.test.ts +255 -6
  339. package/src/runtime/routes/acp-routes.ts +8 -1
  340. package/src/runtime/routes/avatar-routes.ts +10 -10
  341. package/src/runtime/routes/background-wake-routes.ts +188 -0
  342. package/src/runtime/routes/browser-tabs-routes.ts +200 -0
  343. package/src/runtime/routes/btw-routes.ts +0 -6
  344. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  345. package/src/runtime/routes/conversation-list-routes.ts +12 -4
  346. package/src/runtime/routes/conversation-management-routes.ts +77 -20
  347. package/src/runtime/routes/conversation-query-routes.ts +142 -36
  348. package/src/runtime/routes/conversation-routes.ts +252 -410
  349. package/src/runtime/routes/conversation-starter-routes.ts +6 -3
  350. package/src/runtime/routes/disk-pressure-routes.ts +1 -1
  351. package/src/runtime/routes/domain-routes.ts +60 -10
  352. package/src/runtime/routes/email-routes.ts +5 -2
  353. package/src/runtime/routes/events-routes.ts +54 -10
  354. package/src/runtime/routes/group-routes.ts +24 -8
  355. package/src/runtime/routes/host-browser-routes.ts +10 -2
  356. package/src/runtime/routes/host-cu-routes.ts +2 -2
  357. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
  358. package/src/runtime/routes/index.ts +8 -0
  359. package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
  360. package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
  361. package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
  362. package/src/runtime/routes/memory-item-routes.ts +8 -3
  363. package/src/runtime/routes/memory-v2-routes.ts +215 -5
  364. package/src/runtime/routes/memory-v3-routes.ts +316 -0
  365. package/src/runtime/routes/migration-routes.ts +21 -24
  366. package/src/runtime/routes/plugins-routes.ts +337 -0
  367. package/src/runtime/routes/rename-conversation-routes.ts +6 -2
  368. package/src/runtime/routes/secret-routes.ts +25 -5
  369. package/src/runtime/routes/settings-routes.ts +12 -11
  370. package/src/runtime/routes/slack-channel-routes.ts +5 -4
  371. package/src/runtime/routes/workspace-routes.ts +25 -10
  372. package/src/runtime/sync/resource-sync-events.ts +106 -38
  373. package/src/runtime/sync/sync-publisher.test.ts +49 -0
  374. package/src/runtime/sync/sync-publisher.ts +2 -1
  375. package/src/runtime/verification-outbound-actions.ts +73 -1
  376. package/src/telemetry/types.ts +12 -0
  377. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  378. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  379. package/src/tools/acp/spawn.test.ts +119 -0
  380. package/src/tools/acp/spawn.ts +15 -2
  381. package/src/tools/apps/definitions.ts +2 -8
  382. package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
  383. package/src/tools/ask-question/ask-question-tool.ts +38 -45
  384. package/src/tools/browser/__tests__/pinned-tabs.test.ts +70 -0
  385. package/src/tools/browser/browser-execution.ts +16 -3
  386. package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
  387. package/src/tools/browser/cdp-client/__tests__/types.test.ts +3 -0
  388. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +12 -0
  389. package/src/tools/browser/cdp-client/extension-cdp-client.ts +27 -1
  390. package/src/tools/browser/cdp-client/factory.ts +100 -17
  391. package/src/tools/browser/cdp-client/local-cdp-client.ts +12 -0
  392. package/src/tools/browser/cdp-client/types.ts +65 -0
  393. package/src/tools/browser/pinned-tabs.ts +96 -40
  394. package/src/tools/computer-use/definitions.ts +22 -78
  395. package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
  396. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
  397. package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
  398. package/src/tools/credentials/vault.ts +3 -9
  399. package/src/tools/document/document-tool.ts +59 -0
  400. package/src/tools/execution-target.ts +21 -23
  401. package/src/tools/executor.ts +6 -1
  402. package/src/tools/filesystem/edit.ts +3 -9
  403. package/src/tools/filesystem/list.ts +3 -9
  404. package/src/tools/filesystem/read.ts +3 -9
  405. package/src/tools/filesystem/write.ts +3 -9
  406. package/src/tools/host-filesystem/edit.ts +3 -9
  407. package/src/tools/host-filesystem/read.ts +3 -9
  408. package/src/tools/host-filesystem/transfer.ts +3 -9
  409. package/src/tools/host-filesystem/write.ts +3 -9
  410. package/src/tools/host-terminal/host-shell.ts +3 -9
  411. package/src/tools/mcp/mcp-tool-factory.ts +1 -8
  412. package/src/tools/memory/register.test.ts +1 -1
  413. package/src/tools/memory/register.ts +4 -9
  414. package/src/tools/network/web-fetch.ts +3 -9
  415. package/src/tools/network/web-search.ts +25 -32
  416. package/src/tools/registry.ts +7 -23
  417. package/src/tools/schema-transforms.ts +1 -1
  418. package/src/tools/skills/execute.ts +3 -9
  419. package/src/tools/skills/load.ts +3 -9
  420. package/src/tools/skills/skill-tool-factory.ts +1 -8
  421. package/src/tools/subagent/notify-parent.ts +3 -9
  422. package/src/tools/system/request-permission.ts +3 -9
  423. package/src/tools/terminal/shell.ts +3 -9
  424. package/src/tools/tool-defaults.ts +94 -0
  425. package/src/tools/types.ts +27 -98
  426. package/src/tools/ui-surface/definitions.ts +6 -22
  427. package/src/usage/pricing.ts +23 -0
  428. package/src/usage/types.ts +12 -0
  429. package/src/util/logger.ts +16 -7
  430. package/src/util/platform.ts +7 -2
  431. package/src/util/sqlite3-runtime.ts +65 -0
  432. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
  433. package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
  434. package/src/workspace/migrations/registry.ts +2 -0
  435. package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
  436. package/src/__tests__/message-complete-display-id.test.ts +0 -175
  437. package/src/daemon/query-complexity-router.ts +0 -75
  438. package/src/prompts/cache-boundary.ts +0 -8
@@ -4,6 +4,10 @@
4
4
  * Suites:
5
5
  * - POST /v1/acp/spawn — the three failure paths produced by
6
6
  * `resolveAcpAgent` (acp_disabled, unknown_agent, binary_not_found).
7
+ * - POST /v1/acp/spawn (env injection) — CLAUDE_CODE_OAUTH_TOKEN is read
8
+ * from the secure store under the canonical
9
+ * `credential/acp/claude_oauth_token` key (built by `credentialKey()`)
10
+ * and merged into `agentConfig.env` ONLY for the `claude` agent.
7
11
  * - DELETE /v1/acp/sessions?status=completed — the bulk-clear route that
8
12
  * wipes terminal-state rows (completed/failed/cancelled) from
9
13
  * `acp_session_history` while leaving running/initializing rows intact.
@@ -42,10 +46,21 @@ afterAll(() => {
42
46
  });
43
47
 
44
48
  // Stub `getAcpSessionManager` so the DELETE /:id tests can drive the
45
- // in-memory-status check without spawning real ACP processes. Stored in
46
- // a mutable map so individual tests can plant arbitrary states.
49
+ // in-memory-status check without spawning real ACP processes, and so the
50
+ // env-injection spawn tests can capture the `agentConfig` arg without
51
+ // launching a real subprocess. Stored in mutable state so individual tests
52
+ // can plant arbitrary states / inspect capture.
47
53
  const inMemoryStates = new Map<string, AcpSessionState>();
48
54
 
55
+ interface CapturedSpawn {
56
+ agent: string;
57
+ agentConfig: { env?: Record<string, string> };
58
+ task: string;
59
+ conversationId: string;
60
+ }
61
+
62
+ const capturedSpawns: CapturedSpawn[] = [];
63
+
49
64
  mock.module("../../acp/index.js", () => ({
50
65
  getAcpSessionManager: () => ({
51
66
  getStatus: (id?: string) => {
@@ -56,9 +71,27 @@ mock.module("../../acp/index.js", () => ({
56
71
  if (!state) throw new Error(`ACP session "${id}" not found`);
57
72
  return state;
58
73
  },
74
+ spawn: async (
75
+ agent: string,
76
+ agentConfig: { env?: Record<string, string> },
77
+ task: string,
78
+ _cwd: string | undefined,
79
+ conversationId: string,
80
+ ) => {
81
+ capturedSpawns.push({ agent, agentConfig, task, conversationId });
82
+ return { acpSessionId: "acp-test", protocolSessionId: "proto-test" };
83
+ },
59
84
  }),
60
85
  }));
61
86
 
87
+ // Stub secure-keys so env-injection tests can plant a known token (or
88
+ // absence). Driven via `secureKeyStore` per test in beforeEach.
89
+ const secureKeyStore = new Map<string, string>();
90
+
91
+ mock.module("../../security/secure-keys.js", () => ({
92
+ getSecureKeyAsync: async (key: string) => secureKeyStore.get(key),
93
+ }));
94
+
62
95
  import { eq } from "drizzle-orm";
63
96
 
64
97
  import { getDb, getSqlite } from "../../memory/db-connection.js";
@@ -79,6 +112,8 @@ function getSpawnHandler() {
79
112
  beforeEach(() => {
80
113
  config.setConfig({});
81
114
  which.setWhich((cmd) => `/usr/local/bin/${cmd}`);
115
+ capturedSpawns.length = 0;
116
+ secureKeyStore.clear();
82
117
  });
83
118
 
84
119
  // ---------------------------------------------------------------------------
@@ -146,6 +181,212 @@ describe("POST /v1/acp/spawn", () => {
146
181
  });
147
182
  });
148
183
 
184
+ // ---------------------------------------------------------------------------
185
+ // POST /v1/acp/spawn — CLAUDE_CODE_OAUTH_TOKEN env injection + preflight
186
+ //
187
+ // claude-agent-acp authenticates via CLAUDE_CODE_OAUTH_TOKEN. The route
188
+ // accepts the token from two provisioning routes:
189
+ // 1. Secure store under the canonical `credential/acp/claude_oauth_token`
190
+ // key (built by `credentialKey()`), populated by
191
+ // `assistant credentials set --service acp --field claude_oauth_token`.
192
+ // 2. `acp.agents.claude.env.CLAUDE_CODE_OAUTH_TOKEN` in the user's
193
+ // config.json, surfaced on `resolved.agent.env` by the resolver.
194
+ // After merging the secure-store value into `agentConfig.env`, the route
195
+ // preflights for the token and throws `FailedDependencyError` if it is
196
+ // still absent. The "fail-fast" behavior is symmetric with the existing
197
+ // `binary_not_found` preflight and avoids the zombie-subprocess footgun
198
+ // where claude-agent-acp launches, crashes on auth, and leaves the
199
+ // caller with no useful signal.
200
+ //
201
+ // These tests pin both the happy paths and the throw path so a future
202
+ // drift in the key path, the env-override route, or the preflight check
203
+ // fails the suite loudly.
204
+ // ---------------------------------------------------------------------------
205
+
206
+ describe("POST /v1/acp/spawn — CLAUDE_CODE_OAUTH_TOKEN injection", () => {
207
+ test("injects CLAUDE_CODE_OAUTH_TOKEN from credential/acp/claude_oauth_token for the claude agent", async () => {
208
+ secureKeyStore.set(
209
+ "credential/acp/claude_oauth_token",
210
+ "test-token-abc123",
211
+ );
212
+
213
+ const handler = getSpawnHandler();
214
+ await handler({
215
+ body: {
216
+ agent: "claude",
217
+ task: "do a thing",
218
+ conversationId: "conv-1",
219
+ },
220
+ });
221
+
222
+ expect(capturedSpawns).toHaveLength(1);
223
+ expect(capturedSpawns[0]?.agent).toBe("claude");
224
+ expect(capturedSpawns[0]?.agentConfig.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe(
225
+ "test-token-abc123",
226
+ );
227
+ });
228
+
229
+ test("accepts CLAUDE_CODE_OAUTH_TOKEN from acp.agents.claude.env (config.json override) without a secure-store entry", async () => {
230
+ // The user-supplied config.json env override is the first-priority
231
+ // provisioning route. resolveAcpAgent returns it on `resolved.agent.env`,
232
+ // which the route then preserves on `agentConfig.env`. The preflight
233
+ // should accept this path with no secure-store entry needed.
234
+ config.setConfig({
235
+ agents: {
236
+ claude: {
237
+ command: "claude-agent-acp",
238
+ args: [],
239
+ env: { CLAUDE_CODE_OAUTH_TOKEN: "config-token-xyz789" },
240
+ },
241
+ },
242
+ });
243
+
244
+ const handler = getSpawnHandler();
245
+ await handler({
246
+ body: {
247
+ agent: "claude",
248
+ task: "do a thing",
249
+ conversationId: "conv-1",
250
+ },
251
+ });
252
+
253
+ expect(capturedSpawns).toHaveLength(1);
254
+ expect(capturedSpawns[0]?.agentConfig.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe(
255
+ "config-token-xyz789",
256
+ );
257
+ });
258
+
259
+ test("config.json env override wins over a secure-store token (precedence pin)", async () => {
260
+ // Codex review feedback (PR #31901 / P2): when a user explicitly sets
261
+ // CLAUDE_CODE_OAUTH_TOKEN under `acp.agents.<id>.env` (per-workspace,
262
+ // rotated, scoped credential, etc.), the secure-store value must NOT
263
+ // silently overwrite it. Vault is fallback, not override.
264
+ secureKeyStore.set("credential/acp/claude_oauth_token", "vault-token-AAA");
265
+ config.setConfig({
266
+ agents: {
267
+ claude: {
268
+ command: "claude-agent-acp",
269
+ args: [],
270
+ env: { CLAUDE_CODE_OAUTH_TOKEN: "config-token-BBB" },
271
+ },
272
+ },
273
+ });
274
+
275
+ const handler = getSpawnHandler();
276
+ await handler({
277
+ body: {
278
+ agent: "claude",
279
+ task: "do a thing",
280
+ conversationId: "conv-1",
281
+ },
282
+ });
283
+
284
+ expect(capturedSpawns).toHaveLength(1);
285
+ expect(capturedSpawns[0]?.agentConfig.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe(
286
+ "config-token-BBB",
287
+ );
288
+ });
289
+
290
+ test("injects via command match for a user-defined agent id aliased to claude-agent-acp", async () => {
291
+ // Codex review feedback (PR #31901 / P2): gating is keyed off the
292
+ // resolved command (basename), not the agent id. A custom agent id
293
+ // pointing at claude-agent-acp still needs CLAUDE_CODE_OAUTH_TOKEN,
294
+ // so injection + preflight must fire regardless of the id string.
295
+ secureKeyStore.set("credential/acp/claude_oauth_token", "vault-token-zzz");
296
+ config.setConfig({
297
+ agents: {
298
+ "my-claude": {
299
+ command: "claude-agent-acp",
300
+ args: [],
301
+ },
302
+ },
303
+ });
304
+
305
+ const handler = getSpawnHandler();
306
+ await handler({
307
+ body: {
308
+ agent: "my-claude",
309
+ task: "do a thing",
310
+ conversationId: "conv-1",
311
+ },
312
+ });
313
+
314
+ expect(capturedSpawns).toHaveLength(1);
315
+ expect(capturedSpawns[0]?.agent).toBe("my-claude");
316
+ expect(capturedSpawns[0]?.agentConfig.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe(
317
+ "vault-token-zzz",
318
+ );
319
+ });
320
+
321
+ test("throws FailedDependencyError when no CLAUDE_CODE_OAUTH_TOKEN is available from any source", async () => {
322
+ // secureKeyStore intentionally empty AND no agentConfig.env override —
323
+ // simulates a fresh install where the user hasn't provisioned a token
324
+ // via either route. Fail-fast preflight surfaces this immediately
325
+ // instead of letting claude-agent-acp launch, crash on auth, and leave
326
+ // a zombie subprocess behind.
327
+
328
+ const handler = getSpawnHandler();
329
+ await expect(
330
+ handler({
331
+ body: {
332
+ agent: "claude",
333
+ task: "do a thing",
334
+ conversationId: "conv-1",
335
+ },
336
+ }),
337
+ ).rejects.toThrow(/CLAUDE_CODE_OAUTH_TOKEN/);
338
+ expect(capturedSpawns).toHaveLength(0);
339
+ });
340
+
341
+ test("does NOT inject CLAUDE_CODE_OAUTH_TOKEN for agents whose command is not claude-agent-acp", async () => {
342
+ // Token-injection AND preflight are scoped to claude-agent-acp by
343
+ // command basename. A codex-acp spawn with the secure-store key set
344
+ // must still launch without that env var — and must not be blocked
345
+ // by claude's preflight.
346
+ secureKeyStore.set(
347
+ "credential/acp/claude_oauth_token",
348
+ "test-token-abc123",
349
+ );
350
+
351
+ const handler = getSpawnHandler();
352
+ await handler({
353
+ body: {
354
+ agent: "codex",
355
+ task: "do a thing",
356
+ conversationId: "conv-1",
357
+ },
358
+ });
359
+
360
+ expect(capturedSpawns).toHaveLength(1);
361
+ expect(capturedSpawns[0]?.agent).toBe("codex");
362
+ expect(
363
+ capturedSpawns[0]?.agentConfig.env?.CLAUDE_CODE_OAUTH_TOKEN,
364
+ ).toBeUndefined();
365
+ });
366
+
367
+ test("does NOT pick up a token planted at the legacy non-`credential/` key path", async () => {
368
+ // Regression guard: the original implementation used the raw key
369
+ // "acp/claude/oauth_token". The fix routes through `credentialKey()`
370
+ // so the CLI (`assistant credentials set --service acp --field
371
+ // claude_oauth_token`) is the canonical provisioning path. Pin this
372
+ // by planting the token ONLY under the legacy key — the preflight
373
+ // should fail-fast because the canonical path is empty.
374
+ secureKeyStore.set("acp/claude/oauth_token", "legacy-token-should-miss");
375
+
376
+ const handler = getSpawnHandler();
377
+ await expect(
378
+ handler({
379
+ body: {
380
+ agent: "claude",
381
+ task: "do a thing",
382
+ conversationId: "conv-1",
383
+ },
384
+ }),
385
+ ).rejects.toThrow(/CLAUDE_CODE_OAUTH_TOKEN/);
386
+ expect(capturedSpawns).toHaveLength(0);
387
+ });
388
+ });
389
+
149
390
  // ---------------------------------------------------------------------------
150
391
  // DELETE /v1/acp/sessions?status=completed — bulk-clear terminal rows
151
392
  // ---------------------------------------------------------------------------
@@ -205,7 +446,9 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
205
446
  seedHistoryRow("row-initializing", "initializing", 5000);
206
447
 
207
448
  const handler = getBulkDeleteHandler();
208
- const result = (await handler({ queryParams: { status: "completed" } })) as {
449
+ const result = (await handler({
450
+ queryParams: { status: "completed" },
451
+ })) as {
209
452
  deleted: number;
210
453
  };
211
454
  expect(result.deleted).toBe(3);
@@ -221,7 +464,9 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
221
464
  seedHistoryRow("row-running", "running", 1000);
222
465
 
223
466
  const handler = getBulkDeleteHandler();
224
- const result = (await handler({ queryParams: { status: "completed" } })) as {
467
+ const result = (await handler({
468
+ queryParams: { status: "completed" },
469
+ })) as {
225
470
  deleted: number;
226
471
  };
227
472
  expect(result.deleted).toBe(0);
@@ -244,7 +489,9 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
244
489
  seedHistoryRow("row-completed", "completed", 1000);
245
490
 
246
491
  const handler = getBulkDeleteHandler();
247
- expect(() => handler({ queryParams: { status: "failed" } })).toThrow("status");
492
+ expect(() => handler({ queryParams: { status: "failed" } })).toThrow(
493
+ "status",
494
+ );
248
495
  expect(listRows()).toHaveLength(1);
249
496
  });
250
497
  });
@@ -297,7 +544,9 @@ describe("DELETE /v1/acp/sessions/:id", () => {
297
544
  insertHistoryRow({ id: "sess-completed", status: "completed" });
298
545
 
299
546
  const handler = getDeleteSessionHandler();
300
- const result = (await handler({ pathParams: { id: "sess-completed" } })) as {
547
+ const result = (await handler({
548
+ pathParams: { id: "sess-completed" },
549
+ })) as {
301
550
  deleted: boolean;
302
551
  };
303
552
  expect(result.deleted).toBe(true);
@@ -8,6 +8,7 @@ import { desc, eq, inArray } from "drizzle-orm";
8
8
  import { z } from "zod";
9
9
 
10
10
  import { getAcpSessionManager } from "../../acp/index.js";
11
+ import { prepareAgentEnv } from "../../acp/prepare-agent-env.js";
11
12
  import { resolveAcpAgent } from "../../acp/resolve-agent.js";
12
13
  import type { AcpSessionState } from "../../acp/types.js";
13
14
  import { getDb } from "../../memory/db-connection.js";
@@ -81,6 +82,12 @@ async function spawnSession({ body }: RouteHandlerArgs) {
81
82
  }
82
83
  }
83
84
 
85
+ // Inject required env vars and preflight via the shared helper. See
86
+ // `acp/prepare-agent-env.ts` for the full rationale; calling it here
87
+ // keeps the HTTP route in lockstep with the skill-tool spawn path
88
+ // (`tools/acp/spawn.ts`).
89
+ const agentConfig = await prepareAgentEnv(resolved.agent);
90
+
84
91
  log.info(
85
92
  { agent, task: task.slice(0, 100), conversationId },
86
93
  "ACP spawn request received",
@@ -89,7 +96,7 @@ async function spawnSession({ body }: RouteHandlerArgs) {
89
96
  const manager = getAcpSessionManager();
90
97
  const { acpSessionId, protocolSessionId } = await manager.spawn(
91
98
  agent,
92
- resolved.agent,
99
+ agentConfig,
93
100
  task,
94
101
  cwd,
95
102
  conversationId,
@@ -40,7 +40,7 @@ function handleGetCharacterComponents() {
40
40
  return getCharacterComponents();
41
41
  }
42
42
 
43
- function handleRenderFromTraits({ body }: RouteHandlerArgs) {
43
+ function handleRenderFromTraits({ body, headers }: RouteHandlerArgs) {
44
44
  const traits = body as CharacterTraits | undefined;
45
45
 
46
46
  if (
@@ -69,11 +69,11 @@ function handleRenderFromTraits({ body }: RouteHandlerArgs) {
69
69
  }
70
70
 
71
71
  updateIdentityAvatarSection(null, log);
72
- publishAvatarChanged();
72
+ publishAvatarChanged(headers?.["x-vellum-client-id"]?.trim() || undefined);
73
73
  return { ok: true };
74
74
  }
75
75
 
76
- async function handleGenerateAvatar({ body }: RouteHandlerArgs) {
76
+ async function handleGenerateAvatar({ body, headers }: RouteHandlerArgs) {
77
77
  const description = (body as Record<string, unknown>)?.description as
78
78
  | string
79
79
  | undefined;
@@ -109,11 +109,11 @@ async function handleGenerateAvatar({ body }: RouteHandlerArgs) {
109
109
  }
110
110
 
111
111
  updateIdentityAvatarSection(null, log);
112
- publishAvatarChanged();
112
+ publishAvatarChanged(headers?.["x-vellum-client-id"]?.trim() || undefined);
113
113
  return { ok: true, message: result.content };
114
114
  }
115
115
 
116
- function handleSetAvatar({ body }: RouteHandlerArgs) {
116
+ function handleSetAvatar({ body, headers }: RouteHandlerArgs) {
117
117
  const imagePath = (body as Record<string, unknown>)?.imagePath as
118
118
  | string
119
119
  | undefined;
@@ -142,11 +142,11 @@ function handleSetAvatar({ body }: RouteHandlerArgs) {
142
142
  copyFileSync(normalized, avatarPath);
143
143
 
144
144
  updateIdentityAvatarSection(null, log);
145
- publishAvatarChanged();
145
+ publishAvatarChanged(headers?.["x-vellum-client-id"]?.trim() || undefined);
146
146
  return { ok: true };
147
147
  }
148
148
 
149
- function handleRemoveAvatar(_args: RouteHandlerArgs) {
149
+ function handleRemoveAvatar({ headers }: RouteHandlerArgs) {
150
150
  const avatarPath = getAvatarImagePath();
151
151
 
152
152
  if (!existsSync(avatarPath)) {
@@ -172,7 +172,7 @@ function handleRemoveAvatar(_args: RouteHandlerArgs) {
172
172
  "Default character avatar (no custom image set)",
173
173
  log,
174
174
  );
175
- publishAvatarChanged();
175
+ publishAvatarChanged(headers?.["x-vellum-client-id"]?.trim() || undefined);
176
176
  return { ok: true, hadAvatar: true };
177
177
  }
178
178
 
@@ -286,8 +286,8 @@ export const ROUTES: RouteDefinition[] = [
286
286
  operationId: "notify_avatar_updated",
287
287
  endpoint: "avatar/notify-updated",
288
288
  method: "POST",
289
- handler: () => {
290
- publishAvatarChanged();
289
+ handler: ({ headers }: RouteHandlerArgs) => {
290
+ publishAvatarChanged(headers?.["x-vellum-client-id"]?.trim() || undefined);
291
291
  return { ok: true };
292
292
  },
293
293
  summary: "Notify avatar updated",
@@ -0,0 +1,188 @@
1
+ import { z } from "zod";
2
+
3
+ import { computeNextBackgroundWakeIntent } from "../../background-wake/next-wake.js";
4
+ import { getBackgroundWakeRuntime } from "../../background-wake/runtime-registry.js";
5
+ import { BadRequestError, ServiceUnavailableError } from "./errors.js";
6
+ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
7
+
8
+ const PREPARE_SLEEP_DEFER_WINDOW_MS = 60_000;
9
+ const HEARTBEAT_DUE_TOLERANCE_MS = 1_000;
10
+
11
+ let computeWakeIntent = computeNextBackgroundWakeIntent;
12
+ const timestampInputSchema = z.union([z.number(), z.string()]);
13
+
14
+ const wakeIntentSchema = z
15
+ .object({
16
+ nextWakeAt: z.number(),
17
+ actualNextDueAt: z.number(),
18
+ reason: z.enum(["heartbeat", "schedule", "mixed"]),
19
+ sourceGeneration: z.string(),
20
+ computedAt: z.number(),
21
+ sourcePayload: z.record(z.string(), z.unknown()),
22
+ })
23
+ .nullable();
24
+
25
+ const normalizedDrainDueRequestSchema = z.object({
26
+ leaseId: z.string().min(1),
27
+ reason: z.string().min(1),
28
+ sourceGeneration: z.string().min(1),
29
+ startedAt: z.number(),
30
+ deadlineAt: z.number(),
31
+ });
32
+
33
+ const drainDueRequestSchema = z.union([
34
+ z.object({
35
+ leaseId: z.string().min(1),
36
+ reason: z.string().min(1),
37
+ sourceGeneration: z.string().min(1),
38
+ startedAt: timestampInputSchema,
39
+ deadlineAt: timestampInputSchema,
40
+ }),
41
+ z.object({
42
+ lease_id: z.string().min(1),
43
+ reason: z.string().min(1),
44
+ source_generation: z.string().min(1),
45
+ started_at: timestampInputSchema,
46
+ deadline_at: timestampInputSchema,
47
+ }),
48
+ ]);
49
+
50
+ function normalizeDrainDueBody(body: Record<string, unknown>) {
51
+ return {
52
+ leaseId: body.leaseId ?? body.lease_id,
53
+ reason: body.reason,
54
+ sourceGeneration: body.sourceGeneration ?? body.source_generation,
55
+ startedAt: normalizeTimestamp(body.startedAt ?? body.started_at),
56
+ deadlineAt: normalizeTimestamp(body.deadlineAt ?? body.deadline_at),
57
+ };
58
+ }
59
+
60
+ function normalizeTimestamp(value: unknown): unknown {
61
+ if (typeof value === "number") return value;
62
+ if (typeof value !== "string") return value;
63
+ const numeric = Number(value);
64
+ if (Number.isFinite(numeric)) return numeric;
65
+ const parsed = Date.parse(value);
66
+ return Number.isFinite(parsed) ? parsed : value;
67
+ }
68
+
69
+ function parseDrainDueBody(body: Record<string, unknown>) {
70
+ const parsed = normalizedDrainDueRequestSchema.safeParse(
71
+ normalizeDrainDueBody(body),
72
+ );
73
+ if (!parsed.success) {
74
+ throw new BadRequestError(
75
+ "leaseId, reason, sourceGeneration, startedAt, and deadlineAt are required",
76
+ );
77
+ }
78
+ return parsed.data;
79
+ }
80
+
81
+ function handleGetIntent() {
82
+ return { intent: computeWakeIntent() };
83
+ }
84
+
85
+ function handlePrepareSleep() {
86
+ const now = Date.now();
87
+ const intent = computeWakeIntent(now);
88
+ return {
89
+ intent,
90
+ deferSleep:
91
+ intent != null &&
92
+ intent.nextWakeAt <= now + PREPARE_SLEEP_DEFER_WINDOW_MS,
93
+ };
94
+ }
95
+
96
+ async function handleDrainDue(body: Record<string, unknown>) {
97
+ const request = parseDrainDueBody(body);
98
+ const runtime = getBackgroundWakeRuntime();
99
+ if (!runtime) {
100
+ throw new ServiceUnavailableError(
101
+ "Background wake runtime is not registered",
102
+ );
103
+ }
104
+
105
+ const now = Date.now();
106
+ const heartbeatDue =
107
+ runtime.heartbeat.nextRunAt != null &&
108
+ runtime.heartbeat.nextRunAt <= now + HEARTBEAT_DUE_TOLERANCE_MS;
109
+ const heartbeatRan = heartbeatDue ? await runtime.heartbeat.runOnce() : false;
110
+ const scheduledCount = await runtime.scheduler.runOnce();
111
+
112
+ return {
113
+ leaseId: request.leaseId,
114
+ reason: request.reason,
115
+ sourceGeneration: request.sourceGeneration,
116
+ startedAt: request.startedAt,
117
+ deadlineAt: request.deadlineAt,
118
+ counts: {
119
+ heartbeat: heartbeatRan ? 1 : 0,
120
+ scheduler: scheduledCount,
121
+ total: (heartbeatRan ? 1 : 0) + scheduledCount,
122
+ },
123
+ nextIntent: computeWakeIntent(),
124
+ };
125
+ }
126
+
127
+ /** @internal Test helper for route-only tests. */
128
+ export function setBackgroundWakeIntentComputerForTest(
129
+ nextCompute: typeof computeNextBackgroundWakeIntent | null,
130
+ ): void {
131
+ computeWakeIntent = nextCompute ?? computeNextBackgroundWakeIntent;
132
+ }
133
+
134
+ export const ROUTES: RouteDefinition[] = [
135
+ {
136
+ operationId: "getBackgroundWakeIntent",
137
+ endpoint: "background-wake/intent",
138
+ method: "GET",
139
+ policyKey: "background-wake",
140
+ summary: "Get background wake intent",
141
+ description: "Return the current computed background wake intent.",
142
+ tags: ["background-wake"],
143
+ responseBody: z.object({
144
+ intent: wakeIntentSchema,
145
+ }),
146
+ handler: () => handleGetIntent(),
147
+ },
148
+ {
149
+ operationId: "prepareBackgroundWakeSleep",
150
+ endpoint: "background-wake/prepare-sleep",
151
+ method: "POST",
152
+ policyKey: "background-wake",
153
+ summary: "Prepare for assistant sleep",
154
+ description:
155
+ "Return the current background wake intent and whether sleep should be deferred.",
156
+ tags: ["background-wake"],
157
+ responseBody: z.object({
158
+ intent: wakeIntentSchema,
159
+ deferSleep: z.boolean(),
160
+ }),
161
+ handler: () => handlePrepareSleep(),
162
+ },
163
+ {
164
+ operationId: "drainDueBackgroundWake",
165
+ endpoint: "background-wake/drain-due",
166
+ method: "POST",
167
+ policyKey: "background-wake",
168
+ summary: "Drain due background wake work",
169
+ description:
170
+ "Run due heartbeat and scheduler work for a background wake lease.",
171
+ tags: ["background-wake"],
172
+ requestBody: drainDueRequestSchema,
173
+ responseBody: z.object({
174
+ leaseId: z.string(),
175
+ reason: z.string(),
176
+ sourceGeneration: z.string(),
177
+ startedAt: z.number(),
178
+ deadlineAt: z.number(),
179
+ counts: z.object({
180
+ heartbeat: z.number(),
181
+ scheduler: z.number(),
182
+ total: z.number(),
183
+ }),
184
+ nextIntent: wakeIntentSchema,
185
+ }),
186
+ handler: ({ body }: RouteHandlerArgs) => handleDrainDue(body ?? {}),
187
+ },
188
+ ];