@vellumai/assistant 0.8.7-dev.202606052232.2ddc989 → 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 (262) hide show
  1. package/bun.lock +2 -2
  2. package/docs/plugins.md +832 -0
  3. package/examples/plugins/echo/README.md +60 -61
  4. package/examples/plugins/echo/package.json +2 -1
  5. package/examples/plugins/echo/register.ts +143 -0
  6. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +6 -7
  7. package/openapi.yaml +5 -15
  8. package/package.json +2 -2
  9. package/src/__tests__/agent-loop-exit-reason.test.ts +56 -3
  10. package/src/__tests__/anthropic-provider.test.ts +1 -1
  11. package/src/__tests__/app-control-flow.test.ts +1 -1
  12. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  13. package/src/__tests__/approval-routes-http.test.ts +1 -4
  14. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  15. package/src/__tests__/channel-approvals.test.ts +1 -1
  16. package/src/__tests__/circuit-breaker-pipeline.test.ts +405 -0
  17. package/src/__tests__/compaction-pipeline.test.ts +210 -0
  18. package/src/__tests__/compaction-timeout-recovery.test.ts +251 -0
  19. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +3 -0
  20. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +3 -0
  21. package/src/__tests__/conversation-agent-loop-overflow.test.ts +7 -3
  22. package/src/__tests__/conversation-agent-loop.test.ts +39 -42
  23. package/src/__tests__/conversation-clean-command.test.ts +2 -5
  24. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -4
  25. package/src/__tests__/conversation-runtime-assembly.test.ts +71 -140
  26. package/src/__tests__/conversation-runtime-workspace.test.ts +27 -108
  27. package/src/__tests__/conversation-starter-routes.test.ts +6 -14
  28. package/src/__tests__/conversation-workspace-cache-state.test.ts +16 -17
  29. package/src/__tests__/conversation-workspace-injection.test.ts +1 -61
  30. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -7
  31. package/src/__tests__/db-acp-history.test.ts +0 -101
  32. package/src/__tests__/dynamic-page-surface.test.ts +0 -31
  33. package/src/__tests__/file-write-tool.test.ts +0 -63
  34. package/src/__tests__/gateway-only-guard.test.ts +2 -12
  35. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  36. package/src/__tests__/guardian-routing-invariants.test.ts +4 -2
  37. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  38. package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -1
  39. package/src/__tests__/heartbeat-service.test.ts +0 -1
  40. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  41. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  42. package/src/__tests__/injector-background-turn.test.ts +1 -1
  43. package/src/__tests__/injector-chain.test.ts +6 -34
  44. package/src/__tests__/injector-disk-pressure.test.ts +34 -77
  45. package/src/__tests__/injector-document-comments.test.ts +1 -1
  46. package/src/__tests__/list-messages-hidden-metadata.test.ts +0 -38
  47. package/src/__tests__/memory-v2-static-injector.test.ts +1 -1
  48. package/src/__tests__/{overflow-reduction-loop.test.ts → overflow-reduce-pipeline.test.ts} +284 -64
  49. package/src/__tests__/pipeline-runner.test.ts +554 -0
  50. package/src/__tests__/plugin-api-shim.test.ts +6 -3
  51. package/src/__tests__/plugin-bootstrap.test.ts +23 -12
  52. package/src/__tests__/plugin-registry.test.ts +49 -3
  53. package/src/__tests__/plugin-types.test.ts +70 -0
  54. package/src/__tests__/reaction-persistence.test.ts +1 -1
  55. package/src/__tests__/send-endpoint-busy.test.ts +1 -4
  56. package/src/__tests__/skill-feature-flags-integration.test.ts +0 -33
  57. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  58. package/src/__tests__/subagent-fork-notifications.test.ts +3 -1
  59. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  60. package/src/__tests__/subagent-manager-notify.test.ts +3 -1
  61. package/src/__tests__/subagent-notify-parent.test.ts +3 -1
  62. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  63. package/src/__tests__/user-plugin-loader.test.ts +286 -54
  64. package/src/acp/__tests__/client-handler.test.ts +0 -40
  65. package/src/acp/__tests__/prepare-agent-env.test.ts +0 -137
  66. package/src/acp/__tests__/session-manager-persistence.test.ts +28 -95
  67. package/src/acp/agent-process.ts +1 -61
  68. package/src/acp/client-handler.ts +0 -31
  69. package/src/acp/prepare-agent-env.ts +29 -83
  70. package/src/acp/resolve-agent.test.ts +7 -320
  71. package/src/acp/resolve-agent.ts +18 -182
  72. package/src/acp/session-manager.ts +73 -495
  73. package/src/acp/types.ts +0 -8
  74. package/src/agent/compaction-circuit.ts +102 -60
  75. package/src/agent/loop.ts +59 -32
  76. package/src/api/responses/conversation-message.ts +1 -7
  77. package/src/approvals/guardian-request-resolvers.ts +1 -1
  78. package/src/background-wake/next-wake.ts +0 -1
  79. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  80. package/src/config/acp-defaults.test.ts +0 -10
  81. package/src/config/acp-defaults.ts +0 -6
  82. package/src/config/bundled-skills/acp/SKILL.md +31 -83
  83. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  84. package/src/config/bundled-skills/app-builder/SKILL.md +381 -224
  85. package/src/config/bundled-skills/app-builder/TOOLS.json +0 -29
  86. package/src/config/bundled-skills/document-editor/SKILL.md +23 -28
  87. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  88. package/src/config/bundled-tool-registry.ts +0 -2
  89. package/src/config/feature-flag-registry.json +5 -14
  90. package/src/config/schemas/heartbeat.ts +0 -9
  91. package/src/context/strip-injections.ts +2 -8
  92. package/src/context/window-manager.ts +1 -2
  93. package/src/daemon/conversation-agent-loop-handlers.ts +11 -0
  94. package/src/daemon/conversation-agent-loop.ts +279 -62
  95. package/src/daemon/conversation-runtime-assembly.ts +69 -106
  96. package/src/daemon/conversation-store.ts +90 -9
  97. package/src/daemon/conversation-workspace.ts +0 -17
  98. package/src/daemon/conversation.ts +6 -0
  99. package/src/daemon/external-plugins-bootstrap.ts +11 -11
  100. package/src/daemon/handlers/conversations.ts +1 -3
  101. package/src/daemon/handlers/skills.ts +1 -4
  102. package/src/daemon/lifecycle.ts +0 -21
  103. package/src/daemon/server.ts +0 -2
  104. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -3
  105. package/src/heartbeat/heartbeat-run-store.ts +1 -23
  106. package/src/heartbeat/heartbeat-service.ts +0 -26
  107. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  108. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  109. package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -15
  110. package/src/ipc/skill-routes/memory.ts +2 -4
  111. package/src/memory/conversation-starter-checkpoints.ts +0 -1
  112. package/src/memory/db-init.ts +0 -2
  113. package/src/memory/job-handlers/conversation-starters.ts +2 -13
  114. package/src/memory/jobs-worker.ts +1 -1
  115. package/src/memory/migrations/index.ts +0 -1
  116. package/src/memory/schema/acp.ts +0 -4
  117. package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
  118. package/src/memory/v2/consolidation-job.ts +4 -13
  119. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/assign.test.ts +4 -4
  120. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/live-integration.test.ts +4 -4
  121. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/maintain-job.test.ts +5 -5
  122. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/orchestrate.test.ts +3 -3
  123. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/reconcile.test.ts +2 -2
  124. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/render-injection.test.ts +1 -1
  125. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/router.test.ts +3 -3
  126. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selection-log-store.test.ts +8 -8
  127. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selector.test.ts +3 -3
  128. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/shadow-plugin.test.ts +12 -12
  129. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/assign.ts +5 -5
  130. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/capabilities.ts +2 -2
  131. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/maintain-job.ts +8 -8
  132. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/page-content.ts +2 -2
  133. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/provider-blocks.ts +1 -1
  134. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/reconcile.ts +3 -3
  135. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/render-injection.ts +1 -1
  136. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/router.ts +3 -3
  137. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selection-log-store.ts +4 -4
  138. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selector.ts +4 -4
  139. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/shadow-plugin.ts +90 -28
  140. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/tree.ts +1 -1
  141. package/src/plugin-api/index.ts +5 -0
  142. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +93 -0
  143. package/src/plugins/defaults/{memory-v3-shadow → circuit-breaker}/package.json +2 -2
  144. package/src/plugins/defaults/circuit-breaker/register.ts +39 -0
  145. package/src/plugins/defaults/compaction/middlewares/compaction.ts +25 -0
  146. package/src/plugins/defaults/compaction/package.json +1 -1
  147. package/src/plugins/defaults/compaction/register.ts +19 -8
  148. package/src/plugins/defaults/compaction/terminal.ts +73 -0
  149. package/src/plugins/defaults/index.ts +5 -3
  150. package/src/plugins/defaults/{memory-retrieval/injectors.ts → injectors/register.ts} +7 -45
  151. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +7 -11
  152. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  153. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +126 -0
  154. package/src/plugins/defaults/overflow-reduce/package.json +15 -0
  155. package/src/plugins/defaults/overflow-reduce/register.ts +42 -0
  156. package/src/plugins/external-api.ts +2 -2
  157. package/src/plugins/pipeline.ts +293 -6
  158. package/src/plugins/registry.ts +37 -9
  159. package/src/plugins/types.ts +336 -32
  160. package/src/plugins/user-loader.ts +127 -30
  161. package/src/proactive-artifact/aux-message-injector.ts +1 -1
  162. package/src/proactive-artifact/job.test.ts +1 -1
  163. package/src/prompts/__tests__/system-prompt.test.ts +0 -6
  164. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +2 -4
  165. package/src/runtime/__tests__/agent-wake.test.ts +5 -5
  166. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  167. package/src/runtime/agent-wake.ts +3 -0
  168. package/src/runtime/assistant-event-hub.ts +1 -1
  169. package/src/runtime/channel-approvals.ts +1 -1
  170. package/src/runtime/interactive-ui.ts +1 -1
  171. package/src/runtime/routes/__tests__/acp-routes.test.ts +55 -283
  172. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  173. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +4 -5
  174. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -4
  175. package/src/runtime/routes/acp-routes.test.ts +25 -89
  176. package/src/runtime/routes/acp-routes.ts +29 -81
  177. package/src/runtime/routes/approval-routes.ts +1 -1
  178. package/src/runtime/routes/browser-routes.ts +1 -1
  179. package/src/runtime/routes/browser-tabs-routes.ts +10 -6
  180. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  181. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  182. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  183. package/src/runtime/routes/conversation-routes.ts +2 -15
  184. package/src/runtime/routes/conversation-starter-routes.ts +7 -13
  185. package/src/runtime/routes/conversations-import-routes.ts +7 -24
  186. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  187. package/src/runtime/routes/host-cu-routes.ts +1 -1
  188. package/src/runtime/routes/identity-routes.ts +3 -18
  189. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  190. package/src/runtime/routes/memory-v3-routes.ts +6 -16
  191. package/src/runtime/routes/playground/helpers.ts +1 -1
  192. package/src/runtime/routes/surface-conversation-resolver.ts +3 -4
  193. package/src/runtime/routes/work-items-routes.ts +4 -2
  194. package/src/runtime/services/conversation-serializer.ts +1 -1
  195. package/src/signals/cancel.ts +4 -2
  196. package/src/subagent/manager.ts +5 -17
  197. package/src/tools/acp/list-agents.test.ts +1 -7
  198. package/src/tools/acp/spawn.test.ts +55 -158
  199. package/src/tools/acp/spawn.ts +72 -47
  200. package/src/tools/acp/steer.test.ts +8 -105
  201. package/src/tools/acp/steer.ts +17 -48
  202. package/src/tools/apps/executors.ts +8 -13
  203. package/src/tools/filesystem/write.ts +0 -34
  204. package/src/tools/subagent/spawn.ts +4 -2
  205. package/src/tools/ui-surface/definitions.ts +4 -25
  206. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +5 -4
  207. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +45 -69
  208. package/examples/plugins/echo/hooks/post-tool-use.ts +0 -18
  209. package/examples/plugins/echo/hooks/stop.ts +0 -16
  210. package/examples/plugins/echo/hooks/user-prompt-submit.ts +0 -18
  211. package/examples/plugins/echo/src/emit.ts +0 -19
  212. package/src/__tests__/compaction-circuit.test.ts +0 -258
  213. package/src/__tests__/compaction-direct.test.ts +0 -132
  214. package/src/__tests__/conversations-import-system-filter.test.ts +0 -101
  215. package/src/acp/__tests__/agent-process.test.ts +0 -161
  216. package/src/acp/__tests__/helpers/acp-history-db.ts +0 -82
  217. package/src/acp/__tests__/helpers/exec-file-stub.ts +0 -101
  218. package/src/acp/__tests__/session-manager-resume.test.ts +0 -736
  219. package/src/acp/auto-install.test.ts +0 -196
  220. package/src/acp/auto-install.ts +0 -177
  221. package/src/acp/feature-gate.test.ts +0 -48
  222. package/src/acp/feature-gate.ts +0 -34
  223. package/src/acp/resume-hint.ts +0 -25
  224. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +0 -48
  225. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +0 -57
  226. package/src/config/bundled-skills/app-builder/references/SLIDES.md +0 -38
  227. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -62
  228. package/src/daemon/conversation-registry.ts +0 -159
  229. package/src/daemon/overflow-reduction-loop.ts +0 -230
  230. package/src/memory/migrations/272-acp-session-history-cwd.ts +0 -36
  231. package/src/plugins/defaults/compaction/compact.ts +0 -59
  232. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +0 -14
  233. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +0 -19
  234. package/src/plugins/defaults/memory-v3-shadow/injector.ts +0 -75
  235. package/src/plugins/defaults/memory-v3-shadow/register.ts +0 -26
  236. package/src/tools/acp/context.ts +0 -20
  237. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/capabilities.test.ts +0 -0
  238. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/core.test.ts +0 -0
  239. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/eval-turns.json +0 -0
  240. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/live-turns.json +0 -0
  241. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/health.test.ts +0 -0
  242. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/needle.test.ts +0 -0
  243. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/provider-blocks.test.ts +0 -0
  244. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/snapshot.test.ts +0 -0
  245. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/tree.test.ts +0 -0
  246. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/types.test.ts +0 -0
  247. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-eviction.test.ts +0 -0
  248. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-skeleton.test.ts +0 -0
  249. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/core.ts +0 -0
  250. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/README.md +0 -0
  251. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/assignments.json +0 -0
  252. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/core.json +0 -0
  253. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-x.md +0 -0
  254. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-y.md +0 -0
  255. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-b/topic-z.md +0 -0
  256. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/health.ts +0 -0
  257. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/llm-retry.ts +0 -0
  258. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/needle.ts +0 -0
  259. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/orchestrate.ts +0 -0
  260. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/snapshot.ts +0 -0
  261. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/types.ts +0 -0
  262. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/working-set.ts +0 -0
