@wrongstack/core 0.6.4 → 0.6.6

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 (66) 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 +22 -21
  10. package/dist/defaults/index.js +1359 -919
  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 +116 -13
  14. package/dist/execution/index.js +1438 -131
  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-HHgaq5ue.d.ts} +3 -3
  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 +29 -28
  21. package/dist/index.js +1105 -666
  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 +9 -4
  37. package/dist/sdd/index.js +21 -0
  38. package/dist/sdd/index.js.map +1 -1
  39. package/dist/{secret-scrubber-CfMdAJ_l.d.ts → secret-scrubber-CT7wefiO.d.ts} +1 -1
  40. package/dist/{secret-scrubber-BzQR5BiL.d.ts → secret-scrubber-I0QHY_ob.d.ts} +1 -1
  41. package/dist/security/index.d.ts +3 -3
  42. package/dist/{selector-C7HqnZJU.d.ts → selector-DDb_mq9X.d.ts} +1 -1
  43. package/dist/{session-reader-CzfRA6Vk.d.ts → session-reader-B9nVkziM.d.ts} +1 -1
  44. package/dist/storage/index.d.ts +6 -6
  45. package/dist/storage/index.js +29 -3
  46. package/dist/storage/index.js.map +1 -1
  47. package/dist/{system-prompt-Dl2QY1_B.d.ts → system-prompt-gL06H9P4.d.ts} +1 -1
  48. package/dist/{task-graph-BITvWt4t.d.ts → task-graph-D1YQbpxF.d.ts} +1 -0
  49. package/dist/{tool-executor-FoxBjULX.d.ts → tool-executor-BF7QfYVE.d.ts} +4 -4
  50. package/dist/types/index.d.ts +16 -16
  51. package/dist/types/index.js +5 -16
  52. package/dist/types/index.js.map +1 -1
  53. package/dist/utils/index.d.ts +1 -1
  54. package/package.json +1 -1
  55. package/skills/audit-log/SKILL.md +57 -28
  56. package/skills/bug-hunter/SKILL.md +85 -61
  57. package/skills/git-flow/SKILL.md +73 -18
  58. package/skills/multi-agent/SKILL.md +69 -40
  59. package/skills/node-modern/SKILL.md +111 -19
  60. package/skills/prompt-engineering/SKILL.md +97 -16
  61. package/skills/react-modern/SKILL.md +104 -18
  62. package/skills/refactor-planner/SKILL.md +73 -43
  63. package/skills/sdd/SKILL.md +123 -165
  64. package/skills/security-scanner/SKILL.md +95 -93
  65. package/skills/skill-creator/SKILL.md +58 -25
  66. 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));
