@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
@@ -5,13 +5,21 @@
5
5
  * before it is sent to the provider. They are pure (no side effects).
6
6
  */
7
7
 
8
- import { existsSync, readFileSync, statSync } from "node:fs";
9
- import { join, resolve } from "node:path";
8
+ import { statSync } from "node:fs";
9
+ import { join } from "node:path";
10
10
 
11
11
  import { type ChannelId, parseInterfaceId } from "../channels/types.js";
12
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
12
13
  import { getConfig } from "../config/loader.js";
14
+ import { stripUserTextBlocksByPrefix } from "../context/strip-injections.js";
13
15
  import { createContextSummaryMessage } from "../context/window-manager.js";
14
- import { getAppDirPath, listAppFiles } from "../memory/app-store.js";
16
+ import { getDocumentsForConversation } from "../documents/document-store.js";
17
+ import {
18
+ getApp,
19
+ getAppDirPath,
20
+ listAppFiles,
21
+ resolveAppDir,
22
+ } from "../memory/app-store.js";
15
23
  import {
16
24
  getMessages as defaultGetMessages,
17
25
  type MessageRow,
@@ -21,8 +29,6 @@ import {
21
29
  extractMemoryPrefixBlocks,
22
30
  stripAllMemoryInjections,
23
31
  } from "../memory/graph/conversation-graph-memory.js";
24
- import type { QdrantSparseVector } from "../memory/qdrant-client.js";
25
- import { MEMORY_V3_BLOCK_ID } from "../memory/v3/types.js";
26
32
  import {
27
33
  readSlackMetadata,
28
34
  readSlackMetadataFromMessageMetadata,
@@ -35,9 +41,9 @@ import {
35
41
  type RenderedSlackTranscriptMessage,
36
42
  renderSlackTranscriptWithProvenance,
37
43
  } from "../messaging/providers/slack/render-transcript.js";
38
- import { getInjectors } from "../plugins/registry.js";
44
+ import { getInjectorChain } from "../plugins/defaults/memory-retrieval/injector-chain.js";
45
+ import { MEMORY_V3_BLOCK_ID } from "../plugins/defaults/memory-v3-shadow/types.js";
39
46
  import type {
40
- DiskPressureInjectionContext,
41
47
  InjectionBlock,
42
48
  InjectionPlacement,
43
49
  TurnContext,
@@ -53,12 +59,19 @@ import { channelStatusToMemberStatus } from "../runtime/routes/inbound-stages/ac
53
59
  import type { SubagentState } from "../subagent/types.js";
54
60
  import { TERMINAL_STATUSES } from "../subagent/types.js";
55
61
  import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
56
- import { getWorkspaceDir, getWorkspacePromptPath } from "../util/platform.js";
57
- import { stripCommentLines } from "../util/strip-comment-lines.js";
62
+ import { findConversationOrSubagent } from "./conversation-registry.js";
63
+ import type {
64
+ DynamicPageSurfaceData,
65
+ SurfaceData,
66
+ SurfaceType,
67
+ } from "./message-protocol.js";
58
68
  import { filterMessagesForUntrustedActor } from "./message-provenance.js";
59
- import { type PkbContextConversation } from "./pkb-context-tracker.js";
60
69
  import type { TrustContext } from "./trust-context.js";
61
70
 
71
+ // The compaction strip lives in the compaction layer (`context/`) so the agent
72
+ // loop can own it; re-exported here for this module's existing consumers.
73
+ export { stripInjectionsForCompaction } from "../context/strip-injections.js";
74
+
62
75
  /**
63
76
  * Describes the capabilities of the channel through which the user is
64
77
  * interacting. Used to gate UI-specific references and permission asks.
@@ -257,8 +270,8 @@ export function isGroupChatType(chatType?: string): boolean {
257
270
  }
258
271
  }
259
272
 
260
- /** Context about the active workspace surface, passed to applyRuntimeInjections. */
261
- export interface ActiveSurfaceContext {
273
+ /** Context about the active workspace surface, rendered into the `<active_workspace>` block. */
274
+ interface ActiveSurfaceContext {
262
275
  surfaceId: string;
263
276
  html: string;
264
277
  /** When set, the surface is backed by a persisted app. */
@@ -271,8 +284,72 @@ export interface ActiveSurfaceContext {
271
284
  appPages?: Record<string, string>;
272
285
  /** The page currently displayed in the WebView (e.g. "settings.html"). */
273
286
  currentPage?: string;
274
- /** Pre-fetched list of files in the app directory. */
275
- appFiles?: string[];
287
+ }
288
+
289
+ /**
290
+ * Resolve the conversation's active workspace surface into the context block
291
+ * consumed by {@link applyRuntimeInjections}, or `null` when no dynamic-page
292
+ * surface is active. App-backed surfaces are enriched with their persisted app
293
+ * metadata; the file tree is listed on demand by the injector.
294
+ */
295
+ export function buildActiveSurfaceContext(params: {
296
+ currentActiveSurfaceId: string | undefined;
297
+ currentPage: string | undefined;
298
+ surfaceState: ReadonlyMap<
299
+ string,
300
+ { surfaceType: SurfaceType; data: SurfaceData }
301
+ >;
302
+ }): ActiveSurfaceContext | null {
303
+ const { currentActiveSurfaceId, currentPage, surfaceState } = params;
304
+ if (!currentActiveSurfaceId) return null;
305
+
306
+ const stored = surfaceState.get(currentActiveSurfaceId);
307
+ if (!stored || stored.surfaceType !== "dynamic_page") return null;
308
+
309
+ const data = stored.data as DynamicPageSurfaceData;
310
+ const activeSurface: ActiveSurfaceContext = {
311
+ surfaceId: currentActiveSurfaceId,
312
+ html: data.html,
313
+ currentPage,
314
+ };
315
+
316
+ if (data.appId) {
317
+ const app = getApp(data.appId);
318
+ if (app) {
319
+ activeSurface.appId = app.id;
320
+ activeSurface.appName = app.name;
321
+ activeSurface.appDirName = resolveAppDir(app.id).dirName;
322
+ activeSurface.appSchemaJson = app.schemaJson;
323
+ if (app.pages && Object.keys(app.pages).length > 0) {
324
+ activeSurface.appPages = app.pages;
325
+ }
326
+ }
327
+ }
328
+
329
+ return activeSurface;
330
+ }
331
+
332
+ /**
333
+ * Lists the conversation's active documents as the lightweight summaries the
334
+ * `active-documents` injector surfaces to the assistant — letting it target
335
+ * existing documents with `document_update` instead of issuing duplicate
336
+ * `document_create` calls. Returns `null` when the conversation has none.
337
+ */
338
+ export function buildActiveDocuments(conversationId: string): Array<{
339
+ surfaceId: string;
340
+ title: string;
341
+ wordCount: number;
342
+ updatedAt: number;
343
+ }> | null {
344
+ const conversationDocs = getDocumentsForConversation(conversationId);
345
+ return conversationDocs.length > 0
346
+ ? conversationDocs.map((d) => ({
347
+ surfaceId: d.surfaceId,
348
+ title: d.title,
349
+ wordCount: d.wordCount,
350
+ updatedAt: d.updatedAt,
351
+ }))
352
+ : null;
276
353
  }
277
354
 
278
355
  const MAX_CONTEXT_LENGTH = 100_000;
@@ -315,7 +392,7 @@ function injectActiveSurfaceContext(
315
392
  );
316
393
 
317
394
  // File tree with sizes (capped at 50 files to bound prompt size)
318
- const files = ctx.appFiles ?? listAppFiles(ctx.appId);
395
+ const files = listAppFiles(ctx.appId);
319
396
  const MAX_FILE_TREE_ENTRIES = 50;
320
397
  const displayFiles = files.slice(0, MAX_FILE_TREE_ENTRIES);
321
398
  lines.push("", "App files:");
@@ -490,7 +567,7 @@ export function buildSubagentStatusBlock(
490
567
  }
491
568
 
492
569
  // The `<active_subagents>` block is emitted by the `subagent-status` default
493
- // injector (`plugins/defaults/injectors/register.ts`) as an `append-user-tail`
570
+ // injector (`plugins/defaults/memory-retrieval/injectors.ts`) as an `append-user-tail`
494
571
  // placement. Use {@link applyRuntimeInjections} with
495
572
  // `options.subagentStatusBlock` set, or drive the injector chain directly
496
573
  // via `collectInjectorBlocks`.
@@ -514,30 +591,7 @@ function injectVoiceCallControlContext(
514
591
  // NOW.md scratchpad injection
515
592
  // ---------------------------------------------------------------------------
516
593
 
517
- /**
518
- * Read the NOW.md scratchpad from the workspace prompt directory.
519
- *
520
- * Returns the trimmed content with `_`-prefixed comment lines stripped,
521
- * or `null` if the file is missing, empty, or unreadable.
522
- */
523
- export function readNowScratchpad(): string | null {
524
- const nowPath = getWorkspacePromptPath("NOW.md");
525
- if (!existsSync(nowPath)) return null;
526
- try {
527
- const stripped = stripCommentLines(readFileSync(nowPath, "utf-8")).trim();
528
- return stripped.length > 0 ? stripped : null;
529
- } catch {
530
- return null;
531
- }
532
- }
533
-
534
- /**
535
- * The `<NOW.md>` block is emitted by the `now-md` default injector
536
- * (`plugins/defaults/injectors/register.ts`) as an `after-memory-prefix` placement.
537
- * Use {@link applyRuntimeInjections} with `options.nowScratchpad` set.
538
- */
539
-
540
- /** Strip `<NOW.md>` blocks injected by `injectNowScratchpad`. */
594
+ /** Strip `<NOW.md>` blocks injected by the `now-md` default injector. */
541
595
  export function stripNowScratchpad(messages: Message[]): Message[] {
542
596
  return stripUserTextBlocksByPrefix(messages, [
543
597
  // Shared prefix catches both the current tag and any pre-line-limit
@@ -547,102 +601,6 @@ export function stripNowScratchpad(messages: Message[]): Message[] {
547
601
  ]);
548
602
  }
549
603
 
550
- // ---------------------------------------------------------------------------
551
- // PKB (Personal Knowledge Base) injection
552
- // ---------------------------------------------------------------------------
553
-
554
- const PKB_DEFAULT_FILES = [
555
- "INDEX.md",
556
- "essentials.md",
557
- "threads.md",
558
- "buffer.md",
559
- ];
560
-
561
- const AUTOINJECT_FILENAME = "_autoinject.md";
562
-
563
- /** Max buffer.md lines injected into prompts — keeps context bounded even when filing is off. */
564
- const MAX_BUFFER_LINES = 50;
565
-
566
- /**
567
- * Read `_autoinject.md` from the PKB directory and return the list of
568
- * filenames to inject.
569
- *
570
- * - Returns `null` when the file is missing or unreadable — callers
571
- * should fall back to the hardcoded defaults.
572
- * - Returns `[]` when the file exists but has no entries (empty or
573
- * comments only) — an explicit opt-out meaning "inject nothing."
574
- */
575
- export function readAutoinjectList(pkbDir: string): string[] | null {
576
- const filePath = join(pkbDir, AUTOINJECT_FILENAME);
577
- if (!existsSync(filePath)) return null;
578
- try {
579
- const raw = stripCommentLines(readFileSync(filePath, "utf-8"));
580
- const files = raw
581
- .split("\n")
582
- .map((l) => l.trim())
583
- .filter((l) => l.length > 0);
584
- return files.length > 0 ? files : [];
585
- } catch {
586
- return null;
587
- }
588
- }
589
-
590
- /**
591
- * Resolve the effective list of auto-inject filenames for a PKB directory.
592
- *
593
- * This is the single source of truth used both by `readPkbContext` (which
594
- * actually injects the files) and by the PKB reminder-hint tracker in
595
- * `conversation-agent-loop.ts` (which needs to know what's already in
596
- * context so it doesn't redundantly recommend those files).
597
- *
598
- * Returns `PKB_DEFAULT_FILES` when `_autoinject.md` is missing/unreadable,
599
- * or the parsed list (possibly empty) when it is present.
600
- */
601
- export function getPkbAutoInjectList(pkbRoot: string): string[] {
602
- return readAutoinjectList(pkbRoot) ?? PKB_DEFAULT_FILES;
603
- }
604
-
605
- /**
606
- * Read the always-loaded PKB files and append a nudge encouraging the
607
- * assistant to proactively read topic files and use `remember` aggressively.
608
- *
609
- * Which files are loaded is determined by `pkb/_autoinject.md` (one filename
610
- * per line). Falls back to the built-in defaults when that file is absent.
611
- *
612
- * Returns the concatenated content ready for injection, or `null` if all
613
- * files are missing or empty.
614
- */
615
- export function readPkbContext(): string | null {
616
- const pkbDir = join(getWorkspaceDir(), "pkb");
617
- if (!existsSync(pkbDir)) return null;
618
-
619
- const filesToInject = getPkbAutoInjectList(pkbDir);
620
-
621
- const parts: string[] = [];
622
- for (const file of filesToInject) {
623
- // Path traversal guard: reject entries that escape the pkb directory
624
- const filePath = resolve(pkbDir, file);
625
- if (!filePath.startsWith(pkbDir + "/")) continue;
626
-
627
- if (!existsSync(filePath)) continue;
628
- try {
629
- let content = stripCommentLines(readFileSync(filePath, "utf-8")).trim();
630
- if (file === "buffer.md" && content.length > 0) {
631
- // Cap buffer entries to prevent unbounded growth when filing is disabled
632
- const lines = content.split("\n");
633
- if (lines.length > MAX_BUFFER_LINES) {
634
- content = lines.slice(-MAX_BUFFER_LINES).join("\n");
635
- }
636
- }
637
- if (content.length > 0) parts.push(content);
638
- } catch {
639
- // Skip unreadable files
640
- }
641
- }
642
-
643
- return parts.length > 0 ? parts.join("\n\n") : null;
644
- }
645
-
646
604
  /**
647
605
  * Prepend channel capability context to the last user message so the
648
606
  * model knows what the current channel can and cannot do.
@@ -994,53 +952,6 @@ export function buildUnifiedTurnContextBlock(
994
952
  return lines.join("\n");
995
953
  }
996
954
 
997
- // ---------------------------------------------------------------------------
998
- // Prefix-based stripping primitive
999
- // ---------------------------------------------------------------------------
1000
-
1001
- /**
1002
- * A matcher for an injected text block. A plain string matches by prefix
1003
- * (`startsWith`). A `{ prefix, suffix }` wrapper requires BOTH the opening
1004
- * prefix and the closing suffix, so user-authored content that merely begins
1005
- * with an injection-like opening tag (e.g. a message discussing `<info>`
1006
- * markup) is not mistaken for an injected block and dropped. This mirrors
1007
- * `countMemoryPrefixBlocks`, which only treats `<memory>…</memory>` /
1008
- * `<info>…</info>` blocks as injected when the full wrapper is present.
1009
- */
1010
- type InjectionMatcher = string | { prefix: string; suffix: string };
1011
-
1012
- /**
1013
- * Remove text blocks from user messages that match any of the given matchers.
1014
- * If stripping removes all content blocks from a message, the message itself
1015
- * is dropped.
1016
- *
1017
- * This is the shared primitive behind the individual strip* functions and
1018
- * the `stripInjectionsForCompaction` pipeline.
1019
- */
1020
- function stripUserTextBlocksByPrefix(
1021
- messages: Message[],
1022
- matchers: InjectionMatcher[],
1023
- ): Message[] {
1024
- return messages
1025
- .map((message) => {
1026
- if (message.role !== "user") return message;
1027
- const nextContent = message.content.filter((block) => {
1028
- if (block.type !== "text") return true;
1029
- return !matchers.some((m) =>
1030
- typeof m === "string"
1031
- ? block.text.startsWith(m)
1032
- : block.text.startsWith(m.prefix) && block.text.endsWith(m.suffix),
1033
- );
1034
- });
1035
- if (nextContent.length === message.content.length) return message;
1036
- if (nextContent.length === 0) return null;
1037
- return { ...message, content: nextContent };
1038
- })
1039
- .filter(
1040
- (message): message is NonNullable<typeof message> => message != null,
1041
- );
1042
- }
1043
-
1044
955
  // ---------------------------------------------------------------------------
1045
956
  // Individual strip functions (thin wrappers around the primitive)
1046
957
  // ---------------------------------------------------------------------------
@@ -1726,98 +1637,6 @@ export function loadSlackActiveThreadFocusBlock(
1726
1637
  return assembleSlackActiveThreadFocusBlock(rows, capabilities);
1727
1638
  }
1728
1639
 
1729
- /** Matchers stripped by the pipeline (order doesn't matter — single pass). */
1730
- const RUNTIME_INJECTION_PREFIXES: InjectionMatcher[] = [
1731
- "<channel_capabilities>",
1732
- "<channel_command_context>",
1733
- "<disk_pressure_warning>",
1734
- "<channel_turn_context>", // backward-compat: strip legacy separate channel blocks
1735
- "<guardian_context>",
1736
- "<inbound_actor_context>", // backward-compat: strip legacy separate actor blocks
1737
- "<interface_turn_context>", // backward-compat: strip legacy separate interface blocks
1738
- // NOTE: <turn_context> is intentionally NOT stripped — unified turn context
1739
- // blocks persist in history so the assistant retains temporal/actor grounding.
1740
- "<background_turn>",
1741
- "<memory_context __injected>",
1742
- "<memory_context>", // backward-compat: strip legacy blocks from pre-__injected history
1743
- // The static `memory-v2-static` block (`<info>\n…</info>`) and the
1744
- // dynamic activation block (`<memory>\n…</memory>`, plus legacy
1745
- // `<memory __injected>…`) are both stripped so each compaction
1746
- // re-injects the freshest essentials/threads/recent/buffer view and
1747
- // re-runs the activation pipeline, matching the `<knowledge_base>`
1748
- // cadence. The activation pipeline dedupes via `everInjected`, and
1749
- // compaction handles aggregate growth, so accumulation does not cause
1750
- // unbounded context growth. Both wrappers may appear in persisted rows.
1751
- //
1752
- // These two use the full `{ prefix, suffix }` wrapper shape (not a bare
1753
- // prefix) so that user-authored text merely starting with `<memory>\n` or
1754
- // `<info>\n` is never silently dropped during compaction/`/clean`. This
1755
- // matches the full-wrapper requirement in `countMemoryPrefixBlocks`.
1756
- { prefix: "<memory>\n", suffix: "\n</memory>" },
1757
- { prefix: "<info>\n", suffix: "\n</info>" },
1758
- "<voice_call_control>",
1759
- "<workspace_top_level>", // backward-compat: strip legacy workspace blocks
1760
- // NOTE: <workspace> is intentionally NOT stripped — workspace context
1761
- // persists in history so the assistant retains workspace grounding.
1762
- "<temporal_context>\nToday:", // backward-compat: strip legacy temporal blocks
1763
- "<active_subagents>",
1764
- "<active_workspace>",
1765
- "<active_dynamic_page>",
1766
- "<non_interactive_context>",
1767
- // Shared prefix catches both the current NOW.md tag and any pre-line-limit
1768
- // variant that may linger in in-flight histories during a rolling deploy.
1769
- "<NOW.md Always keep this up to date",
1770
- "<now_scratchpad>", // backward-compat: strip legacy blocks from pre-rename history
1771
- "<knowledge_base>",
1772
- "<pkb>", // backward-compat: strip legacy tag from pre-rename history
1773
- "<system_reminder>",
1774
- "<transport_hints>",
1775
- // The Slack active-thread focus block is non-persisted and injected on
1776
- // the FINAL user turn only. Strip it here so re-assembly during compaction
1777
- // and overflow recovery does not duplicate it across turns.
1778
- "<active_thread>",
1779
- "<system_notice>One or more tool calls returned an error.",
1780
- ];
1781
-
1782
- /**
1783
- * Strip all runtime-injected context from message history in a single pass.
1784
- *
1785
- * Used only during compaction and overflow recovery — not on normal turns.
1786
- * Runtime injections persist in history to keep the conversation prefix
1787
- * stable for Anthropic's prefix caching. Stripping is only needed when
1788
- * compaction rewrites the message array (cache miss is expected anyway).
1789
- */
1790
- export function stripInjectionsForCompaction(messages: Message[]): Message[] {
1791
- return stripUserTextBlocksByPrefix(messages, RUNTIME_INJECTION_PREFIXES);
1792
- }
1793
-
1794
- /**
1795
- * Extract the most recently injected NOW.md content from the message history.
1796
- * Returns null if no NOW.md injection is found.
1797
- */
1798
- export function findLastInjectedNowContent(messages: Message[]): string | null {
1799
- // Matches every NOW.md opening tag we emit (the tag text may evolve over
1800
- // time, e.g. adding a line-limit hint), so in-flight histories with older
1801
- // tag variants remain discoverable during a rolling deploy.
1802
- const openTagPrefix = "<NOW.md Always keep this up to date";
1803
- const suffix = "\n</NOW.md>";
1804
- for (let i = messages.length - 1; i >= 0; i--) {
1805
- const msg = messages[i];
1806
- if (msg.role !== "user") continue;
1807
- for (const block of msg.content) {
1808
- if (block.type !== "text" || !block.text.startsWith(openTagPrefix)) {
1809
- continue;
1810
- }
1811
- const tagEnd = block.text.indexOf(">\n");
1812
- if (tagEnd < 0) continue;
1813
- const contentStart = tagEnd + ">\n".length;
1814
- const end = block.text.lastIndexOf(suffix);
1815
- if (end > contentStart) return block.text.slice(contentStart, end);
1816
- }
1817
- }
1818
- return null;
1819
- }
1820
-
1821
1640
  /**
1822
1641
  * Controls which runtime injections are applied.
1823
1642
  *
@@ -1865,8 +1684,14 @@ export interface RuntimeInjectionResult {
1865
1684
  }
1866
1685
 
1867
1686
  /**
1868
- * Run every registered {@link Injector}'s `produce()` in ascending `order`
1869
- * and return every non-null block the chain produced.
1687
+ * Run every {@link Injector} in the chain ({@link getInjectorChain}, already
1688
+ * sorted by ascending `order`) and return every non-null block it produced.
1689
+ *
1690
+ * `runMessages` is the turn's working message array, forwarded to each
1691
+ * injector so producers that need the current prompt contents read it from a
1692
+ * parameter rather than the shared {@link TurnContext}. Omitted by text-only
1693
+ * callers ({@link composeInjectorChain}) that drive the chain without a
1694
+ * message array.
1870
1695
  *
1871
1696
  * Injectors returning `null` are omitted from the result. The returned array
1872
1697
  * preserves ascending-`order` sort so downstream callers (notably
@@ -1875,12 +1700,11 @@ export interface RuntimeInjectionResult {
1875
1700
  */
1876
1701
  async function collectInjectorBlocks(
1877
1702
  ctx: TurnContext,
1703
+ runMessages?: Message[],
1878
1704
  ): Promise<InjectionBlock[]> {
1879
- const injectors = getInjectors();
1880
- if (injectors.length === 0) return [];
1881
1705
  const out: InjectionBlock[] = [];
1882
- for (const injector of injectors) {
1883
- const block = await injector.produce(ctx);
1706
+ for (const injector of getInjectorChain()) {
1707
+ const block = await injector.produce(ctx, runMessages);
1884
1708
  if (block) out.push(block);
1885
1709
  }
1886
1710
  return out;
@@ -1998,63 +1822,19 @@ function applyInjectionBlock(
1998
1822
  * bag attached to the {@link TurnContext} the caller provides (or to an
1999
1823
  * ephemeral {@link TurnContext} synthesized for test call sites). A small
2000
1824
  * number of fields drive hardcoded branches that live outside the injector
2001
- * chain — `activeSurface`, `channelCapabilities`, `channelCommandContext`,
2002
- * `voiceCallControlPrompt`, `transportHints`, and `isNonInteractive`
2003
- * because they are orchestrator-owned content that never made sense as
2004
- * plugin-overridable default injectors.
1825
+ * chain — `isNonInteractive` — because it is orchestrator-owned content that
1826
+ * never made sense as a plugin-overridable default injector.
1827
+ *
1828
+ * The active workspace surface, the channel capabilities, the active document
1829
+ * list, the channel command context, the voice call-control prompt, and the
1830
+ * transport hints are not on this bag: `applyRuntimeInjections` resolves them
1831
+ * from the live conversation itself (its surface state, `channelCapabilities`,
1832
+ * the document store keyed by `conversationId`, its `commandIntent`, its
1833
+ * `voiceCallControlPrompt`, and its `transportHints` respectively), so the
1834
+ * orchestrator does not compute or thread them per turn.
2005
1835
  */
2006
1836
  export interface RuntimeInjectionOptions {
2007
- diskPressureContext?: DiskPressureInjectionContext | null;
2008
- /**
2009
- * Active dashboard-surface context (read from `<active_workspace>`). Kept
2010
- * on the options bag rather than an injector because it is a
2011
- * channel-capability concern that has never been gated as a default
2012
- * injector.
2013
- */
2014
- activeSurface?: ActiveSurfaceContext | null;
2015
- workspaceTopLevelContext?: string | null;
2016
- channelCapabilities?: ChannelCapabilities | null;
2017
- channelCommandContext?: ChannelCommandContext | null;
2018
1837
  unifiedTurnContext?: string | null;
2019
- voiceCallControlPrompt?: string | null;
2020
- pkbContext?: string | null;
2021
- pkbActive?: boolean;
2022
- /**
2023
- * Dense query vector surfaced from the graph memory retriever.
2024
- * When present together with `pkbActive`, used to run `searchPkbFiles`
2025
- * to surface relevance hints in the PKB system reminder. When missing,
2026
- * the reminder falls back to the flat static text.
2027
- */
2028
- pkbQueryVector?: number[];
2029
- /** Optional sparse vector accompanying `pkbQueryVector`. */
2030
- pkbSparseVector?: QdrantSparseVector;
2031
- /** Memory scope id used to filter PKB search results. */
2032
- pkbScopeId?: string;
2033
- /**
2034
- * The live conversation (or a minimal shape containing `messages`) used
2035
- * to compute which PKB paths are already "in context" and therefore
2036
- * suppressed from hint suggestions.
2037
- */
2038
- pkbConversation?: PkbContextConversation;
2039
- /** Auto-injected PKB filenames (resolved relative to `pkbRoot`). */
2040
- pkbAutoInjectList?: string[];
2041
- /** Absolute path to the PKB directory (e.g. `<workspace>/pkb`). */
2042
- pkbRoot?: string;
2043
- /**
2044
- * Working directory against which relative `file_read` tool paths
2045
- * resolve, used to detect workspace-relative reads like
2046
- * `pkb/threads.md`. Falls back to `pkbRoot` when omitted.
2047
- */
2048
- pkbWorkingDir?: string;
2049
- /**
2050
- * Pre-rendered v2 static memory content (essentials/threads/recent/buffer
2051
- * concatenated, header-wrapped). When non-null on full-mode turns the
2052
- * `memory-v2-static` injector wraps it in `<info>` and splices it onto
2053
- * the user message; subsequent turns leave the prior block cached on its
2054
- * original user message.
2055
- */
2056
- memoryV2Static?: string | null;
2057
- nowScratchpad?: string | null;
2058
1838
  subagentStatusBlock?: string | null;
2059
1839
  isNonInteractive?: boolean;
2060
1840
  /**
@@ -2064,7 +1844,6 @@ export interface RuntimeInjectionOptions {
2064
1844
  * configured reminder.
2065
1845
  */
2066
1846
  isBackgroundConversation?: boolean;
2067
- transportHints?: string[] | null;
2068
1847
  /**
2069
1848
  * Pre-rendered Slack chronological transcript that replaces the
2070
1849
  * default `runMessages` history for any Slack conversation (channels
@@ -2099,22 +1878,7 @@ export interface RuntimeInjectionOptions {
2099
1878
  * `undefined` when the inbound is a top-level (non-thread) post.
2100
1879
  */
2101
1880
  slackActiveThreadFocusBlock?: string | null;
2102
- activeDocuments?: TurnInjectionInputs["activeDocuments"];
2103
1881
  mode?: InjectionMode;
2104
- /**
2105
- * memory-v3-live: when true AND the v3 injector produced a `<memory>` block
2106
- * this turn (placement `after-memory-prefix`, id `memory-v3`), the v2
2107
- * `<memory>` injection that `graphMemory.prepareMemory` prepended to the
2108
- * tail is stripped from EVERY user message before the v3 block is spliced —
2109
- * so v3 becomes the sole `<memory>` source and history stays byte-stable for
2110
- * prompt caching.
2111
- *
2112
- * The strip is keyed off whether v3 ACTUALLY produced a block, not off the
2113
- * flag alone: when v3 errors or selects nothing (its injector returns
2114
- * `null`), v2's block is left in place so the turn still ships memory rather
2115
- * than dropping it (fallback-to-v2). Default false — v2 untouched.
2116
- */
2117
- suppressV2MemoryForV3?: boolean;
2118
1882
  /**
2119
1883
  * Per-turn {@link TurnContext} forwarded to plugin-registered
2120
1884
  * {@link Injector}s via {@link collectInjectorBlocks}. When omitted,
@@ -2141,44 +1905,35 @@ export interface RuntimeInjectionOptions {
2141
1905
  */
2142
1906
  function buildTurnInjectionInputs(
2143
1907
  options: RuntimeInjectionOptions,
1908
+ channelCapabilities: ChannelCapabilities | null,
1909
+ activeDocuments: TurnInjectionInputs["activeDocuments"],
2144
1910
  ): TurnInjectionInputs {
2145
1911
  return {
2146
1912
  mode: options.mode,
2147
- diskPressureContext: options.diskPressureContext,
2148
- workspaceTopLevelContext: options.workspaceTopLevelContext,
2149
1913
  unifiedTurnContext: options.unifiedTurnContext,
2150
- pkbContext: options.pkbContext,
2151
- pkbActive: options.pkbActive,
2152
- pkbQueryVector: options.pkbQueryVector,
2153
- pkbSparseVector: options.pkbSparseVector,
2154
- pkbScopeId: options.pkbScopeId,
2155
- pkbConversation: options.pkbConversation,
2156
- pkbAutoInjectList: options.pkbAutoInjectList,
2157
- pkbRoot: options.pkbRoot,
2158
- pkbWorkingDir: options.pkbWorkingDir,
2159
- memoryV2Static: options.memoryV2Static,
2160
- nowScratchpad: options.nowScratchpad,
2161
1914
  subagentStatusBlock: options.subagentStatusBlock,
2162
- channelCapabilities: options.channelCapabilities,
1915
+ channelCapabilities,
2163
1916
  slackChronologicalMessages: options.slackChronologicalMessages,
2164
1917
  slackActiveThreadFocusBlock: options.slackActiveThreadFocusBlock,
2165
- activeSurface: options.activeSurface,
2166
- channelCommandContext: options.channelCommandContext,
2167
- voiceCallControlPrompt: options.voiceCallControlPrompt,
2168
- transportHints: options.transportHints,
2169
1918
  isNonInteractive: options.isNonInteractive,
2170
1919
  isBackgroundConversation: options.isBackgroundConversation,
2171
- activeDocuments: options.activeDocuments,
1920
+ activeDocuments,
2172
1921
  };
2173
1922
  }
2174
1923
 
1924
+ /**
1925
+ * Conversation id used for the synthetic fallback {@link TurnContext} and the
1926
+ * live-conversation lookup when the caller omits a context (test call sites).
1927
+ */
1928
+ const RUNTIME_ASSEMBLY_FALLBACK_CONVERSATION_ID = "runtime-assembly-fallback";
1929
+
2175
1930
  /** Minimal synthetic TurnContext used when the caller omits one. */
2176
1931
  function synthesizeFallbackTurnContext(
2177
1932
  inputs: TurnInjectionInputs,
2178
1933
  ): TurnContext {
2179
1934
  return {
2180
- requestId: "runtime-assembly-fallback",
2181
- conversationId: "runtime-assembly-fallback",
1935
+ requestId: RUNTIME_ASSEMBLY_FALLBACK_CONVERSATION_ID,
1936
+ conversationId: RUNTIME_ASSEMBLY_FALLBACK_CONVERSATION_ID,
2182
1937
  turnIndex: 0,
2183
1938
  trust: {
2184
1939
  sourceChannel: inputs.channelCapabilities?.channel
@@ -2219,6 +1974,9 @@ function synthesizeFallbackTurnContext(
2219
1974
  * 6. Run the remaining hardcoded branches (`isNonInteractive`,
2220
1975
  * `voiceCallControlPrompt`, `activeSurface`, `channelCapabilities`,
2221
1976
  * `channelCommandContext`, `transportHints`) in their historical order.
1977
+ * `voiceCallControlPrompt`, `activeSurface`, `channelCapabilities`,
1978
+ * `channelCommandContext`, and `transportHints` are sourced from the live
1979
+ * conversation rather than `options`.
2222
1980
  * 7. Finally, apply the chain's remaining blocks by placement:
2223
1981
  * `"append-user-tail"` in ascending `order`, then `"prepend-user-tail"`
2224
1982
  * in descending `order` so the lowest-`order` prepend lands topmost in
@@ -2233,19 +1991,63 @@ export async function applyRuntimeInjections(
2233
1991
  options: RuntimeInjectionOptions,
2234
1992
  ): Promise<RuntimeInjectionResult> {
2235
1993
  const mode = options.mode ?? "full";
2236
- const slackConversation = options.channelCapabilities?.channel === "slack";
1994
+
1995
+ // Source the channel capabilities from the live conversation rather than a
1996
+ // per-turn option computed by the orchestrator. The same value drives the
1997
+ // Slack-conversation gate, the `<channel_capabilities>` branch, and the
1998
+ // per-injector inputs the Slack injectors read.
1999
+ const conversationId =
2000
+ options.turnContext?.conversationId ??
2001
+ RUNTIME_ASSEMBLY_FALLBACK_CONVERSATION_ID;
2002
+ const channelCapabilities =
2003
+ findConversationOrSubagent(conversationId)?.channelCapabilities ?? null;
2004
+ const slackConversation = channelCapabilities?.channel === "slack";
2005
+
2006
+ // Source the active documents from the document store keyed by the same
2007
+ // conversation id rather than a per-turn option computed by the
2008
+ // orchestrator; the `active-documents` and `document-comments` injectors
2009
+ // read the result off their per-injector inputs.
2010
+ const activeDocuments = buildActiveDocuments(conversationId);
2011
+
2012
+ // Source the channel command intent (e.g. Telegram /start) from the live
2013
+ // conversation rather than a per-turn option. It is set on the conversation
2014
+ // at message-processing start and cleared at turn end, so the same value
2015
+ // drives the `<channel_command_context>` branch on every assembly call
2016
+ // within the turn.
2017
+ const channelCommandContext =
2018
+ findConversationOrSubagent(conversationId)?.commandIntent ?? null;
2019
+
2020
+ // Source the voice call-control prompt from the live conversation rather
2021
+ // than a per-turn option. The voice-session bridge sets it on the
2022
+ // conversation when a call attaches and clears it when the call ends, so
2023
+ // the same value drives the `<voice_call_control>` branch on every assembly
2024
+ // call within the turn.
2025
+ const voiceCallControlPrompt =
2026
+ findConversationOrSubagent(conversationId)?.voiceCallControlPrompt ?? null;
2027
+
2028
+ // Source the transport hints from the live conversation rather than a
2029
+ // per-turn option. They are set on the conversation from the inbound
2030
+ // message's transport when the message is processed, so the same value
2031
+ // drives the `<transport_hints>` branch on every assembly call within the
2032
+ // turn.
2033
+ const transportHints =
2034
+ findConversationOrSubagent(conversationId)?.transportHints ?? null;
2237
2035
 
2238
2036
  // Build the per-injector inputs and attach them to the caller's
2239
2037
  // TurnContext (without mutating it). When the caller didn't supply one,
2240
2038
  // synthesize a minimal fallback so the chain still runs — test call sites
2241
2039
  // that drive injection via `options` without constructing a full context
2242
2040
  // continue to work.
2243
- const injectionInputs = buildTurnInjectionInputs(options);
2041
+ const injectionInputs = buildTurnInjectionInputs(
2042
+ options,
2043
+ channelCapabilities,
2044
+ activeDocuments,
2045
+ );
2244
2046
  const turnCtx: TurnContext = options.turnContext
2245
2047
  ? { ...options.turnContext, injectionInputs }
2246
2048
  : synthesizeFallbackTurnContext(injectionInputs);
2247
2049
 
2248
- const chainBlocks = await collectInjectorBlocks(turnCtx);
2050
+ const chainBlocks = await collectInjectorBlocks(turnCtx, runMessages);
2249
2051
 
2250
2052
  // Split the chain output by placement so the downstream assembly can
2251
2053
  // process each slot with the correct ordering rule.
@@ -2325,21 +2127,25 @@ export async function applyRuntimeInjections(
2325
2127
  : undefined;
2326
2128
 
2327
2129
  // ── Step 0: memory-v3-live v2 suppression ──
2328
- // When v3 live mode is on AND the v3 injector actually produced a block this
2329
- // turn, v3 is the sole `<memory>` source. v2's `prepareMemory` already
2330
- // prepended its own `<memory>` block to the tail user message (and historical
2331
- // turns may carry v2 blocks from earlier turns). Strip every user message's
2332
- // memory prefix here so:
2130
+ // When the `memory-v3-live` flag is on AND the v3 injector actually produced
2131
+ // a block this turn, v3 is the sole `<memory>` source. v2's `prepareMemory`
2132
+ // already prepended its own `<memory>` block to the tail user message (and
2133
+ // historical turns may carry v2 blocks from earlier turns). Strip every user
2134
+ // message's memory prefix here so:
2333
2135
  // 1. The v3 `after-memory-prefix` block (Step 2) lands at the top of the
2334
2136
  // tail with no v2 prefix ahead of it — exactly one `<memory>` block.
2335
2137
  // 2. History is byte-stable across turns for prompt caching.
2336
2138
  // Keyed off the v3 block being present (not the flag alone) so a v3 failure
2337
2139
  // (`produce()` → null) leaves v2's block intact — fallback rather than a
2338
2140
  // memory-less turn. Idempotent: re-injection sites that already stripped
2339
- // see no change.
2141
+ // see no change. Flag off → bit-for-bit identical to the v2 path.
2142
+ const suppressV2MemoryForV3 = isAssistantFeatureFlagEnabled(
2143
+ "memory-v3-live",
2144
+ getConfig(),
2145
+ );
2340
2146
  const v3ProducedBlock = afterMemory.some((b) => b.id === MEMORY_V3_BLOCK_ID);
2341
2147
  let runMessagesForAssembly = runMessages;
2342
- if (options.suppressV2MemoryForV3 && v3ProducedBlock) {
2148
+ if (suppressV2MemoryForV3 && v3ProducedBlock) {
2343
2149
  runMessagesForAssembly = stripAllMemoryInjections(runMessages);
2344
2150
  }
2345
2151
 
@@ -2410,42 +2216,56 @@ export async function applyRuntimeInjections(
2410
2216
  }
2411
2217
  }
2412
2218
 
2413
- if (options.voiceCallControlPrompt) {
2219
+ if (voiceCallControlPrompt) {
2414
2220
  const userTail = result[result.length - 1];
2415
2221
  if (userTail && userTail.role === "user") {
2416
2222
  result = [
2417
2223
  ...result.slice(0, -1),
2418
- injectVoiceCallControlContext(userTail, options.voiceCallControlPrompt),
2224
+ injectVoiceCallControlContext(userTail, voiceCallControlPrompt),
2419
2225
  ];
2420
2226
  }
2421
2227
  }
2422
2228
 
2423
- if (mode === "full" && options.activeSurface) {
2424
- const userTail = result[result.length - 1];
2425
- if (userTail && userTail.role === "user") {
2426
- result = [
2427
- ...result.slice(0, -1),
2428
- injectActiveSurfaceContext(userTail, options.activeSurface),
2429
- ];
2229
+ if (mode === "full") {
2230
+ // Source the active workspace surface from the live conversation's surface
2231
+ // state rather than from a per-turn option computed by the orchestrator.
2232
+ const surfaceConversation = findConversationOrSubagent(
2233
+ turnCtx.conversationId,
2234
+ );
2235
+ const activeSurface = surfaceConversation
2236
+ ? buildActiveSurfaceContext({
2237
+ currentActiveSurfaceId: surfaceConversation.currentActiveSurfaceId,
2238
+ currentPage: surfaceConversation.currentPage,
2239
+ surfaceState: surfaceConversation.surfaceState,
2240
+ })
2241
+ : null;
2242
+ if (activeSurface) {
2243
+ const userTail = result[result.length - 1];
2244
+ if (userTail && userTail.role === "user") {
2245
+ result = [
2246
+ ...result.slice(0, -1),
2247
+ injectActiveSurfaceContext(userTail, activeSurface),
2248
+ ];
2249
+ }
2430
2250
  }
2431
2251
  }
2432
2252
 
2433
- if (options.channelCapabilities) {
2253
+ if (channelCapabilities) {
2434
2254
  const userTail = result[result.length - 1];
2435
2255
  if (userTail && userTail.role === "user") {
2436
2256
  result = [
2437
2257
  ...result.slice(0, -1),
2438
- injectChannelCapabilityContext(userTail, options.channelCapabilities),
2258
+ injectChannelCapabilityContext(userTail, channelCapabilities),
2439
2259
  ];
2440
2260
  }
2441
2261
  }
2442
2262
 
2443
- if (mode === "full" && options.channelCommandContext) {
2263
+ if (mode === "full" && channelCommandContext) {
2444
2264
  const userTail = result[result.length - 1];
2445
2265
  if (userTail && userTail.role === "user") {
2446
2266
  result = [
2447
2267
  ...result.slice(0, -1),
2448
- injectChannelCommandContext(userTail, options.channelCommandContext),
2268
+ injectChannelCommandContext(userTail, channelCommandContext),
2449
2269
  ];
2450
2270
  }
2451
2271
  }
@@ -2459,14 +2279,14 @@ export async function applyRuntimeInjections(
2459
2279
  if (
2460
2280
  mode === "full" &&
2461
2281
  !slackConversation &&
2462
- options.transportHints &&
2463
- options.transportHints.length > 0
2282
+ transportHints &&
2283
+ transportHints.length > 0
2464
2284
  ) {
2465
2285
  const userTail = result[result.length - 1];
2466
2286
  if (userTail && userTail.role === "user") {
2467
2287
  result = [
2468
2288
  ...result.slice(0, -1),
2469
- injectTransportHints(userTail, options.transportHints),
2289
+ injectTransportHints(userTail, transportHints),
2470
2290
  ];
2471
2291
  }
2472
2292
  }