@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
@@ -0,0 +1,237 @@
1
+ import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
2
+ import { createServer, type Server, type Socket } from "node:net";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { afterEach,beforeEach, describe, expect, mock, test } from "bun:test";
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Isolated temp directory for the IPC socket
9
+ // ---------------------------------------------------------------------------
10
+ const testRoot = mkdtempSync(join(tmpdir(), "flag-listener-test-"));
11
+ const socketPath = join(testRoot, "gateway.sock");
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Mock socket-path resolution to use our test socket
15
+ // ---------------------------------------------------------------------------
16
+ mock.module("../ipc/socket-path.js", () => ({
17
+ resolveIpcSocketPath: (_name: string) => ({
18
+ path: socketPath,
19
+ source: "workspace" as const,
20
+ }),
21
+ getAssistantSocketPath: () => join(testRoot, "assistant.sock"),
22
+ }));
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Track calls to refreshOverridesFromGateway
26
+ // ---------------------------------------------------------------------------
27
+ let refreshCallCount = 0;
28
+
29
+ mock.module("../config/assistant-feature-flags.js", () => ({
30
+ refreshOverridesFromGateway: async () => {
31
+ refreshCallCount++;
32
+ },
33
+ initFeatureFlagOverrides: async () => {},
34
+ clearFeatureFlagOverridesCache: () => {},
35
+ isAssistantFeatureFlagEnabled: () => true,
36
+ _setOverridesForTesting: () => {},
37
+ }));
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Track calls to publishSyncInvalidation so we can assert the listener
41
+ // fans flag changes out onto the SSE hub.
42
+ // ---------------------------------------------------------------------------
43
+ let publishedTagSets: string[][] = [];
44
+
45
+ mock.module("../runtime/sync/sync-publisher.js", () => ({
46
+ publishSyncInvalidation: async (tags: string[]) => {
47
+ publishedTagSets.push([...tags]);
48
+ return { type: "sync_changed", tags };
49
+ },
50
+ }));
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Dynamic imports (after mock.module)
54
+ // ---------------------------------------------------------------------------
55
+ const { startGatewayFlagListener, stopGatewayFlagListener } = await import(
56
+ "../ipc/gateway-flag-listener.js"
57
+ );
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Helpers
61
+ // ---------------------------------------------------------------------------
62
+
63
+ function createTestServer(): {
64
+ server: Server;
65
+ clients: Set<Socket>;
66
+ emit: (event: string, data?: unknown) => void;
67
+ waitForClient: () => Promise<Socket>;
68
+ } {
69
+ const clients = new Set<Socket>();
70
+ const clientWaiters: Array<(socket: Socket) => void> = [];
71
+
72
+ const server = createServer((socket) => {
73
+ clients.add(socket);
74
+ socket.on("close", () => clients.delete(socket));
75
+ const waiter = clientWaiters.shift();
76
+ if (waiter) waiter(socket);
77
+ });
78
+
79
+ return {
80
+ server,
81
+ clients,
82
+ emit: (event: string, data?: unknown) => {
83
+ const payload = JSON.stringify({ event, data }) + "\n";
84
+ for (const client of clients) {
85
+ if (!client.destroyed) client.write(payload);
86
+ }
87
+ },
88
+ waitForClient: () =>
89
+ new Promise((resolve) => {
90
+ if (clients.size > 0) {
91
+ resolve(clients.values().next().value!);
92
+ return;
93
+ }
94
+ clientWaiters.push(resolve);
95
+ }),
96
+ };
97
+ }
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Tests
101
+ // ---------------------------------------------------------------------------
102
+ describe("gateway-flag-listener", () => {
103
+ let testServer: ReturnType<typeof createTestServer>;
104
+
105
+ beforeEach(() => {
106
+ mkdirSync(testRoot, { recursive: true });
107
+ refreshCallCount = 0;
108
+ publishedTagSets = [];
109
+ testServer = createTestServer();
110
+ });
111
+
112
+ afterEach(async () => {
113
+ stopGatewayFlagListener();
114
+ await new Promise<void>((resolve) => {
115
+ for (const client of testServer.clients) {
116
+ if (!client.destroyed) client.destroy();
117
+ }
118
+ testServer.server.close(() => resolve());
119
+ });
120
+ try {
121
+ rmSync(socketPath, { force: true });
122
+ } catch {
123
+ // best effort
124
+ }
125
+ });
126
+
127
+ test("refreshes flag cache on connect and on feature_flags_changed event", async () => {
128
+ await new Promise<void>((resolve) => {
129
+ testServer.server.listen(socketPath, resolve);
130
+ });
131
+
132
+ startGatewayFlagListener();
133
+ await testServer.waitForClient();
134
+ await new Promise((r) => setTimeout(r, 100));
135
+
136
+ expect(refreshCallCount).toBe(1);
137
+
138
+ testServer.emit("feature_flags_changed");
139
+ await new Promise((r) => setTimeout(r, 100));
140
+
141
+ expect(refreshCallCount).toBe(2);
142
+ });
143
+
144
+ test("broadcasts feature-flags sync_changed when flags change", async () => {
145
+ await new Promise<void>((resolve) => {
146
+ testServer.server.listen(socketPath, resolve);
147
+ });
148
+
149
+ startGatewayFlagListener();
150
+ await testServer.waitForClient();
151
+ await new Promise((r) => setTimeout(r, 100));
152
+
153
+ // Connect refresh should not broadcast — only an actual change does.
154
+ expect(publishedTagSets.length).toBe(0);
155
+
156
+ testServer.emit("feature_flags_changed");
157
+ await new Promise((r) => setTimeout(r, 100));
158
+
159
+ expect(publishedTagSets.length).toBe(1);
160
+ expect(publishedTagSets[0]).toEqual([
161
+ "feature-flags:client",
162
+ "feature-flags:assistant",
163
+ ]);
164
+ });
165
+
166
+ test("ignores non-flag events", async () => {
167
+ await new Promise<void>((resolve) => {
168
+ testServer.server.listen(socketPath, resolve);
169
+ });
170
+
171
+ startGatewayFlagListener();
172
+ await testServer.waitForClient();
173
+ await new Promise((r) => setTimeout(r, 100));
174
+
175
+ const countAfterConnect = refreshCallCount;
176
+
177
+ testServer.emit("some_other_event");
178
+ await new Promise((r) => setTimeout(r, 100));
179
+
180
+ expect(refreshCallCount).toBe(countAfterConnect);
181
+ });
182
+
183
+ test("reconnects on disconnect and handles events on new connection", async () => {
184
+ await new Promise<void>((resolve) => {
185
+ testServer.server.listen(socketPath, resolve);
186
+ });
187
+
188
+ startGatewayFlagListener();
189
+ const firstClient = await testServer.waitForClient();
190
+
191
+ // Wait for the close event to propagate back to the server before
192
+ // setting up the next waitForClient — otherwise waitForClient might
193
+ // resolve with the old (now-destroyed) socket that is still in the set.
194
+ await new Promise<void>((resolve) => {
195
+ firstClient.on("close", resolve);
196
+ firstClient.destroy();
197
+ });
198
+
199
+ // Wait for reconnect (initial backoff is 1s)
200
+ const secondClient = await Promise.race([
201
+ testServer.waitForClient(),
202
+ new Promise<null>((r) => setTimeout(() => r(null), 3000)),
203
+ ]);
204
+
205
+ expect(secondClient).not.toBeNull();
206
+
207
+ await new Promise((r) => setTimeout(r, 100));
208
+ const countAfterReconnect = refreshCallCount;
209
+ expect(countAfterReconnect).toBeGreaterThan(0);
210
+
211
+ const payload =
212
+ JSON.stringify({ event: "feature_flags_changed" }) + "\n";
213
+ secondClient!.write(payload);
214
+ await new Promise((r) => setTimeout(r, 200));
215
+
216
+ expect(refreshCallCount).toBe(countAfterReconnect + 1);
217
+ });
218
+
219
+ test("stopGatewayFlagListener cleans up and does not reconnect", async () => {
220
+ await new Promise<void>((resolve) => {
221
+ testServer.server.listen(socketPath, resolve);
222
+ });
223
+
224
+ startGatewayFlagListener();
225
+ await testServer.waitForClient();
226
+
227
+ stopGatewayFlagListener();
228
+ await new Promise((r) => setTimeout(r, 50));
229
+
230
+ const initialClientCount = testServer.clients.size;
231
+
232
+ // Wait past reconnect backoff — should not reconnect
233
+ await new Promise((r) => setTimeout(r, 1500));
234
+
235
+ expect(testServer.clients.size).toBeLessThanOrEqual(initialClientCount);
236
+ });
237
+ });
@@ -75,6 +75,12 @@ mock.module("@google/genai", () => ({
75
75
  };
76
76
  },
77
77
  ApiError: FakeApiError,
78
+ ThinkingLevel: {
79
+ MINIMAL: "MINIMAL",
80
+ LOW: "LOW",
81
+ MEDIUM: "MEDIUM",
82
+ HIGH: "HIGH",
83
+ },
78
84
  }));
