@vellumai/assistant 0.8.7 → 0.8.8

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 (387) hide show
  1. package/Dockerfile +20 -4
  2. package/docker-entrypoint.sh +4 -2
  3. package/docker-init-apt-root.sh +3 -1
  4. package/docker-kata-apt-env.sh +3 -1
  5. package/docker-kata-runtime-family.sh +12 -0
  6. package/docs/architecture/memory.md +1 -1
  7. package/docs/plugins.md +75 -79
  8. package/examples/plugins/echo/README.md +6 -12
  9. package/examples/plugins/echo/register.ts +0 -41
  10. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
  11. package/openapi.yaml +3381 -348
  12. package/package.json +1 -1
  13. package/scripts/generate-openapi.ts +68 -41
  14. package/src/__tests__/agent-loop-exit-reason.test.ts +34 -39
  15. package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
  16. package/src/__tests__/agent-loop.test.ts +37 -87
  17. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  18. package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
  19. package/src/__tests__/annotate-risk-options.test.ts +2 -3
  20. package/src/__tests__/anthropic-provider.test.ts +95 -2
  21. package/src/__tests__/assistant-event-hub.test.ts +25 -0
  22. package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
  23. package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
  24. package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
  25. package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
  26. package/src/__tests__/btw-routes.test.ts +62 -3
  27. package/src/__tests__/build-persisted-content.test.ts +184 -0
  28. package/src/__tests__/catalog-files.test.ts +1 -1
  29. package/src/__tests__/clawhub-files.test.ts +1 -1
  30. package/src/__tests__/compaction-pipeline.test.ts +1 -1
  31. package/src/__tests__/compaction.benchmark.test.ts +0 -30
  32. package/src/__tests__/config-watcher.test.ts +1 -1
  33. package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
  34. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -2
  35. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -4
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +313 -1136
  37. package/src/__tests__/conversation-agent-loop.test.ts +596 -1616
  38. package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
  39. package/src/__tests__/conversation-history-web-search.test.ts +11 -1
  40. package/src/__tests__/conversation-pairing.test.ts +4 -31
  41. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +26 -5
  43. package/src/__tests__/conversation-queue.test.ts +2 -0
  44. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
  45. package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +170 -229
  47. package/src/__tests__/conversation-runtime-workspace.test.ts +3 -24
  48. package/src/__tests__/conversation-slash-commands.test.ts +8 -42
  49. package/src/__tests__/conversation-slash-queue.test.ts +6 -1
  50. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
  51. package/src/__tests__/conversation-sync-tags.test.ts +27 -15
  52. package/src/__tests__/conversation-title-service.test.ts +135 -2
  53. package/src/__tests__/conversation-workspace-injection.test.ts +6 -1
  54. package/src/__tests__/cross-provider-web-search.test.ts +214 -1
  55. package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
  56. package/src/__tests__/dm-persistence.test.ts +5 -1
  57. package/src/__tests__/empty-response-hook.test.ts +304 -0
  58. package/src/__tests__/feature-flag-test-helpers.ts +2 -2
  59. package/src/__tests__/gemini-image-service.test.ts +13 -0
  60. package/src/__tests__/helpers/mock-provider.ts +110 -0
  61. package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
  62. package/src/__tests__/history-repair-hook.test.ts +1 -0
  63. package/src/__tests__/identity-intro-cache.test.ts +12 -100
  64. package/src/__tests__/identity-routes.test.ts +248 -7
  65. package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
  66. package/src/__tests__/injector-background-turn.test.ts +2 -8
  67. package/src/__tests__/injector-chain.test.ts +106 -270
  68. package/src/__tests__/injector-disk-pressure.test.ts +3 -12
  69. package/src/__tests__/injector-document-comments.test.ts +2 -2
  70. package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
  71. package/src/__tests__/injector-v3-suppression.test.ts +31 -37
  72. package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
  73. package/src/__tests__/list-messages-page-latest.test.ts +60 -0
  74. package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
  75. package/src/__tests__/llm-usage-store.test.ts +223 -1
  76. package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
  77. package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
  78. package/src/__tests__/native-web-search.test.ts +191 -0
  79. package/src/__tests__/onboarding-template-contract.test.ts +2 -0
  80. package/src/__tests__/openai-image-service.test.ts +17 -0
  81. package/src/__tests__/openai-provider.test.ts +31 -1
  82. package/src/__tests__/persist-unsendable-image.test.ts +215 -0
  83. package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
  84. package/src/__tests__/pipeline-runner.test.ts +29 -39
  85. package/src/__tests__/pkb-autoinject.test.ts +2 -5
  86. package/src/__tests__/plugin-bootstrap.test.ts +13 -28
  87. package/src/__tests__/plugin-registry.test.ts +0 -27
  88. package/src/__tests__/plugin-types.test.ts +2 -125
  89. package/src/__tests__/process-message-display-content.test.ts +6 -2
  90. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
  91. package/src/__tests__/resolve-trust-class.test.ts +4 -4
  92. package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
  93. package/src/__tests__/schedule-routes.test.ts +603 -2
  94. package/src/__tests__/schedule-store.test.ts +41 -0
  95. package/src/__tests__/schedule-tools.test.ts +35 -0
  96. package/src/__tests__/server-history-render.test.ts +314 -1
  97. package/src/__tests__/skillssh-files.test.ts +1 -1
  98. package/src/__tests__/system-prompt.test.ts +20 -0
  99. package/src/__tests__/task-scheduler.test.ts +162 -1
  100. package/src/__tests__/terminal-tools.test.ts +6 -1
  101. package/src/__tests__/title-generate-hook.test.ts +319 -0
  102. package/src/__tests__/tool-error-hook.test.ts +278 -0
  103. package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
  104. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  105. package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
  106. package/src/__tests__/tool-result-truncation.test.ts +0 -2
  107. package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
  108. package/src/__tests__/ui-work-result-surface.test.ts +159 -0
  109. package/src/__tests__/usage-routes.test.ts +285 -1
  110. package/src/__tests__/user-plugin-loader.test.ts +2 -2
  111. package/src/__tests__/voice-session-bridge.test.ts +6 -3
  112. package/src/__tests__/web-search-backend-failure.test.ts +166 -0
  113. package/src/agent/loop.ts +346 -442
  114. package/src/api/events/assistant-thinking-delta.ts +33 -0
  115. package/src/api/events/tool-output-chunk.ts +45 -0
  116. package/src/api/events/tool-use-preview-start.ts +32 -0
  117. package/src/api/events/trace-event.ts +69 -0
  118. package/src/api/index.ts +48 -13
  119. package/src/api/responses/conversation-message.ts +368 -0
  120. package/src/avatar/__tests__/avatar-store.test.ts +34 -29
  121. package/src/cli/commands/__tests__/notifications.test.ts +58 -14
  122. package/src/cli/commands/notifications.ts +112 -60
  123. package/src/config/assistant-feature-flags.ts +22 -11
  124. package/src/config/bundled-skills/app-builder/SKILL.md +3 -20
  125. package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
  126. package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
  127. package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
  128. package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
  129. package/src/config/bundled-skills/document-editor/SKILL.md +1 -1
  130. package/src/config/bundled-skills/messaging/SKILL.md +0 -7
  131. package/src/config/feature-flag-cache.ts +3 -3
  132. package/src/config/feature-flag-registry.json +35 -3
  133. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  134. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
  135. package/src/config/schemas/llm.ts +1 -0
  136. package/src/config/schemas/memory-v2.ts +8 -0
  137. package/src/config/schemas/memory-v3.ts +8 -0
  138. package/src/config/schemas/platform.ts +8 -0
  139. package/src/config/seed-inference-profiles.ts +2 -2
  140. package/src/config/skills.ts +13 -0
  141. package/src/context/compactor.ts +1 -1
  142. package/src/context/strip-injections.ts +122 -0
  143. package/src/context/token-estimator.ts +23 -0
  144. package/src/context/tool-result-truncation.ts +0 -23
  145. package/src/context/window-manager.ts +3 -6
  146. package/src/credential-execution/executable-discovery.ts +16 -0
  147. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
  148. package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
  149. package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
  150. package/src/daemon/assistant-attachments.ts +1 -1
  151. package/src/daemon/config-watcher.ts +2 -2
  152. package/src/daemon/context-overflow-reducer.ts +0 -1
  153. package/src/daemon/conversation-agent-loop-handlers.ts +605 -153
  154. package/src/daemon/conversation-agent-loop.ts +281 -760
  155. package/src/daemon/conversation-history.ts +5 -4
  156. package/src/daemon/conversation-lifecycle.ts +3 -4
  157. package/src/daemon/conversation-messaging.ts +7 -6
  158. package/src/daemon/conversation-process.ts +11 -16
  159. package/src/daemon/conversation-runtime-assembly.ts +130 -347
  160. package/src/daemon/conversation-slash.ts +6 -25
  161. package/src/daemon/conversation-surfaces.ts +222 -4
  162. package/src/daemon/conversation-tool-setup.ts +2 -29
  163. package/src/daemon/conversation.ts +32 -14
  164. package/src/daemon/external-plugins-bootstrap.ts +9 -10
  165. package/src/daemon/handlers/config-a2a.ts +51 -36
  166. package/src/daemon/handlers/config-slack-channel.ts +20 -14
  167. package/src/daemon/handlers/config-telegram.ts +16 -2
  168. package/src/daemon/handlers/shared.ts +156 -84
  169. package/src/daemon/handlers/skills.ts +39 -10
  170. package/src/daemon/lifecycle.ts +4 -0
  171. package/src/daemon/message-types/apps.ts +1 -29
  172. package/src/daemon/message-types/messages.ts +9 -57
  173. package/src/daemon/message-types/skills.ts +2 -0
  174. package/src/daemon/message-types/surfaces.ts +136 -3
  175. package/src/daemon/now-scratchpad.ts +21 -0
  176. package/src/daemon/orphan-reaper.test.ts +210 -0
  177. package/src/daemon/orphan-reaper.ts +240 -0
  178. package/src/daemon/persist-unsendable-image.ts +117 -0
  179. package/src/daemon/process-message.ts +1 -3
  180. package/src/daemon/trace-emitter.ts +6 -4
  181. package/src/daemon/trust-context.ts +19 -0
  182. package/src/daemon/wake-target-adapter.ts +3 -1
  183. package/src/home/home-greeting-cache.ts +24 -1
  184. package/src/ipc/gateway-client.test.ts +2 -2
  185. package/src/ipc/gateway-client.ts +3 -3
  186. package/src/media/gemini-image-service.ts +15 -0
  187. package/src/media/openai-image-service.ts +14 -0
  188. package/src/media/types.ts +34 -0
  189. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
  190. package/src/memory/auth-fallback-events-store.ts +94 -0
  191. package/src/memory/conversation-title-service.ts +65 -41
  192. package/src/memory/db-init.ts +4 -0
  193. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  194. package/src/memory/graph/conversation-graph-memory.ts +65 -0
  195. package/src/memory/jobs-store.ts +33 -0
  196. package/src/memory/jobs-worker.ts +31 -4
  197. package/src/memory/llm-usage-store.ts +224 -50
  198. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
  199. package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
  200. package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
  201. package/src/memory/migrations/index.ts +2 -0
  202. package/src/memory/pkb/autoinject.ts +61 -0
  203. package/src/memory/pkb/context.ts +50 -0
  204. package/src/memory/pkb/types.ts +14 -0
  205. package/src/memory/schedule-attribution-sql.ts +104 -0
  206. package/src/memory/schema/infrastructure.ts +16 -0
  207. package/src/memory/usage-grouped-buckets.ts +6 -1
  208. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -1
  209. package/src/memory/v2/consolidation-job.ts +1 -1
  210. package/src/memory/v3/__tests__/health.test.ts +16 -0
  211. package/src/memory/v3/__tests__/orchestrate.test.ts +45 -9
  212. package/src/memory/v3/__tests__/provider-blocks.test.ts +13 -0
  213. package/src/memory/v3/__tests__/router.test.ts +101 -29
  214. package/src/memory/v3/__tests__/selector.test.ts +93 -27
  215. package/src/memory/v3/__tests__/shadow-plugin.test.ts +23 -5
  216. package/src/memory/v3/health.ts +0 -0
  217. package/src/memory/v3/llm-retry.ts +32 -0
  218. package/src/memory/v3/orchestrate.ts +26 -14
  219. package/src/memory/v3/provider-blocks.ts +15 -5
  220. package/src/memory/v3/router.ts +48 -42
  221. package/src/memory/v3/selector.ts +57 -42
  222. package/src/memory/v3/shadow-plugin.ts +47 -15
  223. package/src/memory/v3/types.ts +8 -0
  224. package/src/notifications/conversation-pairing.ts +8 -15
  225. package/src/notifications/decision-engine.ts +6 -3
  226. package/src/notifications/home-feed-side-effect.ts +12 -1
  227. package/src/permissions/prompter.ts +4 -0
  228. package/src/plugin-api/constants.ts +4 -0
  229. package/src/plugin-api/index.ts +8 -1
  230. package/src/plugin-api/types.ts +151 -1
  231. package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
  232. package/src/plugins/defaults/empty-response/register.ts +8 -13
  233. package/src/plugins/defaults/index.ts +1 -15
  234. package/src/plugins/defaults/injectors/register.ts +243 -74
  235. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +91 -0
  236. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
  237. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
  238. package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
  239. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
  240. package/src/plugins/defaults/title-generate/package.json +1 -1
  241. package/src/plugins/defaults/title-generate/register.ts +18 -18
  242. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
  243. package/src/plugins/defaults/tool-error/package.json +1 -1
  244. package/src/plugins/defaults/tool-error/register.ts +9 -21
  245. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
  246. package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
  247. package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
  248. package/src/plugins/pipeline.ts +6 -18
  249. package/src/plugins/registry.ts +8 -25
  250. package/src/plugins/types.ts +43 -474
  251. package/src/proactive-artifact/aux-message-injector.ts +3 -3
  252. package/src/proactive-artifact/job.test.ts +7 -12
  253. package/src/prompts/__tests__/system-prompt.test.ts +36 -0
  254. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +62 -0
  255. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  256. package/src/prompts/templates/system-sections.ts +15 -0
  257. package/src/providers/anthropic/client.ts +37 -29
  258. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
  259. package/src/providers/openai/chat-completions-provider.ts +44 -0
  260. package/src/providers/openrouter/client.ts +1 -0
  261. package/src/providers/placeholder-sentinels.ts +35 -0
  262. package/src/runtime/__tests__/agent-wake.test.ts +5 -1
  263. package/src/runtime/agent-wake.ts +2 -2
  264. package/src/runtime/assistant-event-hub.ts +36 -6
  265. package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
  266. package/src/runtime/http-router.ts +16 -21
  267. package/src/runtime/http-types.ts +16 -70
  268. package/src/runtime/pending-interactions.ts +1 -0
  269. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
  270. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
  271. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
  272. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  273. package/src/runtime/routes/app-management-routes.ts +6 -117
  274. package/src/runtime/routes/app-routes.ts +13 -15
  275. package/src/runtime/routes/attachment-routes.ts +26 -15
  276. package/src/runtime/routes/avatar-routes.ts +26 -0
  277. package/src/runtime/routes/btw-routes.ts +29 -23
  278. package/src/runtime/routes/consolidation-routes.ts +120 -20
  279. package/src/runtime/routes/conversation-query-routes.ts +2 -0
  280. package/src/runtime/routes/conversation-routes.ts +358 -184
  281. package/src/runtime/routes/documents-routes.ts +4 -0
  282. package/src/runtime/routes/domain-routes.ts +51 -37
  283. package/src/runtime/routes/epoch-millis-range.ts +34 -0
  284. package/src/runtime/routes/events-routes.ts +28 -34
  285. package/src/runtime/routes/gateway-log-routes.ts +26 -4
  286. package/src/runtime/routes/heartbeat-routes.ts +32 -12
  287. package/src/runtime/routes/identity-intro-cache.ts +11 -34
  288. package/src/runtime/routes/identity-routes.ts +208 -17
  289. package/src/runtime/routes/image-generation-routes.ts +40 -2
  290. package/src/runtime/routes/index.ts +2 -0
  291. package/src/runtime/routes/integrations/a2a.ts +12 -10
  292. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
  293. package/src/runtime/routes/integrations/slack/channel.ts +4 -0
  294. package/src/runtime/routes/integrations/slack/share.ts +27 -6
  295. package/src/runtime/routes/integrations/telegram.ts +6 -0
  296. package/src/runtime/routes/integrations/twilio.ts +42 -0
  297. package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
  298. package/src/runtime/routes/log-export-routes.ts +8 -0
  299. package/src/runtime/routes/memory-v2-routes.ts +15 -8
  300. package/src/runtime/routes/memory-v3-routes.ts +50 -28
  301. package/src/runtime/routes/oauth-apps.ts +66 -12
  302. package/src/runtime/routes/oauth-providers.ts +44 -5
  303. package/src/runtime/routes/platform-routes.ts +81 -5
  304. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
  305. package/src/runtime/routes/playground/force-compact.ts +1 -1
  306. package/src/runtime/routes/rename-conversation-routes.ts +5 -0
  307. package/src/runtime/routes/schedule-routes.ts +152 -42
  308. package/src/runtime/routes/secret-routes.ts +14 -2
  309. package/src/runtime/routes/skills-routes.ts +43 -14
  310. package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
  311. package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
  312. package/src/runtime/routes/trust-rules-routes.ts +26 -2
  313. package/src/runtime/routes/tts-routes.ts +35 -0
  314. package/src/runtime/routes/types.ts +66 -8
  315. package/src/runtime/routes/usage-routes.ts +47 -39
  316. package/src/runtime/routes/webhook-routes.ts +41 -2
  317. package/src/runtime/routes/workspace-routes.ts +4 -0
  318. package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
  319. package/src/runtime/services/analyze-conversation.ts +2 -2
  320. package/src/schedule/schedule-store.ts +20 -1
  321. package/src/schedule/schedule-usage-store.ts +83 -0
  322. package/src/schedule/scheduler.ts +12 -5
  323. package/src/skills/catalog-files.ts +2 -2
  324. package/src/skills/catalog-install.ts +3 -0
  325. package/src/skills/categories-cache.ts +118 -0
  326. package/src/skills/clawhub-files.ts +1 -2
  327. package/src/skills/skillssh-files.ts +1 -2
  328. package/src/telemetry/types.ts +29 -1
  329. package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
  330. package/src/telemetry/usage-telemetry-reporter.ts +57 -2
  331. package/src/tools/executor.ts +1 -53
  332. package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
  333. package/src/tools/network/__tests__/web-search.test.ts +11 -3
  334. package/src/tools/network/web-search-error.test.ts +248 -0
  335. package/src/tools/network/web-search-error.ts +267 -0
  336. package/src/tools/network/web-search.ts +207 -48
  337. package/src/tools/schedule/create.ts +2 -0
  338. package/src/tools/terminal/safe-env.ts +10 -1
  339. package/src/tools/ui-surface/definitions.ts +9 -1
  340. package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
  341. package/src/tts/provider-catalog.ts +76 -1
  342. package/src/util/mutex.ts +47 -0
  343. package/src/workspace/git-service.ts +1 -42
  344. package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
  345. package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
  346. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +93 -0
  347. package/src/workspace/migrations/registry.ts +6 -0
  348. package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
  349. package/src/__tests__/empty-response-pipeline.test.ts +0 -423
  350. package/src/__tests__/llm-call-pipeline.test.ts +0 -287
  351. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
  352. package/src/__tests__/persistence-pipeline.test.ts +0 -503
  353. package/src/__tests__/title-generate-pipeline.test.ts +0 -211
  354. package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
  355. package/src/__tests__/tool-error-pipeline.test.ts +0 -241
  356. package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
  357. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
  358. package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
  359. package/src/gallery/default-gallery.ts +0 -1359
  360. package/src/gallery/gallery-manifest.ts +0 -28
  361. package/src/home/feature-gate.ts +0 -22
  362. package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
  363. package/src/plugins/defaults/empty-response/terminal.ts +0 -106
  364. package/src/plugins/defaults/injectors/package.json +0 -15
  365. package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
  366. package/src/plugins/defaults/llm-call/package.json +0 -15
  367. package/src/plugins/defaults/llm-call/register.ts +0 -45
  368. package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
  369. package/src/plugins/defaults/memory-retrieval/package.json +0 -15
  370. package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
  371. package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
  372. package/src/plugins/defaults/persistence/package.json +0 -15
  373. package/src/plugins/defaults/persistence/register.ts +0 -38
  374. package/src/plugins/defaults/persistence/terminal.ts +0 -83
  375. package/src/plugins/defaults/title-generate/terminal.ts +0 -31
  376. package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
  377. package/src/plugins/defaults/token-estimate/package.json +0 -15
  378. package/src/plugins/defaults/token-estimate/register.ts +0 -34
  379. package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
  380. package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
  381. package/src/plugins/defaults/tool-error/terminal.ts +0 -47
  382. package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
  383. package/src/plugins/defaults/tool-execute/package.json +0 -15
  384. package/src/plugins/defaults/tool-execute/register.ts +0 -49
  385. package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
  386. package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
  387. package/src/skills/category-inference.ts +0 -111
