@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
@@ -214,6 +214,115 @@ describe("forkConversation", () => {
214
214
  ]);
215
215
  });
216
216
 
217
+ test("advances fork boundary through consecutive assistant rows after the requested message", async () => {
218
+ // When the read-path merges consecutive assistant DB rows into a single
219
+ // display row, the client only addresses the anchor id. Forking through
220
+ // the anchor must still include the merged tail rows that follow.
221
+ const source = createConversation("Multi-row turn thread");
222
+ await addMessage(source.id, "user", "Message 1", undefined, {
223
+ skipIndexing: true,
224
+ });
225
+ const anchor = await addMessage(
226
+ source.id,
227
+ "assistant",
228
+ "Assistant text segment",
229
+ undefined,
230
+ { skipIndexing: true },
231
+ );
232
+ const toolRow = await addMessage(
233
+ source.id,
234
+ "assistant",
235
+ "Tool turn row",
236
+ undefined,
237
+ { skipIndexing: true },
238
+ );
239
+ const tailRow = await addMessage(
240
+ source.id,
241
+ "assistant",
242
+ "Final assistant segment",
243
+ undefined,
244
+ { skipIndexing: true },
245
+ );
246
+ await addMessage(source.id, "user", "Next user turn", undefined, {
247
+ skipIndexing: true,
248
+ });
249
+
250
+ const fork = forkConversation({
251
+ conversationId: source.id,
252
+ throughMessageId: anchor.id,
253
+ });
254
+
255
+ // Boundary advances past the entire consecutive-assistant cluster, so the
256
+ // full turn is preserved in the fork — not just the anchor row.
257
+ expect(getMessages(fork.id).map((message) => message.content)).toEqual([
258
+ "Message 1",
259
+ "Assistant text segment",
260
+ "Tool turn row",
261
+ "Final assistant segment",
262
+ ]);
263
+ expect(fork.forkParentMessageId).toBe(tailRow.id);
264
+ expect(toolRow.id).not.toBe(anchor.id);
265
+ });
266
+
267
+ test("advances fork boundary across tool-result-only user rows between assistant rows", async () => {
268
+ // Read-path collapse folds tool-result-only user rows into the
269
+ // surrounding assistant turn (`mergeToolResultsIntoAssistantMessages`
270
+ // suppresses them). The client only sees a single display turn anchored
271
+ // at the first assistant row, so forking through the anchor must include
272
+ // both halves of the assistant turn plus the suppressed user row in
273
+ // between — otherwise the fork loses tool_use ↔ tool_result pairing
274
+ // and produces an invalid LLM history.
275
+ const source = createConversation("Tool-result gap thread");
276
+ await addMessage(source.id, "user", "Find the latest sales numbers", undefined, {
277
+ skipIndexing: true,
278
+ });
279
+ const anchor = await addMessage(
280
+ source.id,
281
+ "assistant",
282
+ JSON.stringify([
283
+ { type: "text", text: "Looking up the data." },
284
+ { type: "tool_use", id: "tool_1", name: "lookup", input: {} },
285
+ ]),
286
+ undefined,
287
+ { skipIndexing: true },
288
+ );
289
+ const toolResultUserRow = await addMessage(
290
+ source.id,
291
+ "user",
292
+ JSON.stringify([
293
+ { type: "tool_result", tool_use_id: "tool_1", content: "data" },
294
+ ]),
295
+ undefined,
296
+ { skipIndexing: true },
297
+ );
298
+ const tailAssistantRow = await addMessage(
299
+ source.id,
300
+ "assistant",
301
+ "Here are the numbers.",
302
+ undefined,
303
+ { skipIndexing: true },
304
+ );
305
+ await addMessage(source.id, "user", "Thanks", undefined, {
306
+ skipIndexing: true,
307
+ });
308
+
309
+ const fork = forkConversation({
310
+ conversationId: source.id,
311
+ throughMessageId: anchor.id,
312
+ });
313
+
314
+ // All three DB rows of the assistant display turn — including the
315
+ // suppressed tool-result user row in the middle — land in the fork.
316
+ const forkedContent = getMessages(fork.id).map((m) => m.content);
317
+ expect(forkedContent).toHaveLength(4);
318
+ expect(forkedContent[0]).toBe("Find the latest sales numbers");
319
+ expect(forkedContent[1]).toContain("tool_use");
320
+ expect(forkedContent[2]).toContain("tool_result");
321
+ expect(forkedContent[3]).toBe("Here are the numbers.");
322
+ expect(fork.forkParentMessageId).toBe(tailAssistantRow.id);
323
+ expect(toolResultUserRow.id).not.toBe(anchor.id);
324
+ });
325
+
217
326
  test("preserves compacted context when forking from the visible window", async () => {
218
327
  const source = createConversation("Compacted thread");
219
328
  await addMessage(source.id, "user", "Message 1", undefined, {
@@ -297,7 +406,7 @@ describe("forkConversation", () => {
297
406
  ]);
298
407
  });
299
408
 
300
- test("inherits cleanedAt when forking past the clean event", async () => {
409
+ test("inherits historyStrippedAt when forking past the clean event", async () => {
301
410
  const source = createConversation("Clean thread");
302
411
  await addMessage(source.id, "user", "Message 1", undefined, {
303
412
  skipIndexing: true,
@@ -310,10 +419,10 @@ describe("forkConversation", () => {
310
419
  { skipIndexing: true },
311
420
  );
312
421
 
313
- const cleanedAt = preClean.createdAt + 1;
422
+ const historyStrippedAt = preClean.createdAt + 1;
314
423
  getDb()
315
424
  .update(conversations)
316
- .set({ cleanedAt })
425
+ .set({ historyStrippedAt })
317
426
  .where(eq(conversations.id, source.id))
318
427
  .run();
319
428
 
@@ -324,17 +433,17 @@ describe("forkConversation", () => {
324
433
  undefined,
325
434
  { skipIndexing: true },
326
435
  );
327
- expect(postClean.createdAt).toBeGreaterThanOrEqual(cleanedAt);
436
+ expect(postClean.createdAt).toBeGreaterThanOrEqual(historyStrippedAt);
328
437
 
329
438
  const fork = forkConversation({
330
439
  conversationId: source.id,
331
440
  throughMessageId: postClean.id,
332
441
  });
333
442
 
334
- expect(fork.cleanedAt).toBe(cleanedAt);
443
+ expect(fork.historyStrippedAt).toBe(historyStrippedAt);
335
444
  });
336
445
 
337
- test("does not inherit cleanedAt when forking before the clean event", async () => {
446
+ test("does not inherit historyStrippedAt when forking before the clean event", async () => {
338
447
  const source = createConversation("Clean thread");
339
448
  await addMessage(source.id, "user", "Message 1", undefined, {
340
449
  skipIndexing: true,
@@ -347,10 +456,10 @@ describe("forkConversation", () => {
347
456
  { skipIndexing: true },
348
457
  );
349
458
 
350
- const cleanedAt = preClean.createdAt + 1;
459
+ const historyStrippedAt = preClean.createdAt + 1;
351
460
  getDb()
352
461
  .update(conversations)
353
- .set({ cleanedAt })
462
+ .set({ historyStrippedAt })
354
463
  .where(eq(conversations.id, source.id))
355
464
  .run();
356
465
 
@@ -363,10 +472,10 @@ describe("forkConversation", () => {
363
472
  throughMessageId: preClean.id,
364
473
  });
365
474
 
366
- expect(fork.cleanedAt).toBeNull();
475
+ expect(fork.historyStrippedAt).toBeNull();
367
476
  });
368
477
 
369
- test("inherits cleanedAt on a full-history fork", async () => {
478
+ test("inherits historyStrippedAt on a full-history fork", async () => {
370
479
  const source = createConversation("Clean thread");
371
480
  await addMessage(source.id, "user", "Message 1", undefined, {
372
481
  skipIndexing: true,
@@ -379,19 +488,19 @@ describe("forkConversation", () => {
379
488
  { skipIndexing: true },
380
489
  );
381
490
 
382
- const cleanedAt = last.createdAt - 1;
491
+ const historyStrippedAt = last.createdAt - 1;
383
492
  getDb()
384
493
  .update(conversations)
385
- .set({ cleanedAt })
494
+ .set({ historyStrippedAt })
386
495
  .where(eq(conversations.id, source.id))
387
496
  .run();
388
497
 
389
498
  const fork = forkConversation({ conversationId: source.id });
390
499
 
391
- expect(fork.cleanedAt).toBe(cleanedAt);
500
+ expect(fork.historyStrippedAt).toBe(historyStrippedAt);
392
501
  });
393
502
 
394
- test("leaves cleanedAt null when the source has no clean event", async () => {
503
+ test("leaves historyStrippedAt null when the source has no clean event", async () => {
395
504
  const source = createConversation("Unclean thread");
396
505
  await addMessage(source.id, "user", "Message 1", undefined, {
397
506
  skipIndexing: true,
@@ -402,7 +511,61 @@ describe("forkConversation", () => {
402
511
 
403
512
  const fork = forkConversation({ conversationId: source.id });
404
513
 
405
- expect(fork.cleanedAt).toBeNull();
514
+ expect(fork.historyStrippedAt).toBeNull();
515
+ });
516
+
517
+ test("fork from a pre-compaction message preserves historical injection metadata", async () => {
518
+ const source = createConversation("Compacted thread");
519
+ const m1 = await addMessage(
520
+ source.id,
521
+ "user",
522
+ "Historical question",
523
+ {
524
+ pkbContextBlock: "<knowledge_base>\nstale\n</knowledge_base>",
525
+ nowScratchpadBlock:
526
+ "<NOW.md Always keep this up to date>\nstale\n</NOW.md>",
527
+ },
528
+ { skipIndexing: true },
529
+ );
530
+ await addMessage(source.id, "assistant", "Reply 1", undefined, {
531
+ skipIndexing: true,
532
+ });
533
+ await addMessage(source.id, "user", "Tail turn", undefined, {
534
+ skipIndexing: true,
535
+ });
536
+ const compactedAt = Date.now();
537
+ getDb()
538
+ .update(conversations)
539
+ .set({
540
+ contextSummary: "summary",
541
+ contextCompactedMessageCount: 2,
542
+ contextCompactedAt: compactedAt,
543
+ historyStrippedAt: compactedAt,
544
+ })
545
+ .where(eq(conversations.id, source.id))
546
+ .run();
547
+
548
+ const fork = forkConversation({
549
+ conversationId: source.id,
550
+ throughMessageId: m1.id,
551
+ });
552
+
553
+ expect(fork.historyStrippedAt).toBeNull();
554
+ expect(fork.contextSummary).toBeNull();
555
+ expect(fork.contextCompactedMessageCount).toBe(0);
556
+
557
+ const forkedMessages = getMessages(fork.id);
558
+ expect(forkedMessages).toHaveLength(1);
559
+ const meta = parseMetadata(forkedMessages[0].metadata) as Record<
560
+ string,
561
+ unknown
562
+ >;
563
+ expect(meta.pkbContextBlock).toBe(
564
+ "<knowledge_base>\nstale\n</knowledge_base>",
565
+ );
566
+ expect(meta.nowScratchpadBlock).toBe(
567
+ "<NOW.md Always keep this up to date>\nstale\n</NOW.md>",
568
+ );
406
569
  });
407
570
 
408
571
  test("rejects forks when the source conversation has no persisted messages", () => {
@@ -379,7 +379,7 @@ describe("loadFromDb metadata injection rehydration", () => {
379
379
  metadata: JSON.stringify({
380
380
  memoryInjectedBlock: "mem payload",
381
381
  memoryV2StaticBlock:
382
- "<memory>\n## Essentials\n\nAlice prefers VS Code.\n</memory>",
382
+ "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
383
383
  pkbSystemReminderBlock:
384
384
  "<system_reminder>\npkb payload\n</system_reminder>",
385
385
  }),
@@ -406,7 +406,7 @@ describe("loadFromDb metadata injection rehydration", () => {
406
406
  { type: "text", text: "<memory>\nmem payload\n</memory>" },
407
407
  {
408
408
  type: "text",
409
- text: "<memory>\n## Essentials\n\nAlice prefers VS Code.\n</memory>",
409
+ text: "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
410
410
  },
411
411
  {
412
412
  type: "text",
@@ -416,6 +416,47 @@ describe("loadFromDb metadata injection rehydration", () => {
416
416
  ]);
417
417
  });
418
418
 
419
+ test("legacy <memory>-wrapped memoryV2StaticBlock rehydrates verbatim", async () => {
420
+ // `meta.memoryV2StaticBlock` may carry either `<info>…</info>` or
421
+ // legacy `<memory>…</memory>` wrappers depending on when the row was
422
+ // persisted. The rehydrate path replays the stored text verbatim,
423
+ // so both wrappers must round-trip unchanged.
424
+ mockConversation = defaultConv();
425
+ mockDbMessages = [
426
+ {
427
+ id: "m1",
428
+ role: "user",
429
+ content: JSON.stringify([{ type: "text", text: "First turn" }]),
430
+ metadata: JSON.stringify({
431
+ memoryV2StaticBlock:
432
+ "<memory>\n## Essentials\n\nAlice prefers VS Code.\n</memory>",
433
+ }),
434
+ },
435
+ {
436
+ id: "m2",
437
+ role: "assistant",
438
+ content: JSON.stringify([{ type: "text", text: "Reply" }]),
439
+ },
440
+ {
441
+ id: "m3",
442
+ role: "user",
443
+ content: JSON.stringify([{ type: "text", text: "Tail turn" }]),
444
+ },
445
+ ];
446
+
447
+ const conversation = makeConversation();
448
+ await conversation.loadFromDb();
449
+ const messages = conversation.getMessages();
450
+
451
+ expect(messages[0].content).toEqual([
452
+ {
453
+ type: "text",
454
+ text: "<memory>\n## Essentials\n\nAlice prefers VS Code.\n</memory>",
455
+ },
456
+ { type: "text", text: "First turn" },
457
+ ]);
458
+ });
459
+
419
460
  test("tail user row skips memoryV2StaticBlock", async () => {
420
461
  mockConversation = defaultConv();
421
462
  mockDbMessages = [
@@ -434,7 +475,7 @@ describe("loadFromDb metadata injection rehydration", () => {
434
475
  role: "user",
435
476
  content: JSON.stringify([{ type: "text", text: "Tail" }]),
436
477
  metadata: JSON.stringify({
437
- memoryV2StaticBlock: "<memory>\n## Essentials\n\nleak\n</memory>",
478
+ memoryV2StaticBlock: "<info>\n## Essentials\n\nleak\n</info>",
438
479
  }),
439
480
  },
440
481
  ];
@@ -471,7 +512,7 @@ describe("loadFromDb metadata injection rehydration", () => {
471
512
  // survive the row-level filter for non-guardian views.
472
513
  provenanceTrustClass: "trusted_contact",
473
514
  memoryV2StaticBlock:
474
- "<memory>\n## Essentials\n\nAlice prefers VS Code.\n</memory>",
515
+ "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
475
516
  }),
476
517
  },
477
518
  {
@@ -500,7 +541,7 @@ describe("loadFromDb metadata injection rehydration", () => {
500
541
  expect(messages[0].content).toEqual([
501
542
  {
502
543
  type: "text",
503
- text: "<memory>\n## Essentials\n\nAlice prefers VS Code.\n</memory>",
544
+ text: "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
504
545
  },
505
546
  { type: "text", text: "First" },
506
547
  ]);
@@ -510,7 +551,7 @@ describe("loadFromDb metadata injection rehydration", () => {
510
551
  // Injection-time layout (per `applyRuntimeInjections` after-memory-
511
552
  // prefix splicing in ascending injector order: pkb-context 30,
512
553
  // pkb-reminder 35, memory-v2-static 38, now-md 40):
513
- // [<memory __injected>, <memory>v2static</memory>, <NOW.md>,
554
+ // [<memory>dynamic</memory>, <info>v2static</info>, <NOW.md>,
514
555
  // <system_reminder>, <knowledge_base>, ...original]
515
556
  // Rehydration must reproduce this exactly so Anthropic's prefix cache
516
557
  // matches msg[0] across daemon restarts.
@@ -523,7 +564,7 @@ describe("loadFromDb metadata injection rehydration", () => {
523
564
  metadata: JSON.stringify({
524
565
  memoryInjectedBlock: "mem payload",
525
566
  memoryV2StaticBlock:
526
- "<memory>\n## Essentials\n\nAlice prefers VS Code.\n</memory>",
567
+ "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
527
568
  nowScratchpadBlock: "<NOW.md>\nnow body\n</NOW.md>",
528
569
  pkbSystemReminderBlock:
529
570
  "<system_reminder>\npkb reminder body\n</system_reminder>",
@@ -551,7 +592,7 @@ describe("loadFromDb metadata injection rehydration", () => {
551
592
  { type: "text", text: "<memory>\nmem payload\n</memory>" },
552
593
  {
553
594
  type: "text",
554
- text: "<memory>\n## Essentials\n\nAlice prefers VS Code.\n</memory>",
595
+ text: "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
555
596
  },
556
597
  { type: "text", text: "<NOW.md>\nnow body\n</NOW.md>" },
557
598
  {
@@ -575,7 +616,7 @@ describe("loadFromDb metadata injection rehydration", () => {
575
616
  metadata: JSON.stringify({
576
617
  provenanceTrustClass: "trusted_contact",
577
618
  memoryV2StaticBlock:
578
- "<memory>\n## Essentials\n\nprivate memory\n</memory>",
619
+ "<info>\n## Essentials\n\nprivate memory\n</info>",
579
620
  }),
580
621
  },
581
622
  {
@@ -621,7 +662,7 @@ describe("loadFromDb metadata injection rehydration", () => {
621
662
  metadata: JSON.stringify({
622
663
  provenanceTrustClass: "trusted_contact",
623
664
  memoryV2StaticBlock:
624
- "<memory>\n## Essentials\n\nprivate memory\n</memory>",
665
+ "<info>\n## Essentials\n\nprivate memory\n</info>",
625
666
  }),
626
667
  },
627
668
  {
@@ -649,7 +690,7 @@ describe("loadFromDb metadata injection rehydration", () => {
649
690
  expect(conversation.getMessages()[0].content).toEqual([
650
691
  {
651
692
  type: "text",
652
- text: "<memory>\n## Essentials\n\nprivate memory\n</memory>",
693
+ text: "<info>\n## Essentials\n\nprivate memory\n</info>",
653
694
  },
654
695
  { type: "text", text: "First" },
655
696
  ]);
@@ -84,7 +84,7 @@ mock.module("../memory/conversation-crud.js", () => ({
84
84
  getConversation: () => mockConversation,
85
85
  createConversation: () => ({ id: "conv-1" }),
86
86
  addMessage: async () => ({ id: "persisted" }),
87
- setConversationCleanedAt: () => {},
87
+ setConversationHistoryStrippedAt: () => {},
88
88
  setConversationOriginChannelIfUnset: () => {},
89
89
  setConversationOriginInterfaceIfUnset: () => {},
90
90
  }));
@@ -123,19 +123,19 @@ function textBlocks(content: ReadonlyArray<{ type: string; text?: string }>) {
123
123
  .map((b) => b.text);
124
124
  }
125
125
 
126
- describe("loadFromDb with cleanedAt", () => {
126
+ describe("loadFromDb with historyStrippedAt", () => {
127
127
  beforeEach(() => {
128
128
  mockDbMessages = [];
129
129
  mockConversation = null;
130
130
  });
131
131
 
132
- test("strips injection prefixes from pre-clean user content", async () => {
133
- const cleanedAt = 1000;
132
+ test("strips injection prefixes from pre-strip user content", async () => {
133
+ const historyStrippedAt = 1000;
134
134
  mockConversation = {
135
135
  id: "conv-1",
136
136
  contextSummary: null,
137
137
  contextCompactedMessageCount: 0,
138
- cleanedAt,
138
+ historyStrippedAt,
139
139
  totalInputTokens: 0,
140
140
  totalOutputTokens: 0,
141
141
  totalEstimatedCost: 0,
@@ -186,13 +186,13 @@ describe("loadFromDb with cleanedAt", () => {
186
186
  ]);
187
187
  });
188
188
 
189
- test("skips metadata rehydration for pre-clean messages", async () => {
190
- const cleanedAt = 1000;
189
+ test("skips metadata rehydration for pre-strip messages", async () => {
190
+ const historyStrippedAt = 1000;
191
191
  mockConversation = {
192
192
  id: "conv-1",
193
193
  contextSummary: null,
194
194
  contextCompactedMessageCount: 0,
195
- cleanedAt,
195
+ historyStrippedAt,
196
196
  totalInputTokens: 0,
197
197
  totalOutputTokens: 0,
198
198
  totalEstimatedCost: 0,
@@ -201,7 +201,7 @@ describe("loadFromDb with cleanedAt", () => {
201
201
  {
202
202
  id: "m1",
203
203
  role: "user",
204
- content: JSON.stringify([{ type: "text", text: "Pre-clean turn" }]),
204
+ content: JSON.stringify([{ type: "text", text: "Pre-strip turn" }]),
205
205
  createdAt: 500,
206
206
  metadata: JSON.stringify({
207
207
  pkbContextBlock: "<knowledge_base>stale</knowledge_base>",
@@ -217,7 +217,7 @@ describe("loadFromDb with cleanedAt", () => {
217
217
  id: "m3",
218
218
  role: "user",
219
219
  content: JSON.stringify([
220
- { type: "text", text: "Mid post-clean turn" },
220
+ { type: "text", text: "Mid post-strip turn" },
221
221
  ]),
222
222
  createdAt: 1500,
223
223
  metadata: JSON.stringify({
@@ -236,18 +236,18 @@ describe("loadFromDb with cleanedAt", () => {
236
236
  await conversation.loadFromDb();
237
237
  const messages = conversation.getMessages();
238
238
 
239
- expect(textBlocks(messages[0].content)).toEqual(["Pre-clean turn"]);
239
+ expect(textBlocks(messages[0].content)).toEqual(["Pre-strip turn"]);
240
240
  expect(textBlocks(messages[2].content).join("\n")).toContain(
241
241
  "<knowledge_base>kept</knowledge_base>",
242
242
  );
243
243
  });
244
244
 
245
- test("leaves messages untouched when cleanedAt is null", async () => {
245
+ test("leaves messages untouched when historyStrippedAt is null", async () => {
246
246
  mockConversation = {
247
247
  id: "conv-1",
248
248
  contextSummary: null,
249
249
  contextCompactedMessageCount: 0,
250
- cleanedAt: null,
250
+ historyStrippedAt: null,
251
251
  totalInputTokens: 0,
252
252
  totalOutputTokens: 0,
253
253
  totalEstimatedCost: 0,
@@ -179,6 +179,7 @@ mock.module("../memory/conversation-crud.js", () => ({
179
179
  updateConversationUsage: () => {},
180
180
  updateConversationTitle: () => {},
181
181
  updateConversationContextWindow: () => {},
182
+ setConversationHistoryStrippedAt: () => {},
182
183
  getConversationOriginChannel: () => null,
183
184
  getConversationOriginInterface: () => null,
184
185
  provenanceFromTrustContext: () => ({}),
@@ -498,6 +498,115 @@ describe("macOS browser backend fallback (no extension, no cdp-inspect)", () =>
498
498
  });
499
499
  });
500
500
 
501
+ describe("POST /v1/messages — body.conversationId direct id lookup", () => {
502
+ // The handler accepts two scope inputs with distinct semantics:
503
+ //
504
+ // - `body.conversationId` is the assistant-minted internal id and is
505
+ // looked up directly. A missing row is a 404 — clients must obtain
506
+ // the id from a prior daemon response.
507
+ // - `body.conversationKey` is an external key (non-vellum channels /
508
+ // web idempotency); resolved via the conversation_keys table and
509
+ // materialised on first use.
510
+ //
511
+ // When both are sent, `conversationId` wins and `conversationKey` is
512
+ // ignored. (Don't combine — fetch by one and then the other.)
513
+
514
+ async function sendMessage(
515
+ body: Record<string, unknown>,
516
+ successStatus = 202,
517
+ ): Promise<Response> {
518
+ return callHandler(
519
+ (args) =>
520
+ handleSendMessage(args, {
521
+ sendMessageDeps: {
522
+ getOrCreateConversation: async (conversationId: string) =>
523
+ getOrCreateFakeConversation(conversationId),
524
+ assistantEventHub: new AssistantEventHub(),
525
+ resolveAttachments: () => [],
526
+ },
527
+ }),
528
+ new Request("http://localhost/v1/messages", {
529
+ method: "POST",
530
+ headers: {
531
+ "Content-Type": "application/json",
532
+ "x-vellum-principal-type": authContext.principalType,
533
+ },
534
+ body: JSON.stringify(body),
535
+ }),
536
+ undefined,
537
+ successStatus,
538
+ );
539
+ }
540
+
541
+ test("body.conversationId=<existing-id> scopes the send to that conversation", async () => {
542
+ // Pre-materialise a conversation via the key path, then send a message
543
+ // by its assistant-minted internal id.
544
+ const externalKey = `pre-materialised-${crypto.randomUUID()}`;
545
+ const seeded = getOrCreateConversationMapping(externalKey);
546
+
547
+ const response = await sendMessage({
548
+ conversationId: seeded.conversationId,
549
+ content: "Direct id lookup — should reuse the existing conversation.",
550
+ sourceChannel: "vellum",
551
+ interface: "macos",
552
+ });
553
+
554
+ expect(response.status).toBe(202);
555
+ const body = (await response.json()) as {
556
+ accepted: boolean;
557
+ conversationId: string;
558
+ };
559
+ expect(body.accepted).toBe(true);
560
+ expect(body.conversationId).toBe(seeded.conversationId);
561
+
562
+ // No new external-key row should be materialised under the internal id.
563
+ expect(getConversationByKey(seeded.conversationId)).toBeNull();
564
+ });
565
+
566
+ test("body.conversationId=<non-existent-id> returns 404", async () => {
567
+ const response = await sendMessage(
568
+ {
569
+ conversationId: `does-not-exist-${crypto.randomUUID()}`,
570
+ content: "Should 404 — unknown internal id.",
571
+ sourceChannel: "vellum",
572
+ interface: "macos",
573
+ },
574
+ 404,
575
+ );
576
+ expect(response.status).toBe(404);
577
+ const body = (await response.json()) as {
578
+ error?: { code?: string; message?: string };
579
+ };
580
+ expect(body.error?.code).toBe("NOT_FOUND");
581
+ expect(body.error?.message).toMatch(/not found/i);
582
+ });
583
+
584
+ test("body.conversationId is honored and body.conversationKey is ignored when both are sent", async () => {
585
+ // Seed a conversation for the id we'll send. Also seed a separate
586
+ // conversation under a key the client will pass alongside — but the
587
+ // handler must scope to the id, NOT the key.
588
+ const idSeed = getOrCreateConversationMapping(
589
+ `id-honored-${crypto.randomUUID()}`,
590
+ );
591
+ const keyValue = `key-ignored-${crypto.randomUUID()}`;
592
+ const keySeed = getOrCreateConversationMapping(keyValue);
593
+ expect(idSeed.conversationId).not.toBe(keySeed.conversationId);
594
+
595
+ const response = await sendMessage({
596
+ conversationId: idSeed.conversationId,
597
+ conversationKey: keyValue,
598
+ content: "Both fields sent — id should win.",
599
+ sourceChannel: "vellum",
600
+ interface: "macos",
601
+ });
602
+
603
+ expect(response.status).toBe(202);
604
+ const body = (await response.json()) as { conversationId: string };
605
+ expect(body.conversationId).toBe(idSeed.conversationId);
606
+ expect(body.conversationId).not.toBe(keySeed.conversationId);
607
+ });
608
+ });
609
+
501
610
  describe("conversationKey send path disk-view regression", () => {
502
611
  test("first send on a fresh conversationKey creates disk-view dir and writes user+assistant records", async () => {
503
612
  const conversationKey = `fresh-conv-key-${crypto.randomUUID()}`;
@@ -370,6 +370,41 @@ describe("handleSendMessage slash command interception", () => {
370
370
  expect(runAgentLoop).not.toHaveBeenCalled();
371
371
  });
372
372
 
373
+ test("clears processing and drains the queue when /compact's initial persist fails", async () => {
374
+ const { conversation } = makeConversation();
375
+ const drainQueue = mock(async () => {});
376
+ (
377
+ conversation as unknown as { drainQueue: () => Promise<void> }
378
+ ).drainQueue = drainQueue;
379
+
380
+ // Force the user-message persist (the first addMessage in the /compact
381
+ // branch, on the synchronous pre-202 path) to throw.
382
+ addMessageMock.mockImplementationOnce(async () => {
383
+ throw new Error("disk full");
384
+ });
385
+
386
+ // The failure surfaces to the caller rather than silently 202-ing.
387
+ let caught: Error | undefined;
388
+ try {
389
+ await callHandler(
390
+ (args) => handleSendMessage(args, makeDeps(conversation)),
391
+ makeRequest("/compact"),
392
+ undefined,
393
+ 202,
394
+ );
395
+ } catch (err) {
396
+ caught = err as Error;
397
+ }
398
+ expect(caught?.message).toBe("disk full");
399
+
400
+ // Regression: without the guard `processing` stays stuck true, leaving
401
+ // every later send queued forever; the queue must also be drained.
402
+ expect(
403
+ (conversation as unknown as { processing: boolean }).processing,
404
+ ).toBe(false);
405
+ expect(drainQueue).toHaveBeenCalledTimes(1);
406
+ });
407
+
373
408
  test("passes regular messages through to agent loop unchanged", async () => {
374
409
  const {
375
410
  conversation,