@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
@@ -15,12 +15,17 @@
15
15
  * cache turn after turn. The trailing recent-context / current-message block
16
16
  * changes every turn, so it carries no breakpoint.
17
17
  *
18
- * Recall-safe fallbacks. The router exists to widen recall, so every failure
19
- * mode degrades toward opening MORE leaves, never fewer:
20
- * - omitted `ids` (model didn't pass the field) → open ALL leaves,
21
- * - missing/failed tool_use, provider unavailable, or any throw ALL leaves.
22
- * The one path that returns nothing is an explicit empty array (`ids: []`),
23
- * which is the model deliberately abstaining.
18
+ * Failure handling. A *model-call* failure is not the same as the model
19
+ * choosing to open everything, so the two no longer share an outcome:
20
+ * - explicit `ids` → open exactly those leaves,
21
+ * - explicit empty array (`ids: []`) open nothing (deliberate abstention),
22
+ * - omitted `ids` open nothing: the router must name the leaves it wants,
23
+ * never the whole tree (~137 leaves would fan out a full L2 pass per turn),
24
+ * - infrastructure failure (provider unavailable, a throw that survived the
25
+ * provider's own retries, no usable `tool_use`, or a schema mismatch) →
26
+ * open nothing after a short re-prompt retry, degrading to the deterministic
27
+ * recall lanes (always-on core, the BM25 needle, the carry-forward working
28
+ * set) that the orchestrator unions in regardless.
24
29
  */
25
30
 
26
31
  import { z } from "zod";
@@ -28,9 +33,10 @@ import { z } from "zod";
28
33
  import {
29
34
  extractToolUse,
30
35
  getConfiguredProvider,
31
- } from "../../providers/provider-send-message.js";
32
- import type { Message, ToolDefinition } from "../../providers/types.js";
33
- import { getLogger } from "../../util/logger.js";
36
+ } from "../../../providers/provider-send-message.js";
37
+ import type { Message, ToolDefinition } from "../../../providers/types.js";
38
+ import { getLogger } from "../../../util/logger.js";
39
+ import { retryForResult } from "./llm-retry.js";
34
40
  import { cachedTextBlock } from "./provider-blocks.js";
35
41
  import type { LeafPath, LeafTree, TurnContext } from "./types.js";
36
42
 
@@ -40,8 +46,8 @@ const log = getLogger("memory-v3-router");
40
46
  const OPEN_LEAVES_TOOL_NAME = "open_leaves";
41
47
 
42
48
  const OpenLeavesSchema = z.object({
43
- // Optional: an omitted `ids` field is the recall-safe "open everything"
44
- // signal, distinct from an explicit empty array (deliberate abstention).
49
+ // Optional so the field can be absent on the wire, but an omitted `ids` opens
50
+ // nothing the router must name the leaves it wants, never the whole tree.
45
51
  ids: z.array(z.number().int()).optional(),
46
52
  });
47
53
 
@@ -50,8 +56,8 @@ const OPEN_LEAVES_TOOL: ToolDefinition = {
50
56
  description:
51
57
  "Open the leaves whose contents could plausibly bear on the next reply. " +
52
58
  "Lean toward inclusion — a missed relevant leaf is a worse error than an " +
53
- "unused one. Omit `ids` entirely to open every leaf; return `[]` only " +
54
- "when nothing in the tree could possibly help.",
59
+ "unused one. Pass the chosen IDs explicitly; return `[]` only when nothing " +
60
+ "in the tree could possibly help.",
55
61
  input_schema: {
56
62
  type: "object",
57
63
  properties: {
@@ -65,13 +71,14 @@ const OPEN_LEAVES_TOOL: ToolDefinition = {
65
71
 
66
72
  const SYSTEM_PROMPT = `You route a conversation turn to the leaves of a topic tree that should be opened for the next reply.
67
73
 
68
- Each leaf has a numbered ID, a path, and a description of what it holds. Decide which leaves to open by weighing three signals:
74
+ Each leaf has a numbered ID, a path, and a description of what it holds. Decide which leaves to open by weighing four signals:
69
75
 
70
76
  - Topic — entities, projects, and events named or implied by the turn.
71
77
  - Register — the affect and mode of the message (e.g. playful, distressed, formal). A register signal is enough to open a leaf even when no entity is named.
72
78
  - Recent context — the immediately preceding exchange, which resolves references like "this", "that", or "the same thing" to concrete topics.
79
+ - Situation — the current date and a live scratchpad of what is salient right now. A date or state cue can make a leaf relevant even when the message never names it (e.g. a person whose anniversary is today, an active thread).
73
80
 
74
- Include on doubt: open every leaf that could plausibly hold something useful. Missing a relevant leaf is a worse error than opening an unused one. Call \`open_leaves\` with the chosen IDs. Omit \`ids\` to open every leaf; return \`[]\` only when nothing in the tree could possibly help.`;
81
+ Include on doubt: open every leaf that could plausibly hold something useful. Missing a relevant leaf is a worse error than opening an unused one. Call \`open_leaves\` with the chosen IDs explicitly; return \`[]\` only when nothing in the tree could possibly help.`;
75
82
 
76
83
  /** Leaves sorted deterministically by path so the numbered block is stable. */
77
84
  function sortedLeaves(tree: LeafTree): LeafPath[] {
@@ -104,10 +111,10 @@ export function renderLeafBlock(tree: LeafTree): string {
104
111
  }
105
112
 
106
113
  /**
107
- * Run the L1 router for one turn. Returns the leaf paths to open.
108
- *
109
- * Recall-safe: any failure to obtain an explicit selection returns ALL leaves.
110
- * Only an explicit empty `ids` array returns no leaves.
114
+ * Run the L1 router for one turn. Returns the leaf paths to open — only ever the
115
+ * leaves the model names explicitly. An omitted `ids`, an explicit `[]`, or an
116
+ * infrastructure failure (after a short re-prompt retry) all open nothing,
117
+ * degrading to the deterministic recall lanes the orchestrator unions in.
111
118
  */
112
119
  export async function routeL1(
113
120
  turn: TurnContext,
@@ -118,8 +125,10 @@ export async function routeL1(
118
125
 
119
126
  const provider = await getConfiguredProvider("memoryV3RouteL1");
120
127
  if (!provider) {
121
- log.warn("memoryV3RouteL1 provider unavailable; opening all leaves");
122
- return paths;
128
+ log.warn(
129
+ "L1 router provider unavailable; degrading to deterministic lanes",
130
+ );
131
+ return [];
123
132
  }
124
133
 
125
134
  const userMsg: Message = {
@@ -129,15 +138,21 @@ export async function routeL1(
129
138
  {
130
139
  type: "text",
131
140
  text:
141
+ (turn.situationalContext
142
+ ? `<situation>${turn.situationalContext}</situation>\n`
143
+ : "") +
132
144
  `<recent_context>${turn.recentContext}</recent_context>\n` +
133
145
  `<current_message>${turn.currentMessage}</current_message>`,
134
146
  },
135
147
  ],
136
148
  };
137
149
 
138
- let response;
139
- try {
140
- response = await provider.sendMessage([userMsg], {
150
+ // One forced-tool call, retried a few times so a transient malformed response
151
+ // (no usable tool_use, or tool input that fails the schema) re-prompts before
152
+ // we give up. `null` from an attempt means "unusable, retry"; the provider
153
+ // layer already backs off transient throws, so this loop adds no delay.
154
+ const parsed = await retryForResult(async () => {
155
+ const response = await provider.sendMessage([userMsg], {
141
156
  tools: [OPEN_LEAVES_TOOL],
142
157
  systemPrompt: SYSTEM_PROMPT,
143
158
  config: {
@@ -145,37 +160,28 @@ export async function routeL1(
145
160
  tool_choice: { type: "tool" as const, name: OPEN_LEAVES_TOOL_NAME },
146
161
  },
147
162
  });
148
- } catch (err) {
149
- log.warn({ err }, "L1 router call threw; opening all leaves");
150
- return paths;
151
- }
152
-
153
- const toolBlock = extractToolUse(response);
154
- if (!toolBlock || toolBlock.name !== OPEN_LEAVES_TOOL_NAME) {
155
- log.warn(
156
- { stopReason: response.stopReason },
157
- "L1 router returned no open_leaves tool_use; opening all leaves",
158
- );
159
- return paths;
160
- }
163
+ const toolBlock = extractToolUse(response);
164
+ if (!toolBlock || toolBlock.name !== OPEN_LEAVES_TOOL_NAME) return null;
165
+ const result = OpenLeavesSchema.safeParse(toolBlock.input);
166
+ return result.success ? result.data : null;
167
+ });
161
168
 
162
- const parsed = OpenLeavesSchema.safeParse(toolBlock.input);
163
- if (!parsed.success) {
169
+ if (parsed === null) {
164
170
  log.warn(
165
- { error: parsed.error.message },
166
- "L1 router tool input did not match schema; opening all leaves",
171
+ "L1 router could not obtain a selection after retries; degrading to deterministic lanes",
167
172
  );
168
- return paths;
173
+ return [];
169
174
  }
170
175
 
171
- // Omitted `ids` is the recall-safe "open everything" signal.
172
- if (parsed.data.ids === undefined) return paths;
176
+ // An omitted `ids` field means the model named no leaves — open nothing rather
177
+ // than the whole tree. Only explicitly listed IDs open leaves.
178
+ if (parsed.ids === undefined) return [];
173
179
 
174
180
  // Map 1-based IDs back to leaf paths, dropping out-of-range IDs without
175
181
  // throwing. De-duplicate while preserving model-returned order.
176
182
  const seen = new Set<number>();
177
183
  const selected: LeafPath[] = [];
178
- for (const id of parsed.data.ids) {
184
+ for (const id of parsed.ids) {
179
185
  if (id < 1 || id > paths.length || seen.has(id)) continue;
180
186
  seen.add(id);
181
187
  selected.push(paths[id - 1]);
@@ -7,10 +7,10 @@
7
7
  * across turns) and can't be reproduced after the fact.
8
8
  */
9
9
 
10
- import type { MemoryV3SelectionLog } from "../../api/responses/memory-v3-selection-log.js";
11
- import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
12
- import { getConfig } from "../../config/loader.js";
13
- import { getDb, getSqliteFrom } from "../db-connection.js";
10
+ import type { MemoryV3SelectionLog } from "../../../api/responses/memory-v3-selection-log.js";
11
+ import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
12
+ import { getConfig } from "../../../config/loader.js";
13
+ import { getDb, getSqliteFrom } from "../../../memory/db-connection.js";
14
14
  import { renderV3PageContent } from "./page-content.js";
15
15
  import { renderMemoryBlock } from "./render-injection.js";
16
16
  import type { Slug } from "./types.js";
@@ -14,11 +14,18 @@
14
14
  * turn after turn. The trailing recent-context / current-message block changes
15
15
  * every turn and carries no breakpoint. This mirrors `./router.ts`.
16
16
  *
17
- * Recall-safe fallbacks. Like the router, the selector exists to widen recall,
18
- * so every failure degrades toward selecting MORE pages, never fewer:
19
- * - omitted `ids` → select ALL members of the leaf,
20
- * - missing/failed tool_use, provider unavailable, or any throw ALL members.
21
- * Only an explicit empty `ids: []` returns nothing (deliberate abstention).
17
+ * Failure handling. A deliberate "select everything" and a model-call failure
18
+ * are different events with different outcomes:
19
+ * - explicit `ids` → select exactly those pages,
20
+ * - explicit empty `ids: []` select nothing (deliberate abstention),
21
+ * - omitted `ids` select ALL members of the leaf (the recall-safe "this
22
+ * whole leaf is relevant" signal, e.g. "give me all of X"); bounded to one
23
+ * leaf, so unlike the router this stays a select-all,
24
+ * - infrastructure failure (provider unavailable, a throw that survived the
25
+ * provider's own retries, no usable `tool_use`, or a schema mismatch) →
26
+ * select nothing after a short re-prompt retry, degrading to the
27
+ * deterministic recall lanes (core, needle, carry-forward working set) the
28
+ * orchestrator unions in regardless.
22
29
  */
23
30
 
24
31
  import { z } from "zod";
@@ -26,10 +33,11 @@ import { z } from "zod";
26
33
  import {
27
34
  extractToolUse,
28
35
  getConfiguredProvider,
29
- } from "../../providers/provider-send-message.js";
30
- import type { Message, ToolDefinition } from "../../providers/types.js";
31
- import { getLogger } from "../../util/logger.js";
32
- import { mapLimit } from "../../util/map-limit.js";
36
+ } from "../../../providers/provider-send-message.js";
37
+ import type { Message, ToolDefinition } from "../../../providers/types.js";
38
+ import { getLogger } from "../../../util/logger.js";
39
+ import { mapLimit } from "../../../util/map-limit.js";
40
+ import { retryForResult } from "./llm-retry.js";
33
41
  import { cachedTextBlock } from "./provider-blocks.js";
34
42
  import { membersOf } from "./tree.js";
35
43
  import type { LeafPath, LeafTree, Slug, TurnContext } from "./types.js";
@@ -55,11 +63,13 @@ const SelectPagesSchema = z.object({
55
63
  const SELECT_PAGES_TOOL: ToolDefinition = {
56
64
  name: SELECT_PAGES_TOOL_NAME,
57
65
  description:
58
- "Select the pages in this leaf whose content is relevant or useful for " +
59
- "the next reply. Lean toward inclusion — a missed relevant page is a " +
60
- "worse error than an unused one. Pass `pinned_ids` for pages the " +
61
- "conversation is centrally about. Omit `ids` entirely to select every " +
62
- "page; return `[]` only when none of the pages could possibly help.",
66
+ "Select the pages in this leaf whose content the reply would directly " +
67
+ "draw on. Be selectiveprefer a few precisely-relevant pages over many " +
68
+ "loosely-related ones; a leaf opened on a weak signal may yield none. " +
69
+ "Pass `pinned_ids` for pages the conversation is centrally about. Omit " +
70
+ "`ids` only as a recall-safe fallback when you cannot judge the leaf " +
71
+ "(selects every page); return `[]` when pages are present but none are " +
72
+ "directly relevant.",
63
73
  input_schema: {
64
74
  type: "object",
65
75
  properties: {
@@ -75,11 +85,13 @@ const SELECT_PAGES_TOOL: ToolDefinition = {
75
85
  },
76
86
  };
77
87
 
78
- const SYSTEM_PROMPT = `This leaf of the topic tree is potentially relevant to the conversation. Select the pages whose content is relevant or useful for responding.
88
+ const SYSTEM_PROMPT = `This leaf of the topic tree is potentially relevant to the conversation. Select ONLY the pages whose content the reply to THIS message would directly draw on.
79
89
 
80
- Be inclusive include frame and affect matches, not just literal-topic matches. A page that shares the conversation's mode or register can be as useful as one that names the same entity. Missing a relevant page is a worse error than selecting an unused one.
90
+ Be selective: exclude pages that are merely topically adjacent, part of the ever-present background, or only loosely related. Most opened leaves should contribute a few precisely-relevant pages, not most of their contents a leaf opened on a weak signal may yield none.
81
91
 
82
- If the conversation is centrally ABOUT a page (rather than only peripherally relevant to it), mark that page as pinned. Call \`select_pages\` with the chosen IDs. Omit \`ids\` to select every page; return \`[]\` only when none of the pages could possibly help.`;
92
+ A page can also be directly relevant because of the current situation the date or the live scratchpad not only the message: keep a page the situation makes pertinent (e.g. a person whose anniversary is today).
93
+
94
+ If the conversation is centrally ABOUT a page (rather than only peripherally relevant to it), mark that page as pinned. Call \`select_pages\` with the chosen IDs. Omit \`ids\` only as a recall-safe fallback when you cannot judge the leaf (selects every page); return \`[]\` when the pages are present but none are directly relevant.`;
83
95
 
84
96
  /**
85
97
  * Render the STATIC numbered `<pages>` block for a leaf from its member slugs.
@@ -103,8 +115,10 @@ async function renderPagesBlock(
103
115
  /**
104
116
  * Run the L2 selector for a single opened leaf. Returns the pages to inject.
105
117
  *
106
- * Recall-safe: any failure to obtain an explicit selection returns ALL members
107
- * of the leaf. Only an explicit empty `ids` array returns no pages.
118
+ * An omitted `ids` selects ALL members (the recall-safe "whole leaf is
119
+ * relevant" signal); an explicit `[]` selects none; an infrastructure failure
120
+ * (after a short re-prompt retry) selects none, degrading to the deterministic
121
+ * recall lanes the orchestrator unions in.
108
122
  */
109
123
  export async function selectFromLeaf(
110
124
  leaf: LeafPath,
@@ -120,8 +134,11 @@ export async function selectFromLeaf(
120
134
 
121
135
  const provider = await getConfiguredProvider("memoryV3SelectL2");
122
136
  if (!provider) {
123
- log.warn({ leaf }, "memoryV3SelectL2 provider unavailable; selecting all");
124
- return allMembers();
137
+ log.warn(
138
+ { leaf },
139
+ "L2 selector provider unavailable; degrading to deterministic lanes",
140
+ );
141
+ return [];
125
142
  }
126
143
 
127
144
  const userMsg: Message = {
@@ -134,15 +151,21 @@ export async function selectFromLeaf(
134
151
  {
135
152
  type: "text",
136
153
  text:
154
+ (turn.situationalContext
155
+ ? `<situation>${turn.situationalContext}</situation>\n`
156
+ : "") +
137
157
  `<recent_context>${turn.recentContext}</recent_context>\n` +
138
158
  `<current_message>${turn.currentMessage}</current_message>`,
139
159
  },
140
160
  ],
141
161
  };
142
162
 
143
- let response;
144
- try {
145
- response = await provider.sendMessage([userMsg], {
163
+ // One forced-tool call, retried a few times so a transient malformed response
164
+ // (no usable tool_use, or tool input that fails the schema) re-prompts before
165
+ // we give up. `null` from an attempt means "unusable, retry"; the provider
166
+ // layer already backs off transient throws, so this loop adds no delay.
167
+ const parsed = await retryForResult(async () => {
168
+ const response = await provider.sendMessage([userMsg], {
146
169
  tools: [SELECT_PAGES_TOOL],
147
170
  systemPrompt: SYSTEM_PROMPT,
148
171
  config: {
@@ -150,39 +173,31 @@ export async function selectFromLeaf(
150
173
  tool_choice: { type: "tool" as const, name: SELECT_PAGES_TOOL_NAME },
151
174
  },
152
175
  });
153
- } catch (err) {
154
- log.warn({ err, leaf }, "L2 selector call threw; selecting all");
155
- return allMembers();
156
- }
157
-
158
- const toolBlock = extractToolUse(response);
159
- if (!toolBlock || toolBlock.name !== SELECT_PAGES_TOOL_NAME) {
160
- log.warn(
161
- { stopReason: response.stopReason, leaf },
162
- "L2 selector returned no select_pages tool_use; selecting all",
163
- );
164
- return allMembers();
165
- }
176
+ const toolBlock = extractToolUse(response);
177
+ if (!toolBlock || toolBlock.name !== SELECT_PAGES_TOOL_NAME) return null;
178
+ const result = SelectPagesSchema.safeParse(toolBlock.input);
179
+ return result.success ? result.data : null;
180
+ });
166
181
 
167
- const parsed = SelectPagesSchema.safeParse(toolBlock.input);
168
- if (!parsed.success) {
182
+ if (parsed === null) {
169
183
  log.warn(
170
- { error: parsed.error.message, leaf },
171
- "L2 selector tool input did not match schema; selecting all",
184
+ { leaf },
185
+ "L2 selector could not obtain a selection after retries; degrading to deterministic lanes",
172
186
  );
173
- return allMembers();
187
+ return [];
174
188
  }
175
189
 
176
- // Omitted `ids` is the recall-safe "select everything" signal.
177
- if (parsed.data.ids === undefined) return allMembers();
190
+ // Omitted `ids` is the recall-safe "this whole leaf is relevant" signal.
191
+ // Bounded to one leaf, so it stays a select-all (unlike the L1 router).
192
+ if (parsed.ids === undefined) return allMembers();
178
193
 
179
- const pinned = new Set(parsed.data.pinned_ids ?? []);
194
+ const pinned = new Set(parsed.pinned_ids ?? []);
180
195
 
181
196
  // Map 1-based IDs back to member slugs, dropping out-of-range IDs without
182
197
  // throwing. De-duplicate while preserving model-returned order.
183
198
  const seen = new Set<number>();
184
199
  const selected: SelectedPage[] = [];
185
- for (const id of parsed.data.ids) {
200
+ for (const id of parsed.ids) {
186
201
  if (id < 1 || id > members.length || seen.has(id)) continue;
187
202
  seen.add(id);
188
203
  selected.push({ slug: members[id - 1]!, pinned: pinned.has(id) });
@@ -1,18 +1,18 @@
1
1
  /**
2
- * Memory v3 — flag-gated shadow/live plugin.
2
+ * Memory v3 — flag-gated shadow/live orchestration engine.
3
3
  *
4
- * Registered as an {@link Injector} that runs the v3 orchestrator each turn and
5
- * records its selection set to `memory_v3_selections`. Two flags gate its
6
- * injection behavior:
4
+ * Runs the v3 orchestrator each turn and records its selection set to
5
+ * `memory_v3_selections`. Two flags gate behavior:
7
6
  *
8
- * - `memory-v3-shadow` (and live OFF): observation-only. `produce()` returns
9
- * `null`, so v2 injection is bit-for-bit identical whether the flag is on
10
- * or off — the only difference is the side-effect telemetry write.
11
- * - `memory-v3-live`: live injection. `produce()` additionally renders the
12
- * working-set selection into a `<memory>` block via {@link renderMemoryBlock}
13
- * and returns it as an injection block at v2's dynamic-memory placement
7
+ * - `memory-v3-shadow` (live OFF): observation-only. {@link observeTurn}
8
+ * orchestrates and logs the selection set; no injection is produced, so v2
9
+ * injection is bit-for-bit identical — the only difference is the
10
+ * side-effect telemetry write.
11
+ * - `memory-v3-live`: live injection. The injector (`memoryV3Injector` in
12
+ * `./injector.ts`) additionally renders the working-set selection into a
13
+ * `<memory>` block and returns it at v2's dynamic-memory placement
14
14
  * (`after-memory-prefix`). Selections are still logged.
15
- * - both OFF: `produce()` returns `null` and skips orchestration entirely.
15
+ * - both OFF: orchestration is skipped entirely.
16
16
  *
17
17
  * On each turn (either flag on):
18
18
  * 1. Lazy-init the v3 lanes ONCE across the whole process (leaf tree, core
@@ -22,49 +22,45 @@
22
22
  * 3. Run {@link orchestrate} and record its selection set to
23
23
  * `memory_v3_selections` with a best-effort lane attribution.
24
24
  *
25
- * Everything after the flag read is wrapped in try/catch — any failure is
26
- * logged and swallowed so it can never affect the live turn. In live mode a
27
- * failure returns `null` (no v3 injection); v2 suppression keys off BOTH the
28
- * flag AND whether `produce()` actually returned a block, so a v3 failure (or
29
- * empty selection) falls back to v2 memory rather than dropping all memory.
25
+ * {@link observeTurn} wraps everything after the flag read in try/catch — any
26
+ * failure is logged and swallowed so it can never affect the live turn. The
27
+ * injector treats a `null`/empty result as "no v3 injection", so v2 memory
28
+ * remains the fallback rather than dropping all memory.
30
29
  */
31
30
 
32
- import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
33
- import { getConfig } from "../../config/loader.js";
34
- import type { AssistantConfig } from "../../config/schema.js";
35
- import { getMessages } from "../../memory/conversation-crud.js";
36
- import { getDb, getSqliteFrom } from "../../memory/db-connection.js";
37
- import { stringifyMessageContent } from "../../memory/message-content.js";
31
+ import { existsSync, readFileSync } from "node:fs";
32
+
33
+ import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
34
+ import { getConfig } from "../../../config/loader.js";
35
+ import type { AssistantConfig } from "../../../config/schema.js";
36
+ import { getMessages } from "../../../memory/conversation-crud.js";
37
+ import { getDb, getSqliteFrom } from "../../../memory/db-connection.js";
38
+ import { stringifyMessageContent } from "../../../memory/message-content.js";
39
+ import { getPageIndex } from "../../../memory/v2/page-index.js";
40
+ import { getLogger } from "../../../util/logger.js";
38
41
  import {
39
- type InjectionBlock,
40
- type Injector,
41
- type Plugin,
42
- type TurnContext as PluginTurnContext,
43
- } from "../../plugins/types.js";
44
- import { getLogger } from "../../util/logger.js";
45
- import { getWorkspaceDir } from "../../util/platform.js";
46
- import { getPageIndex } from "../v2/page-index.js";
42
+ getWorkspaceDir,
43
+ getWorkspacePromptPath,
44
+ } from "../../../util/platform.js";
45
+ import { stripCommentLines } from "../../../util/strip-comment-lines.js";
47
46
  import { injectCapabilitiesLeaf, isCapabilitySlug } from "./capabilities.js";
48
47
  import { loadCore } from "./core.js";
49
48
  import type { NeedleIndex } from "./needle.js";
50
49
  import { buildNeedleIndex } from "./needle.js";
51
50
  import type { OrchestrateResult } from "./orchestrate.js";
52
51
  import { orchestrate } from "./orchestrate.js";
53
- import { renderV3PageContent } from "./page-content.js";
54
- import { renderMemoryBlock } from "./render-injection.js";
55
52
  import { coreSlugs, loadLeafTree, resolveDataDir } from "./tree.js";
56
53
  import {
57
54
  type LeafPath,
58
55
  type LeafTree,
59
- MEMORY_V3_BLOCK_ID,
60
56
  type SelectionSource,
61
57
  type Slug,
62
58
  type TurnContext,
63
59
  } from "./types.js";
64
60
  import { WorkingSet } from "./working-set.js";
65
61
 
66
- const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
67
- const MEMORY_V3_LIVE = "memory-v3-live" as const;
62
+ export const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
63
+ export const MEMORY_V3_LIVE = "memory-v3-live" as const;
68
64
 
69
65
  const log = getLogger("memory-v3-shadow");
70
66
 
@@ -165,10 +161,42 @@ function getLanes(config: AssistantConfig): Promise<ShadowLanes> {
165
161
  return lanesPromise;
166
162
  }
167
163
 
164
+ /**
165
+ * Read the live NOW.md scratchpad (the user's short "what's salient right now"
166
+ * file), stripped of its comment lines. Mirrors `readNowScratchpad` but reads
167
+ * through the light platform / strip utilities directly, keeping the v3
168
+ * plugin's load (and its test) free of heavier module graphs. Returns `null`
169
+ * when absent, empty, or unreadable.
170
+ */
171
+ function readNowContext(): string | null {
172
+ const nowPath = getWorkspacePromptPath("NOW.md");
173
+ if (!existsSync(nowPath)) return null;
174
+ try {
175
+ const stripped = stripCommentLines(readFileSync(nowPath, "utf-8")).trim();
176
+ return stripped.length > 0 ? stripped : null;
177
+ } catch {
178
+ return null;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Compose the situational signal threaded into L1 routing and L2 selection: the
184
+ * current date plus the live NOW.md scratchpad. The date alone is a weak signal,
185
+ * but together with the scratchpad it lets retrieval surface a leaf the message
186
+ * never names (e.g. an anniversary that falls today). Always returns at least
187
+ * the date line — this mirrors the `c_now`/NOW.md signal the v2 retriever uses.
188
+ */
189
+ function buildSituationalContext(): string {
190
+ const now = readNowContext();
191
+ const today = `Today is ${new Date().toDateString()}.`;
192
+ return now ? `${today}\n\n${now}` : today;
193
+ }
194
+
168
195
  /**
169
196
  * Build a v3 {@link TurnContext} from the conversation's persisted messages.
170
197
  * `currentMessage` is the latest user message; `recentContext` is the tail of
171
- * the recent transcript. Returns `null` when there is no user message to route
198
+ * the recent transcript; `situationalContext` carries the current date and the
199
+ * live NOW.md scratchpad. Returns `null` when there is no user message to route
172
200
  * on (nothing to shadow this turn).
173
201
  */
174
202
  function buildShadowTurn(
@@ -198,6 +226,7 @@ function buildShadowTurn(
198
226
  turnNumber: turnIndex,
199
227
  currentMessage,
200
228
  recentContext,
229
+ situationalContext: buildSituationalContext(),
201
230
  };
202
231
  }
203
232
 
@@ -272,7 +301,7 @@ function writeSelections(
272
301
  * failures are logged and swallowed (returning `null`) so the live turn is
273
302
  * unaffected. Returns `null` when there is no user message to route on.
274
303
  */
275
- async function observeTurn(
304
+ export async function observeTurn(
276
305
  conversationId: string,
277
306
  turnIndex: number,
278
307
  ): Promise<OrchestrateResult | null> {
@@ -280,13 +309,15 @@ async function observeTurn(
280
309
  const turn = buildShadowTurn(conversationId, turnIndex);
281
310
  if (!turn) return null;
282
311
 
283
- const lanes = await getLanes(getConfig());
312
+ const cfg = getConfig();
313
+ const lanes = await getLanes(cfg);
284
314
  const result = await orchestrate(turn, {
285
315
  tree: lanes.tree,
286
316
  core: lanes.core,
287
317
  needle: lanes.needle,
288
318
  workingSet: lanes.workingSet,
289
319
  pageSummary,
320
+ l2Concurrency: cfg.memory.v3.l2Concurrency,
290
321
  });
291
322
 
292
323
  const rows = attributeSelections(lanes.tree, lanes.core, result);
@@ -316,64 +347,3 @@ export async function runShadowObservation(
316
347
  if (!isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, getConfig())) return;
317
348
  await observeTurn(conversationId, turnIndex);
318
349
  }
319
-
320
- /**
321
- * The v3 injector. Reads both flags:
322
- * - `memory-v3-live` on → orchestrate, log, render the working-set selection
323
- * into a `<memory>` block, and return it at v2's dynamic-memory placement.
324
- * - `memory-v3-shadow` on (live off) → orchestrate + log only, return `null`.
325
- * - both off → return `null` (no orchestration).
326
- *
327
- * Empty selection and any failure return `null` (no v3 injection). v2
328
- * suppression keys off BOTH the flag AND this return value, so a `null` here
329
- * (failure or empty selection) falls back to v2 memory rather than dropping all
330
- * memory.
331
- */
332
- const memoryV3Injector: Injector = {
333
- name: "memory-v3-shadow",
334
- // High order so it sorts last; the live `<memory>` block uses the
335
- // after-memory-prefix placement so it lands at the memory boundary regardless
336
- // of this sort key, which only orders content-producing injectors.
337
- order: 1000,
338
- async produce(ctx: PluginTurnContext): Promise<InjectionBlock | null> {
339
- const config = getConfig();
340
- const live = isAssistantFeatureFlagEnabled(MEMORY_V3_LIVE, config);
341
- const shadow = isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, config);
342
- if (!live && !shadow) return null;
343
-
344
- const result = await observeTurn(ctx.conversationId, ctx.turnIndex);
345
- if (!live || !result) return null;
346
-
347
- try {
348
- // `renderMemoryBlock` returns "" for an empty selection; inject nothing.
349
- const text = await renderMemoryBlock(
350
- result.finalInjection,
351
- renderV3PageContent,
352
- );
353
- if (text.length === 0) return null;
354
- return {
355
- id: MEMORY_V3_BLOCK_ID,
356
- text,
357
- // Mirror v2's dynamic `<memory>` block placement.
358
- placement: "after-memory-prefix",
359
- };
360
- } catch (err) {
361
- log.warn(
362
- {
363
- err: err instanceof Error ? err.message : String(err),
364
- conversationId: ctx.conversationId,
365
- },
366
- "memory-v3 live render failed (non-fatal) — falling back to v2",
367
- );
368
- return null;
369
- }
370
- },
371
- };
372
-
373
- export const memoryV3ShadowPlugin: Plugin = {
374
- manifest: {
375
- name: "memory-v3-shadow",
376
- version: "0.0.1",
377
- },
378
- injectors: [memoryV3Injector],
379
- };
@@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url";
5
5
 
6
6
  import { parse as parseYaml } from "yaml";
7
7
 
8
- import { getWorkspaceDir } from "../../util/platform.js";
8
+ import { getWorkspaceDir } from "../../../util/platform.js";
9
9
  import type {
10
10
  LeafFrontmatter,
11
11
  LeafNode,