@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
@@ -287,7 +287,7 @@ describe("shell tool proxy mode", () => {
287
287
  });
288
288
 
289
289
  test("schema includes network_mode and credential_ids", () => {
290
- const def = shellTool.getDefinition();
290
+ const def = shellTool;
291
291
  const props = (def.input_schema as { properties: Record<string, unknown> })
292
292
  .properties;
293
293
  expect(props.network_mode).toBeDefined();
@@ -130,9 +130,9 @@ describe("isAssistantFeatureFlagEnabled with skillFlagKey", () => {
130
130
  // ---------------------------------------------------------------------------
131
131
 
132
132
  describe("isAssistantFeatureFlagEnabled", () => {
133
- test("returns true for unknown flags (open by default)", () => {
133
+ test("returns false for unknown flags (closed by default)", () => {
134
134
  const config = makeConfig();
135
- expect(isAssistantFeatureFlagEnabled("unknown", config)).toBe(true);
135
+ expect(isAssistantFeatureFlagEnabled("unknown", config)).toBe(false);
136
136
  });
137
137
 
138
138
  test("file-based override overrides registry default", () => {
@@ -50,7 +50,7 @@ mock.module("../config/assistant-feature-flags.js", () => ({
50
50
  isAssistantFeatureFlagEnabled: (key: string, _config: unknown): boolean => {
51
51
  const explicit = _mockOverrides[key];
52
52
  if (typeof explicit === "boolean") return explicit;
53
- return true; // undeclared flags default to enabled
53
+ return false; // undeclared flags default to disabled
54
54
  },
55
55
  clearFeatureFlagOverridesCache: () => {
56
56
  _mockOverrides = {};
@@ -121,15 +121,12 @@ mock.module("../tools/skills/skill-tool-factory.js", () => ({
121
121
  description: entry.description,
122
122
  category: entry.category,
123
123
  defaultRiskLevel: RiskLevel.Medium,
124
+ executionTarget: "sandbox" as const,
124
125
  origin: "skill" as const,
125
126
  ownerSkillId: skillId,
126
127
  ownerSkillVersionHash: versionHash,
127
128
  ownerSkillBundled: bundled ?? undefined,
128
- getDefinition: () => ({
129
- name: entry.name,
130
- description: entry.description,
131
- input_schema: entry.input_schema as object,
132
- }),
129
+ input_schema: entry.input_schema as object,
133
130
  execute: async () => ({ content: "", isError: false }),
134
131
  }));
135
132
  },
@@ -422,7 +419,7 @@ describe("projectSkillTools feature flag enforcement", () => {
422
419
  ];
423
420
  const prevActive = new Map<string, string>();
424
421
 
425
- // Declared skill is OFF, plain-skill is undeclared with no persisted override so remains ON.
422
+ // Declared skill is OFF; plain-skill has no featureFlag so remains ON.
426
423
  _setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
427
424
 
428
425
  const result = projectSkillTools(history, {
@@ -23,7 +23,7 @@ mock.module("../util/logger.js", () => ({
23
23
  }));
24
24
 
25
25
  // Mock config loader and feature flags to avoid filesystem reads on CI.
26
- // getConfig returns a minimal config; all feature flags default to enabled.
26
+ // The benchmark fixture treats every feature flag as enabled.
27
27
  mock.module("../config/loader.js", () => ({
28
28
  getConfig: () => ({}),
29
29
  loadConfig: () => ({}),
@@ -114,11 +114,7 @@ mock.module("../tools/skills/skill-tool-factory.js", () => ({
114
114
  ownerSkillId: skillId,
115
115
  ownerSkillVersionHash: versionHash,
116
116
  ownerSkillBundled: bundled,
117
- getDefinition: () => ({
118
- name: e.name,
119
- description: e.description,
120
- input_schema: e.input_schema,
121
- }),
117
+ input_schema: e.input_schema,
122
118
  execute: async () => ({ content: "", isError: false }),
123
119
  })),
124
120
  }));
@@ -145,7 +145,7 @@ describe("createSkillTool", () => {
145
145
  "v1:test",
146
146
  );
147
147
 
148
- const def = tool.getDefinition();
148
+ const def = tool;
149
149
 
150
150
  expect(def.name).toBe("web_scrape");
151
151
  expect(def.description).toBe("Scrape a URL");
@@ -146,7 +146,7 @@ function clearCaptured(): void {
146
146
 
147
147
  describe("notify_parent tool definition", () => {
148
148
  test("has correct core tool definition", () => {
149
- const def = notifyParentTool.getDefinition();
149
+ const def = notifyParentTool;
150
150
  const schema = def.input_schema as Record<string, unknown>;
151
151
  expect(def.name).toBe("notify_parent");
152
152
  expect(schema.required).toContain("message");
@@ -51,6 +51,7 @@ const mockGetMessages = mock((_conversationId: string) => [
51
51
 
52
52
  mock.module("../memory/conversation-crud.js", () => ({
53
53
  getMessages: mockGetMessages,
54
+ getConversation: (_id: string) => null,
54
55
  }));
55
56
 
56
57
  const mockGetConfiguredProvider = mock(async () => ({
@@ -60,4 +60,63 @@ describe("sync message contract", () => {
60
60
  }),
61
61
  ).toThrow();
62
62
  });
63
+
64
+ test("buildSyncChangedMessage includes originClientId when provided", () => {
65
+ const message = buildSyncChangedMessage(
66
+ [SYNC_TAGS.assistantAvatar],
67
+ "client-abc",
68
+ );
69
+
70
+ expect(message).toEqual({
71
+ type: "sync_changed",
72
+ tags: [SYNC_TAGS.assistantAvatar],
73
+ originClientId: "client-abc",
74
+ });
75
+ });
76
+
77
+ test("buildSyncChangedMessage omits originClientId when undefined", () => {
78
+ const message = buildSyncChangedMessage([SYNC_TAGS.assistantAvatar]);
79
+
80
+ expect(message).toEqual({
81
+ type: "sync_changed",
82
+ tags: [SYNC_TAGS.assistantAvatar],
83
+ });
84
+ expect("originClientId" in message).toBe(false);
85
+ });
86
+
87
+ test("buildSyncChangedMessage trims and drops blank originClientId", () => {
88
+ const blank = buildSyncChangedMessage(
89
+ [SYNC_TAGS.assistantAvatar],
90
+ " ",
91
+ );
92
+ expect("originClientId" in blank).toBe(false);
93
+
94
+ const trimmed = buildSyncChangedMessage(
95
+ [SYNC_TAGS.assistantAvatar],
96
+ " client-xyz ",
97
+ );
98
+ expect(trimmed).toEqual({
99
+ type: "sync_changed",
100
+ tags: [SYNC_TAGS.assistantAvatar],
101
+ originClientId: "client-xyz",
102
+ });
103
+ });
104
+
105
+ test("schema accepts a string originClientId and rejects non-string types", () => {
106
+ expect(() =>
107
+ SyncChangedMessageSchema.parse({
108
+ type: "sync_changed",
109
+ tags: [SYNC_TAGS.assistantAvatar],
110
+ originClientId: "client-abc",
111
+ }),
112
+ ).not.toThrow();
113
+
114
+ expect(() =>
115
+ SyncChangedMessageSchema.parse({
116
+ type: "sync_changed",
117
+ tags: [SYNC_TAGS.assistantAvatar],
118
+ originClientId: 42,
119
+ }),
120
+ ).toThrow();
121
+ });
63
122
  });
@@ -69,51 +69,37 @@ mock.module("../prompts/user-reference.js", () => ({
69
69
  resolveUserPronouns: () => null,
70
70
  }));
71
71
 
72
+ // Stub persona-resolver so tests can dictate the slug `buildSystemPrompt`
73
+ // sees without needing to write contact rows to the test DB. The user
74
+ // and channel persona files themselves now flow through bundled sections
75
+ // (`10-user-persona` / `11-channel-persona`) that read from disk, so
76
+ // persona *content* is exercised by writing the file under TEST_DIR
77
+ // rather than mocking it here. Tests mutate `mockPersona` in place;
78
+ // the default (all-null) matches a fresh workspace with no contacts
79
+ // and no `users/default.md`.
80
+ const mockPersona: {
81
+ userSlug: string | null;
82
+ guardianPersona: string | null;
83
+ } = { userSlug: null, guardianPersona: null };
84
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
85
+ const realPersonaResolver = require("../prompts/persona-resolver.js");
86
+ mock.module("../prompts/persona-resolver.js", () => ({
87
+ ...realPersonaResolver,
88
+ resolveUserSlug: () => mockPersona.userSlug,
89
+ resolveGuardianPersona: () => mockPersona.guardianPersona,
90
+ }));
91
+
72
92
  // Import after mock
73
- const {
74
- buildSystemPrompt,
75
- ensurePromptFiles,
76
- stripCommentLines,
77
- SYSTEM_PROMPT_CACHE_BOUNDARY,
78
- } = await import("../prompts/system-prompt.js");
79
-
80
- /**
81
- * Extract BOOTSTRAP.md content + the user persona from the dynamic block
82
- * of the system prompt, stripping configuration, skills catalog, and
83
- * connected services.
84
- *
85
- * Neither SOUL.md nor IDENTITY.md flows through this helper — they
86
- * render as the `09-soul` and `08-identity` workspace-backed sections in
87
- * the static (cached) prefix. Tests that assert on their content slice
88
- * the static block directly.
89
- */
90
- function basePrompt(result: string): string {
91
- // The workspace files are in the dynamic block after the cache boundary.
92
- const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
93
- let s =
94
- boundaryIdx >= 0
95
- ? result.slice(boundaryIdx + SYSTEM_PROMPT_CACHE_BOUNDARY.length)
96
- : result;
97
- for (const heading of [
98
- "## Configuration",
99
- "## Skills Catalog",
100
- "## External Communications Identity",
101
- "# Connected Services",
102
- "## Dynamic Skill Authoring Workflow",
103
- ]) {
104
- if (s.startsWith(heading)) {
105
- s = "";
106
- break;
107
- }
108
- const idx = s.indexOf(`\n\n${heading}`);
109
- if (idx !== -1) s = s.slice(0, idx);
110
- }
111
- return s;
112
- }
93
+ const { buildSystemPrompt, ensurePromptFiles, stripCommentLines } =
94
+ await import("../prompts/system-prompt.js");
113
95
 
114
96
  describe("buildSystemPrompt", () => {
115
97
  beforeEach(() => {
116
98
  mkdirSync(TEST_DIR, { recursive: true });
99
+ // Reset persona stub so each test starts from a fresh
100
+ // no-guardian baseline.
101
+ mockPersona.userSlug = null;
102
+ mockPersona.guardianPersona = null;
117
103
  });
118
104
 
119
105
  afterEach(() => {
@@ -123,8 +109,10 @@ describe("buildSystemPrompt", () => {
123
109
  "USER.md",
124
110
  "BOOTSTRAP.md",
125
111
  "UPDATES.md",
112
+ "VOICE.md",
126
113
  "skills",
127
114
  "users",
115
+ "channels",
128
116
  ]) {
129
117
  const p = join(TEST_DIR, name);
130
118
  if (existsSync(p)) rmSync(p, { recursive: true, force: true });
@@ -134,19 +122,11 @@ describe("buildSystemPrompt", () => {
134
122
  }
135
123
  });
136
124
 
137
- test("returns empty string when no files exist", () => {
138
- const result = buildSystemPrompt();
139
- expect(basePrompt(result)).toBe("");
140
- });
141
-
142
125
  test("uses SOUL.md when it exists", () => {
143
126
  writeFileSync(join(TEST_DIR, "SOUL.md"), "# My Soul\n\nBe awesome.");
144
127
  const result = buildSystemPrompt();
145
- // SOUL.md renders as the `09-soul` workspace-backed section in the
146
- // static (cached) prefix before SYSTEM_PROMPT_CACHE_BOUNDARY.
147
- const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
148
- expect(boundaryIdx).toBeGreaterThan(-1);
149
- expect(result.slice(0, boundaryIdx)).toContain("# My Soul\n\nBe awesome.");
128
+ // SOUL.md renders as the `09-soul` workspace-backed section.
129
+ expect(result).toContain("# My Soul\n\nBe awesome.");
150
130
  });
151
131
 
152
132
  test("uses IDENTITY.md when it exists", () => {
@@ -155,42 +135,40 @@ describe("buildSystemPrompt", () => {
155
135
  "# My Identity\n\nI am Vellum.",
156
136
  );
157
137
  const result = buildSystemPrompt();
158
- // IDENTITY.md renders as the `08-identity` workspace-backed section
159
- // in the static (cached) prefix before SYSTEM_PROMPT_CACHE_BOUNDARY.
160
- const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
161
- expect(boundaryIdx).toBeGreaterThan(-1);
162
- expect(result.slice(0, boundaryIdx)).toContain(
163
- "# My Identity\n\nI am Vellum.",
164
- );
138
+ // IDENTITY.md renders as the `08-identity` workspace-backed section.
139
+ expect(result).toContain("# My Identity\n\nI am Vellum.");
165
140
  });
166
141
 
167
142
  test("composes IDENTITY.md + SOUL.md when both exist", () => {
168
143
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), "# Identity\n\nI am Vellum.");
169
144
  writeFileSync(join(TEST_DIR, "SOUL.md"), "# Soul\n\nBe thoughtful.");
170
145
  const result = buildSystemPrompt();
171
- // Both render in the static (cached) prefix, IDENTITY before SOUL
172
- // (sections `08-identity` and `09-soul`).
173
- const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
174
- expect(boundaryIdx).toBeGreaterThan(-1);
175
- const staticBlock = result.slice(0, boundaryIdx);
176
- expect(staticBlock).toContain("# Identity\n\nI am Vellum.");
177
- expect(staticBlock).toContain("# Soul\n\nBe thoughtful.");
178
- const identityIdx = staticBlock.indexOf("# Identity\n\nI am Vellum.");
179
- const soulIdx = staticBlock.indexOf("# Soul\n\nBe thoughtful.");
146
+ // IDENTITY renders before SOUL (sections `08-identity` then
147
+ // `09-soul`).
148
+ expect(result).toContain("# Identity\n\nI am Vellum.");
149
+ expect(result).toContain("# Soul\n\nBe thoughtful.");
150
+ const identityIdx = result.indexOf("# Identity\n\nI am Vellum.");
151
+ const soulIdx = result.indexOf("# Soul\n\nBe thoughtful.");
180
152
  expect(identityIdx).toBeLessThan(soulIdx);
181
- expect(basePrompt(result)).toBe("");
182
153
  });
183
154
 
184
155
  test("ignores empty SOUL.md", () => {
185
156
  writeFileSync(join(TEST_DIR, "SOUL.md"), " \n \n ");
157
+ writeFileSync(join(TEST_DIR, "IDENTITY.md"), "# Identity\n\nI am Vellum.");
186
158
  const result = buildSystemPrompt();
187
- expect(basePrompt(result)).toBe("");
159
+ // IDENTITY renders but SOUL is gated off by the renderer's
160
+ // empty-body check; no SOUL content should appear.
161
+ expect(result).toContain("# Identity\n\nI am Vellum.");
162
+ expect(result).not.toContain(" \n \n ");
188
163
  });
189
164
 
190
165
  test("ignores empty IDENTITY.md", () => {
191
166
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), "");
167
+ writeFileSync(join(TEST_DIR, "SOUL.md"), "# Soul\n\nBe thoughtful.");
192
168
  const result = buildSystemPrompt();
193
- expect(basePrompt(result)).toBe("");
169
+ // SOUL renders but IDENTITY's empty file is gated off by the
170
+ // renderer's empty-body check.
171
+ expect(result).toContain("# Soul\n\nBe thoughtful.");
194
172
  });
195
173
 
196
174
  test("trims whitespace from file content", () => {
@@ -198,10 +176,8 @@ describe("buildSystemPrompt", () => {
198
176
  const result = buildSystemPrompt();
199
177
  // SOUL.md renders via the `09-soul` workspace-backed section;
200
178
  // stripCommentLines + trim run inside the section renderer.
201
- const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
202
- const staticBlock = result.slice(0, boundaryIdx);
203
- expect(staticBlock).toContain("Be kind");
204
- expect(staticBlock).not.toContain("\n Be kind \n");
179
+ expect(result).toContain("Be kind");
180
+ expect(result).not.toContain("\n Be kind \n");
205
181
  });
206
182
 
207
183
  test("does not include skills catalog in system prompt", () => {
@@ -274,9 +250,6 @@ describe("buildSystemPrompt", () => {
274
250
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), "Identity");
275
251
  writeFileSync(join(TEST_DIR, "SOUL.md"), "Soul");
276
252
  const result = buildSystemPrompt();
277
- // Both IDENTITY and SOUL render in the static (cached) prefix; the
278
- // dynamic block sliced by basePrompt is empty here.
279
- expect(basePrompt(result)).toBe("");
280
253
  expect(result).toContain("Identity");
281
254
  expect(result).toContain("Soul");
282
255
  });
@@ -292,54 +265,117 @@ describe("buildSystemPrompt", () => {
292
265
  const result = buildSystemPrompt();
293
266
  expect(result).not.toContain("stale user content");
294
267
  expect(result).toContain("Identity");
295
- expect(basePrompt(result)).toBe("");
296
268
  });
297
269
 
298
- test("uses options.userPersona instead of USER.md", () => {
270
+ test("includes resolved user persona in the static prefix", () => {
271
+ // User persona flows through the `10-user-persona` bundled section,
272
+ // which reads from `users/<userSlug>.md` (or `users/default.md` as
273
+ // a fallback). Set the slug + write the file to exercise both.
274
+ mockPersona.userSlug = "alice";
275
+ mkdirSync(join(TEST_DIR, "users"), { recursive: true });
276
+ writeFileSync(
277
+ join(TEST_DIR, "users", "alice.md"),
278
+ "# User persona\n\nName: Alice",
279
+ );
299
280
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), "Identity");
300
281
  writeFileSync(join(TEST_DIR, "SOUL.md"), "Soul");
301
- const result = buildSystemPrompt({
302
- userPersona: "# User persona\n\nName: Alice",
303
- });
304
- // IDENTITY and SOUL both render in the static (cached) prefix; only
305
- // the user persona ends up in the dynamic block.
306
- expect(basePrompt(result)).toBe("# User persona\n\nName: Alice");
282
+ const result = buildSystemPrompt();
283
+ // IDENTITY, SOUL, and the user persona all render as workspace-backed
284
+ // bundled sections in the assembled prompt.
307
285
  expect(result).toContain("Identity");
308
286
  expect(result).toContain("Soul");
287
+ expect(result).toContain("# User persona");
288
+ expect(result).toContain("Name: Alice");
289
+ });
290
+
291
+ test("user persona falls back to users/default.md when the slug's file is missing", () => {
292
+ // The `10-user-persona` section's workspacePath is
293
+ // `["users/{{userSlug}}.md", "users/default.md"]` — when the
294
+ // primary file doesn't exist the renderer falls through to default.
295
+ mockPersona.userSlug = "alice";
296
+ mkdirSync(join(TEST_DIR, "users"), { recursive: true });
297
+ writeFileSync(
298
+ join(TEST_DIR, "users", "default.md"),
299
+ "# Default persona\n\nNo contact bound.",
300
+ );
301
+ const result = buildSystemPrompt();
302
+ expect(result).toContain("# Default persona");
303
+ expect(result).toContain("No contact bound.");
304
+ });
305
+
306
+ test("includes channel persona from channels/<channelSlug>.md", () => {
307
+ // Channel persona flows through the `11-channel-persona` section.
308
+ // Default channel is "vellum" when no channelCapabilities passed.
309
+ mkdirSync(join(TEST_DIR, "channels"), { recursive: true });
310
+ writeFileSync(
311
+ join(TEST_DIR, "channels", "vellum.md"),
312
+ "# Channel persona\n\nThis is the Vellum channel.",
313
+ );
314
+ const result = buildSystemPrompt();
315
+ expect(result).toContain("# Channel persona");
316
+ expect(result).toContain("This is the Vellum channel.");
317
+ });
318
+
319
+ test("includes VOICE.md as the 12-voice section with prepended heading", () => {
320
+ // VOICE.md flows through the `12-voice` bundled section. The
321
+ // section transform prepends `# Voice Profile` so the file itself
322
+ // stays heading-free; the model writes voice markers as plain
323
+ // bullets / lines.
324
+ writeFileSync(
325
+ join(TEST_DIR, "VOICE.md"),
326
+ "- Prefers lowercase. Replies tightly. Skips greetings.",
327
+ );
328
+ const result = buildSystemPrompt();
329
+ expect(result).toContain("# Voice Profile");
330
+ expect(result).toContain("Prefers lowercase");
331
+ });
332
+
333
+ test("omits the 12-voice section when VOICE.md is missing", () => {
334
+ const result = buildSystemPrompt();
335
+ expect(result).not.toContain("# Voice Profile");
336
+ });
337
+
338
+ test("omits the 12-voice section when VOICE.md is empty / whitespace-only", () => {
339
+ writeFileSync(join(TEST_DIR, "VOICE.md"), " \n\n \n");
340
+ const result = buildSystemPrompt();
341
+ expect(result).not.toContain("# Voice Profile");
309
342
  });
310
343
 
311
344
  describe("BOOTSTRAP.md user persona placeholder", () => {
312
- test("substitutes {{USER_PERSONA_FILE}} with users/<slug>.md when userSlug is provided", () => {
345
+ test("substitutes {{userSlug}} with the resolved slug when a guardian slug is resolvable", () => {
346
+ // Simulate a guardian contact whose userFile resolves to alice.md.
347
+ mockPersona.userSlug = "alice";
313
348
  writeFileSync(
314
349
  join(TEST_DIR, "BOOTSTRAP.md"),
315
- "# First run\n\nSave facts to users/{{USER_PERSONA_FILE}} immediately.",
350
+ "# First run\n\nSave facts to users/{{userSlug}}.md immediately.",
316
351
  );
317
- const result = buildSystemPrompt({ userSlug: "alice" });
352
+ const result = buildSystemPrompt();
318
353
  expect(result).toContain("users/alice.md");
319
- expect(result).not.toContain("{{USER_PERSONA_FILE}}");
354
+ expect(result).not.toContain("{{userSlug}}");
320
355
  });
321
356
 
322
- test("falls back to users/default.md when userSlug is omitted", () => {
357
+ test("falls back to users/default.md when no slug is resolvable", () => {
323
358
  writeFileSync(
324
359
  join(TEST_DIR, "BOOTSTRAP.md"),
325
- "# First run\n\nSave facts to users/{{USER_PERSONA_FILE}} immediately.",
360
+ "# First run\n\nSave facts to users/{{userSlug}}.md immediately.",
326
361
  );
327
362
  const result = buildSystemPrompt();
328
363
  expect(result).toContain("users/default.md");
329
- expect(result).not.toContain("{{USER_PERSONA_FILE}}");
364
+ expect(result).not.toContain("{{userSlug}}");
330
365
  });
331
366
 
332
367
  test("substitutes the unmodified bundled BOOTSTRAP.md template", () => {
333
368
  // Copy the real bundled BOOTSTRAP.md into the test workspace so we
334
369
  // verify substitution against the actual template the daemon ships.
370
+ mockPersona.userSlug = "alice";
335
371
  const bundled = readFileSync(
336
372
  join(import.meta.dirname, "..", "prompts", "templates", "BOOTSTRAP.md"),
337
373
  "utf-8",
338
374
  );
339
375
  writeFileSync(join(TEST_DIR, "BOOTSTRAP.md"), bundled);
340
- const result = buildSystemPrompt({ userSlug: "alice" });
376
+ const result = buildSystemPrompt();
341
377
  expect(result).toContain("users/alice.md");
342
- expect(result).not.toContain("{{USER_PERSONA_FILE}}");
378
+ expect(result).not.toContain("{{userSlug}}");
343
379
  });
344
380
  });
345
381
 
@@ -513,8 +549,14 @@ describe("buildSystemPrompt", () => {
513
549
 
514
550
  test("file with only comment lines is treated as empty", () => {
515
551
  writeFileSync(join(TEST_DIR, "SOUL.md"), "_ All comments\n_ Nothing else");
552
+ writeFileSync(join(TEST_DIR, "IDENTITY.md"), "# Identity\n\nI am Vellum.");
516
553
  const result = buildSystemPrompt();
517
- expect(basePrompt(result)).toBe("");
554
+ // Comment-only SOUL.md gets stripped down to "" by
555
+ // `stripCommentLines` and is then gated off by the renderer's
556
+ // empty-body check; only IDENTITY contributes content here.
557
+ expect(result).toContain("# Identity\n\nI am Vellum.");
558
+ expect(result).not.toContain("_ All comments");
559
+ expect(result).not.toContain("_ Nothing else");
518
560
  });
519
561
 
520
562
  describe("workspace system prompt sections", () => {
@@ -547,11 +589,7 @@ describe("buildSystemPrompt", () => {
547
589
  );
548
590
  const result = buildSystemPrompt();
549
591
  expect(result.startsWith("You are operating in demo mode.")).toBe(true);
550
- // Prefix lives in the static (cached) block.
551
- const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
552
- expect(boundaryIdx).toBeGreaterThan(-1);
553
- const staticBlock = result.slice(0, boundaryIdx);
554
- expect(staticBlock).toContain("You are operating in demo mode.");
592
+ expect(result).toContain("You are operating in demo mode.");
555
593
  });
556
594
 
557
595
  test("workspace file without frontmatter is rendered as-is (always-on)", () => {
@@ -627,13 +665,10 @@ describe("buildSystemPrompt", () => {
627
665
  expect(result.startsWith("Custom prefix")).toBe(true);
628
666
  // IDENTITY.md renders via 08-identity in the static prefix after
629
667
  // the 00-prefix slot.
630
- const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
631
- const staticBlock = result.slice(0, boundaryIdx);
632
- const prefixIdx = staticBlock.indexOf("Custom prefix");
633
- const identityIdx = staticBlock.indexOf("I am Vellum.");
668
+ const prefixIdx = result.indexOf("Custom prefix");
669
+ const identityIdx = result.indexOf("I am Vellum.");
634
670
  expect(prefixIdx).toBeGreaterThan(-1);
635
671
  expect(identityIdx).toBeGreaterThan(prefixIdx);
636
- expect(basePrompt(result)).toBe("");
637
672
  });
638
673
 
639
674
  test("parallel tool calls section is sourced from workspace when present", () => {
@@ -809,11 +844,7 @@ describe("buildSystemPrompt", () => {
809
844
  expect(result).toContain(
810
845
  "Run `assistant --help` to discover commands.",
811
846
  );
812
- // Section lives in the static (cached) block.
813
- const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
814
- expect(boundaryIdx).toBeGreaterThan(-1);
815
- const staticBlock = result.slice(0, boundaryIdx);
816
- expect(staticBlock).toContain("## Assistant CLI");
847
+ expect(result).toContain("## Assistant CLI");
817
848
  });
818
849
 
819
850
  test("bundled cli-reference default renders when no workspace override", () => {
@@ -854,11 +885,7 @@ describe("buildSystemPrompt", () => {
854
885
  // The no-client body (em-dash separator after sandbox `bash`) must
855
886
  // not leak when the with-client variant is active.
856
887
  expect(result).not.toContain("install tools yourself; (2) browser");
857
- // Section lives in the static (cached) block.
858
- const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
859
- expect(boundaryIdx).toBeGreaterThan(-1);
860
- const staticBlock = result.slice(0, boundaryIdx);
861
- expect(staticBlock).toContain("## External Service Access");
888
+ expect(result).toContain("## External Service Access");
862
889
  });
863
890
 
864
891
  test("hasNoClient=true renders the two-tier (no host_bash) priority list", () => {
@@ -967,8 +994,7 @@ describe("buildSystemPrompt", () => {
967
994
 
968
995
  test("paired {{#flag}} + {{^flag}} acts as if/else", () => {
969
996
  // Use long unique markers — single letters collide with substrings
970
- // in the rest of the system prompt (e.g. "B" lives inside
971
- // SYSTEM_PROMPT_CACHE_BOUNDARY, "A" inside "API keys").
997
+ // in the rest of the system prompt (e.g. "A" inside "API keys").
972
998
  mkdirSync(SYSTEM_PROMPTS_DIR, { recursive: true });
973
999
  writeFileSync(
974
1000
  SECTION_FILE,
@@ -1033,11 +1059,7 @@ describe("buildSystemPrompt", () => {
1033
1059
  const result = buildSystemPrompt();
1034
1060
  expect(result).toContain("## Sending Files to the User");
1035
1061
  expect(result).toContain("Use the `<vellum-attachment />` tag.");
1036
- // Section lives in the static (cached) block.
1037
- const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
1038
- expect(boundaryIdx).toBeGreaterThan(-1);
1039
- const staticBlock = result.slice(0, boundaryIdx);
1040
- expect(staticBlock).toContain("## Sending Files to the User");
1062
+ expect(result).toContain("## Sending Files to the User");
1041
1063
  });
1042
1064
 
1043
1065
  test("renders after the cli-reference section to preserve original order", () => {
@@ -1083,11 +1105,7 @@ describe("buildSystemPrompt", () => {
1083
1105
  const result = buildSystemPrompt();
1084
1106
  expect(result).toContain("## Credential Security");
1085
1107
  expect(result).toContain("Workspace override marker BRAVO_TANGO_7.");
1086
- // Section lives in the static (cached) block.
1087
- const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
1088
- expect(boundaryIdx).toBeGreaterThan(-1);
1089
- const staticBlock = result.slice(0, boundaryIdx);
1090
- expect(staticBlock).toContain("## Credential Security");
1108
+ expect(result).toContain("## Credential Security");
1091
1109
  });
1092
1110
 
1093
1111
  test("bundled credential-security default renders when no workspace override", () => {
@@ -1127,11 +1145,7 @@ describe("buildSystemPrompt", () => {
1127
1145
  const result = buildSystemPrompt();
1128
1146
  expect(result).toContain("## External Content");
1129
1147
  expect(result).toContain("Workspace override marker NEBULA_9X.");
1130
- // Section lives in the static (cached) block.
1131
- const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
1132
- expect(boundaryIdx).toBeGreaterThan(-1);
1133
- const staticBlock = result.slice(0, boundaryIdx);
1134
- expect(staticBlock).toContain("## External Content");
1148
+ expect(result).toContain("## External Content");
1135
1149
  });
1136
1150
 
1137
1151
  test("bundled external-content default renders when no workspace override", () => {
@@ -288,7 +288,7 @@ describe("Shell tool input validation", () => {
288
288
  });
289
289
 
290
290
  test("tool definition includes required schema fields", () => {
291
- const def = shellTool.getDefinition();
291
+ const def = shellTool;
292
292
  const schema = def.input_schema as {
293
293
  required: string[];
294
294
  properties: Record<string, unknown>;
@@ -26,11 +26,7 @@ const fakeTool = {
26
26
  description: "Run a shell command",
27
27
  category: "shell",
28
28
  defaultRiskLevel: "high",
29
- getDefinition: () => ({
30
- name: "bash",
31
- description: "Run a shell command",
32
- input_schema: {},
33
- }),
29
+ input_schema: {},
34
30
  execute: async () => ({ content: "ok", isError: false }),
35
31
  };
36
32
 
@@ -95,7 +95,7 @@ mock.module("../permissions/checker.js", () => ({
95
95
  mock.module("../memory/tool-usage-store.js", () => ({
96
96
  recordToolInvocation: () => {},
97
97
  getRecentInvocations: () => [],
98
- rotateToolInvocations: () => 0,
98
+ rotateToolInvocations: async () => 0,
99
99
  }));
100
100
 
101
101
  // ── Tool registry: return a stub tool whose execute records the call ─
@@ -113,7 +113,7 @@ mock.module("../tools/registry.js", () => ({
113
113
  description: "test tool",
114
114
  category: "test",
115
115
  defaultRiskLevel: "low",
116
- getDefinition: () => ({}),
116
+ input_schema: {},
117
117
  execute: async (input: Record<string, unknown>) => {
118
118
  lastToolCall = { name, input };
119
119
  return fakeToolResult;