@vellumai/assistant 0.8.8 → 0.8.9-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/examples/plugins/echo/README.md +61 -60
  4. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  5. package/examples/plugins/echo/hooks/stop.ts +16 -0
  6. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  7. package/examples/plugins/echo/package.json +1 -2
  8. package/examples/plugins/echo/src/emit.ts +19 -0
  9. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  10. package/openapi.yaml +235 -6
  11. package/package.json +2 -2
  12. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  13. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  14. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  15. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  16. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  17. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  18. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  19. package/src/__tests__/agent-loop.test.ts +441 -96
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  21. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  22. package/src/__tests__/anthropic-provider.test.ts +1 -1
  23. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  24. package/src/__tests__/app-control-flow.test.ts +1 -1
  25. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  26. package/src/__tests__/app-executors.test.ts +132 -0
  27. package/src/__tests__/approval-cascade.test.ts +5 -4
  28. package/src/__tests__/approval-routes-http.test.ts +4 -1
  29. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  30. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  31. package/src/__tests__/channel-approvals.test.ts +1 -1
  32. package/src/__tests__/compaction-circuit.test.ts +258 -0
  33. package/src/__tests__/compaction-direct.test.ts +132 -0
  34. package/src/__tests__/compaction-events.test.ts +5 -5
  35. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  36. package/src/__tests__/context-overflow-reducer.test.ts +1 -1
  37. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
  38. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  39. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  40. package/src/__tests__/conversation-agent-loop-overflow.test.ts +142 -218
  41. package/src/__tests__/conversation-agent-loop.test.ts +297 -586
  42. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  43. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
  44. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  45. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  46. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
  47. package/src/__tests__/conversation-process-callsite.test.ts +14 -14
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +70 -65
  49. package/src/__tests__/conversation-queue.test.ts +9 -9
  50. package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
  51. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  52. package/src/__tests__/conversation-slash-queue.test.ts +6 -4
  53. package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
  54. package/src/__tests__/conversation-speed-override.test.ts +10 -9
  55. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  56. package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
  57. package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
  59. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  61. package/src/__tests__/db-acp-history.test.ts +101 -0
  62. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  63. package/src/__tests__/empty-response-hook.test.ts +1 -1
  64. package/src/__tests__/file-write-tool.test.ts +63 -0
  65. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  66. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  67. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  68. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  69. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  70. package/src/__tests__/heartbeat-service.test.ts +1 -0
  71. package/src/__tests__/history-repair-hook.test.ts +1 -1
  72. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  73. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  74. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  75. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  76. package/src/__tests__/injector-background-turn.test.ts +13 -23
  77. package/src/__tests__/injector-chain.test.ts +268 -44
  78. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  79. package/src/__tests__/injector-document-comments.test.ts +97 -114
  80. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  81. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  82. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  83. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  84. package/src/__tests__/memory-retrieval-hook.test.ts +131 -26
  85. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  86. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  87. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  88. package/src/__tests__/plugin-bootstrap.test.ts +12 -23
  89. package/src/__tests__/plugin-registry.test.ts +3 -49
  90. package/src/__tests__/plugin-types.test.ts +0 -70
  91. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  92. package/src/__tests__/reaction-persistence.test.ts +1 -1
  93. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  94. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  95. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  96. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  97. package/src/__tests__/subagent-detail.test.ts +25 -7
  98. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  99. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  100. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  101. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  102. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  103. package/src/__tests__/title-generate-hook.test.ts +1 -1
  104. package/src/__tests__/tool-error-hook.test.ts +1 -1
  105. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  106. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  107. package/src/acp/__tests__/agent-process.test.ts +161 -0
  108. package/src/acp/__tests__/client-handler.test.ts +40 -0
  109. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  110. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  111. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  112. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  113. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  114. package/src/acp/agent-process.ts +61 -1
  115. package/src/acp/auto-install.test.ts +280 -0
  116. package/src/acp/auto-install.ts +232 -0
  117. package/src/acp/client-handler.ts +31 -0
  118. package/src/acp/feature-gate.test.ts +48 -0
  119. package/src/acp/feature-gate.ts +34 -0
  120. package/src/acp/prepare-agent-env.ts +80 -27
  121. package/src/acp/resolve-agent.test.ts +225 -9
  122. package/src/acp/resolve-agent.ts +122 -17
  123. package/src/acp/resume-hint.ts +23 -0
  124. package/src/acp/session-manager.ts +507 -73
  125. package/src/agent/compaction-circuit.ts +60 -102
  126. package/src/agent/loop.ts +414 -248
  127. package/src/api/responses/conversation-message.ts +14 -1
  128. package/src/approvals/guardian-request-resolvers.ts +1 -1
  129. package/src/background-wake/next-wake.ts +1 -0
  130. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  131. package/src/cli/commands/plugins.ts +43 -37
  132. package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
  133. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  134. package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
  135. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
  136. package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
  137. package/src/cli/lib/install-from-github.ts +464 -55
  138. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  139. package/src/cli/lib/plugin-details.ts +409 -0
  140. package/src/cli/lib/plugin-marketplace.ts +197 -0
  141. package/src/cli/lib/search-plugins.ts +195 -29
  142. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  143. package/src/config/acp-defaults.test.ts +10 -0
  144. package/src/config/acp-defaults.ts +6 -0
  145. package/src/config/bundled-skills/acp/SKILL.md +85 -33
  146. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  147. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  148. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  149. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  150. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  151. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  152. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  153. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  154. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  155. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  156. package/src/config/bundled-tool-registry.ts +4 -0
  157. package/src/config/call-site-defaults.ts +0 -2
  158. package/src/config/feature-flag-registry.json +15 -6
  159. package/src/config/schemas/call-site-catalog.ts +0 -14
  160. package/src/config/schemas/heartbeat.ts +9 -0
  161. package/src/config/schemas/llm.ts +0 -2
  162. package/src/context/compactor.ts +22 -4
  163. package/src/context/strip-injections.ts +8 -2
  164. package/src/context/window-manager.ts +27 -13
  165. package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
  166. package/src/daemon/conversation-agent-loop.ts +175 -1055
  167. package/src/daemon/conversation-lifecycle.ts +11 -255
  168. package/src/daemon/conversation-process.ts +8 -136
  169. package/src/daemon/conversation-registry.ts +159 -0
  170. package/src/daemon/conversation-runtime-assembly.ts +293 -392
  171. package/src/daemon/conversation-store.ts +9 -90
  172. package/src/daemon/conversation-surfaces.ts +24 -8
  173. package/src/daemon/conversation-workspace.ts +17 -0
  174. package/src/daemon/conversation.ts +404 -57
  175. package/src/daemon/disk-pressure-policy.ts +0 -1
  176. package/src/daemon/external-plugins-bootstrap.ts +14 -19
  177. package/src/daemon/handlers/conversations.ts +3 -1
  178. package/src/daemon/handlers/skills.ts +4 -1
  179. package/src/daemon/host-proxy-preactivation.ts +1 -3
  180. package/src/daemon/lifecycle.ts +21 -7
  181. package/src/daemon/server.ts +2 -0
  182. package/src/daemon/wake-conversation-ops.ts +269 -0
  183. package/src/embedded/plugin-api.ts +2 -2
  184. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  185. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  186. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  187. package/src/heartbeat/heartbeat-service.ts +26 -0
  188. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  189. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  190. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  191. package/src/ipc/skill-routes/memory.ts +4 -2
  192. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  193. package/src/memory/conversation-crud.ts +29 -19
  194. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  195. package/src/memory/db-init.ts +2 -0
  196. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  197. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  198. package/src/memory/jobs-worker.ts +25 -1
  199. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  200. package/src/memory/migrations/index.ts +1 -0
  201. package/src/memory/schema/acp.ts +4 -0
  202. package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
  203. package/src/memory/v2/consolidation-job.ts +13 -4
  204. package/src/plugin-api/constants.ts +4 -0
  205. package/src/plugin-api/index.ts +6 -5
  206. package/src/plugin-api/types.ts +75 -0
  207. package/src/plugins/defaults/compaction/compact.ts +59 -0
  208. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +7 -7
  209. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  210. package/src/plugins/defaults/compaction/package.json +1 -2
  211. package/src/plugins/defaults/empty-response/package.json +0 -1
  212. package/src/plugins/defaults/history-repair/package.json +0 -1
  213. package/src/plugins/defaults/index.ts +135 -26
  214. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  215. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +184 -74
  216. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  217. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  218. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  219. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  220. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +9 -6
  221. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  222. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +8 -5
  223. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  224. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  225. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +10 -5
  226. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  227. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +5 -5
  228. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
  229. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
  230. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
  231. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
  232. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  233. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  234. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  235. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
  236. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
  237. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  238. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  239. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
  240. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
  241. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  242. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +5 -5
  243. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  244. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +7 -7
  245. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
  246. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  247. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -1
  248. package/src/plugins/defaults/title-generate/package.json +0 -1
  249. package/src/plugins/defaults/tool-error/package.json +0 -1
  250. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  251. package/src/plugins/pipeline.ts +6 -293
  252. package/src/plugins/registry.ts +9 -37
  253. package/src/plugins/types.ts +76 -381
  254. package/src/plugins/user-loader.ts +30 -127
  255. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  256. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
  257. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  258. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  259. package/src/runtime/agent-wake.ts +108 -209
  260. package/src/runtime/assistant-event-hub.ts +1 -1
  261. package/src/runtime/channel-approvals.ts +1 -1
  262. package/src/runtime/interactive-ui.ts +1 -1
  263. package/src/runtime/routes/__tests__/acp-routes.test.ts +315 -55
  264. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  265. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  266. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  267. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  268. package/src/runtime/routes/acp-routes.test.ts +89 -25
  269. package/src/runtime/routes/acp-routes.ts +81 -29
  270. package/src/runtime/routes/approval-routes.ts +1 -1
  271. package/src/runtime/routes/browser-routes.ts +1 -1
  272. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  273. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  274. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  275. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  276. package/src/runtime/routes/conversation-routes.ts +28 -2
  277. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  278. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  279. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  280. package/src/runtime/routes/host-cu-routes.ts +1 -1
  281. package/src/runtime/routes/identity-routes.ts +18 -3
  282. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  283. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  284. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  285. package/src/runtime/routes/memory-v3-routes.ts +16 -6
  286. package/src/runtime/routes/playground/helpers.ts +1 -1
  287. package/src/runtime/routes/plugins-routes.ts +337 -35
  288. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  289. package/src/runtime/routes/work-items-routes.ts +2 -4
  290. package/src/runtime/services/conversation-serializer.ts +1 -1
  291. package/src/signals/cancel.ts +2 -4
  292. package/src/subagent/manager.ts +21 -5
  293. package/src/tools/acp/context.ts +20 -0
  294. package/src/tools/acp/list-agents.test.ts +8 -2
  295. package/src/tools/acp/spawn.test.ts +176 -195
  296. package/src/tools/acp/spawn.ts +37 -172
  297. package/src/tools/acp/steer.test.ts +105 -8
  298. package/src/tools/acp/steer.ts +48 -17
  299. package/src/tools/apps/executors.ts +166 -50
  300. package/src/tools/filesystem/write.ts +34 -0
  301. package/src/tools/subagent/spawn.ts +2 -4
  302. package/src/tools/ui-surface/definitions.ts +25 -5
  303. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  304. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  305. package/docs/plugins.md +0 -832
  306. package/examples/plugins/echo/register.ts +0 -143
  307. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  308. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  309. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  310. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  311. package/src/__tests__/pipeline-runner.test.ts +0 -554
  312. package/src/__tests__/plugin-external-api.test.ts +0 -68
  313. package/src/daemon/wake-target-adapter.ts +0 -253
  314. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  315. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  316. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  317. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  318. package/src/plugins/defaults/compaction/register.ts +0 -35
  319. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  320. package/src/plugins/defaults/empty-response/register.ts +0 -23
  321. package/src/plugins/defaults/history-repair/register.ts +0 -24
  322. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  323. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  324. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  325. package/src/plugins/defaults/title-generate/register.ts +0 -35
  326. package/src/plugins/defaults/tool-error/register.ts +0 -23
  327. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  328. package/src/plugins/external-api.ts +0 -104
  329. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  330. package/src/proactive-artifact/decision.test.ts +0 -226
  331. package/src/proactive-artifact/decision.ts +0 -165
  332. package/src/proactive-artifact/index.ts +0 -7
  333. package/src/proactive-artifact/job.test.ts +0 -962
  334. package/src/proactive-artifact/job.ts +0 -372
  335. package/src/proactive-artifact/message-copy.ts +0 -58
  336. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  337. package/src/proactive-artifact/trigger-state.ts +0 -123
  338. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  339. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  340. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  341. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  342. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +0 -0
  343. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  344. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/provider-blocks.test.ts +0 -0
  345. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  346. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  347. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  348. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  349. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  350. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  351. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  352. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  353. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  354. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  355. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  356. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  357. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
  358. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  359. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  360. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -33,12 +33,12 @@ import { z } from "zod";
