@wrongstack/core 0.6.4 → 0.6.5

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 (63) hide show
  1. package/dist/{agent-bridge-eb7qnNrd.d.ts → agent-bridge-BBXK_ppx.d.ts} +1 -1
  2. package/dist/agent-subagent-runner-DsSm9lKN.d.ts +174 -0
  3. package/dist/{compactor-RIPuTtWK.d.ts → compactor-C8NhpSt5.d.ts} +1 -1
  4. package/dist/{config-BGGuP_Ar.d.ts → config-DfC6g6KV.d.ts} +1 -1
  5. package/dist/{context-CDRyrkKQ.d.ts → context-DN5v-uQX.d.ts} +11 -0
  6. package/dist/coordination/index.d.ts +10 -9
  7. package/dist/coordination/index.js +113 -20
  8. package/dist/coordination/index.js.map +1 -1
  9. package/dist/defaults/index.d.ts +21 -20
  10. package/dist/defaults/index.js +1329 -925
  11. package/dist/defaults/index.js.map +1 -1
  12. package/dist/{events-BHuIHekD.d.ts → events-CJqwQl8G.d.ts} +17 -1
  13. package/dist/execution/index.d.ts +82 -13
  14. package/dist/execution/index.js +1366 -74
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/extension/index.d.ts +6 -6
  17. package/dist/{goal-store-DVCfj7Ff.d.ts → goal-store-_Er467ya.d.ts} +2 -2
  18. package/dist/{index-CPcDqvZh.d.ts → index-CXnWsGBp.d.ts} +11 -175
  19. package/dist/{index-BOn9NK7D.d.ts → index-DcnXDPdY.d.ts} +6 -6
  20. package/dist/index.d.ts +28 -27
  21. package/dist/index.js +1068 -664
  22. package/dist/index.js.map +1 -1
  23. package/dist/infrastructure/index.d.ts +6 -6
  24. package/dist/kernel/index.d.ts +9 -9
  25. package/dist/kernel/index.js.map +1 -1
  26. package/dist/{mcp-servers-DBdh3cee.d.ts → mcp-servers-CevFHHM1.d.ts} +3 -3
  27. package/dist/models/index.d.ts +2 -2
  28. package/dist/models/index.js +5 -16
  29. package/dist/models/index.js.map +1 -1
  30. package/dist/{multi-agent-CxSb-9dQ.d.ts → multi-agent-D5IbASk_.d.ts} +16 -4
  31. package/dist/observability/index.d.ts +2 -2
  32. package/dist/{path-resolver-CMGNadvq.d.ts → path-resolver-CBx_q1HA.d.ts} +2 -2
  33. package/dist/{plan-templates-BJflQY2i.d.ts → plan-templates-BEOllUJV.d.ts} +5 -4
  34. package/dist/{provider-runner-BFgNXpaP.d.ts → provider-runner-Byh5TcJs.d.ts} +3 -3
  35. package/dist/{retry-policy-LKS8MHsB.d.ts → retry-policy-BZSIMxrJ.d.ts} +1 -1
  36. package/dist/sdd/index.d.ts +3 -3
  37. package/dist/{secret-scrubber-CfMdAJ_l.d.ts → secret-scrubber-CT7wefiO.d.ts} +1 -1
  38. package/dist/{secret-scrubber-BzQR5BiL.d.ts → secret-scrubber-I0QHY_ob.d.ts} +1 -1
  39. package/dist/security/index.d.ts +3 -3
  40. package/dist/{selector-C7HqnZJU.d.ts → selector-DDb_mq9X.d.ts} +1 -1
  41. package/dist/{session-reader-CzfRA6Vk.d.ts → session-reader-B9nVkziM.d.ts} +1 -1
  42. package/dist/storage/index.d.ts +6 -6
  43. package/dist/storage/index.js +27 -2
  44. package/dist/storage/index.js.map +1 -1
  45. package/dist/{system-prompt-Dl2QY1_B.d.ts → system-prompt-gL06H9P4.d.ts} +1 -1
  46. package/dist/{tool-executor-FoxBjULX.d.ts → tool-executor-BF7QfYVE.d.ts} +4 -4
  47. package/dist/types/index.d.ts +15 -15
  48. package/dist/types/index.js +5 -16
  49. package/dist/types/index.js.map +1 -1
  50. package/dist/utils/index.d.ts +1 -1
  51. package/package.json +1 -1
  52. package/skills/audit-log/SKILL.md +57 -28
  53. package/skills/bug-hunter/SKILL.md +85 -61
  54. package/skills/git-flow/SKILL.md +73 -18
  55. package/skills/multi-agent/SKILL.md +69 -40
  56. package/skills/node-modern/SKILL.md +111 -19
  57. package/skills/prompt-engineering/SKILL.md +97 -16
  58. package/skills/react-modern/SKILL.md +104 -18
  59. package/skills/refactor-planner/SKILL.md +73 -43
  60. package/skills/sdd/SKILL.md +54 -112
  61. package/skills/security-scanner/SKILL.md +95 -93
  62. package/skills/skill-creator/SKILL.md +58 -25
  63. package/skills/typescript-strict/SKILL.md +107 -15
@@ -2,7 +2,8 @@ import { execFile } from 'child_process';
2
2
  import { promisify } from 'util';
3
3
  import * as fs from 'fs/promises';
4
4
  import * as path from 'path';
5
- import { randomBytes } from 'crypto';
5
+ import { randomUUID, randomBytes } from 'crypto';
6
+ import { EventEmitter } from 'events';
6
7
 
7
8
  // src/utils/token-estimate.ts
8
9
  var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
@@ -2231,6 +2232,1369 @@ function sleep(ms) {
2231
2232
  return new Promise((resolve) => setTimeout(resolve, ms));
2232
2233
  }
2233
2234
 