@@ -5,13 +5,21 @@
5
5
  * before it is sent to the provider. They are pure (no side effects).
6
6
  */
7
7
 
8
- import { existsSync, readFileSync, statSync } from "node:fs";
9
- import { join, resolve } from "node:path";
8
+ import { statSync } from "node:fs";
9
+ import { join } from "node:path";
10
10
 
11
11
  import { type ChannelId, parseInterfaceId } from "../channels/types.js";
12
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
12
13
  import { getConfig } from "../config/loader.js";
14
+ import { stripUserTextBlocksByPrefix } from "../context/strip-injections.js";
13
15
  import { createContextSummaryMessage } from "../context/window-manager.js";
14
- import { getAppDirPath, listAppFiles } from "../memory/app-store.js";
16
+ import { getDocumentsForConversation } from "../documents/document-store.js";
17
+ import {
18
+ getApp,
19
+ getAppDirPath,
20
+ listAppFiles,
21
+ resolveAppDir,
22
+ } from "../memory/app-store.js";
15
23
  import {
16
24
  getMessages as defaultGetMessages,
17
25
  type MessageRow,
@@ -21,7 +29,6 @@ import {
21
29
  extractMemoryPrefixBlocks,
22
30
  stripAllMemoryInjections,
23
31
  } from "../memory/graph/conversation-graph-memory.js";
24
- import type { QdrantSparseVector } from "../memory/qdrant-client.js";
25
32
  import { MEMORY_V3_BLOCK_ID } from "../memory/v3/types.js";
26
33
  import {
27
34
  readSlackMetadata,
@@ -35,7 +42,7 @@ import {
35
42
  type RenderedSlackTranscriptMessage,
36
43
  renderSlackTranscriptWithProvenance,
37
44
  } from "../messaging/providers/slack/render-transcript.js";
38
- import { getInjectors } from "../plugins/registry.js";
45
+ import { getInjectorChain } from "../plugins/defaults/memory-retrieval/injector-chain.js";
39
46
  import type {
40
47
  DiskPressureInjectionContext,
41
48
  InjectionBlock,
@@ -53,12 +60,18 @@ import { channelStatusToMemberStatus } from "../runtime/routes/inbound-stages/ac
53
60
  import type { SubagentState } from "../subagent/types.js";
54
61
  import { TERMINAL_STATUSES } from "../subagent/types.js";
55
62
  import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
56
- import { getWorkspaceDir, getWorkspacePromptPath } from "../util/platform.js";
57
- import { stripCommentLines } from "../util/strip-comment-lines.js";
63
+ import type {
64
+ DynamicPageSurfaceData,
65
+ SurfaceData,
66
+ SurfaceType,
67
+ } from "./message-protocol.js";
58
68
  import { filterMessagesForUntrustedActor } from "./message-provenance.js";
59
- import { type PkbContextConversation } from "./pkb-context-tracker.js";
60
69
  import type { TrustContext } from "./trust-context.js";
61
70
 
71
+ // The compaction strip lives in the compaction layer (`context/`) so the agent
72
+ // loop can own it; re-exported here for this module's existing consumers.
73
+ export { stripInjectionsForCompaction } from "../context/strip-injections.js";
74
+
62
75
  /**
63
76
  * Describes the capabilities of the channel through which the user is
64
77
  * interacting. Used to gate UI-specific references and permission asks.
@@ -271,8 +284,90 @@ export interface ActiveSurfaceContext {
271
284
  appPages?: Record<string, string>;
272
285
  /** The page currently displayed in the WebView (e.g. "settings.html"). */
273
286
  currentPage?: string;
274
- /** Pre-fetched list of files in the app directory. */
275
- appFiles?: string[];
287
+ }
288
+
289
+ /**
290
+ * Resolve the conversation's active workspace surface into the context block
291
+ * consumed by {@link applyRuntimeInjections}, or `null` when no dynamic-page
292
+ * surface is active. App-backed surfaces are enriched with their persisted app
293
+ * metadata; the file tree is listed on demand by the injector.
294
+ */
295
+ export function buildActiveSurfaceContext(params: {
296
+ currentActiveSurfaceId: string | undefined;
297
+ currentPage: string | undefined;
298
+ surfaceState: ReadonlyMap<
299
+ string,
300
+ { surfaceType: SurfaceType; data: SurfaceData }
301
+ >;
302
+ }): ActiveSurfaceContext | null {
303
+ const { currentActiveSurfaceId, currentPage, surfaceState } = params;
304
+ if (!currentActiveSurfaceId) return null;
305
+
306
+ const stored = surfaceState.get(currentActiveSurfaceId);
307
+ if (!stored || stored.surfaceType !== "dynamic_page") return null;
308
+
309
+ const data = stored.data as DynamicPageSurfaceData;
310
+ const activeSurface: ActiveSurfaceContext = {
311
+ surfaceId: currentActiveSurfaceId,
312
+ html: data.html,
313
+ currentPage,
314
+ };
315
+
316
+ if (data.appId) {
317
+ const app = getApp(data.appId);
318
+ if (app) {
319
+ activeSurface.appId = app.id;
320
+ activeSurface.appName = app.name;
321
+ activeSurface.appDirName = resolveAppDir(app.id).dirName;
322
+ activeSurface.appSchemaJson = app.schemaJson;
323
+ if (app.pages && Object.keys(app.pages).length > 0) {
324
+ activeSurface.appPages = app.pages;
325
+ }
326
+ }
327
+ }
328
+
329
+ return activeSurface;
330
+ }
331
+
332
+ /**
333
+ * Lists the conversation's active documents as the lightweight summaries the
334
+ * `active-documents` injector surfaces to the assistant — letting it target
335
+ * existing documents with `document_update` instead of issuing duplicate
336
+ * `document_create` calls. Returns `null` when the conversation has none.
337
+ */
338
+ export function buildActiveDocuments(conversationId: string): Array<{
339
+ surfaceId: string;
340
+ title: string;
341
+ wordCount: number;
342
+ updatedAt: number;
343
+ }> | null {
344
+ const conversationDocs = getDocumentsForConversation(conversationId);
345
+ return conversationDocs.length > 0
346
+ ? conversationDocs.map((d) => ({
347
+ surfaceId: d.surfaceId,
348
+ title: d.title,
349
+ wordCount: d.wordCount,
350
+ updatedAt: d.updatedAt,
351
+ }))
352
+ : null;
353
+ }
354
+
355
+ /**
356
+ * Resolves the `<workspace>` top-level block for the runtime injector, or
357
+ * `null` when the turn isn't injecting it. The refresh runs every turn so a
358
+ * workspace-mutating tool's `markWorkspaceTopLevelDirty` is picked up on the
359
+ * following turn; it is dirty-guarded, so it only rescans when the cache is
360
+ * stale.
361
+ */
362
+ export function buildWorkspaceTopLevelContext(
363
+ ctx: {
364
+ refreshWorkspaceTopLevelContextIfNeeded(): void;
365
+ workspaceTopLevelContext: string | null;
366
+ },
367
+ shouldInject: boolean,
368
+ ): string | null {
369
+ ctx.refreshWorkspaceTopLevelContextIfNeeded();
370
+ return shouldInject ? ctx.workspaceTopLevelContext : null;
276
371
  }
277
372
 
278
373
  const MAX_CONTEXT_LENGTH = 100_000;
@@ -315,7 +410,7 @@ function injectActiveSurfaceContext(
315
410
  );
316
411
 
317
412
  // File tree with sizes (capped at 50 files to bound prompt size)
318
- const files = ctx.appFiles ?? listAppFiles(ctx.appId);
413
+ const files = listAppFiles(ctx.appId);
319
414
  const MAX_FILE_TREE_ENTRIES = 50;
320
415
  const displayFiles = files.slice(0, MAX_FILE_TREE_ENTRIES);
321
416
  lines.push("", "App files:");
@@ -514,30 +609,7 @@ function injectVoiceCallControlContext(
514
609
  // NOW.md scratchpad injection
515
610
  // ---------------------------------------------------------------------------
516
611
 
517
- /**
518
- * Read the NOW.md scratchpad from the workspace prompt directory.
519
- *
520
- * Returns the trimmed content with `_`-prefixed comment lines stripped,
521
- * or `null` if the file is missing, empty, or unreadable.
522
- */
523
- export function readNowScratchpad(): string | null {
524
- const nowPath = getWorkspacePromptPath("NOW.md");
525
- if (!existsSync(nowPath)) return null;
526
- try {
527
- const stripped = stripCommentLines(readFileSync(nowPath, "utf-8")).trim();
528
- return stripped.length > 0 ? stripped : null;
529
- } catch {
530
- return null;
531
- }
532
- }
533
-
534
- /**
535
- * The `<NOW.md>` block is emitted by the `now-md` default injector
536
- * (`plugins/defaults/injectors/register.ts`) as an `after-memory-prefix` placement.
537
- * Use {@link applyRuntimeInjections} with `options.nowScratchpad` set.
538
- */
539
-
540
- /** Strip `<NOW.md>` blocks injected by `injectNowScratchpad`. */
612
+ /** Strip `<NOW.md>` blocks injected by the `now-md` default injector. */
541
613
  export function stripNowScratchpad(messages: Message[]): Message[] {
542
614
  return stripUserTextBlocksByPrefix(messages, [
543
615
  // Shared prefix catches both the current tag and any pre-line-limit
@@ -547,102 +619,6 @@ export function stripNowScratchpad(messages: Message[]): Message[] {
547
619
  ]);
548
620
  }
549
621
 
550
- // ---------------------------------------------------------------------------
551
- // PKB (Personal Knowledge Base) injection
552
- // ---------------------------------------------------------------------------
553
-
554
- const PKB_DEFAULT_FILES = [
555
- "INDEX.md",
556
- "essentials.md",
557
- "threads.md",
558
- "buffer.md",
559
- ];
560
-
561
- const AUTOINJECT_FILENAME = "_autoinject.md";
562
-
563
- /** Max buffer.md lines injected into prompts — keeps context bounded even when filing is off. */
564
- const MAX_BUFFER_LINES = 50;
565
-
566
- /**
567
- * Read `_autoinject.md` from the PKB directory and return the list of
568
- * filenames to inject.
569
- *
570
- * - Returns `null` when the file is missing or unreadable — callers
571
- * should fall back to the hardcoded defaults.
572
- * - Returns `[]` when the file exists but has no entries (empty or
573
- * comments only) — an explicit opt-out meaning "inject nothing."
574
- */
575
- export function readAutoinjectList(pkbDir: string): string[] | null {
576
- const filePath = join(pkbDir, AUTOINJECT_FILENAME);
577
- if (!existsSync(filePath)) return null;
578
- try {
579
- const raw = stripCommentLines(readFileSync(filePath, "utf-8"));
580
- const files = raw
581
- .split("\n")
582
- .map((l) => l.trim())
583
- .filter((l) => l.length > 0);
584
- return files.length > 0 ? files : [];
585
- } catch {
586
- return null;
587
- }
588
- }
589
-
590
- /**
591
- * Resolve the effective list of auto-inject filenames for a PKB directory.
592
- *
593
- * This is the single source of truth used both by `readPkbContext` (which
594
- * actually injects the files) and by the PKB reminder-hint tracker in
595
- * `conversation-agent-loop.ts` (which needs to know what's already in
596
- * context so it doesn't redundantly recommend those files).
597
- *
598
- * Returns `PKB_DEFAULT_FILES` when `_autoinject.md` is missing/unreadable,
599
- * or the parsed list (possibly empty) when it is present.
600
- */
601
- export function getPkbAutoInjectList(pkbRoot: string): string[] {
602
- return readAutoinjectList(pkbRoot) ?? PKB_DEFAULT_FILES;
603
- }
604
-
605
- /**
606
- * Read the always-loaded PKB files and append a nudge encouraging the
607
- * assistant to proactively read topic files and use `remember` aggressively.
608
- *
609
- * Which files are loaded is determined by `pkb/_autoinject.md` (one filename
610
- * per line). Falls back to the built-in defaults when that file is absent.
611
- *
612
- * Returns the concatenated content ready for injection, or `null` if all
613
- * files are missing or empty.
614
- */
615
- export function readPkbContext(): string | null {
616
- const pkbDir = join(getWorkspaceDir(), "pkb");
617
- if (!existsSync(pkbDir)) return null;
618
-
619
- const filesToInject = getPkbAutoInjectList(pkbDir);
620
-
621
- const parts: string[] = [];
622
- for (const file of filesToInject) {
623
- // Path traversal guard: reject entries that escape the pkb directory
624
- const filePath = resolve(pkbDir, file);
625
- if (!filePath.startsWith(pkbDir + "/")) continue;
626
-
627
- if (!existsSync(filePath)) continue;
628
- try {
629
- let content = stripCommentLines(readFileSync(filePath, "utf-8")).trim();
630
- if (file === "buffer.md" && content.length > 0) {
631
- // Cap buffer entries to prevent unbounded growth when filing is disabled
632
- const lines = content.split("\n");
633
- if (lines.length > MAX_BUFFER_LINES) {
634
- content = lines.slice(-MAX_BUFFER_LINES).join("\n");
635
- }
636
- }
637
- if (content.length > 0) parts.push(content);
638
- } catch {
639
- // Skip unreadable files
640
- }
641
- }
642
-
643
- return parts.length > 0 ? parts.join("\n\n") : null;
644
- }
645
-
646
622
  /**
647
623
  * Prepend channel capability context to the last user message so the
648
624
  * model knows what the current channel can and cannot do.
@@ -994,53 +970,6 @@ export function buildUnifiedTurnContextBlock(
994
970
  return lines.join("\n");
995
971
  }
996
972
 
997
- // ---------------------------------------------------------------------------
998
- // Prefix-based stripping primitive
999
- // ---------------------------------------------------------------------------
1000
-
1001
- /**
1002
- * A matcher for an injected text block. A plain string matches by prefix
1003
- * (`startsWith`). A `{ prefix, suffix }` wrapper requires BOTH the opening
1004
- * prefix and the closing suffix, so user-authored content that merely begins
1005
- * with an injection-like opening tag (e.g. a message discussing `<info>`
1006
- * markup) is not mistaken for an injected block and dropped. This mirrors
1007
- * `countMemoryPrefixBlocks`, which only treats `<memory>…</memory>` /
1008
- * `<info>…</info>` blocks as injected when the full wrapper is present.
1009
- */
1010
- type InjectionMatcher = string | { prefix: string; suffix: string };
1011
-
1012
- /**
1013
- * Remove text blocks from user messages that match any of the given matchers.
1014
- * If stripping removes all content blocks from a message, the message itself
1015
- * is dropped.
1016
- *
1017
- * This is the shared primitive behind the individual strip* functions and
1018
- * the `stripInjectionsForCompaction` pipeline.
1019
- */
1020
- function stripUserTextBlocksByPrefix(
1021
- messages: Message[],
1022
- matchers: InjectionMatcher[],
1023
- ): Message[] {
1024
- return messages
1025
- .map((message) => {
1026
- if (message.role !== "user") return message;
1027
- const nextContent = message.content.filter((block) => {
1028
- if (block.type !== "text") return true;
1029
- return !matchers.some((m) =>
1030
- typeof m === "string"
1031
- ? block.text.startsWith(m)
1032
- : block.text.startsWith(m.prefix) && block.text.endsWith(m.suffix),
1033
- );
1034
- });
1035
- if (nextContent.length === message.content.length) return message;
1036
- if (nextContent.length === 0) return null;
1037
- return { ...message, content: nextContent };
1038
- })
1039
- .filter(
1040
- (message): message is NonNullable<typeof message> => message != null,
1041
- );
1042
- }
1043
-
1044
973
  // ---------------------------------------------------------------------------
1045
974
  // Individual strip functions (thin wrappers around the primitive)
1046
975
  // ---------------------------------------------------------------------------
@@ -1726,98 +1655,6 @@ export function loadSlackActiveThreadFocusBlock(
1726
1655
  return assembleSlackActiveThreadFocusBlock(rows, capabilities);
1727
1656
  }
1728
1657
 
1729
- /** Matchers stripped by the pipeline (order doesn't matter — single pass). */
1730
- const RUNTIME_INJECTION_PREFIXES: InjectionMatcher[] = [
1731
- "<channel_capabilities>",
1732
- "<channel_command_context>",
1733
- "<disk_pressure_warning>",
1734
- "<channel_turn_context>", // backward-compat: strip legacy separate channel blocks
1735
- "<guardian_context>",
1736
- "<inbound_actor_context>", // backward-compat: strip legacy separate actor blocks
1737
- "<interface_turn_context>", // backward-compat: strip legacy separate interface blocks
1738
- // NOTE: <turn_context> is intentionally NOT stripped — unified turn context
1739
- // blocks persist in history so the assistant retains temporal/actor grounding.
1740
- "<background_turn>",
1741
- "<memory_context __injected>",
1742
- "<memory_context>", // backward-compat: strip legacy blocks from pre-__injected history
1743
- // The static `memory-v2-static` block (`<info>\n…</info>`) and the
1744
- // dynamic activation block (`<memory>\n…</memory>`, plus legacy
1745
- // `<memory __injected>…`) are both stripped so each compaction
1746
- // re-injects the freshest essentials/threads/recent/buffer view and
1747
- // re-runs the activation pipeline, matching the `<knowledge_base>`
1748
- // cadence. The activation pipeline dedupes via `everInjected`, and
1749
- // compaction handles aggregate growth, so accumulation does not cause
1750
- // unbounded context growth. Both wrappers may appear in persisted rows.
1751
- //
1752
- // These two use the full `{ prefix, suffix }` wrapper shape (not a bare
1753
- // prefix) so that user-authored text merely starting with `<memory>\n` or
1754
- // `<info>\n` is never silently dropped during compaction/`/clean`. This
1755
- // matches the full-wrapper requirement in `countMemoryPrefixBlocks`.
1756
- { prefix: "<memory>\n", suffix: "\n</memory>" },
1757
- { prefix: "<info>\n", suffix: "\n</info>" },
1758
- "<voice_call_control>",
1759
- "<workspace_top_level>", // backward-compat: strip legacy workspace blocks
1760
- // NOTE: <workspace> is intentionally NOT stripped — workspace context
1761
- // persists in history so the assistant retains workspace grounding.
1762
- "<temporal_context>\nToday:", // backward-compat: strip legacy temporal blocks
1763
- "<active_subagents>",
1764
- "<active_workspace>",
1765
- "<active_dynamic_page>",
1766
- "<non_interactive_context>",
1767
- // Shared prefix catches both the current NOW.md tag and any pre-line-limit
1768
- // variant that may linger in in-flight histories during a rolling deploy.
1769
- "<NOW.md Always keep this up to date",
1770
- "<now_scratchpad>", // backward-compat: strip legacy blocks from pre-rename history
1771
- "<knowledge_base>",
1772
- "<pkb>", // backward-compat: strip legacy tag from pre-rename history
1773
- "<system_reminder>",
1774
- "<transport_hints>",
1775
- // The Slack active-thread focus block is non-persisted and injected on
1776
- // the FINAL user turn only. Strip it here so re-assembly during compaction
1777
- // and overflow recovery does not duplicate it across turns.
1778
- "<active_thread>",
1779
- "<system_notice>One or more tool calls returned an error.",
1780
- ];
1781
-
1782
- /**
1783
- * Strip all runtime-injected context from message history in a single pass.
1784
- *
1785
- * Used only during compaction and overflow recovery — not on normal turns.
1786
- * Runtime injections persist in history to keep the conversation prefix
1787
- * stable for Anthropic's prefix caching. Stripping is only needed when
1788
- * compaction rewrites the message array (cache miss is expected anyway).
1789
- */
1790
- export function stripInjectionsForCompaction(messages: Message[]): Message[] {
1791
- return stripUserTextBlocksByPrefix(messages, RUNTIME_INJECTION_PREFIXES);
1792
- }
1793
-
1794
- /**
1795
- * Extract the most recently injected NOW.md content from the message history.
1796
- * Returns null if no NOW.md injection is found.
1797
- */
1798
- export function findLastInjectedNowContent(messages: Message[]): string | null {
1799
- // Matches every NOW.md opening tag we emit (the tag text may evolve over
1800
- // time, e.g. adding a line-limit hint), so in-flight histories with older
1801
- // tag variants remain discoverable during a rolling deploy.
1802
- const openTagPrefix = "<NOW.md Always keep this up to date";
1803
- const suffix = "\n</NOW.md>";
1804
- for (let i = messages.length - 1; i >= 0; i--) {
1805
- const msg = messages[i];
1806
- if (msg.role !== "user") continue;
1807
- for (const block of msg.content) {
1808
- if (block.type !== "text" || !block.text.startsWith(openTagPrefix)) {
1809
- continue;
1810
- }
1811
- const tagEnd = block.text.indexOf(">\n");
1812
- if (tagEnd < 0) continue;
1813
- const contentStart = tagEnd + ">\n".length;
1814
- const end = block.text.lastIndexOf(suffix);
1815
- if (end > contentStart) return block.text.slice(contentStart, end);
1816
- }
1817
- }
1818
- return null;
1819
- }
1820
-
1821
1658
  /**
1822
1659
  * Controls which runtime injections are applied.
1823
1660
  *
@@ -1865,8 +1702,14 @@ export interface RuntimeInjectionResult {
1865
1702
  }
1866
1703
 
1867
1704
  /**
1868
- * Run every registered {@link Injector}'s `produce()` in ascending `order`
1869
- * and return every non-null block the chain produced.
1705
+ * Run every {@link Injector} in the chain ({@link getInjectorChain}, already
1706
+ * sorted by ascending `order`) and return every non-null block it produced.
1707
+ *
1708
+ * `runMessages` is the turn's working message array, forwarded to each
1709
+ * injector so producers that need the current prompt contents read it from a
1710
+ * parameter rather than the shared {@link TurnContext}. Omitted by text-only
1711
+ * callers ({@link composeInjectorChain}) that drive the chain without a
1712
+ * message array.
1870
1713
  *
1871
1714
  * Injectors returning `null` are omitted from the result. The returned array
1872
1715
  * preserves ascending-`order` sort so downstream callers (notably
@@ -1875,12 +1718,11 @@ export interface RuntimeInjectionResult {
1875
1718
  */
1876
1719
  async function collectInjectorBlocks(
1877
1720
  ctx: TurnContext,
1721
+ runMessages?: Message[],
1878
1722
  ): Promise<InjectionBlock[]> {
1879
- const injectors = getInjectors();
1880
- if (injectors.length === 0) return [];
1881
1723
  const out: InjectionBlock[] = [];
1882
- for (const injector of injectors) {
1883
- const block = await injector.produce(ctx);
1724
+ for (const injector of getInjectorChain()) {
1725
+ const block = await injector.produce(ctx, runMessages);
1884
1726
  if (block) out.push(block);
1885
1727
  }
1886
1728
  return out;
@@ -2017,44 +1859,6 @@ export interface RuntimeInjectionOptions {
2017
1859
  channelCommandContext?: ChannelCommandContext | null;
2018
1860
  unifiedTurnContext?: string | null;
2019
1861
  voiceCallControlPrompt?: string | null;
2020
- pkbContext?: string | null;
2021
- pkbActive?: boolean;
2022
- /**
2023
- * Dense query vector surfaced from the graph memory retriever.
2024
- * When present together with `pkbActive`, used to run `searchPkbFiles`
2025
- * to surface relevance hints in the PKB system reminder. When missing,
2026
- * the reminder falls back to the flat static text.
2027
- */
2028
- pkbQueryVector?: number[];
2029
- /** Optional sparse vector accompanying `pkbQueryVector`. */
2030
- pkbSparseVector?: QdrantSparseVector;
2031
- /** Memory scope id used to filter PKB search results. */
2032
- pkbScopeId?: string;
2033
- /**
2034
- * The live conversation (or a minimal shape containing `messages`) used
2035
- * to compute which PKB paths are already "in context" and therefore
2036
- * suppressed from hint suggestions.
2037
- */
2038
- pkbConversation?: PkbContextConversation;
2039
- /** Auto-injected PKB filenames (resolved relative to `pkbRoot`). */
2040
- pkbAutoInjectList?: string[];
2041
- /** Absolute path to the PKB directory (e.g. `<workspace>/pkb`). */
2042
- pkbRoot?: string;
2043
- /**
2044
- * Working directory against which relative `file_read` tool paths
2045
- * resolve, used to detect workspace-relative reads like
2046
- * `pkb/threads.md`. Falls back to `pkbRoot` when omitted.
2047
- */
2048
- pkbWorkingDir?: string;
2049
- /**
2050
- * Pre-rendered v2 static memory content (essentials/threads/recent/buffer
2051
- * concatenated, header-wrapped). When non-null on full-mode turns the
2052
- * `memory-v2-static` injector wraps it in `<info>` and splices it onto
2053
- * the user message; subsequent turns leave the prior block cached on its
2054
- * original user message.
2055
- */
2056
- memoryV2Static?: string | null;
2057
- nowScratchpad?: string | null;
2058
1862
  subagentStatusBlock?: string | null;
2059
1863
  isNonInteractive?: boolean;
2060
1864
  /**
@@ -2101,20 +1905,6 @@ export interface RuntimeInjectionOptions {
2101
1905
  slackActiveThreadFocusBlock?: string | null;
2102
1906
  activeDocuments?: TurnInjectionInputs["activeDocuments"];
2103
1907
  mode?: InjectionMode;
2104
- /**
2105
- * memory-v3-live: when true AND the v3 injector produced a `<memory>` block
2106
- * this turn (placement `after-memory-prefix`, id `memory-v3`), the v2
2107
- * `<memory>` injection that `graphMemory.prepareMemory` prepended to the
2108
- * tail is stripped from EVERY user message before the v3 block is spliced —
2109
- * so v3 becomes the sole `<memory>` source and history stays byte-stable for
2110
- * prompt caching.
2111
- *
2112
- * The strip is keyed off whether v3 ACTUALLY produced a block, not off the
2113
- * flag alone: when v3 errors or selects nothing (its injector returns
2114
- * `null`), v2's block is left in place so the turn still ships memory rather
2115
- * than dropping it (fallback-to-v2). Default false — v2 untouched.
2116
- */
2117
- suppressV2MemoryForV3?: boolean;
2118
1908
  /**
2119
1909
  * Per-turn {@link TurnContext} forwarded to plugin-registered
2120
1910
  * {@link Injector}s via {@link collectInjectorBlocks}. When omitted,
@@ -2147,17 +1937,6 @@ function buildTurnInjectionInputs(
2147
1937
  diskPressureContext: options.diskPressureContext,
2148
1938
  workspaceTopLevelContext: options.workspaceTopLevelContext,
2149
1939
  unifiedTurnContext: options.unifiedTurnContext,
2150
- pkbContext: options.pkbContext,
2151
- pkbActive: options.pkbActive,
2152
- pkbQueryVector: options.pkbQueryVector,
2153
- pkbSparseVector: options.pkbSparseVector,
2154
- pkbScopeId: options.pkbScopeId,
2155
- pkbConversation: options.pkbConversation,
2156
- pkbAutoInjectList: options.pkbAutoInjectList,
2157
- pkbRoot: options.pkbRoot,
2158
- pkbWorkingDir: options.pkbWorkingDir,
2159
- memoryV2Static: options.memoryV2Static,
2160
- nowScratchpad: options.nowScratchpad,
2161
1940
  subagentStatusBlock: options.subagentStatusBlock,
2162
1941
  channelCapabilities: options.channelCapabilities,
2163
1942
  slackChronologicalMessages: options.slackChronologicalMessages,
@@ -2245,7 +2024,7 @@ export async function applyRuntimeInjections(
2245
2024
  ? { ...options.turnContext, injectionInputs }
2246
2025
  : synthesizeFallbackTurnContext(injectionInputs);
2247
2026
 
2248
- const chainBlocks = await collectInjectorBlocks(turnCtx);
2027
+ const chainBlocks = await collectInjectorBlocks(turnCtx, runMessages);
2249
2028
 
2250
2029
  // Split the chain output by placement so the downstream assembly can
2251
2030
  // process each slot with the correct ordering rule.
@@ -2325,21 +2104,25 @@ export async function applyRuntimeInjections(
2325
2104
  : undefined;
2326
2105
 
2327
2106
  // ── Step 0: memory-v3-live v2 suppression ──
2328
- // When v3 live mode is on AND the v3 injector actually produced a block this
2329
- // turn, v3 is the sole `<memory>` source. v2's `prepareMemory` already
2330
- // prepended its own `<memory>` block to the tail user message (and historical
2331
- // turns may carry v2 blocks from earlier turns). Strip every user message's
2332
- // memory prefix here so:
2107
+ // When the `memory-v3-live` flag is on AND the v3 injector actually produced
2108
+ // a block this turn, v3 is the sole `<memory>` source. v2's `prepareMemory`
2109
+ // already prepended its own `<memory>` block to the tail user message (and
2110
+ // historical turns may carry v2 blocks from earlier turns). Strip every user
2111
+ // message's memory prefix here so:
2333
2112
  // 1. The v3 `after-memory-prefix` block (Step 2) lands at the top of the
2334
2113
  // tail with no v2 prefix ahead of it — exactly one `<memory>` block.
2335
2114
  // 2. History is byte-stable across turns for prompt caching.
2336
2115
  // Keyed off the v3 block being present (not the flag alone) so a v3 failure
2337
2116
  // (`produce()` → null) leaves v2's block intact — fallback rather than a
2338
2117
  // memory-less turn. Idempotent: re-injection sites that already stripped
2339
- // see no change.
2118
+ // see no change. Flag off → bit-for-bit identical to the v2 path.
2119
+ const suppressV2MemoryForV3 = isAssistantFeatureFlagEnabled(
2120
+ "memory-v3-live",
2121
+ getConfig(),
2122
+ );
2340
2123
  const v3ProducedBlock = afterMemory.some((b) => b.id === MEMORY_V3_BLOCK_ID);
2341
2124
  let runMessagesForAssembly = runMessages;
2342
- if (options.suppressV2MemoryForV3 && v3ProducedBlock) {
2125
+ if (suppressV2MemoryForV3 && v3ProducedBlock) {
2343
2126
  runMessagesForAssembly = stripAllMemoryInjections(runMessages);
2344
2127
  }
2345
2128
 
@@ -14,43 +14,24 @@ import { getVisibleProviderCatalog } from "../providers/provider-catalog-visibil
14
14
  export type SlashResolution =
15
15
  | { kind: "passthrough"; content: string }
16
16
  | { kind: "unknown"; message: string }
17
- | { kind: "compact"; targetInputTokensOverride?: number }
17
+ | { kind: "compact" }
18
18
  | { kind: "clean" };
19
19
 
20
- const COMPACT_USAGE_HINT =
21
- "Usage: `/compact [<tokens>]` (e.g. `/compact 30000`, `/compact 30k`, `/compact 1m`).";
20
+ type CompactParse = { kind: "compact" } | { kind: "unknown"; message: string };
22
21
 
23
- type CompactParse =
24
- | { kind: "compact"; targetInputTokensOverride?: number }
25
- | { kind: "unknown"; message: string };
26
-
27
- const TOKEN_COUNT_PATTERN = /^(\d+(?:\.\d+)?)([km])?$/i;
28
22
  const COMPACT_COMMAND_PATTERN = /^\/compact(?:\s+(.+?))?\s*$/i;
29
23
 
30
- function parseTokenCount(input: string): number | null {
31
- const match = input.match(TOKEN_COUNT_PATTERN);
32
- if (!match) return null;
33
- const value = Number.parseFloat(match[1]);
34
- if (!Number.isFinite(value) || value <= 0) return null;
35
- const suffix = match[2]?.toLowerCase();
36
- const multiplier = suffix === "m" ? 1_000_000 : suffix === "k" ? 1_000 : 1;
37
- const tokens = Math.floor(value * multiplier);
38
- return tokens > 0 ? tokens : null;
39
- }
40
-
41
24
  function parseCompactCommand(trimmed: string): CompactParse | null {
42
25
  const match = trimmed.match(COMPACT_COMMAND_PATTERN);
43
26
  if (!match) return null;
44
27
  const rest = match[1]?.trim();
45
- if (!rest) return { kind: "compact" };
46
- const tokens = parseTokenCount(rest);
47
- if (tokens == null) {
28
+ if (rest) {
48
29
  return {
49
30
  kind: "unknown",
50
- message: `Unrecognized argument to \`/compact\`: \`${rest}\`. ${COMPACT_USAGE_HINT}`,
31
+ message: `\`/compact\` does not take arguments. Usage: \`/compact\`.`,
51
32
  };
52
33
  }
53
- return { kind: "compact", targetInputTokensOverride: tokens };
34
+ return { kind: "compact" };
54
35
  }
55
36
 
56
37
  type CleanParse = { kind: "clean" } | { kind: "unknown"; message: string };
@@ -448,7 +429,7 @@ export async function resolveSlash(
448
429
  return await resolveModelList();
449
430
  }
450
431
 
451
- // Handle /compact command (with optional `<tokens>` override).
432
+ // Handle /compact command (summarize history; takes no arguments).
452
433
  const compactParse = parseCompactCommand(trimmed);
453
434
  if (compactParse) return compactParse;
454
435