@@ -1788,23 +1789,33 @@ var EternalAutonomyEngine = class {
1788
1789
  * `agent.run()` avoids race conditions on the shared Context.
1789
1790
  */
1790
1791
  async runOneIteration() {
1792
+ const emit = (stage) => {
1793
+ this.opts.onStage?.(stage);
1794
+ };
1791
1795
  const goal = await loadGoal(this.goalPath);
1792
1796
  if (!goal) {
1797
+ emit({ phase: "stopped" });
1793
1798
  this.stopRequested = true;
1794
1799
  return false;
1795
1800
  }
1796
1801
  const missionState = goal.goalState ?? "active";
1797
1802
  if (missionState !== "active") {
1803
+ emit({ phase: missionState === "paused" ? "paused" : "stopped" });
1798
1804
  this.stopRequested = true;
1799
1805
  return false;
1800
1806
  }
1807
+ emit({ phase: "decide", reason: "picking next task" });
1801
1808
  const action = await this.decide(goal);
1802
1809
  if (!action) {
1803
1810
  if (!this.stopRequested) {
1811
+ emit({ phase: "sleep", ms: 5e3 });
1804
1812
  await sleep(5e3);
1813
+ } else {
1814
+ emit({ phase: "stopped" });
1805
1815
  }
1806
1816
  return false;
1807
1817
  }
1818
+ emit({ phase: "execute", task: action.task });
1808
1819
  const ctrl = new AbortController();
1809
1820
  this.currentCtrl = ctrl;
1810
1821
  const timer = setTimeout(
@@ -1882,6 +1893,7 @@ var EternalAutonomyEngine = class {
1882
1893
  tokens,
1883
1894
  costUsd
1884
1895
  });
1896
+ emit({ phase: "reflect", status, note });
1885
1897
  let iterationIndex = 0;
1886
1898
  try {
1887
1899
  const reloaded = await loadGoal(this.goalPath);
@@ -1903,6 +1915,7 @@ var EternalAutonomyEngine = class {
1903
1915
  this.consecutiveTransientRetries++;
1904
1916
  const delay = this.computeTransientBackoffMs();
1905
1917
  if (delay > 0) {
1918
+ emit({ phase: "sleep", ms: delay });
1906
1919
  await this.sleepInterruptible(delay);
1907
1920
  }
1908
1921
  return false;
@@ -1916,6 +1929,9 @@ var EternalAutonomyEngine = class {
1916
1929
  return false;
1917
1930
  }
1918
1931
  this.consecutiveTransientRetries = 0;
1932
+ const cycleGapMs = this.opts.cycleGapMs ?? 1e3;
1933
+ emit({ phase: "sleep", ms: cycleGapMs });
1934
+ await sleep(cycleGapMs);
1919
1935
  if (GOAL_COMPLETE_MARKER.test(finalText)) {
1920
1936
  await this.markGoalCompleted(action, finalText);
1921
1937
  this.stopRequested = true;
@@ -2231,140 +2247,418 @@ function sleep(ms) {
2231
2247
  return new Promise((resolve) => setTimeout(resolve, ms));
2232
2248
  }
2233
2249
 
2234
- // src/execution/autonomy-prompt-contributor.ts
2235
- function makeAutonomyPromptContributor(opts) {
2236
- return async (ctx) => {
2237
- if (ctx.subagent) return [];
2238
- if (!opts.enabled()) return [];
2239
- let goal;
2250
+ // src/coordination/subagent-budget.ts
2251
+ var BudgetExceededError = class extends Error {
2252
+ kind;
2253
+ limit;
2254
+ observed;
2255
+ constructor(kind, limit, observed) {
2256
+ super(`Budget exceeded: ${kind} (limit=${limit}, observed=${observed})`);
2257
+ this.name = "BudgetExceededError";
2258
+ this.kind = kind;
2259
+ this.limit = limit;
2260
+ this.observed = observed;
2261
+ }
2262
+ };
2263
+ var BudgetThresholdSignal = class extends Error {
2264
+ kind;
2265
+ limit;
2266
+ used;
2267
+ /** Resolves to 'extend' (with optional new limits) or 'stop' */
2268
+ decision;
2269
+ constructor(kind, limit, used, decision) {
2270
+ super(`Budget soft limit: ${kind} (limit=${limit}, used=${used})`);
2271
+ this.name = "BudgetThresholdSignal";
2272
+ this.kind = kind;
2273
+ this.limit = limit;
2274
+ this.used = used;
2275
+ this.decision = decision;
2276
+ }
2277
+ };
2278
+ var SubagentBudget = class _SubagentBudget {
2279
+ limits;
2280
+ iterations = 0;
2281
+ toolCalls = 0;
2282
+ tokenInput = 0;
2283
+ tokenOutput = 0;
2284
+ costUsd = 0;
2285
+ startTime = null;
2286
+ _onThreshold;
2287
+ /**
2288
+ * Tracks which budget kinds currently have an extension request
2289
+ * in flight. While a kind is here, further `checkLimit` calls for the
2290
+ * same kind are no-ops — without this dedup, every `recordIteration`
2291
+ * after the limit is reached spawns a fresh decision Promise (until
2292
+ * the first one lands and patches limits), flooding the FleetBus
2293
+ * with redundant threshold events. Cleared in `checkLimitAsync`'s
2294
+ * `finally`.
2295
+ */
2296
+ pendingExtensions = /* @__PURE__ */ new Set();
2297
+ /**
2298
+ * Hard cap on how long `checkLimitAsync` waits for the coordinator to
2299
+ * respond before defaulting to 'stop'. Without this fallback an absent
2300
+ * or hung listener (Director not built / event filter detached mid-run)
2301
+ * leaves the budget over-limit and never enforces anything, since
2302
+ * `checkLimit` returns synchronously via `void this.checkLimitAsync`.
2303
+ * Hardcoded for now — most fleets set their own per-task timeout that
2304
+ * dwarfs this anyway. 30 s matches the existing event-emit timeoutMs.
2305
+ */
2306
+ static DECISION_TIMEOUT_MS = 3e4;
2307
+ /**
2308
+ * Injected by the runner when wiring the budget to its EventBus.
2309
+ * Used by `checkLimitAsync` to emit `budget.threshold_reached` events.
2310
+ */
2311
+ _events;
2312
+ /**
2313
+ * Optional callback for soft-limit handling. When set, the budget will
2314
+ * call this instead of throwing when a limit is first reached. The
2315
+ * handler decides whether to throw, continue, or ask the coordinator.
2316
+ */
2317
+ get onThreshold() {
2318
+ return this._onThreshold;
2319
+ }
2320
+ set onThreshold(fn) {
2321
+ this._onThreshold = fn;
2322
+ }
2323
+ constructor(limits = {}) {
2324
+ this.limits = Object.freeze({ ...limits });
2325
+ }
2326
+ start() {
2327
+ this.startTime = Date.now();
2328
+ }
2329
+ /** Returns true if we're within 10% of any limit — useful for pre-flight checks. */
2330
+ isNearLimit() {
2331
+ const { maxIterations, maxToolCalls, maxTokens, maxCostUsd } = this.limits;
2332
+ if (maxIterations && this.iterations >= maxIterations * 0.9) return true;
2333
+ if (maxToolCalls && this.toolCalls >= maxToolCalls * 0.9) return true;
2334
+ if (maxTokens && this.tokenInput + this.tokenOutput >= maxTokens * 0.9) return true;
2335
+ if (maxCostUsd && this.costUsd >= maxCostUsd * 0.9) return true;
2336
+ return false;
2337
+ }
2338
+ /**
2339
+ * Synchronous budget check — always throws synchronously so callers
2340
+ * (especially test event handlers using `expect().toThrow()`) get an
2341
+ * unhandled rejection when the budget is exceeded without a handler.
2342
+ * When `onThreshold` IS configured, the actual async threshold-handling
2343
+ * is dispatched as a fire-and-forget promise.
2344
+ */
2345
+ checkLimit(kind, used, limit) {
2346
+ if (!this._onThreshold) {
2347
+ throw new BudgetExceededError(kind, limit, used);
2348
+ }
2349
+ const bus = this._events;
2350
+ if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
2351
+ throw new BudgetExceededError(kind, limit, used);
2352
+ }
2353
+ if (this.pendingExtensions.has(kind)) return;
2354
+ this.pendingExtensions.add(kind);
2355
+ const decision = this.negotiateExtension(kind, used, limit);
2356
+ throw new BudgetThresholdSignal(kind, limit, used, decision);
2357
+ }
2358
+ /**
2359
+ * Drive the threshold handler to a decision. Resolves with `'stop'`
2360
+ * (signal the runner to abort) or `{ extend: ... }` (limits already
2361
+ * patched in-place; the runner should not abort). Always releases the
2362
+ * `pendingExtensions` slot in `finally`.
2363
+ *
2364
+ * The 'continue' return from a sync handler is treated as
2365
+ * `{ extend: {} }` — keep going without patching, next overrun will
2366
+ * fire a fresh signal.
2367
+ */
2368
+ async negotiateExtension(kind, used, limit) {
2240
2369
  try {
2241
- goal = await loadGoal(opts.goalPath);
2242
- } catch {
2243
- return [];
2370
+ const result = this._onThreshold({
2371
+ kind,
2372
+ used,
2373
+ limit,
2374
+ // Inject a requestDecision helper the handler can call to emit the
2375
+ // budget.threshold_reached event and wait for the coordinator's verdict.
2376
+ // A hard fallback timer guarantees the promise eventually resolves
2377
+ // even if no listener responds — without it, an absent/detached
2378
+ // Director would leave the budget permanently in "asking" state.
2379
+ requestDecision: () => {
2380
+ const bus = this._events;
2381
+ if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
2382
+ return Promise.resolve("stop");
2383
+ }
2384
+ return new Promise((resolve) => {
2385
+ let resolved = false;
2386
+ const respond = (d) => {
2387
+ if (resolved) return;
2388
+ resolved = true;
2389
+ resolve(d);
2390
+ };
2391
+ const fallback = setTimeout(
2392
+ () => respond("stop"),
2393
+ _SubagentBudget.DECISION_TIMEOUT_MS
2394
+ );
2395
+ bus.emit("budget.threshold_reached", {
2396
+ kind,
2397
+ used,
2398
+ limit,
2399
+ timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
2400
+ extend: (extra) => {
2401
+ clearTimeout(fallback);
2402
+ respond({ extend: extra });
2403
+ },
2404
+ deny: () => {
2405
+ clearTimeout(fallback);
2406
+ respond("stop");
2407
+ }
2408
+ });
2409
+ });
2410
+ }
2411
+ });
2412
+ if (result === "throw") return "stop";
2413
+ if (result === "continue") return { extend: {} };
2414
+ const decision = await result;
2415
+ if (decision === "stop") return "stop";
2416
+ const ext = decision.extend;
2417
+ if (ext.maxIterations !== void 0) {
2418
+ this.limits.maxIterations = ext.maxIterations;
2419
+ }
2420
+ if (ext.maxToolCalls !== void 0) {
2421
+ this.limits.maxToolCalls = ext.maxToolCalls;
2422
+ }
2423
+ if (ext.maxTokens !== void 0) {
2424
+ this.limits.maxTokens = ext.maxTokens;
2425
+ }
2426
+ if (ext.maxCostUsd !== void 0) {
2427
+ this.limits.maxCostUsd = ext.maxCostUsd;
2428
+ }
2429
+ if (ext.timeoutMs !== void 0) {
2430
+ this.limits.timeoutMs = ext.timeoutMs;
2431
+ }
2432
+ return decision;
2433
+ } finally {
2434
+ this.pendingExtensions.delete(kind);
2244
2435
  }
2245
- if (!goal) return [];
2246
- const missionState = goal.goalState ?? "active";
2247
- if (missionState !== "active") return [];
2248
- const tailSize = opts.journalTailSize ?? 5;
2249
- const journalTail = goal.journal.slice(-tailSize).map((e) => {
2250
- const note = e.note ? ` \u2014 ${e.note.slice(0, 80)}` : "";
2251
- return ` #${e.iteration} [${e.status}] ${e.task}${note}`;
2252
- });
2253
- const text = [
2254
- "## ETERNAL AUTONOMY \u2014 active mission",
2255
- "",
2256
- "You are inside a long-running autonomous loop. The user is asleep",
2257
- "and is not available to confirm decisions. Each turn you receive a",
2258
- "directive describing one concrete sub-task that advances the mission.",
2259
- "",
2260
- `Mission: ${goal.goal}`,
2261
- `Iteration: #${goal.iterations}`,
2262
- journalTail.length > 0 ? `Recent journal (last ${journalTail.length}):
2263
- ${journalTail.join("\n")}` : "Recent journal: (none \u2014 this is the first iteration)",
2264
- "",
2265
- "### Loop control markers",
2266
- "Emit these on their own line in your final text \u2014 case-insensitive,",
2267
- "whitespace-tolerant, but they must occupy the entire line:",
2268
- "- `[continue]` \u2014 chain to the next internal step without returning.",
2269
- "- `[done]` \u2014 the current sub-task is finished; return to the engine.",
2270
- "- `[GOAL_COMPLETE]` \u2014 emit ONLY when the OVERALL mission is",
2271
- " verifiably done. Must be followed by a one-paragraph verification",
2272
- " recipe (artifact path, test command, or 10-second reproduction).",
2273
- " The engine halts on this marker \u2014 false positives waste real",
2274
- " human time. If unsure, emit `[done]` and let the next iteration",
2275
- " decide.",
2276
- "",
2277
- "### Operating principles",
2278
- "- YOLO is active. Do NOT ask for confirmation, do NOT propose",
2279
- " options. Pick the best path and execute it.",
2280
- "- Use tools freely; multiple calls per turn are normal and expected.",
2281
- "- When working on a todo, mark it `in_progress` via the todos tool",
2282
- " before tool work and `completed` (or `cancelled` with a reason)",
2283
- " when done. The loop reads todo state between iterations.",
2284
- "- If an approach fails twice in a row, pivot. Don't grind on the",
2285
- " same wall \u2014 try a different angle, file a cancel on the todo, or",
2286
- " surface the obstacle via `[done]` and let the next iteration",
2287
- " re-plan."
2288
- ].join("\n");
2289
- return [
2290
- {
2291
- type: "text",
2292
- text,
2293
- cache_control: { type: "ephemeral" }
2436
+ }
2437
+ recordIteration() {
2438
+ this.iterations++;
2439
+ if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
2440
+ void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
2441
+ }
2442
+ }
2443
+ recordToolCall() {
2444
+ this.toolCalls++;
2445
+ if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
2446
+ void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
2447
+ }
2448
+ }
2449
+ recordUsage(usage, costUsd = 0) {
2450
+ this.tokenInput += usage.input;
2451
+ this.tokenOutput += usage.output;
2452
+ this.costUsd += costUsd;
2453
+ const totalTokens = this.tokenInput + this.tokenOutput;
2454
+ if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
2455
+ void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
2456
+ }
2457
+ if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
2458
+ void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
2459
+ }
2460
+ }
2461
+ /**
2462
+ * Wall-clock budget check. Unlike other limits, timeout is treated as a
2463
+ * warning-only event it NEVER hard-stops the subagent. When the
2464
+ * elapsed time exceeds timeoutMs, emits `budget.threshold_reached` with
2465
+ * kind='timeout' so the Director can decide whether to extend or warn.
2466
+ * Call this from the iteration loop so a hung tool gets a chance to
2467
+ * negotiate more time before the coordinator's Promise.race kills it.
2468
+ */
2469
+ checkTimeout() {
2470
+ if (this.startTime === null || this.limits.timeoutMs === void 0) return;
2471
+ const elapsed = Date.now() - this.startTime;
2472
+ if (elapsed > this.limits.timeoutMs) {
2473
+ void this.checkLimit("timeout", elapsed, this.limits.timeoutMs);
2474
+ }
2475
+ }
2476
+ /** Returns true if a timeout has occurred without throwing. Useful for races. */
2477
+ isTimedOut() {
2478
+ if (this.startTime === null || this.limits.timeoutMs === void 0) return false;
2479
+ return Date.now() - this.startTime > this.limits.timeoutMs;
2480
+ }
2481
+ usage() {
2482
+ return {
2483
+ iterations: this.iterations,
2484
+ toolCalls: this.toolCalls,
2485
+ tokens: {
2486
+ input: this.tokenInput,
2487
+ output: this.tokenOutput,
2488
+ total: this.tokenInput + this.tokenOutput
2489
+ },
2490
+ costUsd: this.costUsd,
2491
+ elapsedMs: this.startTime === null ? 0 : Date.now() - this.startTime
2492
+ };
2493
+ }
2494
+ };
2495
+
2496
+ // src/coordination/agent-subagent-runner.ts
2497
+ function makeAgentSubagentRunner(opts) {
2498
+ const format = opts.formatTaskInput ?? defaultFormatTaskInput;
2499
+ return async (task, ctx) => {
2500
+ const factoryResult = await opts.factory(ctx.config);
2501
+ const { agent, events } = factoryResult;
2502
+ const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
2503
+ const aborter = new AbortController();
2504
+ ctx.budget._events = events;
2505
+ ctx.budget.onThreshold = ({ requestDecision }) => requestDecision();
2506
+ let budgetError = null;
2507
+ const onBudgetError = (err) => {
2508
+ if (err instanceof BudgetThresholdSignal) {
2509
+ err.decision.then((decision) => {
2510
+ if (decision === "stop") {
2511
+ budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
2512
+ aborter.abort();
2513
+ }
2514
+ }).catch(() => {
2515
+ budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
2516
+ aborter.abort();
2517
+ });
2518
+ return;
2294
2519
  }
2295
- ];
2520
+ aborter.abort();
2521
+ budgetError = err instanceof BudgetExceededError ? err : new BudgetExceededError(
2522
+ "tool_calls",
2523
+ 0,
2524
+ 0
2525
+ );
2526
+ if (budgetError !== err && err instanceof Error) {
2527
+ budgetError.message += ` (caused by: ${err.message})`;
2528
+ }
2529
+ };
2530
+ let lastToolFailed = null;
2531
+ const unsub = [];
2532
+ unsub.push(
2533
+ events.on("tool.executed", (e) => {
2534
+ try {
2535
+ ctx.budget.recordToolCall();
2536
+ } catch (eb) {
2537
+ onBudgetError(eb);
2538
+ }
2539
+ if (e.ok === false) {
2540
+ lastToolFailed = e.name;
2541
+ } else if (e.ok === true) {
2542
+ lastToolFailed = null;
2543
+ }
2544
+ }),
2545
+ events.on("provider.response", (e) => {
2546
+ try {
2547
+ ctx.budget.recordUsage(e.usage);
2548
+ } catch (e2) {
2549
+ void onBudgetError(e2);
2550
+ }
2551
+ }),
2552
+ events.on("iteration.started", () => {
2553
+ try {
2554
+ ctx.budget.recordIteration();
2555
+ } catch (e) {
2556
+ void onBudgetError(e);
2557
+ }
2558
+ const u = ctx.budget.usage();
2559
+ const since = u.iterations - lastSummaryAtIteration;
2560
+ if (since >= SUMMARY_INTERVAL) {
2561
+ lastSummaryAtIteration = u.iterations;
2562
+ events.emit("subagent.iteration_summary", {
2563
+ subagentId: ctx.subagentId,
2564
+ iteration: u.iterations,
2565
+ toolCalls: u.toolCalls,
2566
+ costUsd: u.costUsd,
2567
+ currentTool: currentToolName,
2568
+ partialText: streamingTextAcc.trim() || void 0
2569
+ });
2570
+ }
2571
+ }),
2572
+ // D3: cooperative timeout enforcement DURING a long tool call.
2573
+ // The iteration-loop checkTimeout() only fires between agent
2574
+ // iterations — a single `bash sleep 3600` call would otherwise
2575
+ // park inside one tool execution while the timeout silently
2576
+ // passes, relying solely on the coordinator's hard Promise.race
2577
+ // to interrupt. Tools that emit `tool.progress` (bash chunks,
2578
+ // fetch byte progress, spawn-stream stdout) give us a heartbeat
2579
+ // we can hang the check on. When the budget trips here:
2580
+ // 1. onBudgetError sets budgetError + aborter.abort()
2581
+ // 2. aborter signal propagates to agent.run → tool executor
2582
+ // 3. tool's own signal listener kills the child process
2583
+ // Cheap: O(1) per progress event, and the budget short-circuits
2584
+ // when timeoutMs is unset (most subagents have one set anyway).
2585
+ events.on("tool.progress", () => {
2586
+ try {
2587
+ ctx.budget.checkTimeout();
2588
+ } catch (e) {
2589
+ void onBudgetError(e);
2590
+ }
2591
+ })
2592
+ );
2593
+ let currentToolName;
2594
+ let streamingTextAcc = "";
2595
+ let lastSummaryAtIteration = 0;
2596
+ const SUMMARY_INTERVAL = 25;
2597
+ unsub.push(
2598
+ events.on("tool.started", (e) => {
2599
+ currentToolName = e.name;
2600
+ }),
2601
+ events.on("provider.text_delta", (e) => {
2602
+ streamingTextAcc = (streamingTextAcc + e.text).slice(-200);
2603
+ })
2604
+ );
2605
+ const onParentAbort = () => aborter.abort();
2606
+ ctx.signal.addEventListener("abort", onParentAbort);
2607
+ let result;
2608
+ try {
2609
+ result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
2610
+ } finally {
2611
+ detachFleet?.();
2612
+ ctx.signal.removeEventListener("abort", onParentAbort);
2613
+ for (const u of unsub) u();
2614
+ if (factoryResult.dispose) {
2615
+ try {
2616
+ await factoryResult.dispose();
2617
+ } catch {
2618
+ }
2619
+ }
2620
+ }
2621
+ if (budgetError) {
2622
+ if ("decision" in budgetError) {
2623
+ const decision = await budgetError.decision;
2624
+ if (decision === "stop") {
2625
+ budgetError = new BudgetExceededError(
2626
+ budgetError.kind,
2627
+ budgetError.limit,
2628
+ budgetError.used
2629
+ );
2630
+ } else {
2631
+ budgetError = null;
2632
+ }
2633
+ }
2634
+ if (budgetError) throw budgetError;
2635
+ }
2636
+ if (result.status === "failed") {
2637
+ throw result.error instanceof Error ? result.error : new Error(String(result.error ?? "agent failed"));
2638
+ }
2639
+ if (result.status === "aborted") {
2640
+ throw new Error("agent aborted");
2641
+ }
2642
+ if (result.status === "max_iterations") {
2643
+ throw new Error("agent exhausted iteration limit");
2644
+ }
2645
+ const usage = ctx.budget.usage();
2646
+ const finalText = (result.finalText ?? "").trim();
2647
+ if (finalText.length === 0 && usage.toolCalls === 0) {
2648
+ throw new Error("empty response");
2649
+ }
2650
+ if (finalText.length === 0 && lastToolFailed !== null) {
2651
+ throw new Error(`tool failed: ${lastToolFailed}`);
2652
+ }
2653
+ return {
2654
+ result: result.finalText,
2655
+ iterations: result.iterations,
2656
+ toolCalls: usage.toolCalls
2657
+ };
2296
2658
  };
2297
2659
  }
2298
-
2299
- // src/execution/goal-preamble.ts
2300
- function buildGoalPreamble(goal) {
2301
- return [
2302
- "[GOAL \u2014 LOCKED IN. You will work on this until it is verifiably done.",
2303
- "The user granted you full autonomy. Read these constraints once, then act.",
2304
- "",
2305
- "YOUR GOAL:",
2306
- "---",
2307
- goal,
2308
- "---",
2309
- "",
2310
- "AUTHORITY YOU HAVE:",
2311
- "- Spawn as many subagents as the work needs (delegate / spawn_subagent).",
2312
- " Parallel + recursive fan-out are both fine. There is no spawn budget.",
2313
- "- Use any provider/model per subagent \u2014 pick the right tool for each",
2314
- " piece of work. Heavy reasoning model for planning, fast model for",
2315
- " batch work, specialist model for domain code.",
2316
- "- Run unlimited tool calls and iterations. There is NO hidden budget.",
2317
- " The Agent loop auto-extends every 100 iterations forever.",
2318
- "- Retry failed tools with different inputs, alternative paths, fresh",
2319
- " subagents. Switch providers mid-run if one is rate-limited.",
2320
- "- Re-plan freely when an approach hits a dead end. You are not obliged",
2321
- " to stick with the first plan you proposed.",
2322
- "",
2323
- 'WHAT "DONE" MEANS \u2014 non-negotiable:',
2324
- "- You can name a concrete artifact (a passing test, a written file at",
2325
- " a specific path, a fixed bug verified by re-running the failing case,",
2326
- " a clean grep that previously had matches).",
2327
- "- You can tell the user HOW to verify it themselves in 10 seconds.",
2328
- '- You have NOT hedged. None of: "looks like it should work", "I',
2329
- ' believe this fixes it", "the changes appear correct".',
2330
- "",
2331
- "WHAT IS NOT DONE \u2014 never report any of these as completion:",
2332
- "- An error message you didn't recover from.",
2333
- '- An empty result, a 0-line file, a "no matches found" you accepted',
2334
- " without questioning the search.",
2335
- '- "Should I continue?" / "Want me to also...?" / "Let me know if you',
2336
- ' want X." Those are hedges. The user already told you to finish the',
2337
- " goal \u2014 just do it.",
2338
- "- Partial progress dressed up as success. Fixed 3 of 5 bugs = 60%",
2339
- " done, not done.",
2340
- "- A subagent's failed/timeout/stopped TaskResult that you didn't",
2341
- " respond to with a fresh attempt (different role, different model,",
2342
- " tighter prompt).",
2343
- "",
2344
- "PERSISTENCE PROTOCOL:",
2345
- "- If blocked, try at least 3 different angles before reporting the",
2346
- " problem to the user. Different tool inputs, different subagent",
2347
- " roles, different providers, different decomposition of the task.",
2348
- "- If a tool fails, read its error, alter the input, try again. Do",
2349
- " not just report the failure back.",
2350
- "- If a subagent returns useless output, respawn with a tighter prompt",
2351
- ' or a different role. Do not accept "I could not determine\u2026" as the',
2352
- " final answer.",
2353
- "- Use `ask_subagent` for one-shot questions when you don't need a",
2354
- " full delegated task.",
2355
- "",
2356
- "REPORTING:",
2357
- "- Stream short progress notes between major actions so the user can",
2358
- " monitor. Do not go silent for 50 tool calls then dump a wall of",
2359
- " text \u2014 but also do not narrate every tool call.",
2360
- "- Use the shared scratchpad (if available) to leave breadcrumbs",
2361
- " subagents can read.",
2362
- "- Final response must include: (a) what was accomplished, (b) how",
2363
- " to verify, (c) any caveats (residual TODOs, things the user",
2364
- " should know about).",
2365
- "",
2366
- "BEGIN.]"
2367
- ].join("\n");
2660
+ function defaultFormatTaskInput(task) {
2661
+ return task.description ?? "";
2368
2662
  }
2369
2663
 
2370
2664
  // src/types/provider.ts
@@ -2438,6 +2732,1019 @@ function providerStatusToCode(status, type) {
2438
2732
  if (status >= 500) return "PROVIDER_SERVER_ERROR";
2439
2733
  return "PROVIDER_INVALID_REQUEST";
2440
2734
  }
2735
+ var FLEET_ROSTER_BUDGETS = {
2736
+ "audit-log": { timeoutMs: 8 * 60 * 1e3, maxIterations: 150, maxToolCalls: 500 },
2737
+ "bug-hunter": { timeoutMs: 15 * 60 * 1e3, maxIterations: 200, maxToolCalls: 600 },
2738
+ "refactor-planner": { timeoutMs: 12 * 60 * 1e3, maxIterations: 180, maxToolCalls: 550 },
2739
+ "security-scanner": { timeoutMs: 15 * 60 * 1e3, maxIterations: 200, maxToolCalls: 600 }
2740
+ };
2741
+ function applyRosterBudget(cfg) {
2742
+ const defaultBudget = FLEET_ROSTER_BUDGETS[cfg.role ?? ""];
2743
+ if (!defaultBudget) return cfg;
2744
+ return {
2745
+ ...cfg,
2746
+ timeoutMs: cfg.timeoutMs ?? defaultBudget.timeoutMs,
2747
+ maxIterations: cfg.maxIterations ?? defaultBudget.maxIterations,
2748
+ maxToolCalls: cfg.maxToolCalls ?? defaultBudget.maxToolCalls,
2749
+ maxTokens: cfg.maxTokens ?? defaultBudget.maxTokens,
2750
+ maxCostUsd: cfg.maxCostUsd ?? defaultBudget.maxCostUsd
2751
+ };
2752
+ }
2753
+
2754
+ // src/coordination/multi-agent-coordinator.ts
2755
+ var DefaultMultiAgentCoordinator = class extends EventEmitter {
2756
+ coordinatorId;
2757
+ config;
2758
+ runner;
2759
+ subagents = /* @__PURE__ */ new Map();
2760
+ pendingTasks = [];
2761
+ completedResults = [];
2762
+ totalIterations = 0;
2763
+ inFlight = 0;
2764
+ /**
2765
+ * Subagents currently being stopped. Set on entry to `stop()`, cleared
2766
+ * once `recordCompletion` lands the terminal TaskResult. Used by
2767
+ * `runDispatched` and `findIdleSubagent` to refuse mid-flight dispatch
2768
+ * to a subagent the caller has already asked to terminate — closes the
2769
+ * assign+terminate race where a fresh task could land on a worker that
2770
+ * was about to be killed.
2771
+ */
2772
+ terminating = /* @__PURE__ */ new Set();
2773
+ constructor(config, options = {}) {
2774
+ super();
2775
+ this.coordinatorId = config.coordinatorId;
2776
+ this.config = config;
2777
+ this.runner = options.runner;
2778
+ }
2779
+ /**
2780
+ * Replace the runner after construction. Used when the runner depends
2781
+ * on infrastructure (e.g. FleetBus) that isn't available until after
2782
+ * the coordinator's owning Director is built.
2783
+ */
2784
+ setRunner(runner) {
2785
+ this.runner = runner;
2786
+ }
2787
+ /**
2788
+ * Change the in-flight dispatch ceiling at runtime. Lowering does NOT
2789
+ * preempt running tasks — already-dispatched subagents finish their
2790
+ * current task; only future dispatches respect the new cap. Raising
2791
+ * immediately tries to fill the freed slots from the pending queue.
2792
+ */
2793
+ setMaxConcurrent(n) {
2794
+ if (!Number.isFinite(n) || n < 1) {
2795
+ throw new Error(`maxConcurrent must be a finite integer >= 1, got ${n}`);
2796
+ }
2797
+ this.config.maxConcurrent = Math.floor(n);
2798
+ this.tryDispatchNext();
2799
+ }
2800
+ async spawn(subagent) {
2801
+ const id = subagent.id || randomUUID();
2802
+ if (this.subagents.has(id)) {
2803
+ throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
2804
+ }
2805
+ const context = {
2806
+ subagentId: id,
2807
+ tasks: [],
2808
+ // Wired later by the caller via setSubagentBridge() once the
2809
+ // bidirectional bridge is created. Readers must null-check / use
2810
+ // hasParentBridge() — the type now reflects this.
2811
+ parentBridge: null,
2812
+ doneCondition: this.config.doneCondition,
2813
+ maxConcurrent: this.config.maxConcurrent ?? 4
2814
+ };
2815
+ this.subagents.set(id, {
2816
+ config: { ...subagent, id },
2817
+ context,
2818
+ status: "idle",
2819
+ abortController: new AbortController()
2820
+ });
2821
+ this.emit("subagent.started", { subagent: { ...subagent, id } });
2822
+ return { subagentId: id, agentId: id };
2823
+ }
2824
+ async assign(task) {
2825
+ this.pendingTasks.push(task);
2826
+ this.tryDispatchNext();
2827
+ }
2828
+ async delegate(to, msg) {
2829
+ const subagent = this.subagents.get(to);
2830
+ if (!subagent) throw new Error(`Subagent "${to}" not found`);
2831
+ if (!subagent.context.parentBridge) {
2832
+ throw new Error(`Subagent "${to}" has no parentBridge \u2014 call setSubagentBridge() first`);
2833
+ }
2834
+ await subagent.context.parentBridge.send(msg);
2835
+ }
2836
+ /**
2837
+ * Wire up the communication bridge for a subagent. Call after spawn() once
2838
+ * the caller has created the bidirectional connection.
2839
+ */
2840
+ setSubagentBridge(subagentId, bridge) {
2841
+ const subagent = this.subagents.get(subagentId);
2842
+ if (!subagent) throw new Error(`Subagent "${subagentId}" not found`);
2843
+ subagent.context.parentBridge = bridge;
2844
+ }
2845
+ async stop(subagentId) {
2846
+ const subagent = this.subagents.get(subagentId);
2847
+ if (!subagent) return;
2848
+ this.terminating.add(subagentId);
2849
+ subagent.abortController.abort();
2850
+ subagent.status = "stopped";
2851
+ subagent.currentTask = void 0;
2852
+ subagent.context.parentBridge = null;
2853
+ this.emit("subagent.stopped", { subagentId, reason: "stopped by coordinator" });
2854
+ }
2855
+ async stopAll() {
2856
+ this.drainPendingAsAborted("Coordinator stopAll() drained the pending queue");
2857
+ await Promise.allSettled([...this.subagents.keys()].map((id) => this.stop(id)));
2858
+ }
2859
+ getStatus() {
2860
+ return {
2861
+ coordinatorId: this.coordinatorId,
2862
+ subagents: Array.from(this.subagents.entries()).map(([id, s]) => ({
2863
+ id,
2864
+ name: s.config.name,
2865
+ status: s.status,
2866
+ currentTask: s.currentTask
2867
+ })),
2868
+ pendingTasks: this.pendingTasks.length,
2869
+ completedTasks: this.completedResults.length,
2870
+ totalIterations: this.totalIterations,
2871
+ done: this.isDone()
2872
+ };
2873
+ }
2874
+ /** Expose snapshot of completed results — useful for callers awaiting all done. */
2875
+ results() {
2876
+ return this.completedResults;
2877
+ }
2878
+ /**
2879
+ * Wait for one or more tasks to complete and return their results.
2880
+ * If a task is already done when called, returns immediately.
2881
+ * Resolves to an array in the same order as `taskIds`.
2882
+ */
2883
+ async awaitTasks(taskIds) {
2884
+ return Promise.all(
2885
+ taskIds.map((id) => {
2886
+ const cached = this.completedResults.find((r) => r.taskId === id);
2887
+ if (cached) return cached;
2888
+ return new Promise((resolve, reject) => {
2889
+ const timeout = setTimeout(() => {
2890
+ this.off("task.completed", handler);
2891
+ reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
2892
+ }, this.config.timeoutMs ?? 3e5);
2893
+ const handler = ({ result }) => {
2894
+ if (result.taskId === id) {
2895
+ clearTimeout(timeout);
2896
+ this.off("task.completed", handler);
2897
+ resolve(result);
2898
+ }
2899
+ };
2900
+ this.on("task.completed", handler);
2901
+ });
2902
+ })
2903
+ );
2904
+ }
2905
+ /**
2906
+ * Manual completion — for callers that drive subagents without a runner
2907
+ * (e.g. external orchestrators). When a runner is configured the coordinator
2908
+ * calls this itself.
2909
+ */
2910
+ completeTask(result) {
2911
+ this.recordCompletion(result);
2912
+ }
2913
+ // --- internal dispatching ---------------------------------------------
2914
+ tryDispatchNext() {
2915
+ while (this.canDispatch()) {
2916
+ const dispatchable = this.takeNextDispatchableTask();
2917
+ if (!dispatchable) {
2918
+ if (this.pendingTasks.length > 0 && !this.hasLiveSubagent()) {
2919
+ this.drainPendingAsAborted(
2920
+ "No live subagent available \u2014 all stopped or mid-termination"
2921
+ );
2922
+ }
2923
+ return;
2924
+ }
2925
+ const { subagentId, task } = dispatchable;
2926
+ this.runDispatched(subagentId, task).catch((err) => {
2927
+ this.recordCompletion({
2928
+ subagentId,
2929
+ taskId: task.id,
2930
+ status: "failed",
2931
+ error: classifySubagentError(err),
2932
+ iterations: 0,
2933
+ toolCalls: 0,
2934
+ durationMs: 0
2935
+ });
2936
+ });
2937
+ }
2938
+ }
2939
+ canDispatch() {
2940
+ const max = this.config.maxConcurrent ?? 4;
2941
+ return this.inFlight < max && this.pendingTasks.length > 0;
2942
+ }
2943
+ takeNextDispatchableTask() {
2944
+ for (let i = 0; i < this.pendingTasks.length; i++) {
2945
+ const task = this.pendingTasks[i];
2946
+ const subagentId = task.subagentId ? this.isIdleSubagent(task.subagentId) ? task.subagentId : null : this.findIdleSubagent();
2947
+ if (!subagentId) continue;
2948
+ this.pendingTasks.splice(i, 1);
2949
+ return { subagentId, task };
2950
+ }
2951
+ return null;
2952
+ }
2953
+ findIdleSubagent() {
2954
+ for (const [id, s] of this.subagents) {
2955
+ if (s.status === "idle" && !this.terminating.has(id)) return id;
2956
+ }
2957
+ return null;
2958
+ }
2959
+ isIdleSubagent(id) {
2960
+ const subagent = this.subagents.get(id);
2961
+ return !!subagent && subagent.status === "idle" && !this.terminating.has(id);
2962
+ }
2963
+ /**
2964
+ * Returns true iff at least one spawned subagent could still
2965
+ * process a task. A "live" subagent is one that is not stopped
2966
+ * AND not mid-termination — `running` workers count because they
2967
+ * will eventually finish and become idle.
2968
+ *
2969
+ * When no subagent has ever been spawned, returns `true` so a
2970
+ * pre-spawn `assign()` simply queues (legacy behaviour). The
2971
+ * dead-end detection only fires after `stop()` has retired every
2972
+ * spawned worker.
2973
+ *
2974
+ * Used by `tryDispatchNext` to detect a dead-end pending queue.
2975
+ */
2976
+ hasLiveSubagent() {
2977
+ if (this.subagents.size === 0) return true;
2978
+ for (const [id, s] of this.subagents) {
2979
+ if (s.status !== "stopped" && !this.terminating.has(id)) return true;
2980
+ }
2981
+ return false;
2982
+ }
2983
+ /**
2984
+ * Drain every pending task with a synthetic `aborted_by_parent`
2985
+ * completion event. Same shape as the `stopAll()` drain — we go
2986
+ * around `recordCompletion` because pending tasks were never
2987
+ * counted in `inFlight` and routing them through would trip the
2988
+ * underflow guard on every task after the first.
2989
+ */
2990
+ drainPendingAsAborted(message) {
2991
+ const dropped = this.pendingTasks.splice(0, this.pendingTasks.length);
2992
+ for (const t of dropped) {
2993
+ const synthetic = {
2994
+ subagentId: t.subagentId ?? "unassigned",
2995
+ taskId: t.id,
2996
+ status: "stopped",
2997
+ error: {
2998
+ kind: "aborted_by_parent",
2999
+ message,
3000
+ retryable: false
3001
+ },
3002
+ iterations: 0,
3003
+ toolCalls: 0,
3004
+ durationMs: 0
3005
+ };
3006
+ this.completedResults.push(synthetic);
3007
+ this.emit("task.completed", { task: t, result: synthetic });
3008
+ }
3009
+ }
3010
+ async runDispatched(subagentId, task) {
3011
+ const subagent = this.subagents.get(subagentId);
3012
+ if (!subagent) return;
3013
+ if (this.terminating.has(subagentId) || subagent.status === "stopped") {
3014
+ this.recordCompletion({
3015
+ subagentId,
3016
+ taskId: task.id,
3017
+ status: "stopped",
3018
+ error: {
3019
+ kind: "aborted_by_parent",
3020
+ message: "Subagent was terminated before task could start",
3021
+ retryable: false
3022
+ },
3023
+ iterations: 0,
3024
+ toolCalls: 0,
3025
+ durationMs: 0
3026
+ });
3027
+ return;
3028
+ }
3029
+ subagent.status = "running";
3030
+ subagent.currentTask = task.id;
3031
+ task.subagentId = subagentId;
3032
+ subagent.context.tasks.push(task);
3033
+ this.emit("task.assigned", { task, subagentId });
3034
+ const configWithRosterDefaults = applyRosterBudget(subagent.config);
3035
+ const budget = new SubagentBudget({
3036
+ maxIterations: configWithRosterDefaults.maxIterations ?? this.config.defaultBudget?.maxIterations,
3037
+ maxToolCalls: task.maxToolCalls ?? configWithRosterDefaults.maxToolCalls ?? this.config.defaultBudget?.maxToolCalls,
3038
+ maxTokens: configWithRosterDefaults.maxTokens ?? this.config.defaultBudget?.maxTokens,
3039
+ maxCostUsd: configWithRosterDefaults.maxCostUsd ?? this.config.defaultBudget?.maxCostUsd,
3040
+ timeoutMs: task.timeoutMs ?? configWithRosterDefaults.timeoutMs ?? this.config.defaultBudget?.timeoutMs
3041
+ });
3042
+ subagent.activeBudget = budget;
3043
+ if (!this.runner) {
3044
+ return;
3045
+ }
3046
+ this.inFlight++;
3047
+ const startTime = Date.now();
3048
+ const runCtx = {
3049
+ subagentId,
3050
+ config: subagent.config,
3051
+ budget,
3052
+ signal: subagent.abortController.signal,
3053
+ bridge: subagent.context.parentBridge || null
3054
+ };
3055
+ let result;
3056
+ budget.start();
3057
+ try {
3058
+ const outcome = await this.executeWithTimeout(this.runner, task, runCtx, budget);
3059
+ result = {
3060
+ subagentId,
3061
+ taskId: task.id,
3062
+ status: "success",
3063
+ result: outcome.result,
3064
+ iterations: outcome.iterations,
3065
+ toolCalls: outcome.toolCalls,
3066
+ durationMs: Date.now() - startTime
3067
+ };
3068
+ } catch (err) {
3069
+ const status = err instanceof BudgetExceededError && err.kind === "timeout" ? "timeout" : subagent.abortController.signal.aborted ? "stopped" : "failed";
3070
+ const usage = budget.usage();
3071
+ result = {
3072
+ subagentId,
3073
+ taskId: task.id,
3074
+ status,
3075
+ error: classifySubagentError(err, {
3076
+ parentAborted: subagent.abortController.signal.aborted
3077
+ }),
3078
+ iterations: usage.iterations,
3079
+ toolCalls: usage.toolCalls,
3080
+ durationMs: Date.now() - startTime
3081
+ };
3082
+ }
3083
+ this.recordCompletion(result);
3084
+ }
3085
+ async executeWithTimeout(runner, task, ctx, budget) {
3086
+ const initialTimeoutMs = budget.limits.timeoutMs;
3087
+ if (initialTimeoutMs === void 0) return runner(task, ctx);
3088
+ const start = Date.now();
3089
+ let timer = null;
3090
+ const timeoutPromise = new Promise((_, reject) => {
3091
+ const armFor = (ms) => {
3092
+ if (timer) clearTimeout(timer);
3093
+ timer = setTimeout(async () => {
3094
+ const elapsed = Date.now() - start;
3095
+ const limit = budget.limits.timeoutMs ?? initialTimeoutMs;
3096
+ if (!budget.onThreshold) {
3097
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
3098
+ reject(new BudgetExceededError("timeout", limit, elapsed));
3099
+ return;
3100
+ }
3101
+ try {
3102
+ const result = budget.onThreshold({
3103
+ kind: "timeout",
3104
+ used: elapsed,
3105
+ limit,
3106
+ requestDecision: () => new Promise((resolveDecision) => {
3107
+ budget._events?.emit("budget.threshold_reached", {
3108
+ kind: "timeout",
3109
+ used: elapsed,
3110
+ limit,
3111
+ timeoutMs: 3e4,
3112
+ extend: (extra) => resolveDecision({ extend: extra }),
3113
+ deny: () => resolveDecision("stop")
3114
+ });
3115
+ })
3116
+ });
3117
+ const decision = typeof result === "string" ? result : await result;
3118
+ if (decision === "continue") {
3119
+ armFor(Math.max(1e3, limit));
3120
+ return;
3121
+ }
3122
+ if (decision === "throw" || decision === "stop") {
3123
+ armFor(Math.max(1e3, limit));
3124
+ return;
3125
+ }
3126
+ if (decision.extend.timeoutMs !== void 0) {
3127
+ budget.limits.timeoutMs = decision.extend.timeoutMs;
3128
+ const newLimit = decision.extend.timeoutMs;
3129
+ const remaining = Math.max(1e3, newLimit - elapsed);
3130
+ armFor(remaining);
3131
+ return;
3132
+ }
3133
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
3134
+ reject(new BudgetExceededError("timeout", limit, elapsed));
3135
+ } catch (err) {
3136
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
3137
+ reject(
3138
+ err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
3139
+ );
3140
+ }
3141
+ }, ms);
3142
+ };
3143
+ armFor(initialTimeoutMs);
3144
+ });
3145
+ try {
3146
+ return await Promise.race([runner(task, ctx), timeoutPromise]);
3147
+ } finally {
3148
+ if (timer) clearTimeout(timer);
3149
+ }
3150
+ }
3151
+ recordCompletion(result) {
3152
+ this.completedResults.push(result);
3153
+ this.totalIterations += result.iterations;
3154
+ if (this.inFlight > 0) {
3155
+ this.inFlight--;
3156
+ } else if (this.runner) {
3157
+ this.emit("warning", {
3158
+ type: "inFlight_underflow",
3159
+ taskId: result.taskId,
3160
+ subagentId: result.subagentId
3161
+ });
3162
+ return;
3163
+ }
3164
+ const subagent = this.subagents.get(result.subagentId);
3165
+ if (subagent && subagent.status !== "stopped") {
3166
+ result.status === "failed" || result.status === "timeout";
3167
+ subagent.status = "idle";
3168
+ subagent.currentTask = void 0;
3169
+ if (subagent.abortController.signal.aborted) {
3170
+ subagent.abortController = new AbortController();
3171
+ }
3172
+ }
3173
+ this.terminating.delete(result.subagentId);
3174
+ this.emit("task.completed", {
3175
+ task: subagent?.context.tasks.find((t) => t.id === result.taskId) ?? { id: result.taskId },
3176
+ result
3177
+ });
3178
+ this.tryDispatchNext();
3179
+ if (this.isDone()) {
3180
+ this.emit("done", {
3181
+ results: this.completedResults,
3182
+ totalIterations: this.totalIterations
3183
+ });
3184
+ }
3185
+ }
3186
+ isDone() {
3187
+ if (this.config.doneCondition.type === "all_tasks_done") {
3188
+ return this.pendingTasks.length === 0 && this.inFlight === 0;
3189
+ }
3190
+ if (this.config.doneCondition.maxIterations !== void 0 && this.totalIterations >= this.config.doneCondition.maxIterations) {
3191
+ return true;
3192
+ }
3193
+ return false;
3194
+ }
3195
+ };
3196
+ function classifySubagentError(err, hints = {}) {
3197
+ const cause = err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : void 0;
3198
+ if (err instanceof ProviderError) {
3199
+ const baseMessage2 = err.describe();
3200
+ return providerErrorToSubagentError(err, baseMessage2, cause);
3201
+ }
3202
+ const baseMessage = err instanceof Error ? err.message : String(err);
3203
+ if (err instanceof BudgetExceededError) {
3204
+ const map = {
3205
+ iterations: "budget_iterations",
3206
+ tool_calls: "budget_tool_calls",
3207
+ tokens: "budget_tokens",
3208
+ cost: "budget_cost",
3209
+ timeout: "budget_timeout"
3210
+ };
3211
+ return {
3212
+ kind: map[err.kind],
3213
+ message: baseMessage,
3214
+ // Budgets are user-configured ceilings, not transient failures —
3215
+ // retrying with the same budget will hit the same ceiling. The
3216
+ // orchestrator must raise the budget or narrow the task first.
3217
+ retryable: false,
3218
+ cause
3219
+ };
3220
+ }
3221
+ if (hints.parentAborted) {
3222
+ return {
3223
+ kind: "aborted_by_parent",
3224
+ message: baseMessage,
3225
+ retryable: false,
3226
+ cause
3227
+ };
3228
+ }
3229
+ const lower = baseMessage.toLowerCase();
3230
+ if (/agent aborted$/i.test(baseMessage)) {
3231
+ return {
3232
+ kind: "aborted_by_parent",
3233
+ message: baseMessage,
3234
+ retryable: false,
3235
+ cause
3236
+ };
3237
+ }
3238
+ if (/agent exhausted iteration limit$/i.test(baseMessage)) {
3239
+ return { kind: "budget_iterations", message: baseMessage, retryable: false, cause };
3240
+ }
3241
+ if (/empty response$/i.test(baseMessage)) {
3242
+ return { kind: "empty_response", message: baseMessage, retryable: false, cause };
3243
+ }
3244
+ if (/^tool failed: /i.test(baseMessage)) {
3245
+ return { kind: "tool_failed", message: baseMessage, retryable: false, cause };
3246
+ }
3247
+ if (lower.includes("bridge transport") || /bridge.*(closed|disconnect)/i.test(baseMessage)) {
3248
+ return { kind: "bridge_failed", message: baseMessage, retryable: false, cause };
3249
+ }
3250
+ if (/context length|max.*tokens?.*exceeded|prompt is too long/i.test(baseMessage)) {
3251
+ return { kind: "context_overflow", message: baseMessage, retryable: false, cause };
3252
+ }
3253
+ return {
3254
+ kind: "unknown",
3255
+ message: baseMessage,
3256
+ retryable: false,
3257
+ cause
3258
+ };
3259
+ }
3260
+ function providerErrorToSubagentError(err, message, cause) {
3261
+ const status = err.status;
3262
+ if (status === 429 || err.body?.type === "rate_limit_error") {
3263
+ return {
3264
+ kind: "provider_rate_limit",
3265
+ message,
3266
+ retryable: true,
3267
+ // Conservative default: 5s. Provider-specific code can override
3268
+ // by emitting an error whose body carries an explicit hint.
3269
+ backoffMs: 5e3,
3270
+ cause
3271
+ };
3272
+ }
3273
+ if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
3274
+ return { kind: "provider_auth", message, retryable: false, cause };
3275
+ }
3276
+ if (status === 408 || status === 0) {
3277
+ return { kind: "provider_timeout", message, retryable: true, cause };
3278
+ }
3279
+ if (status >= 500 && status < 600) {
3280
+ return {
3281
+ kind: "provider_5xx",
3282
+ message,
3283
+ retryable: true,
3284
+ backoffMs: 3e3,
3285
+ cause
3286
+ };
3287
+ }
3288
+ return { kind: "unknown", message, retryable: err.retryable, cause };
3289
+ }
3290
+
3291
+ // src/execution/parallel-eternal-engine.ts
3292
+ function sleep2(ms) {
3293
+ return new Promise((resolve) => setTimeout(resolve, ms));
3294
+ }
3295
+ var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
3296
+ var ParallelEternalEngine = class {
3297
+ constructor(opts) {
3298
+ this.opts = opts;
3299
+ this.goalPath = goalFilePath(opts.projectRoot);
3300
+ this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
3301
+ this.timeoutMs = opts.iterationTimeoutMs ?? 3e5;
3302
+ this.agentFactory = opts.subagentFactory ?? (async (config) => ({
3303
+ agent: this.opts.agent,
3304
+ events: this.opts.agent.events
3305
+ }));
3306
+ }
3307
+ opts;
3308
+ state = "idle";
3309
+ stopRequested = false;
3310
+ iterationsSinceCompact = 0;
3311
+ iterations = 0;
3312
+ consecutiveFailures = 0;
3313
+ goalPath;
3314
+ slots;
3315
+ timeoutMs;
3316
+ coordinator = null;
3317
+ agentFactory;
3318
+ get currentState() {
3319
+ return this.state;
3320
+ }
3321
+ stop() {
3322
+ this.stopRequested = true;
3323
+ void this.persistState("stopped").catch(() => {
3324
+ });
3325
+ this.state = "stopped";
3326
+ }
3327
+ async prime() {
3328
+ this.stopRequested = false;
3329
+ this.state = "running";
3330
+ await this.persistState("running");
3331
+ }
3332
+ async run() {
3333
+ this.state = "running";
3334
+ await this.persistState("running");
3335
+ const config = {
3336
+ coordinatorId: `parallel-${randomUUID().slice(0, 8)}`,
3337
+ maxConcurrent: this.slots,
3338
+ doneCondition: { type: "all_tasks_done" }
3339
+ };
3340
+ this.coordinator = new DefaultMultiAgentCoordinator(config);
3341
+ const runner = makeAgentSubagentRunner({ factory: this.agentFactory });
3342
+ this.coordinator.setRunner?.(runner);
3343
+ try {
3344
+ while (!this.stopRequested) {
3345
+ try {
3346
+ await this.runOneIteration();
3347
+ } catch (err) {
3348
+ this.consecutiveFailures++;
3349
+ this.opts.onError?.(err instanceof Error ? err : new Error(String(err)), this.consecutiveFailures);
3350
+ await this.appendFailure("engine error", err instanceof Error ? err.message : String(err));
3351
+ }
3352
+ if (this.stopRequested) break;
3353
+ await sleep2(2e3);
3354
+ }
3355
+ } finally {
3356
+ this.state = "stopped";
3357
+ await this.persistState("stopped").catch(() => {
3358
+ });
3359
+ }
3360
+ }
3361
+ /**
3362
+ * Execute one tick: decompose → fan-out → aggregate → compact.
3363
+ * Called by the REPL in its main loop (REPL drives, engine is stateless per tick).
3364
+ */
3365
+ async runOneIteration() {
3366
+ this.iterations++;
3367
+ const goal = await loadGoal(this.goalPath);
3368
+ if (!goal) {
3369
+ this.stopRequested = true;
3370
+ return false;
3371
+ }
3372
+ if (goal.goalState !== "active") {
3373
+ this.stopRequested = true;
3374
+ return false;
3375
+ }
3376
+ if (!this.coordinator) {
3377
+ const config = {
3378
+ coordinatorId: `parallel-${randomUUID().slice(0, 8)}`,
3379
+ maxConcurrent: this.slots,
3380
+ doneCondition: { type: "all_tasks_done" }
3381
+ };
3382
+ this.coordinator = new DefaultMultiAgentCoordinator(config);
3383
+ const runner = makeAgentSubagentRunner({ factory: this.agentFactory });
3384
+ this.coordinator.setRunner?.(runner);
3385
+ }
3386
+ const tasks = await this.decomposeGoal(goal);
3387
+ if (!tasks || tasks.length === 0) {
3388
+ await sleep2(5e3);
3389
+ return false;
3390
+ }
3391
+ const fanOut = await this.fanOut(goal, tasks);
3392
+ this.iterationsSinceCompact++;
3393
+ const successCount = fanOut.results.filter((r) => r.status === "success").length;
3394
+ const status = fanOut.goalComplete ? "success" : fanOut.allSuccessful ? "success" : "failure";
3395
+ const note = [
3396
+ `${successCount}/${fanOut.results.length} subagents succeeded`,
3397
+ fanOut.goalComplete ? "[GOAL_COMPLETE]" : "",
3398
+ fanOut.partialOutput ? `Output: ${fanOut.partialOutput.slice(0, 120)}` : ""
3399
+ ].filter(Boolean).join(" | ");
3400
+ await this.appendIterationEntry({
3401
+ source: "parallel",
3402
+ task: `parallel:${tasks.length} slots \u2014 ${tasks.slice(0, 3).join(", ")}${tasks.length > 3 ? "..." : ""}`,
3403
+ status,
3404
+ note
3405
+ });
3406
+ if (fanOut.goalComplete) {
3407
+ this.stopRequested = true;
3408
+ return true;
3409
+ }
3410
+ await this.maybeCompact();
3411
+ return fanOut.allSuccessful;
3412
+ }
3413
+ // -------------------------------------------------------------------------
3414
+ // Fan-out
3415
+ // -------------------------------------------------------------------------
3416
+ async fanOut(goal, tasks) {
3417
+ const coordinator = this.coordinator;
3418
+ const slotCount = Math.min(this.slots, tasks.length);
3419
+ const recentJournal = goal.journal.slice(-5).map((e) => ` #${e.iteration} [${e.status}] ${e.task}${e.note ? ` \u2014 ${e.note.slice(0, 80)}` : ""}`).join("\n");
3420
+ const directivePreamble = [
3421
+ "\u2550\u2550\u2550 ETERNAL AUTONOMY \u2014 parallel task slot \u2550\u2550\u2550",
3422
+ "",
3423
+ `Mission: ${goal.goal}`,
3424
+ `Total parallel slots: ${slotCount}`,
3425
+ "",
3426
+ recentJournal ? `Recent journal (last 5):
3427
+ ${recentJournal}` : "No prior iterations.",
3428
+ "",
3429
+ "\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
3430
+ "\u2022 Execute the assigned task end-to-end using multiple tool calls.",
3431
+ "\u2022 Emit `[done]` on its own line when the task is complete.",
3432
+ "\u2022 Do not ask for confirmation \u2014 YOLO is active.",
3433
+ "\u2022 If the overall Mission is accomplished, emit `[GOAL_COMPLETE]` followed by a verification recipe.",
3434
+ "\u2022 Keep output concise \u2014 summarize findings, do not transcribe files."
3435
+ ].join("\n");
3436
+ const taskIds = [];
3437
+ const subagentIds = [];
3438
+ const spawnPromises = [];
3439
+ for (let i = 0; i < slotCount; i++) {
3440
+ const task = tasks[i];
3441
+ const subagentId = `parallel-${this.iterations}-${i}`;
3442
+ const taskId = randomUUID();
3443
+ const spec = {
3444
+ id: taskId,
3445
+ description: `${directivePreamble}
3446
+
3447
+ \u2500\u2500 SLOT ${i + 1}/${slotCount} \u2500\u2500
3448
+ Task: ${task}
3449
+ `,
3450
+ subagentId
3451
+ };
3452
+ spawnPromises.push((async () => {
3453
+ try {
3454
+ await coordinator.spawn({
3455
+ id: subagentId,
3456
+ name: `slot-${subagentId.slice(-6)}`,
3457
+ maxIterations: 50,
3458
+ maxToolCalls: 200,
3459
+ timeoutMs: this.timeoutMs
3460
+ });
3461
+ subagentIds.push(subagentId);
3462
+ taskIds.push(taskId);
3463
+ await coordinator.assign(spec);
3464
+ } catch {
3465
+ }
3466
+ })());
3467
+ }
3468
+ await Promise.all(spawnPromises);
3469
+ if (taskIds.length === 0) {
3470
+ return { results: [], allSuccessful: false, goalComplete: false, partialOutput: "" };
3471
+ }
3472
+ let results = [];
3473
+ try {
3474
+ const ctrl = new AbortController();
3475
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs + 6e4);
3476
+ try {
3477
+ results = await coordinator.awaitTasks(taskIds);
3478
+ } finally {
3479
+ clearTimeout(timer);
3480
+ }
3481
+ } catch {
3482
+ results = coordinator.results().slice(-taskIds.length);
3483
+ }
3484
+ const allSuccessful = results.length > 0 && results.every((r) => r.status === "success");
3485
+ const goalComplete = results.some(
3486
+ (r) => r.status === "success" && typeof r.result === "string" && GOAL_COMPLETE_MARKER2.test(r.result)
3487
+ );
3488
+ const partialOutput = results.map((r) => typeof r.result === "string" ? r.result : "").filter(Boolean).join("\n\n");
3489
+ return { results, allSuccessful, goalComplete, partialOutput };
3490
+ }
3491
+ // -------------------------------------------------------------------------
3492
+ // Goal decomposition
3493
+ // -------------------------------------------------------------------------
3494
+ async decomposeGoal(goal) {
3495
+ const todos = this.opts.agent.ctx?.todos;
3496
+ const tasks = [];
3497
+ if (Array.isArray(todos)) {
3498
+ const pending = todos.filter((t) => t.status === "pending").slice(0, this.slots);
3499
+ for (const t of pending) {
3500
+ tasks.push(`[todo] ${t.content}`);
3501
+ }
3502
+ }
3503
+ if (tasks.length < this.slots) {
3504
+ try {
3505
+ const gitStatus = await (this.opts.gitStatusReader?.() ?? this.readGitStatus());
3506
+ const dirty = gitStatus.trim();
3507
+ if (dirty) {
3508
+ const lines = dirty.split("\n").slice(0, this.slots - tasks.length);
3509
+ for (const line of lines) {
3510
+ const file = line.replace(/^[ MADRUC?]{2}\s*/, "").trim();
3511
+ if (file) tasks.push(`[git] inspect and fix: ${file}`);
3512
+ }
3513
+ }
3514
+ } catch {
3515
+ }
3516
+ }
3517
+ if (tasks.length < this.slots) {
3518
+ const remaining = this.slots - tasks.length;
3519
+ const brainstormed = await this.brainstormSubtasks(goal, remaining);
3520
+ tasks.push(...brainstormed);
3521
+ }
3522
+ return tasks.length > 0 ? tasks.slice(0, this.slots) : null;
3523
+ }
3524
+ async readGitStatus() {
3525
+ const { execFile: execFile2 } = await import('child_process');
3526
+ const { promisify: promisify2 } = await import('util');
3527
+ const execFileP2 = promisify2(execFile2);
3528
+ const { stdout } = await execFileP2("git", ["status", "--porcelain"], {
3529
+ cwd: this.opts.projectRoot,
3530
+ timeout: 5e3
3531
+ });
3532
+ return stdout;
3533
+ }
3534
+ async brainstormSubtasks(goal, count) {
3535
+ const lastFew = goal.journal.slice(-5).map((e) => ` - [${e.status}] ${e.task}`).join("\n");
3536
+ const directive = [
3537
+ `Decompose this goal into exactly ${count} independent sub-tasks for parallel execution.`,
3538
+ "",
3539
+ `Goal: ${goal.goal}`,
3540
+ "",
3541
+ lastFew ? `Recent:
3542
+ ${lastFew}` : "No prior iterations.",
3543
+ "",
3544
+ `Output exactly ${count} tasks, one per line, under 120 chars each.`,
3545
+ "Format: TASK-1 | TASK-2 | ... (pipe-separated, no numbering, no preamble).",
3546
+ "Each task must be independently actionable with no shared dependencies."
3547
+ ].join("\n");
3548
+ try {
3549
+ const ctrl = new AbortController();
3550
+ const timer = setTimeout(() => ctrl.abort(), 6e4);
3551
+ try {
3552
+ const result = await this.opts.agent.run([{ type: "text", text: directive }], {
3553
+ signal: ctrl.signal,
3554
+ maxIterations: 1
3555
+ });
3556
+ if (result.status !== "done") return [];
3557
+ const text = (result.finalText ?? "").trim();
3558
+ if (!text) return [];
3559
+ const tasks = text.split("|").map((t) => t.trim()).filter((t) => t.length > 10 && t.length < 240);
3560
+ return tasks.slice(0, count);
3561
+ } finally {
3562
+ clearTimeout(timer);
3563
+ }
3564
+ } catch {
3565
+ return [];
3566
+ }
3567
+ }
3568
+ // -------------------------------------------------------------------------
3569
+ // Compaction
3570
+ // -------------------------------------------------------------------------
3571
+ async maybeCompact() {
3572
+ const compactor = this.opts.compactor;
3573
+ if (!compactor) return;
3574
+ const ctx = this.opts.agent.ctx;
3575
+ if (!ctx) return;
3576
+ const shouldRun = this.iterationsSinceCompact >= (this.opts.compactEveryNIterations ?? 25);
3577
+ if (!shouldRun) return;
3578
+ const report = await compactor.compact(ctx, { aggressive: false });
3579
+ this.iterationsSinceCompact = 0;
3580
+ await this.appendIterationEntry({
3581
+ source: "manual",
3582
+ task: "compaction (cadence)",
3583
+ status: "success",
3584
+ note: `saved ~${report.before - report.after} tokens`
3585
+ });
3586
+ }
3587
+ // -------------------------------------------------------------------------
3588
+ // Helpers
3589
+ // -------------------------------------------------------------------------
3590
+ async appendIterationEntry(entry) {
3591
+ const current = await loadGoal(this.goalPath);
3592
+ if (!current) return;
3593
+ const updated = appendJournal(current, entry);
3594
+ await saveGoal(this.goalPath, updated);
3595
+ const entryWithMeta = {
3596
+ at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
3597
+ iteration: updated.iterations,
3598
+ ...entry
3599
+ };
3600
+ this.opts.onIteration?.(entryWithMeta);
3601
+ }
3602
+ async appendFailure(task, note) {
3603
+ await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
3604
+ }
3605
+ async persistState(state) {
3606
+ const current = await loadGoal(this.goalPath);
3607
+ if (!current) return;
3608
+ if (current.engineState === state) return;
3609
+ await saveGoal(this.goalPath, { ...current, engineState: state });
3610
+ }
3611
+ };
3612
+
3613
+ // src/execution/autonomy-prompt-contributor.ts
3614
+ function makeAutonomyPromptContributor(opts) {
3615
+ return async (ctx) => {
3616
+ if (ctx.subagent) return [];
3617
+ if (!opts.enabled()) return [];
3618
+ let goal;
3619
+ try {
3620
+ goal = await loadGoal(opts.goalPath);
3621
+ } catch {
3622
+ return [];
3623
+ }
3624
+ if (!goal) return [];
3625
+ const missionState = goal.goalState ?? "active";
3626
+ if (missionState !== "active") return [];
3627
+ const tailSize = opts.journalTailSize ?? 5;
3628
+ const journalTail = goal.journal.slice(-tailSize).map((e) => {
3629
+ const note = e.note ? ` \u2014 ${e.note.slice(0, 80)}` : "";
3630
+ return ` #${e.iteration} [${e.status}] ${e.task}${note}`;
3631
+ });
3632
+ const text = [
3633
+ "## ETERNAL AUTONOMY \u2014 active mission",
3634
+ "",
3635
+ "You are inside a long-running autonomous loop. The user is asleep",
3636
+ "and is not available to confirm decisions. Each turn you receive a",
3637
+ "directive describing one concrete sub-task that advances the mission.",
3638
+ "",
3639
+ `Mission: ${goal.goal}`,
3640
+ `Iteration: #${goal.iterations}`,
3641
+ journalTail.length > 0 ? `Recent journal (last ${journalTail.length}):
3642
+ ${journalTail.join("\n")}` : "Recent journal: (none \u2014 this is the first iteration)",
3643
+ "",
3644
+ "### Loop control markers",
3645
+ "Emit these on their own line in your final text \u2014 case-insensitive,",
3646
+ "whitespace-tolerant, but they must occupy the entire line:",
3647
+ "- `[continue]` \u2014 chain to the next internal step without returning.",
3648
+ "- `[done]` \u2014 the current sub-task is finished; return to the engine.",
3649
+ "- `[GOAL_COMPLETE]` \u2014 emit ONLY when the OVERALL mission is",
3650
+ " verifiably done. Must be followed by a one-paragraph verification",
3651
+ " recipe (artifact path, test command, or 10-second reproduction).",
3652
+ " The engine halts on this marker \u2014 false positives waste real",
3653
+ " human time. If unsure, emit `[done]` and let the next iteration",
3654
+ " decide.",
3655
+ "",
3656
+ "### Operating principles",
3657
+ "- YOLO is active. Do NOT ask for confirmation, do NOT propose",
3658
+ " options. Pick the best path and execute it.",
3659
+ "- Use tools freely; multiple calls per turn are normal and expected.",
3660
+ "- When working on a todo, mark it `in_progress` via the todos tool",
3661
+ " before tool work and `completed` (or `cancelled` with a reason)",
3662
+ " when done. The loop reads todo state between iterations.",
3663
+ "- If an approach fails twice in a row, pivot. Don't grind on the",
3664
+ " same wall \u2014 try a different angle, file a cancel on the todo, or",
3665
+ " surface the obstacle via `[done]` and let the next iteration",
3666
+ " re-plan."
3667
+ ].join("\n");
3668
+ return [
3669
+ {
3670
+ type: "text",
3671
+ text,
3672
+ cache_control: { type: "ephemeral" }
3673
+ }
3674
+ ];
3675
+ };
3676
+ }
3677
+
3678
+ // src/execution/goal-preamble.ts
3679
+ function buildGoalPreamble(goal) {
3680
+ return [
3681
+ "[GOAL \u2014 LOCKED IN. You will work on this until it is verifiably done.",
3682
+ "The user granted you full autonomy. Read these constraints once, then act.",
3683
+ "",
3684
+ "YOUR GOAL:",
3685
+ "---",
3686
+ goal,
3687
+ "---",
3688
+ "",
3689
+ "AUTHORITY YOU HAVE:",
3690
+ "- Spawn as many subagents as the work needs (delegate / spawn_subagent).",
3691
+ " Parallel + recursive fan-out are both fine. There is no spawn budget.",
3692
+ "- Use any provider/model per subagent \u2014 pick the right tool for each",
3693
+ " piece of work. Heavy reasoning model for planning, fast model for",
3694
+ " batch work, specialist model for domain code.",
3695
+ "- Run unlimited tool calls and iterations. There is NO hidden budget.",
3696
+ " The Agent loop auto-extends every 100 iterations forever.",
3697
+ "- Retry failed tools with different inputs, alternative paths, fresh",
3698
+ " subagents. Switch providers mid-run if one is rate-limited.",
3699
+ "- Re-plan freely when an approach hits a dead end. You are not obliged",
3700
+ " to stick with the first plan you proposed.",
3701
+ "",
3702
+ 'WHAT "DONE" MEANS \u2014 non-negotiable:',
3703
+ "- You can name a concrete artifact (a passing test, a written file at",
3704
+ " a specific path, a fixed bug verified by re-running the failing case,",
3705
+ " a clean grep that previously had matches).",
3706
+ "- You can tell the user HOW to verify it themselves in 10 seconds.",
3707
+ '- You have NOT hedged. None of: "looks like it should work", "I',
3708
+ ' believe this fixes it", "the changes appear correct".',
3709
+ "",
3710
+ "WHAT IS NOT DONE \u2014 never report any of these as completion:",
3711
+ "- An error message you didn't recover from.",
3712
+ '- An empty result, a 0-line file, a "no matches found" you accepted',
3713
+ " without questioning the search.",
3714
+ '- "Should I continue?" / "Want me to also...?" / "Let me know if you',
3715
+ ' want X." Those are hedges. The user already told you to finish the',
3716
+ " goal \u2014 just do it.",
3717
+ "- Partial progress dressed up as success. Fixed 3 of 5 bugs = 60%",
3718
+ " done, not done.",
3719
+ "- A subagent's failed/timeout/stopped TaskResult that you didn't",
3720
+ " respond to with a fresh attempt (different role, different model,",
3721
+ " tighter prompt).",
3722
+ "",
3723
+ "PERSISTENCE PROTOCOL:",
3724
+ "- If blocked, try at least 3 different angles before reporting the",
3725
+ " problem to the user. Different tool inputs, different subagent",
3726
+ " roles, different providers, different decomposition of the task.",
3727
+ "- If a tool fails, read its error, alter the input, try again. Do",
3728
+ " not just report the failure back.",
3729
+ "- If a subagent returns useless output, respawn with a tighter prompt",
3730
+ ' or a different role. Do not accept "I could not determine\u2026" as the',
3731
+ " final answer.",
3732
+ "- Use `ask_subagent` for one-shot questions when you don't need a",
3733
+ " full delegated task.",
3734
+ "",
3735
+ "REPORTING:",
3736
+ "- Stream short progress notes between major actions so the user can",
3737
+ " monitor. Do not go silent for 50 tool calls then dump a wall of",
3738
+ " text \u2014 but also do not narrate every tool call.",
3739
+ "- Use the shared scratchpad (if available) to leave breadcrumbs",
3740
+ " subagents can read.",
3741
+ "- Final response must include: (a) what was accomplished, (b) how",
3742
+ " to verify, (c) any caveats (residual TODOs, things the user",
3743
+ " should know about).",
3744
+ "",
3745
+ "BEGIN.]"
3746
+ ].join("\n");
3747
+ }
2441
3748
 
2442
3749
  // src/execution/retry-policy.ts
2443
3750
  var DefaultRetryPolicy = class _DefaultRetryPolicy {
@@ -2679,6 +3986,6 @@ function parseDescription(raw) {
2679
3986
  return { trigger, scope };
2680
3987
  }
2681
3988
 
2682
- export { AutoCompactionMiddleware, AutonomousRunner, DefaultErrorHandler, DefaultRetryPolicy, DefaultSkillLoader, DoneConditionChecker, EternalAutonomyEngine, HybridCompactor, IntelligentCompactor, SelectiveCompactor, ToolExecutor, buildGoalPreamble, makeAutonomyPromptContributor };
3989
+ export { AutoCompactionMiddleware, AutonomousRunner, DefaultErrorHandler, DefaultRetryPolicy, DefaultSkillLoader, DoneConditionChecker, EternalAutonomyEngine, HybridCompactor, IntelligentCompactor, ParallelEternalEngine, SelectiveCompactor, ToolExecutor, buildGoalPreamble, makeAutonomyPromptContributor };
2683
3990
  //# sourceMappingURL=index.js.map
2684
3991
  //# sourceMappingURL=index.js.map