@vellumai/assistant 0.8.8 → 0.8.9-staging.2

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 (360) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/examples/plugins/echo/README.md +61 -60
  4. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  5. package/examples/plugins/echo/hooks/stop.ts +16 -0
  6. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  7. package/examples/plugins/echo/package.json +1 -2
  8. package/examples/plugins/echo/src/emit.ts +19 -0
  9. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  10. package/openapi.yaml +235 -6
  11. package/package.json +2 -2
  12. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  13. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  14. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  15. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  16. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  17. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  18. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  19. package/src/__tests__/agent-loop.test.ts +441 -96
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  21. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  22. package/src/__tests__/anthropic-provider.test.ts +1 -1
  23. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  24. package/src/__tests__/app-control-flow.test.ts +1 -1
  25. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  26. package/src/__tests__/app-executors.test.ts +132 -0
  27. package/src/__tests__/approval-cascade.test.ts +5 -4
  28. package/src/__tests__/approval-routes-http.test.ts +4 -1
  29. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  30. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  31. package/src/__tests__/channel-approvals.test.ts +1 -1
  32. package/src/__tests__/compaction-circuit.test.ts +258 -0
  33. package/src/__tests__/compaction-direct.test.ts +132 -0
  34. package/src/__tests__/compaction-events.test.ts +5 -5
  35. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  36. package/src/__tests__/context-overflow-reducer.test.ts +1 -1
  37. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
  38. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  39. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  40. package/src/__tests__/conversation-agent-loop-overflow.test.ts +142 -218
  41. package/src/__tests__/conversation-agent-loop.test.ts +297 -586
  42. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  43. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
  44. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  45. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  46. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
  47. package/src/__tests__/conversation-process-callsite.test.ts +14 -14
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +70 -65
  49. package/src/__tests__/conversation-queue.test.ts +9 -9
  50. package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
  51. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  52. package/src/__tests__/conversation-slash-queue.test.ts +6 -4
  53. package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
  54. package/src/__tests__/conversation-speed-override.test.ts +10 -9
  55. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  56. package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
  57. package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
  59. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  61. package/src/__tests__/db-acp-history.test.ts +101 -0
  62. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  63. package/src/__tests__/empty-response-hook.test.ts +1 -1
  64. package/src/__tests__/file-write-tool.test.ts +63 -0
  65. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  66. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  67. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  68. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  69. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  70. package/src/__tests__/heartbeat-service.test.ts +1 -0
  71. package/src/__tests__/history-repair-hook.test.ts +1 -1
  72. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  73. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  74. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  75. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  76. package/src/__tests__/injector-background-turn.test.ts +13 -23
  77. package/src/__tests__/injector-chain.test.ts +268 -44
  78. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  79. package/src/__tests__/injector-document-comments.test.ts +97 -114
  80. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  81. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  82. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  83. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  84. package/src/__tests__/memory-retrieval-hook.test.ts +131 -26
  85. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  86. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  87. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  88. package/src/__tests__/plugin-bootstrap.test.ts +12 -23
  89. package/src/__tests__/plugin-registry.test.ts +3 -49
  90. package/src/__tests__/plugin-types.test.ts +0 -70
  91. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  92. package/src/__tests__/reaction-persistence.test.ts +1 -1
  93. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  94. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  95. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  96. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  97. package/src/__tests__/subagent-detail.test.ts +25 -7
  98. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  99. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  100. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  101. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  102. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  103. package/src/__tests__/title-generate-hook.test.ts +1 -1
  104. package/src/__tests__/tool-error-hook.test.ts +1 -1
  105. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  106. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  107. package/src/acp/__tests__/agent-process.test.ts +161 -0
  108. package/src/acp/__tests__/client-handler.test.ts +40 -0
  109. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  110. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  111. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  112. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  113. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  114. package/src/acp/agent-process.ts +61 -1
  115. package/src/acp/auto-install.test.ts +280 -0
  116. package/src/acp/auto-install.ts +232 -0
  117. package/src/acp/client-handler.ts +31 -0
  118. package/src/acp/feature-gate.test.ts +48 -0
  119. package/src/acp/feature-gate.ts +34 -0
  120. package/src/acp/prepare-agent-env.ts +80 -27
  121. package/src/acp/resolve-agent.test.ts +225 -9
  122. package/src/acp/resolve-agent.ts +122 -17
  123. package/src/acp/resume-hint.ts +23 -0
  124. package/src/acp/session-manager.ts +507 -73
  125. package/src/agent/compaction-circuit.ts +60 -102
  126. package/src/agent/loop.ts +414 -248
  127. package/src/api/responses/conversation-message.ts +14 -1
  128. package/src/approvals/guardian-request-resolvers.ts +1 -1
  129. package/src/background-wake/next-wake.ts +1 -0
  130. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  131. package/src/cli/commands/plugins.ts +43 -37
  132. package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
  133. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  134. package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
  135. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
  136. package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
  137. package/src/cli/lib/install-from-github.ts +464 -55
  138. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  139. package/src/cli/lib/plugin-details.ts +409 -0
  140. package/src/cli/lib/plugin-marketplace.ts +197 -0
  141. package/src/cli/lib/search-plugins.ts +195 -29
  142. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  143. package/src/config/acp-defaults.test.ts +10 -0
  144. package/src/config/acp-defaults.ts +6 -0
  145. package/src/config/bundled-skills/acp/SKILL.md +85 -33
  146. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  147. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  148. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  149. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  150. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  151. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  152. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  153. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  154. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  155. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  156. package/src/config/bundled-tool-registry.ts +4 -0
  157. package/src/config/call-site-defaults.ts +0 -2
  158. package/src/config/feature-flag-registry.json +15 -6
  159. package/src/config/schemas/call-site-catalog.ts +0 -14
  160. package/src/config/schemas/heartbeat.ts +9 -0
  161. package/src/config/schemas/llm.ts +0 -2
  162. package/src/context/compactor.ts +22 -4
  163. package/src/context/strip-injections.ts +8 -2
  164. package/src/context/window-manager.ts +27 -13
  165. package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
  166. package/src/daemon/conversation-agent-loop.ts +175 -1055
  167. package/src/daemon/conversation-lifecycle.ts +11 -255
  168. package/src/daemon/conversation-process.ts +8 -136
  169. package/src/daemon/conversation-registry.ts +159 -0
  170. package/src/daemon/conversation-runtime-assembly.ts +293 -392
  171. package/src/daemon/conversation-store.ts +9 -90
  172. package/src/daemon/conversation-surfaces.ts +24 -8
  173. package/src/daemon/conversation-workspace.ts +17 -0
  174. package/src/daemon/conversation.ts +404 -57
  175. package/src/daemon/disk-pressure-policy.ts +0 -1
  176. package/src/daemon/external-plugins-bootstrap.ts +14 -19
  177. package/src/daemon/handlers/conversations.ts +3 -1
  178. package/src/daemon/handlers/skills.ts +4 -1
  179. package/src/daemon/host-proxy-preactivation.ts +1 -3
  180. package/src/daemon/lifecycle.ts +21 -7
  181. package/src/daemon/server.ts +2 -0
  182. package/src/daemon/wake-conversation-ops.ts +269 -0
  183. package/src/embedded/plugin-api.ts +2 -2
  184. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  185. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  186. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  187. package/src/heartbeat/heartbeat-service.ts +26 -0
  188. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  189. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  190. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  191. package/src/ipc/skill-routes/memory.ts +4 -2
  192. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  193. package/src/memory/conversation-crud.ts +29 -19
  194. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  195. package/src/memory/db-init.ts +2 -0
  196. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  197. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  198. package/src/memory/jobs-worker.ts +25 -1
  199. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  200. package/src/memory/migrations/index.ts +1 -0
  201. package/src/memory/schema/acp.ts +4 -0
  202. package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
  203. package/src/memory/v2/consolidation-job.ts +13 -4
  204. package/src/plugin-api/constants.ts +4 -0
  205. package/src/plugin-api/index.ts +6 -5
  206. package/src/plugin-api/types.ts +75 -0
  207. package/src/plugins/defaults/compaction/compact.ts +59 -0
  208. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +7 -7
  209. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  210. package/src/plugins/defaults/compaction/package.json +1 -2
  211. package/src/plugins/defaults/empty-response/package.json +0 -1
  212. package/src/plugins/defaults/history-repair/package.json +0 -1
  213. package/src/plugins/defaults/index.ts +135 -26
  214. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  215. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +184 -74
  216. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  217. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  218. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  219. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  220. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +9 -6
  221. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  222. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +8 -5
  223. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  224. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  225. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +10 -5
  226. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  227. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +5 -5
  228. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
  229. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
  230. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
  231. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
  232. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  233. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  234. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  235. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
  236. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
  237. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  238. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  239. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
  240. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
  241. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  242. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +5 -5
  243. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  244. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +7 -7
  245. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
  246. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  247. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -1
  248. package/src/plugins/defaults/title-generate/package.json +0 -1
  249. package/src/plugins/defaults/tool-error/package.json +0 -1
  250. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  251. package/src/plugins/pipeline.ts +6 -293
  252. package/src/plugins/registry.ts +9 -37
  253. package/src/plugins/types.ts +76 -381
  254. package/src/plugins/user-loader.ts +30 -127
  255. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  256. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
  257. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  258. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  259. package/src/runtime/agent-wake.ts +108 -209
  260. package/src/runtime/assistant-event-hub.ts +1 -1
  261. package/src/runtime/channel-approvals.ts +1 -1
  262. package/src/runtime/interactive-ui.ts +1 -1
  263. package/src/runtime/routes/__tests__/acp-routes.test.ts +315 -55
  264. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  265. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  266. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  267. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  268. package/src/runtime/routes/acp-routes.test.ts +89 -25
  269. package/src/runtime/routes/acp-routes.ts +81 -29
  270. package/src/runtime/routes/approval-routes.ts +1 -1
  271. package/src/runtime/routes/browser-routes.ts +1 -1
  272. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  273. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  274. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  275. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  276. package/src/runtime/routes/conversation-routes.ts +28 -2
  277. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  278. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  279. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  280. package/src/runtime/routes/host-cu-routes.ts +1 -1
  281. package/src/runtime/routes/identity-routes.ts +18 -3
  282. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  283. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  284. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  285. package/src/runtime/routes/memory-v3-routes.ts +16 -6
  286. package/src/runtime/routes/playground/helpers.ts +1 -1
  287. package/src/runtime/routes/plugins-routes.ts +337 -35
  288. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  289. package/src/runtime/routes/work-items-routes.ts +2 -4
  290. package/src/runtime/services/conversation-serializer.ts +1 -1
  291. package/src/signals/cancel.ts +2 -4
  292. package/src/subagent/manager.ts +21 -5
  293. package/src/tools/acp/context.ts +20 -0
  294. package/src/tools/acp/list-agents.test.ts +8 -2
  295. package/src/tools/acp/spawn.test.ts +176 -195
  296. package/src/tools/acp/spawn.ts +37 -172
  297. package/src/tools/acp/steer.test.ts +105 -8
  298. package/src/tools/acp/steer.ts +48 -17
  299. package/src/tools/apps/executors.ts +166 -50
  300. package/src/tools/filesystem/write.ts +34 -0
  301. package/src/tools/subagent/spawn.ts +2 -4
  302. package/src/tools/ui-surface/definitions.ts +25 -5
  303. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  304. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  305. package/docs/plugins.md +0 -832
  306. package/examples/plugins/echo/register.ts +0 -143
  307. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  308. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  309. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  310. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  311. package/src/__tests__/pipeline-runner.test.ts +0 -554
  312. package/src/__tests__/plugin-external-api.test.ts +0 -68
  313. package/src/daemon/wake-target-adapter.ts +0 -253
  314. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  315. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  316. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  317. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  318. package/src/plugins/defaults/compaction/register.ts +0 -35
  319. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  320. package/src/plugins/defaults/empty-response/register.ts +0 -23
  321. package/src/plugins/defaults/history-repair/register.ts +0 -24
  322. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  323. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  324. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  325. package/src/plugins/defaults/title-generate/register.ts +0 -35
  326. package/src/plugins/defaults/tool-error/register.ts +0 -23
  327. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  328. package/src/plugins/external-api.ts +0 -104
  329. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  330. package/src/proactive-artifact/decision.test.ts +0 -226
  331. package/src/proactive-artifact/decision.ts +0 -165
  332. package/src/proactive-artifact/index.ts +0 -7
  333. package/src/proactive-artifact/job.test.ts +0 -962
  334. package/src/proactive-artifact/job.ts +0 -372
  335. package/src/proactive-artifact/message-copy.ts +0 -58
  336. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  337. package/src/proactive-artifact/trigger-state.ts +0 -123
  338. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  339. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  340. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  341. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  342. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +0 -0
  343. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  344. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/provider-blocks.test.ts +0 -0
  345. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  346. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  347. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  348. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  349. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  350. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  351. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  352. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  353. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  354. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  355. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  356. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  357. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
  358. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  359. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  360. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -53,6 +53,11 @@ mock.module("../config/loader.js", () => ({
53
53
  // Real DB — same pattern as conversation-crud-inference-profile.test.ts
54
54
  // ---------------------------------------------------------------------------
55
55
 
56
+ import type { Conversation } from "../daemon/conversation.js";
57
+ import {
58
+ deleteConversation,
59
+ setConversation,
60
+ } from "../daemon/conversation-registry.js";
56
61
  import {
57
62
  createConversation,
58
63
  getConversation,
@@ -78,6 +83,37 @@ function resetDb() {
78
83
  db.run(`DELETE FROM conversations`);
79
84
  }
80
85
 
86
+ /**
87
+ * Minimal live `Conversation` stub exposing just the inference-profile
88
+ * mirror fields and {@link Conversation.applyInferenceProfileState}, so the
89
+ * handler's live-instance sync can be observed without constructing a full
90
+ * conversation.
91
+ */
92
+ function makeLiveConversationStub(conversationId: string): Conversation {
93
+ return {
94
+ conversationId,
95
+ inferenceProfile: null as string | null,
96
+ inferenceProfileSessionId: null as string | null,
97
+ inferenceProfileExpiresAt: null as number | null,
98
+ applyInferenceProfileState(
99
+ this: {
100
+ inferenceProfile: string | null;
101
+ inferenceProfileSessionId: string | null;
102
+ inferenceProfileExpiresAt: number | null;
103
+ },
104
+ state: {
105
+ profile: string | null;
106
+ sessionId: string | null;
107
+ expiresAt: number | null;
108
+ },
109
+ ) {
110
+ this.inferenceProfile = state.profile;
111
+ this.inferenceProfileSessionId = state.sessionId;
112
+ this.inferenceProfileExpiresAt = state.expiresAt;
113
+ },
114
+ } as unknown as Conversation;
115
+ }
116
+
81
117
  // ---------------------------------------------------------------------------
82
118
  // Tests
83
119
  // ---------------------------------------------------------------------------
@@ -175,6 +211,56 @@ describe("setInferenceProfileSession", () => {
175
211
  expect(second.replaced!.expiresAt).toBe(first.expiresAt);
176
212
  });
177
213
 
214
+ test("mirrors the session state onto the live in-memory Conversation on open", async () => {
215
+ // GIVEN a conversation with a live in-memory instance registered for it
216
+ const conv = createConversation("sess-handler-live-sync-open");
217
+ const liveConversation = makeLiveConversationStub(conv.id);
218
+ setConversation(conv.id, liveConversation);
219
+
220
+ // WHEN a session-backed override is opened
221
+ const opened = await setInferenceProfileSession({
222
+ conversationId: conv.id,
223
+ profile: "balanced",
224
+ ttlSeconds: 600,
225
+ });
226
+
227
+ // THEN the live instance mirrors the persisted session state, so the
228
+ // per-turn override derivation reads it without re-fetching the DB row
229
+ expect(liveConversation.inferenceProfile).toBe("balanced");
230
+ expect(liveConversation.inferenceProfileSessionId).toBe(opened.sessionId);
231
+ expect(liveConversation.inferenceProfileExpiresAt).toBe(opened.expiresAt);
232
+
233
+ deleteConversation(conv.id);
234
+ });
235
+
236
+ test("clears the live in-memory Conversation state on clear", async () => {
237
+ // GIVEN a conversation with a live in-memory instance registered for it
238
+ const conv = createConversation("sess-handler-live-sync-clear");
239
+ const liveConversation = makeLiveConversationStub(conv.id);
240
+ setConversation(conv.id, liveConversation);
241
+
242
+ // AND an active session-backed override mirrored onto that instance
243
+ await setInferenceProfileSession({
244
+ conversationId: conv.id,
245
+ profile: "balanced",
246
+ ttlSeconds: 600,
247
+ });
248
+ expect(liveConversation.inferenceProfile).toBe("balanced");
249
+
250
+ // WHEN the override is cleared
251
+ await setInferenceProfileSession({
252
+ conversationId: conv.id,
253
+ profile: null,
254
+ });
255
+
256
+ // THEN the live instance is cleared in lock-step with the DB row
257
+ expect(liveConversation.inferenceProfile).toBeNull();
258
+ expect(liveConversation.inferenceProfileSessionId).toBeNull();
259
+ expect(liveConversation.inferenceProfileExpiresAt).toBeNull();
260
+
261
+ deleteConversation(conv.id);
262
+ });
263
+
178
264
  test("open with profile=null — all three cleared; replaced set if prior existed", async () => {
179
265
  const conv = createConversation("sess-handler-clear");
180
266
 
@@ -23,7 +23,7 @@ mock.module("../config/loader.js", () => ({
23
23
  import {
24
24
  DEFAULT_INJECTOR_ORDER,
25
25
  defaultInjectors,
26
- } from "../plugins/defaults/injectors/register.js";
26
+ } from "../plugins/defaults/memory-retrieval/injectors.js";
27
27
  import type { Injector, TurnContext } from "../plugins/types.js";
28
28
 
29
29
  function findInjector(name: string): Injector {
@@ -59,10 +59,8 @@ describe("background-turn injector", () => {
59
59
  test("returns null when isBackgroundConversation is false", async () => {
60
60
  const result = await backgroundInjector.produce(
61
61
  makeContext({
62
- injectionInputs: {
63
- isBackgroundConversation: false,
64
- isNonInteractive: true,
65
- },
62
+ isBackgroundConversation: false,
63
+ isNonInteractive: true,
66
64
  }),
67
65
  );
68
66
  expect(result).toBeNull();
@@ -70,7 +68,7 @@ describe("background-turn injector", () => {
70
68
 
71
69
  test("returns null when isBackgroundConversation is unset", async () => {
72
70
  const result = await backgroundInjector.produce(
73
- makeContext({ injectionInputs: { isNonInteractive: true } }),
71
+ makeContext({ isNonInteractive: true }),
74
72
  );
75
73
  expect(result).toBeNull();
76
74
  });
@@ -78,10 +76,8 @@ describe("background-turn injector", () => {
78
76
  test("returns null when the guardian is actively connected (interactive turn)", async () => {
79
77
  const result = await backgroundInjector.produce(
80
78
  makeContext({
81
- injectionInputs: {
82
- isBackgroundConversation: true,
83
- isNonInteractive: false,
84
- },
79
+ isBackgroundConversation: true,
80
+ isNonInteractive: false,
85
81
  }),
86
82
  );
87
83
  expect(result).toBeNull();
@@ -89,7 +85,7 @@ describe("background-turn injector", () => {
89
85
 
90
86
  test("returns null when isNonInteractive is unset", async () => {
91
87
  const result = await backgroundInjector.produce(
92
- makeContext({ injectionInputs: { isBackgroundConversation: true } }),
88
+ makeContext({ isBackgroundConversation: true }),
93
89
  );
94
90
  expect(result).toBeNull();
95
91
  });
@@ -97,10 +93,8 @@ describe("background-turn injector", () => {
97
93
  test("wraps configured text in <background_turn> tags when active and non-interactive", async () => {
98
94
  const block = await backgroundInjector.produce(
99
95
  makeContext({
100
- injectionInputs: {
101
- isBackgroundConversation: true,
102
- isNonInteractive: true,
103
- },
96
+ isBackgroundConversation: true,
97
+ isNonInteractive: true,
104
98
  }),
105
99
  );
106
100
 
@@ -119,10 +113,8 @@ describe("background-turn injector", () => {
119
113
 
120
114
  const result = await backgroundInjector.produce(
121
115
  makeContext({
122
- injectionInputs: {
123
- isBackgroundConversation: true,
124
- isNonInteractive: true,
125
- },
116
+ isBackgroundConversation: true,
117
+ isNonInteractive: true,
126
118
  }),
127
119
  );
128
120
  expect(result).toBeNull();
@@ -133,10 +125,8 @@ describe("background-turn injector", () => {
133
125
 
134
126
  const block = await backgroundInjector.produce(
135
127
  makeContext({
136
- injectionInputs: {
137
- isBackgroundConversation: true,
138
- isNonInteractive: true,
139
- },
128
+ isBackgroundConversation: true,
129
+ isNonInteractive: true,
140
130
  }),
141
131
  );
142
132
 
@@ -38,23 +38,66 @@ mock.module("../config/loader.js", () => ({
38
38
  },
39
39
  }));
40
40
 
41
- const { applyRuntimeInjections, composeInjectorChain } =
42
- await import("../daemon/conversation-runtime-assembly.js");
41
+ // `applyRuntimeInjections` computes the `<turn_context>` `current_time` live
42
+ // via `formatTurnTimestamp`; pin it so the rendered block matches the
43
+ // deterministic `buildUnifiedTurnContextBlock` expectations below. The rest of
44
+ // the module (timezone canonicalization/resolution) keeps its real behavior.
45
+ const FIXED_TURN_TIMESTAMP = "2026-04-22";
46
+ const realDateContext = await import("../daemon/date-context.js");
47
+ mock.module("../daemon/date-context.js", () => ({
48
+ ...realDateContext,
49
+ formatTurnTimestamp: () => FIXED_TURN_TIMESTAMP,
50
+ }));
51
+
52
+ const {
53
+ applyRuntimeInjections,
54
+ buildSubagentStatusBlock,
55
+ composeInjectorChain,
56
+ } = await import("../daemon/conversation-runtime-assembly.js");
43
57
  const { DEFAULT_INJECTOR_ORDER, defaultInjectors } =
44
- await import("../plugins/defaults/injectors/register.js");
58
+ await import("../plugins/defaults/memory-retrieval/injectors.js");
45
59
  const { getInjectorChain } =
46
60
  await import("../plugins/defaults/memory-retrieval/injector-chain.js");
61
+ import { eq } from "drizzle-orm";
62
+
63
+ import {
64
+ clearConversations,
65
+ setConversation,
66
+ } from "../daemon/conversation-registry.js";
47
67
  import { buildPkbReminder } from "../daemon/pkb-reminder-builder.js";
68
+ import { getDb } from "../memory/db-connection.js";
69
+ import { initializeDb } from "../memory/db-init.js";
48
70
  import { getPkbRoot } from "../memory/pkb/types.js";
71
+ import { conversations, messages } from "../memory/schema.js";
72
+ import {
73
+ type SlackMessageMetadata,
74
+ writeSlackMetadata,
75
+ } from "../messaging/providers/slack/message-metadata.js";
76
+ import { buildUnifiedTurnContextBlock } from "../plugins/defaults/memory-retrieval/unified-turn-context.js";
49
77
  import type { TurnContext } from "../plugins/types.js";
50
78
  import type { Message } from "../providers/types.js";
79
+ import { getSubagentManager } from "../subagent/index.js";
80
+ import type { SubagentState } from "../subagent/types.js";
51
81
  import { getWorkspacePromptPath } from "../util/platform.js";
52
82
 
83
+ // `applyRuntimeInjections` self-resolves the Slack active-thread focus block
84
+ // from the persisted message rows, so the schema must exist for Slack-channel
85
+ // turns; with no seeded rows the focus loader resolves to null.
86
+ initializeDb();
87
+
88
+ // `makeTurnContext` and the workspace registry seed share this id so the
89
+ // `workspace-context` injector resolves the seeded block for the turn.
90
+ const TEST_CONVERSATION_ID = "conv-test-1";
91
+ // Conversation id the fallback-context test registers its live conversation
92
+ // under; passed explicitly now that `applyRuntimeInjections` requires the
93
+ // caller to name the conversation it resolves through.
94
+ const FALLBACK_CONVERSATION_ID = "runtime-assembly-fallback";
95
+
53
96
  /** A fake TurnContext sufficient for driving `composeInjectorChain`. */
54
97
  function makeTurnContext(): TurnContext {
55
98
  return {
56
99
  requestId: "req-test-1",
57
- conversationId: "conv-test-1",
100
+ conversationId: TEST_CONVERSATION_ID,
58
101
  turnIndex: 0,
59
102
  trust: {
60
103
  sourceChannel: "vellum",
@@ -94,10 +137,158 @@ function clearNowScratchpad(): void {
94
137
  rmSync(getWorkspacePromptPath("NOW.md"), { force: true });
95
138
  }
96
139
 
140
+ // The workspace-context injector sources its block off the live `Conversation`
141
+ // looked up by `conversationId`. Seed a fake instance carrying a non-dirty
142
+ // workspace cache under the id `makeTurnContext()` uses so the injector emits
143
+ // the block; `clearConversations()` between tests keeps suites that assert the
144
+ // workspace block is absent unaffected.
145
+ function seedWorkspaceContext(
146
+ text: string,
147
+ currentTurnTemporalSnapshot?: {
148
+ clientTimezone: string | null;
149
+ },
150
+ interfaceName?: string,
151
+ ): void {
152
+ setConversation(TEST_CONVERSATION_ID, {
153
+ conversationId: TEST_CONVERSATION_ID,
154
+ workingDir: "/sandbox",
155
+ workspaceTopLevelContext: text,
156
+ workspaceTopLevelDirty: false,
157
+ currentTurnTemporalSnapshot,
158
+ currentTurnInterfaceContext: interfaceName
159
+ ? {
160
+ userMessageInterface: interfaceName,
161
+ assistantMessageInterface: interfaceName,
162
+ }
163
+ : undefined,
164
+ } as never);
165
+ }
166
+
167
+ // `applyRuntimeInjections` gates the `<turn_context>` block on the live
168
+ // conversation's frozen `currentTurnTemporalSnapshot` (computing `current_time`
169
+ // live). Tests that drive the chain without a caller-supplied `turnContext`
170
+ // fall back to the runtime-assembly fallback conversation, so seed the snapshot
171
+ // there.
172
+ function seedFallbackTemporalSnapshot(): void {
173
+ setConversation("runtime-assembly-fallback", {
174
+ conversationId: "runtime-assembly-fallback",
175
+ workingDir: "/sandbox",
176
+ workspaceTopLevelContext: "",
177
+ workspaceTopLevelDirty: false,
178
+ currentTurnTemporalSnapshot: { clientTimezone: null },
179
+ } as never);
180
+ }
181
+
182
+ // Persist Slack-channel rows for the turn conversation so
183
+ // `applyRuntimeInjections` self-resolves the chronological transcript from
184
+ // conversation state, exactly as production does (the slack-messages injector
185
+ // reads the live conversation rather than receiving a pre-built transcript).
186
+ const SLACK_CHANNEL_ID = "C0123CHANNEL";
187
+ function seedSlackChannelRows(
188
+ conversationId: string,
189
+ rows: Array<{
190
+ id: string;
191
+ role: "user" | "assistant";
192
+ text: string;
193
+ channelTs: string;
194
+ displayName: string;
195
+ createdAt: number;
196
+ }>,
197
+ ): void {
198
+ const db = getDb();
199
+ const now = Date.now();
200
+ db.insert(conversations)
201
+ .values({ id: conversationId, createdAt: now, updatedAt: now })
202
+ .onConflictDoNothing()
203
+ .run();
204
+ for (const r of rows) {
205
+ const meta: SlackMessageMetadata = {
206
+ source: "slack",
207
+ channelId: SLACK_CHANNEL_ID,
208
+ channelTs: r.channelTs,
209
+ eventKind: "message",
210
+ displayName: r.displayName,
211
+ } as SlackMessageMetadata;
212
+ db.insert(messages)
213
+ .values({
214
+ id: r.id,
215
+ conversationId,
216
+ role: r.role,
217
+ content: JSON.stringify([{ type: "text", text: r.text }]),
218
+ createdAt: r.createdAt,
219
+ metadata: JSON.stringify({
220
+ provenanceTrustClass: "guardian",
221
+ slackMeta: writeSlackMetadata(meta),
222
+ }),
223
+ })
224
+ .run();
225
+ }
226
+ }
227
+
228
+ // `applyRuntimeInjections` self-resolves the `<active_subagents>` block from the
229
+ // global subagent manager keyed by the conversation, so tests seed children
230
+ // directly into the manager's private maps rather than threading a pre-built
231
+ // block through options.
232
+ interface SubagentManagerTestInternals {
233
+ subagents: Map<string, { state: SubagentState }>;
234
+ parentToChildren: Map<string, Set<string>>;
235
+ }
236
+
237
+ function makeSubagentState(
238
+ id: string,
239
+ label: string,
240
+ status: SubagentState["status"],
241
+ ): SubagentState {
242
+ return {
243
+ config: {
244
+ id,
245
+ parentConversationId: TEST_CONVERSATION_ID,
246
+ label,
247
+ objective: "obj",
248
+ },
249
+ status,
250
+ conversationId: `conv-${id}`,
251
+ isFork: false,
252
+ createdAt: Date.now() - 60_000,
253
+ startedAt: Date.now() - 55_000,
254
+ completedAt: status === "completed" ? Date.now() : undefined,
255
+ usage: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
256
+ };
257
+ }
258
+
259
+ function seedSubagentChild(
260
+ parentConversationId: string,
261
+ state: SubagentState,
262
+ ): void {
263
+ const internals =
264
+ getSubagentManager() as unknown as SubagentManagerTestInternals;
265
+ internals.subagents.set(state.config.id, { state });
266
+ const ids =
267
+ internals.parentToChildren.get(parentConversationId) ?? new Set<string>();
268
+ ids.add(state.config.id);
269
+ internals.parentToChildren.set(parentConversationId, ids);
270
+ }
271
+
272
+ function clearSeededSubagents(): void {
273
+ const internals =
274
+ getSubagentManager() as unknown as SubagentManagerTestInternals;
275
+ internals.subagents.clear();
276
+ internals.parentToChildren.clear();
277
+ }
278
+
97
279
  describe("injector chain", () => {
98
280
  beforeEach(() => {
99
281
  clearPkbContent();
100
282
  clearNowScratchpad();
283
+ clearConversations();
284
+ clearSeededSubagents();
285
+ const db = getDb();
286
+ db.delete(messages)
287
+ .where(eq(messages.conversationId, TEST_CONVERSATION_ID))
288
+ .run();
289
+ db.delete(conversations)
290
+ .where(eq(conversations.id, TEST_CONVERSATION_ID))
291
+ .run();
101
292
  });
102
293
 
103
294
  test("defaultInjectors lists the defaults in the documented order", () => {
@@ -188,7 +379,7 @@ describe("injector chain", () => {
188
379
  ];
189
380
 
190
381
  const result = await applyRuntimeInjections(runMessages, {
191
- turnContext: makeTurnContext(),
382
+ ...makeTurnContext(),
192
383
  });
193
384
 
194
385
  expect(result.blocks.injectorChainBlock).toBeUndefined();
@@ -200,22 +391,28 @@ describe("injector chain", () => {
200
391
  test("applyRuntimeInjections without turnContext still runs the chain under a synthesized context", async () => {
201
392
  // The static chain is the canonical injection path, so
202
393
  // `applyRuntimeInjections` must drive it even when the caller doesn't
203
- // pass a `turnContext`. Call sites that rely on option fields to opt
204
- // into injections continue to work because the synthesized fallback
205
- // exposes `injectionInputs` built from `options`.
394
+ // pass a `turnContext`. With no caller-supplied context the assembly
395
+ // falls back to the runtime-assembly fallback conversation id, so the
396
+ // live-sourced unified turn context resolves off that conversation.
206
397
  const runMessages: Message[] = [
207
398
  { role: "user", content: [{ type: "text", text: "hi" }] },
208
399
  ];
209
400
 
401
+ // The fallback conversation has no per-turn or origin interface, so the
402
+ // unified-turn-context injector resolves the interface label to the `web`
403
+ // default.
404
+ const synthesizedBlock = buildUnifiedTurnContextBlock({
405
+ timestamp: FIXED_TURN_TIMESTAMP,
406
+ interfaceName: "web",
407
+ });
408
+ seedFallbackTemporalSnapshot();
210
409
  const result = await applyRuntimeInjections(runMessages, {
211
- unifiedTurnContext: "<turn_context>\nsynthesized\n</turn_context>",
410
+ conversationId: FALLBACK_CONVERSATION_ID,
212
411
  });
213
412
 
214
413
  // The unified-turn-context injector fires even without a caller-supplied
215
414
  // turnContext, proving the chain runs under the synthesized context.
216
- expect(result.blocks.unifiedTurnContext).toBe(
217
- "<turn_context>\nsynthesized\n</turn_context>",
218
- );
415
+ expect(result.blocks.unifiedTurnContext).toBe(synthesizedBlock);
219
416
  });
220
417
 
221
418
  test("golden-path: default chain injects workspace + unified-turn + PKB + NOW + subagent in the correct positions", async () => {
@@ -248,16 +445,21 @@ describe("injector chain", () => {
248
445
 
249
446
  const workspaceText =
250
447
  "<workspace>\nRoot: /sandbox\nDirectories: src, lib\n</workspace>";
251
- const unifiedTurn =
252
- "<turn_context>\ncurrent_time: 2026-04-22\ninterface: macos\n</turn_context>";
253
- const subagentBlock =
254
- '<active_subagents>\n- [running] "worker" (sub-1) | elapsed: 5s\n</active_subagents>';
448
+ // The interface label flows through the options bag; the timestamp is
449
+ // computed live at injection time.
450
+ const unifiedTurn = buildUnifiedTurnContextBlock({
451
+ timestamp: FIXED_TURN_TIMESTAMP,
452
+ interfaceName: "macos",
453
+ });
454
+ // A completed child renders deterministically (no elapsed clock), so the
455
+ // block the injector self-resolves matches `buildSubagentStatusBlock`.
456
+ const subagentChild = makeSubagentState("sub-1", "worker", "completed");
457
+ seedSubagentChild(TEST_CONVERSATION_ID, subagentChild);
458
+ const subagentBlock = buildSubagentStatusBlock([subagentChild])!;
255
459
 
460
+ seedWorkspaceContext(workspaceText, { clientTimezone: null }, "macos");
256
461
  const result = await applyRuntimeInjections(runMessages, {
257
- turnContext: makeTurnContext(),
258
- workspaceTopLevelContext: workspaceText,
259
- unifiedTurnContext: unifiedTurn,
260
- subagentStatusBlock: subagentBlock,
462
+ ...makeTurnContext(),
261
463
  });
262
464
 
263
465
  // Extract the tail user message content as a list of text strings.
@@ -295,13 +497,12 @@ describe("injector chain", () => {
295
497
  );
296
498
  });
297
499
 
298
- test("slack-messages injector replaces runMessages when a chronological transcript is provided", async () => {
299
- // End-to-end verification for the `replace-run-messages` placement:
300
- // a Slack channel turn with a pre-rendered chronological transcript
301
- // swaps the incoming `runMessages` for the transcript before the
302
- // after-memory/append placements run. Memory-prefix blocks from the
303
- // original tail are re-prepended onto the new tail so PKB / NOW
304
- // splices still find them.
500
+ test("slack-messages injector replaces runMessages with the self-resolved transcript", async () => {
501
+ // End-to-end verification for the `replace-run-messages` placement: a
502
+ // Slack channel turn swaps the incoming `runMessages` for the
503
+ // self-resolved chronological transcript before the after-memory/append
504
+ // placements run. Memory-prefix blocks from the original tail are
505
+ // re-prepended onto the new tail so PKB / NOW splices still find them.
305
506
  const originalRun: Message[] = [
306
507
  {
307
508
  role: "user",
@@ -316,19 +517,30 @@ describe("injector chain", () => {
316
517
  ],
317
518
  },
318
519
  ];
319
- const slackTranscript: Message[] = [
520
+ seedSlackChannelRows(TEST_CONVERSATION_ID, [
320
521
  {
522
+ id: "s1",
321
523
  role: "user",
322
- content: [{ type: "text", text: "[12:00 alice]: kickoff" }],
524
+ text: "kickoff",
525
+ channelTs: "1700000000.000001",
526
+ displayName: "alice",
527
+ createdAt: 1700000000_000,
323
528
  },
324
529
  {
530
+ id: "s2",
325
531
  role: "user",
326
- content: [{ type: "text", text: "[12:05 @user]: What's happening?" }],
532
+ text: "What's happening?",
533
+ channelTs: "1700000005.000001",
534
+ displayName: "user",
535
+ createdAt: 1700000005_000,
327
536
  },
328
- ];
329
-
330
- const result = await applyRuntimeInjections(originalRun, {
331
- turnContext: makeTurnContext(),
537
+ ]);
538
+ setConversation(TEST_CONVERSATION_ID, {
539
+ conversationId: TEST_CONVERSATION_ID,
540
+ workingDir: "/sandbox",
541
+ workspaceTopLevelContext: "",
542
+ workspaceTopLevelDirty: false,
543
+ trustContext: { trustClass: "guardian" },
332
544
  channelCapabilities: {
333
545
  channel: "slack",
334
546
  dashboardCapable: false,
@@ -336,7 +548,9 @@ describe("injector chain", () => {
336
548
  supportsVoiceInput: false,
337
549
  chatType: "channel",
338
550
  },
339
- slackChronologicalMessages: slackTranscript,
551
+ } as never);
552
+ const result = await applyRuntimeInjections(originalRun, {
553
+ ...makeTurnContext(),
340
554
  });
341
555
 
342
556
  // The swap replaced the run-messages wholesale but preserved the
@@ -354,7 +568,7 @@ describe("injector chain", () => {
354
568
  true,
355
569
  );
356
570
  expect(texts).toContain("<memory __injected>\nrecalled fact\n</memory>");
357
- expect(texts[texts.length - 1]).toBe("[12:05 @user]: What's happening?");
571
+ expect(texts[texts.length - 1]).toContain("What's happening?");
358
572
  });
359
573
 
360
574
  test("minimal mode: only unified-turn-context survives; workspace/PKB/NOW/subagent are skipped", async () => {
@@ -363,6 +577,21 @@ describe("injector chain", () => {
363
577
  // opts out in minimal mode, so the tail should carry only the turn
364
578
  // context prepend plus any non-injector hardcoded content (none
365
579
  // here).
580
+ // Empty workspace text keeps that injector inert while the unified
581
+ // turn-context timestamp flows through the conversation's frozen temporal
582
+ // snapshot. The interface label is sourced from the live conversation,
583
+ // which has no per-turn or origin interface here and so resolves to the
584
+ // `web` default. A live child subagent is seeded so the subagent-status
585
+ // injector has a block to skip.
586
+ const minimalTurnBlock = buildUnifiedTurnContextBlock({
587
+ timestamp: FIXED_TURN_TIMESTAMP,
588
+ interfaceName: "web",
589
+ });
590
+ seedWorkspaceContext("", { clientTimezone: null });
591
+ seedSubagentChild(
592
+ TEST_CONVERSATION_ID,
593
+ makeSubagentState("sub-1", "worker", "running"),
594
+ );
366
595
  const result = await applyRuntimeInjections(
367
596
  [
368
597
  {
@@ -371,11 +600,8 @@ describe("injector chain", () => {
371
600
  },
372
601
  ],
373
602
  {
374
- turnContext: makeTurnContext(),
603
+ ...makeTurnContext(),
375
604
  mode: "minimal",
376
- workspaceTopLevelContext: "<workspace>...</workspace>",
377
- unifiedTurnContext: "<turn_context>...</turn_context>",
378
- subagentStatusBlock: "<active_subagents>...</active_subagents>",
379
605
  },
380
606
  );
381
607
 
@@ -384,10 +610,8 @@ describe("injector chain", () => {
384
610
  .filter((b): b is { type: "text"; text: string } => b.type === "text")
385
611
  .map((b) => b.text);
386
612
 
387
- expect(texts).toEqual(["<turn_context>...</turn_context>", "hi"]);
388
- expect(result.blocks.unifiedTurnContext).toBe(
389
- "<turn_context>...</turn_context>",
390
- );
613
+ expect(texts).toEqual([minimalTurnBlock, "hi"]);
614
+ expect(result.blocks.unifiedTurnContext).toBe(minimalTurnBlock);
391
615
  expect(result.blocks.workspaceBlock).toBeUndefined();
392
616
  expect(result.blocks.pkbContextBlock).toBeUndefined();
393
617
  expect(result.blocks.nowScratchpadBlock).toBeUndefined();