@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,305 @@
1
+ /**
2
+ * Memory v3 — live-shadow `memoryRetrieval` middleware.
3
+ *
4
+ * Registered unconditionally into the `memoryRetrieval` pipeline, but inert
5
+ * unless BOTH `config.memory.v3.enabled` and `config.memory.v3.shadow` are on.
6
+ * When inert it is a byte-for-byte pass-through: it returns `next(args)`
7
+ * verbatim and performs zero extra work (no v3 call, no DB read, no log write).
8
+ *
9
+ * When active, it:
10
+ * 1. Returns the real (v2/default) `MemoryResult` from `next(args)` promptly —
11
+ * the injected context is ALWAYS the v2 result, never v3.
12
+ * 2. Kicks off the v3 retrieval loop DETACHED (not awaited on the path that
13
+ * returns the result), so the shadow run can never block or slow the turn.
14
+ * 3. Logs v3's selection set to `memory_v2_activation_logs` with
15
+ * `mode = "v3_shadow"`. The harness oracle filters `mode='router'`, so
16
+ * shadow rows never pollute it; the inspector can still surface them.
17
+ *
18
+ * The shadow build mirrors the inputs the v2 router receives (recent turn
19
+ * pairs, NOW context, prior-ever-injected slugs, config) so its recall is
20
+ * measured against the same situational context the live path saw. Failures
21
+ * are swallowed with a warn — the shadow is observational only and must never
22
+ * affect the live turn.
23
+ */
24
+
25
+ import { desc, eq } from "drizzle-orm";
26
+
27
+ import { getConfig } from "../../config/loader.js";
28
+ import { registerPlugin } from "../../plugins/registry.js";
29
+ import {
30
+ type MemoryArgs,
31
+ type MemoryResult,
32
+ type Middleware,
33
+ type Plugin,
34
+ PluginExecutionError,
35
+ } from "../../plugins/types.js";
36
+ import type { ContentBlock } from "../../providers/types.js";
37
+ import { getLogger } from "../../util/logger.js";
38
+ import { getWorkspaceDir } from "../../util/platform.js";
39
+ import type { DrizzleDb } from "../db-connection.js";
40
+ import { getDb } from "../db-connection.js";
41
+ import {
42
+ type MemoryV2ConceptRowRecord,
43
+ type MemoryV2ConfigSnapshot,
44
+ recordMemoryV2ActivationLog,
45
+ } from "../memory-v2-activation-log-store.js";
46
+ import { messages } from "../schema.js";
47
+ import { hydrate } from "../v2/activation-store.js";
48
+ import type { RetrievalInput } from "../v2/harness/retriever.js";
49
+ import { loadNowText } from "../v2/now-text.js";
50
+ import type { RouterTurnPair } from "../v2/router.js";
51
+ import type { EverInjectedEntry } from "../v2/types.js";
52
+ import { runRetrievalLoop } from "./loop.js";
53
+
54
+ const log = getLogger("memory-v3-shadow");
55
+
56
+ /**
57
+ * Extract the recent (assistant, user) turn pairs from a conversation's
58
+ * message list, newest-pair-last, capped at `k`. Mirrors production
59
+ * `extractRecentTurnPairs` in `conversation-graph-memory.ts` (and its harness
60
+ * twin in `replay-input.ts`) so the shadow's `recentTurnPairs` matches what the
61
+ * live router was fed.
62
+ */
63
+ function extractRecentTurnPairs(
64
+ msgs: ReadonlyArray<{ role: string; content: ContentBlock[] }>,
65
+ k: number,
66
+ ): RouterTurnPair[] {
67
+ const messageText = (content: ContentBlock[]): string =>
68
+ content
69
+ .filter(
70
+ (b): b is Extract<ContentBlock, { type: "text" }> => b.type === "text",
71
+ )
72
+ .map((b) => b.text)
73
+ .join(" ");
74
+
75
+ const pairs: RouterTurnPair[] = [];
76
+ let pendingUser: string | null = null;
77
+ for (let i = msgs.length - 1; i >= 0 && pairs.length < k; i--) {
78
+ const msg = msgs[i]!;
79
+ if (msg.role === "user" && pendingUser === null) {
80
+ pendingUser = messageText(msg.content);
81
+ } else if (msg.role === "assistant" && pendingUser !== null) {
82
+ pairs.unshift({
83
+ assistantMessage: messageText(msg.content),
84
+ userMessage: pendingUser,
85
+ });
86
+ pendingUser = null;
87
+ }
88
+ }
89
+ if (pendingUser !== null && pairs.length < k) {
90
+ pairs.unshift({ assistantMessage: "", userMessage: pendingUser });
91
+ }
92
+ if (pairs.length === 0) {
93
+ pairs.push({ assistantMessage: "", userMessage: "" });
94
+ }
95
+ return pairs;
96
+ }
97
+
98
+ /** Parse a persisted JSON content-block string; tolerate malformed rows. */
99
+ function parseContent(raw: string): ContentBlock[] {
100
+ try {
101
+ const parsed = JSON.parse(raw);
102
+ return Array.isArray(parsed) ? (parsed as ContentBlock[]) : [];
103
+ } catch {
104
+ return [];
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Load the most recent messages for a conversation, oldest-first, bounded to a
110
+ * small generous multiple of `historicalPairs`. Pair extraction only needs the
111
+ * tail, so a bounded `LIMIT` query avoids loading an entire (potentially
112
+ * multi-GB) conversation on every shadow turn — mirrors the harness's bounded
113
+ * fetch in `replay-input.ts`.
114
+ */
115
+ function loadRecentMessages(
116
+ db: DrizzleDb,
117
+ conversationId: string,
118
+ historicalPairs: number,
119
+ ): Array<{ role: string; content: ContentBlock[] }> {
120
+ const fetchWindow = Math.max(20, historicalPairs * 12);
121
+ const rows = db
122
+ .select({ role: messages.role, content: messages.content })
123
+ .from(messages)
124
+ .where(eq(messages.conversationId, conversationId))
125
+ .orderBy(desc(messages.createdAt), desc(messages.id))
126
+ .limit(fetchWindow)
127
+ .all();
128
+ return rows
129
+ .reverse()
130
+ .map((r) => ({ role: r.role, content: parseContent(r.content) }));
131
+ }
132
+
133
+ /**
134
+ * Empty config snapshot for shadow log rows. The activation-state values are
135
+ * meaningless for a v3 selection (it computes no spreading-activation scores),
136
+ * so they are zeroed — exactly as the v2 router-mode rows do.
137
+ */
138
+ const SHADOW_CONFIG_SNAPSHOT: MemoryV2ConfigSnapshot = {
139
+ d: 0,
140
+ c_user: 0,
141
+ c_assistant: 0,
142
+ c_now: 0,
143
+ k: 0,
144
+ hops: 0,
145
+ top_k: 0,
146
+ epsilon: 0,
147
+ };
148
+
149
+ /**
150
+ * Build the concept rows logged for a v3 shadow selection. Each selected slug
151
+ * becomes a zeroed concept row tagged `source: "router"` and
152
+ * `status: "injected"` — the shadow has no activation scores to record, and the
153
+ * `mode='v3_shadow'` row tag (not the concept source) is what distinguishes
154
+ * shadow telemetry from live router selections.
155
+ */
156
+ function buildShadowConceptRows(
157
+ selectedSlugs: readonly string[],
158
+ ): MemoryV2ConceptRowRecord[] {
159
+ return selectedSlugs.map((slug) => ({
160
+ slug,
161
+ finalActivation: 0,
162
+ ownActivation: 0,
163
+ priorActivation: 0,
164
+ simUser: 0,
165
+ simAssistant: 0,
166
+ simNow: 0,
167
+ simUserRerankBoost: 0,
168
+ simAssistantRerankBoost: 0,
169
+ inRerankPool: false,
170
+ spreadContribution: 0,
171
+ source: "router",
172
+ status: "injected",
173
+ }));
174
+ }
175
+
176
+ /**
177
+ * Run the v3 retrieval loop for the shadow and log its selection. Best-effort:
178
+ * any failure is logged and swallowed. Honors `signal` so a cancelled turn
179
+ * stops the shadow's lane work.
180
+ */
181
+ async function runShadowAndLog(
182
+ args: MemoryArgs,
183
+ signal: AbortSignal,
184
+ ): Promise<void> {
185
+ try {
186
+ if (signal.aborted) return;
187
+
188
+ const config = getConfig();
189
+ const workspaceDir = getWorkspaceDir();
190
+ const db = getDb();
191
+
192
+ const historicalPairs = config.memory.v2.router.historical_pairs;
193
+ const recentMessages = loadRecentMessages(
194
+ db,
195
+ args.conversationId,
196
+ historicalPairs,
197
+ );
198
+ const recentTurnPairs = extractRecentTurnPairs(
199
+ recentMessages,
200
+ historicalPairs,
201
+ );
202
+
203
+ const nowText = await loadNowText(workspaceDir);
204
+
205
+ let priorEverInjected: readonly EverInjectedEntry[] = [];
206
+ try {
207
+ const state = await hydrate(db, args.conversationId);
208
+ priorEverInjected = state?.everInjected ?? [];
209
+ } catch (err) {
210
+ log.warn(
211
+ { err, conversationId: args.conversationId },
212
+ "v3 shadow: failed to hydrate prior-ever-injected; continuing with empty set",
213
+ );
214
+ }
215
+
216
+ if (signal.aborted) return;
217
+
218
+ const input: RetrievalInput = {
219
+ workspaceDir,
220
+ recentTurnPairs,
221
+ nowText,
222
+ priorEverInjected,
223
+ config,
224
+ signal,
225
+ };
226
+
227
+ const output = await runRetrievalLoop(input, {
228
+ db,
229
+ conversationId: args.conversationId,
230
+ turn: args.turnIndex,
231
+ });
232
+
233
+ if (signal.aborted) return;
234
+
235
+ recordMemoryV2ActivationLog({
236
+ conversationId: args.conversationId,
237
+ turn: args.turnIndex,
238
+ mode: "v3_shadow",
239
+ concepts: buildShadowConceptRows(output.selectedSlugs),
240
+ config: SHADOW_CONFIG_SNAPSHOT,
241
+ });
242
+ } catch (err) {
243
+ log.warn(
244
+ { err, conversationId: args.conversationId, turn: args.turnIndex },
245
+ "v3 shadow retrieval failed; live turn unaffected",
246
+ );
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Live-shadow `memoryRetrieval` middleware.
252
+ *
253
+ * Flag-gated INSIDE the middleware (per-turn, live-toggle): when v3 shadow is
254
+ * off it is a pure pass-through. When on, it fires the v3 loop detached and
255
+ * returns the unchanged downstream (v2) result immediately.
256
+ */
257
+ export const memoryV3ShadowMiddleware: Middleware<MemoryArgs, MemoryResult> =
258
+ async function memoryV3Shadow(args, next) {
259
+ const v3 = getConfig().memory.v3;
260
+ if (!v3?.enabled || !v3?.shadow) {
261
+ // Inert: byte-for-byte pass-through, zero extra work.
262
+ return next(args);
263
+ }
264
+
265
+ // Detached — never awaited on the path that returns the result, so the
266
+ // shadow can neither block nor slow the live turn. Errors are swallowed
267
+ // inside `runShadowAndLog`.
268
+ void runShadowAndLog(args, args.signal);
269
+
270
+ return next(args);
271
+ };
272
+
273
+ /**
274
+ * First-party plugin contributing the live-shadow `memoryRetrieval`
275
+ * middleware. Registered unconditionally by the plugin bootstrap (it is inert
276
+ * unless both v3 flags are on), so the registration is always present but does
277
+ * zero work in the default (flags-off) configuration.
278
+ */
279
+ export const memoryV3ShadowPlugin: Plugin = {
280
+ manifest: {
281
+ name: "memory-v3-shadow",
282
+ version: "0.0.1",
283
+ },
284
+ middleware: {
285
+ memoryRetrieval: memoryV3ShadowMiddleware,
286
+ },
287
+ };
288
+
289
+ // Module-load side effect: register the shadow plugin at import time so the
290
+ // registry is populated even in tests that skip `bootstrapPlugins()`, matching
291
+ // the first-party `default-*` plugins. Idempotent via the swallowed
292
+ // duplicate-name check (the defaults aggregator also lists this plugin).
293
+ try {
294
+ registerPlugin(memoryV3ShadowPlugin);
295
+ } catch (err) {
296
+ if (
297
+ err instanceof PluginExecutionError &&
298
+ err.message.includes("already registered")
299
+ ) {
300
+ // already registered — expected when both the defaults aggregator and the
301
+ // direct module import run in the same process.
302
+ } else {
303
+ throw err;
304
+ }
305
+ }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Memory v3 — Tree traversal primitives.
3
+ *
4
+ * The *mechanical* half of the v3 read loop: a deterministic, provider-free
5
+ * walk over the {@link TreeIndex} DAG. The intelligence — *which* child nodes
6
+ * to recurse into at each level — is injected via the `descend` callback so
7
+ * this module stays pure and unit-testable without an LLM. The driver PR wires
8
+ * `descend` to the model's descend/skip decision; here `descend` is just a
9
+ * function `(nodeId, children) => chosen node-children`.
10
+ *
11
+ * `walkTree` fans out from a `start` node and any `seeds`, level by level:
12
+ * - At each node it resolves the ordered child refs, hands them to `descend`,
13
+ * and recurses into the chosen `node:` children (capped by `breadthBudget`).
14
+ * - The `page:` children the `descend` decision chooses to *keep* are collected
15
+ * into the returned `pages` set — pages are leaves, never recursed into. A
16
+ * page the decision does not keep is dropped, so the walk emits a curated
17
+ * selection rather than every page it passes.
18
+ * - A `visited` set keyed by canonical id (`node:<id>`) dedups shared
19
+ * sub-nodes (the DAG case) and terminates cycles (A ↔ B). A node is walked
20
+ * at most once regardless of how many parents reference it.
21
+ * - `maxDepth` bounds how deep the recursion goes; the start/seed level is
22
+ * depth 0.
23
+ *
24
+ * Each walked node emits one {@link TreeLevel} (the `harness/trace.ts` shape)
25
+ * recording what was considered, descended, and skipped. `reasoning` is
26
+ * supplied by the `descend` callback (the driver attaches the model's stated
27
+ * reason); the mechanical walk defaults it to `""`.
28
+ *
29
+ * Processing is strictly level-by-level so `visited` mutations are never raced:
30
+ * within a level the per-node `descend` calls run concurrently (`Promise.all`),
31
+ * but the chosen children for the *next* level are only dedup'd and enqueued
32
+ * after the whole level resolves.
33
+ */
34
+
35
+ import type { TreeLevel } from "../v2/harness/trace.js";
36
+ import type { ChildRef, TreeIndex } from "./tree-index.js";
37
+
38
+ /**
39
+ * The decision injected into {@link walkTree}. Given a node id and its ordered
40
+ * child refs, return the *node* children to recurse into and the *page* children
41
+ * to keep for the answer. The driver wires this to the LLM; tests pass a
42
+ * deterministic stub.
43
+ *
44
+ * Returning a `reasoning` string is optional — when present it is threaded into
45
+ * the emitted {@link TreeLevel}; absent, the level's `reasoning` defaults to
46
+ * `""`. Returned `descend` refs that are not `node:` children of `nodeId`, and
47
+ * `keep` refs that are not `page:` children of `nodeId`, are ignored by the walk
48
+ * (it only acts on the distinct children it actually offered).
49
+ */
50
+ export type DescendDecision = (
51
+ nodeId: string,
52
+ children: ReadonlyArray<ChildRef>,
53
+ ) => Promise<DescendResult> | DescendResult;
54
+
55
+ /**
56
+ * The result of a {@link DescendDecision}. `descend` lists the `node:` children
57
+ * chosen for recursion; `keep` lists the `page:` children to collect into the
58
+ * walk's result; `reasoning` is the optional model rationale recorded on the
59
+ * level.
60
+ */
61
+ export interface DescendResult {
62
+ descend: ChildRef[];
63
+ keep: ChildRef[];
64
+ reasoning?: string;
65
+ }
66
+
67
+ /** Options controlling a {@link walkTree} run. */
68
+ export interface WalkOptions {
69
+ /** Entry node id; defaults to `tree.root`. */
70
+ start?: string;
71
+ /** Extra node ids to start from in parallel with `start`. */
72
+ seeds?: string[];
73
+ /** Max `node:` children to descend into per node (after the `descend` pick). */
74
+ breadthBudget: number;
75
+ /** Max recursion depth; the start/seed level is depth 0. */
76
+ maxDepth: number;
77
+ /** Injected descend decision (the LLM hook). */
78
+ descend: DescendDecision;
79
+ }
80
+
81
+ /** The result of a {@link walkTree} run. */
82
+ export interface WalkResult {
83
+ /** Every `page:` slug the descend decision kept across the walk, dedup'd. */
84
+ pages: Set<string>;
85
+ /** One {@link TreeLevel} per walked node, in walk order. */
86
+ levels: TreeLevel[];
87
+ }
88
+
89
+ /**
90
+ * Resolve the ordered child refs for `nodeId`. Thin accessor over
91
+ * `tree.childrenByNode`; returns an empty array for an unknown / leaf node id so
92
+ * callers never branch on `undefined`.
93
+ */
94
+ export function resolveChildren(
95
+ tree: TreeIndex,
96
+ nodeId: string,
97
+ ): ReadonlyArray<ChildRef> {
98
+ return tree.childrenByNode.get(nodeId) ?? [];
99
+ }
100
+
101
+ /** Canonical visited-set key for a node id. */
102
+ function nodeKey(nodeId: string): string {
103
+ return `node:${nodeId}`;
104
+ }
105
+
106
+ /**
107
+ * Walk the {@link TreeIndex} DAG from `start` (default `tree.root`) plus any
108
+ * `seeds`, driven by the injected `descend` decision. Deterministic and
109
+ * provider-free — see the module docstring for the full contract.
110
+ *
111
+ * Returns the collected leaf `pages` and the per-node `levels` trace.
112
+ */
113
+ export async function walkTree(
114
+ tree: TreeIndex,
115
+ opts: WalkOptions,
116
+ ): Promise<WalkResult> {
117
+ const { breadthBudget, maxDepth, descend } = opts;
118
+ const start = opts.start ?? tree.root;
119
+
120
+ const pages = new Set<string>();
121
+ const levels: TreeLevel[] = [];
122
+ const visited = new Set<string>();
123
+
124
+ // Seed the frontier with `start` + `seeds`, dedup'd and marked visited up
125
+ // front so a node that is both the start and a seed is walked once.
126
+ let frontier: string[] = [];
127
+ for (const id of [start, ...(opts.seeds ?? [])]) {
128
+ const key = nodeKey(id);
129
+ if (visited.has(key)) continue;
130
+ visited.add(key);
131
+ frontier.push(id);
132
+ }
133
+
134
+ // Depth 0 is the start/seed level; stop once we'd exceed `maxDepth`.
135
+ for (let depth = 0; depth <= maxDepth && frontier.length > 0; depth++) {
136
+ // Resolve every node on this level concurrently. `visited` is not mutated
137
+ // here — only after the whole level settles — so the concurrency is safe.
138
+ const levelResults = await Promise.all(
139
+ frontier.map(async (nodeId) => {
140
+ const children = resolveChildren(tree, nodeId);
141
+ const result = await descend(nodeId, children);
142
+ return { nodeId, children, result };
143
+ }),
144
+ );
145
+
146
+ const nextFrontier: string[] = [];
147
+
148
+ for (const { nodeId, children, result } of levelResults) {
149
+ // Collect only the page children the decision chose to keep, filtered to
150
+ // the page children this node actually offered — a decision can't keep a
151
+ // page the node never presented.
152
+ const offeredPageRefs = new Set(
153
+ children.filter((c) => c.kind === "page").map((c) => c.ref),
154
+ );
155
+ for (const choice of result.keep) {
156
+ if (choice.kind === "page" && offeredPageRefs.has(choice.ref)) {
157
+ pages.add(choice.ref);
158
+ }
159
+ }
160
+
161
+ // The set of node children this node legitimately offered, in order. The
162
+ // descend pick is intersected with this so a stub returning bogus or
163
+ // duplicate refs can't make the walk recurse into something not offered.
164
+ const offeredNodes = children.filter((c) => c.kind === "node");
165
+ const offeredRefs = new Set(offeredNodes.map((c) => c.ref));
166
+
167
+ // Honor the descend pick in the order it was returned, dedup'd, filtered
168
+ // to genuinely-offered node children, and capped by `breadthBudget`.
169
+ const descended: string[] = [];
170
+ const descendedSet = new Set<string>();
171
+ for (const choice of result.descend) {
172
+ if (choice.kind !== "node") continue;
173
+ if (!offeredRefs.has(choice.ref)) continue;
174
+ if (descendedSet.has(choice.ref)) continue;
175
+ if (descended.length >= breadthBudget) break;
176
+ descendedSet.add(choice.ref);
177
+ descended.push(choice.ref);
178
+ }
179
+
180
+ const considered = offeredNodes.map((c) => c.ref);
181
+ const skipped = considered.filter((ref) => !descendedSet.has(ref));
182
+
183
+ levels.push({
184
+ node: nodeId,
185
+ considered,
186
+ descended,
187
+ skipped,
188
+ reasoning: result.reasoning ?? "",
189
+ });
190
+
191
+ // Enqueue chosen node children for the next level. Mark visited now (the
192
+ // level has fully resolved) so a shared sub-node or a cycle is enqueued at
193
+ // most once across the whole walk.
194
+ for (const ref of descended) {
195
+ const key = nodeKey(ref);
196
+ if (visited.has(key)) continue;
197
+ visited.add(key);
198
+ nextFrontier.push(ref);
199
+ }
200
+ }
201
+
202
+ frontier = nextFrontier;
203
+ }
204
+
205
+ return { pages, levels };
206
+ }