@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
@@ -0,0 +1,355 @@
1
+ import {
2
+ ERROR_CODES,
3
+ WrongStackError
4
+ } from "./chunk-WOEZV3Q4.js";
5
+ import {
6
+ toErrorMessage
7
+ } from "./chunk-TBRL2RCC.js";
8
+
9
+ // src/kernel/run-controller.ts
10
+ var RunController = class {
11
+ ctrl = new AbortController();
12
+ hooks = [];
13
+ disposed = false;
14
+ hooksDrained = false;
15
+ errorSink;
16
+ /**
17
+ * Shared promise for the in-flight `runHooks()` call. Set by the abort
18
+ * signal handler or by `dispose()`, whichever fires first. Both callers
19
+ * await the same promise so abort-triggered async cleanup (session flush,
20
+ * socket close) completes before `dispose()` resolves.
21
+ */
22
+ _hooksPromise = null;
23
+ constructor(opts = {}) {
24
+ this.errorSink = opts.errorSink ?? ((err, where) => {
25
+ console.warn(JSON.stringify({
26
+ level: "warn",
27
+ event: "run.cleanup_hook_failed",
28
+ where,
29
+ message: toErrorMessage(err),
30
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
31
+ }));
32
+ });
33
+ if (opts.parentSignal) {
34
+ const parent = opts.parentSignal;
35
+ if (parent.aborted) {
36
+ this.ctrl.abort(parent.reason);
37
+ queueMicrotask(() => {
38
+ void this.runHooks();
39
+ });
40
+ } else {
41
+ const onParentAbort = () => this.ctrl.abort(parent.reason);
42
+ parent.addEventListener("abort", onParentAbort, { once: true });
43
+ this.onAbort(() => parent.removeEventListener("abort", onParentAbort));
44
+ }
45
+ }
46
+ this.ctrl.signal.addEventListener(
47
+ "abort",
48
+ () => {
49
+ this.ensureHooksRun();
50
+ },
51
+ { once: true }
52
+ );
53
+ }
54
+ get signal() {
55
+ return this.ctrl.signal;
56
+ }
57
+ get aborted() {
58
+ return this.ctrl.signal.aborted;
59
+ }
60
+ abort(reason) {
61
+ if (this.ctrl.signal.aborted) return;
62
+ this.ctrl.abort(reason);
63
+ }
64
+ /**
65
+ * Register a teardown hook. Returns an unsubscribe function so callers
66
+ * can opt out before the hook fires (e.g. when a tool finishes cleanly
67
+ * before abort happens).
68
+ *
69
+ * If the controller has already drained its hooks (a prior abort() or
70
+ * dispose() ran), the new hook is fired immediately on a best-effort
71
+ * basis — otherwise a hook registered after teardown would be silently
72
+ * dropped and the resource it cleans up would leak. The returned
73
+ * unsubscribe is a no-op in that case (the hook has already run). Errors
74
+ * from the immediate run are routed through `errorSink` like any other
75
+ * cleanup failure.
76
+ */
77
+ onAbort(fn) {
78
+ if (this.hooksDrained) {
79
+ void (async () => {
80
+ try {
81
+ await fn();
82
+ } catch (err) {
83
+ this.errorSink(err, "RunController.onAbort(post-drain)");
84
+ }
85
+ })();
86
+ return () => {
87
+ };
88
+ }
89
+ this.hooks.push(fn);
90
+ return () => {
91
+ const idx = this.hooks.indexOf(fn);
92
+ if (idx !== -1) this.hooks.splice(idx, 1);
93
+ };
94
+ }
95
+ /**
96
+ * Fire cleanup hooks and tear down listeners — called when the run
97
+ * ends *normally* so cleanup happens regardless of outcome. Subsequent
98
+ * aborts become no-ops.
99
+ */
100
+ async dispose() {
101
+ if (this.disposed) return;
102
+ this.disposed = true;
103
+ await this.ensureHooksRun();
104
+ }
105
+ /**
106
+ * Start running hooks (or wait for an already-in-flight run) and return
107
+ * a promise that settles when all hooks complete. Both the abort signal
108
+ * handler and `dispose()` call this so they share the same underlying
109
+ * promise — abort-triggered async cleanup (session flush, socket close)
110
+ * is guaranteed to finish before `dispose()` resolves.
111
+ */
112
+ ensureHooksRun() {
113
+ if (!this._hooksPromise) {
114
+ this._hooksPromise = this.runHooks();
115
+ }
116
+ return this._hooksPromise;
117
+ }
118
+ async runHooks() {
119
+ if (this.hooksDrained) return;
120
+ this.hooksDrained = true;
121
+ const snapshot = this.hooks.splice(0, this.hooks.length).reverse();
122
+ if (snapshot.length === 0) return;
123
+ await Promise.allSettled(
124
+ snapshot.map(
125
+ (hook) => Promise.resolve().then(() => hook()).catch((err) => {
126
+ this.errorSink(err, "RunController.dispose");
127
+ })
128
+ )
129
+ );
130
+ }
131
+ };
132
+
133
+ // src/kernel/tokens.ts
134
+ var t = (name) => /* @__PURE__ */ Symbol.for(`@wrongstack/core/kernel#${name}`);
135
+ var TOKENS = {
136
+ Logger: t("Logger"),
137
+ TokenCounter: t("TokenCounter"),
138
+ SessionStore: t("SessionStore"),
139
+ /**
140
+ * @deprecated Token name retained for compatibility.
141
+ * The resolved value IS a `MemoryPort`, NOT the legacy `MemoryStore` interface.
142
+ * Callers that cast or destructure as `MemoryStore` will lose `getCapability`,
143
+ * `health`, `dispose`, and the narrowed `withTraceId(): MemoryPort` return type.
144
+ * Port implementations are opaque — query optional features via `getCapability`.
145
+ * Use `TOKENS.MemoryPort` instead.
146
+ */
147
+ MemoryStore: t("MemoryStore"),
148
+ /** Production memory backend. Use instead of the deprecated `MemoryStore` token. */
149
+ MemoryPort: t("MemoryPort"),
150
+ PermissionPolicy: t("PermissionPolicy"),
151
+ Compactor: t("Compactor"),
152
+ PathResolver: t("PathResolver"),
153
+ ConfigLoader: t("ConfigLoader"),
154
+ ConfigStore: t("ConfigStore"),
155
+ FallbackProfileManager: t("FallbackProfileManager"),
156
+ /** Shared (provider, model) health tracker; fallback extensions read/write it. */
157
+ ProviderModelStatusTracker: t("ProviderModelStatusTracker"),
158
+ Renderer: t("Renderer"),
159
+ InputReader: t("InputReader"),
160
+ ErrorHandler: t("ErrorHandler"),
161
+ RetryPolicy: t("RetryPolicy"),
162
+ SkillLoader: t("SkillLoader"),
163
+ PromptLoader: t("PromptLoader"),
164
+ SystemPromptBuilder: t("SystemPromptBuilder"),
165
+ SecretScrubber: t("SecretScrubber"),
166
+ ModelsRegistry: t("ModelsRegistry"),
167
+ ModeStore: t("ModeStore"),
168
+ /** Replaces the entire provider call layer — retry, streaming, tracing. */
169
+ ProviderRunner: t("ProviderRunner"),
170
+ /** Optional git-worktree lifecycle manager (per-phase isolation in Goal). */
171
+ WorktreeManager: t("WorktreeManager"),
172
+ /** Optional global Brain arbiter for policy/decision escalation. */
173
+ BrainArbiter: t("BrainArbiter"),
174
+ /** Lifecycle hook registry (shell + in-process hooks). */
175
+ HookRegistry: t("HookRegistry")
176
+ };
177
+
178
+ // src/kernel/pipeline.ts
179
+ var Pipeline = class {
180
+ chain = [];
181
+ errorHandler;
182
+ /**
183
+ * Optional operational logger. When set, the `swallow` error-boundary path
184
+ * emits a structured warning so a swallowed middleware crash is never
185
+ * completely silent (P2 #7, before-release.md). Without a logger, the
186
+ * swallow path still works but the crash is invisible — host code should
187
+ * always wire one.
188
+ */
189
+ logger;
190
+ /**
191
+ * Install an error boundary. When a middleware throws or rejects, the
192
+ * handler is called and decides whether to swallow (continue with the
193
+ * pre-handler value) or rethrow. Without a handler, errors propagate.
194
+ *
195
+ * Wire one per pipeline at boot — the host CLI typically installs a
196
+ * single boundary that logs to the operational log and emits a
197
+ * `pipeline.error` event for /diag.
198
+ */
199
+ setErrorHandler(handler) {
200
+ this.errorHandler = handler;
201
+ return this;
202
+ }
203
+ /**
204
+ * Set the operational logger used by the swallow error-boundary path.
205
+ * Without this, a swallowed middleware crash is completely silent.
206
+ */
207
+ setLogger(logger) {
208
+ this.logger = logger;
209
+ return this;
210
+ }
211
+ use(mw) {
212
+ this.ensureUnique(mw.name);
213
+ this.chain.push(mw);
214
+ return this;
215
+ }
216
+ prepend(mw) {
217
+ this.ensureUnique(mw.name);
218
+ this.chain.unshift(mw);
219
+ return this;
220
+ }
221
+ /**
222
+ * Insert middleware at an explicit index. Out-of-range indices are clamped.
223
+ * Use this when insertBefore/insertAfter are insufficient (e.g. to place
224
+ * a middleware at a known position regardless of named targets).
225
+ */
226
+ insertAt(index, mw) {
227
+ this.ensureUnique(mw.name);
228
+ const idx = Math.max(0, Math.min(index, this.chain.length));
229
+ this.chain.splice(idx, 0, mw);
230
+ return this;
231
+ }
232
+ /**
233
+ * Insert mw immediately before the first occurrence of target.
234
+ * If called multiple times with the same target, each call inserts
235
+ * before the target's current position — so after insertBefore('B', X)
236
+ * then insertBefore('B', Y), the order is Y → X → B.
237
+ */
238
+ insertBefore(target, mw, opts) {
239
+ this.ensureUnique(mw.name);
240
+ const idx = this.indexOf(target, opts?.optional);
241
+ if (idx === -1) return this;
242
+ this.chain.splice(idx, 0, mw);
243
+ return this;
244
+ }
245
+ /**
246
+ * Insert mw immediately after the first occurrence of target.
247
+ * If called multiple times with the same target, each call inserts
248
+ * after the target's current position — so after insertAfter('B', X)
249
+ * then insertAfter('B', Y), the order is B → X → Y.
250
+ */
251
+ insertAfter(target, mw, opts) {
252
+ this.ensureUnique(mw.name);
253
+ const idx = this.indexOf(target, opts?.optional);
254
+ if (idx === -1) return this;
255
+ this.chain.splice(idx + 1, 0, mw);
256
+ return this;
257
+ }
258
+ replace(target, mw, opts) {
259
+ if (mw.name !== target) this.ensureUnique(mw.name);
260
+ const idx = this.indexOf(target, opts?.optional);
261
+ if (idx === -1) return this;
262
+ this.chain[idx] = mw;
263
+ return this;
264
+ }
265
+ remove(name, opts) {
266
+ const idx = this.indexOf(name, opts?.optional);
267
+ if (idx === -1) return this;
268
+ this.chain.splice(idx, 1);
269
+ return this;
270
+ }
271
+ list() {
272
+ return this.chain.map((m) => m.name);
273
+ }
274
+ size() {
275
+ return this.chain.length;
276
+ }
277
+ /** Return a read-only view suitable for passing to plugins. */
278
+ asReadonly() {
279
+ const self = this;
280
+ const view = Object.freeze({
281
+ get size() {
282
+ return self.chain.length;
283
+ },
284
+ list() {
285
+ return Object.freeze(self.chain.map((m) => m.name));
286
+ },
287
+ run(input) {
288
+ return self.run(input);
289
+ }
290
+ });
291
+ return view;
292
+ }
293
+ async run(input) {
294
+ let index = -1;
295
+ const chain = this.chain;
296
+ const errorHandler = this.errorHandler;
297
+ const dispatch = async (i, value) => {
298
+ if (i <= index) {
299
+ const offender = chain[index]?.name;
300
+ throw new WrongStackError({
301
+ message: `Pipeline: next() called multiple times in "${offender}"`,
302
+ code: ERROR_CODES.VALIDATION_ERROR,
303
+ subsystem: "container",
304
+ context: { middleware: offender }
305
+ });
306
+ }
307
+ index = i;
308
+ const mw = chain[i];
309
+ if (!mw) return value;
310
+ try {
311
+ return await mw.handler(value, (v) => dispatch(i + 1, v));
312
+ } catch (err) {
313
+ if (!errorHandler) throw err;
314
+ const policy = await errorHandler({ middleware: mw.name, owner: mw.owner, err });
315
+ if (policy === "rethrow") throw err;
316
+ this.logger?.warn("pipeline.error", {
317
+ middleware: mw.name,
318
+ owner: mw.owner,
319
+ error: err instanceof Error ? err.message : String(err)
320
+ });
321
+ return value;
322
+ }
323
+ };
324
+ return dispatch(0, input);
325
+ }
326
+ indexOf(name, optional = false) {
327
+ const idx = this.chain.findIndex((m) => m.name === name);
328
+ if (idx === -1 && !optional) {
329
+ throw new WrongStackError({
330
+ message: `Pipeline: middleware "${name}" not found`,
331
+ code: ERROR_CODES.REGISTRY_NOT_FOUND,
332
+ subsystem: "container",
333
+ context: { middleware: name }
334
+ });
335
+ }
336
+ return idx;
337
+ }
338
+ ensureUnique(name) {
339
+ if (this.chain.some((m) => m.name === name)) {
340
+ throw new WrongStackError({
341
+ message: `Pipeline: middleware "${name}" already registered`,
342
+ code: ERROR_CODES.REGISTRY_DUPLICATE,
343
+ subsystem: "container",
344
+ context: { middleware: name }
345
+ });
346
+ }
347
+ }
348
+ };
349
+
350
+ export {
351
+ RunController,
352
+ TOKENS,
353
+ Pipeline
354
+ };
355
+ //# sourceMappingURL=chunk-Y2GE5TSI.js.map
@@ -0,0 +1,351 @@
1
+ import {
2
+ SessionCatalogProjectClient
3
+ } from "./chunk-52RTVH4M.js";
4
+ import {
5
+ sessionCatalogProjectServerMetadataPath
6
+ } from "./chunk-E6I5RXVK.js";
7
+
8
+ // src/session-catalog/registry.ts
9
+ import { randomUUID } from "node:crypto";
10
+ import * as fs from "node:fs/promises";
11
+ import * as path from "node:path";
12
+ var HEARTBEAT_INTERVAL_MS = 5e3;
13
+ var AGENT_WRITE_THROTTLE_MS = 300;
14
+ var MAX_PROJECT_CLIENTS = 128;
15
+ var MAX_GLOBAL_ROOTS = 16;
16
+ var ProjectSessionRegistry = class {
17
+ constructor(globalRoot) {
18
+ this.globalRoot = globalRoot;
19
+ }
20
+ globalRoot;
21
+ instanceId = randomUUID();
22
+ clients = /* @__PURE__ */ new Map();
23
+ current;
24
+ heartbeatTimer;
25
+ agentRevision = 0;
26
+ pendingAgents;
27
+ agentTimer;
28
+ lastAgentWriteAt = 0;
29
+ bindingKey(projectDir) {
30
+ const resolved = path.resolve(projectDir);
31
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
32
+ }
33
+ async closeBinding(binding) {
34
+ if (this.current?.binding === binding) return;
35
+ const key = this.bindingKey(binding.projectDir);
36
+ if (this.clients.get(key) === binding) this.clients.delete(key);
37
+ await binding.client.close();
38
+ }
39
+ binding(projectSlug, projectRoot) {
40
+ const projectDir = path.join(this.globalRoot, "projects", projectSlug);
41
+ const key = this.bindingKey(projectDir);
42
+ let binding = this.clients.get(key);
43
+ if (!binding) {
44
+ if (this.clients.size >= MAX_PROJECT_CLIENTS) {
45
+ const oldest = [...this.clients.entries()].find(
46
+ ([, candidate]) => candidate !== this.current?.binding
47
+ );
48
+ if (oldest) {
49
+ this.clients.delete(oldest[0]);
50
+ void oldest[1].client.close();
51
+ }
52
+ }
53
+ binding = {
54
+ projectDir,
55
+ projectRoot: path.resolve(projectRoot),
56
+ client: new SessionCatalogProjectClient({ projectDir, projectRoot })
57
+ };
58
+ this.clients.set(key, binding);
59
+ } else {
60
+ this.clients.delete(key);
61
+ this.clients.set(key, binding);
62
+ }
63
+ return binding;
64
+ }
65
+ fullEntry(entry) {
66
+ const agents = entry.agents ?? [];
67
+ return {
68
+ ...entry,
69
+ status: agents.some((agent) => agent.status !== "idle") ? "active" : "idle",
70
+ lastHeartbeatAt: (/* @__PURE__ */ new Date()).toISOString(),
71
+ agentCount: agents.length,
72
+ agents
73
+ };
74
+ }
75
+ async register(entry) {
76
+ const full = this.fullEntry(entry);
77
+ if (this.current?.entry.sessionId === full.sessionId && this.current.entry.pid === full.pid) {
78
+ this.current.entry = full;
79
+ this.current.credential = await this.current.binding.client.call("heartbeat", {
80
+ credential: this.current.credential,
81
+ status: full.status
82
+ });
83
+ return;
84
+ }
85
+ const nextBinding = this.binding(full.projectSlug, full.projectRoot);
86
+ let nextCredential;
87
+ try {
88
+ nextCredential = await nextBinding.client.call("claim_new", {
89
+ entry: full,
90
+ ownerInstanceId: this.instanceId
91
+ });
92
+ } catch (error) {
93
+ await this.closeBinding(nextBinding).catch(() => void 0);
94
+ throw error;
95
+ }
96
+ const previous = this.current;
97
+ this.current = { binding: nextBinding, credential: nextCredential, entry: full };
98
+ this.agentRevision = 0;
99
+ this.cancelAgentTimer();
100
+ this.startHeartbeat();
101
+ if (previous) {
102
+ await previous.binding.client.call("release", { credential: previous.credential }).catch(() => void 0);
103
+ if (previous.binding !== nextBinding) await this.closeBinding(previous.binding);
104
+ }
105
+ }
106
+ /** Reserve before transcript hydration; activation swaps ownership only after the writer opened. */
107
+ async reserveResume(target) {
108
+ const binding = this.binding(target.projectSlug, target.projectRoot);
109
+ let reservation;
110
+ try {
111
+ reservation = await binding.client.call("reserve_resume", {
112
+ targetSessionId: target.sessionId,
113
+ requesterInstanceId: this.instanceId,
114
+ ...this.current ? { currentSessionId: this.current.entry.sessionId } : {}
115
+ });
116
+ } catch (error) {
117
+ await this.closeBinding(binding).catch(() => void 0);
118
+ throw error;
119
+ }
120
+ let settled = false;
121
+ return {
122
+ reservation,
123
+ activate: async (registration) => {
124
+ if (settled) throw new Error("Resume reservation is already settled");
125
+ const entry = this.fullEntry(registration);
126
+ const credential = await binding.client.call("activate_reservation", {
127
+ reservation,
128
+ entry
129
+ });
130
+ const previous = this.current;
131
+ this.current = { binding, credential, entry };
132
+ settled = true;
133
+ this.agentRevision = 0;
134
+ this.cancelAgentTimer();
135
+ this.startHeartbeat();
136
+ if (previous) {
137
+ await previous.binding.client.call("release", { credential: previous.credential }).catch(() => void 0);
138
+ if (previous.binding !== binding) await this.closeBinding(previous.binding);
139
+ }
140
+ },
141
+ cancel: async () => {
142
+ if (settled) return;
143
+ settled = true;
144
+ try {
145
+ await binding.client.call("cancel_reservation", {
146
+ reservationId: reservation.reservationId,
147
+ requesterInstanceId: this.instanceId
148
+ }).catch(() => void 0);
149
+ } finally {
150
+ await this.closeBinding(binding).catch(() => void 0);
151
+ }
152
+ }
153
+ };
154
+ }
155
+ async updateAgents(agents) {
156
+ if (!this.current) return;
157
+ this.pendingAgents = agents;
158
+ this.current.entry = {
159
+ ...this.current.entry,
160
+ agents,
161
+ agentCount: agents.length,
162
+ status: agents.some((agent) => agent.status !== "idle") ? "active" : "idle",
163
+ lastHeartbeatAt: (/* @__PURE__ */ new Date()).toISOString()
164
+ };
165
+ const elapsed = Date.now() - this.lastAgentWriteAt;
166
+ if (!this.agentTimer && elapsed >= AGENT_WRITE_THROTTLE_MS) {
167
+ await this.flushAgents();
168
+ return;
169
+ }
170
+ if (!this.agentTimer) {
171
+ this.agentTimer = setTimeout(
172
+ () => {
173
+ this.agentTimer = void 0;
174
+ void this.flushAgents();
175
+ },
176
+ Math.max(0, AGENT_WRITE_THROTTLE_MS - elapsed)
177
+ );
178
+ this.agentTimer.unref?.();
179
+ }
180
+ }
181
+ async flushAgents() {
182
+ const agents = this.pendingAgents;
183
+ const current = this.current;
184
+ if (!agents || !current) return;
185
+ this.pendingAgents = void 0;
186
+ this.lastAgentWriteAt = Date.now();
187
+ const revision = ++this.agentRevision;
188
+ await current.binding.client.call("publish_agents", {
189
+ credential: current.credential,
190
+ revision,
191
+ agents
192
+ });
193
+ }
194
+ async markClosing() {
195
+ this.stopHeartbeat();
196
+ this.cancelAgentTimer();
197
+ if (this.current)
198
+ await this.current.binding.client.call("mark_closing", {
199
+ credential: this.current.credential
200
+ });
201
+ }
202
+ async unregister() {
203
+ this.stopHeartbeat();
204
+ this.cancelAgentTimer();
205
+ const current = this.current;
206
+ this.current = void 0;
207
+ if (current) {
208
+ try {
209
+ await current.binding.client.call("release", { credential: current.credential });
210
+ } finally {
211
+ await this.closeBinding(current.binding);
212
+ }
213
+ }
214
+ }
215
+ async list() {
216
+ const projectsDir = path.join(this.globalRoot, "projects");
217
+ let directories = [];
218
+ try {
219
+ directories = (await fs.readdir(projectsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).slice(0, 1e3).map((entry) => entry.name);
220
+ } catch {
221
+ return [];
222
+ }
223
+ const snapshots = await Promise.all(
224
+ directories.map(async (slug) => {
225
+ const projectDir = path.join(projectsDir, slug);
226
+ let client;
227
+ try {
228
+ const metadata = JSON.parse(
229
+ await fs.readFile(sessionCatalogProjectServerMetadataPath(projectDir), "utf8")
230
+ );
231
+ if (typeof metadata.projectRoot !== "string" || !metadata.projectRoot) return [];
232
+ client = new SessionCatalogProjectClient({
233
+ projectDir,
234
+ projectRoot: metadata.projectRoot
235
+ });
236
+ return await client.callExisting("list_live", {});
237
+ } catch {
238
+ return [];
239
+ } finally {
240
+ await client?.close().catch(() => void 0);
241
+ }
242
+ })
243
+ );
244
+ return snapshots.flat();
245
+ }
246
+ async listByProject(projectSlug) {
247
+ const current = this.current;
248
+ if (current && path.basename(current.binding.projectDir) === projectSlug) {
249
+ return current.binding.client.call("list_live", {}).catch(() => []);
250
+ }
251
+ const projectDir = path.join(this.globalRoot, "projects", projectSlug);
252
+ let client;
253
+ try {
254
+ const metadata = JSON.parse(
255
+ await fs.readFile(sessionCatalogProjectServerMetadataPath(projectDir), "utf8")
256
+ );
257
+ if (typeof metadata.projectRoot !== "string") return [];
258
+ client = new SessionCatalogProjectClient({ projectDir, projectRoot: metadata.projectRoot });
259
+ return await client.callExisting("list_live", {}).catch(() => []);
260
+ } catch {
261
+ return [];
262
+ } finally {
263
+ await client?.close().catch(() => void 0);
264
+ }
265
+ }
266
+ async get(sessionId) {
267
+ return (await this.list()).find((entry) => entry.sessionId === sessionId);
268
+ }
269
+ subscribeProject(projectSlug, projectRoot, listener) {
270
+ const binding = this.binding(projectSlug, projectRoot);
271
+ return binding.client.subscribe(listener).then((unsubscribe) => async () => {
272
+ await unsubscribe();
273
+ await this.closeBinding(binding);
274
+ });
275
+ }
276
+ get registryPath() {
277
+ return path.join(this.globalRoot, "projects");
278
+ }
279
+ async dispose() {
280
+ await this.unregister().catch(() => void 0);
281
+ await Promise.all([...this.clients.values()].map((binding) => binding.client.close()));
282
+ this.clients.clear();
283
+ }
284
+ /** Whether this facade currently owns a live session lease. */
285
+ ownsSession() {
286
+ return this.current !== void 0;
287
+ }
288
+ startHeartbeat() {
289
+ this.stopHeartbeat();
290
+ this.heartbeatTimer = setInterval(() => {
291
+ void this.heartbeat();
292
+ }, HEARTBEAT_INTERVAL_MS);
293
+ this.heartbeatTimer.unref?.();
294
+ }
295
+ stopHeartbeat() {
296
+ if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
297
+ this.heartbeatTimer = void 0;
298
+ }
299
+ cancelAgentTimer() {
300
+ if (this.agentTimer) clearTimeout(this.agentTimer);
301
+ this.agentTimer = void 0;
302
+ this.pendingAgents = void 0;
303
+ }
304
+ async heartbeat() {
305
+ const current = this.current;
306
+ if (!current) return;
307
+ try {
308
+ const credential = await current.binding.client.call("heartbeat", {
309
+ credential: current.credential,
310
+ status: current.entry.status
311
+ });
312
+ if (this.current === current) current.credential = credential;
313
+ } catch {
314
+ }
315
+ }
316
+ };
317
+ var registries = /* @__PURE__ */ new Map();
318
+ var lastRegistryKey;
319
+ function getProjectSessionRegistry(globalRoot) {
320
+ const key = globalRoot !== void 0 ? path.resolve(globalRoot) : lastRegistryKey;
321
+ if (!key)
322
+ throw new Error("SessionRegistry not initialized. Call getSessionRegistry(globalRoot) first.");
323
+ let registry = registries.get(key);
324
+ if (!registry) {
325
+ if (registries.size >= MAX_GLOBAL_ROOTS) {
326
+ const idle = [...registries.entries()].find(([, candidate]) => !candidate.ownsSession());
327
+ if (!idle)
328
+ throw new Error(`Session Registry global-root limit reached (${MAX_GLOBAL_ROOTS})`);
329
+ registries.delete(idle[0]);
330
+ void idle[1].dispose();
331
+ }
332
+ registry = new ProjectSessionRegistry(key);
333
+ registries.set(key, registry);
334
+ } else {
335
+ registries.delete(key);
336
+ registries.set(key, registry);
337
+ }
338
+ lastRegistryKey = key;
339
+ return registry;
340
+ }
341
+ function hasProjectSessionRegistry(globalRoot) {
342
+ if (globalRoot === void 0) return registries.size > 0;
343
+ return registries.has(path.resolve(globalRoot));
344
+ }
345
+
346
+ export {
347
+ ProjectSessionRegistry,
348
+ getProjectSessionRegistry,
349
+ hasProjectSessionRegistry
350
+ };
351
+ //# sourceMappingURL=chunk-YBUJCZ57.js.map