79
85
 
80
86
  // Import after mocking
@@ -223,6 +229,78 @@ describe("GeminiProvider", () => {
223
229
  expect(config.systemInstruction).toBe("You are a helpful assistant.");
224
230
  });
225
231
 
232
+ // -----------------------------------------------------------------------
233
+ // Thinking config
234
+ // -----------------------------------------------------------------------
235
+ test("omits thinkingConfig when no thinking config is supplied", async () => {
236
+ fakeChunks = [textChunk("OK"), finishChunk("STOP", 10, 2)];
237
+
238
+ await provider.sendMessage([
239
+ { role: "user", content: [{ type: "text", text: "Hi" }] },
240
+ ]);
241
+
242
+ const config = lastStreamParams!.config as Record<string, unknown>;
243
+ expect(config.thinkingConfig).toBeUndefined();
244
+ });
245
+
246
+ test("maps wire { type: 'adaptive', level, streamThinking } to Gemini thinkingConfig", async () => {
247
+ fakeChunks = [textChunk("OK"), finishChunk("STOP", 10, 2)];
248
+
249
+ await provider.sendMessage(
250
+ [{ role: "user", content: [{ type: "text", text: "Hi" }] }],
251
+ undefined,
252
+ undefined,
253
+ {
254
+ config: {
255
+ thinking: {
256
+ type: "adaptive",
257
+ level: "high",
258
+ streamThinking: false,
259
+ },
260
+ },
261
+ },
262
+ );
263
+
264
+ const config = lastStreamParams!.config as Record<string, unknown>;
265
+ expect(config.thinkingConfig).toEqual({
266
+ thinkingLevel: "HIGH",
267
+ includeThoughts: false,
268
+ });
269
+ });
270
+
271
+ test("maps wire { type: 'disabled' } to MINIMAL thinking level", async () => {
272
+ fakeChunks = [textChunk("OK"), finishChunk("STOP", 10, 2)];
273
+
274
+ await provider.sendMessage(
275
+ [{ role: "user", content: [{ type: "text", text: "Hi" }] }],
276
+ undefined,
277
+ undefined,
278
+ { config: { thinking: { type: "disabled" } } },
279
+ );
280
+
281
+ const config = lastStreamParams!.config as Record<string, unknown>;
282
+ expect(config.thinkingConfig).toEqual({
283
+ thinkingLevel: "MINIMAL",
284
+ includeThoughts: false,
285
+ });
286
+ });
287
+
288
+ test("omits thinkingConfig when wire shape is adaptive with no extras", async () => {
289
+ fakeChunks = [textChunk("OK"), finishChunk("STOP", 10, 2)];
290
+
291
+ await provider.sendMessage(
292
+ [{ role: "user", content: [{ type: "text", text: "Hi" }] }],
293
+ undefined,
294
+ undefined,
295
+ { config: { thinking: { type: "adaptive" } } },
296
+ );
297
+
298
+ const config = lastStreamParams!.config as Record<string, unknown>;
299
+ // No level/streamThinking → omit so Google's per-model default applies
300
+ // (Gemini 3.x defaults to "medium" with dynamic thinking).
301
+ expect(config.thinkingConfig).toBeUndefined();
302
+ });
303
+
226
304
  // -----------------------------------------------------------------------
