@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
@@ -1,4 +1,6 @@
1
- import { beforeEach, describe, expect, mock, test } from "bun:test";
1
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
4
 
3
5
  // This test exercises v1 PKB injection. `config.memory.v2.enabled` (default
4
6
  // `true`) makes the PKB injector go silent — force it off here so the v1
@@ -33,6 +35,10 @@ mock.module("../memory/pkb/pkb-search.js", () => ({
33
35
  },
34
36
  }));
35
37
 
38
+ import {
39
+ clearConversations,
40
+ setConversation,
41
+ } from "../daemon/conversation-registry.js";
36
42
  import type {
37
43
  ChannelCapabilities,
38
44
  SlackTranscriptInputRow,
@@ -44,7 +50,6 @@ import {
44
50
  assembleSlackChronologicalMessages,
45
51
  buildSubagentStatusBlock,
46
52
  buildUnifiedTurnContextBlock,
47
- findLastInjectedNowContent,
48
53
  getSlackCompactionWatermarkForPrefix,
49
54
  injectChannelCapabilityContext,
50
55
  injectChannelCommandContext,
@@ -58,31 +63,106 @@ import {
58
63
  stripInjectionsForCompaction,
59
64
  stripNowScratchpad,
60
65
  } from "../daemon/conversation-runtime-assembly.js";
66
+ import type { SurfaceData, SurfaceType } from "../daemon/message-protocol.js";
61
67
  import { buildPkbReminder } from "../daemon/pkb-reminder-builder.js";
62
68
  import type { MessageRow } from "../memory/conversation-crud.js";
69
+ import { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
70
+ import { getPkbRoot } from "../memory/pkb/types.js";
63
71
  import {
64
72
  type SlackMessageMetadata,
65
73
  writeSlackMetadata,
66
74
  } from "../messaging/providers/slack/message-metadata.js";
67
75
  import { parentAlias } from "../messaging/providers/slack/render-transcript.js";
68
- import { defaultInjectorsPlugin } from "../plugins/defaults/injectors/register.js";
69
- import {
70
- registerPlugin,
71
- resetPluginRegistryForTests,
72
- } from "../plugins/registry.js";
73
76
  import type { Message } from "../providers/types.js";
74
77
  import { wrapUntrustedContent } from "../security/untrusted-content.js";
75
78
  import type { SubagentState } from "../subagent/types.js";
79
+ import { getWorkspacePromptPath } from "../util/platform.js";
80
+
81
+ // The pkb-reminder injector derives PKB-active state from the workspace itself
82
+ // — `readPkbContext()` returning content behind the personal-memory trust gate
83
+ // — rather than from a threaded flag. Tests that need the reminder to fire seed
84
+ // a default auto-injected PKB file under the workspace pkb root; tests that
85
+ // need it suppressed leave the pkb dir empty.
86
+ function seedPkbContent(): void {
87
+ const root = getPkbRoot();
88
+ mkdirSync(root, { recursive: true });
89
+ writeFileSync(join(root, "INDEX.md"), "workspace knowledge index", "utf-8");
90
+ }
76
91
 
77
- // `applyRuntimeInjections` is now driven by the default injector chain
78
- // (PR G2.1). The default-injectors plugin must be registered for the chain
79
- // to emit workspace, PKB, NOW.md, subagent, Slack, and thread-focus blocks.
80
- // Each test gets a clean registry so a test that registers its own plugin
81
- // doesn't leak into the next one.
82
- beforeEach(() => {
83
- resetPluginRegistryForTests();
84
- registerPlugin(defaultInjectorsPlugin);
85
- });
92
+ function clearPkbContent(): void {
93
+ rmSync(getPkbRoot(), { recursive: true, force: true });
94
+ }
95
+
96
+ // The now-md injector derives NOW.md state from the workspace itself —
97
+ // `readNowScratchpad()` returning content behind the personal-memory trust gate
98
+ // and the `scratchpadInjection` config toggle — rather than from a threaded
99
+ // option. Seed the file so the injector fires; clear it so suites that assert
100
+ // NOW.md is absent stay unaffected.
101
+ function seedNowScratchpad(content: string): void {
102
+ const nowPath = getWorkspacePromptPath("NOW.md");
103
+ mkdirSync(dirname(nowPath), { recursive: true });
104
+ writeFileSync(nowPath, content, "utf-8");
105
+ }
106
+
107
+ function clearNowScratchpad(): void {
108
+ rmSync(getWorkspacePromptPath("NOW.md"), { force: true });
109
+ }
110
+
111
+ // The workspace-context injector sources its block off the live `Conversation`
112
+ // looked up by `conversationId`. Register a fake instance carrying both a
113
+ // non-dirty workspace context (non-null content, not dirty) so
114
+ // `resolveWorkspaceTopLevelContext` returns it verbatim without rescanning the
115
+ // filesystem, and an active dynamic-page surface, so `applyRuntimeInjections`
116
+ // resolves the `<workspace>` and `<active_workspace>` blocks from it;
117
+ // `clearConversations()` between tests keeps suites that assert the blocks are
118
+ // absent unaffected.
119
+ function seedActiveSurfaceConversation(
120
+ conversationId: string,
121
+ workspaceText: string,
122
+ surfaceId: string,
123
+ data: SurfaceData,
124
+ channelCapabilities?: ChannelCapabilities,
125
+ commandIntent?: { type: string; payload?: string; languageCode?: string },
126
+ ): void {
127
+ setConversation(conversationId, {
128
+ conversationId,
129
+ workingDir: "/sandbox",
130
+ workspaceTopLevelContext: workspaceText,
131
+ workspaceTopLevelDirty: false,
132
+ currentActiveSurfaceId: surfaceId,
133
+ surfaceState: new Map<
134
+ string,
135
+ { surfaceType: SurfaceType; data: SurfaceData }
136
+ >([[surfaceId, { surfaceType: "dynamic_page", data }]]),
137
+ channelCapabilities: channelCapabilities ?? undefined,
138
+ commandIntent,
139
+ } as never);
140
+ }
141
+
142
+ function clearWorkspaceContext(): void {
143
+ clearConversations();
144
+ }
145
+
146
+ // The `<channel_capabilities>` branch and the Slack gates source the channel
147
+ // capabilities off the live `Conversation` looked up by `conversationId`.
148
+ // Register a fake fallback instance carrying the given capabilities (and a
149
+ // non-dirty empty workspace + empty surface so the other live-sourced branches
150
+ // stay inert) so `applyRuntimeInjections` resolves them; `clearConversations()`
151
+ // between tests keeps suites asserting absence unaffected.
152
+ function seedChannelCapabilitiesConversation(
153
+ caps: ChannelCapabilities | null,
154
+ transportHints?: string[],
155
+ ): void {
156
+ setConversation("runtime-assembly-fallback", {
157
+ conversationId: "runtime-assembly-fallback",
158
+ workingDir: "/sandbox",
159
+ workspaceTopLevelContext: "",
160
+ workspaceTopLevelDirty: false,
161
+ surfaceState: new Map(),
162
+ channelCapabilities: caps ?? undefined,
163
+ transportHints,
164
+ } as never);
165
+ }
86
166
 
87
167
  // ---------------------------------------------------------------------------
88
168
  // resolveChannelCapabilities
@@ -556,6 +636,8 @@ describe("stripChannelCapabilityContext", () => {
556
636
  // ---------------------------------------------------------------------------
557
637
 
558
638
  describe("applyRuntimeInjections with channelCapabilities", () => {
639
+ afterEach(clearConversations);
640
+
559
641
  const baseMessages: Message[] = [
560
642
  {
561
643
  role: "user",
@@ -571,9 +653,8 @@ describe("applyRuntimeInjections with channelCapabilities", () => {
571
653
  supportsVoiceInput: false,
572
654
  };
573
655
 
574
- const { messages: result } = await applyRuntimeInjections(baseMessages, {
575
- channelCapabilities: caps,
576
- });
656
+ seedChannelCapabilitiesConversation(caps);
657
+ const { messages: result } = await applyRuntimeInjections(baseMessages, {});
577
658
 
578
659
  expect(result.length).toBe(1);
579
660
  expect(result[0].content.length).toBe(2);
@@ -584,9 +665,8 @@ describe("applyRuntimeInjections with channelCapabilities", () => {
584
665
  });
585
666
 
586
667
  test("does not inject when channelCapabilities is null", async () => {
587
- const { messages: result } = await applyRuntimeInjections(baseMessages, {
588
- channelCapabilities: null,
589
- });
668
+ seedChannelCapabilitiesConversation(null);
669
+ const { messages: result } = await applyRuntimeInjections(baseMessages, {});
590
670
 
591
671
  expect(result.length).toBe(1);
592
672
  expect(result[0].content.length).toBe(1);
@@ -607,9 +687,8 @@ describe("applyRuntimeInjections with channelCapabilities", () => {
607
687
  supportsVoiceInput: false,
608
688
  };
609
689
 
610
- const { messages: result } = await applyRuntimeInjections(baseMessages, {
611
- channelCapabilities: caps,
612
- });
690
+ seedChannelCapabilitiesConversation(caps);
691
+ const { messages: result } = await applyRuntimeInjections(baseMessages, {});
613
692
 
614
693
  expect(result.length).toBe(1);
615
694
  // channelCapabilities prepends
@@ -736,24 +815,52 @@ describe("applyRuntimeInjections — injection mode", () => {
736
815
  },
737
816
  ];
738
817
 
818
+ const channelCapabilities: ChannelCapabilities = {
819
+ channel: "telegram",
820
+ dashboardCapable: false,
821
+ supportsDynamicUi: false,
822
+ supportsVoiceInput: false,
823
+ };
824
+
739
825
  const fullOptions = {
740
- workspaceTopLevelContext: "<workspace>\nRoot: /sandbox\n</workspace>",
741
- channelCommandContext: { type: "start" } as const,
742
- activeSurface: { surfaceId: "sf_1", html: "<div>test</div>" },
743
- channelCapabilities: {
744
- channel: "telegram",
745
- dashboardCapable: false,
746
- supportsDynamicUi: false,
747
- supportsVoiceInput: false,
748
- } as ChannelCapabilities,
749
826
  unifiedTurnContext:
750
827
  "<turn_context>\ncurrent_time: 2026-03-04 (Tuesday) 12:00:00 +00:00 (UTC)\ninterface: telegram\n</turn_context>",
751
- nowScratchpad: "Current focus: shipping PR 3",
752
- pkbContext: "essentials content here",
753
- pkbActive: true,
754
828
  isNonInteractive: true,
829
+ // Guardian trust so the personal-memory gate admits the actor regardless
830
+ // of the telegram channel capabilities under test, letting the reminder
831
+ // gate hinge purely on PKB content presence.
832
+ turnContext: {
833
+ requestId: "injection-mode-req",
834
+ conversationId: "injection-mode-conv",
835
+ turnIndex: 0,
836
+ trust: {
837
+ sourceChannel: "vellum" as const,
838
+ trustClass: "guardian" as const,
839
+ },
840
+ },
755
841
  };
756
842
 
843
+ // The reminder fires only when the workspace has PKB content, and the now-md
844
+ // injector only when NOW.md has content; seed both so the full-mode cases
845
+ // exercise the active branch.
846
+ beforeEach(() => {
847
+ seedPkbContent();
848
+ seedNowScratchpad("Current focus: shipping PR 3");
849
+ seedActiveSurfaceConversation(
850
+ "injection-mode-conv",
851
+ "<workspace>\nRoot: /sandbox\n</workspace>",
852
+ "sf_1",
853
+ { html: "<div>test</div>" },
854
+ channelCapabilities,
855
+ { type: "start" },
856
+ );
857
+ });
858
+ afterEach(() => {
859
+ clearPkbContent();
860
+ clearNowScratchpad();
861
+ clearWorkspaceContext();
862
+ });
863
+
757
864
  test("full mode (default) includes all injections", async () => {
758
865
  const { messages: result } = await applyRuntimeInjections(
759
866
  baseMessages,
@@ -860,12 +967,10 @@ describe("applyRuntimeInjections — injection mode", () => {
860
967
  });
861
968
  });
862
969
 
863
- // The standalone `injectNowScratchpad` helper was removed in G2.1. The
864
- // now-md default injector (registered by `defaultInjectorsPlugin`) emits
865
- // the `<NOW.md>` block as an `after-memory-prefix` placement during
866
- // `applyRuntimeInjections`. The suites below (`applyRuntimeInjections with
867
- // nowScratchpad` and the injection-mode tests) cover that behaviour
868
- // end-to-end.
970
+ // The now-md default injector emits the `<NOW.md>` block as an
971
+ // `after-memory-prefix` placement during `applyRuntimeInjections`. The suites
972
+ // below (`applyRuntimeInjections with nowScratchpad` and the injection-mode
973
+ // tests) cover that behaviour end-to-end.
869
974
 
870
975
  // ---------------------------------------------------------------------------
871
976
  // stripNowScratchpad
@@ -1013,7 +1118,7 @@ describe("stripInjectionsForCompaction preserves persistent blocks", () => {
1013
1118
  ).toContain("<turn_context>");
1014
1119
  });
1015
1120
 
1016
- test("<workspace> blocks are NOT stripped", () => {
1121
+ test("<workspace> blocks ARE stripped so the injector re-sources them post-compaction", () => {
1017
1122
  const messages: Message[] = [
1018
1123
  {
1019
1124
  role: "user",
@@ -1029,10 +1134,10 @@ describe("stripInjectionsForCompaction preserves persistent blocks", () => {
1029
1134
 
1030
1135
  const result = stripInjectionsForCompaction(messages);
1031
1136
  expect(result.length).toBe(1);
1032
- expect(result[0].content.length).toBe(2);
1033
- expect(
1034
- (result[0].content[0] as { type: "text"; text: string }).text,
1035
- ).toContain("<workspace>");
1137
+ expect(result[0].content.length).toBe(1);
1138
+ expect((result[0].content[0] as { type: "text"; text: string }).text).toBe(
1139
+ "Hello",
1140
+ );
1036
1141
  });
1037
1142
 
1038
1143
  test("legacy <workspace_top_level> blocks ARE stripped for backward compat", () => {
@@ -1192,10 +1297,14 @@ describe("applyRuntimeInjections with nowScratchpad", () => {
1192
1297
  },
1193
1298
  ];
1194
1299
 
1195
- test("injects NOW.md block when provided", async () => {
1196
- const { messages: result } = await applyRuntimeInjections(baseMessages, {
1197
- nowScratchpad: "Current focus: fix the bug",
1198
- });
1300
+ // The now-md injector sources NOW.md from the workspace itself rather than
1301
+ // from a threaded option, so seed the file to drive injection and clear it so
1302
+ // the absent-content cases see no block.
1303
+ afterEach(clearNowScratchpad);
1304
+
1305
+ test("injects NOW.md block when the file has content", async () => {
1306
+ seedNowScratchpad("Current focus: fix the bug");
1307
+ const { messages: result } = await applyRuntimeInjections(baseMessages, {});
1199
1308
 
1200
1309
  expect(result.length).toBe(1);
1201
1310
  expect(result[0].content.length).toBe(2);
@@ -1206,9 +1315,8 @@ describe("applyRuntimeInjections with nowScratchpad", () => {
1206
1315
  });
1207
1316
 
1208
1317
  test("scratchpad appears before user's original text content", async () => {
1209
- const { messages: result } = await applyRuntimeInjections(baseMessages, {
1210
- nowScratchpad: "scratchpad notes",
1211
- });
1318
+ seedNowScratchpad("scratchpad notes");
1319
+ const { messages: result } = await applyRuntimeInjections(baseMessages, {});
1212
1320
 
1213
1321
  // Scratchpad comes first (before user content)
1214
1322
  expect(
@@ -1220,16 +1328,7 @@ describe("applyRuntimeInjections with nowScratchpad", () => {
1220
1328
  );
1221
1329
  });
1222
1330
 
1223
- test("does not inject when nowScratchpad is null", async () => {
1224
- const { messages: result } = await applyRuntimeInjections(baseMessages, {
1225
- nowScratchpad: null,
1226
- });
1227
-
1228
- expect(result.length).toBe(1);
1229
- expect(result[0].content.length).toBe(1);
1230
- });
1231
-
1232
- test("does not inject when nowScratchpad is omitted", async () => {
1331
+ test("does not inject when the NOW.md file is absent", async () => {
1233
1332
  const { messages: result } = await applyRuntimeInjections(baseMessages, {});
1234
1333
 
1235
1334
  expect(result.length).toBe(1);
@@ -1237,8 +1336,8 @@ describe("applyRuntimeInjections with nowScratchpad", () => {
1237
1336
  });
1238
1337
 
1239
1338
  test("skipped in minimal mode", async () => {
1339
+ seedNowScratchpad("Current focus: fix the bug");
1240
1340
  const { messages: result } = await applyRuntimeInjections(baseMessages, {
1241
- nowScratchpad: "Current focus: fix the bug",
1242
1341
  mode: "minimal",
1243
1342
  });
1244
1343
 
@@ -1782,85 +1881,6 @@ describe("applyRuntimeInjections blocks.unifiedTurnContext", () => {
1782
1881
  });
1783
1882
  });
1784
1883
 
1785
- // ---------------------------------------------------------------------------
1786
- // findLastInjectedNowContent
1787
- // ---------------------------------------------------------------------------
1788
-
1789
- describe("findLastInjectedNowContent", () => {
1790
- test("extracts NOW.md content from the last user message", () => {
1791
- const messages: Message[] = [
1792
- {
1793
- role: "user",
1794
- content: [
1795
- {
1796
- type: "text",
1797
- text: "<NOW.md Always keep this up to date>\nCurrent focus: fix the bug\n</NOW.md>",
1798
- },
1799
- { type: "text", text: "Hello" },
1800
- ],
1801
- },
1802
- ];
1803
-
1804
- expect(findLastInjectedNowContent(messages)).toBe(
1805
- "Current focus: fix the bug",
1806
- );
1807
- });
1808
-
1809
- test("returns null when no NOW.md injection exists", () => {
1810
- const messages: Message[] = [
1811
- {
1812
- role: "user",
1813
- content: [{ type: "text", text: "Hello" }],
1814
- },
1815
- ];
1816
-
1817
- expect(findLastInjectedNowContent(messages)).toBeNull();
1818
- });
1819
-
1820
- test("returns the most recent injection when multiple exist", () => {
1821
- const messages: Message[] = [
1822
- {
1823
- role: "user",
1824
- content: [
1825
- {
1826
- type: "text",
1827
- text: "<NOW.md Always keep this up to date>\nOld focus\n</NOW.md>",
1828
- },
1829
- ],
1830
- },
1831
- { role: "assistant", content: [{ type: "text", text: "OK" }] },
1832
- {
1833
- role: "user",
1834
- content: [
1835
- {
1836
- type: "text",
1837
- text: "<NOW.md Always keep this up to date>\nNew focus\n</NOW.md>",
1838
- },
1839
- ],
1840
- },
1841
- ];
1842
-
1843
- expect(findLastInjectedNowContent(messages)).toBe("New focus");
1844
- });
1845
-
1846
- test("skips assistant messages", () => {
1847
- const messages: Message[] = [
1848
- {
1849
- role: "user",
1850
- content: [
1851
- {
1852
- type: "text",
1853
- text: "<NOW.md Always keep this up to date>\nUser focus\n</NOW.md>",
1854
- },
1855
- ],
1856
- },
1857
- { role: "assistant", content: [{ type: "text", text: "response" }] },
1858
- ];
1859
-
1860
- expect(findLastInjectedNowContent(messages)).toBe("User focus");
1861
- });
1862
- });
1863
-
1864
1884
  // ---------------------------------------------------------------------------
1865
1885
  // Subagent status injection
1866
1886
  // ---------------------------------------------------------------------------
@@ -2032,21 +2052,33 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
2032
2052
 
2033
2053
  const FLAT_REMINDER = buildPkbReminder([]);
2034
2054
 
2035
- // Use a platform-agnostic absolute workspace root so the tests work on
2036
- // macOS and Linux runners alike. `pkbRoot` sits under `pkbWorkingDir` to
2037
- // mirror production, where `pkbRoot = join(workingDir, "pkb")`.
2038
- const pkbWorkingDir = "/tmp/fake-workspace";
2039
- const pkbRoot = `${pkbWorkingDir}/pkb`;
2055
+ // The pkb-reminder injector sources the PKB root itself via `getPkbRoot()`,
2056
+ // so the in-context file paths these tests build must resolve against the
2057
+ // same per-test workspace the injector sees.
2058
+ const pkbRoot = getPkbRoot();
2059
+
2060
+ // The pkb-reminder injector reads the dense/sparse PKB query pair off the
2061
+ // conversation's live graph handle (the memory-retrieval hook records it
2062
+ // there during retrieval), not from the injection options. Register a handle
2063
+ // for the fallback conversation id `applyRuntimeInjections` synthesizes and
2064
+ // seed it with a query vector so the hint-search branch runs.
2065
+ let graphHandle: ConversationGraphMemory;
2066
+ beforeEach(() => {
2067
+ graphHandle = new ConversationGraphMemory("runtime-assembly-fallback");
2068
+ graphHandle.recordPkbQueryVectors([0.1, 0.2, 0.3], undefined);
2069
+ });
2070
+ afterEach(() => {
2071
+ graphHandle.dispose();
2072
+ });
2073
+
2074
+ // PKB content makes `readPkbContext()` non-null so the (vellum/guardian-
2075
+ // equivalent) fallback trust gate admits the reminder; cleared after each
2076
+ // test to avoid leaking into suites that assert the reminder is absent.
2077
+ beforeEach(seedPkbContent);
2078
+ afterEach(clearPkbContent);
2040
2079
 
2041
2080
  function makePkbOptions(overrides: Record<string, unknown> = {}) {
2042
2081
  return {
2043
- pkbActive: true,
2044
- pkbQueryVector: [0.1, 0.2, 0.3],
2045
- pkbScopeId: "scope-1",
2046
- pkbConversation: { messages: baseMessages },
2047
- pkbRoot,
2048
- pkbWorkingDir,
2049
- pkbAutoInjectList: [],
2050
2082
  ...overrides,
2051
2083
  };
2052
2084
  }
@@ -2082,10 +2114,11 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
2082
2114
  test("default auto-injected files (from PKB_DEFAULT_FILES) are filtered out of hints", async () => {
2083
2115
  // Regression test: when `_autoinject.md` is missing, `readPkbContext`
2084
2116
  // falls back to PKB_DEFAULT_FILES — so those files ARE in the prompt.
2085
- // The tracker must know about them too, otherwise the reminder would
2086
- // redundantly recommend e.g. `essentials.md` even though its contents
2087
- // are already injected. The agent-loop passes the effective auto-inject
2088
- // list (via `getPkbAutoInjectList`) to `applyRuntimeInjections`.
2117
+ // The injector sources the same fallback via `getPkbAutoInjectList`, so
2118
+ // it must know about them too, otherwise the reminder would redundantly
2119
+ // recommend e.g. `essentials.md` even though its contents are already
2120
+ // injected. The per-test workspace has no `_autoinject.md`, so the
2121
+ // injector resolves PKB_DEFAULT_FILES here.
2089
2122
  pkbSearchResults = [
2090
2123
  { path: "essentials.md", denseScore: 0.95 },
2091
2124
  { path: "topics/alpha.md", denseScore: 0.9 },
@@ -2094,16 +2127,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
2094
2127
 
2095
2128
  const { messages: result } = await applyRuntimeInjections(
2096
2129
  baseMessages,
2097
- makePkbOptions({
2098
- // Simulate the fallback the agent-loop now threads through:
2099
- // `_autoinject.md` is missing, so defaults are injected.
2100
- pkbAutoInjectList: [
2101
- "INDEX.md",
2102
- "essentials.md",
2103
- "threads.md",
2104
- "buffer.md",
2105
- ],
2106
- }),
2130
+ makePkbOptions(),
2107
2131
  );
2108
2132
  const texts = extractTexts(result);
2109
2133
  const reminder = texts.find((t) => t.startsWith("<system_reminder>"));
@@ -2143,27 +2167,26 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
2143
2167
  ];
2144
2168
  pkbSearchThrows = null;
2145
2169
 
2146
- // Build a conversation that has already read topics/beta.md via file_read.
2147
- const conversationWithRead: { messages: Message[] } = {
2148
- messages: [
2149
- ...baseMessages,
2150
- {
2151
- role: "assistant",
2152
- content: [
2153
- {
2154
- type: "tool_use",
2155
- id: "tu_1",
2156
- name: "file_read",
2157
- input: { path: `${pkbRoot}/topics/beta.md` },
2158
- },
2159
- ],
2160
- },
2161
- ],
2162
- };
2170
+ // Working messages where topics/beta.md was already read via file_read on
2171
+ // an earlier turn, followed by the current user prompt as the tail.
2172
+ const conversationWithRead: Message[] = [
2173
+ {
2174
+ role: "assistant",
2175
+ content: [
2176
+ {
2177
+ type: "tool_use",
2178
+ id: "tu_1",
2179
+ name: "file_read",
2180
+ input: { path: `${pkbRoot}/topics/beta.md` },
2181
+ },
2182
+ ],
2183
+ },
2184
+ ...baseMessages,
2185
+ ];
2163
2186
 
2164
2187
  const { messages: result } = await applyRuntimeInjections(
2165
- baseMessages,
2166
- makePkbOptions({ pkbConversation: conversationWithRead }),
2188
+ conversationWithRead,
2189
+ makePkbOptions(),
2167
2190
  );
2168
2191
  const texts = extractTexts(result);
2169
2192
  const reminder = texts.find((t) => t.startsWith("<system_reminder>"));
@@ -2201,10 +2224,13 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
2201
2224
 
2202
2225
  test("missing query vector → flat fallback, search is not attempted", async () => {
2203
2226
  pkbSearchThrows = new Error("should not be called");
2227
+ // No dense vector was recorded on the graph handle this turn, so the
2228
+ // injector must skip the hint search entirely.
2229
+ graphHandle.recordPkbQueryVectors(undefined, undefined);
2204
2230
 
2205
2231
  const { messages: result } = await applyRuntimeInjections(
2206
2232
  baseMessages,
2207
- makePkbOptions({ pkbQueryVector: undefined }),
2233
+ makePkbOptions(),
2208
2234
  );
2209
2235
  const texts = extractTexts(result);
2210
2236
  const reminder = texts.find((t) => t.startsWith("<system_reminder>"));
@@ -2318,37 +2344,28 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
2318
2344
  ];
2319
2345
  pkbSearchThrows = null;
2320
2346
 
2321
- // Pre-compaction conversation: beta was already read.
2322
- const preCompactionConversation: { messages: Message[] } = {
2323
- messages: [
2324
- ...baseMessages,
2325
- {
2326
- role: "assistant",
2327
- content: [
2328
- {
2329
- type: "tool_use",
2330
- id: "tu_pre",
2331
- name: "file_read",
2332
- input: { path: `${pkbRoot}/topics/beta.md` },
2333
- },
2334
- ],
2335
- },
2336
- ],
2337
- };
2347
+ // Pre-compaction working messages: beta was already read on an earlier
2348
+ // turn, followed by the current user prompt as the tail.
2349
+ const preCompactionMessages: Message[] = [
2350
+ {
2351
+ role: "assistant",
2352
+ content: [
2353
+ {
2354
+ type: "tool_use",
2355
+ id: "tu_pre",
2356
+ name: "file_read",
2357
+ input: { path: `${pkbRoot}/topics/beta.md` },
2358
+ },
2359
+ ],
2360
+ },
2361
+ ...baseMessages,
2362
+ ];
2338
2363
 
2339
2364
  // 1. Initial injection sees the pre-compaction state — beta should be
2340
2365
  // filtered out.
2341
2366
  const { messages: initialResult } = await applyRuntimeInjections(
2342
- baseMessages,
2343
- {
2344
- pkbActive: true,
2345
- pkbQueryVector: [0.1, 0.2],
2346
- pkbScopeId: "scope-1",
2347
- pkbConversation: preCompactionConversation,
2348
- pkbRoot,
2349
- pkbWorkingDir,
2350
- pkbAutoInjectList: [],
2351
- },
2367
+ preCompactionMessages,
2368
+ {},
2352
2369
  );
2353
2370
  // Unwrap the injected reminder from the last user message.
2354
2371
  const initialTexts = extractTexts(initialResult);
@@ -2360,41 +2377,29 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
2360
2377
  expect(initialReminder).toBeDefined();
2361
2378
  expect(initialReminder).not.toContain("- topics/beta.md");
2362
2379
 
2363
- // 2. Simulate compaction: strip all runtime injections, rebuild
2364
- // conversation to reflect the post-compaction state (tool_use blocks
2365
- // are serialized into summary text, so the only live file_read is the
2366
- // newly-read gamma).
2367
- const postCompactionConversation: { messages: Message[] } = {
2368
- messages: [
2369
- ...baseMessages,
2370
- {
2371
- role: "assistant",
2372
- content: [
2373
- {
2374
- type: "tool_use",
2375
- id: "tu_post",
2376
- name: "file_read",
2377
- input: { path: `${pkbRoot}/topics/gamma.md` },
2378
- },
2379
- ],
2380
- },
2381
- ],
2382
- };
2383
- const postCompactionMessages = stripInjectionsForCompaction(initialResult);
2380
+ // 2. After compaction the working messages reflect the post-compaction
2381
+ // state: beta's tool_use was serialized into summary text and dropped,
2382
+ // so the only live file_read is the newly-read gamma.
2383
+ const postCompactionMessages: Message[] = [
2384
+ {
2385
+ role: "assistant",
2386
+ content: [
2387
+ {
2388
+ type: "tool_use",
2389
+ id: "tu_post",
2390
+ name: "file_read",
2391
+ input: { path: `${pkbRoot}/topics/gamma.md` },
2392
+ },
2393
+ ],
2394
+ },
2395
+ ...baseMessages,
2396
+ ];
2384
2397
 
2385
- // 3. Re-inject with the new conversation — gamma (now in context)
2398
+ // 3. Re-inject over the post-compaction messages — gamma (now in context)
2386
2399
  // should be filtered, and beta (no longer "in context") should appear.
2387
2400
  const { messages: rebuiltResult } = await applyRuntimeInjections(
2388
2401
  postCompactionMessages,
2389
- {
2390
- pkbActive: true,
2391
- pkbQueryVector: [0.1, 0.2],
2392
- pkbScopeId: "scope-1",
2393
- pkbConversation: postCompactionConversation,
2394
- pkbRoot,
2395
- pkbWorkingDir,
2396
- pkbAutoInjectList: [],
2397
- },
2402
+ {},
2398
2403
  );
2399
2404
  const rebuiltTexts = extractTexts(rebuiltResult);
2400
2405
  const rebuiltReminder = rebuiltTexts.find(
@@ -2414,6 +2419,8 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
2414
2419
  // ---------------------------------------------------------------------------
2415
2420
 
2416
2421
  describe("Slack channel chronological rendering — multi-thread", () => {
2422
+ afterEach(clearConversations);
2423
+
2417
2424
  // Slack ts values are seconds-since-epoch with microsecond precision.
2418
2425
  // Pick a few stable anchors so thread aliases (sha-derived) stay
2419
2426
  // predictable across the scenarios.
@@ -2499,8 +2506,8 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2499
2506
  role: "user",
2500
2507
  content: [{ type: "text", text: "current turn" }],
2501
2508
  };
2509
+ seedChannelCapabilitiesConversation(slackChannelCaps);
2502
2510
  const { messages } = await applyRuntimeInjections([lastUserMessage], {
2503
- channelCapabilities: slackChannelCaps,
2504
2511
  slackChronologicalMessages,
2505
2512
  });
2506
2513
  return messages;
@@ -2788,15 +2795,15 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2788
2795
  role: "user",
2789
2796
  content: [{ type: "text", text: "vellum question" }],
2790
2797
  };
2798
+ seedChannelCapabilitiesConversation({
2799
+ channel: "vellum",
2800
+ dashboardCapable: true,
2801
+ supportsDynamicUi: true,
2802
+ supportsVoiceInput: true,
2803
+ });
2791
2804
  const { messages: result } = await applyRuntimeInjections(
2792
2805
  [lastUserMessage],
2793
2806
  {
2794
- channelCapabilities: {
2795
- channel: "vellum",
2796
- dashboardCapable: true,
2797
- supportsDynamicUi: true,
2798
- supportsVoiceInput: true,
2799
- },
2800
2807
  // Even if we accidentally pass a chronological transcript, the
2801
2808
  // branch must be a no-op for non-slack channels.
2802
2809
  slackChronologicalMessages: [
@@ -2826,16 +2833,16 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2826
2833
  role: "user",
2827
2834
  content: [{ type: "text", text: "DM question" }],
2828
2835
  };
2836
+ seedChannelCapabilitiesConversation({
2837
+ channel: "slack",
2838
+ dashboardCapable: false,
2839
+ supportsDynamicUi: false,
2840
+ supportsVoiceInput: false,
2841
+ chatType: "im",
2842
+ });
2829
2843
  const { messages: result } = await applyRuntimeInjections(
2830
2844
  [lastUserMessage],
2831
2845
  {
2832
- channelCapabilities: {
2833
- channel: "slack",
2834
- dashboardCapable: false,
2835
- supportsDynamicUi: false,
2836
- supportsVoiceInput: false,
2837
- chatType: "im",
2838
- },
2839
2846
  slackChronologicalMessages: [
2840
2847
  {
2841
2848
  role: "user",
@@ -2894,10 +2901,10 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2894
2901
  ],
2895
2902
  },
2896
2903
  ];
2904
+ seedChannelCapabilitiesConversation(slackCaps);
2897
2905
  const { messages: result } = await applyRuntimeInjections(
2898
2906
  runMessagesWithMemory,
2899
2907
  {
2900
- channelCapabilities: slackCaps,
2901
2908
  slackChronologicalMessages: [
2902
2909
  {
2903
2910
  role: "user",
@@ -2958,10 +2965,10 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2958
2965
  ],
2959
2966
  },
2960
2967
  ];
2968
+ seedChannelCapabilitiesConversation(slackCaps);
2961
2969
  const { messages: result } = await applyRuntimeInjections(
2962
2970
  runMessagesWithMemory,
2963
2971
  {
2964
- channelCapabilities: slackCaps,
2965
2972
  slackChronologicalMessages: [
2966
2973
  {
2967
2974
  role: "user",
@@ -2997,10 +3004,10 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2997
3004
  supportsVoiceInput: false,
2998
3005
  chatType: "im",
2999
3006
  };
3007
+ seedChannelCapabilitiesConversation(slackCaps);
3000
3008
  const { messages: result } = await applyRuntimeInjections(
3001
3009
  [{ role: "user", content: [{ type: "text", text: "inbound" }] }],
3002
3010
  {
3003
- channelCapabilities: slackCaps,
3004
3011
  slackChronologicalMessages: [
3005
3012
  {
3006
3013
  role: "user",
@@ -3041,12 +3048,13 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3041
3048
  { loader: () => rows, trustClass: "guardian" },
3042
3049
  );
3043
3050
 
3051
+ seedChannelCapabilitiesConversation(slackChannelCaps, [
3052
+ "thread context: ...",
3053
+ ]);
3044
3054
  const { messages: result } = await applyRuntimeInjections(
3045
3055
  [{ role: "user", content: [{ type: "text", text: "current turn" }] }],
3046
3056
  {
3047
- channelCapabilities: slackChannelCaps,
3048
3057
  slackChronologicalMessages,
3049
- transportHints: ["thread context: ..."],
3050
3058
  },
3051
3059
  );
3052
3060
 
@@ -3071,12 +3079,10 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3071
3079
  chatType: "im",
3072
3080
  };
3073
3081
 
3082
+ seedChannelCapabilitiesConversation(slackDmCaps, ["dm context: ..."]);
3074
3083
  const { messages: result } = await applyRuntimeInjections(
3075
3084
  [{ role: "user", content: [{ type: "text", text: "hi DM" }] }],
3076
- {
3077
- channelCapabilities: slackDmCaps,
3078
- transportHints: ["dm context: ..."],
3079
- },
3085
+ {},
3080
3086
  );
3081
3087
 
3082
3088
  const allText = result
@@ -3090,18 +3096,19 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3090
3096
 
3091
3097
  // ── transport_hints kept for non-slack channels ───────────────────────
3092
3098
  test("non-slack conversations still receive <transport_hints>", async () => {
3093
- const { messages: result } = await applyRuntimeInjections(
3094
- [{ role: "user", content: [{ type: "text", text: "hi" }] }],
3099
+ seedChannelCapabilitiesConversation(
3095
3100
  {
3096
- channelCapabilities: {
3097
- channel: "telegram",
3098
- dashboardCapable: false,
3099
- supportsDynamicUi: false,
3100
- supportsVoiceInput: false,
3101
- chatType: "private",
3102
- },
3103
- transportHints: ["please answer concisely"],
3101
+ channel: "telegram",
3102
+ dashboardCapable: false,
3103
+ supportsDynamicUi: false,
3104
+ supportsVoiceInput: false,
3105
+ chatType: "private",
3104
3106
  },
3107
+ ["please answer concisely"],
3108
+ );
3109
+ const { messages: result } = await applyRuntimeInjections(
3110
+ [{ role: "user", content: [{ type: "text", text: "hi" }] }],
3111
+ {},
3105
3112
  );
3106
3113
  const allText = result
3107
3114
  .flatMap((m) => m.content)
@@ -3426,8 +3433,8 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3426
3433
  role: "user",
3427
3434
  content: [{ type: "text", text: "current turn" }],
3428
3435
  };
3436
+ seedChannelCapabilitiesConversation(slackChannelCaps);
3429
3437
  const { messages } = await applyRuntimeInjections([lastUserMessage], {
3430
- channelCapabilities: slackChannelCaps,
3431
3438
  slackChronologicalMessages,
3432
3439
  slackActiveThreadFocusBlock: focusBlock,
3433
3440
  });
@@ -3696,8 +3703,8 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3696
3703
  chatType: "channel",
3697
3704
  };
3698
3705
  const newFocus = "<active_thread>\nnewly built\n</active_thread>";
3706
+ seedChannelCapabilitiesConversation(slackChannelCaps);
3699
3707
  const { messages: reInjected } = await applyRuntimeInjections(stripped, {
3700
- channelCapabilities: slackChannelCaps,
3701
3708
  slackActiveThreadFocusBlock: newFocus,
3702
3709
  });
3703
3710
  const reInjectedTexts = reInjected
@@ -3717,15 +3724,15 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3717
3724
  // Defensive: the focus injection is gated on `slackChannel` (i.e.
3718
3725
  // `isSlackChannelConversation`). Even if a caller mistakenly forwards
3719
3726
  // a focus block on a non-Slack channel, it must NOT be appended.
3727
+ seedChannelCapabilitiesConversation({
3728
+ channel: "vellum",
3729
+ dashboardCapable: true,
3730
+ supportsDynamicUi: true,
3731
+ supportsVoiceInput: true,
3732
+ });
3720
3733
  const { messages: result } = await applyRuntimeInjections(
3721
3734
  [{ role: "user", content: [{ type: "text", text: "vellum question" }] }],
3722
3735
  {
3723
- channelCapabilities: {
3724
- channel: "vellum",
3725
- dashboardCapable: true,
3726
- supportsDynamicUi: true,
3727
- supportsVoiceInput: true,
3728
- },
3729
3736
  slackActiveThreadFocusBlock: "<active_thread>\nbogus\n</active_thread>",
3730
3737
  },
3731
3738
  );
@@ -3742,16 +3749,16 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3742
3749
  // Same as above but for Slack DMs (chatType === "im"). The focus
3743
3750
  // injection is keyed on `isSlackChannelConversation` which excludes
3744
3751
  // DMs, so the block must not appear.
3752
+ seedChannelCapabilitiesConversation({
3753
+ channel: "slack",
3754
+ dashboardCapable: false,
3755
+ supportsDynamicUi: false,
3756
+ supportsVoiceInput: false,
3757
+ chatType: "im",
3758
+ });
3745
3759
  const { messages: result } = await applyRuntimeInjections(
3746
3760
  [{ role: "user", content: [{ type: "text", text: "DM question" }] }],
3747
3761
  {
3748
- channelCapabilities: {
3749
- channel: "slack",
3750
- dashboardCapable: false,
3751
- supportsDynamicUi: false,
3752
- supportsVoiceInput: false,
3753
- chatType: "im",
3754
- },
3755
3762
  slackActiveThreadFocusBlock: "<active_thread>\nbogus\n</active_thread>",
3756
3763
  },
3757
3764
  );
@@ -5100,11 +5107,15 @@ describe("applyRuntimeInjections blocks.pkbSystemReminder", () => {
5100
5107
  },
5101
5108
  ];
5102
5109
 
5110
+ // The reminder gate hinges on PKB content presence; clear it after each test
5111
+ // so the "PKB inactive" case starts from an empty workspace pkb dir.
5112
+ afterEach(clearPkbContent);
5113
+
5103
5114
  test("captures exact reminder bytes when full mode and PKB active", async () => {
5115
+ seedPkbContent();
5104
5116
  pkbSearchResults = [];
5105
5117
  pkbSearchThrows = null;
5106
5118
  const { blocks } = await applyRuntimeInjections(baseMessages, {
5107
- pkbActive: true,
5108
5119
  mode: "full",
5109
5120
  });
5110
5121
 
@@ -5113,8 +5124,8 @@ describe("applyRuntimeInjections blocks.pkbSystemReminder", () => {
5113
5124
  });
5114
5125
 
5115
5126
  test("not captured in minimal mode", async () => {
5127
+ seedPkbContent();
5116
5128
  const { blocks } = await applyRuntimeInjections(baseMessages, {
5117
- pkbActive: true,
5118
5129
  mode: "minimal",
5119
5130
  });
5120
5131
 
@@ -5123,7 +5134,6 @@ describe("applyRuntimeInjections blocks.pkbSystemReminder", () => {
5123
5134
 
5124
5135
  test("not captured when PKB inactive", async () => {
5125
5136
  const { blocks } = await applyRuntimeInjections(baseMessages, {
5126
- pkbActive: false,
5127
5137
  mode: "full",
5128
5138
  });
5129
5139