33
33
  import {
34
34
  extractToolUse,
35
35
  getConfiguredProvider,
36
- } from "../../providers/provider-send-message.js";
37
- import type { Message, ToolDefinition } from "../../providers/types.js";
38
- import { getLogger } from "../../util/logger.js";
36
+ } from "../../../providers/provider-send-message.js";
37
+ import type { Message, ToolDefinition } from "../../../providers/types.js";
38
+ import { getLogger } from "../../../util/logger.js";
39
39
  import { retryForResult } from "./llm-retry.js";
40
40
  import { cachedTextBlock } from "./provider-blocks.js";
41
- import type { LeafPath, LeafTree, TurnContext } from "./types.js";
41
+ import type { LeafPath, LeafTree, MemoryRoutingTurn } from "./types.js";
42
42
 
43
43
  const log = getLogger("memory-v3-router");
44
44
 
@@ -117,7 +117,7 @@ export function renderLeafBlock(tree: LeafTree): string {
117
117
  * degrading to the deterministic recall lanes the orchestrator unions in.
118
118
  */
119
119
  export async function routeL1(
120
- turn: TurnContext,
120
+ turn: MemoryRoutingTurn,
121
121
  tree: LeafTree,
122
122
  ): Promise<LeafPath[]> {
123
123
  const paths = sortedLeaves(tree);
@@ -7,10 +7,10 @@
7
7
  * across turns) and can't be reproduced after the fact.
8
8
  */
9
9
 
10
- import type { MemoryV3SelectionLog } from "../../api/responses/memory-v3-selection-log.js";
11
- import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
12
- import { getConfig } from "../../config/loader.js";
13
- import { getDb, getSqliteFrom } from "../db-connection.js";
10
+ import type { MemoryV3SelectionLog } from "../../../api/responses/memory-v3-selection-log.js";
11
+ import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
12
+ import { getConfig } from "../../../config/loader.js";
13
+ import { getDb, getSqliteFrom } from "../../../memory/db-connection.js";
14
14
  import { renderV3PageContent } from "./page-content.js";
15
15
  import { renderMemoryBlock } from "./render-injection.js";
16
16
  import type { Slug } from "./types.js";
@@ -33,14 +33,14 @@ import { z } from "zod";
33
33
  import {
34
34
  extractToolUse,
35
35
  getConfiguredProvider,
36
- } from "../../providers/provider-send-message.js";
37
- import type { Message, ToolDefinition } from "../../providers/types.js";
38
- import { getLogger } from "../../util/logger.js";
39
- import { mapLimit } from "../../util/map-limit.js";
36
+ } from "../../../providers/provider-send-message.js";
37
+ import type { Message, ToolDefinition } from "../../../providers/types.js";
38
+ import { getLogger } from "../../../util/logger.js";
39
+ import { mapLimit } from "../../../util/map-limit.js";
40
40
  import { retryForResult } from "./llm-retry.js";