227
305
  // Tool definitions
228
306
  // -----------------------------------------------------------------------
@@ -56,7 +56,6 @@ mock.module("../notifications/emit-signal.js", () => ({
56
56
  }
57
57
  return mockEmitResult;
58
58
  },
59
- registerBroadcastFn: () => {},
60
59
  }));
61
60
 
62
61
  import {
@@ -280,11 +280,13 @@ describe("startOutbound", () => {
280
280
  expect(voiceCallInitCalls.length).toBe(1);
281
281
  });
282
282
 
283
- test("unsupported channel returns unsupported_channel", async () => {
284
- // Cast to bypass type checking for test purposes
285
- const result = await startOutbound({ channel: "email" as never });
286
- expect(result.success).toBe(false);
287
- expect(result.error).toBe("unsupported_channel");
283
+ test("email channel creates outbound session", async () => {
284
+ const result = await startOutbound({
285
+ channel: "email",
286
+ destination: "user@example.com",
287
+ });
288
+ expect(result.success).toBe(true);
289
+ expect(result.channel).toBe("email");
288
290
  });
289
291
  });
290
292
 
@@ -193,6 +193,6 @@ describe("handleConfirmationResponse canonical status sync", () => {
193
193
  // Canonical status sync is now handled inside Conversation.handleConfirmationResponse,
194
194
  // which this test mocks out — so the handler itself no longer calls resolveCanonicalGuardianRequest.
195
195
  expect(resolveCanonicalGuardianRequestMock).not.toHaveBeenCalled();
196
- expect(resolveMock).toHaveBeenCalledWith("req-confirm-allow");
196
+ expect(resolveMock).toHaveBeenCalledWith("req-confirm-allow", "approved");
197
197
  });
198
198
  });