2235
+ // src/coordination/subagent-budget.ts
2236
+ var BudgetExceededError = class extends Error {
2237
+ kind;
2238
+ limit;
2239
+ observed;
2240
+ constructor(kind, limit, observed) {
2241
+ super(`Budget exceeded: ${kind} (limit=${limit}, observed=${observed})`);
2242
+ this.name = "BudgetExceededError";
2243
+ this.kind = kind;
2244
+ this.limit = limit;
2245
+ this.observed = observed;
2246
+ }
2247
+ };
2248
+ var BudgetThresholdSignal = class extends Error {
2249
+ kind;
2250
+ limit;
2251
+ used;
2252
+ /** Resolves to 'extend' (with optional new limits) or 'stop' */
2253
+ decision;
2254
+ constructor(kind, limit, used, decision) {
2255
+ super(`Budget soft limit: ${kind} (limit=${limit}, used=${used})`);
2256
+ this.name = "BudgetThresholdSignal";
2257
+ this.kind = kind;
2258
+ this.limit = limit;
2259
+ this.used = used;
2260
+ this.decision = decision;
2261
+ }
2262
+ };
2263
+ var SubagentBudget = class _SubagentBudget {
2264
+ limits;
2265
+ iterations = 0;
2266
+ toolCalls = 0;
2267
+ tokenInput = 0;
2268
+ tokenOutput = 0;
2269
+ costUsd = 0;
2270
+ startTime = null;
2271
+ _onThreshold;
2272
+ /**
2273
+ * Tracks which budget kinds currently have an extension request
2274
+ * in flight. While a kind is here, further `checkLimit` calls for the
2275
+ * same kind are no-ops — without this dedup, every `recordIteration`
2276
+ * after the limit is reached spawns a fresh decision Promise (until
2277
+ * the first one lands and patches limits), flooding the FleetBus
2278
+ * with redundant threshold events. Cleared in `checkLimitAsync`'s
2279
+ * `finally`.
2280
+ */
2281
+ pendingExtensions = /* @__PURE__ */ new Set();
2282
+ /**
2283
+ * Hard cap on how long `checkLimitAsync` waits for the coordinator to
2284
+ * respond before defaulting to 'stop'. Without this fallback an absent
2285
+ * or hung listener (Director not built / event filter detached mid-run)
2286
+ * leaves the budget over-limit and never enforces anything, since
2287
+ * `checkLimit` returns synchronously via `void this.checkLimitAsync`.
2288
+ * Hardcoded for now — most fleets set their own per-task timeout that
2289
+ * dwarfs this anyway. 30 s matches the existing event-emit timeoutMs.
2290
+ */
2291
+ static DECISION_TIMEOUT_MS = 3e4;
2292
+ /**
2293
+ * Injected by the runner when wiring the budget to its EventBus.
2294
+ * Used by `checkLimitAsync` to emit `budget.threshold_reached` events.
2295
+ */
2296
+ _events;
2297
+ /**
2298
+ * Optional callback for soft-limit handling. When set, the budget will
2299
+ * call this instead of throwing when a limit is first reached. The
2300
+ * handler decides whether to throw, continue, or ask the coordinator.
2301
+ */
2302
+ get onThreshold() {
2303
+ return this._onThreshold;
2304
+ }
2305
+ set onThreshold(fn) {
2306
+ this._onThreshold = fn;
2307
+ }
2308
+ constructor(limits = {}) {
2309
+ this.limits = Object.freeze({ ...limits });
2310
+ }
2311
+ start() {
2312
+ this.startTime = Date.now();
2313
+ }
2314
+ /** Returns true if we're within 10% of any limit — useful for pre-flight checks. */
2315
+ isNearLimit() {
2316
+ const { maxIterations, maxToolCalls, maxTokens, maxCostUsd } = this.limits;
2317
+ if (maxIterations && this.iterations >= maxIterations * 0.9) return true;
2318
+ if (maxToolCalls && this.toolCalls >= maxToolCalls * 0.9) return true;
2319
+ if (maxTokens && this.tokenInput + this.tokenOutput >= maxTokens * 0.9) return true;
2320
+ if (maxCostUsd && this.costUsd >= maxCostUsd * 0.9) return true;
2321
+ return false;
2322
+ }
2323
+ /**
2324
+ * Synchronous budget check — always throws synchronously so callers
2325
+ * (especially test event handlers using `expect().toThrow()`) get an
2326
+ * unhandled rejection when the budget is exceeded without a handler.
2327
+ * When `onThreshold` IS configured, the actual async threshold-handling
2328
+ * is dispatched as a fire-and-forget promise.
2329
+ */
2330
+ checkLimit(kind, used, limit) {
2331
+ if (!this._onThreshold) {
2332
+ throw new BudgetExceededError(kind, limit, used);
2333
+ }
2334
+ const bus = this._events;
2335
+ if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
2336
+ throw new BudgetExceededError(kind, limit, used);
2337
+ }
2338
+ if (this.pendingExtensions.has(kind)) return;
2339
+ this.pendingExtensions.add(kind);
2340
+ const decision = this.negotiateExtension(kind, used, limit);
2341
+ throw new BudgetThresholdSignal(kind, limit, used, decision);
2342
+ }
2343
+ /**
2344
+ * Drive the threshold handler to a decision. Resolves with `'stop'`
2345
+ * (signal the runner to abort) or `{ extend: ... }` (limits already
2346
+ * patched in-place; the runner should not abort). Always releases the
2347
+ * `pendingExtensions` slot in `finally`.
2348
+ *
2349
+ * The 'continue' return from a sync handler is treated as
2350
+ * `{ extend: {} }` — keep going without patching, next overrun will
2351
+ * fire a fresh signal.
2352
+ */
2353
+ async negotiateExtension(kind, used, limit) {
2354
+ try {
2355
+ const result = this._onThreshold({
2356
+ kind,
2357
+ used,
2358
+ limit,
2359
+ // Inject a requestDecision helper the handler can call to emit the
2360
+ // budget.threshold_reached event and wait for the coordinator's verdict.
2361
+ // A hard fallback timer guarantees the promise eventually resolves
2362
+ // even if no listener responds — without it, an absent/detached
2363
+ // Director would leave the budget permanently in "asking" state.
2364
+ requestDecision: () => {
2365
+ const bus = this._events;
2366
+ if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
2367
+ return Promise.resolve("stop");
2368
+ }
2369
+ return new Promise((resolve) => {
2370
+ let resolved = false;
2371
+ const respond = (d) => {
2372
+ if (resolved) return;
2373
+ resolved = true;
2374
+ resolve(d);
2375
+ };
2376
+ const fallback = setTimeout(
2377
+ () => respond("stop"),
2378
+ _SubagentBudget.DECISION_TIMEOUT_MS
2379
+ );
2380
+ bus.emit("budget.threshold_reached", {
2381
+ kind,
2382
+ used,
2383
+ limit,
2384
+ timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
2385
+ extend: (extra) => {
2386
+ clearTimeout(fallback);
2387
+ respond({ extend: extra });
2388
+ },
2389
+ deny: () => {
2390
+ clearTimeout(fallback);
2391
+ respond("stop");
2392
+ }
2393
+ });
2394
+ });
2395
+ }
2396
+ });
2397
+ if (result === "throw") return "stop";
2398
+ if (result === "continue") return { extend: {} };
2399
+ const decision = await result;
2400
+ if (decision === "stop") return "stop";
2401
+ const ext = decision.extend;
2402
+ if (ext.maxIterations !== void 0) {
2403
+ this.limits.maxIterations = ext.maxIterations;
2404
+ }
2405
+ if (ext.maxToolCalls !== void 0) {
2406
+ this.limits.maxToolCalls = ext.maxToolCalls;
2407
+ }
2408
+ if (ext.maxTokens !== void 0) {
2409
+ this.limits.maxTokens = ext.maxTokens;
2410
+ }
2411
+ if (ext.maxCostUsd !== void 0) {
2412
+ this.limits.maxCostUsd = ext.maxCostUsd;
2413
+ }
2414
+ if (ext.timeoutMs !== void 0) {
2415
+ this.limits.timeoutMs = ext.timeoutMs;
2416
+ }
2417
+ return decision;
2418
+ } finally {
2419
+ this.pendingExtensions.delete(kind);
2420
+ }
2421
+ }
2422
+ recordIteration() {
2423
+ this.iterations++;
2424
+ if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
2425
+ void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
2426
+ }
2427
+ }
2428
+ recordToolCall() {
2429
+ this.toolCalls++;
2430
+ if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
2431
+ void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
2432
+ }
2433
+ }
2434
+ recordUsage(usage, costUsd = 0) {
2435
+ this.tokenInput += usage.input;
2436
+ this.tokenOutput += usage.output;
2437
+ this.costUsd += costUsd;
2438
+ const totalTokens = this.tokenInput + this.tokenOutput;
2439
+ if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
2440
+ void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
2441
+ }
2442
+ if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
2443
+ void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
2444
+ }
2445
+ }
2446
+ /**
2447
+ * Wall-clock budget check. Unlike other limits, timeout is treated as a
2448
+ * warning-only event — it NEVER hard-stops the subagent. When the
2449
+ * elapsed time exceeds timeoutMs, emits `budget.threshold_reached` with
2450
+ * kind='timeout' so the Director can decide whether to extend or warn.
2451
+ * Call this from the iteration loop so a hung tool gets a chance to
2452
+ * negotiate more time before the coordinator's Promise.race kills it.
2453
+ */
2454
+ checkTimeout() {
2455
+ if (this.startTime === null || this.limits.timeoutMs === void 0) return;
2456
+ const elapsed = Date.now() - this.startTime;
2457
+ if (elapsed > this.limits.timeoutMs) {
2458
+ void this.checkLimit("timeout", elapsed, this.limits.timeoutMs);
2459
+ }
2460
+ }
2461
+ /** Returns true if a timeout has occurred without throwing. Useful for races. */
2462
+ isTimedOut() {
2463
+ if (this.startTime === null || this.limits.timeoutMs === void 0) return false;
2464
+ return Date.now() - this.startTime > this.limits.timeoutMs;
2465
+ }
2466
+ usage() {
2467
+ return {
2468
+ iterations: this.iterations,
2469
+ toolCalls: this.toolCalls,
2470
+ tokens: {
2471
+ input: this.tokenInput,
2472
+ output: this.tokenOutput,
2473
+ total: this.tokenInput + this.tokenOutput
2474
+ },
2475
+ costUsd: this.costUsd,
2476
+ elapsedMs: this.startTime === null ? 0 : Date.now() - this.startTime
2477
+ };
2478
+ }
2479
+ };
2480
+
2481
+ // src/coordination/agent-subagent-runner.ts
2482
+ function makeAgentSubagentRunner(opts) {
2483
+ const format = opts.formatTaskInput ?? defaultFormatTaskInput;
2484
+ return async (task, ctx) => {
2485
+ const factoryResult = await opts.factory(ctx.config);
2486
+ const { agent, events } = factoryResult;
2487
+ const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
2488
+ const aborter = new AbortController();
2489
+ ctx.budget._events = events;
2490
+ ctx.budget.onThreshold = ({ requestDecision }) => requestDecision();
2491
+ let budgetError = null;
2492
+ const onBudgetError = (err) => {
2493
+ if (err instanceof BudgetThresholdSignal) {
2494
+ err.decision.then((decision) => {
2495
+ if (decision === "stop") {
2496
+ budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
2497
+ aborter.abort();
2498
+ }
2499
+ }).catch(() => {
2500
+ budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
2501
+ aborter.abort();
2502
+ });
2503
+ return;
2504
+ }
2505
+ aborter.abort();
2506
+ budgetError = err instanceof BudgetExceededError ? err : new BudgetExceededError(
2507
+ "tool_calls",
2508
+ 0,
2509
+ 0
2510
+ );
2511
+ if (budgetError !== err && err instanceof Error) {
2512
+ budgetError.message += ` (caused by: ${err.message})`;
2513
+ }
2514
+ };
2515
+ let lastToolFailed = null;
2516
+ const unsub = [];
2517
+ unsub.push(
2518
+ events.on("tool.executed", (e) => {
2519
+ try {
2520
+ ctx.budget.recordToolCall();
2521
+ } catch (eb) {
2522
+ onBudgetError(eb);
2523
+ }
2524
+ if (e.ok === false) {
2525
+ lastToolFailed = e.name;
2526
+ } else if (e.ok === true) {
2527
+ lastToolFailed = null;
2528
+ }
2529
+ }),
2530
+ events.on("provider.response", (e) => {
2531
+ try {
2532
+ ctx.budget.recordUsage(e.usage);
2533
+ } catch (e2) {
2534
+ void onBudgetError(e2);
2535
+ }
2536
+ }),
2537
+ events.on("iteration.started", () => {
2538
+ try {
2539
+ ctx.budget.recordIteration();
2540
+ } catch (e) {
2541
+ void onBudgetError(e);
2542
+ }
2543
+ const u = ctx.budget.usage();
2544
+ const since = u.iterations - lastSummaryAtIteration;
2545
+ if (since >= SUMMARY_INTERVAL) {
2546
+ lastSummaryAtIteration = u.iterations;
2547
+ events.emit("subagent.iteration_summary", {
2548
+ subagentId: ctx.subagentId,
2549
+ iteration: u.iterations,
2550
+ toolCalls: u.toolCalls,
2551
+ costUsd: u.costUsd,
2552
+ currentTool: currentToolName,
2553
+ partialText: streamingTextAcc.trim() || void 0
2554
+ });
2555
+ }
2556
+ }),
2557
+ // D3: cooperative timeout enforcement DURING a long tool call.
2558
+ // The iteration-loop checkTimeout() only fires between agent
2559
+ // iterations — a single `bash sleep 3600` call would otherwise
2560
+ // park inside one tool execution while the timeout silently
2561
+ // passes, relying solely on the coordinator's hard Promise.race
2562
+ // to interrupt. Tools that emit `tool.progress` (bash chunks,
2563
+ // fetch byte progress, spawn-stream stdout) give us a heartbeat
2564
+ // we can hang the check on. When the budget trips here:
2565
+ // 1. onBudgetError sets budgetError + aborter.abort()
2566
+ // 2. aborter signal propagates to agent.run → tool executor
2567
+ // 3. tool's own signal listener kills the child process
2568
+ // Cheap: O(1) per progress event, and the budget short-circuits
2569
+ // when timeoutMs is unset (most subagents have one set anyway).
2570
+ events.on("tool.progress", () => {
2571
+ try {
2572
+ ctx.budget.checkTimeout();
2573
+ } catch (e) {
2574
+ void onBudgetError(e);
2575
+ }
2576
+ })
2577
+ );
2578
+ let currentToolName;
2579
+ let streamingTextAcc = "";
2580
+ let lastSummaryAtIteration = 0;
2581
+ const SUMMARY_INTERVAL = 25;
2582
+ unsub.push(
2583
+ events.on("tool.started", (e) => {
2584
+ currentToolName = e.name;
2585
+ }),
2586
+ events.on("provider.text_delta", (e) => {
2587
+ streamingTextAcc = (streamingTextAcc + e.text).slice(-200);
2588
+ })
2589
+ );
2590
+ const onParentAbort = () => aborter.abort();
2591
+ ctx.signal.addEventListener("abort", onParentAbort);
2592
+ let result;
2593
+ try {
2594
+ result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
2595
+ } finally {
2596
+ detachFleet?.();
2597
+ ctx.signal.removeEventListener("abort", onParentAbort);
2598
+ for (const u of unsub) u();
2599
+ if (factoryResult.dispose) {
2600
+ try {
2601
+ await factoryResult.dispose();
2602
+ } catch {
2603
+ }
2604
+ }
2605
+ }
2606
+ if (budgetError) {
2607
+ if ("decision" in budgetError) {
2608
+ const decision = await budgetError.decision;
2609
+ if (decision === "stop") {
2610
+ budgetError = new BudgetExceededError(
2611
+ budgetError.kind,
2612
+ budgetError.limit,
2613
+ budgetError.used
2614
+ );
2615
+ } else {
2616
+ budgetError = null;
2617
+ }
2618
+ }
2619
+ if (budgetError) throw budgetError;
2620
+ }
2621
+ if (result.status === "failed") {
2622
+ throw result.error instanceof Error ? result.error : new Error(String(result.error ?? "agent failed"));
2623
+ }
2624
+ if (result.status === "aborted") {
2625
+ throw new Error("agent aborted");
2626
+ }
2627
+ if (result.status === "max_iterations") {
2628
+ throw new Error("agent exhausted iteration limit");
2629
+ }
2630
+ const usage = ctx.budget.usage();
2631
+ const finalText = (result.finalText ?? "").trim();
2632
+ if (finalText.length === 0 && usage.toolCalls === 0) {
2633
+ throw new Error("empty response");
2634
+ }
2635
+ if (finalText.length === 0 && lastToolFailed !== null) {
2636
+ throw new Error(`tool failed: ${lastToolFailed}`);
2637
+ }
2638
+ return {
2639
+ result: result.finalText,
2640
+ iterations: result.iterations,
2641
+ toolCalls: usage.toolCalls
2642
+ };
2643
+ };
2644
+ }
2645
+ function defaultFormatTaskInput(task) {
2646
+ return task.description ?? "";
2647
+ }
2648
+
2649
+ // src/types/provider.ts
2650
+ var ProviderError = class extends WrongStackError {
2651
+ status;
2652
+ retryable;
2653
+ providerId;
2654
+ body;
2655
+ constructor(message, status, retryable, providerId, opts = {}) {
2656
+ super({
2657
+ message,
2658
+ code: providerStatusToCode(status, opts.body?.type),
2659
+ subsystem: "provider",
2660
+ severity: status >= 500 ? "error" : "warning",
2661
+ recoverable: retryable,
2662
+ context: { providerId, status },
2663
+ cause: opts.cause
2664
+ });
2665
+ this.name = "ProviderError";
2666
+ this.status = status;
2667
+ this.retryable = retryable;
2668
+ this.providerId = providerId;
2669
+ this.body = opts.body;
2670
+ }
2671
+ /**
2672
+ * Render a one-line, user-facing description. Designed for the CLI/TUI
2673
+ * status line and the agent's retry warning. Avoids dumping raw JSON
2674
+ * (which is what users see today when a 529 lands and the log message
2675
+ * includes the full `{"type":"error",...}` body).
2676
+ *
2677
+ * Examples:
2678
+ * "minimax-coding-plan overloaded (529): High traffic detected. Upgrade for highspeed model. [req 06534785201de9c0…]"
2679
+ * "openai rate limited (429): Retry after 12s"
2680
+ * "anthropic invalid request (400): messages.0.role must be one of 'user'|'assistant'"
2681
+ * "groq HTTP 500 (server error)"
2682
+ */
2683
+ describe() {
2684
+ const kind = describeStatus(this.status, this.body?.type);
2685
+ const head = `${this.providerId} ${kind}`;
2686
+ const detail = this.body?.message?.trim();
2687
+ const reqId = this.body?.requestId ? ` [req ${this.body.requestId.slice(0, 16)}${this.body.requestId.length > 16 ? "\u2026" : ""}]` : "";
2688
+ if (detail && detail.length > 0) {
2689
+ return `${head}: ${truncate(detail, 240)}${reqId}`;
2690
+ }
2691
+ return `${head}${reqId}`;
2692
+ }
2693
+ };
2694
+ function describeStatus(status, type) {
2695
+ if (status === 0) return "network error";
2696
+ if (type === "overloaded_error" || status === 529) return `overloaded (${status})`;
2697
+ if (type === "rate_limit_error" || status === 429) return `rate limited (${status})`;
2698
+ if (type === "authentication_error" || status === 401) return `auth failed (${status})`;
2699
+ if (type === "permission_error" || status === 403) return `forbidden (${status})`;
2700
+ if (type === "not_found_error" || status === 404) return `not found (${status})`;
2701
+ if (type === "invalid_request_error" || status === 400) return `invalid request (${status})`;
2702
+ if (status === 408) return `timeout (${status})`;
2703
+ if (status >= 500 && status < 600) return `HTTP ${status} (server error)`;
2704
+ if (type) return `${type} (${status})`;
2705
+ return `HTTP ${status}`;
2706
+ }
2707
+ function truncate(s, n) {
2708
+ return s.length <= n ? s : `${s.slice(0, n - 1)}\u2026`;
2709
+ }
2710
+ function providerStatusToCode(status, type) {
2711
+ if (status === 0) return "PROVIDER_NETWORK_ERROR";
2712
+ if (type === "rate_limit_error" || status === 429) return "PROVIDER_RATE_LIMITED";
2713
+ if (type === "authentication_error" || status === 401) return "PROVIDER_AUTH_FAILED";
2714
+ if (type === "overloaded_error" || status === 529) return "PROVIDER_OVERLOADED";
2715
+ if (type === "invalid_request_error" || status === 400) return "PROVIDER_INVALID_REQUEST";
2716
+ if (status === 408) return "PROVIDER_NETWORK_ERROR";
2717
+ if (status >= 500) return "PROVIDER_SERVER_ERROR";
2718
+ return "PROVIDER_INVALID_REQUEST";
2719
+ }
2720
+ var FLEET_ROSTER_BUDGETS = {
2721
+ "audit-log": { timeoutMs: 8 * 60 * 1e3, maxIterations: 150, maxToolCalls: 500 },
2722
+ "bug-hunter": { timeoutMs: 15 * 60 * 1e3, maxIterations: 200, maxToolCalls: 600 },
2723
+ "refactor-planner": { timeoutMs: 12 * 60 * 1e3, maxIterations: 180, maxToolCalls: 550 },
2724
+ "security-scanner": { timeoutMs: 15 * 60 * 1e3, maxIterations: 200, maxToolCalls: 600 }
2725
+ };
2726
+ function applyRosterBudget(cfg) {
2727
+ const defaultBudget = FLEET_ROSTER_BUDGETS[cfg.role ?? ""];
2728
+ if (!defaultBudget) return cfg;
2729
+ return {
2730
+ ...cfg,
2731
+ timeoutMs: cfg.timeoutMs ?? defaultBudget.timeoutMs,
2732
+ maxIterations: cfg.maxIterations ?? defaultBudget.maxIterations,
2733
+ maxToolCalls: cfg.maxToolCalls ?? defaultBudget.maxToolCalls,
2734
+ maxTokens: cfg.maxTokens ?? defaultBudget.maxTokens,
2735
+ maxCostUsd: cfg.maxCostUsd ?? defaultBudget.maxCostUsd
2736
+ };
2737
+ }
2738
+
2739
+ // src/coordination/multi-agent-coordinator.ts
2740
+ var DefaultMultiAgentCoordinator = class extends EventEmitter {
2741
+ coordinatorId;
2742
+ config;
2743
+ runner;
2744
+ subagents = /* @__PURE__ */ new Map();
2745
+ pendingTasks = [];
2746
+ completedResults = [];
2747
+ totalIterations = 0;
2748
+ inFlight = 0;
2749
+ /**
2750
+ * Subagents currently being stopped. Set on entry to `stop()`, cleared
2751
+ * once `recordCompletion` lands the terminal TaskResult. Used by
2752
+ * `runDispatched` and `findIdleSubagent` to refuse mid-flight dispatch
2753
+ * to a subagent the caller has already asked to terminate — closes the
2754
+ * assign+terminate race where a fresh task could land on a worker that
2755
+ * was about to be killed.
2756
+ */
2757
+ terminating = /* @__PURE__ */ new Set();
2758
+ constructor(config, options = {}) {
2759
+ super();
2760
+ this.coordinatorId = config.coordinatorId;
2761
+ this.config = config;
2762
+ this.runner = options.runner;
2763
+ }
2764
+ /**
2765
+ * Replace the runner after construction. Used when the runner depends
2766
+ * on infrastructure (e.g. FleetBus) that isn't available until after
2767
+ * the coordinator's owning Director is built.
2768
+ */
2769
+ setRunner(runner) {
2770
+ this.runner = runner;
2771
+ }
2772
+ /**
2773
+ * Change the in-flight dispatch ceiling at runtime. Lowering does NOT
2774
+ * preempt running tasks — already-dispatched subagents finish their
2775
+ * current task; only future dispatches respect the new cap. Raising
2776
+ * immediately tries to fill the freed slots from the pending queue.
2777
+ */
2778
+ setMaxConcurrent(n) {
2779
+ if (!Number.isFinite(n) || n < 1) {
2780
+ throw new Error(`maxConcurrent must be a finite integer >= 1, got ${n}`);
2781
+ }
2782
+ this.config.maxConcurrent = Math.floor(n);
2783
+ this.tryDispatchNext();
2784
+ }
2785
+ async spawn(subagent) {
2786
+ const id = subagent.id || randomUUID();
2787
+ if (this.subagents.has(id)) {
2788
+ throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
2789
+ }
2790
+ const context = {
2791
+ subagentId: id,
2792
+ tasks: [],
2793
+ // Wired later by the caller via setSubagentBridge() once the
2794
+ // bidirectional bridge is created. Readers must null-check / use
2795
+ // hasParentBridge() — the type now reflects this.
2796
+ parentBridge: null,
2797
+ doneCondition: this.config.doneCondition,
2798
+ maxConcurrent: this.config.maxConcurrent ?? 4
2799
+ };
2800
+ this.subagents.set(id, {
2801
+ config: { ...subagent, id },
2802
+ context,
2803
+ status: "idle",
2804
+ abortController: new AbortController()
2805
+ });
2806
+ this.emit("subagent.started", { subagent: { ...subagent, id } });
2807
+ return { subagentId: id, agentId: id };
2808
+ }
2809
+ async assign(task) {
2810
+ this.pendingTasks.push(task);
2811
+ this.tryDispatchNext();
2812
+ }
2813
+ async delegate(to, msg) {
2814
+ const subagent = this.subagents.get(to);
2815
+ if (!subagent) throw new Error(`Subagent "${to}" not found`);
2816
+ if (!subagent.context.parentBridge) {
2817
+ throw new Error(`Subagent "${to}" has no parentBridge \u2014 call setSubagentBridge() first`);
2818
+ }
2819
+ await subagent.context.parentBridge.send(msg);
2820
+ }
2821
+ /**
2822
+ * Wire up the communication bridge for a subagent. Call after spawn() once
2823
+ * the caller has created the bidirectional connection.
2824
+ */
2825
+ setSubagentBridge(subagentId, bridge) {
2826
+ const subagent = this.subagents.get(subagentId);
2827
+ if (!subagent) throw new Error(`Subagent "${subagentId}" not found`);
2828
+ subagent.context.parentBridge = bridge;
2829
+ }
2830
+ async stop(subagentId) {
2831
+ const subagent = this.subagents.get(subagentId);
2832
+ if (!subagent) return;
2833
+ this.terminating.add(subagentId);
2834
+ subagent.abortController.abort();
2835
+ subagent.status = "stopped";
2836
+ subagent.currentTask = void 0;
2837
+ subagent.context.parentBridge = null;
2838
+ this.emit("subagent.stopped", { subagentId, reason: "stopped by coordinator" });
2839
+ }
2840
+ async stopAll() {
2841
+ this.drainPendingAsAborted("Coordinator stopAll() drained the pending queue");
2842
+ await Promise.allSettled([...this.subagents.keys()].map((id) => this.stop(id)));
2843
+ }
2844
+ getStatus() {
2845
+ return {
2846
+ coordinatorId: this.coordinatorId,
2847
+ subagents: Array.from(this.subagents.entries()).map(([id, s]) => ({
2848
+ id,
2849
+ name: s.config.name,
2850
+ status: s.status,
2851
+ currentTask: s.currentTask
2852
+ })),
2853
+ pendingTasks: this.pendingTasks.length,
2854
+ completedTasks: this.completedResults.length,
2855
+ totalIterations: this.totalIterations,
2856
+ done: this.isDone()
2857
+ };
2858
+ }
2859
+ /** Expose snapshot of completed results — useful for callers awaiting all done. */
2860
+ results() {
2861
+ return this.completedResults;
2862
+ }
2863
+ /**
2864
+ * Wait for one or more tasks to complete and return their results.
2865
+ * If a task is already done when called, returns immediately.
2866
+ * Resolves to an array in the same order as `taskIds`.
2867
+ */
2868
+ async awaitTasks(taskIds) {
2869
+ return Promise.all(
2870
+ taskIds.map((id) => {
2871
+ const cached = this.completedResults.find((r) => r.taskId === id);
2872
+ if (cached) return cached;
2873
+ return new Promise((resolve, reject) => {
2874
+ const timeout = setTimeout(() => {
2875
+ this.off("task.completed", handler);
2876
+ reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
2877
+ }, this.config.timeoutMs ?? 3e5);
2878
+ const handler = ({ result }) => {
2879
+ if (result.taskId === id) {
2880
+ clearTimeout(timeout);
2881
+ this.off("task.completed", handler);
2882
+ resolve(result);
2883
+ }
2884
+ };
2885
+ this.on("task.completed", handler);
2886
+ });
2887
+ })
2888
+ );
2889
+ }
2890
+ /**
2891
+ * Manual completion — for callers that drive subagents without a runner
2892
+ * (e.g. external orchestrators). When a runner is configured the coordinator
2893
+ * calls this itself.
2894
+ */
2895
+ completeTask(result) {
2896
+ this.recordCompletion(result);
2897
+ }
2898
+ // --- internal dispatching ---------------------------------------------
2899
+ tryDispatchNext() {
2900
+ while (this.canDispatch()) {
2901
+ const dispatchable = this.takeNextDispatchableTask();
2902
+ if (!dispatchable) {
2903
+ if (this.pendingTasks.length > 0 && !this.hasLiveSubagent()) {
2904
+ this.drainPendingAsAborted(
2905
+ "No live subagent available \u2014 all stopped or mid-termination"
2906
+ );
2907
+ }
2908
+ return;
2909
+ }
2910
+ const { subagentId, task } = dispatchable;
2911
+ this.runDispatched(subagentId, task).catch((err) => {
2912
+ this.recordCompletion({
2913
+ subagentId,
2914
+ taskId: task.id,
2915
+ status: "failed",
2916
+ error: classifySubagentError(err),
2917
+ iterations: 0,
2918
+ toolCalls: 0,
2919
+ durationMs: 0
2920
+ });
2921
+ });
2922
+ }
2923
+ }
2924
+ canDispatch() {
2925
+ const max = this.config.maxConcurrent ?? 4;
2926
+ return this.inFlight < max && this.pendingTasks.length > 0;
2927
+ }
2928
+ takeNextDispatchableTask() {
2929
+ for (let i = 0; i < this.pendingTasks.length; i++) {
2930
+ const task = this.pendingTasks[i];
2931
+ const subagentId = task.subagentId ? this.isIdleSubagent(task.subagentId) ? task.subagentId : null : this.findIdleSubagent();
2932
+ if (!subagentId) continue;
2933
+ this.pendingTasks.splice(i, 1);
2934
+ return { subagentId, task };
2935
+ }
2936
+ return null;
2937
+ }
2938
+ findIdleSubagent() {
2939
+ for (const [id, s] of this.subagents) {
2940
+ if (s.status === "idle" && !this.terminating.has(id)) return id;
2941
+ }
2942
+ return null;
2943
+ }
2944
+ isIdleSubagent(id) {
2945
+ const subagent = this.subagents.get(id);
2946
+ return !!subagent && subagent.status === "idle" && !this.terminating.has(id);
2947
+ }
2948
+ /**
2949
+ * Returns true iff at least one spawned subagent could still
2950
+ * process a task. A "live" subagent is one that is not stopped
2951
+ * AND not mid-termination — `running` workers count because they
2952
+ * will eventually finish and become idle.
2953
+ *
2954
+ * When no subagent has ever been spawned, returns `true` so a
2955
+ * pre-spawn `assign()` simply queues (legacy behaviour). The
2956
+ * dead-end detection only fires after `stop()` has retired every
2957
+ * spawned worker.
2958
+ *
2959
+ * Used by `tryDispatchNext` to detect a dead-end pending queue.
2960
+ */
2961
+ hasLiveSubagent() {
2962
+ if (this.subagents.size === 0) return true;
2963
+ for (const [id, s] of this.subagents) {
2964
+ if (s.status !== "stopped" && !this.terminating.has(id)) return true;
2965
+ }
2966
+ return false;
2967
+ }
2968
+ /**
2969
+ * Drain every pending task with a synthetic `aborted_by_parent`
2970
+ * completion event. Same shape as the `stopAll()` drain — we go
2971
+ * around `recordCompletion` because pending tasks were never
2972
+ * counted in `inFlight` and routing them through would trip the
2973
+ * underflow guard on every task after the first.
2974
+ */
2975
+ drainPendingAsAborted(message) {
2976
+ const dropped = this.pendingTasks.splice(0, this.pendingTasks.length);
2977
+ for (const t of dropped) {
2978
+ const synthetic = {
2979
+ subagentId: t.subagentId ?? "unassigned",
2980
+ taskId: t.id,
2981
+ status: "stopped",
2982
+ error: {
2983
+ kind: "aborted_by_parent",
2984
+ message,
2985
+ retryable: false
2986
+ },
2987
+ iterations: 0,
2988
+ toolCalls: 0,
2989
+ durationMs: 0
2990
+ };
2991
+ this.completedResults.push(synthetic);
2992
+ this.emit("task.completed", { task: t, result: synthetic });
2993
+ }
2994
+ }
2995
+ async runDispatched(subagentId, task) {
2996
+ const subagent = this.subagents.get(subagentId);
2997
+ if (!subagent) return;
2998
+ if (this.terminating.has(subagentId) || subagent.status === "stopped") {
2999
+ this.recordCompletion({
3000
+ subagentId,
3001
+ taskId: task.id,
3002
+ status: "stopped",
3003
+ error: {
3004
+ kind: "aborted_by_parent",
3005
+ message: "Subagent was terminated before task could start",
3006
+ retryable: false
3007
+ },
3008
+ iterations: 0,
3009
+ toolCalls: 0,
3010
+ durationMs: 0
3011
+ });
3012
+ return;
3013
+ }
3014
+ subagent.status = "running";
3015
+ subagent.currentTask = task.id;
3016
+ task.subagentId = subagentId;
3017
+ subagent.context.tasks.push(task);
3018
+ this.emit("task.assigned", { task, subagentId });
3019
+ const configWithRosterDefaults = applyRosterBudget(subagent.config);
3020
+ const budget = new SubagentBudget({
3021
+ maxIterations: configWithRosterDefaults.maxIterations ?? this.config.defaultBudget?.maxIterations,
3022
+ maxToolCalls: task.maxToolCalls ?? configWithRosterDefaults.maxToolCalls ?? this.config.defaultBudget?.maxToolCalls,
3023
+ maxTokens: configWithRosterDefaults.maxTokens ?? this.config.defaultBudget?.maxTokens,
3024
+ maxCostUsd: configWithRosterDefaults.maxCostUsd ?? this.config.defaultBudget?.maxCostUsd,
3025
+ timeoutMs: task.timeoutMs ?? configWithRosterDefaults.timeoutMs ?? this.config.defaultBudget?.timeoutMs
3026
+ });
3027
+ subagent.activeBudget = budget;
3028
+ if (!this.runner) {
3029
+ return;
3030
+ }
3031
+ this.inFlight++;
3032
+ const startTime = Date.now();
3033
+ const runCtx = {
3034
+ subagentId,
3035
+ config: subagent.config,
3036
+ budget,
3037
+ signal: subagent.abortController.signal,
3038
+ bridge: subagent.context.parentBridge || null
3039
+ };
3040
+ let result;
3041
+ budget.start();
3042
+ try {
3043
+ const outcome = await this.executeWithTimeout(this.runner, task, runCtx, budget);
3044
+ result = {
3045
+ subagentId,
3046
+ taskId: task.id,
3047
+ status: "success",
3048
+ result: outcome.result,
3049
+ iterations: outcome.iterations,
3050
+ toolCalls: outcome.toolCalls,
3051
+ durationMs: Date.now() - startTime
3052
+ };
3053
+ } catch (err) {
3054
+ const status = err instanceof BudgetExceededError && err.kind === "timeout" ? "timeout" : subagent.abortController.signal.aborted ? "stopped" : "failed";
3055
+ const usage = budget.usage();
3056
+ result = {
3057
+ subagentId,
3058
+ taskId: task.id,
3059
+ status,
3060
+ error: classifySubagentError(err, {
3061
+ parentAborted: subagent.abortController.signal.aborted
3062
+ }),
3063
+ iterations: usage.iterations,
3064
+ toolCalls: usage.toolCalls,
3065
+ durationMs: Date.now() - startTime
3066
+ };
3067
+ }
3068
+ this.recordCompletion(result);
3069
+ }
3070
+ async executeWithTimeout(runner, task, ctx, budget) {
3071
+ const initialTimeoutMs = budget.limits.timeoutMs;
3072
+ if (initialTimeoutMs === void 0) return runner(task, ctx);
3073
+ const start = Date.now();
3074
+ let timer = null;
3075
+ const timeoutPromise = new Promise((_, reject) => {
3076
+ const armFor = (ms) => {
3077
+ if (timer) clearTimeout(timer);
3078
+ timer = setTimeout(async () => {
3079
+ const elapsed = Date.now() - start;
3080
+ const limit = budget.limits.timeoutMs ?? initialTimeoutMs;
3081
+ if (!budget.onThreshold) {
3082
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
3083
+ reject(new BudgetExceededError("timeout", limit, elapsed));
3084
+ return;
3085
+ }
3086
+ try {
3087
+ const result = budget.onThreshold({
3088
+ kind: "timeout",
3089
+ used: elapsed,
3090
+ limit,
3091
+ requestDecision: () => new Promise((resolveDecision) => {
3092
+ budget._events?.emit("budget.threshold_reached", {
3093
+ kind: "timeout",
3094
+ used: elapsed,
3095
+ limit,
3096
+ timeoutMs: 3e4,
3097
+ extend: (extra) => resolveDecision({ extend: extra }),
3098
+ deny: () => resolveDecision("stop")
3099
+ });
3100
+ })
3101
+ });
3102
+ const decision = typeof result === "string" ? result : await result;
3103
+ if (decision === "continue") {
3104
+ armFor(Math.max(1e3, limit));
3105
+ return;
3106
+ }
3107
+ if (decision === "throw" || decision === "stop") {
3108
+ armFor(Math.max(1e3, limit));
3109
+ return;
3110
+ }
3111
+ if (decision.extend.timeoutMs !== void 0) {
3112
+ budget.limits.timeoutMs = decision.extend.timeoutMs;
3113
+ const newLimit = decision.extend.timeoutMs;
3114
+ const remaining = Math.max(1e3, newLimit - elapsed);
3115
+ armFor(remaining);
3116
+ return;
3117
+ }
3118
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
3119
+ reject(new BudgetExceededError("timeout", limit, elapsed));
3120
+ } catch (err) {
3121
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
3122
+ reject(
3123
+ err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
3124
+ );
3125
+ }
3126
+ }, ms);
3127
+ };
3128
+ armFor(initialTimeoutMs);
3129
+ });
3130
+ try {
3131
+ return await Promise.race([runner(task, ctx), timeoutPromise]);
3132
+ } finally {
3133
+ if (timer) clearTimeout(timer);
3134
+ }
3135
+ }
3136
+ recordCompletion(result) {
3137
+ this.completedResults.push(result);
3138
+ this.totalIterations += result.iterations;
3139
+ if (this.inFlight > 0) {
3140
+ this.inFlight--;
3141
+ } else if (this.runner) {
3142
+ this.emit("warning", {
3143
+ type: "inFlight_underflow",
3144
+ taskId: result.taskId,
3145
+ subagentId: result.subagentId
3146
+ });
3147
+ return;
3148
+ }
3149
+ const subagent = this.subagents.get(result.subagentId);
3150
+ if (subagent && subagent.status !== "stopped") {
3151
+ result.status === "failed" || result.status === "timeout";
3152
+ subagent.status = "idle";
3153
+ subagent.currentTask = void 0;
3154
+ if (subagent.abortController.signal.aborted) {
3155
+ subagent.abortController = new AbortController();
3156
+ }
3157
+ }
3158
+ this.terminating.delete(result.subagentId);
3159
+ this.emit("task.completed", {
3160
+ task: subagent?.context.tasks.find((t) => t.id === result.taskId) ?? { id: result.taskId },
3161
+ result
3162
+ });
3163
+ this.tryDispatchNext();
3164
+ if (this.isDone()) {
3165
+ this.emit("done", {
3166
+ results: this.completedResults,
3167
+ totalIterations: this.totalIterations
3168
+ });
3169
+ }
3170
+ }
3171
+ isDone() {
3172
+ if (this.config.doneCondition.type === "all_tasks_done") {
3173
+ return this.pendingTasks.length === 0 && this.inFlight === 0;
3174
+ }
3175
+ if (this.config.doneCondition.maxIterations !== void 0 && this.totalIterations >= this.config.doneCondition.maxIterations) {
3176
+ return true;
3177
+ }
3178
+ return false;
3179
+ }
3180
+ };
3181
+ function classifySubagentError(err, hints = {}) {
3182
+ const cause = err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : void 0;
3183
+ if (err instanceof ProviderError) {
3184
+ const baseMessage2 = err.describe();
3185
+ return providerErrorToSubagentError(err, baseMessage2, cause);
3186
+ }
3187
+ const baseMessage = err instanceof Error ? err.message : String(err);
3188
+ if (err instanceof BudgetExceededError) {
3189
+ const map = {
3190
+ iterations: "budget_iterations",
3191
+ tool_calls: "budget_tool_calls",
3192
+ tokens: "budget_tokens",
3193
+ cost: "budget_cost",
3194
+ timeout: "budget_timeout"
3195
+ };
3196
+ return {
3197
+ kind: map[err.kind],
3198
+ message: baseMessage,
3199
+ // Budgets are user-configured ceilings, not transient failures —
3200
+ // retrying with the same budget will hit the same ceiling. The
3201
+ // orchestrator must raise the budget or narrow the task first.
3202
+ retryable: false,
3203
+ cause
3204
+ };
3205
+ }
3206
+ if (hints.parentAborted) {
3207
+ return {
3208
+ kind: "aborted_by_parent",
3209
+ message: baseMessage,
3210
+ retryable: false,
3211
+ cause
3212
+ };
3213
+ }
3214
+ const lower = baseMessage.toLowerCase();
3215
+ if (/agent aborted$/i.test(baseMessage)) {
3216
+ return {
3217
+ kind: "aborted_by_parent",
3218
+ message: baseMessage,
3219
+ retryable: false,
3220
+ cause
3221
+ };
3222
+ }
3223
+ if (/agent exhausted iteration limit$/i.test(baseMessage)) {
3224
+ return { kind: "budget_iterations", message: baseMessage, retryable: false, cause };
3225
+ }
3226
+ if (/empty response$/i.test(baseMessage)) {
3227
+ return { kind: "empty_response", message: baseMessage, retryable: false, cause };
3228
+ }
3229
+ if (/^tool failed: /i.test(baseMessage)) {
3230
+ return { kind: "tool_failed", message: baseMessage, retryable: false, cause };
3231
+ }
3232
+ if (lower.includes("bridge transport") || /bridge.*(closed|disconnect)/i.test(baseMessage)) {
3233
+ return { kind: "bridge_failed", message: baseMessage, retryable: false, cause };
3234
+ }
3235
+ if (/context length|max.*tokens?.*exceeded|prompt is too long/i.test(baseMessage)) {
3236
+ return { kind: "context_overflow", message: baseMessage, retryable: false, cause };
3237
+ }
3238
+ return {
3239
+ kind: "unknown",
3240
+ message: baseMessage,
3241
+ retryable: false,
3242
+ cause
3243
+ };
3244
+ }
3245
+ function providerErrorToSubagentError(err, message, cause) {
3246
+ const status = err.status;
3247
+ if (status === 429 || err.body?.type === "rate_limit_error") {
3248
+ return {
3249
+ kind: "provider_rate_limit",
3250
+ message,
3251
+ retryable: true,
3252
+ // Conservative default: 5s. Provider-specific code can override
3253
+ // by emitting an error whose body carries an explicit hint.
3254
+ backoffMs: 5e3,
3255
+ cause
3256
+ };
3257
+ }
3258
+ if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
3259
+ return { kind: "provider_auth", message, retryable: false, cause };
3260
+ }
3261
+ if (status === 408 || status === 0) {
3262
+ return { kind: "provider_timeout", message, retryable: true, cause };
3263
+ }
3264
+ if (status >= 500 && status < 600) {
3265
+ return {
3266
+ kind: "provider_5xx",
3267
+ message,
3268
+ retryable: true,
3269
+ backoffMs: 3e3,
3270
+ cause
3271
+ };
3272
+ }
3273
+ return { kind: "unknown", message, retryable: err.retryable, cause };
3274
+ }
3275
+
3276
+ // src/execution/parallel-eternal-engine.ts
3277
+ function sleep2(ms) {
3278
+ return new Promise((resolve) => setTimeout(resolve, ms));
3279
+ }
3280
+ var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
3281
+ var ParallelEternalEngine = class {
3282
+ constructor(opts) {
3283
+ this.opts = opts;
3284
+ this.goalPath = goalFilePath(opts.projectRoot);
3285
+ this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
3286
+ this.timeoutMs = opts.iterationTimeoutMs ?? 3e5;
3287
+ this.agentFactory = opts.subagentFactory ?? (async (config) => ({
3288
+ agent: this.opts.agent,
3289
+ events: this.opts.agent.events
3290
+ }));
3291
+ }
3292
+ opts;
3293
+ state = "idle";
3294
+ stopRequested = false;
3295
+ iterationsSinceCompact = 0;
3296
+ iterations = 0;
3297
+ consecutiveFailures = 0;
3298
+ goalPath;
3299
+ slots;
3300
+ timeoutMs;
3301
+ coordinator = null;
3302
+ agentFactory;
3303
+ get currentState() {
3304
+ return this.state;
3305
+ }
3306
+ stop() {
3307
+ this.stopRequested = true;
3308
+ void this.persistState("stopped").catch(() => {
3309
+ });
3310
+ this.state = "stopped";
3311
+ }
3312
+ async prime() {
3313
+ this.stopRequested = false;
3314
+ this.state = "running";
3315
+ await this.persistState("running");
3316
+ }
3317
+ async run() {
3318
+ this.state = "running";
3319
+ await this.persistState("running");
3320
+ const config = {
3321
+ coordinatorId: `parallel-${randomUUID().slice(0, 8)}`,
3322
+ maxConcurrent: this.slots,
3323
+ doneCondition: { type: "all_tasks_done" }
3324
+ };
3325
+ this.coordinator = new DefaultMultiAgentCoordinator(config);
3326
+ const runner = makeAgentSubagentRunner({ factory: this.agentFactory });
3327
+ this.coordinator.setRunner?.(runner);
3328
+ try {
3329
+ while (!this.stopRequested) {
3330
+ try {
3331
+ await this.runOneIteration();
3332
+ } catch (err) {
3333
+ this.consecutiveFailures++;
3334
+ this.opts.onError?.(err instanceof Error ? err : new Error(String(err)), this.consecutiveFailures);
3335
+ await this.appendFailure("engine error", err instanceof Error ? err.message : String(err));
3336
+ }
3337
+ if (this.stopRequested) break;
3338
+ await sleep2(2e3);
3339
+ }
3340
+ } finally {
3341
+ this.state = "stopped";
3342
+ await this.persistState("stopped").catch(() => {
3343
+ });
3344
+ }
3345
+ }
3346
+ /**
3347
+ * Execute one tick: decompose → fan-out → aggregate → compact.
3348
+ * Called by the REPL in its main loop (REPL drives, engine is stateless per tick).
3349
+ */
3350
+ async runOneIteration() {
3351
+ this.iterations++;
3352
+ const goal = await loadGoal(this.goalPath);
3353
+ if (!goal) {
3354
+ this.stopRequested = true;
3355
+ return false;
3356
+ }
3357
+ if (goal.goalState !== "active") {
3358
+ this.stopRequested = true;
3359
+ return false;
3360
+ }
3361
+ if (!this.coordinator) {
3362
+ const config = {
3363
+ coordinatorId: `parallel-${randomUUID().slice(0, 8)}`,
3364
+ maxConcurrent: this.slots,
3365
+ doneCondition: { type: "all_tasks_done" }
3366
+ };
3367
+ this.coordinator = new DefaultMultiAgentCoordinator(config);
3368
+ const runner = makeAgentSubagentRunner({ factory: this.agentFactory });
3369
+ this.coordinator.setRunner?.(runner);
3370
+ }
3371
+ const tasks = await this.decomposeGoal(goal);
3372
+ if (!tasks || tasks.length === 0) {
3373
+ await sleep2(5e3);
3374
+ return false;
3375
+ }
3376
+ const fanOut = await this.fanOut(goal, tasks);
3377
+ this.iterationsSinceCompact++;
3378
+ const successCount = fanOut.results.filter((r) => r.status === "success").length;
3379
+ const status = fanOut.goalComplete ? "success" : fanOut.allSuccessful ? "success" : "failure";
3380
+ const note = [
3381
+ `${successCount}/${fanOut.results.length} subagents succeeded`,
3382
+ fanOut.goalComplete ? "[GOAL_COMPLETE]" : "",
3383
+ fanOut.partialOutput ? `Output: ${fanOut.partialOutput.slice(0, 120)}` : ""
3384
+ ].filter(Boolean).join(" | ");
3385
+ await this.appendIterationEntry({
3386
+ source: "parallel",
3387
+ task: `parallel:${tasks.length} slots \u2014 ${tasks.slice(0, 3).join(", ")}${tasks.length > 3 ? "..." : ""}`,
3388
+ status,
3389
+ note
3390
+ });
3391
+ if (fanOut.goalComplete) {
3392
+ this.stopRequested = true;
3393
+ return true;
3394
+ }
3395
+ await this.maybeCompact();
3396
+ return fanOut.allSuccessful;
3397
+ }
3398
+ // -------------------------------------------------------------------------
3399
+ // Fan-out
3400
+ // -------------------------------------------------------------------------
3401
+ async fanOut(goal, tasks) {
3402
+ const coordinator = this.coordinator;
3403
+ const slotCount = Math.min(this.slots, tasks.length);
3404
+ const recentJournal = goal.journal.slice(-5).map((e) => ` #${e.iteration} [${e.status}] ${e.task}${e.note ? ` \u2014 ${e.note.slice(0, 80)}` : ""}`).join("\n");
3405
+ const directivePreamble = [
3406
+ "\u2550\u2550\u2550 ETERNAL AUTONOMY \u2014 parallel task slot \u2550\u2550\u2550",
3407
+ "",
3408
+ `Mission: ${goal.goal}`,
3409
+ `Total parallel slots: ${slotCount}`,
3410
+ "",
3411
+ recentJournal ? `Recent journal (last 5):
3412
+ ${recentJournal}` : "No prior iterations.",
3413
+ "",
3414
+ "\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
3415
+ "\u2022 Execute the assigned task end-to-end using multiple tool calls.",
3416
+ "\u2022 Emit `[done]` on its own line when the task is complete.",
3417
+ "\u2022 Do not ask for confirmation \u2014 YOLO is active.",
3418
+ "\u2022 If the overall Mission is accomplished, emit `[GOAL_COMPLETE]` followed by a verification recipe.",
3419
+ "\u2022 Keep output concise \u2014 summarize findings, do not transcribe files."
3420
+ ].join("\n");
3421
+ const taskIds = [];
3422
+ const subagentIds = [];
3423
+ const spawnPromises = [];
3424
+ for (let i = 0; i < slotCount; i++) {
3425
+ const task = tasks[i];
3426
+ const subagentId = `parallel-${this.iterations}-${i}`;
3427
+ const taskId = randomUUID();
3428
+ const spec = {
3429
+ id: taskId,
3430
+ description: `${directivePreamble}
3431
+
3432
+ \u2500\u2500 SLOT ${i + 1}/${slotCount} \u2500\u2500
3433
+ Task: ${task}
3434
+ `,
3435
+ subagentId
3436
+ };
3437
+ spawnPromises.push((async () => {
3438
+ try {
3439
+ await coordinator.spawn({
3440
+ id: subagentId,
3441
+ name: `slot-${subagentId.slice(-6)}`,
3442
+ maxIterations: 50,
3443
+ maxToolCalls: 200,
3444
+ timeoutMs: this.timeoutMs
3445
+ });
3446
+ subagentIds.push(subagentId);
3447
+ taskIds.push(taskId);
3448
+ await coordinator.assign(spec);
3449
+ } catch {
3450
+ }
3451
+ })());
3452
+ }
3453
+ await Promise.all(spawnPromises);
3454
+ if (taskIds.length === 0) {
3455
+ return { results: [], allSuccessful: false, goalComplete: false, partialOutput: "" };
3456
+ }
3457
+ let results = [];
3458
+ try {
3459
+ const ctrl = new AbortController();
3460
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs + 6e4);
3461
+ try {
3462
+ results = await coordinator.awaitTasks(taskIds);
3463
+ } finally {
3464
+ clearTimeout(timer);
3465
+ }
3466
+ } catch {
3467
+ results = coordinator.results().slice(-taskIds.length);
3468
+ }
3469
+ const allSuccessful = results.length > 0 && results.every((r) => r.status === "success");
3470
+ const goalComplete = results.some(
3471
+ (r) => r.status === "success" && typeof r.result === "string" && GOAL_COMPLETE_MARKER2.test(r.result)
3472
+ );
3473
+ const partialOutput = results.map((r) => typeof r.result === "string" ? r.result : "").filter(Boolean).join("\n\n");
3474
+ return { results, allSuccessful, goalComplete, partialOutput };
3475
+ }
3476
+ // -------------------------------------------------------------------------
3477
+ // Goal decomposition
3478
+ // -------------------------------------------------------------------------
3479
+ async decomposeGoal(goal) {
3480
+ const todos = this.opts.agent.ctx?.todos;
3481
+ const tasks = [];
3482
+ if (Array.isArray(todos)) {
3483
+ const pending = todos.filter((t) => t.status === "pending").slice(0, this.slots);
3484
+ for (const t of pending) {
3485
+ tasks.push(`[todo] ${t.content}`);
3486
+ }
3487
+ }
3488
+ if (tasks.length < this.slots) {
3489
+ try {
3490
+ const gitStatus = await (this.opts.gitStatusReader?.() ?? this.readGitStatus());
3491
+ const dirty = gitStatus.trim();
3492
+ if (dirty) {
3493
+ const lines = dirty.split("\n").slice(0, this.slots - tasks.length);
3494
+ for (const line of lines) {
3495
+ const file = line.replace(/^[ MADRUC?]{2}\s*/, "").trim();
3496
+ if (file) tasks.push(`[git] inspect and fix: ${file}`);
3497
+ }
3498
+ }
3499
+ } catch {
3500
+ }
3501
+ }
3502
+ if (tasks.length < this.slots) {
3503
+ const remaining = this.slots - tasks.length;
3504
+ const brainstormed = await this.brainstormSubtasks(goal, remaining);
3505
+ tasks.push(...brainstormed);
3506
+ }
3507
+ return tasks.length > 0 ? tasks.slice(0, this.slots) : null;
3508
+ }
3509
+ async readGitStatus() {
3510
+ const { execFile: execFile2 } = await import('child_process');
3511
+ const { promisify: promisify2 } = await import('util');
3512
+ const execFileP2 = promisify2(execFile2);
3513
+ const { stdout } = await execFileP2("git", ["status", "--porcelain"], {
3514
+ cwd: this.opts.projectRoot,
3515
+ timeout: 5e3
3516
+ });
3517
+ return stdout;
3518
+ }
3519
+ async brainstormSubtasks(goal, count) {
3520
+ const lastFew = goal.journal.slice(-5).map((e) => ` - [${e.status}] ${e.task}`).join("\n");
3521
+ const directive = [
3522
+ `Decompose this goal into exactly ${count} independent sub-tasks for parallel execution.`,
3523
+ "",
3524
+ `Goal: ${goal.goal}`,
3525
+ "",
3526
+ lastFew ? `Recent:
3527
+ ${lastFew}` : "No prior iterations.",
3528
+ "",
3529
+ `Output exactly ${count} tasks, one per line, under 120 chars each.`,
3530
+ "Format: TASK-1 | TASK-2 | ... (pipe-separated, no numbering, no preamble).",
3531
+ "Each task must be independently actionable with no shared dependencies."
3532
+ ].join("\n");
3533
+ try {
3534
+ const ctrl = new AbortController();
3535
+ const timer = setTimeout(() => ctrl.abort(), 6e4);
3536
+ try {
3537
+ const result = await this.opts.agent.run([{ type: "text", text: directive }], {
3538
+ signal: ctrl.signal,
3539
+ maxIterations: 1
3540
+ });
3541
+ if (result.status !== "done") return [];
3542
+ const text = (result.finalText ?? "").trim();
3543
+ if (!text) return [];
3544
+ const tasks = text.split("|").map((t) => t.trim()).filter((t) => t.length > 10 && t.length < 240);
3545
+ return tasks.slice(0, count);
3546
+ } finally {
3547
+ clearTimeout(timer);
3548
+ }
3549
+ } catch {
3550
+ return [];
3551
+ }
3552
+ }
3553
+ // -------------------------------------------------------------------------
3554
+ // Compaction
3555
+ // -------------------------------------------------------------------------
3556
+ async maybeCompact() {
3557
+ const compactor = this.opts.compactor;
3558
+ if (!compactor) return;
3559
+ const ctx = this.opts.agent.ctx;
3560
+ if (!ctx) return;
3561
+ const shouldRun = this.iterationsSinceCompact >= (this.opts.compactEveryNIterations ?? 25);
3562
+ if (!shouldRun) return;
3563
+ const report = await compactor.compact(ctx, { aggressive: false });
3564
+ this.iterationsSinceCompact = 0;
3565
+ await this.appendIterationEntry({
3566
+ source: "manual",
3567
+ task: "compaction (cadence)",
3568
+ status: "success",
3569
+ note: `saved ~${report.before - report.after} tokens`
3570
+ });
3571
+ }
3572
+ // -------------------------------------------------------------------------
3573
+ // Helpers
3574
+ // -------------------------------------------------------------------------
3575
+ async appendIterationEntry(entry) {
3576
+ const current = await loadGoal(this.goalPath);
3577
+ if (!current) return;
3578
+ const updated = appendJournal(current, entry);
3579
+ await saveGoal(this.goalPath, updated);
3580
+ const entryWithMeta = {
3581
+ at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
3582
+ iteration: updated.iterations,
3583
+ ...entry
3584
+ };
3585
+ this.opts.onIteration?.(entryWithMeta);
3586
+ }
3587
+ async appendFailure(task, note) {
3588
+ await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
3589
+ }
3590
+ async persistState(state) {
3591
+ const current = await loadGoal(this.goalPath);
3592
+ if (!current) return;
3593
+ if (current.engineState === state) return;
3594
+ await saveGoal(this.goalPath, { ...current, engineState: state });
3595
+ }
3596
+ };
3597
+
2234
3598
  // src/execution/autonomy-prompt-contributor.ts
