@vellumai/assistant 0.8.7 → 0.8.8-dev.202606052332.17fc8ea

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 (570) hide show
  1. package/Dockerfile +20 -4
  2. package/bun.lock +2 -2
  3. package/docker-entrypoint.sh +4 -2
  4. package/docker-init-apt-root.sh +3 -1
  5. package/docker-kata-apt-env.sh +3 -1
  6. package/docker-kata-runtime-family.sh +12 -0
  7. package/docs/architecture/memory.md +1 -1
  8. package/examples/plugins/echo/README.md +61 -66
  9. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  10. package/examples/plugins/echo/hooks/stop.ts +16 -0
  11. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  12. package/examples/plugins/echo/package.json +1 -2
  13. package/examples/plugins/echo/src/emit.ts +19 -0
  14. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
  15. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  16. package/openapi.yaml +3378 -335
  17. package/package.json +2 -2
  18. package/scripts/generate-openapi.ts +68 -41
  19. package/src/__tests__/agent-loop-exit-reason.test.ts +35 -93
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
  21. package/src/__tests__/agent-loop.test.ts +37 -87
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  23. package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
  24. package/src/__tests__/annotate-risk-options.test.ts +2 -3
  25. package/src/__tests__/anthropic-provider.test.ts +95 -2
  26. package/src/__tests__/app-control-flow.test.ts +1 -1
  27. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  28. package/src/__tests__/approval-routes-http.test.ts +4 -1
  29. package/src/__tests__/assistant-event-hub.test.ts +25 -0
  30. package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
  31. package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
  32. package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
  33. package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
  34. package/src/__tests__/btw-routes.test.ts +62 -3
  35. package/src/__tests__/build-persisted-content.test.ts +184 -0
  36. package/src/__tests__/catalog-files.test.ts +1 -1
  37. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  38. package/src/__tests__/channel-approvals.test.ts +1 -1
  39. package/src/__tests__/clawhub-files.test.ts +1 -1
  40. package/src/__tests__/compaction-circuit.test.ts +258 -0
  41. package/src/__tests__/compaction-direct.test.ts +132 -0
  42. package/src/__tests__/compaction.benchmark.test.ts +0 -30
  43. package/src/__tests__/config-watcher.test.ts +1 -1
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -5
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -7
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +316 -1143
  48. package/src/__tests__/conversation-agent-loop.test.ts +638 -1655
  49. package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
  50. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  51. package/src/__tests__/conversation-history-web-search.test.ts +11 -1
  52. package/src/__tests__/conversation-pairing.test.ts +4 -31
  53. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -10
  55. package/src/__tests__/conversation-queue.test.ts +2 -0
  56. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
  57. package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
  58. package/src/__tests__/conversation-runtime-assembly.test.ts +310 -300
  59. package/src/__tests__/conversation-runtime-workspace.test.ts +105 -45
  60. package/src/__tests__/conversation-slash-commands.test.ts +8 -42
  61. package/src/__tests__/conversation-slash-queue.test.ts +6 -1
  62. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  63. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
  64. package/src/__tests__/conversation-sync-tags.test.ts +27 -15
  65. package/src/__tests__/conversation-title-service.test.ts +135 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +17 -16
  67. package/src/__tests__/conversation-workspace-injection.test.ts +67 -2
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +7 -6
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/cross-provider-web-search.test.ts +214 -1
  71. package/src/__tests__/db-acp-history.test.ts +101 -0
  72. package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
  73. package/src/__tests__/dm-persistence.test.ts +5 -1
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +304 -0
  76. package/src/__tests__/feature-flag-test-helpers.ts +2 -2
  77. package/src/__tests__/file-write-tool.test.ts +63 -0
  78. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  79. package/src/__tests__/gemini-image-service.test.ts +13 -0
  80. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  81. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  82. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  83. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  84. package/src/__tests__/heartbeat-service.test.ts +1 -0
  85. package/src/__tests__/helpers/mock-provider.ts +110 -0
  86. package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
  87. package/src/__tests__/history-repair-hook.test.ts +1 -0
  88. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  89. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  90. package/src/__tests__/identity-intro-cache.test.ts +12 -100
  91. package/src/__tests__/identity-routes.test.ts +248 -7
  92. package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
  93. package/src/__tests__/injector-background-turn.test.ts +3 -9
  94. package/src/__tests__/injector-chain.test.ts +139 -275
  95. package/src/__tests__/injector-disk-pressure.test.ts +75 -41
  96. package/src/__tests__/injector-document-comments.test.ts +3 -3
  97. package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
  98. package/src/__tests__/injector-v3-suppression.test.ts +31 -37
  99. package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
  100. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  101. package/src/__tests__/list-messages-page-latest.test.ts +60 -0
  102. package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
  103. package/src/__tests__/llm-usage-store.test.ts +223 -1
  104. package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
  105. package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
  106. package/src/__tests__/native-web-search.test.ts +191 -0
  107. package/src/__tests__/onboarding-template-contract.test.ts +2 -0
  108. package/src/__tests__/openai-image-service.test.ts +17 -0
  109. package/src/__tests__/openai-provider.test.ts +31 -1
  110. package/src/__tests__/{overflow-reduce-pipeline.test.ts → overflow-reduction-loop.test.ts} +64 -284
  111. package/src/__tests__/persist-unsendable-image.test.ts +215 -0
  112. package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
  113. package/src/__tests__/pkb-autoinject.test.ts +2 -5
  114. package/src/__tests__/plugin-api-shim.test.ts +3 -6
  115. package/src/__tests__/plugin-bootstrap.test.ts +14 -40
  116. package/src/__tests__/plugin-registry.test.ts +3 -76
  117. package/src/__tests__/plugin-types.test.ts +0 -193
  118. package/src/__tests__/process-message-display-content.test.ts +6 -2
  119. package/src/__tests__/reaction-persistence.test.ts +1 -1
  120. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
  121. package/src/__tests__/resolve-trust-class.test.ts +4 -4
  122. package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
  123. package/src/__tests__/schedule-routes.test.ts +603 -2
  124. package/src/__tests__/schedule-store.test.ts +41 -0
  125. package/src/__tests__/schedule-tools.test.ts +35 -0
  126. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  127. package/src/__tests__/server-history-render.test.ts +314 -1
  128. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  129. package/src/__tests__/skillssh-files.test.ts +1 -1
  130. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  131. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  132. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  133. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  134. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  135. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  136. package/src/__tests__/system-prompt.test.ts +20 -0
  137. package/src/__tests__/task-scheduler.test.ts +162 -1
  138. package/src/__tests__/terminal-tools.test.ts +6 -1
  139. package/src/__tests__/title-generate-hook.test.ts +319 -0
  140. package/src/__tests__/tool-error-hook.test.ts +278 -0
  141. package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
  142. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  143. package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
  144. package/src/__tests__/tool-result-truncation.test.ts +0 -2
  145. package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
  146. package/src/__tests__/ui-work-result-surface.test.ts +159 -0
  147. package/src/__tests__/usage-routes.test.ts +285 -1
  148. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  149. package/src/__tests__/voice-session-bridge.test.ts +6 -3
  150. package/src/__tests__/web-search-backend-failure.test.ts +166 -0
  151. package/src/acp/__tests__/agent-process.test.ts +161 -0
  152. package/src/acp/__tests__/client-handler.test.ts +40 -0
  153. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  154. package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
  155. package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
  156. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  157. package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
  158. package/src/acp/agent-process.ts +61 -1
  159. package/src/acp/auto-install.test.ts +196 -0
  160. package/src/acp/auto-install.ts +177 -0
  161. package/src/acp/client-handler.ts +31 -0
  162. package/src/acp/feature-gate.test.ts +48 -0
  163. package/src/acp/feature-gate.ts +34 -0
  164. package/src/acp/prepare-agent-env.ts +83 -29
  165. package/src/acp/resolve-agent.test.ts +320 -7
  166. package/src/acp/resolve-agent.ts +182 -18
  167. package/src/acp/resume-hint.ts +25 -0
  168. package/src/acp/session-manager.ts +495 -73
  169. package/src/acp/types.ts +8 -0
  170. package/src/agent/compaction-circuit.ts +60 -102
  171. package/src/agent/loop.ts +362 -485
  172. package/src/api/events/assistant-thinking-delta.ts +33 -0
  173. package/src/api/events/tool-output-chunk.ts +45 -0
  174. package/src/api/events/tool-use-preview-start.ts +32 -0
  175. package/src/api/events/trace-event.ts +69 -0
  176. package/src/api/index.ts +48 -13
  177. package/src/api/responses/conversation-message.ts +374 -0
  178. package/src/approvals/guardian-request-resolvers.ts +1 -1
  179. package/src/avatar/__tests__/avatar-store.test.ts +34 -29
  180. package/src/background-wake/next-wake.ts +1 -0
  181. package/src/cli/commands/__tests__/notifications.test.ts +58 -14
  182. package/src/cli/commands/notifications.ts +112 -60
  183. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  184. package/src/config/acp-defaults.test.ts +10 -0
  185. package/src/config/acp-defaults.ts +6 -0
  186. package/src/config/assistant-feature-flags.ts +22 -11
  187. package/src/config/bundled-skills/acp/SKILL.md +83 -31
  188. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  189. package/src/config/bundled-skills/app-builder/SKILL.md +224 -398
  190. package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
  191. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  192. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  193. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  194. package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
  195. package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
  196. package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
  197. package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
  198. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  199. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  200. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  201. package/src/config/bundled-skills/messaging/SKILL.md +0 -7
  202. package/src/config/bundled-tool-registry.ts +2 -0
  203. package/src/config/feature-flag-cache.ts +3 -3
  204. package/src/config/feature-flag-registry.json +48 -7
  205. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
  207. package/src/config/schemas/heartbeat.ts +9 -0
  208. package/src/config/schemas/llm.ts +1 -0
  209. package/src/config/schemas/memory-v2.ts +8 -0
  210. package/src/config/schemas/memory-v3.ts +8 -0
  211. package/src/config/schemas/platform.ts +8 -0
  212. package/src/config/seed-inference-profiles.ts +2 -2
  213. package/src/config/skills.ts +13 -0
  214. package/src/context/compactor.ts +1 -1
  215. package/src/context/strip-injections.ts +128 -0
  216. package/src/context/token-estimator.ts +23 -0
  217. package/src/context/tool-result-truncation.ts +0 -23
  218. package/src/context/window-manager.ts +5 -7
  219. package/src/credential-execution/executable-discovery.ts +16 -0
  220. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
  221. package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
  222. package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
  223. package/src/daemon/assistant-attachments.ts +1 -1
  224. package/src/daemon/config-watcher.ts +2 -2
  225. package/src/daemon/context-overflow-reducer.ts +0 -1
  226. package/src/daemon/conversation-agent-loop-handlers.ts +594 -153
  227. package/src/daemon/conversation-agent-loop.ts +301 -997
  228. package/src/daemon/conversation-history.ts +5 -4
  229. package/src/daemon/conversation-lifecycle.ts +3 -4
  230. package/src/daemon/conversation-messaging.ts +7 -6
  231. package/src/daemon/conversation-process.ts +11 -16
  232. package/src/daemon/conversation-registry.ts +159 -0
  233. package/src/daemon/conversation-runtime-assembly.ts +218 -398
  234. package/src/daemon/conversation-slash.ts +6 -25
  235. package/src/daemon/conversation-store.ts +9 -90
  236. package/src/daemon/conversation-surfaces.ts +222 -4
  237. package/src/daemon/conversation-tool-setup.ts +2 -29
  238. package/src/daemon/conversation-workspace.ts +17 -0
  239. package/src/daemon/conversation.ts +32 -20
  240. package/src/daemon/external-plugins-bootstrap.ts +17 -18
  241. package/src/daemon/handlers/config-a2a.ts +51 -36
  242. package/src/daemon/handlers/config-slack-channel.ts +20 -14
  243. package/src/daemon/handlers/config-telegram.ts +16 -2
  244. package/src/daemon/handlers/conversations.ts +3 -1
  245. package/src/daemon/handlers/shared.ts +156 -84
  246. package/src/daemon/handlers/skills.ts +42 -10
  247. package/src/daemon/lifecycle.ts +25 -0
  248. package/src/daemon/message-types/apps.ts +1 -29
  249. package/src/daemon/message-types/messages.ts +9 -57
  250. package/src/daemon/message-types/skills.ts +2 -0
  251. package/src/daemon/message-types/surfaces.ts +136 -3
  252. package/src/daemon/now-scratchpad.ts +21 -0
  253. package/src/daemon/orphan-reaper.test.ts +210 -0
  254. package/src/daemon/orphan-reaper.ts +240 -0
  255. package/src/daemon/overflow-reduction-loop.ts +230 -0
  256. package/src/daemon/persist-unsendable-image.ts +117 -0
  257. package/src/daemon/process-message.ts +1 -3
  258. package/src/daemon/server.ts +2 -0
  259. package/src/daemon/trace-emitter.ts +6 -4
  260. package/src/daemon/trust-context.ts +19 -0
  261. package/src/daemon/wake-target-adapter.ts +3 -1
  262. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  263. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  264. package/src/heartbeat/heartbeat-service.ts +26 -0
  265. package/src/home/home-greeting-cache.ts +24 -1
  266. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  267. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  268. package/src/ipc/gateway-client.test.ts +2 -2
  269. package/src/ipc/gateway-client.ts +3 -3
  270. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  271. package/src/ipc/skill-routes/memory.ts +4 -2
  272. package/src/media/gemini-image-service.ts +15 -0
  273. package/src/media/openai-image-service.ts +14 -0
  274. package/src/media/types.ts +34 -0
  275. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
  276. package/src/memory/auth-fallback-events-store.ts +94 -0
  277. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  278. package/src/memory/conversation-title-service.ts +65 -41
  279. package/src/memory/db-init.ts +6 -0
  280. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  281. package/src/memory/graph/conversation-graph-memory.ts +65 -0
  282. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  283. package/src/memory/jobs-store.ts +33 -0
  284. package/src/memory/jobs-worker.ts +32 -5
  285. package/src/memory/llm-usage-store.ts +224 -50
  286. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
  287. package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
  288. package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
  289. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  290. package/src/memory/migrations/index.ts +3 -0
  291. package/src/memory/pkb/autoinject.ts +61 -0
  292. package/src/memory/pkb/context.ts +50 -0
  293. package/src/memory/pkb/types.ts +14 -0
  294. package/src/memory/schedule-attribution-sql.ts +104 -0
  295. package/src/memory/schema/acp.ts +4 -0
  296. package/src/memory/schema/infrastructure.ts +16 -0
  297. package/src/memory/usage-grouped-buckets.ts +6 -1
  298. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -4
  299. package/src/memory/v2/consolidation-job.ts +14 -5
  300. package/src/notifications/conversation-pairing.ts +8 -15
  301. package/src/notifications/decision-engine.ts +6 -3
  302. package/src/notifications/home-feed-side-effect.ts +12 -1
  303. package/src/permissions/prompter.ts +4 -0
  304. package/src/plugin-api/constants.ts +4 -0
  305. package/src/plugin-api/index.ts +7 -5
  306. package/src/plugin-api/types.ts +151 -1
  307. package/src/plugins/defaults/compaction/compact.ts +59 -0
  308. package/src/plugins/defaults/compaction/package.json +1 -1
  309. package/src/plugins/defaults/compaction/register.ts +8 -19
  310. package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
  311. package/src/plugins/defaults/empty-response/register.ts +8 -13
  312. package/src/plugins/defaults/index.ts +2 -18
  313. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +95 -0
  314. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
  315. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
  316. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +288 -81
  317. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  318. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +16 -0
  319. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +4 -4
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  321. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +48 -12
  322. package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +13 -0
  323. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  324. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  325. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +104 -32
  326. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +96 -30
  328. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +34 -16
  329. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +5 -5
  330. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +2 -2
  331. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  332. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  333. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  334. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  335. package/src/plugins/defaults/memory-v3-shadow/llm-retry.ts +32 -0
  336. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +8 -8
  337. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +26 -14
  338. package/src/plugins/defaults/{llm-call → memory-v3-shadow}/package.json +2 -2
  339. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  340. package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +26 -0
  341. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +3 -3
  342. package/src/plugins/defaults/memory-v3-shadow/register.ts +26 -0
  343. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  344. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +51 -45
  345. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  346. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +61 -46
  347. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +69 -99
  348. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  349. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +8 -0
  350. package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
  351. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
  352. package/src/plugins/defaults/title-generate/package.json +1 -1
  353. package/src/plugins/defaults/title-generate/register.ts +18 -18
  354. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
  355. package/src/plugins/defaults/tool-error/package.json +1 -1
  356. package/src/plugins/defaults/tool-error/register.ts +9 -21
  357. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
  358. package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
  359. package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
  360. package/src/plugins/external-api.ts +2 -2
  361. package/src/plugins/pipeline.ts +6 -305
  362. package/src/plugins/registry.ts +10 -55
  363. package/src/plugins/types.ts +62 -797
  364. package/src/plugins/user-loader.ts +30 -127
  365. package/src/proactive-artifact/aux-message-injector.ts +4 -4
  366. package/src/proactive-artifact/job.test.ts +8 -13
  367. package/src/prompts/__tests__/system-prompt.test.ts +42 -0
  368. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +64 -0
  369. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  370. package/src/prompts/templates/system-sections.ts +15 -0
  371. package/src/providers/anthropic/client.ts +37 -29
  372. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
  373. package/src/providers/openai/chat-completions-provider.ts +44 -0
  374. package/src/providers/openrouter/client.ts +1 -0
  375. package/src/providers/placeholder-sentinels.ts +35 -0
  376. package/src/runtime/__tests__/agent-wake.test.ts +10 -6
  377. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  378. package/src/runtime/agent-wake.ts +2 -5
  379. package/src/runtime/assistant-event-hub.ts +37 -7
  380. package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
  381. package/src/runtime/channel-approvals.ts +1 -1
  382. package/src/runtime/http-router.ts +16 -21
  383. package/src/runtime/http-types.ts +16 -70
  384. package/src/runtime/interactive-ui.ts +1 -1
  385. package/src/runtime/pending-interactions.ts +1 -0
  386. package/src/runtime/routes/__tests__/acp-routes.test.ts +283 -55
  387. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
  388. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  389. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
  390. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
  391. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  392. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  393. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  394. package/src/runtime/routes/acp-routes.test.ts +89 -25
  395. package/src/runtime/routes/acp-routes.ts +81 -29
  396. package/src/runtime/routes/app-management-routes.ts +6 -117
  397. package/src/runtime/routes/app-routes.ts +13 -15
  398. package/src/runtime/routes/approval-routes.ts +1 -1
  399. package/src/runtime/routes/attachment-routes.ts +26 -15
  400. package/src/runtime/routes/avatar-routes.ts +26 -0
  401. package/src/runtime/routes/browser-routes.ts +1 -1
  402. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  403. package/src/runtime/routes/btw-routes.ts +29 -23
  404. package/src/runtime/routes/consolidation-routes.ts +120 -20
  405. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  406. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  407. package/src/runtime/routes/conversation-query-routes.ts +3 -1
  408. package/src/runtime/routes/conversation-routes.ts +372 -185
  409. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  410. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  411. package/src/runtime/routes/documents-routes.ts +4 -0
  412. package/src/runtime/routes/domain-routes.ts +51 -37
  413. package/src/runtime/routes/epoch-millis-range.ts +34 -0
  414. package/src/runtime/routes/events-routes.ts +28 -34
  415. package/src/runtime/routes/gateway-log-routes.ts +26 -4
  416. package/src/runtime/routes/heartbeat-routes.ts +32 -12
  417. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  418. package/src/runtime/routes/host-cu-routes.ts +1 -1
  419. package/src/runtime/routes/identity-intro-cache.ts +11 -34
  420. package/src/runtime/routes/identity-routes.ts +224 -18
  421. package/src/runtime/routes/image-generation-routes.ts +40 -2
  422. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  423. package/src/runtime/routes/index.ts +2 -0
  424. package/src/runtime/routes/integrations/a2a.ts +12 -10
  425. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
  426. package/src/runtime/routes/integrations/slack/channel.ts +4 -0
  427. package/src/runtime/routes/integrations/slack/share.ts +27 -6
  428. package/src/runtime/routes/integrations/telegram.ts +6 -0
  429. package/src/runtime/routes/integrations/twilio.ts +42 -0
  430. package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
  431. package/src/runtime/routes/log-export-routes.ts +8 -0
  432. package/src/runtime/routes/memory-v2-routes.ts +15 -8
  433. package/src/runtime/routes/memory-v3-routes.ts +66 -34
  434. package/src/runtime/routes/oauth-apps.ts +66 -12
  435. package/src/runtime/routes/oauth-providers.ts +44 -5
  436. package/src/runtime/routes/platform-routes.ts +81 -5
  437. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
  438. package/src/runtime/routes/playground/force-compact.ts +1 -1
  439. package/src/runtime/routes/playground/helpers.ts +1 -1
  440. package/src/runtime/routes/rename-conversation-routes.ts +5 -0
  441. package/src/runtime/routes/schedule-routes.ts +152 -42
  442. package/src/runtime/routes/secret-routes.ts +14 -2
  443. package/src/runtime/routes/skills-routes.ts +43 -14
  444. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  445. package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
  446. package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
  447. package/src/runtime/routes/trust-rules-routes.ts +26 -2
  448. package/src/runtime/routes/tts-routes.ts +35 -0
  449. package/src/runtime/routes/types.ts +66 -8
  450. package/src/runtime/routes/usage-routes.ts +47 -39
  451. package/src/runtime/routes/webhook-routes.ts +41 -2
  452. package/src/runtime/routes/work-items-routes.ts +2 -4
  453. package/src/runtime/routes/workspace-routes.ts +4 -0
  454. package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
  455. package/src/runtime/services/analyze-conversation.ts +2 -2
  456. package/src/runtime/services/conversation-serializer.ts +1 -1
  457. package/src/schedule/schedule-store.ts +20 -1
  458. package/src/schedule/schedule-usage-store.ts +83 -0
  459. package/src/schedule/scheduler.ts +12 -5
  460. package/src/signals/cancel.ts +2 -4
  461. package/src/skills/catalog-files.ts +2 -2
  462. package/src/skills/catalog-install.ts +3 -0
  463. package/src/skills/categories-cache.ts +118 -0
  464. package/src/skills/clawhub-files.ts +1 -2
  465. package/src/skills/skillssh-files.ts +1 -2
  466. package/src/subagent/manager.ts +17 -5
  467. package/src/telemetry/types.ts +29 -1
  468. package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
  469. package/src/telemetry/usage-telemetry-reporter.ts +57 -2
  470. package/src/tools/acp/context.ts +20 -0
  471. package/src/tools/acp/list-agents.test.ts +7 -1
  472. package/src/tools/acp/spawn.test.ts +158 -55
  473. package/src/tools/acp/spawn.ts +47 -72
  474. package/src/tools/acp/steer.test.ts +105 -8
  475. package/src/tools/acp/steer.ts +48 -17
  476. package/src/tools/apps/executors.ts +13 -8
  477. package/src/tools/executor.ts +1 -53
  478. package/src/tools/filesystem/write.ts +34 -0
  479. package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
  480. package/src/tools/network/__tests__/web-search.test.ts +11 -3
  481. package/src/tools/network/web-search-error.test.ts +248 -0
  482. package/src/tools/network/web-search-error.ts +267 -0
  483. package/src/tools/network/web-search.ts +207 -48
  484. package/src/tools/schedule/create.ts +2 -0
  485. package/src/tools/subagent/spawn.ts +2 -4
  486. package/src/tools/terminal/safe-env.ts +10 -1
  487. package/src/tools/ui-surface/definitions.ts +34 -5
  488. package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
  489. package/src/tts/provider-catalog.ts +76 -1
  490. package/src/util/mutex.ts +47 -0
  491. package/src/workspace/git-service.ts +1 -42
  492. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  493. package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
  494. package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
  495. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +117 -0
  496. package/src/workspace/migrations/registry.ts +6 -0
  497. package/docs/plugins.md +0 -836
  498. package/examples/plugins/echo/register.ts +0 -184
  499. package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
  500. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  501. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  502. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  503. package/src/__tests__/empty-response-pipeline.test.ts +0 -423
  504. package/src/__tests__/llm-call-pipeline.test.ts +0 -287
  505. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
  506. package/src/__tests__/persistence-pipeline.test.ts +0 -503
  507. package/src/__tests__/pipeline-runner.test.ts +0 -564
  508. package/src/__tests__/title-generate-pipeline.test.ts +0 -211
  509. package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
  510. package/src/__tests__/tool-error-pipeline.test.ts +0 -241
  511. package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
  512. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
  513. package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
  514. package/src/gallery/default-gallery.ts +0 -1359
  515. package/src/gallery/gallery-manifest.ts +0 -28
  516. package/src/home/feature-gate.ts +0 -22
  517. package/src/memory/v3/provider-blocks.ts +0 -16
  518. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  519. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  520. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  521. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  522. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  523. package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
  524. package/src/plugins/defaults/empty-response/terminal.ts +0 -106
  525. package/src/plugins/defaults/injectors/package.json +0 -15
  526. package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
  527. package/src/plugins/defaults/llm-call/register.ts +0 -45
  528. package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
  529. package/src/plugins/defaults/memory-retrieval/package.json +0 -15
  530. package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
  531. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  532. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  533. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  534. package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
  535. package/src/plugins/defaults/persistence/package.json +0 -15
  536. package/src/plugins/defaults/persistence/register.ts +0 -38
  537. package/src/plugins/defaults/persistence/terminal.ts +0 -83
  538. package/src/plugins/defaults/title-generate/terminal.ts +0 -31
  539. package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
  540. package/src/plugins/defaults/token-estimate/package.json +0 -15
  541. package/src/plugins/defaults/token-estimate/register.ts +0 -34
  542. package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
  543. package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
  544. package/src/plugins/defaults/tool-error/terminal.ts +0 -47
  545. package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
  546. package/src/plugins/defaults/tool-execute/package.json +0 -15
  547. package/src/plugins/defaults/tool-execute/register.ts +0 -49
  548. package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
  549. package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
  550. package/src/skills/category-inference.ts +0 -111
  551. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  552. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  553. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  554. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  555. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  556. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  557. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  558. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +0 -0
  559. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  560. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  561. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  562. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  563. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  564. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  565. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  566. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  567. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  568. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  569. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  570. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -17,7 +17,7 @@ import type {
17
17
  Message,
18
18
  Provider,
19
19
  ProviderResponse,
20
- } from "../../../providers/types.js";
20
+ } from "../../../../providers/types.js";
21
21
  import type { NeedleIndex } from "../needle.js";