@@ -91,6 +91,10 @@ mock.module("../notifications/emit-signal.js", () => ({
91
91
  mock.module("../prompts/persona-resolver.js", () => ({
92
92
  GUARDIAN_PERSONA_TEMPLATE: "# User Profile\n",
93
93
  resolveGuardianPersona: () => "# User Profile\n",
94
+ // buildSystemPrompt now uses resolveUserSlug (for ctx) instead of
95
+ // resolvePersonaContext — give the mock a noop so the import
96
+ // doesn't fail.
97
+ resolveUserSlug: () => null,
94
98
  }));
95
99
 
96
100
  mock.module("../memory/conversation-title-service.js", () => ({
@@ -112,6 +112,10 @@ let mockGuardianPersona: string | null = null;
112
112
  mock.module("../prompts/persona-resolver.js", () => ({
113
113
  GUARDIAN_PERSONA_TEMPLATE,
114
114
  resolveGuardianPersona: () => mockGuardianPersona,
115
+ // buildSystemPrompt now uses resolveUserSlug (for ctx) instead of
116
+ // resolvePersonaContext — give the mock a noop so the import
117
+ // doesn't fail.
118
+ resolveUserSlug: () => null,
115
119
  }));
116
120
 
117
121
  // Mock conversation store
@@ -264,7 +264,7 @@ describe("host_bash — baseline: no sandbox isolation", () => {
264
264
  // These tests lock that boundary so any accidental addition is caught.
265
265
 
266
266
  describe("host_bash — regression: no proxied-mode additions", () => {
267
- const definition = hostShellTool.getDefinition();
267
+ const definition = hostShellTool;
268
268
  const schemaProps = (definition.input_schema as Record<string, unknown>)
269
269
  .properties as Record<string, unknown>;
270
270
 
@@ -47,9 +47,9 @@ describe("initFeatureFlagOverrides", () => {
47
47
  // through the backoff schedule.
48
48
  await initFeatureFlagOverrides({ retryBackoffsMs: [] });
49
49
 
50
- // Without gateway data or file, undeclared flags default to true
50
+ // Without gateway data or file, undeclared flags default to false
51
51
  const config = {} as any;
52
- expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(true);
52
+ expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(false);
53
53
  });
54
54
 
55
55
  it("respects false values from gateway IPC", async () => {
@@ -72,10 +72,9 @@ describe("initFeatureFlagOverrides", () => {
72
72
  // through the production backoff schedule.
73
73
  await initFeatureFlagOverrides({ retryBackoffsMs: [] });
74
74
 
75
- // Undeclared flags without overrides default to true (not false from
76
- // a cached empty map)
75
+ // Undeclared flags without overrides default to false.
77
76
  const config = {} as any;
78
- expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(true);
77
+ expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(false);
79
78
  });
80
79
 
81
80
  it("retries empty gateway responses and picks up flags once they become available", async () => {
@@ -111,6 +110,6 @@ describe("initFeatureFlagOverrides", () => {
111
110
  // first-call must still be true (from the cached first fetch)
112
111
  expect(isAssistantFeatureFlagEnabled("first-call", config)).toBe(true);
113
112
  // second-call should not be in the cache since init was a no-op
114
- expect(isAssistantFeatureFlagEnabled("second-call", config)).toBe(true); // defaults to true (undeclared)
113
+ expect(isAssistantFeatureFlagEnabled("second-call", config)).toBe(false);
115
114
  });
116
115
  });
@@ -175,15 +175,13 @@ describe("handleListMessages tool_result merging", () => {
175
175
  expect(body.messages[2].content).toBe("how are you?");
176
176
  });
177
177
 
178
- test("tool_result at start of array (no preceding assistant) is dropped", async () => {
178
+ test("orphan tool_result at pagination boundary is suppressed entirely", async () => {
179
179
  const conv = createConversation();
180
- // Orphan tool_result with no preceding assistant. Without the parent
181
- // tool_use we can't tell the user what tool ran, so the result is
182
- // meaningless renderHistoryContent drops it rather than synthesizing
183
- // a phantom "unknown" tool call. See shared.ts comment.
184
- // The user message itself is preserved at the pagination boundary
185
- // (mergeToolResultsIntoAssistantMessages keeps it to avoid data loss
186
- // in case the matching tool_use lives on the previous page).
180
+ // Orphan tool_result with no preceding assistant in this page. The
181
+ // matching tool_use lives on the previous page, so renderHistoryContent
182
+ // would silently drop the orphan downstream and leave a blank user
183
+ // bubble. The merger now suppresses the user row outright at the
184
+ // boundary to prevent that blank bubble from reaching the client.
187
185
  await addMessage(
188
186
  conv.id,
189
187
  "user",
@@ -204,13 +202,74 @@ describe("handleListMessages tool_result merging", () => {
204
202
  const response = handleListMessages(createTestArgs(conv.id));
205
203
  const body = response as { messages: MessagePayload[] };
206
204
 
207
- // Both messages exist user message preserved at pagination boundary,
208
- // but the orphan tool_result is dropped (no phantom toolCalls)
205
+ // User row dropped entirely; only the assistant survives.
206
+ expect(body.messages).toHaveLength(1);
207
+ expect(body.messages[0].role).toBe("assistant");
208
+ expect(body.messages[0].content).toBe("response");
209
+ });
210
+
211
+ test("mixed content at pagination boundary keeps real user text", async () => {
212
+ const conv = createConversation();
213
+ // Row has both real user text AND an orphan tool_result. The orphan
214
+ // gets stripped (it'd be dropped by renderHistoryContent anyway), but
215
+ // the user text is preserved.
216
+ await addMessage(
217
+ conv.id,
218
+ "user",
219
+ JSON.stringify([
220
+ {
221
+ type: "tool_result",
222
+ tool_use_id: "tu_orphan",
223
+ content: "stale result",
224
+ },
225
+ { type: "text", text: "what about this?" },
226
+ ]),
227
+ );
228
+ await addMessage(
229
+ conv.id,
230
+ "assistant",
231
+ JSON.stringify([{ type: "text", text: "answering" }]),
232
+ );
233
+
234
+ const response = handleListMessages(createTestArgs(conv.id));
235
+ const body = response as { messages: MessagePayload[] };
236
+
209
237
  expect(body.messages).toHaveLength(2);
210
238
  expect(body.messages[0].role).toBe("user");
239
+ expect(body.messages[0].content).toBe("what about this?");
211
240
  expect(body.messages[0].toolCalls).toBeUndefined();
212
241
  expect(body.messages[1].role).toBe("assistant");
213
- expect(body.messages[1].content).toBe("response");
242
+ expect(body.messages[1].content).toBe("answering");
243
+ });
244
+
245
+ test("orphan tool_result + system_notice at boundary is suppressed", async () => {
246
+ const conv = createConversation();
247
+ // System notices ride alongside tool_results in the agent loop. At a
248
+ // boundary they shouldn't keep the row alive on their own — the row
249
+ // is still semantically tool-result-only from the user's perspective.
250
+ await addMessage(
251
+ conv.id,
252
+ "user",
253
+ JSON.stringify([
254
+ {
255
+ type: "tool_result",
256
+ tool_use_id: "tu_orphan",
257
+ content: "stale",
258
+ },
259
+ { type: "text", text: "<system_notice>internal</system_notice>" },
260
+ ]),
261
+ );
262
+ await addMessage(
263
+ conv.id,
264
+ "assistant",
265
+ JSON.stringify([{ type: "text", text: "ok" }]),
266
+ );
267
+
268
+ const response = handleListMessages(createTestArgs(conv.id));
269
+ const body = response as { messages: MessagePayload[] };
270
+
271
+ expect(body.messages).toHaveLength(1);
272
+ expect(body.messages[0].role).toBe("assistant");
214
273
  });
215
274
 
216
275
  test("multi-turn: each tool_result merges into correct assistant", async () => {
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Tests for the `call_site` column on `llm_request_logs`. The column is
3
+ * stamped by `recordRequestLog` at insertion time and surfaces in
4
+ * `LogRow.callSite`. Historical rows (pre-migration 264) stay NULL —
5
+ * "we don't know" rather than guessing `mainAgent`.
6
+ */
7
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
8
+
9
+ mock.module("../util/logger.js", () => ({
10
+ getLogger: () =>
11
+ new Proxy({} as Record<string, unknown>, {
12
+ get: () => () => {},
13
+ }),
14
+ }));
15
+
16
+ mock.module("../config/loader.js", () => ({
17
+ getConfig: () => ({
18
+ ui: {},
19
+ model: "test",
20
+ provider: "test",
21
+ memory: { enabled: false },
22
+ rateLimit: { maxRequestsPerMinute: 0 },
23
+ secretDetection: { enabled: false },
24
+ }),
25
+ }));
26
+
27
+ import { getDb, getSqliteFrom } from "../memory/db-connection.js";
28
+ import { initializeDb } from "../memory/db-init.js";
29
+ import {
30
+ getRequestLogById,
31
+ recordRequestLog,
32
+ } from "../memory/llm-request-log-store.js";
33
+ import { migrateLlmRequestLogCallSite } from "../memory/migrations/264-llm-request-log-call-site.js";
34
+ import { llmRequestLogs } from "../memory/schema.js";
35
+
36
+ initializeDb();
37
+
38
+ function resetLogs(): void {
39
+ const db = getDb();
40
+ db.delete(llmRequestLogs).run();
41
+ }
42
+
43
+ describe("recordRequestLog call_site stamping", () => {
44
+ beforeEach(resetLogs);
45
+
46
+ test("stamps callSite when provided", () => {
47
+ const id = recordRequestLog(
48
+ "conv-1",
49
+ '{"req":1}',
50
+ '{"res":1}',
51
+ undefined,
52
+ "anthropic",
53
+ "mainAgent",
54
+ );
55
+ const row = getRequestLogById(id);
56
+ expect(row).not.toBeNull();
57
+ expect(row!.callSite).toBe("mainAgent");
58
+ });
59
+
60
+ test("leaves callSite NULL when omitted (backward compat)", () => {
61
+ const id = recordRequestLog("conv-1", '{"req":1}', '{"res":1}');
62
+ const row = getRequestLogById(id);
63
+ expect(row).not.toBeNull();
64
+ expect(row!.callSite).toBeNull();
65
+ });
66
+
67
+ test("supports the compactionAgent value", () => {
68
+ const id = recordRequestLog(
69
+ "conv-1",
70
+ '{"req":1}',
71
+ '{"res":1}',
72
+ undefined,
73
+ "anthropic",
74
+ "compactionAgent",
75
+ );
76
+ expect(getRequestLogById(id)?.callSite).toBe("compactionAgent");
77
+ });
78
+
79
+ test("two rows in the same conversation can carry different callSites", () => {
80
+ const mainId = recordRequestLog(
81
+ "conv-1",
82
+ '{"req":1}',
83
+ '{"res":1}',
84
+ undefined,
85
+ "anthropic",
86
+ "mainAgent",
87
+ );
88
+ const compactId = recordRequestLog(
89
+ "conv-1",
90
+ '{"req":2}',
91
+ '{"res":2}',
92
+ undefined,
93
+ "anthropic",
94
+ "compactionAgent",
95
+ );
96
+ expect(getRequestLogById(mainId)?.callSite).toBe("mainAgent");
97
+ expect(getRequestLogById(compactId)?.callSite).toBe("compactionAgent");
98
+ });
99
+ });
100
+
101
+ describe("migrateLlmRequestLogCallSite", () => {
102
+ test("adds the call_site column when missing", () => {
103
+ const db = getDb();
104
+ const raw = getSqliteFrom(db);
105
+
106
+ // Drop the column if present (simulate pre-264 state). SQLite supports
107
+ // `DROP COLUMN` since 3.35 (June 2021) — bun-sqlite ships well past that.
108
+ const before = raw
109
+ .query(`PRAGMA table_info(llm_request_logs)`)
110
+ .all() as Array<{ name: string }>;
111
+ if (before.some((c) => c.name === "call_site")) {
112
+ raw.exec(`ALTER TABLE llm_request_logs DROP COLUMN call_site`);
113
+ }
114
+
115
+ const without = raw
116
+ .query(`PRAGMA table_info(llm_request_logs)`)
117
+ .all() as Array<{ name: string }>;
118
+ expect(without.some((c) => c.name === "call_site")).toBe(false);
119
+
120
+ migrateLlmRequestLogCallSite(db);
121
+
122
+ const after = raw
123
+ .query(`PRAGMA table_info(llm_request_logs)`)
124
+ .all() as Array<{ name: string }>;
125
+ expect(after.some((c) => c.name === "call_site")).toBe(true);
126
+ });
127
+
128
+ test("is idempotent — second run is a no-op", () => {
129
+ const db = getDb();
130
+ // First run (column may or may not exist depending on test order; either
131
+ // path is fine for the idempotency contract).
132
+ migrateLlmRequestLogCallSite(db);
133
+ // Second run must not throw.
134
+ expect(() => migrateLlmRequestLogCallSite(db)).not.toThrow();
135
+ });
136
+ });