41
41
  import { cachedTextBlock } from "./provider-blocks.js";
42
42
  import { membersOf } from "./tree.js";
43
- import type { LeafPath, LeafTree, Slug, TurnContext } from "./types.js";
43
+ import type { LeafPath, LeafTree, MemoryRoutingTurn, Slug } from "./types.js";
44
44
 
45
45
  const log = getLogger("memory-v3-selector");
46
46
 
@@ -122,7 +122,7 @@ async function renderPagesBlock(
122
122
  */
123
123
  export async function selectFromLeaf(
124
124
  leaf: LeafPath,
125
- turn: TurnContext,
125
+ turn: MemoryRoutingTurn,
126
126
  tree: LeafTree,
127
127
  pageSummary: (slug: Slug) => Promise<string>,
128
128
  ): Promise<SelectedPage[]> {
@@ -214,7 +214,7 @@ export async function selectFromLeaf(
214
214
  */
215
215
  export async function selectAcrossLeaves(
216
216
  leaves: LeafPath[],
217
- turn: TurnContext,
217
+ turn: MemoryRoutingTurn,
218
218
  tree: LeafTree,
219
219
  pageSummary: (slug: Slug) => Promise<string>,
220
220
  concurrency = 4,
@@ -1,75 +1,66 @@
1
1
  /**
2
- * Memory v3 — flag-gated shadow/live injector.
2
+ * Memory v3 — flag-gated shadow/live orchestration engine.
3
3
  *
4
- * An {@link Injector} that runs the v3 orchestrator each turn and
5
- * records its selection set to `memory_v3_selections`. Two flags gate its
6
- * injection behavior:
4
+ * Runs the v3 orchestrator each turn and records its selection set to
5
+ * `memory_v3_selections`. Two flags gate behavior:
7
6
  *
8
- * - `memory-v3-shadow` (and live OFF): observation-only. `produce()` returns
9
- * `null`, so v2 injection is bit-for-bit identical whether the flag is on
10
- * or off — the only difference is the side-effect telemetry write.
11
- * - `memory-v3-live`: live injection. `produce()` additionally renders the
12
- * working-set selection into a `<memory>` block via {@link renderMemoryBlock}
13
- * and returns it as an injection block at v2's dynamic-memory placement
7
+ * - `memory-v3-shadow` (live OFF): observation-only. {@link observeTurn}
8
+ * orchestrates and logs the selection set; no injection is produced, so v2
9
+ * injection is bit-for-bit identical — the only difference is the
10
+ * side-effect telemetry write.
11
+ * - `memory-v3-live`: live injection. The injector (`memoryV3Injector` in
12
+ * `./injector.ts`) additionally renders the working-set selection into a
13
+ * `<memory>` block and returns it at v2's dynamic-memory placement
14
14
  * (`after-memory-prefix`). Selections are still logged.
15
- * - both OFF: `produce()` returns `null` and skips orchestration entirely.
15
+ * - both OFF: orchestration is skipped entirely.
16
16
  *
17
17
  * On each turn (either flag on):
18
18
  * 1. Lazy-init the v3 lanes ONCE across the whole process (leaf tree, core
19
19
  * set, BM25 needle, carry-forward working set), memoizing the init
20
20
  * promise so concurrent first turns share a single build.
21
- * 2. Build a {@link TurnContext} from the conversation's recent messages.
21
+ * 2. Build a {@link MemoryRoutingTurn} from the conversation's recent messages.
22
22
  * 3. Run {@link orchestrate} and record its selection set to
23
23
  * `memory_v3_selections` with a best-effort lane attribution.
24
24
  *
25
- * Everything after the flag read is wrapped in try/catch — any failure is
26
- * logged and swallowed so it can never affect the live turn. In live mode a
27
- * failure returns `null` (no v3 injection); v2 suppression keys off BOTH the
28
- * flag AND whether `produce()` actually returned a block, so a v3 failure (or
29
- * empty selection) falls back to v2 memory rather than dropping all memory.
25
+ * {@link observeTurn} wraps everything after the flag read in try/catch — any
26
+ * failure is logged and swallowed so it can never affect the live turn. The
27
+ * injector treats a `null`/empty result as "no v3 injection", so v2 memory
28
+ * remains the fallback rather than dropping all memory.
30
29
  */
31
30
 
32
31
  import { existsSync, readFileSync } from "node:fs";
33
32
 
34
- import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
35
- import { getConfig } from "../../config/loader.js";
36
- import type { AssistantConfig } from "../../config/schema.js";
37
- import { getMessages } from "../../memory/conversation-crud.js";
38
- import { getDb, getSqliteFrom } from "../../memory/db-connection.js";
39
- import { stringifyMessageContent } from "../../memory/message-content.js";
40
- import {
41
- type InjectionBlock,
42
- type Injector,
43
- type TurnContext as PluginTurnContext,
44
- } from "../../plugins/types.js";
45
- import { getLogger } from "../../util/logger.js";
33
+ import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
34
+ import { getConfig } from "../../../config/loader.js";
35
+ import type { AssistantConfig } from "../../../config/schema.js";
36
+ import { getMessages } from "../../../memory/conversation-crud.js";
37
+ import { getDb, getSqliteFrom } from "../../../memory/db-connection.js";
38
+ import { stringifyMessageContent } from "../../../memory/message-content.js";
39
+ import { getPageIndex } from "../../../memory/v2/page-index.js";
40
+ import { getLogger } from "../../../util/logger.js";
46
41
  import {
47
42
  getWorkspaceDir,
48
43
  getWorkspacePromptPath,
49
- } from "../../util/platform.js";
50
- import { stripCommentLines } from "../../util/strip-comment-lines.js";
51
- import { getPageIndex } from "../v2/page-index.js";
44
+ } from "../../../util/platform.js";
45
+ import { stripCommentLines } from "../../../util/strip-comment-lines.js";
52
46
  import { injectCapabilitiesLeaf, isCapabilitySlug } from "./capabilities.js";
53
47
  import { loadCore } from "./core.js";
54
48
  import type { NeedleIndex } from "./needle.js";
55
49
  import { buildNeedleIndex } from "./needle.js";
56
50
  import type { OrchestrateResult } from "./orchestrate.js";
57
51
  import { orchestrate } from "./orchestrate.js";
58
- import { renderV3PageContent } from "./page-content.js";
59
- import { renderMemoryBlock } from "./render-injection.js";
60
52
  import { coreSlugs, loadLeafTree, resolveDataDir } from "./tree.js";
61
53
  import {
62
54
  type LeafPath,
63
55
  type LeafTree,
64
- MEMORY_V3_BLOCK_ID,
56
+ type MemoryRoutingTurn,
65
57
  type SelectionSource,
66
58
  type Slug,
67
- type TurnContext,
68
59
  } from "./types.js";
69
60
  import { WorkingSet } from "./working-set.js";
70
61
 
71
- const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
72
- const MEMORY_V3_LIVE = "memory-v3-live" as const;
62
+ export const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
63
+ export const MEMORY_V3_LIVE = "memory-v3-live" as const;
73
64
 
74
65
  const log = getLogger("memory-v3-shadow");
75
66
 
@@ -202,7 +193,7 @@ function buildSituationalContext(): string {
202
193
  }
203
194
 
204
195
  /**
205
- * Build a v3 {@link TurnContext} from the conversation's persisted messages.
196
+ * Build a v3 {@link MemoryRoutingTurn} from the conversation's persisted messages.
206
197
  * `currentMessage` is the latest user message; `recentContext` is the tail of
207
198
  * the recent transcript; `situationalContext` carries the current date and the
208
199
  * live NOW.md scratchpad. Returns `null` when there is no user message to route
@@ -211,7 +202,7 @@ function buildSituationalContext(): string {
211
202
  function buildShadowTurn(
212
203
  conversationId: string,
213
204
  turnIndex: number,
214
- ): TurnContext | null {
205
+ ): MemoryRoutingTurn | null {
215
206
  const rows = getMessages(conversationId);
216
207
  if (rows.length === 0) return null;
217
208
 
@@ -310,7 +301,7 @@ function writeSelections(
310
301
  * failures are logged and swallowed (returning `null`) so the live turn is
311
302
  * unaffected. Returns `null` when there is no user message to route on.
312
303
  */
313
- async function observeTurn(
304
+ export async function observeTurn(
314
305
  conversationId: string,
315
306
  turnIndex: number,
316
307
  ): Promise<OrchestrateResult | null> {
@@ -356,56 +347,3 @@ export async function runShadowObservation(
356
347
  if (!isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, getConfig())) return;
357
348
  await observeTurn(conversationId, turnIndex);
358
349
  }
359
-
360
- /**
361
- * The v3 injector. Reads both flags:
362
- * - `memory-v3-live` on → orchestrate, log, render the working-set selection
363
- * into a `<memory>` block, and return it at v2's dynamic-memory placement.
364
- * - `memory-v3-shadow` on (live off) → orchestrate + log only, return `null`.
365
- * - both off → return `null` (no orchestration).
366
- *
367
- * Empty selection and any failure return `null` (no v3 injection). v2
368
- * suppression keys off BOTH the flag AND this return value, so a `null` here
369
- * (failure or empty selection) falls back to v2 memory rather than dropping all
370
- * memory.
371
- */
372
- export const memoryV3Injector: Injector = {
373
- name: "memory-v3-shadow",
374
- // High order so it sorts last; the live `<memory>` block uses the
375
- // after-memory-prefix placement so it lands at the memory boundary regardless
376
- // of this sort key, which only orders content-producing injectors.
377
- order: 1000,
378
- async produce(ctx: PluginTurnContext): Promise<InjectionBlock | null> {
379
- const config = getConfig();
380
- const live = isAssistantFeatureFlagEnabled(MEMORY_V3_LIVE, config);
381
- const shadow = isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, config);
382
- if (!live && !shadow) return null;
383
-
384
- const result = await observeTurn(ctx.conversationId, ctx.turnIndex);
385
- if (!live || !result) return null;
386
-
387
- try {
388
- // `renderMemoryBlock` returns "" for an empty selection; inject nothing.
389
- const text = await renderMemoryBlock(
390
- result.finalInjection,
391
- renderV3PageContent,
392
- );
393
- if (text.length === 0) return null;
394
- return {
395
- id: MEMORY_V3_BLOCK_ID,
396
- text,
397
- // Mirror v2's dynamic `<memory>` block placement.
398
- placement: "after-memory-prefix",
399
- };
400
- } catch (err) {
401
- log.warn(
402
- {
403
- err: err instanceof Error ? err.message : String(err),
404
- conversationId: ctx.conversationId,
405
- },
406
- "memory-v3 live render failed (non-fatal) — falling back to v2",
407
- );
408
- return null;
409
- }
410
- },
411
- };
@@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url";
5
5
 
6
6
  import { parse as parseYaml } from "yaml";
7
7
 
8
- import { getWorkspaceDir } from "../../util/platform.js";
8
+ import { getWorkspaceDir } from "../../../util/platform.js";
9
9
  import type {
10
10
  LeafFrontmatter,
11
11
  LeafNode,
@@ -41,7 +41,7 @@ export interface WorkingSetEntry {
41
41
  lastSeenTurn: number;
42
42
  }
43
43
 
44
- export interface TurnContext {
44
+ export interface MemoryRoutingTurn {
45
45
  conversationId: string;
46
46
  turnNumber: number;
47
47
  currentMessage: string;
@@ -5,7 +5,6 @@
5
5
  "private": true,
6
6
  "license": "MIT",
7
7
  "type": "module",
8
- "main": "./register.ts",
9
8
  "engines": {
10
9
  "node": ">=20.12.0"
11
10
  },
@@ -5,7 +5,6 @@
5
5
  "private": true,
6
6
  "license": "MIT",
7
7
  "type": "module",
8
- "main": "./register.ts",
9
8
  "engines": {
10
9
  "node": ">=20.12.0"
11
10
  },
@@ -5,7 +5,6 @@
5
5
  "private": true,
6
6
  "license": "MIT",
7
7
  "type": "module",
8
- "main": "./register.ts",
9
8
  "engines": {
10
9
  "node": ">=20.12.0"
11
10
  },
@@ -1,297 +1,16 @@
1
1
  /**
2
- * Plugin pipeline runner.
2
+ * Plugin hook runner.
3
3
  *
4
- * A "pipeline" is a named chain of {@link Middleware}s wrapped around a
5
- * terminal handler (the original behavior that existed before plugins). The
6
- * runner composes the chain in onion order, runs it under an optional
7
- * per-pipeline timeout, and emits one structured log record per invocation
8
- * covering success, error, and timeout uniformly.
4
+ * A "hook" is a named lifecycle event (`user-prompt-submit`, `post-tool-use`,
5
+ * …) that every registered plugin may handle. The runner walks each plugin's
6
+ * hook for a given event in registration order, threading a context value
7
+ * through the chain so hooks can observe and transform it.
9
8
  *
10
- * Design doc: `.private/plans/agent-plugin-system.md` (PR 12 notes).
11
- *
12
- * Semantics:
13
- * - Onion composition — the first element of `middlewares` is the outermost
14
- * wrapper; it sees the request first and the response last. Terminal runs
15
- * in the middle.
16
- * - Strict-fail — there is NO `try/catch` around user middleware. Errors,
17
- * including `PluginTimeoutError` from a breached budget, propagate to the
18
- * caller. The `finally` block only handles logging, never error recovery.
19
- * - Timeout — when `timeoutMs` is a finite number, the invocation races a
20
- * timer. A timeout rejection is a `PluginTimeoutError` carrying the
21
- * pipeline name, the best-known offending plugin (from `ctx.pluginName`),
22
- * and elapsed ms. When `timeoutMs` is `null`/`undefined`, no timer is
23
- * armed — pipelines like `compaction` rely on downstream timeouts instead.
9
+ * Design doc: `.private/plans/agent-plugin-system.md`.
24
10
  */
25
11
 
26
- import type { Logger } from "pino";
27
-
28
12
  import type { HookName } from "../plugin-api/constants.js";
29
- import { getLogger } from "../util/logger.js";
30
13
  import { getHooksFor } from "./registry.js";
31
- import {
32
- type Middleware,
33
- type PipelineName,
34
- PluginTimeoutError,
35
- type TurnContext,
36
- } from "./types.js";
37
-
38
- const moduleLogger = getLogger("plugin-pipeline");
39
-
40
- // ─── Default timeouts ───────────────────────────────────────────────────────
41
-
42
- /**
43
- * Default per-pipeline timeout budgets in milliseconds. A value of `null`
44
- * means the runner does NOT arm a timer — the pipeline relies on its
45
- * downstream for budgeting (e.g. `compaction` defers to the provider-level
46
- * HTTP timeouts of the summary call it wraps).
47
- *
48
- * Callers pass the appropriate entry as `runPipeline`'s `timeoutMs` argument.
49
- * The design doc locks these numbers in; do not tweak without coordinating
50
- * a design update.
51
- */
52
- export const DEFAULT_TIMEOUTS: Record<PipelineName, number | null> = {
53
- compaction: null,
54
- overflowReduce: null,
55
- circuitBreaker: null,
56
- };
57
-
58
- // ─── Composition ────────────────────────────────────────────────────────────
59
-
60
- /**
61
- * Compose an ordered list of {@link Middleware}s around a terminal handler
62
- * using onion semantics. The first element of `middlewares` is the outermost
63
- * layer.
64
- *
65
- * The returned function accepts `(args, ctx)` and runs the entire chain:
66
- * ```
67
- * middlewares[0] → middlewares[1] → ... → terminal → ... → middlewares[1] → middlewares[0]
68
- * ```
69
- *
70
- * Middlewares that never call `next` short-circuit the chain — the terminal
71
- * and any deeper middleware are never invoked. Middlewares that throw abort
72
- * the chain; the error flows back out through any outer middleware unchanged
73
- * (no internal try/catch).
74
- */
75
- export function composeMiddleware<A, R>(
76
- middlewares: ReadonlyArray<Middleware<A, R>>,
77
- terminal: (args: A) => Promise<R>,
78
- ): (args: A, ctx: TurnContext) => Promise<R> {
79
- return (args, ctx) => {
80
- // Walk back-to-front, wrapping each middleware around `next`. The last
81
- // middleware in the array wraps `terminal`; earlier entries wrap the
82
- // accumulated chain so they execute first.
83
- let next: (args: A) => Promise<R> = terminal;
84
- for (let i = middlewares.length - 1; i >= 0; i--) {
85
- const mw = middlewares[i]!;
86
- const downstream = next;
87
- next = (innerArgs: A) => mw(innerArgs, downstream, ctx);
88
- }
89
- return next(args);
90
- };
91
- }
92
-
93
- // ─── Abort-signal linking ───────────────────────────────────────────────────
94
-
95
- /**
96
- * Return a shallow-cloned `args` object where every `AbortSignal`-typed
97
- * top-level property is swapped for a signal linked to `internalController`.
98
- *
99
- * "Linked" here means: the returned signal aborts when either the caller's
100
- * original signal aborts OR `internalController` aborts (e.g. the pipeline
101
- * timer fires). The caller's args are never mutated.
102
- *
103
- * When `args` carries no `AbortSignal` property, the original object is
104
- * returned unchanged — pipelines whose terminals don't consume a signal
105
- * (e.g. `circuitBreaker`) see identical behavior to before.
106
- * The return value's `cleanup()` tears down any `addEventListener("abort",
107
- * ...)` handlers attached to the caller's signal so a pipeline that
108
- * completes successfully doesn't leak listeners on the caller's controller.
109
- */
110
- function linkAbortSignal<A>(
111
- args: A,
112
- internalController: AbortController,
113
- ): { args: A; cleanup: () => void } {
114
- if (args === null || typeof args !== "object") {
115
- return { args, cleanup: () => {} };
116
- }
117
- const abortListeners: Array<{
118
- signal: AbortSignal;
119
- listener: () => void;
120
- }> = [];
121
- const record = args as Record<string, unknown>;
122
- const patched: Record<string, unknown> = { ...record };
123
- let swappedAny = false;
124
- for (const key of Object.keys(record)) {
125
- const value = record[key];
126
- if (value instanceof AbortSignal) {
127
- swappedAny = true;
128
- if (value.aborted) {
129
- // Caller already aborted — propagate immediately so the inner call
130
- // sees the abort without waiting for the listener to fire.
131
- internalController.abort();
132
- } else {
133
- const listener = () => internalController.abort();
134
- value.addEventListener("abort", listener, { once: true });
135
- abortListeners.push({ signal: value, listener });
136
- }
137
- patched[key] = internalController.signal;
138
- }
139
- }
140
- const cleanup = () => {
141
- for (const { signal, listener } of abortListeners) {
142
- signal.removeEventListener("abort", listener);
143
- }
144
- };
145
- return { args: (swappedAny ? patched : args) as A, cleanup };
146
- }
147
-
148
- // ─── Runner ─────────────────────────────────────────────────────────────────
149
-
150
- type PipelineLogRecord = {
151
- event: "plugin.pipeline";
152
- pipeline: PipelineName;
153
- chain: ReadonlyArray<string>;
154
- durationMs: number;
155
- outcome: "success" | "error" | "timeout";
156
- pluginName?: string;
157
- errorName?: string;
158
- errorMessage?: string;
159
- errorStack?: string;
160
- timeoutMs?: number;
161
- requestId: string;
162
- conversationId: string;
163
- turnIndex?: number;
164
- };
165
-
166
- /**
167
- * Best-effort detection of a pino-compatible logger on the `TurnContext`.
168
- *
169
- * `TurnContext` intentionally doesn't require a logger slot — most code paths
170
- * use the module-level logger. Some callers (notably plugin-scoped pipelines
171
- * in later PRs) may attach a child logger via `(ctx as any).logger`. We type
172
- * the field loosely to avoid coupling `TurnContext` to pino.
173
- */
174
- function selectLogger(ctx: TurnContext): Logger {
175
- const maybe = (ctx as { logger?: unknown }).logger;
176
- if (maybe && typeof maybe === "object" && "info" in maybe) {
177
- return maybe as Logger;
178
- }
179
- return moduleLogger;
180
- }
181
-
182
- /**
183
- * Execute a named pipeline: compose middleware, run it under an optional
184
- * timeout, and emit one structured log record covering success, error, or
185
- * timeout uniformly.
186
- *
187
- * @param name The pipeline identifier. Used for telemetry and error
188
- * attribution.
189
- * @param middlewares Ordered list of middlewares to wrap around `terminal`.
190
- * Names for the log `chain` are pulled from `Function.name`
191
- * (empty strings are kept so length always matches).
192
- * @param terminal The original behavior. Runs when all middlewares call
193
- * `next`.
194
- * @param args Pipeline-specific input payload.
195
- * @param ctx Per-turn context. May carry an optional `logger` slot;
196
- * otherwise the module logger is used.
197
- * @param timeoutMs Deadline in milliseconds. `null`/`undefined` disables
198
- * the timer entirely (inherits downstream timeouts).
199
- *
200
- * @throws {PluginTimeoutError} When `timeoutMs` is non-null and the chain
201
- * exceeds the budget. `ctx.pluginName` (if set) is attached as the
202
- * offending plugin.
203
- * @throws Any error produced by user middleware or the terminal — flows
204
- * through unchanged.
205
- */
206
- export async function runPipeline<A, R>(
207
- name: PipelineName,
208
- middlewares: ReadonlyArray<Middleware<A, R>>,
209
- terminal: (args: A) => Promise<R>,
210
- args: A,
211
- ctx: TurnContext,
212
- timeoutMs?: number | null,
213
- ): Promise<R> {
214
- const logger = selectLogger(ctx);
215
- const chain = middlewares.map((m) => m.name || "anonymous");
216
- const composed = composeMiddleware(middlewares, terminal);
217
- const start = performance.now();
218
-
219
- let outcome: "success" | "error" | "timeout" = "success";
220
- let thrown: unknown = undefined;
221
- let timer: ReturnType<typeof setTimeout> | undefined;
222
-
223
- try {
224
- if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
225
- const budget = timeoutMs;
226
- // Internal controller: fires on either (a) the timer, so the inner call
227
- // actually observes the budget breach instead of running forever after
228
- // `Promise.race` rejects; or (b) the caller's own signal, so external
229
- // cancellation still reaches the inner call transparently. Any
230
- // `AbortSignal`-typed property on `args` (e.g. `signal` on
231
- // `CompactionArgs`, `abortSignal` on `OverflowReduceArgs`) is swapped
232
- // for this linked signal on a shallow-cloned args object — we never
233
- // mutate the caller's args.
234
- const internalController = new AbortController();
235
- const { args: effectiveArgs, cleanup } = linkAbortSignal(
236
- args,
237
- internalController,
238
- );
239
- const timeoutPromise = new Promise<never>((_resolve, reject) => {
240
- timer = setTimeout(() => {
241
- internalController.abort();
242
- reject(
243
- new PluginTimeoutError(
244
- name,
245
- ctx.pluginName,
246
- Math.round(performance.now() - start),
247
- ),
248
- );
249
- }, budget);
250
- });
251
- try {
252
- return await Promise.race([
253
- composed(effectiveArgs, ctx),
254
- timeoutPromise,
255
- ]);
256
- } finally {
257
- cleanup();
258
- }
259
- }
260
- return await composed(args, ctx);
261
- } catch (err) {
262
- thrown = err;
263
- outcome = err instanceof PluginTimeoutError ? "timeout" : "error";
264
- throw err;
265
- } finally {
266
- if (timer !== undefined) clearTimeout(timer);
267
- const durationMs = Math.round(performance.now() - start);
268
- const record: PipelineLogRecord = {
269
- event: "plugin.pipeline",
270
- pipeline: name,
271
- chain,
272
- durationMs,
273
- outcome,
274
- requestId: ctx.requestId,
275
- conversationId: ctx.conversationId,
276
- };
277
- if (ctx.turnIndex !== undefined) record.turnIndex = ctx.turnIndex;
278
- if (ctx.pluginName !== undefined) record.pluginName = ctx.pluginName;
279
- if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
280
- record.timeoutMs = timeoutMs;
281
- }
282
- if (thrown !== undefined) {
283
- if (thrown instanceof Error) {
284
- record.errorName = thrown.name;
285
- record.errorMessage = thrown.message;
286
- if (thrown.stack) record.errorStack = thrown.stack;
287
- } else {
288
- record.errorName = "NonError";
289
- record.errorMessage = String(thrown);
290
- }
291
- }
292
- logger.info(record, "plugin.pipeline");
293
- }
294
- }
295
14
 
296
15
  // ─── Hook runner ────────────────────────────────────────────────────────────
297
16
 
@@ -302,12 +21,6 @@ export async function runPipeline<A, R>(
302
21
  * that replaces the threaded value for subsequent hooks. The final context
303
22
  * after the chain settles is returned.
304
23
  *
305
- * `runHook` is the hook-side counterpart to {@link runPipeline}:
306
- * `runPipeline` composes middleware around a terminal handler for stateful
307
- * request/response pipelines (compaction, overflow reduction, etc.);
308
- * `runHook` walks ordered hook functions for declarative chain-style
309
- * context transformations (`user-prompt-submit`, `post-tool-use`).
310
- *
311
24
  * @param name The hook identifier — pick one from {@link HOOKS}.
312
25
  * @param initialCtx Context the first hook receives.
313
26
  * @returns The final context after the chain settles. Same reference as