@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
@@ -61,22 +61,43 @@ mock.module("../prompts/user-reference.js", () => ({
61
61
  resolveUserPronouns: () => null,
62
62
  }));
63
63
 
64
- const { buildSystemPrompt, SYSTEM_PROMPT_CACHE_BOUNDARY } =
65
- await import("../prompts/system-prompt.js");
64
+ // Stub persona-resolver so tests can dictate the slug `buildSystemPrompt`
65
+ // sees without writing contact rows to the test DB. User and channel
66
+ // persona content now flows through bundled sections that read files
67
+ // directly, so tests write the persona file under TEST_DIR rather than
68
+ // stubbing the content here.
69
+ const mockPersona: {
70
+ userSlug: string | null;
71
+ guardianPersona: string | null;
72
+ } = { userSlug: null, guardianPersona: null };
73
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
74
+ const realPersonaResolver = require("../prompts/persona-resolver.js");
75
+ mock.module("../prompts/persona-resolver.js", () => ({
76
+ ...realPersonaResolver,
77
+ resolveUserSlug: () => mockPersona.userSlug,
78
+ resolveGuardianPersona: () => mockPersona.guardianPersona,
79
+ }));
80
+
81
+ const { buildSystemPrompt } = await import("../prompts/system-prompt.js");
66
82
 
67
83
  /**
68
- * Extract the dynamic block (workspace-file content) from the full system prompt.
84
+ * Slice the assembled system prompt from the `# First-Run Ritual`
85
+ * marker through the end of the prompt, returning just the
86
+ * `13-bootstrap` section's rendered payload. Returns "" when the
87
+ * section isn't rendered (no BOOTSTRAP.md, `excludeBootstrap: true`,
88
+ * etc.).
69
89
  */
