@wrongstack/core 0.313.0 → 0.313.1

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 (182) hide show
  1. package/dist/chronicle/index.js +84 -6391
  2. package/dist/chronicle/project-server.js +84 -4868
  3. package/dist/chunk-2BUUIMQB.js +225 -0
  4. package/dist/chunk-2FK4WTFF.js +207 -0
  5. package/dist/chunk-2FQL4PDC.js +292 -0
  6. package/dist/chunk-2KKODIQU.js +2256 -0
  7. package/dist/chunk-2YU4WVBW.js +134 -0
  8. package/dist/chunk-3E244UDA.js +356 -0
  9. package/dist/chunk-3XPVB7BA.js +135 -0
  10. package/dist/chunk-4LJJYBAU.js +388 -0
  11. package/dist/chunk-4VWO3ZXL.js +342 -0
  12. package/dist/chunk-4XV6IR37.js +187 -0
  13. package/dist/chunk-4ZHTKJHM.js +183 -0
  14. package/dist/chunk-52RTVH4M.js +361 -0
  15. package/dist/chunk-57GEWK5X.js +26 -0
  16. package/dist/chunk-5DSMEVA4.js +931 -0
  17. package/dist/chunk-5RP3QTRL.js +1223 -0
  18. package/dist/chunk-5S437PRC.js +50 -0
  19. package/dist/chunk-5XDUGTKN.js +511 -0
  20. package/dist/chunk-62V2AFVG.js +422 -0
  21. package/dist/chunk-65RDIOR2.js +2470 -0
  22. package/dist/chunk-6AKOTGLM.js +597 -0
  23. package/dist/chunk-7ZHZE5EO.js +28 -0
  24. package/dist/chunk-A455ATZB.js +140 -0
  25. package/dist/chunk-AD7ZFZCS.js +46 -0
  26. package/dist/chunk-AI4LLQ2U.js +856 -0
  27. package/dist/chunk-ASX6AW36.js +789 -0
  28. package/dist/chunk-AT4V4XGI.js +3522 -0
  29. package/dist/chunk-AWNQ6ED6.js +311 -0
  30. package/dist/chunk-B4AMLZPB.js +9 -0
  31. package/dist/chunk-BAKVJLEV.js +417 -0
  32. package/dist/chunk-BJHVULHO.js +55 -0
  33. package/dist/chunk-CB2HFKTP.js +9 -0
  34. package/dist/chunk-CE4ZOCOI.js +1640 -0
  35. package/dist/chunk-CQQL4ZOQ.js +151 -0
  36. package/dist/chunk-DEIQMBD2.js +65 -0
  37. package/dist/chunk-E6I5RXVK.js +58 -0
  38. package/dist/chunk-EFEHDQGY.js +1589 -0
  39. package/dist/chunk-EODDVJBM.js +82 -0
  40. package/dist/chunk-EPC3GZD4.js +308 -0
  41. package/dist/chunk-FAYRX2AD.js +13 -0
  42. package/dist/chunk-FFSX2QDQ.js +362 -0
  43. package/dist/chunk-FHAMQS7Q.js +5856 -0
  44. package/dist/chunk-FIV4IJL7.js +2263 -0
  45. package/dist/chunk-FNXVT7HF.js +52 -0
  46. package/dist/chunk-FSWCWSTS.js +395 -0
  47. package/dist/chunk-FW5LDDMJ.js +16464 -0
  48. package/dist/chunk-FXQLIX3K.js +211 -0
  49. package/dist/chunk-G5AAKEEZ.js +228 -0
  50. package/dist/chunk-G5S3S5ZN.js +38 -0
  51. package/dist/chunk-GAZEWQEZ.js +231 -0
  52. package/dist/chunk-GFJOMSHG.js +125 -0
  53. package/dist/chunk-GFXZTHNI.js +158 -0
  54. package/dist/chunk-GM54GJBU.js +960 -0
  55. package/dist/chunk-GPQ3NJPR.js +900 -0
  56. package/dist/chunk-GTCU6DZ3.js +1 -0
  57. package/dist/chunk-H5E77KUJ.js +15 -0
  58. package/dist/chunk-H72H4XDU.js +1023 -0
  59. package/dist/chunk-HA4ZYA6T.js +178 -0
  60. package/dist/chunk-I45KAW2N.js +5020 -0
  61. package/dist/chunk-IQ35HMQL.js +1396 -0
  62. package/dist/chunk-IYFBFX34.js +86 -0
  63. package/dist/chunk-JJPMWPB6.js +109 -0
  64. package/dist/chunk-JTHKOYWU.js +663 -0
  65. package/dist/chunk-JVGPR3TS.js +365 -0
  66. package/dist/chunk-JVKB2TBL.js +48 -0
  67. package/dist/chunk-JX7VBZDK.js +31 -0
  68. package/dist/chunk-K3G6JBUC.js +5232 -0
  69. package/dist/chunk-K4YUNKU7.js +589 -0
  70. package/dist/chunk-KBZGE6LN.js +710 -0
  71. package/dist/chunk-KDQJCT2J.js +45 -0
  72. package/dist/chunk-KI4L2DUL.js +560 -0
  73. package/dist/chunk-KJ2ZTQSL.js +47 -0
  74. package/dist/chunk-KSJ5UTP7.js +154 -0
  75. package/dist/chunk-LHOA5DWA.js +28 -0
  76. package/dist/chunk-LX5QWLBL.js +27 -0
  77. package/dist/chunk-LZDDLNCZ.js +279 -0
  78. package/dist/chunk-M2WF6AXG.js +115 -0
  79. package/dist/chunk-M7BS2K6U.js +1 -0
  80. package/dist/chunk-NDBIPFNY.js +102 -0
  81. package/dist/chunk-NEMIWEXC.js +1199 -0
  82. package/dist/chunk-NFAYHPRH.js +323 -0
  83. package/dist/chunk-O37BQ65S.js +21 -0
  84. package/dist/chunk-O74MJHXU.js +187 -0
  85. package/dist/chunk-OJZEX73F.js +112 -0
  86. package/dist/chunk-PAAWLLZX.js +74 -0
  87. package/dist/chunk-PFXFQQRT.js +339 -0
  88. package/dist/chunk-PFZO3C6H.js +14 -0
  89. package/dist/chunk-PHH7LD45.js +113 -0
  90. package/dist/chunk-QRTG4XTI.js +188 -0
  91. package/dist/chunk-QVOVMXJR.js +1351 -0
  92. package/dist/chunk-RCQYT2I4.js +757 -0
  93. package/dist/chunk-RD6DEE36.js +1178 -0
  94. package/dist/chunk-RJIZEJ42.js +30 -0
  95. package/dist/chunk-RPOA7IAE.js +150 -0
  96. package/dist/chunk-SN2DQRL6.js +1172 -0
  97. package/dist/chunk-SQJVFEBV.js +41 -0
  98. package/dist/chunk-SQP64RCS.js +5391 -0
  99. package/dist/chunk-SVT4RNTR.js +49 -0
  100. package/dist/chunk-SWUFN7XM.js +43 -0
  101. package/dist/chunk-SXMVYC5H.js +174 -0
  102. package/dist/chunk-SZ6AYUIZ.js +23 -0
  103. package/dist/chunk-T3KGZBU5.js +3171 -0
  104. package/dist/chunk-TBRL2RCC.js +9 -0
  105. package/dist/chunk-TRT2HRWZ.js +18 -0
  106. package/dist/chunk-TYW2GI5I.js +1077 -0
  107. package/dist/chunk-UIWONWVG.js +4959 -0
  108. package/dist/chunk-VB3H5WWB.js +151 -0
  109. package/dist/chunk-W4A6QA3D.js +258 -0
  110. package/dist/chunk-WBPVKEEE.js +227 -0
  111. package/dist/chunk-WEBMSQP2.js +123 -0
  112. package/dist/chunk-WOEZV3Q4.js +333 -0
  113. package/dist/chunk-WQQON3SV.js +473 -0
  114. package/dist/chunk-WQTOVMGV.js +119 -0
  115. package/dist/chunk-WZO23AWO.js +2779 -0
  116. package/dist/chunk-XBDP4QPW.js +7 -0
  117. package/dist/chunk-XEBHSJLT.js +103 -0
  118. package/dist/chunk-XQ2ECNK3.js +1 -0
  119. package/dist/chunk-XQ2XGIBF.js +2086 -0
  120. package/dist/chunk-Y2GE5TSI.js +355 -0
  121. package/dist/chunk-YBUJCZ57.js +351 -0
  122. package/dist/chunk-YNCVSFGF.js +149 -0
  123. package/dist/chunk-YUPMXOGP.js +282 -0
  124. package/dist/chunk-YY6APYUM.js +49 -0
  125. package/dist/chunk-Z2IJUO45.js +691 -0
  126. package/dist/chunk-ZEHUCJHY.js +129 -0
  127. package/dist/chunk-ZYU2YOJ6.js +1496 -0
  128. package/dist/chunk-ZZ4V3WIZ.js +163 -0
  129. package/dist/coordination/agents/index.js +145 -6260
  130. package/dist/coordination/index.js +365 -34324
  131. package/dist/coordination/mailbox-project-server.js +48 -3276
  132. package/dist/coordination/provider-status-tracker.d.ts +4 -0
  133. package/dist/core/agent.d.ts +14 -1
  134. package/dist/core/index.js +267 -12480
  135. package/dist/core/model-ref.js +5 -23
  136. package/dist/core/provider-runner.d.ts +36 -1
  137. package/dist/design/index.js +50 -1087
  138. package/dist/execution/index.d.ts +1 -1
  139. package/dist/execution/index.js +1599 -21888
  140. package/dist/execution/prompt-enhancer.js +16 -535
  141. package/dist/execution/retry-policy.d.ts +31 -1
  142. package/dist/extension/index.js +7 -311
  143. package/dist/goal/index.js +33 -3435
  144. package/dist/hooks/index.js +14 -1138
  145. package/dist/hq/index.js +112 -5139
  146. package/dist/hq/protocol.js +21 -668
  147. package/dist/index.js +1410 -92656
  148. package/dist/infrastructure/index.js +64 -4811
  149. package/dist/kernel/events/provider-events.d.ts +15 -0
  150. package/dist/kernel/index.js +14 -992
  151. package/dist/models/index.js +40 -1117
  152. package/dist/notifications/index.js +3 -108
  153. package/dist/observability/index.js +17 -279
  154. package/dist/plugin/index.js +584 -14835
  155. package/dist/prompts/index.js +14 -948
  156. package/dist/registry/index.js +9 -822
  157. package/dist/registry-R23UBE7H.js +14 -0
  158. package/dist/replay/index.js +6 -84
  159. package/dist/review-finding-store-DKAV3AS6.js +21 -0
  160. package/dist/review-report-store-OWY3EC3N.js +19 -0
  161. package/dist/security/index.js +114 -2202
  162. package/dist/session-catalog/index.js +36 -2103
  163. package/dist/session-catalog/project-server.js +42 -1435
  164. package/dist/skills/index.js +35 -1578
  165. package/dist/storage/index.js +182 -15600
  166. package/dist/tasking/index.js +10 -676
  167. package/dist/tools/index.js +52 -9590
  168. package/dist/types/config/autonomy.d.ts +5 -0
  169. package/dist/types/config/ui.d.ts +1 -1
  170. package/dist/types/errors.d.ts +18 -0
  171. package/dist/types/index.js +121 -1342
  172. package/dist/types/provider-runner.d.ts +8 -0
  173. package/dist/types/session-markers.js +8 -144
  174. package/dist/utils/error.js +3 -4
  175. package/dist/utils/expect-defined.js +3 -9
  176. package/dist/utils/heap-watchdog.js +6 -706
  177. package/dist/utils/index.js +269 -5685
  178. package/dist/utils/sage-output-block.js +5 -42
  179. package/dist/wiring/proxy-rewrite.d.ts +18 -0
  180. package/dist/wiring/proxy-rewrite.js +18 -121
  181. package/dist/worktree/index.js +6 -887
  182. package/package.json +4 -4