2235
3599
  function makeAutonomyPromptContributor(opts) {
2236
3600
  return async (ctx) => {
@@ -2367,78 +3731,6 @@ function buildGoalPreamble(goal) {
2367
3731
  ].join("\n");
2368
3732
  }
2369
3733
 
2370
- // src/types/provider.ts
2371
- var ProviderError = class extends WrongStackError {
2372
- status;
2373
- retryable;
2374
- providerId;
2375
- body;
2376
- constructor(message, status, retryable, providerId, opts = {}) {
2377
- super({
2378
- message,
2379
- code: providerStatusToCode(status, opts.body?.type),
2380
- subsystem: "provider",
2381
- severity: status >= 500 ? "error" : "warning",
2382
- recoverable: retryable,
2383
- context: { providerId, status },
2384
- cause: opts.cause
2385
- });
2386
- this.name = "ProviderError";
2387
- this.status = status;
2388
- this.retryable = retryable;
2389
- this.providerId = providerId;
2390
- this.body = opts.body;
2391
- }
2392
- /**
2393
- * Render a one-line, user-facing description. Designed for the CLI/TUI
2394
- * status line and the agent's retry warning. Avoids dumping raw JSON
2395
- * (which is what users see today when a 529 lands and the log message
2396
- * includes the full `{"type":"error",...}` body).
2397
- *
2398
- * Examples:
2399
- * "minimax-coding-plan overloaded (529): High traffic detected. Upgrade for highspeed model. [req 06534785201de9c0…]"
2400
- * "openai rate limited (429): Retry after 12s"
2401
- * "anthropic invalid request (400): messages.0.role must be one of 'user'|'assistant'"
2402
- * "groq HTTP 500 (server error)"
2403
- */
2404
- describe() {
2405
- const kind = describeStatus(this.status, this.body?.type);
2406
- const head = `${this.providerId} ${kind}`;
2407
- const detail = this.body?.message?.trim();
2408
- const reqId = this.body?.requestId ? ` [req ${this.body.requestId.slice(0, 16)}${this.body.requestId.length > 16 ? "\u2026" : ""}]` : "";
2409
- if (detail && detail.length > 0) {
2410
- return `${head}: ${truncate(detail, 240)}${reqId}`;
2411
- }
2412
- return `${head}${reqId}`;
2413
- }
2414
- };
2415
- function describeStatus(status, type) {
2416
- if (status === 0) return "network error";
2417
- if (type === "overloaded_error" || status === 529) return `overloaded (${status})`;
2418
- if (type === "rate_limit_error" || status === 429) return `rate limited (${status})`;
2419
- if (type === "authentication_error" || status === 401) return `auth failed (${status})`;
2420
- if (type === "permission_error" || status === 403) return `forbidden (${status})`;
2421
- if (type === "not_found_error" || status === 404) return `not found (${status})`;
2422
- if (type === "invalid_request_error" || status === 400) return `invalid request (${status})`;
2423
- if (status === 408) return `timeout (${status})`;
2424
- if (status >= 500 && status < 600) return `HTTP ${status} (server error)`;
2425
- if (type) return `${type} (${status})`;
2426
- return `HTTP ${status}`;
2427
- }
2428
- function truncate(s, n) {
2429
- return s.length <= n ? s : `${s.slice(0, n - 1)}\u2026`;
2430
- }
2431
- function providerStatusToCode(status, type) {
2432
- if (status === 0) return "PROVIDER_NETWORK_ERROR";
2433
- if (type === "rate_limit_error" || status === 429) return "PROVIDER_RATE_LIMITED";
2434
- if (type === "authentication_error" || status === 401) return "PROVIDER_AUTH_FAILED";
2435
- if (type === "overloaded_error" || status === 529) return "PROVIDER_OVERLOADED";
2436
- if (type === "invalid_request_error" || status === 400) return "PROVIDER_INVALID_REQUEST";
2437
- if (status === 408) return "PROVIDER_NETWORK_ERROR";
2438
- if (status >= 500) return "PROVIDER_SERVER_ERROR";
2439
- return "PROVIDER_INVALID_REQUEST";
2440
- }
2441
-
2442
3734
  // src/execution/retry-policy.ts
2443
3735
  var DefaultRetryPolicy = class _DefaultRetryPolicy {
2444
3736
  static NETWORK_ERR_RE = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i;
@@ -2679,6 +3971,6 @@ function parseDescription(raw) {
2679
3971
  return { trigger, scope };
2680
3972
  }
2681
3973
 
2682
- export { AutoCompactionMiddleware, AutonomousRunner, DefaultErrorHandler, DefaultRetryPolicy, DefaultSkillLoader, DoneConditionChecker, EternalAutonomyEngine, HybridCompactor, IntelligentCompactor, SelectiveCompactor, ToolExecutor, buildGoalPreamble, makeAutonomyPromptContributor };
3974
+ export { AutoCompactionMiddleware, AutonomousRunner, DefaultErrorHandler, DefaultRetryPolicy, DefaultSkillLoader, DoneConditionChecker, EternalAutonomyEngine, HybridCompactor, IntelligentCompactor, ParallelEternalEngine, SelectiveCompactor, ToolExecutor, buildGoalPreamble, makeAutonomyPromptContributor };
2683
3975
  //# sourceMappingURL=index.js.map
2684
3976
  //# sourceMappingURL=index.js.map