70
- function dynamicBlock(result: string): string {
71
- const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
72
- return boundaryIdx >= 0
73
- ? result.slice(boundaryIdx + SYSTEM_PROMPT_CACHE_BOUNDARY.length)
74
- : result;
90
+ function bootstrapBlock(result: string): string {
91
+ const ritualIdx = result.indexOf("# First-Run Ritual");
92
+ if (ritualIdx < 0) return "";
93
+ return result.slice(ritualIdx);
75
94
  }
76
95
 
77
96
  describe("pre-chat onboarding contract", () => {
78
97
  beforeEach(() => {
79
98
  mkdirSync(TEST_DIR, { recursive: true });
99
+ mockPersona.userSlug = null;
100
+ mockPersona.guardianPersona = null;
80
101
  });
81
102
 
82
103
  afterEach(() => {
@@ -87,6 +108,8 @@ describe("pre-chat onboarding contract", () => {
87
108
  "BOOTSTRAP.md",
88
109
  "BOOTSTRAP-REFERENCE.md",
89
110
  "UPDATES.md",
111
+ "users",
112
+ "channels",
90
113
  ]) {
91
114
  const p = join(TEST_DIR, name);
92
115
  if (existsSync(p)) rmSync(p, { recursive: true, force: true });
@@ -151,20 +174,20 @@ describe("pre-chat onboarding contract", () => {
151
174
  };
152
175
 
153
176
  const result = buildSystemPrompt({ onboardingContext: context });
154
- const dynamic = dynamicBlock(result);
177
+ const bootstrap = bootstrapBlock(result);
155
178
 
156
- expect(dynamic).toContain("## First-Run User Context");
157
- expect(dynamic).toContain(
179
+ expect(bootstrap).toContain("## First-Run User Context");
180
+ expect(bootstrap).toContain(
158
181
  "The user completed setup before this conversation.",
159
182
  );
160
- expect(dynamic).toContain("- Daily tools: Slack, Linear");
161
- expect(dynamic).toContain("- Common work: builds code, apps, or tools");
162
- expect(dynamic).toContain("- Name: Alex");
163
- expect(dynamic).toContain("- Chosen assistant name: Nova");
164
- expect(dynamic).toContain("Apply this context quietly.");
183
+ expect(bootstrap).toContain("- Daily tools: Slack, Linear");
184
+ expect(bootstrap).toContain("- Common work: builds code, apps, or tools");
185
+ expect(bootstrap).toContain("- Name: Alex");
186
+ expect(bootstrap).toContain("- Chosen assistant name: Nova");
187
+ expect(bootstrap).toContain("Apply this context quietly.");
165
188
 
166
189
  // Raw JSON must NOT be present
167
- expect(dynamic).not.toContain("```json");
190
+ expect(bootstrap).not.toContain("```json");
168
191
  });
169
192
 
170
193
  test("does NOT inject onboarding context when BOOTSTRAP.md does not exist", () => {
@@ -177,11 +200,11 @@ describe("pre-chat onboarding contract", () => {
177
200
  };
178
201
 
179
202
  const result = buildSystemPrompt({ onboardingContext: context });
180
- const dynamic = dynamicBlock(result);
203
+ const bootstrap = bootstrapBlock(result);
181
204
 
182
- expect(dynamic).not.toContain("## First-Run User Context");
183
- expect(dynamic).not.toContain("First-Run User Context");
184
- expect(dynamic).not.toContain("- Daily tools:");
205
+ expect(bootstrap).not.toContain("## First-Run User Context");
206
+ expect(bootstrap).not.toContain("First-Run User Context");
207
+ expect(bootstrap).not.toContain("- Daily tools:");
185
208
  });
186
209
 
187
210
  test("does NOT inject onboarding context when excludeBootstrap is true", () => {
@@ -200,10 +223,10 @@ describe("pre-chat onboarding contract", () => {
200
223
  onboardingContext: context,
201
224
  excludeBootstrap: true,
202
225
  });
203
- const dynamic = dynamicBlock(result);
226
+ const bootstrap = bootstrapBlock(result);
204
227
 
205
- expect(dynamic).not.toContain("## First-Run User Context");
206
- expect(dynamic).not.toContain("First-Run Ritual");
228
+ expect(bootstrap).not.toContain("## First-Run User Context");
229
+ expect(bootstrap).not.toContain("First-Run Ritual");
207
230
  });
208
231
 
209
232
  test("omits onboarding section when context is undefined", () => {
@@ -213,12 +236,12 @@ describe("pre-chat onboarding contract", () => {
213
236
  );
214
237
 
215
238
  const result = buildSystemPrompt({ onboardingContext: undefined });
216
- const dynamic = dynamicBlock(result);
239
+ const bootstrap = bootstrapBlock(result);
217
240
 
218
241
  // Bootstrap should still be present
219
- expect(dynamic).toContain("First-Run Ritual");
242
+ expect(bootstrap).toContain("First-Run Ritual");
220
243
  // But no onboarding context section
221
- expect(dynamic).not.toContain("## First-Run User Context");
244
+ expect(bootstrap).not.toContain("## First-Run User Context");
222
245
  });
223
246
 
224
247
  test("accepts all four personality tones", () => {
@@ -238,10 +261,10 @@ describe("pre-chat onboarding contract", () => {
238
261
  };
239
262
 
240
263
  const result = buildSystemPrompt({ onboardingContext: context });
241
- const dynamic = dynamicBlock(result);
264
+ const bootstrap = bootstrapBlock(result);
242
265
 
243
- expect(dynamic).toContain("## First-Run User Context");
244
- expect(dynamic).toContain(`- Preferred initial voice: ${tone}`);
266
+ expect(bootstrap).toContain("## First-Run User Context");
267
+ expect(bootstrap).toContain(`- Preferred initial voice: ${tone}`);
245
268
  }
246
269
  });
247
270
 
@@ -260,20 +283,20 @@ describe("pre-chat onboarding contract", () => {
260
283
  };
261
284
 
262
285
  const result = buildSystemPrompt({ onboardingContext: context });
263
- const dynamic = dynamicBlock(result);
286
+ const bootstrap = bootstrapBlock(result);
264
287
 
265
288
  // Should contain compact markdown lines
266
- expect(dynamic).toContain("## First-Run User Context");
267
- expect(dynamic).toContain("- Name: Jane");
268
- expect(dynamic).toContain("- Common work: plans and coordinates work");
269
- expect(dynamic).toContain("- Daily tools: Notion");
270
- expect(dynamic).toContain("- Chosen assistant name: Kit");
271
- expect(dynamic).toContain("- Preferred initial voice: warm");
289
+ expect(bootstrap).toContain("## First-Run User Context");
290
+ expect(bootstrap).toContain("- Name: Jane");
291
+ expect(bootstrap).toContain("- Common work: plans and coordinates work");
292
+ expect(bootstrap).toContain("- Daily tools: Notion");
293
+ expect(bootstrap).toContain("- Chosen assistant name: Kit");
294
+ expect(bootstrap).toContain("- Preferred initial voice: warm");
272
295
 
273
296
  // Must NOT contain JSON output
274
- expect(dynamic).not.toContain("```json");
297
+ expect(bootstrap).not.toContain("```json");
275
298
  const expectedJson = JSON.stringify(context, null, 2);
276
- expect(dynamic).not.toContain(expectedJson);
299
+ expect(bootstrap).not.toContain(expectedJson);
277
300
  });
278
301
 
279
302
  test("empty tools/tasks arrays result in no Daily tools / Common work lines", () => {
@@ -290,12 +313,12 @@ describe("pre-chat onboarding contract", () => {
290
313
  };
291
314
 
292
315
  const result = buildSystemPrompt({ onboardingContext: context });
293
- const dynamic = dynamicBlock(result);
316
+ const bootstrap = bootstrapBlock(result);
294
317
 
295
- expect(dynamic).toContain("## First-Run User Context");
296
- expect(dynamic).toContain("- Name: Alex");
297
- expect(dynamic).not.toContain("- Daily tools:");
298
- expect(dynamic).not.toContain("- Common work:");
318
+ expect(bootstrap).toContain("## First-Run User Context");
319
+ expect(bootstrap).toContain("- Name: Alex");
320
+ expect(bootstrap).not.toContain("- Daily tools:");
321
+ expect(bootstrap).not.toContain("- Common work:");
299
322
  });
300
323
 
301
324
  test("absent userName results in no Name line", () => {
@@ -311,16 +334,16 @@ describe("pre-chat onboarding contract", () => {
311
334
  };
312
335
 
313
336
  const result = buildSystemPrompt({ onboardingContext: context });
314
- const dynamic = dynamicBlock(result);
337
+ const bootstrap = bootstrapBlock(result);
315
338
 
316
- expect(dynamic).toContain("## First-Run User Context");
317
- expect(dynamic).not.toContain("- Name:");
339
+ expect(bootstrap).toContain("## First-Run User Context");
340
+ expect(bootstrap).not.toContain("- Name:");
318
341
  // Other fields should still be present
319
- expect(dynamic).toContain("- Daily tools: Slack");
320
- expect(dynamic).toContain(
342
+ expect(bootstrap).toContain("- Daily tools: Slack");
343
+ expect(bootstrap).toContain(
321
344
  "- Common work: writes docs, emails, or content",
322
345
  );
323
- expect(dynamic).toContain("- Preferred initial voice: warm");
346
+ expect(bootstrap).toContain("- Preferred initial voice: warm");
324
347
  });
325
348
  });
326
349
 
@@ -356,26 +379,26 @@ describe("pre-chat onboarding contract", () => {
356
379
  };
357
380
 
358
381
  const result = buildSystemPrompt({ onboardingContext: context });
359
- const dynamic = dynamicBlock(result);
382
+ const bootstrap = bootstrapBlock(result);
360
383
 
361
384
  // Heading is present
362
- expect(dynamic).toContain("## First-Run User Context");
385
+ expect(bootstrap).toContain("## First-Run User Context");
363
386
 
364
387
  // Normalized labels appear (capitalised tool names, human-readable task descriptions)
365
- expect(dynamic).toContain("- Daily tools: Slack, Notion, Linear");
366
- expect(dynamic).toContain("- Name: Alice");
367
- expect(dynamic).toContain("- Chosen assistant name: Pax");
368
- expect(dynamic).toContain("- Preferred initial voice: grounded");
388
+ expect(bootstrap).toContain("- Daily tools: Slack, Notion, Linear");
389
+ expect(bootstrap).toContain("- Name: Alice");
390
+ expect(bootstrap).toContain("- Chosen assistant name: Pax");
391
+ expect(bootstrap).toContain("- Preferred initial voice: grounded");
369
392
  // Common work descriptions are normalised from task IDs
370
- expect(dynamic).toContain("- Common work:");
371
-
372
- // No raw JSON anywhere in the dynamic block
373
- expect(dynamic).not.toContain("```json");
374
- expect(dynamic).not.toContain('"tools"');
375
- expect(dynamic).not.toContain('"tasks"');
376
- expect(dynamic).not.toContain('"tone"');
377
- expect(dynamic).not.toContain('"userName"');
378
- expect(dynamic).not.toContain('"assistantName"');
393
+ expect(bootstrap).toContain("- Common work:");
394
+
395
+ // No raw JSON anywhere in the bootstrap block
396
+ expect(bootstrap).not.toContain("```json");
397
+ expect(bootstrap).not.toContain('"tools"');
398
+ expect(bootstrap).not.toContain('"tasks"');
399
+ expect(bootstrap).not.toContain('"tone"');
400
+ expect(bootstrap).not.toContain('"userName"');
401
+ expect(bootstrap).not.toContain('"assistantName"');
379
402
  });
380
403
 
381
404
  test("without BOOTSTRAP.md, onboarding context does NOT appear in system prompt", () => {
@@ -389,15 +412,15 @@ describe("pre-chat onboarding contract", () => {
389
412
  };
390
413
 
391
414
  const result = buildSystemPrompt({ onboardingContext: context });
392
- const dynamic = dynamicBlock(result);
415
+ const bootstrap = bootstrapBlock(result);
393
416
 
394
417
  // Onboarding section must be absent
395
- expect(dynamic).not.toContain("## First-Run User Context");
396
- expect(dynamic).not.toContain("First-Run Ritual");
397
- expect(dynamic).not.toContain("- Daily tools:");
398
- expect(dynamic).not.toContain("- Name: Bob");
399
- expect(dynamic).not.toContain("- Chosen assistant name:");
400
- expect(dynamic).not.toContain("Apply this context quietly.");
418
+ expect(bootstrap).not.toContain("## First-Run User Context");
419
+ expect(bootstrap).not.toContain("First-Run Ritual");
420
+ expect(bootstrap).not.toContain("- Daily tools:");
421
+ expect(bootstrap).not.toContain("- Name: Bob");
422
+ expect(bootstrap).not.toContain("- Chosen assistant name:");
423
+ expect(bootstrap).not.toContain("Apply this context quietly.");
401
424
  });
402
425
 
403
426
  test("excludeBootstrap suppresses both bootstrap and onboarding sections", () => {
@@ -418,34 +441,38 @@ describe("pre-chat onboarding contract", () => {
418
441
  onboardingContext: context,
419
442
  excludeBootstrap: true,
420
443
  });
421
- const dynamic = dynamicBlock(result);
444
+ const bootstrap = bootstrapBlock(result);
422
445
 
423
446
  // Both bootstrap and onboarding must be suppressed
424
- expect(dynamic).not.toContain("First-Run Ritual");
425
- expect(dynamic).not.toContain("## First-Run User Context");
426
- expect(dynamic).not.toContain("- Daily tools:");
427
- expect(dynamic).not.toContain("- Name: Charlie");
428
- expect(dynamic).not.toContain("Apply this context quietly.");
447
+ expect(bootstrap).not.toContain("First-Run Ritual");
448
+ expect(bootstrap).not.toContain("## First-Run User Context");
449
+ expect(bootstrap).not.toContain("- Daily tools:");
450
+ expect(bootstrap).not.toContain("- Name: Charlie");
451
+ expect(bootstrap).not.toContain("Apply this context quietly.");
429
452
  });
430
453
 
431
454
  test("userPersona is included independently of onboarding context", () => {
432
- // No BOOTSTRAP.md — the durable persona path after bootstrap is deleted
433
- const personaContent =
434
- "# User Persona\n\nPrefers concise answers. Works in fintech.";
455
+ // No BOOTSTRAP.md — the durable persona path after bootstrap is deleted.
456
+ // User persona content now lives in `users/<slug>.md` and renders
457
+ // via the `10-user-persona` bundled section in the static prefix.
458
+ mkdirSync(join(TEST_DIR, "users"), { recursive: true });
459
+ writeFileSync(
460
+ join(TEST_DIR, "users", "default.md"),
461
+ "# User Persona\n\nPrefers concise answers. Works in fintech.",
462
+ );
435
463
 
436
464
  const result = buildSystemPrompt({
437
- userPersona: personaContent,
438
465
  // No onboardingContext — simulates post-onboarding conversation
439
466
  });
440
- const dynamic = dynamicBlock(result);
441
467
 
442
468
  // Persona content appears in prompt even without bootstrap or onboarding
443
- expect(dynamic).toContain("# User Persona");
444
- expect(dynamic).toContain("Prefers concise answers. Works in fintech.");
469
+ expect(result).toContain("# User Persona");
470
+ expect(result).toContain("Prefers concise answers. Works in fintech.");
445
471
 
446
472
  // No onboarding section should be present
447
- expect(dynamic).not.toContain("## First-Run User Context");
448
- expect(dynamic).not.toContain("First-Run Ritual");
473
+ const bootstrap = bootstrapBlock(result);
474
+ expect(bootstrap).not.toContain("## First-Run User Context");
475
+ expect(bootstrap).not.toContain("First-Run Ritual");
449
476
  });
450
477
 
451
478
  test("userPersona appears alongside onboarding context during first run", () => {
@@ -454,8 +481,14 @@ describe("pre-chat onboarding contract", () => {
454
481
  "# Bootstrap\n\nOnboarding flow.",
455
482
  );
456
483
 
457
- const personaContent =
458
- "# User Persona\n\nEarly-stage startup founder. Likes bullet points.";
484
+ // User persona file renders via the `10-user-persona` section
485
+ // and the First-Run Ritual + onboarding context render via the
486
+ // `13-bootstrap` section — both in the static prefix.
487
+ mkdirSync(join(TEST_DIR, "users"), { recursive: true });
488
+ writeFileSync(
489
+ join(TEST_DIR, "users", "default.md"),
490
+ "# User Persona\n\nEarly-stage startup founder. Likes bullet points.",
491
+ );
459
492
  const context: OnboardingContext = {
460
493
  tools: ["slack"],
461
494
  tasks: ["writing"],
@@ -464,17 +497,17 @@ describe("pre-chat onboarding contract", () => {
464
497
  };
465
498
 
466
499
  const result = buildSystemPrompt({
467
- userPersona: personaContent,
468
500
  onboardingContext: context,
469
501
  });
470
- const dynamic = dynamicBlock(result);
471
-
472
- // Both persona and onboarding context appear
473
- expect(dynamic).toContain("# User Persona");
474
- expect(dynamic).toContain("Likes bullet points.");
475
- expect(dynamic).toContain("## First-Run User Context");
476
- expect(dynamic).toContain("- Name: Dana");
477
- expect(dynamic).toContain("- Daily tools: Slack");
502
+ const bootstrap = bootstrapBlock(result);
503
+
504
+ // Both persona and onboarding context appear in the static prefix
505
+ // (`10-user-persona` and `13-bootstrap` respectively)
506
+ expect(result).toContain("# User Persona");
507
+ expect(result).toContain("Likes bullet points.");
508
+ expect(bootstrap).toContain("## First-Run User Context");
509
+ expect(bootstrap).toContain("- Name: Dana");
510
+ expect(bootstrap).toContain("- Daily tools: Slack");
478
511
  });
479
512
  });
480
513
  });
@@ -216,6 +216,17 @@ describe("resolvePricing", () => {
216
216
  expect(result.estimatedCostUsd).toBe(0.25 + 1.5);
217
217
  });
218
218
 
219
+ test("returns priced for gemini-3.1-flash-lite", () => {
220
+ const result = resolvePricing(
221
+ "gemini",
222
+ "gemini-3.1-flash-lite",
223
+ 1_000_000,
224
+ 1_000_000,
225
+ );
226
+ expect(result.pricingStatus).toBe("priced");
227
+ expect(result.estimatedCostUsd).toBe(0.25 + 1.5);
228
+ });
229
+
219
230
  test("prices gemini-2.5-pro at the low-context tier through 200k input tokens", () => {
220
231
  const result = resolvePricing(
221
232
  "gemini",
@@ -489,6 +500,7 @@ describe("resolvePricingForUsage", () => {
489
500
  const cases = [
490
501
  ["gemini-3-flash-preview", 0.05],
491
502
  ["gemini-3.1-flash-lite-preview", 0.025],
503
+ ["gemini-3.1-flash-lite", 0.025],
492
504
  ["gemini-2.5-flash", 0.03],
493
505
  ["gemini-2.5-flash-lite", 0.01],
494
506
  ["gemini-2.5-pro", 0.625],
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Unit coverage for the `parseDeletedCount` helper that reads the row
3
+ * count out of the sqlite3 CLI's stdout after a prune DELETE.
4
+ *
5
+ * The shape of the stdout we expect:
6
+ *
7
+ * "<n>\n"
8
+ *
9
+ * — where `<n>` is the `SELECT changes()` value the CLI prints in
10
+ * default output mode after the DELETE statement runs. The helper has
11
+ * to tolerate empty/missing stdout, blank trailing lines, and any
12
+ * incidental log line emitted above the count.
13
+ */
14
+
15
+ import { describe, expect, test } from "bun:test";
16
+
17
+ import { _parseDeletedCount as parseDeletedCount } from "../memory/job-handlers/cleanup.js";
18
+
19
+ describe("parseDeletedCount", () => {
20
+ test("bare integer on its own line", () => {
21
+ expect(parseDeletedCount("100\n")).toBe(100);
22
+ });
23
+
24
+ test("integer with trailing whitespace/blank lines", () => {
25
+ expect(parseDeletedCount("100\n\n")).toBe(100);
26
+ expect(parseDeletedCount(" 42 \n")).toBe(42);
27
+ });
28
+
29
+ test("zero is a valid count", () => {
30
+ expect(parseDeletedCount("0\n")).toBe(0);
31
+ });
32
+
33
+ test("PRUNE_LOG_BATCH_LIMIT value", () => {
34
+ expect(parseDeletedCount("1000\n")).toBe(1000);
35
+ });
36
+
37
+ test("undefined stdout (subprocess never wrote anything)", () => {
38
+ expect(parseDeletedCount(undefined)).toBe(0);
39
+ });
40
+
41
+ test("empty stdout", () => {
42
+ expect(parseDeletedCount("")).toBe(0);
43
+ });
44
+
45
+ test("only whitespace", () => {
46
+ expect(parseDeletedCount(" \n\n ")).toBe(0);
47
+ });
48
+
49
+ test("non-numeric lines are skipped, last numeric line wins", () => {
50
+ // Sample shape if the CLI ever logged an incidental warning above the count
51
+ expect(parseDeletedCount("warning: foo\n250\n")).toBe(250);
52
+ });
53
+
54
+ test("negative values are not accepted (sqlite changes() is unsigned)", () => {
55
+ expect(parseDeletedCount("-1\n")).toBe(0);
56
+ });
57
+
58
+ test("CRLF line endings", () => {
59
+ expect(parseDeletedCount("750\r\n")).toBe(750);
60
+ });
61
+ });
@@ -1,7 +1,6 @@
1
1
  import { afterAll, beforeEach, describe, expect, test } from "bun:test";
2
2
 
3
3
  import { RiskLevel } from "../permissions/types.js";
4
- import type { ToolDefinition } from "../providers/types.js";
5
4
  import {
6
5
  __clearRegistryForTesting,
7
6
  __resetRegistryForTesting,
@@ -30,13 +29,8 @@ function makeFakeTool(name: string): Tool {
30
29
  description: `Fake ${name}`,
31
30
  category: "test",
32
31
  defaultRiskLevel: RiskLevel.Low,
33
- getDefinition(): ToolDefinition {
34
- return {
35
- name,
36
- description: `Fake ${name}`,
37
- input_schema: { type: "object", properties: {}, required: [] },
38
- };
39
- },
32
+ executionTarget: "sandbox",
33
+ input_schema: { type: "object", properties: {}, required: [] },
40
34
  async execute(
41
35
  _input: Record<string, unknown>,
42
36
  _context: ToolContext,
@@ -103,7 +103,7 @@ mock.module("../permissions/checker.js", () => ({
103
103
  mock.module("../memory/tool-usage-store.js", () => ({
104
104
  recordToolInvocation: () => {},
105
105
  getRecentInvocations: () => [],
106
- rotateToolInvocations: () => 0,
106
+ rotateToolInvocations: async () => 0,
107
107
  }));
108
108
 
109
109
  mock.module("../tools/registry.js", () => ({
@@ -116,7 +116,7 @@ mock.module("../tools/registry.js", () => ({
116
116
  category: isGmailTool ? "gmail" : "credential-execution",
117
117
  defaultRiskLevel: "high",
118
118
  executionTarget: isGmailTool ? ("host" as const) : undefined,
119
- getDefinition: () => ({}),
119
+ input_schema: {},
120
120
  execute: async () => fakeToolResult,
121
121
  };
122
122
  },
@@ -0,0 +1,154 @@
1
+ /**
2
+ * `GET /v1/events` (`handleSubscribeAssistantEvents`) — bilingual scope
3
+ * resolution. Two query params are accepted, with distinct semantics:
4
+ *
5
+ * - `?conversationId=<internal-id>` — looks up the conversation row
6
+ * directly by its assistant-minted id. 404 if not found. Does NOT
7
+ * materialise a new row.
8
+ * - `?conversationKey=<external-key>` — resolves via the
9
+ * `conversation_keys` table; materialises on first use. Ignored when
10
+ * `conversationId` is also supplied.
11
+ *
12
+ * Companion to `runtime-events-sse.test.ts`, which exercises the broader
13
+ * `?conversationKey=` happy/error path.
14
+ */
15
+
16
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
17
+
18
+ mock.module("../util/logger.js", () => ({
19
+ getLogger: () =>
20
+ new Proxy({} as Record<string, unknown>, {
21
+ get: () => () => {},
22
+ }),
23
+ }));
24
+
25
+ mock.module("../config/loader.js", () => ({
26
+ getConfig: () => ({
27
+ ui: {},
28
+ model: "test",
29
+ provider: "test",
30
+ memory: { enabled: false },
31
+ rateLimit: { maxRequestsPerMinute: 0 },
32
+ secretDetection: { enabled: false },
33
+ }),
34
+ }));
35
+
36
+ import { getOrCreateConversation } from "../memory/conversation-key-store.js";
37
+ import { getDb } from "../memory/db-connection.js";
38
+ import { initializeDb } from "../memory/db-init.js";
39
+ import { buildAssistantEvent } from "../runtime/assistant-event.js";
40
+ import { AssistantEventHub } from "../runtime/assistant-event-hub.js";
41
+ import {
42
+ BadRequestError,
43
+ NotFoundError,
44
+ } from "../runtime/routes/errors.js";
45
+ import { handleSubscribeAssistantEvents } from "../runtime/routes/events-routes.js";
46
+
47
+ initializeDb();
48
+
49
+ describe("GET /v1/events — bilingual scope query params", () => {
50
+ beforeEach(() => {
51
+ const db = getDb();
52
+ db.run("DELETE FROM conversation_keys");
53
+ db.run("DELETE FROM conversations");
54
+ });
55
+
56
+ test("?conversationId=<existing-id> scopes the stream to that conversation", async () => {
57
+ // Materialise a conversation via the key path, then subscribe to it
58
+ // directly by its internal id.
59
+ const { conversationId } = getOrCreateConversation("sse-id-scope-source");
60
+
61
+ const ac = new AbortController();
62
+ const testHub = new AssistantEventHub();
63
+
64
+ const stream = handleSubscribeAssistantEvents(
65
+ {
66
+ queryParams: { conversationId },
67
+ abortSignal: ac.signal,
68
+ },
69
+ { hub: testHub },
70
+ );
71
+
72
+ const reader = stream.getReader();
73
+ // Consume the initial heartbeat.
74
+ const heartbeat = await reader.read();
75
+ expect(new TextDecoder().decode(heartbeat.value)).toBe(": heartbeat\n\n");
76
+
77
+ // Publish an event scoped to that conversation — should be delivered.
78
+ await testHub.publish(buildAssistantEvent({ type: "pong" }, conversationId));
79
+
80
+ const { value, done } = await reader.read();
81
+ ac.abort();
82
+
83
+ expect(done).toBe(false);
84
+ const frame = new TextDecoder().decode(value);
85
+ expect(frame).toContain("event: assistant_event");
86
+ expect(frame).toContain(`"conversationId":"${conversationId}"`);
87
+ });
88
+
89
+ test("?conversationId=<non-existent-id> throws NotFoundError", () => {
90
+ expect(() =>
91
+ handleSubscribeAssistantEvents({
92
+ queryParams: { conversationId: "does-not-exist" },
93
+ abortSignal: new AbortController().signal,
94
+ }),
95
+ ).toThrow(NotFoundError);
96
+ });
97
+
98
+ test("?conversationId is honored and ?conversationKey is ignored when both are present", async () => {
99
+ // Materialise two distinct conversations: one we'll subscribe to by id,
100
+ // one we'll publish to via the ignored key.
101
+ const { conversationId: idConv } = getOrCreateConversation("sse-id-wins");
102
+ const { conversationId: keyConv } = getOrCreateConversation(
103
+ "sse-key-ignored",
104
+ );
105
+ expect(idConv).not.toBe(keyConv);
106
+
107
+ const ac = new AbortController();
108
+ const testHub = new AssistantEventHub();
109
+
110
+ const stream = handleSubscribeAssistantEvents(
111
+ {
112
+ queryParams: {
113
+ conversationId: idConv,
114
+ conversationKey: "sse-key-ignored",
115
+ },
116
+ abortSignal: ac.signal,
117
+ },
118
+ { hub: testHub },
119
+ );
120
+ const reader = stream.getReader();
121
+ await reader.read(); // heartbeat
122
+
123
+ // Publish on the "key" conversation — should NOT be delivered (filter
124
+ // is locked to idConv because conversationId wins).
125
+ await testHub.publish(buildAssistantEvent({ type: "pong" }, keyConv));
126
+ // Publish on the "id" conversation — should be delivered.
127
+ await testHub.publish(buildAssistantEvent({ type: "pong" }, idConv));
128
+
129
+ const { value } = await reader.read();
130
+ ac.abort();
131
+ const frame = new TextDecoder().decode(value);
132
+
133
+ expect(frame).toContain(`"conversationId":"${idConv}"`);
134
+ expect(frame).not.toContain(`"conversationId":"${keyConv}"`);
135
+ });
136
+
137
+ test("empty conversationId is rejected with BadRequestError", () => {
138
+ expect(() =>
139
+ handleSubscribeAssistantEvents({
140
+ queryParams: { conversationId: "" },
141
+ abortSignal: new AbortController().signal,
142
+ }),
143
+ ).toThrow(BadRequestError);
144
+ });
145
+
146
+ test("empty conversationKey is still rejected (legacy parity)", () => {
147
+ expect(() =>
148
+ handleSubscribeAssistantEvents({
149
+ queryParams: { conversationKey: "" },
150
+ abortSignal: new AbortController().signal,
151
+ }),
152
+ ).toThrow(BadRequestError);
153
+ });
154
+ });