@@ -1,995 +1,17 @@
1
- // src/utils/error.ts
2
- function toErrorMessage(err) {
3
- return err instanceof Error ? err.message : String(err);
4
- }
5
-
6
- // src/types/errors.ts
7
- var ERROR_CODES = {
8
- // Provider
9
- PROVIDER_RATE_LIMITED: "PROVIDER_RATE_LIMITED",
10
- PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED",
11
- PROVIDER_OVERLOADED: "PROVIDER_OVERLOADED",
12
- PROVIDER_INVALID_REQUEST: "PROVIDER_INVALID_REQUEST",
13
- PROVIDER_SERVER_ERROR: "PROVIDER_SERVER_ERROR",
14
- PROVIDER_NETWORK_ERROR: "PROVIDER_NETWORK_ERROR",
15
- PROVIDER_CONTEXT_OVERFLOW: "PROVIDER_CONTEXT_OVERFLOW",
16
- // Tool
17
- TOOL_NOT_FOUND: "TOOL_NOT_FOUND",
18
- TOOL_PERMISSION_DENIED: "TOOL_PERMISSION_DENIED",
19
- TOOL_EXECUTION_FAILED: "TOOL_EXECUTION_FAILED",
20
- TOOL_TIMEOUT: "TOOL_TIMEOUT",
21
- TOOL_INPUT_INVALID: "TOOL_INPUT_INVALID",
22
- // Config
23
- CONFIG_INVALID: "CONFIG_INVALID",
24
- CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
25
- CONFIG_PARSE_FAILED: "CONFIG_PARSE_FAILED",
26
- CONFIG_MIGRATION_NEEDED: "CONFIG_MIGRATION_NEEDED",
27
- // Plugin
28
- PLUGIN_LOAD_FAILED: "PLUGIN_LOAD_FAILED",
29
- PLUGIN_API_MISMATCH: "PLUGIN_API_MISMATCH",
30
- PLUGIN_MISSING_DEPENDENCY: "PLUGIN_MISSING_DEPENDENCY",
31
- // Agent
32
- AGENT_ITERATION_LIMIT: "AGENT_ITERATION_LIMIT",
33
- AGENT_CONTEXT_OVERFLOW: "AGENT_CONTEXT_OVERFLOW",
34
- AGENT_ABORTED: "AGENT_ABORTED",
35
- AGENT_RUN_FAILED: "AGENT_RUN_FAILED",
36
- // Session
37
- SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
38
- SESSION_CORRUPTED: "SESSION_CORRUPTED",
39
- SESSION_WRITE_FAILED: "SESSION_WRITE_FAILED",
40
- // Container / Registry
41
- CONTAINER_TOKEN_ALREADY_BOUND: "CONTAINER_TOKEN_ALREADY_BOUND",
42
- CONTAINER_TOKEN_NOT_BOUND: "CONTAINER_TOKEN_NOT_BOUND",
43
- CONTAINER_CIRCULAR_DEPENDENCY: "CONTAINER_CIRCULAR_DEPENDENCY",
44
- REGISTRY_DUPLICATE: "REGISTRY_DUPLICATE",
45
- REGISTRY_NOT_FOUND: "REGISTRY_NOT_FOUND",
46
- REGISTRY_INVALID: "REGISTRY_INVALID",
47
- // File system
48
- FS_READ_FAILED: "FS_READ_FAILED",
49
- FS_WRITE_FAILED: "FS_WRITE_FAILED",
50
- FS_MKDIR_FAILED: "FS_MKDIR_FAILED",
51
- FS_DELETE_FAILED: "FS_DELETE_FAILED",
52
- FS_ATOMIC_WRITE_FAILED: "FS_ATOMIC_WRITE_FAILED",
53
- // SDD (Spec-Driven Development)
54
- SDD_VALIDATION_FAILED: "SDD_VALIDATION_FAILED",
55
- SDD_PARSE_FAILED: "SDD_PARSE_FAILED",
56
- SDD_INVALID_STATE: "SDD_INVALID_STATE",
57
- SDD_NOT_READY: "SDD_NOT_READY",
58
- // General
59
- VALIDATION_ERROR: "VALIDATION_ERROR",
60
- PARSE_FAILED: "PARSE_FAILED",
61
- UNKNOWN: "UNKNOWN"
62
- };
63
- var WrongStackError = class extends Error {
64
- code;
65
- subsystem;
66
- severity;
67
- recoverable;
68
- context;
69
- constructor(opts) {
70
- super(opts.message, { cause: opts.cause });
71
- this.name = "WrongStackError";
72
- this.code = opts.code;
73
- this.subsystem = opts.subsystem;
74
- this.severity = opts.severity ?? "error";
75
- this.recoverable = opts.recoverable ?? false;
76
- this.context = opts.context;
77
- }
78
- /**
79
- * Render a one-line user-facing description.
80
- * Subclasses should override for domain-specific formatting.
81
- */
82
- describe() {
83
- const ctx = this.context ? ` ${formatContext(this.context)}` : "";
84
- return `${this.code}: ${this.message}${ctx}`;
85
- }
86
- };
87
- function formatContext(ctx) {
88
- const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
89
- return parts.length > 0 ? `[${parts.join(" ")}]` : "";
90
- }
91
-
92
- // src/kernel/container.ts
93
- var Container = class {
94
- entries = /* @__PURE__ */ new Map();
95
- /**
96
- * Tokens currently mid-resolve. Tracked so we can detect circular
97
- * dependencies (A → B → A) and throw a structured error instead of
98
- * overflowing the call stack with "Maximum call stack size exceeded".
99
- *
100
- * Not a memoization cache — the per-entry `cache` field is the source
101
- * of truth for "have I built this before?". This set only lives for
102
- * the duration of a single resolve call.
103
- */
104
- resolving = /* @__PURE__ */ new Set();
105
- bind(token, factory, opts = {}) {
106
- if (this.entries.has(token)) {
107
- throw new WrongStackError({
108
- message: `Container: token "${token.description ?? "unknown"}" already bound`,
109
- code: ERROR_CODES.CONTAINER_TOKEN_ALREADY_BOUND,
110
- subsystem: "container",
111
- context: { token: token.description }
112
- });
113
- }
114
- this.entries.set(token, {
115
- factory,
116
- singleton: opts.singleton ?? true,
117
- decorators: [],
118
- hasCache: false,
119
- owner: opts.owner ?? "core"
120
- });
121
- }
122
- override(token, factory, opts = {}) {
123
- const existing = this.entries.get(token);
124
- if (!existing) {
125
- throw new WrongStackError({
126
- message: `Container: cannot override "${token.description ?? "unknown"}" \u2014 not bound`,
127
- code: ERROR_CODES.CONTAINER_TOKEN_NOT_BOUND,
128
- subsystem: "container",
129
- context: { token: token.description }
130
- });
131
- }
132
- this.entries.set(token, {
133
- factory,
134
- singleton: opts.singleton ?? existing.singleton,
135
- decorators: existing.decorators,
136
- hasCache: false,
137
- owner: opts.owner ?? existing.owner
138
- });
139
- }
140
- decorate(token, decorator, owner = "core") {
141
- const existing = this.entries.get(token);
142
- if (!existing) {
143
- throw new WrongStackError({
144
- message: `Container: cannot decorate "${token.description ?? "unknown"}" \u2014 not bound`,
145
- code: ERROR_CODES.CONTAINER_TOKEN_NOT_BOUND,
146
- subsystem: "container",
147
- context: { token: token.description }
148
- });
149
- }
150
- existing.decorators.push(decorator);
151
- existing.cache = void 0;
152
- existing.hasCache = false;
153
- existing.owner = `${existing.owner}+${owner}`;
154
- }
155
- resolve(token) {
156
- const entry = this.entries.get(token);
157
- if (!entry) {
158
- throw new WrongStackError({
159
- message: `Container: token "${token.description ?? "unknown"}" not bound`,
160
- code: ERROR_CODES.CONTAINER_TOKEN_NOT_BOUND,
161
- subsystem: "container",
162
- context: { token: token.description }
163
- });
164
- }
165
- if (entry.singleton && entry.hasCache) {
166
- return entry.cache;
167
- }
168
- if (this.resolving.has(token)) {
169
- const cycle = this.describeCycle(token);
170
- throw new WrongStackError({
171
- message: `Container: circular dependency detected \u2014 ${cycle}`,
172
- code: ERROR_CODES.CONTAINER_CIRCULAR_DEPENDENCY,
173
- subsystem: "container",
174
- context: { token: token.description, cycle }
175
- });
176
- }
177
- this.resolving.add(token);
178
- try {
179
- let value = entry.factory(this);
180
- for (const d of entry.decorators) {
181
- value = d(value, this);
182
- }
183
- if (entry.singleton) {
184
- entry.cache = value;
185
- entry.hasCache = true;
186
- }
187
- return value;
188
- } finally {
189
- this.resolving.delete(token);
190
- }
191
- }
192
- /**
193
- * Build a human-readable description of the dependency cycle that
194
- * caused the resolution to re-enter. Lists the tokens in the order
195
- * they were entered, then appends the re-entered token to close the
196
- * loop. Falls back to a generic message if the resolving set is
197
- * somehow empty (shouldn't happen, but defensive).
198
- */
199
- describeCycle(reentry) {
200
- const descs = [];
201
- const seen = /* @__PURE__ */ new Set();
202
- for (const t2 of this.resolving) {
203
- if (seen.has(t2)) continue;
204
- seen.add(t2);
205
- descs.push(t2.description ?? "unknown");
206
- }
207
- descs.push(reentry.description ?? "unknown");
208
- return descs.join(" \u2192 ");
209
- }
210
- has(token) {
211
- return this.entries.has(token);
212
- }
213
- /**
214
- * Resolve a token if it is bound, otherwise return undefined.
215
- * Unlike `resolve()`, this does not throw if the token is unbound.
216
- *
217
- * Single map lookup — `has()` + `resolve()` would walk the entries
218
- * map twice. The bound check is folded into the resolve path so a
219
- * non-existent token can never enter the `resolving` cycle-detection
220
- * set.
221
- */
222
- safeResolve(token) {
223
- if (!this.entries.has(token)) return void 0;
224
- return this.resolve(token);
225
- }
226
- ownerOf(token) {
227
- return this.entries.get(token)?.owner;
228
- }
229
- /**
230
- * Remove a token's binding (along with any decorators stacked on it).
231
- * Returns true if the token existed. Use this to withdraw temporary
232
- * bindings installed by a short-lived run or plugin — without it, the
233
- * entry persists in the map forever.
234
- */
235
- unbind(token) {
236
- return this.entries.delete(token);
237
- }
238
- /**
239
- * Drop every binding. Intended for tests and short-lived CLI invocations
240
- * that rebuild the container from scratch. Production code should prefer
241
- * `unbind` on the specific tokens it owns.
242
- */
243
- clear() {
244
- this.entries.clear();
245
- }
246
- list() {
247
- return Array.from(this.entries.entries()).map(([token, entry]) => ({
248
- token,
249
- owner: entry.owner
250
- }));
251
- }
252
- /**
253
- * Inspect a binding's full shape, including decorator count and whether
254
- * a singleton value is cached. Returns null if the token is unbound.
255
- * Decorator and factory function references are not exposed — only counts
256
- * and metadata, to keep internal state hidden.
257
- */
258
- inspect(token) {
259
- const entry = this.entries.get(token);
260
- if (!entry) return null;
261
- return {
262
- owner: entry.owner,
263
- singleton: entry.singleton,
264
- decoratorCount: entry.decorators.length,
265
- cached: entry.hasCache
266
- };
267
- }
268
- };
269
-
270
- // src/kernel/pipeline.ts
271
- var Pipeline = class {
272
- chain = [];
273
- errorHandler;
274
- /**
275
- * Optional operational logger. When set, the `swallow` error-boundary path
276
- * emits a structured warning so a swallowed middleware crash is never
277
- * completely silent (P2 #7, before-release.md). Without a logger, the
278
- * swallow path still works but the crash is invisible — host code should
279
- * always wire one.
280
- */
281
- logger;
282
- /**
283
- * Install an error boundary. When a middleware throws or rejects, the
284
- * handler is called and decides whether to swallow (continue with the
285
- * pre-handler value) or rethrow. Without a handler, errors propagate.
286
- *
287
- * Wire one per pipeline at boot — the host CLI typically installs a
288
- * single boundary that logs to the operational log and emits a
289
- * `pipeline.error` event for /diag.
290
- */
291
- setErrorHandler(handler) {
292
- this.errorHandler = handler;
293
- return this;
294
- }
295
- /**
296
- * Set the operational logger used by the swallow error-boundary path.
297
- * Without this, a swallowed middleware crash is completely silent.
298
- */
299
- setLogger(logger) {
300
- this.logger = logger;
301
- return this;
302
- }
303
- use(mw) {
304
- this.ensureUnique(mw.name);
305
- this.chain.push(mw);
306
- return this;
307
- }
308
- prepend(mw) {
309
- this.ensureUnique(mw.name);
310
- this.chain.unshift(mw);
311
- return this;
312
- }
313
- /**
314
- * Insert middleware at an explicit index. Out-of-range indices are clamped.
315
- * Use this when insertBefore/insertAfter are insufficient (e.g. to place
316
- * a middleware at a known position regardless of named targets).
317
- */
318
- insertAt(index, mw) {
319
- this.ensureUnique(mw.name);
320
- const idx = Math.max(0, Math.min(index, this.chain.length));
321
- this.chain.splice(idx, 0, mw);
322
- return this;
323
- }
324
- /**
325
- * Insert mw immediately before the first occurrence of target.
326
- * If called multiple times with the same target, each call inserts
327
- * before the target's current position — so after insertBefore('B', X)
328
- * then insertBefore('B', Y), the order is Y → X → B.
329
- */
330
- insertBefore(target, mw, opts) {
331
- this.ensureUnique(mw.name);
332
- const idx = this.indexOf(target, opts?.optional);
333
- if (idx === -1) return this;
334
- this.chain.splice(idx, 0, mw);
335
- return this;
336
- }
337
- /**
338
- * Insert mw immediately after the first occurrence of target.
339
- * If called multiple times with the same target, each call inserts
340
- * after the target's current position — so after insertAfter('B', X)
341
- * then insertAfter('B', Y), the order is B → X → Y.
342
- */
343
- insertAfter(target, mw, opts) {
344
- this.ensureUnique(mw.name);
345
- const idx = this.indexOf(target, opts?.optional);
346
- if (idx === -1) return this;
347
- this.chain.splice(idx + 1, 0, mw);
348
- return this;
349
- }
350
- replace(target, mw, opts) {
351
- if (mw.name !== target) this.ensureUnique(mw.name);
352
- const idx = this.indexOf(target, opts?.optional);
353
- if (idx === -1) return this;
354
- this.chain[idx] = mw;
355
- return this;
356
- }
357
- remove(name, opts) {
358
- const idx = this.indexOf(name, opts?.optional);
359
- if (idx === -1) return this;
360
- this.chain.splice(idx, 1);
361
- return this;
362
- }
363
- list() {
364
- return this.chain.map((m) => m.name);
365
- }
366
- size() {
367
- return this.chain.length;
368
- }
369
- /** Return a read-only view suitable for passing to plugins. */
370
- asReadonly() {
371
- const self = this;
372
- const view = Object.freeze({
373
- get size() {
374
- return self.chain.length;
375
- },
376
- list() {
377
- return Object.freeze(self.chain.map((m) => m.name));
378
- },
379
- run(input) {
380
- return self.run(input);
381
- }
382
- });
383
- return view;
384
- }
385
- async run(input) {
386
- let index = -1;
387
- const chain = this.chain;
388
- const errorHandler = this.errorHandler;
389
- const dispatch = async (i, value) => {
390
- if (i <= index) {
391
- const offender = chain[index]?.name;
392
- throw new WrongStackError({
393
- message: `Pipeline: next() called multiple times in "${offender}"`,
394
- code: ERROR_CODES.VALIDATION_ERROR,
395
- subsystem: "container",
396
- context: { middleware: offender }
397
- });
398
- }
399
- index = i;
400
- const mw = chain[i];
401
- if (!mw) return value;
402
- try {
403
- return await mw.handler(value, (v) => dispatch(i + 1, v));
404
- } catch (err) {
405
- if (!errorHandler) throw err;
406
- const policy = await errorHandler({ middleware: mw.name, owner: mw.owner, err });
407
- if (policy === "rethrow") throw err;
408
- this.logger?.warn("pipeline.error", {
409
- middleware: mw.name,
410
- owner: mw.owner,
411
- error: err instanceof Error ? err.message : String(err)
412
- });
413
- return value;
414
- }
415
- };
416
- return dispatch(0, input);
417
- }
418
- indexOf(name, optional = false) {
419
- const idx = this.chain.findIndex((m) => m.name === name);
420
- if (idx === -1 && !optional) {
421
- throw new WrongStackError({
422
- message: `Pipeline: middleware "${name}" not found`,
423
- code: ERROR_CODES.REGISTRY_NOT_FOUND,
424
- subsystem: "container",
425
- context: { middleware: name }
426
- });
427
- }
428
- return idx;
429
- }
430
- ensureUnique(name) {
431
- if (this.chain.some((m) => m.name === name)) {
432
- throw new WrongStackError({
433
- message: `Pipeline: middleware "${name}" already registered`,
434
- code: ERROR_CODES.REGISTRY_DUPLICATE,
435
- subsystem: "container",
436
- context: { middleware: name }
437
- });
438
- }
439
- }
440
- };
441
-
442
- // src/kernel/events.ts
443
- var MAX_WILDCARDS = 500;
444
- var MAX_NAMED_LISTENERS = 2e3;
445
- var EventBus = class {
446
- listeners = /* @__PURE__ */ new Map();
447
- wildcards = [];
448
- logger;
449
- /**
450
- * Dispatch arrays cached per event name, rebuilt lazily after a
451
- * subscription change. See {@link namedSnapshot}. Every mutation of
452
- * `listeners` must invalidate the matching entry, and every mutation of
453
- * `wildcards` must null `wildcardSnapshotCache` — a missed invalidation
454
- * means an emit dispatches to a stale listener set.
455
- */
456
- listenerSnapshots = /* @__PURE__ */ new Map();
457
- wildcardSnapshotCache = null;
458
- setLogger(logger) {
459
- this.logger = logger;
460
- }
461
- on(event, fn) {
462
- if (this.listenerCount() >= MAX_NAMED_LISTENERS) {
463
- this.logger?.error(
464
- `EventBus named listener limit (~${MAX_NAMED_LISTENERS}) reached \u2014 rejecting on("${event}"). Callers must dispose their named listeners to prevent unbounded memory growth.`
465
- );
466
- return () => {
467
- };
468
- }
469
- let set = this.listeners.get(event);
470
- if (!set) {
471
- set = /* @__PURE__ */ new Set();
472
- this.listeners.set(event, set);
473
- }
474
- set.add(fn);
475
- this.listenerSnapshots.delete(event);
476
- return () => this.off(event, fn);
477
- }
478
- off(event, fn) {
479
- const set = this.listeners.get(event);
480
- if (!set) return;
481
- set.delete(fn);
482
- this.listenerSnapshots.delete(event);
483
- if (set.size === 0) this.listeners.delete(event);
484
- }
485
- once(event, fn) {
486
- const wrapper = (payload) => {
487
- this.off(event, wrapper);
488
- fn(payload);
489
- };
490
- this.on(event, wrapper);
491
- return () => {
492
- this.off(event, wrapper);
493
- };
494
- }
495
- /**
496
- * Subscribe to all events, regardless of name. Short-hand for
497
- * `onPattern('*')`. Use for logging, debugging, or forwarding every
498
- * event to another bus (as FleetBus does).
499
- *
500
- * Returns an unsubscribe function.
501
- */
502
- onAny(fn, owner) {
503
- return this.onPattern("*", fn, owner);
504
- }
505
- /**
506
- * Subscribe to all events whose name matches a glob-style prefix.
507
- * `'tool.*'` matches `tool.started`, `tool.executed`, `tool.progress`, etc.
508
- * `'*'` matches every event.
509
- *
510
- * The handler receives `(eventName, payload)` with the event name as a
511
- * string and the payload as `unknown`. Use for logging, debugging, or
512
- * metrics collection across a family of events.
513
- *
514
- * Returns an unsubscribe function.
515
- */
516
- onPattern(pattern, fn, owner) {
517
- if (this.wildcards.length >= MAX_WILDCARDS) {
518
- this.logger?.error(
519
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onPattern("${pattern}")` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
520
- );
521
- return () => {
522
- };
523
- }
524
- const match = makePatternMatcher(pattern);
525
- const entry = { match, fn, owner };
526
- this.wildcards.push(entry);
527
- this.wildcardSnapshotCache = null;
528
- return () => {
529
- const idx = this.wildcards.indexOf(entry);
530
- if (idx >= 0) {
531
- this.wildcards.splice(idx, 1);
532
- this.wildcardSnapshotCache = null;
533
- }
534
- };
535
- }
536
- /**
537
- * Subscribe to all events whose name matches a RegExp.
538
- * More flexible than `onPattern` — use when you need regex features
539
- * (alternation, character classes, capture groups).
540
- *
541
- * Returns an unsubscribe function.
542
- */
543
- onRegex(regex, fn, owner) {
544
- if (this.wildcards.length >= MAX_WILDCARDS) {
545
- this.logger?.error(
546
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onRegex(${regex})` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
547
- );
548
- return () => {
549
- };
550
- }
551
- const entry = { match: (e) => regex.test(e), fn, owner };
552
- this.wildcards.push(entry);
553
- this.wildcardSnapshotCache = null;
554
- return () => {
555
- const idx = this.wildcards.indexOf(entry);
556
- if (idx >= 0) {
557
- this.wildcards.splice(idx, 1);
558
- this.wildcardSnapshotCache = null;
559
- }
560
- };
561
- }
562
- emit(event, payload) {
563
- const snapshot = this.namedSnapshot(event);
564
- if (snapshot !== void 0) {
565
- for (const fn of snapshot) {
566
- try {
567
- fn(payload);
568
- } catch (err) {
569
- this.logger?.error(`EventBus listener for "${event}" threw`, err);
570
- }
571
- }
572
- }
573
- if (this.wildcards.length > 0) {
574
- const name = event;
575
- for (const { match, fn } of this.wildcardSnapshot()) {
576
- if (!match(name)) continue;
577
- try {
578
- fn(name, payload);
579
- } catch (err) {
580
- this.logger?.error(`EventBus wildcard listener for "${name}" threw`, err);
581
- }
582
- }
583
- }
584
- }
585
- /**
586
- * Dispatch array for one event name, or `undefined` when nothing is
587
- * subscribed.
588
- *
589
- * Dispatch iterates a stable array rather than the live Set so a listener
590
- * that subscribes or unsubscribes mid-emit cannot change what this round
591
- * delivers: an addition fires from the next emit, a removal may still fire
592
- * this round. That is the contract callers rely on, and it is unchanged.
593
- *
594
- * What changed is who pays for it. Building the array per emit did O(number
595
- * of listeners) copying on every event, including `tool.progress` and
596
- * streaming deltas — the highest-frequency paths in the process. The array
597
- * is now cached and rebuilt only when the subscription set actually changes,
598
- * which is wiring time and essentially never during a run. Measured at 2M
599
- * emits with 12 named + 6 wildcard listeners: 207 ms → 141 ms.
600
- *
601
- * This is a throughput win, not a footprint one: the per-emit arrays died in
602
- * the nursery and never showed up as retained heap (measured heap growth was
603
- * the same either way). Do not cite this as a memory fix.
604
- *
605
- * A mutation during dispatch invalidates the cache for the *next* emit while
606
- * the in-flight loop keeps walking the array it started with — which is
607
- * exactly the snapshot semantics described above.
608
- */
609
- namedSnapshot(event) {
610
- const cached = this.listenerSnapshots.get(event);
611
- if (cached !== void 0) return cached;
612
- const set = this.listeners.get(event);
613
- if (!set || set.size === 0) return void 0;
614
- const snapshot = [...set];
615
- this.listenerSnapshots.set(event, snapshot);
616
- return snapshot;
617
- }
618
- /** Wildcard counterpart to {@link namedSnapshot}; same caching rationale. */
619
- wildcardSnapshot() {
620
- this.wildcardSnapshotCache ??= this.wildcards.slice();
621
- return this.wildcardSnapshotCache;
622
- }
623
- /**
624
- * Emit a plugin-defined event that is intentionally outside EventMap.
625
- * Custom events are delivered to wildcard/pattern listeners only; typed
626
- * listeners remain reserved for core EventMap keys.
627
- */
628
- emitCustom(event, payload) {
629
- if (this.wildcards.length === 0) return;
630
- for (const { match, fn } of this.wildcardSnapshot()) {
631
- if (!match(event)) continue;
632
- try {
633
- fn(event, payload);
634
- } catch (err) {
635
- this.logger?.error(`EventBus wildcard listener for "${event}" threw`, err);
636
- }
637
- }
638
- }
639
- clear() {
640
- this.listeners.clear();
641
- this.wildcards.length = 0;
642
- this.listenerSnapshots.clear();
643
- this.wildcardSnapshotCache = null;
644
- }
645
- /**
646
- * V2-D: introspection helper. Pass an `event` to count handlers for a
647
- * single key, or omit to get the total across every event. Used by the
648
- * leak-detection smoke test to flag handler accumulation across runs.
649
- * Does NOT include wildcard listeners.
650
- */
651
- listenerCount(event) {
652
- if (event !== void 0) return this.listeners.get(event)?.size ?? 0;
653
- let total = 0;
654
- for (const set of this.listeners.values()) total += set.size;
655
- return total;
656
- }
657
- /**
658
- * Number of wildcard listeners currently registered.
659
- */
660
- wildcardCount() {
661
- return this.wildcards.length;
662
- }
663
- /**
664
- * True if anything would receive an emit for `event` — a named listener
665
- * OR a wildcard/regex pattern that matches the event name. Unlike
666
- * `listenerCount`, this DOES account for wildcards, so callers that gate
667
- * behavior on "is anyone listening?" (e.g. SubagentBudget deciding whether
668
- * to negotiate a soft limit vs hard-stop) don't misfire when the only
669
- * subscriber is a pattern listener like the FleetBus's `onPattern('*')`.
670
- */
671
- hasListenerFor(event) {
672
- if ((this.listeners.get(event)?.size ?? 0) > 0) return true;
673
- return this.wildcards.some((w) => w.match(event));
674
- }
675
- };
676
- var ScopedEventBus = class extends EventBus {
677
- // Track registrations by a unique counter key so that EventBus.once()'s
678
- // internal listener-removal doesn't affect our tracking (once removes the
679
- // fn from EventBus but we still need to call our unsub during teardown).
680
- registrations = /* @__PURE__ */ new Map();
681
- nextKey = 0;
682
- /**
683
- * Identical to `EventBus.on` but the listener is tracked so that
684
- * `teardown()` will remove it automatically.
685
- */
686
- on(event, fn) {
687
- const key = this.nextKey++;
688
- const unsub = super.on(event, fn);
689
- this.registrations.set(key, unsub);
690
- return () => {
691
- this.registrations.delete(key);
692
- unsub();
693
- };
694
- }
695
- /**
696
- * Identical to `EventBus.once` but the listener is tracked so that
697
- * `teardown()` will remove it automatically.
698
- *
699
- * Uses EventBus's public API directly to avoid triggering our own `on()`
700
- * override (which would consume a key slot for the wrapper, then orphan
701
- * our registration entry under a different key).
702
- *
703
- * When the wrapper fires, it cleans up BOTH the underlying EventBus
704
- * listener AND the tracking entry — so `scopedListenerCount` returns to
705
- * its pre-`once()` value without requiring the caller to invoke the
706
- * returned unsubscribe. The returned `unsub` is still safe to call
707
- * after auto-removal (its delete is a no-op and its off() finds
708
- * nothing to remove).
709
- */
710
- once(event, fn) {
711
- const key = this.nextKey++;
712
- const wrapper = (payload) => {
713
- EventBus.prototype.off.call(this, event, wrapper);
714
- this.registrations.delete(key);
715
- fn(payload);
716
- };
717
- EventBus.prototype.on.call(this, event, wrapper);
718
- const unsub = () => {
719
- this.registrations.delete(key);
720
- EventBus.prototype.off.call(this, event, wrapper);
721
- };
722
- this.registrations.set(key, unsub);
723
- return unsub;
724
- }
725
- /**
726
- * Subscribe to all events. Alias for `onPattern('*')` — the listener is
727
- * tracked so that `teardown()` will remove it automatically.
728
- */
729
- onAny(fn, owner) {
730
- if (this.wildcards.length >= MAX_WILDCARDS) {
731
- this.logger?.error(
732
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onAny()` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
733
- );
734
- return () => {
735
- };
736
- }
737
- const key = this.nextKey++;
738
- const unsub = EventBus.prototype.onPattern.call(this, "*", fn, owner);
739
- this.registrations.set(key, unsub);
740
- return () => {
741
- this.registrations.delete(key);
742
- unsub();
743
- };
744
- }
745
- /**
746
- * Identical to `EventBus.onPattern` but the listener is tracked so that
747
- * `teardown()` will remove it automatically.
748
- */
749
- onPattern(pattern, fn, owner) {
750
- if (this.wildcards.length >= MAX_WILDCARDS) {
751
- this.logger?.error(
752
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onPattern("${pattern}")` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
753
- );
754
- return () => {
755
- };
756
- }
757
- const key = this.nextKey++;
758
- const unsub = super.onPattern(pattern, fn, owner);
759
- this.registrations.set(key, unsub);
760
- return () => {
761
- this.registrations.delete(key);
762
- unsub();
763
- };
764
- }
765
- /**
766
- * Identical to `EventBus.onRegex` but the listener is tracked so that
767
- * `teardown()` will remove it automatically.
768
- */
769
- onRegex(regex, fn, owner) {
770
- if (this.wildcards.length >= MAX_WILDCARDS) {
771
- this.logger?.error(
772
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onRegex(${regex})` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
773
- );
774
- return () => {
775
- };
776
- }
777
- const key = this.nextKey++;
778
- const unsub = super.onRegex(regex, fn, owner);
779
- this.registrations.set(key, unsub);
780
- return () => {
781
- this.registrations.delete(key);
782
- unsub();
783
- };
784
- }
785
- /**
786
- * Remove every listener that was registered through this scoped bus.
787
- * Idempotent — calling it multiple times is safe.
788
- *
789
- * Also available as `[Symbol.dispose]` for explicit resource management:
790
- * ```ts
791
- * using scope = new ScopedEventBus();
792
- * scope.on('tool.executed', handler);
793
- * // automatically teardown()'d when scope exits
794
- * ```
795
- */
796
- teardown() {
797
- for (const unsub of this.registrations.values()) {
798
- try {
799
- unsub();
800
- } catch {
801
- }
802
- }
803
- this.registrations.clear();
804
- this.clear();
805
- }
806
- /** Alias for `teardown()` — enables `using new ScopedEventBus()` in Node ≥ 22. */
807
- [Symbol.dispose]() {
808
- this.teardown();
809
- }
810
- /** Number of tracked registrations. */
811
- get scopedListenerCount() {
812
- return this.registrations.size;
813
- }
814
- };
815
- var MATCH_ALL = () => true;
816
- function makePatternMatcher(pattern) {
817
- if (pattern === "*") return MATCH_ALL;
818
- if (pattern.endsWith(".*")) {
819
- const prefix = pattern.slice(0, -2);
820
- return (e) => e.startsWith(`${prefix}.`);
821
- }
822
- return (e) => e === pattern;
823
- }
824
-
825
- // src/kernel/tokens.ts
826
- var t = (name) => /* @__PURE__ */ Symbol.for(`@wrongstack/core/kernel#${name}`);
827
- var TOKENS = {
828
- Logger: t("Logger"),
829
- TokenCounter: t("TokenCounter"),
830
- SessionStore: t("SessionStore"),
831
- /**
832
- * @deprecated Token name retained for compatibility.
833
- * The resolved value IS a `MemoryPort`, NOT the legacy `MemoryStore` interface.
834
- * Callers that cast or destructure as `MemoryStore` will lose `getCapability`,
835
- * `health`, `dispose`, and the narrowed `withTraceId(): MemoryPort` return type.
836
- * Port implementations are opaque — query optional features via `getCapability`.
837
- * Use `TOKENS.MemoryPort` instead.
838
- */
839
- MemoryStore: t("MemoryStore"),
840
- /** Production memory backend. Use instead of the deprecated `MemoryStore` token. */
841
- MemoryPort: t("MemoryPort"),
842
- PermissionPolicy: t("PermissionPolicy"),
843
- Compactor: t("Compactor"),
844
- PathResolver: t("PathResolver"),
845
- ConfigLoader: t("ConfigLoader"),
846
- ConfigStore: t("ConfigStore"),
847
- FallbackProfileManager: t("FallbackProfileManager"),
848
- /** Shared (provider, model) health tracker; fallback extensions read/write it. */
849
- ProviderModelStatusTracker: t("ProviderModelStatusTracker"),
850
- Renderer: t("Renderer"),
851
- InputReader: t("InputReader"),
852
- ErrorHandler: t("ErrorHandler"),
853
- RetryPolicy: t("RetryPolicy"),
854
- SkillLoader: t("SkillLoader"),
855
- PromptLoader: t("PromptLoader"),
856
- SystemPromptBuilder: t("SystemPromptBuilder"),
857
- SecretScrubber: t("SecretScrubber"),
858
- ModelsRegistry: t("ModelsRegistry"),
859
- ModeStore: t("ModeStore"),
860
- /** Replaces the entire provider call layer — retry, streaming, tracing. */
861
- ProviderRunner: t("ProviderRunner"),
862
- /** Optional git-worktree lifecycle manager (per-phase isolation in Goal). */
863
- WorktreeManager: t("WorktreeManager"),
864
- /** Optional global Brain arbiter for policy/decision escalation. */
865
- BrainArbiter: t("BrainArbiter"),
866
- /** Lifecycle hook registry (shell + in-process hooks). */
867
- HookRegistry: t("HookRegistry")
868
- };
869
-
870
- // src/kernel/run-controller.ts
871
- var RunController = class {
872
- ctrl = new AbortController();
873
- hooks = [];
874
- disposed = false;
875
- hooksDrained = false;
876
- errorSink;
877
- /**
878
- * Shared promise for the in-flight `runHooks()` call. Set by the abort
879
- * signal handler or by `dispose()`, whichever fires first. Both callers
880
- * await the same promise so abort-triggered async cleanup (session flush,
881
- * socket close) completes before `dispose()` resolves.
882
- */
883
- _hooksPromise = null;
884
- constructor(opts = {}) {
885
- this.errorSink = opts.errorSink ?? ((err, where) => {
886
- console.warn(JSON.stringify({
887
- level: "warn",
888
- event: "run.cleanup_hook_failed",
889
- where,
890
- message: toErrorMessage(err),
891
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
892
- }));
893
- });
894
- if (opts.parentSignal) {
895
- const parent = opts.parentSignal;
896
- if (parent.aborted) {
897
- this.ctrl.abort(parent.reason);
898
- queueMicrotask(() => {
899
- void this.runHooks();
900
- });
901
- } else {
902
- const onParentAbort = () => this.ctrl.abort(parent.reason);
903
- parent.addEventListener("abort", onParentAbort, { once: true });
904
- this.onAbort(() => parent.removeEventListener("abort", onParentAbort));
905
- }
906
- }
907
- this.ctrl.signal.addEventListener(
908
- "abort",
909
- () => {
910
- this.ensureHooksRun();
911
- },
912
- { once: true }
913
- );
914
- }
915
- get signal() {
916
- return this.ctrl.signal;
917
- }
918
- get aborted() {
919
- return this.ctrl.signal.aborted;
920
- }
921
- abort(reason) {
922
- if (this.ctrl.signal.aborted) return;
923
- this.ctrl.abort(reason);
924
- }
925
- /**
926
- * Register a teardown hook. Returns an unsubscribe function so callers
927
- * can opt out before the hook fires (e.g. when a tool finishes cleanly
928
- * before abort happens).
929
- *
930
- * If the controller has already drained its hooks (a prior abort() or
931
- * dispose() ran), the new hook is fired immediately on a best-effort
932
- * basis — otherwise a hook registered after teardown would be silently
933
- * dropped and the resource it cleans up would leak. The returned
934
- * unsubscribe is a no-op in that case (the hook has already run). Errors
935
- * from the immediate run are routed through `errorSink` like any other
936
- * cleanup failure.
937
- */
938
- onAbort(fn) {
939
- if (this.hooksDrained) {
940
- void (async () => {
941
- try {
942
- await fn();
943
- } catch (err) {
944
- this.errorSink(err, "RunController.onAbort(post-drain)");
945
- }
946
- })();
947
- return () => {
948
- };
949
- }
950
- this.hooks.push(fn);
951
- return () => {
952
- const idx = this.hooks.indexOf(fn);
953
- if (idx !== -1) this.hooks.splice(idx, 1);
954
- };
955
- }
956
- /**
957
- * Fire cleanup hooks and tear down listeners — called when the run
958
- * ends *normally* so cleanup happens regardless of outcome. Subsequent
959
- * aborts become no-ops.
960
- */
961
- async dispose() {
962
- if (this.disposed) return;
963
- this.disposed = true;
964
- await this.ensureHooksRun();
965
- }
966
- /**
967
- * Start running hooks (or wait for an already-in-flight run) and return
968
- * a promise that settles when all hooks complete. Both the abort signal
969
- * handler and `dispose()` call this so they share the same underlying
970
- * promise — abort-triggered async cleanup (session flush, socket close)
971
- * is guaranteed to finish before `dispose()` resolves.
972
- */
973
- ensureHooksRun() {
974
- if (!this._hooksPromise) {
975
- this._hooksPromise = this.runHooks();
976
- }
977
- return this._hooksPromise;
978
- }
979
- async runHooks() {
980
- if (this.hooksDrained) return;
981
- this.hooksDrained = true;
982
- const snapshot = this.hooks.splice(0, this.hooks.length).reverse();
983
- if (snapshot.length === 0) return;
984
- await Promise.allSettled(
985
- snapshot.map(
986
- (hook) => Promise.resolve().then(() => hook()).catch((err) => {
987
- this.errorSink(err, "RunController.dispose");
988
- })
989
- )
990
- );
991
- }
992
- };
1
+ import {
2
+ Container
3
+ } from "../chunk-O74MJHXU.js";
4
+ import {
5
+ EventBus,
6
+ ScopedEventBus
7
+ } from "../chunk-4LJJYBAU.js";
8
+ import {
9
+ Pipeline,
10
+ RunController,
11
+ TOKENS
12
+ } from "../chunk-Y2GE5TSI.js";
13
+ import "../chunk-WOEZV3Q4.js";
14
+ import "../chunk-TBRL2RCC.js";
993
15
  export {
994
16
  Container,
995
17
  EventBus,