22
22
  import type {
23
23
  LeafNode,
@@ -35,13 +35,13 @@ import evalTurns from "./fixtures/eval-turns.json" with { type: "json" };
35
35
 
36
36
  let providerStub: Provider | null = null;
37
37
 
38
- mock.module("../../../providers/provider-send-message.js", () => ({
38
+ mock.module("../../../../providers/provider-send-message.js", () => ({
39
39
  getConfiguredProvider: async () => providerStub,
40
40
  extractToolUse: (response: ProviderResponse) =>
41
41
  response.content.find((b) => b.type === "tool_use"),
42
42
  }));
43
43
 
44
- mock.module("../../../util/logger.js", () => ({
44
+ mock.module("../../../../util/logger.js", () => ({
45
45
  getLogger: () =>
46
46
  new Proxy({} as Record<string, unknown>, {
47
47
  get: (_t, prop) => (prop === "child" ? () => ({}) : () => {}),
@@ -217,6 +217,43 @@ describe("orchestrate — fixture sequence (carry-forward)", () => {
217
217
  expect(t2.currentSelections.map((s) => s.slug)).not.toContain("page-a");
218
218
  expect(t2.finalInjection).toContain("page-a");
219
219
  });
220
+
221
+ test("carry-forward survives a turn whose selections fill the cap", async () => {
222
+ const tree = makeTree();
223
+ // Cap of 1: under a naive record-then-cap order this turn's own selection
224
+ // would evict the carried page before injection. Snapshotting the carry
225
+ // BEFORE recording this turn keeps the earlier page in the injection.
226
+ const workingSet = new WorkingSet(1);
227
+ const needle = fakeNeedle([]);
228
+ const stub = (selectIds: number[]): Provider => ({
229
+ name: "stub",
230
+ sendMessage: async (_messages, options) =>
231
+ options?.tools?.[0]?.name === "open_leaves"
232
+ ? toolUseResponse("open_leaves", { ids: [1] })
233
+ : toolUseResponse("select_pages", { ids: selectIds, pinned_ids: [] }),
234
+ });
235
+
236
+ providerStub = stub([1]); // turn 1 → page-a
237
+ await orchestrate(makeTurn(1, "page a"), {
238
+ tree,
239
+ core: new Set(),
240
+ needle,
241
+ workingSet,
242
+ pageSummary: summaryOf,
243
+ });
244
+
245
+ providerStub = stub([2]); // turn 2 → page-b, never re-selects page-a
246
+ const t2 = await orchestrate(makeTurn(2, "page b"), {
247
+ tree,
248
+ core: new Set(),
249
+ needle,
250
+ workingSet,
251
+ pageSummary: summaryOf,
252
+ });
253
+
254
+ expect(t2.currentSelections.map((s) => s.slug)).toEqual(["page-b"]);
255
+ expect(t2.finalInjection).toContain("page-a"); // carried despite the cap
256
+ });
220
257
  });
221
258
 
222
259
  // ---------------------------------------------------------------------------
@@ -298,14 +335,16 @@ describe("orchestrate — edge cases", () => {
298
335
  expect(result.finalInjection).toEqual(["page-a", "page-b"]);
299
336
  });
300
337
 
301
- test("L1 routing fallback (omitted ids all leaves) still works", async () => {
338
+ test("omitted L1 ids opens only the deterministic lanes, not the whole tree", async () => {
302
339
  const tree = makeTree();
303
- // Omitted ids → routeL1 opens ALL leaves; select everything per leaf.
340
+ // L1 omits ids → routeL1 opens NO routed leaves; only the needle/core lanes
341
+ // drive the open set, so the whole tree is never fanned out (topic-y, which
342
+ // nothing routes or needles to, stays closed).
304
343
  providerStub = {
305
344
  name: "stub",
306
345
  sendMessage: async (_messages, options) => {
307
346
  if (options?.tools?.[0]?.name === "open_leaves") {
308
- return toolUseResponse("open_leaves", {}); // omitted ids
347
+ return toolUseResponse("open_leaves", {}); // omitted ids → []
309
348
  }
310
349
  return toolUseResponse("select_pages", {}); // omitted → all members
311
350
  },
@@ -313,15 +352,12 @@ describe("orchestrate — edge cases", () => {
313
352
  const result = await orchestrate(makeTurn(1, "x"), {
314
353
  tree,
315
354
  core: new Set(),
316
- needle: fakeNeedle([]),
355
+ needle: fakeNeedle(["page-a"]), // needle opens domain-a/topic-x only
317
356
  workingSet: new WorkingSet(),
318
357
  pageSummary: summaryOf,
319
358
  });
320
- expect(result.openedLeaves).toEqual([
321
- "domain-a/topic-x",
322
- "domain-a/topic-y",
323
- ]);
324
- expect(result.finalInjection).toEqual(["page-a", "page-b", "page-c"]);
359
+ expect(result.openedLeaves).toEqual(["domain-a/topic-x"]);
360
+ expect(result.finalInjection).toEqual(["page-a", "page-b"]);
325
361
  });
326
362
 
327
363
  test("pinned current-turn selections land in the working set", async () => {
@@ -0,0 +1,13 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { cachedTextBlock } from "../provider-blocks.js";
4
+
5
+ describe("cachedTextBlock", () => {
6
+ test("stamps an ephemeral cache_control with a 1h TTL", () => {
7
+ const block = cachedTextBlock("stable leaf block");
8
+ expect(block).toMatchObject({ type: "text", text: "stable leaf block" });
9
+ expect(
10
+ (block as unknown as { cache_control?: unknown }).cache_control,
11
+ ).toEqual({ type: "ephemeral", ttl: "1h" });
12
+ });
13
+ });
@@ -3,8 +3,8 @@ import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
5
5
 
6
- import type { Provider } from "../../../providers/types.js";
7
- import { readPage, writePage } from "../../v2/page-store.js";
6
+ import { readPage, writePage } from "../../../../memory/v2/page-store.js";
7
+ import type { Provider } from "../../../../providers/types.js";
8
8
  import { type LeafRef, reconcileTree } from "../reconcile.js";
9
9
 
10
10
  /**
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
- import { wrapMemoryBlock } from "../../memory-marker.js";
3
+ import { wrapMemoryBlock } from "../../../../memory/memory-marker.js";
4
4
  import { renderMemoryBlock } from "../render-injection.js";
5
5
  import { Slug } from "../types.js";
6
6
 
@@ -3,10 +3,13 @@
3
3
  *
4
4
  * Coverage matrix:
5
5
  * - Returned IDs map to the right leaves by 1-based index, in model order.
6
- * - Omitted `ids` → ALL leaves (recall-safe).
6
+ * - Omitted `ids` → no leaves (the router must name leaves explicitly,
7
+ * never open the whole tree).
7
8
  * - Explicit `ids: []` → no leaves (deliberate abstention).
8
9
  * - Out-of-range / duplicate IDs ignored, no throw.
9
- * - No provider / missing tool_use / schema mismatch / throw → ALL leaves.
10
+ * - No provider / missing tool_use / schema mismatch / throw → no leaves
11
+ * (degrade to the deterministic lanes), the last three after a re-prompt
12
+ * retry; a malformed response that recovers on retry returns its IDs.
10
13
  * - The rendered leaf block is byte-identical across two calls with
11
14
  * different queries (the cache invariant).
12
15
  * - The system prompt mentions "register" (locks the routing commitment).
@@ -22,7 +25,7 @@ import type {
22
25
  ProviderResponse,
23
26
  SendMessageOptions,
24
27
  ToolUseContent,
25
- } from "../../../providers/types.js";
28
+ } from "../../../../providers/types.js";
26
29
  import type { LeafNode, LeafPath, LeafTree, TurnContext } from "../types.js";
27
30
 
28
31
  // ---------------------------------------------------------------------------
@@ -38,13 +41,13 @@ interface ProviderCall {
38
41
  }
39
42
  const providerCalls: ProviderCall[] = [];
40
43
 
41
- mock.module("../../../providers/provider-send-message.js", () => ({
44
+ mock.module("../../../../providers/provider-send-message.js", () => ({
42
45
  getConfiguredProvider: async () => providerStub,
43
46
  extractToolUse: (response: ProviderResponse) =>
44
47
  response.content.find((b): b is ToolUseContent => b.type === "tool_use"),
45
48
  }));
46
49
 
47
- mock.module("../../../util/logger.js", () => ({
50
+ mock.module("../../../../util/logger.js", () => ({
48
51
  getLogger: () =>
49
52
  new Proxy({} as Record<string, unknown>, {
50
53
  get: (_t, prop) => (prop === "child" ? () => ({}) : () => {}),
@@ -76,6 +79,45 @@ function toolUseResponse(input: Record<string, unknown>): ProviderResponse {
76
79
  };
77
80
  }
78
81
 
82
+ /** A 200 response that carries no tool_use — the malformed-but-successful case
83
+ * the re-prompt retry exists to recover from. */
84
+ function noToolResponse(): ProviderResponse {
85
+ return {
86
+ model: "stub-model",
87
+ stopReason: "end_turn",
88
+ usage: { inputTokens: 0, outputTokens: 0 },
89
+ content: [{ type: "text", text: "no tool call" }],
90
+ };
91
+ }
92
+
93
+ /** Provider returning a different response per call (the i-th call returns
94
+ * responses[i], or the last entry once exhausted), recording each call so a
95
+ * test can assert how many attempts were made. */
96
+ function makeSequenceProvider(responses: ProviderResponse[]): Provider {
97
+ let i = 0;
98
+ return {
99
+ name: "sequence",
100
+ sendMessage: async (messages, options) => {
101
+ providerCalls.push({ messages, options });
102
+ const response = responses[Math.min(i, responses.length - 1)];
103
+ i += 1;
104
+ return response;
105
+ },
106
+ };
107
+ }
108
+
109
+ /** Provider that records each call and then throws — for the throw-after-retries
110
+ * path (the provider's own RetryProvider has already exhausted its backoff). */
111
+ function makeThrowingProvider(): Provider {
112
+ return {
113
+ name: "throwing",
114
+ sendMessage: async (messages, options) => {
115
+ providerCalls.push({ messages, options });
116
+ throw new Error("boom");
117
+ },
118
+ };
119
+ }
120
+
79
121
  function makeLeaf(path: LeafPath, description: string): LeafNode {
80
122
  return {
81
123
  path,
@@ -101,8 +143,6 @@ function makeTree(): LeafTree {
101
143
  };
102
144
  }
103
145
 
104
- const SORTED_PATHS = ["people/alice", "people/bob", "projects/atlas"];
105
-
106
146
  function makeTurn(currentMessage: string): TurnContext {
107
147
  return {
108
148
  conversationId: "conv-xyz",
@@ -128,10 +168,10 @@ describe("routeL1 — id mapping", () => {
128
168
  expect(result).toEqual(["projects/atlas", "people/alice"]);
129
169
  });
130
170
 
131
- test("omitted ids opens ALL leaves (recall-safe)", async () => {
171
+ test("omitted ids opens no leaves (must name leaves explicitly)", async () => {
132
172
  providerStub = makeProvider(toolUseResponse({}));
133
173
  const result = await routeL1(makeTurn("anything"), makeTree());
134
- expect(result).toEqual(SORTED_PATHS);
174
+ expect(result).toEqual([]);
135
175
  });
136
176
 
137
177
  test("explicit empty ids opens no leaves (abstention)", async () => {
@@ -157,39 +197,43 @@ describe("routeL1 — id mapping", () => {
157
197
  });
158
198
  });
159
199
 
160
- describe("routeL1 — recall-safe fallbacks", () => {
161
- test("no provider → ALL leaves", async () => {
200
+ describe("routeL1 — degradation on failure", () => {
201
+ test("no provider → no leaves, without calling the provider", async () => {
162
202
  providerStub = null;
163
203
  const result = await routeL1(makeTurn("x"), makeTree());
164
- expect(result).toEqual(SORTED_PATHS);
204
+ expect(result).toEqual([]);
205
+ expect(providerCalls).toHaveLength(0);
165
206
  });
166
207
 
167
- test("missing tool_use → ALL leaves", async () => {
168
- providerStub = makeProvider({
169
- model: "stub-model",
170
- stopReason: "end_turn",
171
- usage: { inputTokens: 0, outputTokens: 0 },
172
- content: [{ type: "text", text: "no tool call" }],
173
- });
208
+ test("missing tool_use → no leaves after retrying", async () => {
209
+ providerStub = makeProvider(noToolResponse());
174
210
  const result = await routeL1(makeTurn("x"), makeTree());
175
- expect(result).toEqual(SORTED_PATHS);
211
+ expect(result).toEqual([]);
212
+ expect(providerCalls).toHaveLength(3);
176
213
  });
177
214
 
178
- test("schema mismatch → ALL leaves", async () => {
215
+ test("schema mismatch → no leaves after retrying", async () => {
179
216
  providerStub = makeProvider(toolUseResponse({ ids: "not-an-array" }));
180
217
  const result = await routeL1(makeTurn("x"), makeTree());
181
- expect(result).toEqual(SORTED_PATHS);
218
+ expect(result).toEqual([]);
219
+ expect(providerCalls).toHaveLength(3);
182
220
  });
183
221
 
184
- test("provider throw → ALL leaves", async () => {
185
- providerStub = {
186
- name: "throwing",
187
- sendMessage: async () => {
188
- throw new Error("boom");
189
- },
190
- };
222
+ test("provider throw → no leaves after retrying", async () => {
223
+ providerStub = makeThrowingProvider();
191
224
  const result = await routeL1(makeTurn("x"), makeTree());
192
- expect(result).toEqual(SORTED_PATHS);
225
+ expect(result).toEqual([]);
226
+ expect(providerCalls).toHaveLength(3);
227
+ });
228
+
229
+ test("a malformed response that recovers on retry returns its IDs", async () => {
230
+ providerStub = makeSequenceProvider([
231
+ noToolResponse(),
232
+ toolUseResponse({ ids: [2] }),
233
+ ]);
234
+ const result = await routeL1(makeTurn("bob?"), makeTree());
235
+ expect(result).toEqual(["people/bob"]);
236
+ expect(providerCalls).toHaveLength(2);
193
237
  });
194
238
  });
195
239
 
@@ -213,11 +257,11 @@ describe("routeL1 — request shape", () => {
213
257
  const [blockA, blockB] = providerCalls[0].messages[0].content as Array<{
214
258
  type: string;
215
259
  text: string;
216
- cache_control?: { type: string };
260
+ cache_control?: { type: string; ttl?: string };
217
261
  }>;
218
262
  expect(blockA.type).toBe("text");
219
263
  expect(blockA.text).toContain("<leaves>");
220
- expect(blockA.cache_control).toEqual({ type: "ephemeral" });
264
+ expect(blockA.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
221
265
 
222
266
  expect(blockB.type).toBe("text");
223
267
  expect(blockB.text).toContain("<current_message>alice?</current_message>");
@@ -225,6 +269,34 @@ describe("routeL1 — request shape", () => {
225
269
  expect(blockB.cache_control).toBeUndefined();
226
270
  });
227
271
 
272
+ test("situational context renders in the per-turn block when present", async () => {
273
+ providerStub = makeProvider(toolUseResponse({ ids: [1] }));
274
+ await routeL1(
275
+ {
276
+ ...makeTurn("x"),
277
+ situationalContext: "Today is Saturday. Alice's anniversary is today.",
278
+ },
279
+ makeTree(),
280
+ );
281
+ const blockB = providerCalls[0].messages[0].content[1] as { text: string };
282
+ expect(blockB.text).toContain(
283
+ "<situation>Today is Saturday. Alice's anniversary is today.</situation>",
284
+ );
285
+ });
286
+
287
+ test("situational context is omitted when the turn has none", async () => {
288
+ providerStub = makeProvider(toolUseResponse({ ids: [1] }));
289
+ await routeL1(makeTurn("x"), makeTree());
290
+ const blockB = providerCalls[0].messages[0].content[1] as { text: string };
291
+ expect(blockB.text).not.toContain("<situation>");
292
+ });
293
+
294
+ test("system prompt mentions situation (locks the routing commitment)", async () => {
295
+ providerStub = makeProvider(toolUseResponse({ ids: [1] }));
296
+ await routeL1(makeTurn("x"), makeTree());
297
+ expect(providerCalls[0].options?.systemPrompt).toMatch(/[Ss]ituation/);
298
+ });
299
+
228
300
  test("system prompt mentions register (locks the routing commitment)", async () => {
229
301
  providerStub = makeProvider(toolUseResponse({ ids: [1] }));
230
302
  await routeL1(makeTurn("x"), makeTree());
@@ -20,14 +20,14 @@ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
20
20
 
21
21
  import { drizzle } from "drizzle-orm/bun-sqlite";
22
22
 
23
- import { migrateAddMemoryV3Selections } from "../../migrations/268-add-memory-v3-selections.js";
24
- import * as schema from "../../schema.js";
23
+ import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
24
+ import * as schema from "../../../../memory/schema.js";
25
25
 
26
26
  const realFlags = {
27
- ...(await import("../../../config/assistant-feature-flags.js")),
27
+ ...(await import("../../../../config/assistant-feature-flags.js")),
28
28
  };
29
- const realLoader = { ...(await import("../../../config/loader.js")) };
30
- const realDb = { ...(await import("../../db-connection.js")) };
29
+ const realLoader = { ...(await import("../../../../config/loader.js")) };
30
+ const realDb = { ...(await import("../../../../memory/db-connection.js")) };
31
31
  const realPageContent = { ...(await import("../page-content.js")) };
32
32
 
33
33
  let storeMockActive = false;
@@ -64,7 +64,7 @@ function seed(
64
64
  }
65
65
  }
66
66
 
67
- mock.module("../../../config/assistant-feature-flags.js", () => ({
67
+ mock.module("../../../../config/assistant-feature-flags.js", () => ({
68
68
  ...realFlags,
69
69
  isAssistantFeatureFlagEnabled: (key: string, config: unknown) =>
70
70
  storeMockActive
@@ -81,12 +81,12 @@ mock.module("../../../config/assistant-feature-flags.js", () => ({
81
81
  ),
82
82
  }));
83
83
 
84
- mock.module("../../../config/loader.js", () => ({
84
+ mock.module("../../../../config/loader.js", () => ({
85
85
  ...realLoader,
86
86
  getConfig: () => (storeMockActive ? {} : realLoader.getConfig()),
87
87
  }));
88
88
 
89
- mock.module("../../db-connection.js", () => ({
89
+ mock.module("../../../../memory/db-connection.js", () => ({
90
90
  ...realDb,
91
91
  getDb: () => (storeMockActive ? testDb : realDb.getDb()),
92
92
  getSqliteFrom: (db: unknown) =>
@@ -4,9 +4,12 @@
4
4
  * Coverage matrix:
5
5
  * - Returned IDs map to the right member slugs by 1-based index, with
6
6
  * `pinned` driven by `pinned_ids`.
7
- * - Omitted `ids` → ALL members of the leaf (recall-safe).
7
+ * - Omitted `ids` → ALL members of the leaf (recall-safe; bounded to one
8
+ * leaf, so this stays a select-all unlike the L1 router).
8
9
  * - Explicit `ids: []` → no pages (deliberate abstention).
9
- * - No provider / missing tool_use / schema mismatch / throw → ALL members.
10
+ * - No provider / missing tool_use / schema mismatch / throw → no pages
11
+ * (degrade to the deterministic lanes), the last three after a re-prompt
12
+ * retry; a malformed response that recovers on retry returns its pages.
10
13
  * - The per-leaf `<pages>` prefix is byte-identical across two calls with
11
14
  * different turns (the cache invariant).
12
15
  * - `selectAcrossLeaves` flattens per-leaf results and never exceeds the
@@ -23,7 +26,7 @@ import type {
23
26
  ProviderResponse,
24
27
  SendMessageOptions,
25
28
  ToolUseContent,
26
- } from "../../../providers/types.js";
29
+ } from "../../../../providers/types.js";
27
30
  import type {
28
31
  LeafNode,
29
32
  LeafPath,
@@ -45,13 +48,13 @@ interface ProviderCall {
45
48
  }
46
49
  const providerCalls: ProviderCall[] = [];
47
50
 
48
- mock.module("../../../providers/provider-send-message.js", () => ({
51
+ mock.module("../../../../providers/provider-send-message.js", () => ({
49
52
  getConfiguredProvider: async () => providerStub,
50
53
  extractToolUse: (response: ProviderResponse) =>
51
54
  response.content.find((b): b is ToolUseContent => b.type === "tool_use"),
52
55
  }));
53
56
 
54
- mock.module("../../../util/logger.js", () => ({
57
+ mock.module("../../../../util/logger.js", () => ({
55
58
  getLogger: () =>
56
59
  new Proxy({} as Record<string, unknown>, {
57
60
  get: (_t, prop) => (prop === "child" ? () => ({}) : () => {}),
@@ -83,6 +86,45 @@ function toolUseResponse(input: Record<string, unknown>): ProviderResponse {
83
86
  };
84
87
  }
85
88
 
89
+ /** A 200 response that carries no tool_use — the malformed-but-successful case
90
+ * the re-prompt retry exists to recover from. */
91
+ function noToolResponse(): ProviderResponse {
92
+ return {
93
+ model: "stub-model",
94
+ stopReason: "end_turn",
95
+ usage: { inputTokens: 0, outputTokens: 0 },
96
+ content: [{ type: "text", text: "no tool call" }],
97
+ };
98
+ }
99
+
100
+ /** Provider returning a different response per call (the i-th call returns
101
+ * responses[i], or the last entry once exhausted), recording each call so a
102
+ * test can assert how many attempts were made. */
103
+ function makeSequenceProvider(responses: ProviderResponse[]): Provider {
104
+ let i = 0;
105
+ return {
106
+ name: "sequence",
107
+ sendMessage: async (messages, options) => {
108
+ providerCalls.push({ messages, options });
109
+ const response = responses[Math.min(i, responses.length - 1)];
110
+ i += 1;
111
+ return response;
112
+ },
113
+ };
114
+ }
115
+
116
+ /** Provider that records each call and then throws — for the throw-after-retries
117
+ * path (the provider's own RetryProvider has already exhausted its backoff). */
118
+ function makeThrowingProvider(): Provider {
119
+ return {
120
+ name: "throwing",
121
+ sendMessage: async (messages, options) => {
122
+ providerCalls.push({ messages, options });
123
+ throw new Error("boom");
124
+ },
125
+ };
126
+ }
127
+
86
128
  function makeLeaf(path: LeafPath, members: Slug[]): LeafNode {
87
129
  return {
88
130
  path,
@@ -206,10 +248,8 @@ describe("selectFromLeaf — id mapping", () => {
206
248
  // selectFromLeaf — recall-safe fallbacks.
207
249
  // ---------------------------------------------------------------------------
208
250
 
209
- describe("selectFromLeaf — recall-safe fallbacks", () => {
210
- const allAlice = ALICE_MEMBERS.map((slug) => ({ slug, pinned: false }));
211
-
212
- test("no provider → ALL members", async () => {
251
+ describe("selectFromLeaf — degradation on failure", () => {
252
+ test("no provider no pages, without calling the provider", async () => {
213
253
  providerStub = null;
214
254
  const result = await selectFromLeaf(
215
255
  "people/alice",
@@ -217,26 +257,23 @@ describe("selectFromLeaf — recall-safe fallbacks", () => {
217
257
  makeTree(),
218
258
  summaryOf,
219
259
  );
220
- expect(result).toEqual(allAlice);
260
+ expect(result).toEqual([]);
261
+ expect(providerCalls).toHaveLength(0);
221
262
  });
222
263
 
223
- test("missing tool_use → ALL members", async () => {
224
- providerStub = makeProvider({
225
- model: "stub-model",
226
- stopReason: "end_turn",
227
- usage: { inputTokens: 0, outputTokens: 0 },
228
- content: [{ type: "text", text: "no tool call" }],
229
- });
264
+ test("missing tool_use → no pages after retrying", async () => {
265
+ providerStub = makeProvider(noToolResponse());
230
266
  const result = await selectFromLeaf(
231
267
  "people/alice",
232
268
  makeTurn("x"),
233
269
  makeTree(),
234
270
  summaryOf,
235
271
  );
236
- expect(result).toEqual(allAlice);
272
+ expect(result).toEqual([]);
273
+ expect(providerCalls).toHaveLength(3);
237
274
  });
238
275
 
239
- test("schema mismatch → ALL members", async () => {
276
+ test("schema mismatch → no pages after retrying", async () => {
240
277
  providerStub = makeProvider(toolUseResponse({ ids: "not-an-array" }));
241
278
  const result = await selectFromLeaf(
242
279
  "people/alice",
@@ -244,23 +281,35 @@ describe("selectFromLeaf — recall-safe fallbacks", () => {
244
281
  makeTree(),
245
282
  summaryOf,
246
283
  );
247
- expect(result).toEqual(allAlice);
284
+ expect(result).toEqual([]);
285
+ expect(providerCalls).toHaveLength(3);
248
286
  });
249
287
 
250
- test("provider throw → ALL members", async () => {
251
- providerStub = {
252
- name: "throwing",
253
- sendMessage: async () => {
254
- throw new Error("boom");
255
- },
256
- };
288
+ test("provider throw → no pages after retrying", async () => {
289
+ providerStub = makeThrowingProvider();
257
290
  const result = await selectFromLeaf(
258
291
  "people/alice",
259
292
  makeTurn("x"),
260
293
  makeTree(),
261
294
  summaryOf,
262
295
  );
263
- expect(result).toEqual(allAlice);
296
+ expect(result).toEqual([]);
297
+ expect(providerCalls).toHaveLength(3);
298
+ });
299
+
300
+ test("a malformed response that recovers on retry returns its pages", async () => {
301
+ providerStub = makeSequenceProvider([
302
+ noToolResponse(),
303
+ toolUseResponse({ ids: [2] }),
304
+ ]);
305
+ const result = await selectFromLeaf(
306
+ "people/alice",
307
+ makeTurn("the 1:1"),
308
+ makeTree(),
309
+ summaryOf,
310
+ );
311
+ expect(result).toEqual([{ slug: "alice-1on1", pinned: false }]);
312
+ expect(providerCalls).toHaveLength(2);
264
313
  });
265
314
  });
266
315
 
@@ -298,13 +347,13 @@ describe("selectFromLeaf — request shape", () => {
298
347
  const [blockA, blockB] = providerCalls[0].messages[0].content as Array<{
299
348
  type: string;
300
349
  text: string;
301
- cache_control?: { type: string };
350
+ cache_control?: { type: string; ttl?: string };
302
351
  }>;
303
352
  expect(blockA.type).toBe("text");
304
353
  expect(blockA.text).toContain("<leaf>people/alice</leaf>");
305
354
  expect(blockA.text).toContain("<pages>");
306
355
  expect(blockA.text).toContain("[1] alice-bio — summary of alice-bio");
307
- expect(blockA.cache_control).toEqual({ type: "ephemeral" });
356
+ expect(blockA.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
308
357
 
309
358
  expect(blockB.type).toBe("text");
310
359
  expect(blockB.text).toContain("<current_message>alice?</current_message>");
@@ -312,6 +361,23 @@ describe("selectFromLeaf — request shape", () => {
312
361
  expect(blockB.cache_control).toBeUndefined();
313
362
  });
314
363
 
364
+ test("situational context renders in the per-turn block when present", async () => {
365
+ providerStub = makeProvider(toolUseResponse({ ids: [1] }));
366
+ await selectFromLeaf(
367
+ "people/alice",
368
+ {
369
+ ...makeTurn("alice?"),
370
+ situationalContext: "Today is Saturday. Alice's anniversary is today.",
371
+ },
372
+ makeTree(),
373
+ summaryOf,
374
+ );
375
+ const blockB = providerCalls[0].messages[0].content[1] as { text: string };
376
+ expect(blockB.text).toContain(
377
+ "<situation>Today is Saturday. Alice's anniversary is today.</situation>",
378
+ );
379
+ });
380
+
315
381
  test("system prompt mentions pinned (locks the pinning commitment)", async () => {
316
382
  providerStub = makeProvider(toolUseResponse({ ids: [1] }));
317
383
  await selectFromLeaf("people/alice", makeTurn("x"), makeTree(), summaryOf);