@@ -10,16 +10,20 @@
10
10
  * late-arriving registrations.
11
11
  *
12
12
  * Registration is order-preserving: {@link getRegisteredPlugins} and
13
- * {@link getHooksFor} reflect the order in which {@link registerPlugin} was
14
- * called, which in turn determines the order plugin hooks run.
13
+ * {@link getMiddlewaresFor} reflect the order in which {@link registerPlugin}
14
+ * was called, which in turn determines onion order for middleware composition
15
+ * in the pipeline runner.
15
16
  *
16
17
  * This module does not call `Plugin.init()` — that is the job of the
17
- * bootstrap. It also does not wire the registry into the daemon.
18
+ * bootstrap (see PR 14). It also does not wire the registry into the daemon;
19
+ * later PRs introduce consumers.
18
20
  *
19
- * Design doc: `.private/plans/agent-plugin-system.md`.
21
+ * Design doc: `.private/plans/agent-plugin-system.md` (PR 13).
20
22
  */
21
23
 
22
24
  import {
25
+ type PipelineMiddlewareMap,
26
+ type PipelineName,
23
27
  type Plugin,
24
28
  PluginExecutionError,
25
29
  type PluginHookFn,
@@ -29,7 +33,7 @@ import {
29
33
 
30
34
  /**
31
35
  * Registered plugins keyed by `manifest.name`. A `Map` preserves insertion
32
- * order, which the registry relies on for hook ordering and
36
+ * order, which the registry relies on for middleware composition and
33
37
  * `getRegisteredPlugins()` output.
34
38
  */
35
39
  const registeredPlugins = new Map<string, Plugin>();
@@ -42,7 +46,8 @@ const registeredPlugins = new Map<string, Plugin>();
42
46
  * top-level `await` later resolves and still tries to call
43
47
  * {@link registerPlugin}. Without the latch such a late arrival would land in
44
48
  * the registry after `bootstrapPlugins()` has already walked it, leaving the
45
- * plugin visible in the registry with its `init()` hook never invoked.
49
+ * plugin visible to `getMiddlewaresFor()` with its
50
+ * `init()` hook never invoked.
46
51
  */
47
52
  let registrationClosed = false;
48
53
 
@@ -61,7 +66,7 @@ let registrationClosed = false;
61
66
  *
62
67
  * On success the plugin is appended to the registry in the order this
63
68
  * function is called. This function does NOT invoke `plugin.init()` — that
64
- * runs in the bootstrap sequence.
69
+ * runs in the bootstrap sequence (PR 14).
65
70
  */
66
71
  export function registerPlugin(plugin: Plugin): void {
67
72
  // Basic shape / required-field validation. The type system already enforces
@@ -138,6 +143,27 @@ export function getRegisteredPlugins(): Plugin[] {
138
143
  return Array.from(registeredPlugins.values());
139
144
  }
140
145
 
146
+ /**
147
+ * Collect the middleware each registered plugin contributes for the given
148
+ * pipeline, in registration order. Consumers feed the returned array into the
149
+ * pipeline runner's `composeMiddleware` helper (PR 12), which applies the
150
+ * outermost-first convention.
151
+ *
152
+ * Plugins that don't declare a middleware for `pipeline` are skipped.
153
+ */
154
+ export function getMiddlewaresFor<P extends PipelineName>(
155
+ pipeline: P,
156
+ ): PipelineMiddlewareMap[P][] {
157
+ const out: PipelineMiddlewareMap[P][] = [];
158
+ for (const plugin of registeredPlugins.values()) {
159
+ const middleware = plugin.middleware?.[pipeline];
160
+ if (middleware) {
161
+ out.push(middleware);
162
+ }
163
+ }
164
+ return out;
165
+ }
166
+
141
167
  /**
142
168
  * Collect every registered plugin's hook for the given name, in
143
169
  * registration order. Plugins that don't declare a hook for `name` are
@@ -182,8 +208,10 @@ export function closeRegistration(): void {
182
208
 
183
209
  /**
184
210
  * Remove a plugin from the registry. Invoked from the bootstrap's failure path
185
- * after {@link Plugin.onShutdown} and contribution teardown have run, so the
186
- * registry no longer exposes a plugin whose `init()` aborted mid-bootstrap.
211
+ * after {@link Plugin.onShutdown} and contribution teardown have run, so
212
+ * {@link getMiddlewaresFor} no longer exposes a plugin whose `init()` aborted
213
+ * mid-bootstrap. Without this, every subsequent pipeline invocation would
214
+ * re-enter the uninitialized plugin's middleware.
187
215
  * Safe to call on an already-absent name (no-op).
188
216
  */
189
217
  export function unregisterPlugin(name: string): void {
@@ -1,20 +1,34 @@
1
1
  /**
2
2
  * Plugin core types.
3
3
  *
4
- * A plugin may contribute lifecycle hooks ({@link PluginHooks}) that the
5
- * runtime invokes at named events, and model-visible capabilities (`tools`,
6
- * `routes`, `skills`). The registry tracks every registered plugin in
7
- * registration order.
4
+ * This file is scaffolding only it defines the shape of the plugin system
5
+ * without wiring any behavior. Later PRs in the `agent-plugin-system` plan
6
+ * refine per-pipeline argument/result types (currently `unknown`-based
7
+ * placeholders) and add the pipeline runner, registry, and bootstrap.
8
+ *
9
+ * The assistant composes behavior around a small set of named pipelines
10
+ * (`compaction`, `overflowReduce`, ...). Each plugin may contribute one
11
+ * {@link Middleware} per pipeline; the registry composes them in onion
12
+ * order at runtime. Plugins may also contribute {@link Injector}s that emit
13
+ * system-prompt-time content, as well as model-visible capabilities
14
+ * (`tools`, `routes`, `skills`).
8
15
  *
9
16
  * Design doc: `.private/plans/agent-plugin-system.md`.
10
17
  */
11
18
 
12
19
  import type { CompactionCircuitClosedEvent } from "../api/events/compaction-circuit-closed.js";
13
20
  import type { CompactionCircuitOpenEvent } from "../api/events/compaction-circuit-open.js";
21
+ import type { ContextWindowConfig } from "../config/schemas/inference.js";
14
22
  import type { LLMCallSite } from "../config/schemas/llm.js";
15
- import type { ContextWindowManager } from "../context/window-manager.js";
16
23
  import type {
24
+ ContextWindowManager,
25
+ ContextWindowResult,
26
+ } from "../context/window-manager.js";
27
+ import type { ReducerState } from "../daemon/context-overflow-reducer.js";
28
+ import type {
29
+ ActiveSurfaceContext,
17
30
  ChannelCapabilities,
31
+ ChannelCommandContext,
18
32
  InjectionMode,
19
33
  } from "../daemon/conversation-runtime-assembly.js";
20
34
  import type { TrustContext } from "../daemon/trust-context.js";
@@ -74,7 +88,32 @@ export type {
74
88
  PluginShutdownContext,
75
89
  } from "../plugin-api/types.js";
76
90
 
77
- // ─── Memory-graph result ─────────────────────────────────────────────────────
91
+ // ─── Middleware ──────────────────────────────────────────────────────────────
92
+
93
+ /**
94
+ * Generic onion-style middleware. Each middleware may observe/modify the
95
+ * arguments, decide whether to call `next` (short-circuit) or return a
96
+ * synthetic result, and observe/modify the downstream result. `ctx` is the
97
+ * immutable per-turn {@link TurnContext}.
98
+ */
99
+ export type Middleware<A, R> = (
100
+ args: A,
101
+ next: (args: A) => Promise<R>,
102
+ ctx: TurnContext,
103
+ ) => Promise<R>;
104
+
105
+ // ─── Pipeline names ──────────────────────────────────────────────────────────
106
+
107
+ /**
108
+ * Exhaustive list of pipeline slot names. New pipelines must be added here
109
+ * and in `DEFAULT_TIMEOUTS` (PR 12). The registry only understands these.
110
+ */
111
+ export type PipelineName = "compaction" | "overflowReduce" | "circuitBreaker";
112
+
113
+ // ─── Per-pipeline args / results (placeholder shapes) ────────────────────────
114
+ // Concrete field-level types land in M2/M3 PRs as each pipeline is wrapped.
115
+ // Until then we expose `unknown`-tagged aliases so downstream code can name
116
+ // the types without depending on unstable internal shapes.
78
117
 
79
118
  /**
80
119
  * Full output of a single memory-graph retrieval — the object returned by
@@ -87,7 +126,186 @@ export type GraphMemoryResult = Awaited<
87
126
  >;
88
127
 
89
128
  /**
90
- * The complete set of compaction circuit-breaker transition events:
129
+ * Pipeline inputs for the `compaction` slot the arguments the assistant
130
+ * would otherwise have passed to {@link ContextWindowManager.maybeCompact}.
131
+ *
132
+ * Typed via `unknown`-forwarded aliases to keep this module free of runtime
133
+ * imports from `context/window-manager.ts` (which would pull the full
134
+ * compaction machinery into anything that merely imports plugin types).
135
+ * The default compaction plugin re-casts back to the concrete types before
136
+ * delegating to the manager.
137
+ */
138
+ export type CompactionArgs = {
139
+ /** The message history to consider for compaction. */
140
+ readonly messages: unknown;
141
+ /** Abort signal forwarded to the compaction summary call. */
142
+ readonly signal?: AbortSignal;
143
+ /** `ContextWindowCompactOptions` — options block forwarded verbatim. */
144
+ readonly options?: unknown;
145
+ };
146
+ /**
147
+ * Pipeline result for the `compaction` slot — the full
148
+ * {@link import("../context/window-manager.js").ContextWindowResult}
149
+ * object returned by `maybeCompact()`. Kept as `unknown` here for the
150
+ * same decoupling reason as {@link CompactionArgs}; consumers in
151
+ * `daemon/conversation-agent-loop.ts` cast back to the concrete shape.
152
+ */
153
+ export type CompactionResult = unknown;
154
+
155
+ /**
156
+ * Input to the `overflowReduce` pipeline. Captures everything the reducer
157
+ * tier loop needs, including the message history, reducer configuration,
158
+ * and side-effect callbacks that bridge the pipeline back to the orchestrator's
159
+ * mutable per-turn state (context-window manager, activity emitter, runtime
160
+ * injection reassembly, memory reinjection).
161
+ *
162
+ * The callbacks are supplied by the orchestrator because the reducer loop
163
+ * needs to coordinate with state that lives on the `AgentLoopConversationContext`
164
+ * (message mutation, compaction event emission, circuit breaker tracking,
165
+ * injection block reassembly). Keeping them as explicit callbacks — rather
166
+ * than pulling the whole context into the pipeline — preserves the rule that
167
+ * `TurnContext` stays minimal and pipeline-agnostic.
168
+ */
169
+ export interface OverflowReduceArgs {
170
+ /** Bare persisted message history (mutable copy — the default middleware
171
+ * applies reducer results in-place via the `applyMessages` callback). */
172
+ readonly messages: Message[];
173
+ /** Current run-time message array with runtime injections applied. */
174
+ readonly runMessages: Message[];
175
+ /** System prompt used for post-step token estimation. */
176
+ readonly systemPrompt: string;
177
+ /** Provider name used for token estimation (calibration provider key). */
178
+ readonly providerName: string;
179
+ /** Context window config (drives compaction behavior). */
180
+ readonly contextWindow: ContextWindowConfig;
181
+ /** Token budget the reducer must get below (preflight budget). */
182
+ readonly preflightBudget: number;
183
+ /** Tool-token overhead included in every estimation call. */
184
+ readonly toolTokenBudget?: number;
185
+ /** Maximum reducer iterations before the loop exits unconditionally. */
186
+ readonly maxAttempts: number;
187
+ /** Abort signal threaded through compaction calls. */
188
+ readonly abortSignal?: AbortSignal;
189
+ /**
190
+ * Compaction callback — the reducer never owns the ContextWindowManager
191
+ * instance. The orchestrator supplies this closure so the default plugin
192
+ * can delegate the forced-compaction tier without crossing the
193
+ * pipeline/infra boundary on its own.
194
+ */
195
+ readonly compactFn: (
196
+ messages: Message[],
197
+ signal: AbortSignal | undefined,
198
+ options: unknown,
199
+ ) => Promise<ContextWindowResult>;
200
+ /**
201
+ * Invoked before each reducer iteration to emit the `context_compacting`
202
+ * activity state. The orchestrator owns activity emission because the
203
+ * signal is trust/channel aware.
204
+ */
205
+ readonly emitActivityState: () => void;
206
+ /**
207
+ * Invoked after each reducer step that produced a successful compaction.
208
+ * Handles circuit-breaker tracking, event emission, and context mutation.
209
+ * The pipeline passes back `didCompact` so the orchestrator can flip its
210
+ * `reducerCompacted` / `shouldInjectWorkspace` flags and the next
211
+ * re-injection uses the fresh messages.
212
+ */
213
+ readonly onCompactionResult: (
214
+ result: ContextWindowResult,
215
+ compactedBasis?: Message[],
216
+ ) => void | Promise<void>;
217
+ /**
218
+ * Invoked after each step to rebuild `runMessages` from the step's
219
+ * reduced history with the requested injection mode. The orchestrator
220
+ * owns this helper so the full per-turn injection options object doesn't
221
+ * leak into the pipeline surface. The plugin passes the current reduced
222
+ * messages array explicitly so the orchestrator doesn't need to read
223
+ * mutable shared state. Returns the new `runMessages`.
224
+ *
225
+ * Two distinct "did compact" signals are passed so the orchestrator can
226
+ * apply the correct per-iteration vs sticky gating:
227
+ * - `stepCompacted` — whether THIS iteration's reducer step produced a
228
+ * fresh compaction. Gates PKB / NOW re-injection: compaction strips the
229
+ * existing blocks, so only iterations that just compacted need the
230
+ * content re-threaded. Iterations that only truncated tool results or
231
+ * downgraded injections must NOT force a re-injection or the token
232
+ * count grows each round.
233
+ * - `accumulatedCompacted` — whether ANY iteration in this pipeline
234
+ * invocation has compacted. Gates `slackChronologicalMessages`
235
+ * suppression: once compaction has run, the captured Slack transcript
236
+ * snapshot would overwrite the compacted history, so it must stay
237
+ * suppressed for every subsequent iteration even if that iteration
238
+ * didn't re-compact.
239
+ */
240
+ readonly reinjectForMode: (
241
+ messages: Message[],
242
+ mode: InjectionMode,
243
+ stepCompacted: boolean,
244
+ accumulatedCompacted: boolean,
245
+ ) => Promise<Message[]>;
246
+ /**
247
+ * Invoked after each step to post-estimate the rebuilt `runMessages`.
248
+ * Pulled out so the orchestrator controls how estimation is performed
249
+ * (and which fields feed it) without the pipeline reimplementing it.
250
+ */
251
+ readonly estimatePostInjection: (runMessages: Message[]) => number;
252
+ }
253
+
254
+ /** Output of the `overflowReduce` pipeline. */
255
+ export interface OverflowReduceResult {
256
+ /** Final reduced `ctx.messages` value (mutated in place by the reducer). */
257
+ readonly messages: Message[];
258
+ /** Final `runMessages` with re-applied runtime injections. */
259
+ readonly runMessages: Message[];
260
+ /** Final injection mode (may be `"minimal"` if the downgrade tier fired). */
261
+ readonly injectionMode: InjectionMode;
262
+ /** Accumulated reducer state at exit. */
263
+ readonly reducerState: ReducerState;
264
+ /** True if any step successfully compacted history. */
265
+ readonly reducerCompacted: boolean;
266
+ /** How many iterations of the tier loop executed. */
267
+ readonly attempts: number;
268
+ }
269
+
270
+ /**
271
+ * Arguments for the `circuitBreaker` pipeline.
272
+ *
273
+ * A single call pattern handles both querying and updating the breaker:
274
+ * - `{ key }` — query-only. Returns the current `{ open, cooldownRemainingMs? }`.
275
+ * - `{ key, outcome }` — update state, then return the post-update decision.
276
+ *
277
+ * `key` identifies the circuit bucket so independent circuits (e.g. per
278
+ * conversation, per provider) can coexist. The default compaction plugin
279
+ * uses `"compaction:<conversationId>"`.
280
+ *
281
+ * `state` is a pragmatic extension beyond the minimal `{ key, outcome? }`
282
+ * shape: the `Conversation` owns `consecutiveCompactionFailures` and
283
+ * `compactionCircuitOpenUntil` because dev-only playground routes read and
284
+ * mutate those fields directly. The default plugin reads/updates the same
285
+ * container so the pipeline stays a pure wrapper rather than forking state
286
+ * ownership.
287
+ *
288
+ * `onEvent` is optional — when provided, the default plugin emits
289
+ * `compaction_circuit_open` / `compaction_circuit_closed` transition events
290
+ * through it. Its parameter is narrowed to {@link CompactionCircuitEvent} (the
291
+ * only two messages this pipeline ever emits) rather than the full
292
+ * `ServerMessage` union, so a caller whose outbound channel can carry just
293
+ * these two events can satisfy it. Callers that only want to query without
294
+ * emitting can omit it.
295
+ */
296
+ export type CircuitBreakerArgs = {
297
+ readonly key: string;
298
+ readonly outcome?: "success" | "failure";
299
+ readonly state: {
300
+ readonly conversationId: string;
301
+ consecutiveCompactionFailures: number;
302
+ compactionCircuitOpenUntil: number | null;
303
+ };
304
+ readonly onEvent?: (msg: CompactionCircuitEvent) => void;
305
+ };
306
+
307
+ /**
308
+ * The complete set of transition events the `circuitBreaker` pipeline emits:
91
309
  * `compaction_circuit_open` when the breaker trips and `compaction_circuit_closed`
92
310
  * on the open→closed transition. Both are a subset of `ServerMessage`, so any
93
311
  * existing `ServerMessage` sink remains assignable to a
@@ -97,6 +315,31 @@ export type CompactionCircuitEvent =
97
315
  | CompactionCircuitOpenEvent
98
316
  | CompactionCircuitClosedEvent;
99
317
 
318
+ /**
319
+ * Result of a `circuitBreaker` pipeline invocation.
320
+ *
321
+ * - `open` — `true` when the breaker is currently tripped (auto paths must
322
+ * skip). `false` when closed (auto paths may proceed).
323
+ * - `cooldownRemainingMs` — when `open` is `true`, the number of ms until
324
+ * the breaker auto-closes (for informational display). Omitted when the
325
+ * breaker is closed.
326
+ */
327
+ export type CircuitBreakerResult = {
328
+ readonly open: boolean;
329
+ readonly cooldownRemainingMs?: number;
330
+ };
331
+
332
+ /**
333
+ * Mapping from {@link PipelineName} to the middleware signature the registry
334
+ * expects for that slot. Used both to shape `Plugin.middleware` and to drive
335
+ * `getMiddlewaresFor<P>()` type narrowing in PR 13.
336
+ */
337
+ export interface PipelineMiddlewareMap {
338
+ compaction: Middleware<CompactionArgs, CompactionResult>;
339
+ overflowReduce: Middleware<OverflowReduceArgs, OverflowReduceResult>;
340
+ circuitBreaker: Middleware<CircuitBreakerArgs, CircuitBreakerResult>;
341
+ }
342
+
100
343
  // ─── TurnContext ─────────────────────────────────────────────────────────────
101
344
 
102
345
  /**
@@ -122,6 +365,10 @@ export interface TurnInjectionInputs {
122
365
  * context (unified turn context, etc.). Drives per-injector gating.
123
366
  */
124
367
  readonly mode?: InjectionMode;
368
+ /** Disk-pressure cleanup-mode context or null to skip the warning. */
369
+ readonly diskPressureContext?: DiskPressureInjectionContext | null;
370
+ /** Workspace top-level context text (`<workspace>...`) or null to skip. */
371
+ readonly workspaceTopLevelContext?: string | null;
125
372
  /** Pre-built unified-turn-context text (`<turn_context>...`) or null to skip. */
126
373
  readonly unifiedTurnContext?: string | null;
127
374
  /** Pre-built `<active_subagents>` block or null to skip. */
@@ -140,6 +387,18 @@ export interface TurnInjectionInputs {
140
387
  * no focus block is appended.
141
388
  */
142
389
  readonly slackActiveThreadFocusBlock?: string | null;
390
+ /**
391
+ * Active dashboard-surface context (read from `<active_workspace>`). Kept
392
+ * on the injection inputs bag (not its own injector) because it is
393
+ * orchestrator-owned surface state, not a default-chain element.
394
+ */
395
+ readonly activeSurface?: ActiveSurfaceContext | null;
396
+ /** Channel command context (e.g. Telegram /start) or null to skip. */
397
+ readonly channelCommandContext?: ChannelCommandContext | null;
398
+ /** Voice call-control prompt or null to skip. */
399
+ readonly voiceCallControlPrompt?: string | null;
400
+ /** Gateway-provided transport hints (e.g. Slack thread context). */
401
+ readonly transportHints?: string[] | null;
143
402
  /**
144
403
  * When true, inject the `<non_interactive_context>` block so the model
145
404
  * knows no human is present to answer clarification questions.
@@ -165,12 +424,18 @@ export interface TurnInjectionInputs {
165
424
  }> | null;
166
425
  }
167
426
 
427
+ export interface DiskPressureInjectionContext {
428
+ /** True when the current turn is allowed to run only for storage cleanup. */
429
+ readonly cleanupModeActive: boolean;
430
+ }
431
+
168
432
  /**
169
- * Per-turn execution context threaded to the injector chain and to hook
170
- * consumers that need turn-level identity.
433
+ * Per-turn execution context threaded through every middleware invocation.
171
434
  *
172
435
  * Combines turn-level identifiers (`requestId`, `conversationId`,
173
- * `turnIndex`) with the canonical {@link TrustContext} that carries trust
436
+ * `turnIndex`), the optionally-bound `pluginName` (set by the pipeline
437
+ * runner when invoking a specific plugin's middleware, for error
438
+ * attribution), and the canonical {@link TrustContext} that carries trust
174
439
  * class and channel identity for the inbound actor.
175
440
  *
176
441
  * `TrustContext` is re-exposed here (rather than redefined) so the plugin
@@ -183,42 +448,51 @@ export interface TurnContext {
183
448
  conversationId: string;
184
449
  /** 0-based turn index within the conversation. */
185
450
  turnIndex: number;
451
+ /**
452
+ * When the pipeline runner is executing a specific plugin's middleware,
453
+ * this is set to that plugin's name so downstream code (error wrapping,
454
+ * telemetry) can attribute work accurately.
455
+ */
456
+ pluginName?: string;
186
457
  /** Trust classification and channel identity for the inbound actor. */
187
458
  trust: TrustContext;
188
459
  /**
189
460
  * Optional handle to the conversation's {@link ContextWindowManager}.
190
461
  *
191
- * Attached by the orchestrator so the default compaction implementation can
192
- * defer to the real compaction machinery via `context.manager`. Call sites
193
- * that never touch compaction may omit it.
462
+ * Attached by the orchestrator when building the per-turn context for
463
+ * pipeline invocations that need to defer to the real compaction machinery
464
+ * (notably the default `compaction` plugin). Pipelines that never touch
465
+ * compaction can ignore this field; the default compaction plugin throws
466
+ * a {@link PluginExecutionError} if it is missing, which keeps the failure
467
+ * attributed to the plugin rather than surfacing as a late NPE downstream.
194
468
  *
195
469
  * Declared as an optional typed field so plugin code can read it without a
196
- * lenient cast. The optional shape is load-bearing: synthesized handler
197
- * contexts and other non-compaction call sites still construct valid
198
- * `TurnContext` literals without attaching a manager.
470
+ * lenient cast. The optional shape is load-bearing: pipeline runner tests,
471
+ * synthesized handler contexts, and other non-compaction call sites still
472
+ * construct valid `TurnContext` literals without attaching a manager.
199
473
  */
200
474
  contextWindowManager?: ContextWindowManager;
201
475
  /**
202
476
  * Per-turn injection inputs consumed by the default injector chain.
203
477
  *
204
- * Omitted for call sites that don't drive runtime injection (synthesized
205
- * handler contexts, some background jobs). Each default injector treats
206
- * missing/absent fields as "no injection on this turn", so a context
207
- * without `injectionInputs` produces an empty injection chain.
478
+ * Omitted for call sites that don't drive runtime injection (pipeline-runner
479
+ * tests, synthesized handler contexts, some background jobs). Each default
480
+ * injector treats missing/absent fields as "no injection on this turn", so
481
+ * a context without `injectionInputs` produces an empty injection chain.
208
482
  */
209
483
  injectionInputs?: TurnInjectionInputs;
210
484
  /**
211
- * The {@link LLMCallSite} this turn belongs to — `"mainAgent"` for the
212
- * user-facing conversational reply, or the specific background/utility site
213
- * (`"compactionAgent"`, `"subagentSpawn"`, `"memoryConsolidation"`,
214
- * `"conversationTitle"`, …) when the agent loop is driving non-main work
215
- * that happens to share the same `conversationId`.
485
+ * The {@link LLMCallSite} this turn's pipeline work belongs to —
486
+ * `"mainAgent"` for the user-facing conversational reply, or the specific
487
+ * background/utility site (`"compactionAgent"`, `"subagentSpawn"`,
488
+ * `"memoryConsolidation"`, `"conversationTitle"`, …) when the agent loop is
489
+ * driving non-main work that happens to share the same `conversationId`.
216
490
  *
217
- * Lets {@link Injector}s scope their behaviour to the main reply and stay
218
- * out of background turns, which `onEvent` presence alone cannot distinguish
219
- * (compaction and subagent loops also stream). Omitted by call sites that
220
- * don't tag a site (synthesized test contexts); consumers should treat
221
- * absence conservatively.
491
+ * Lets {@link Injector}s and pipeline middleware scope their behaviour to
492
+ * the main reply and stay out of background turns, which `onEvent` presence
493
+ * alone cannot distinguish (compaction and subagent loops also stream).
494
+ * Omitted by call sites that don't tag a site (synthesized test contexts);
495
+ * consumers should treat absence conservatively.
222
496
  */
223
497
  callSite?: LLMCallSite;
224
498
  }
@@ -401,7 +675,7 @@ export type PluginHooks = Record<string, PluginHookFn<any>>;
401
675
 
402
676
  /**
403
677
  * A registered plugin. Every field besides `manifest` is optional — a plugin
404
- * may contribute any combination of lifecycle hooks and model-visible
678
+ * may contribute any combination of middleware and model-visible
405
679
  * capabilities. Lifecycle hooks live under `hooks`.
406
680
  */
407
681
  export interface Plugin {
@@ -423,13 +697,43 @@ export interface Plugin {
423
697
  routes?: PluginRouteRegistration[];
424
698
  /** Skill registrations loaded at startup. */
425
699
  skills?: PluginSkillRegistration[];
700
+ /**
701
+ * Named middleware slots. At most one middleware per slot per plugin.
702
+ * The registry composes multiple plugins' middleware for a slot in
703
+ * registration order (outermost first).
704
+ */
705
+ middleware?: Partial<PipelineMiddlewareMap>;
426
706
  }
427
707
 
428
708
  // ─── Errors ──────────────────────────────────────────────────────────────────
429
709
 
710
+ /**
711
+ * Thrown by the pipeline runner when a plugin's middleware exceeds the
712
+ * pipeline's configured timeout. Carries the pipeline name, the offending
713
+ * plugin (if known), and the elapsed-milliseconds budget that was breached.
714
+ */
715
+ export class PluginTimeoutError extends AssistantError {
716
+ constructor(
717
+ public readonly pipeline: PipelineName,
718
+ public readonly pluginName: string | undefined,
719
+ public readonly elapsedMs: number,
720
+ options?: { cause?: unknown },
721
+ ) {
722
+ super(
723
+ `Plugin pipeline '${pipeline}' timed out after ${elapsedMs}ms${
724
+ pluginName ? ` (offending plugin: ${pluginName})` : ""
725
+ }`,
726
+ ErrorCode.INTERNAL_ERROR,
727
+ options,
728
+ );
729
+ this.name = "PluginTimeoutError";
730
+ }
731
+ }
732
+
430
733
  /**
431
734
  * Thrown by registry and bootstrap for plugin lifecycle errors — registration
432
- * validation failures, API-version mismatches, init throw-outs.
735
+ * validation failures, API-version mismatches, init throw-outs. Distinct from
736
+ * {@link PluginTimeoutError} so callers can discriminate.
433
737
  */
434
738
  export class PluginExecutionError extends AssistantError {
435
739
  constructor(