@wrongstack/core 0.6.5 → 0.6.7

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 (53) hide show
  1. package/dist/{agent-bridge-BBXK_ppx.d.ts → agent-bridge-BCVxmrOn.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-DsSm9lKN.d.ts → agent-subagent-runner-BTloSwjE.d.ts} +3 -3
  3. package/dist/{compactor-C8NhpSt5.d.ts → compactor-BUJ8550X.d.ts} +1 -1
  4. package/dist/{config-DfC6g6KV.d.ts → config-BHkYGzUT.d.ts} +1 -1
  5. package/dist/{context-DN5v-uQX.d.ts → context-C8mBUzBv.d.ts} +3 -5
  6. package/dist/coordination/index.d.ts +10 -10
  7. package/dist/coordination/index.js +62 -30
  8. package/dist/coordination/index.js.map +1 -1
  9. package/dist/defaults/index.d.ts +21 -21
  10. package/dist/defaults/index.js +130 -32
  11. package/dist/defaults/index.js.map +1 -1
  12. package/dist/{events-CJqwQl8G.d.ts → events-DounT6pP.d.ts} +10 -1
  13. package/dist/execution/index.d.ts +56 -14
  14. package/dist/execution/index.js +74 -18
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/extension/index.d.ts +6 -6
  17. package/dist/{goal-store-_Er467ya.d.ts → goal-store-HHgaq5ue.d.ts} +1 -1
  18. package/dist/{index-CXnWsGBp.d.ts → index-BlVITqm-.d.ts} +6 -9
  19. package/dist/{index-DcnXDPdY.d.ts → index-DiIGZh5I.d.ts} +20 -5
  20. package/dist/index.d.ts +28 -28
  21. package/dist/index.js +251 -131
  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-CevFHHM1.d.ts → mcp-servers-CXFfDN_d.d.ts} +3 -3
  27. package/dist/models/index.d.ts +2 -2
  28. package/dist/{multi-agent-D5IbASk_.d.ts → multi-agent-Iy8YUe3v.d.ts} +4 -4
  29. package/dist/observability/index.d.ts +2 -2
  30. package/dist/{path-resolver-CBx_q1HA.d.ts → path-resolver-DwJO5XBn.d.ts} +2 -2
  31. package/dist/{plan-templates-BEOllUJV.d.ts → plan-templates-CG_pInJV.d.ts} +4 -4
  32. package/dist/{provider-runner-Byh5TcJs.d.ts → provider-runner-DoohzfyD.d.ts} +3 -3
  33. package/dist/{retry-policy-BZSIMxrJ.d.ts → retry-policy-DOnwHAyz.d.ts} +1 -1
  34. package/dist/sdd/index.d.ts +9 -4
  35. package/dist/sdd/index.js +21 -0
  36. package/dist/sdd/index.js.map +1 -1
  37. package/dist/{secret-scrubber-CT7wefiO.d.ts → secret-scrubber-C9UJxFCJ.d.ts} +1 -1
  38. package/dist/{secret-scrubber-I0QHY_ob.d.ts → secret-scrubber-DcTK9JHC.d.ts} +1 -1
  39. package/dist/security/index.d.ts +3 -3
  40. package/dist/{selector-DDb_mq9X.d.ts → selector-DVo3eT-P.d.ts} +1 -1
  41. package/dist/{session-reader-B9nVkziM.d.ts → session-reader-BXWxFAhS.d.ts} +1 -1
  42. package/dist/storage/index.d.ts +6 -6
  43. package/dist/storage/index.js +3 -2
  44. package/dist/storage/index.js.map +1 -1
  45. package/dist/{system-prompt-gL06H9P4.d.ts → system-prompt-BBGles-N.d.ts} +1 -1
  46. package/dist/{task-graph-BITvWt4t.d.ts → task-graph-D1YQbpxF.d.ts} +1 -0
  47. package/dist/{tool-executor-BF7QfYVE.d.ts → tool-executor-CwpjhwHC.d.ts} +4 -4
  48. package/dist/types/index.d.ts +16 -16
  49. package/dist/types/index.js +1 -3
  50. package/dist/types/index.js.map +1 -1
  51. package/dist/utils/index.d.ts +1 -1
  52. package/package.json +1 -1
  53. package/skills/sdd/SKILL.md +122 -106
package/dist/index.js CHANGED
@@ -2558,10 +2558,8 @@ var InMemoryAgentBridge = class {
2558
2558
  this.pendingRequests.delete(correlationId);
2559
2559
  reject(new Error(`Request ${correlationId} timed out after ${timeout}ms`));
2560
2560
  }, timeout);
2561
- if (this.stopped) {
2561
+ if (!this.inflightGuards.has(correlationId)) {
2562
2562
  clearTimeout(timer);
2563
- this.inflightGuards.delete(correlationId);
2564
- this.pendingRequests.delete(correlationId);
2565
2563
  reject(new Error("Bridge stopped"));
2566
2564
  return;
2567
2565
  }
@@ -5745,7 +5743,7 @@ var DirectorStateCheckpoint = class {
5745
5743
  this.timer = null;
5746
5744
  }
5747
5745
  await this.persist();
5748
- if (this.rewriteRequested) {
5746
+ while (this.rewriteRequested) {
5749
5747
  this.rewriteRequested = false;
5750
5748
  await this.persist();
5751
5749
  }
@@ -7428,7 +7426,8 @@ function formatGoal(goal, journalLimit = 10) {
7428
7426
  lines.push(`Set: ${goal.setAt}`);
7429
7427
  lines.push(`Last activity: ${goal.lastActivityAt}`);
7430
7428
  lines.push(`Iterations: ${goal.iterations}`);
7431
- lines.push(`Mission: ${goal.goalState ?? "active"}`);
7429
+ const stateLabel = goal.goalState ?? "active";
7430
+ lines.push(`State: ${stateLabel}${goal.iterations > 0 ? ` (iteration #${goal.iterations})` : ""}`);
7432
7431
  lines.push(`Engine: ${goal.engineState}`);
7433
7432
  const usage = summarizeUsage(goal);
7434
7433
  if (usage.iterationsWithUsage > 0) {
@@ -7453,7 +7452,8 @@ function formatGoal(goal, journalLimit = 10) {
7453
7452
  // src/execution/eternal-autonomy.ts
7454
7453
  var execFileP = promisify(execFile);
7455
7454
  var BRAINSTORM_DONE = /* @__PURE__ */ Symbol("brainstorm-done");
7456
- var GOAL_COMPLETE_MARKER = /^\s*\[goal[_\s-]?complete\]\s*$/im;
7455
+ var GOAL_COMPLETE_MARKER = /^\s*\[goal[_\s-]*complete\]\s*$/im;
7456
+ var GOAL_CLEAR_MARKER = /^\s*\[\/?goal\s*clear\]\s*$/im;
7457
7457
  var EternalAutonomyEngine = class {
7458
7458
  constructor(opts) {
7459
7459
  this.opts = opts;
@@ -7535,23 +7535,33 @@ var EternalAutonomyEngine = class {
7535
7535
  * `agent.run()` avoids race conditions on the shared Context.
7536
7536
  */
7537
7537
  async runOneIteration() {
7538
+ const emit = (stage) => {
7539
+ this.opts.onStage?.(stage);
7540
+ };
7538
7541
  const goal = await loadGoal(this.goalPath);
7539
7542
  if (!goal) {
7543
+ emit({ phase: "stopped" });
7540
7544
  this.stopRequested = true;
7541
7545
  return false;
7542
7546
  }
7543
7547
  const missionState = goal.goalState ?? "active";
7544
7548
  if (missionState !== "active") {
7549
+ emit({ phase: missionState === "paused" ? "paused" : "stopped" });
7545
7550
  this.stopRequested = true;
7546
7551
  return false;
7547
7552
  }
7553
+ emit({ phase: "decide", reason: "picking next task" });
7548
7554
  const action = await this.decide(goal);
7549
7555
  if (!action) {
7550
7556
  if (!this.stopRequested) {
7557
+ emit({ phase: "sleep", ms: 5e3 });
7551
7558
  await sleep(5e3);
7559
+ } else {
7560
+ emit({ phase: "stopped" });
7552
7561
  }
7553
7562
  return false;
7554
7563
  }
7564
+ emit({ phase: "execute", task: action.task });
7555
7565
  const ctrl = new AbortController();
7556
7566
  this.currentCtrl = ctrl;
7557
7567
  const timer = setTimeout(
@@ -7629,6 +7639,7 @@ var EternalAutonomyEngine = class {
7629
7639
  tokens,
7630
7640
  costUsd
7631
7641
  });
7642
+ emit({ phase: "reflect", status, note });
7632
7643
  let iterationIndex = 0;
7633
7644
  try {
7634
7645
  const reloaded = await loadGoal(this.goalPath);
@@ -7650,6 +7661,7 @@ var EternalAutonomyEngine = class {
7650
7661
  this.consecutiveTransientRetries++;
7651
7662
  const delay = this.computeTransientBackoffMs();
7652
7663
  if (delay > 0) {
7664
+ emit({ phase: "sleep", ms: delay });
7653
7665
  await this.sleepInterruptible(delay);
7654
7666
  }
7655
7667
  return false;
@@ -7663,8 +7675,16 @@ var EternalAutonomyEngine = class {
7663
7675
  return false;
7664
7676
  }
7665
7677
  this.consecutiveTransientRetries = 0;
7678
+ const cycleGapMs = this.opts.cycleGapMs ?? 1e3;
7679
+ emit({ phase: "sleep", ms: cycleGapMs });
7680
+ await sleep(cycleGapMs);
7666
7681
  if (GOAL_COMPLETE_MARKER.test(finalText)) {
7667
- await this.markGoalCompleted(action, finalText);
7682
+ await this.clearGoalManually(finalText);
7683
+ this.stopRequested = true;
7684
+ return true;
7685
+ }
7686
+ if (GOAL_CLEAR_MARKER.test(finalText)) {
7687
+ await this.clearGoalManually(finalText);
7668
7688
  this.stopRequested = true;
7669
7689
  return true;
7670
7690
  }
@@ -7964,6 +7984,30 @@ ${recentJournal}` : "No prior iterations.",
7964
7984
  });
7965
7985
  await saveGoal(this.goalPath, withEntry);
7966
7986
  }
7987
+ /**
7988
+ * Manually clear the goal — equivalent to `/goal clear` typed by the user.
7989
+ * Sets goalState to `abandoned`, removes the goal file, and fires
7990
+ * `onEternalStop` so the REPL returns to normal mode.
7991
+ */
7992
+ async clearGoalManually(note) {
7993
+ const current = await loadGoal(this.goalPath);
7994
+ if (current) {
7995
+ const abandoned = { ...current, goalState: "abandoned" };
7996
+ await saveGoal(this.goalPath, abandoned);
7997
+ }
7998
+ try {
7999
+ const { unlink: unlink10 } = await import('fs/promises');
8000
+ await unlink10(this.goalPath);
8001
+ } catch {
8002
+ }
8003
+ this.opts.onEternalStop?.();
8004
+ void this.appendIterationEntry({
8005
+ source: "manual",
8006
+ task: "goal cleared",
8007
+ status: "success",
8008
+ note: note.slice(0, 240)
8009
+ });
8010
+ }
7967
8011
  async appendFailure(task, note) {
7968
8012
  await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
7969
8013
  }
@@ -8031,10 +8075,10 @@ var SubagentBudget = class _SubagentBudget {
8031
8075
  * or hung listener (Director not built / event filter detached mid-run)
8032
8076
  * leaves the budget over-limit and never enforces anything, since
8033
8077
  * `checkLimit` returns synchronously via `void this.checkLimitAsync`.
8034
- * Hardcoded for now most fleets set their own per-task timeout that
8035
- * dwarfs this anyway. 30 s matches the existing event-emit timeoutMs.
8078
+ * Raised from 30s to 60s to give subagents more headroom before
8079
+ * the threshold negotiation times out and triggers a hard stop.
8036
8080
  */
8037
- static DECISION_TIMEOUT_MS = 3e4;
8081
+ static DECISION_TIMEOUT_MS = 6e4;
8038
8082
  /**
8039
8083
  * Injected by the runner when wiring the budget to its EventBus.
8040
8084
  * Used by `checkLimitAsync` to emit `budget.threshold_reached` events.
@@ -8565,13 +8609,19 @@ var FLEET_ROSTER = {
8565
8609
  "security-scanner": SECURITY_SCANNER_AGENT
8566
8610
  };
8567
8611
  var FLEET_ROSTER_BUDGETS = {
8568
- "audit-log": { timeoutMs: 8 * 60 * 1e3, maxIterations: 150, maxToolCalls: 500 },
8569
- "bug-hunter": { timeoutMs: 15 * 60 * 1e3, maxIterations: 200, maxToolCalls: 600 },
8570
- "refactor-planner": { timeoutMs: 12 * 60 * 1e3, maxIterations: 180, maxToolCalls: 550 },
8571
- "security-scanner": { timeoutMs: 15 * 60 * 1e3, maxIterations: 200, maxToolCalls: 600 }
8612
+ "audit-log": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 5e3, maxToolCalls: 15e3 },
8613
+ "bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
8614
+ "refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
8615
+ "security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 }
8616
+ };
8617
+ var GENERIC_SUBAGENT_BUDGET = {
8618
+ timeoutMs: 3 * 60 * 60 * 1e3,
8619
+ maxIterations: 5e3,
8620
+ maxToolCalls: 15e3
8572
8621
  };
8573
8622
  function applyRosterBudget(cfg) {
8574
- const defaultBudget = FLEET_ROSTER_BUDGETS[cfg.role ?? ""];
8623
+ const roleBudget = cfg.role ? FLEET_ROSTER_BUDGETS[cfg.role] : void 0;
8624
+ const defaultBudget = roleBudget ?? (cfg.name ? GENERIC_SUBAGENT_BUDGET : void 0);
8575
8625
  if (!defaultBudget) return cfg;
8576
8626
  return {
8577
8627
  ...cfg,
@@ -8643,7 +8693,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
8643
8693
  // hasParentBridge() — the type now reflects this.
8644
8694
  parentBridge: null,
8645
8695
  doneCondition: this.config.doneCondition,
8646
- maxConcurrent: this.config.maxConcurrent ?? 4
8696
+ maxConcurrent: this.config.maxConcurrent ?? 16
8647
8697
  };
8648
8698
  this.subagents.set(id, {
8649
8699
  config: { ...subagent, id },
@@ -8770,7 +8820,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
8770
8820
  }
8771
8821
  }
8772
8822
  canDispatch() {
8773
- const max = this.config.maxConcurrent ?? 4;
8823
+ const max = this.config.maxConcurrent ?? 16;
8774
8824
  return this.inFlight < max && this.pendingTasks.length > 0;
8775
8825
  }
8776
8826
  takeNextDispatchableTask() {
@@ -8864,13 +8914,18 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
8864
8914
  task.subagentId = subagentId;
8865
8915
  subagent.context.tasks.push(task);
8866
8916
  this.emit("task.assigned", { task, subagentId });
8917
+ const rawMaxIterations = subagent.config.maxIterations;
8918
+ const rawMaxToolCalls = subagent.config.maxToolCalls;
8919
+ const rawMaxTokens = subagent.config.maxTokens;
8920
+ const rawMaxCostUsd = subagent.config.maxCostUsd;
8921
+ const rawTimeoutMs = subagent.config.timeoutMs;
8867
8922
  const configWithRosterDefaults = applyRosterBudget(subagent.config);
8868
8923
  const budget = new SubagentBudget({
8869
- maxIterations: configWithRosterDefaults.maxIterations ?? this.config.defaultBudget?.maxIterations,
8870
- maxToolCalls: task.maxToolCalls ?? configWithRosterDefaults.maxToolCalls ?? this.config.defaultBudget?.maxToolCalls,
8871
- maxTokens: configWithRosterDefaults.maxTokens ?? this.config.defaultBudget?.maxTokens,
8872
- maxCostUsd: configWithRosterDefaults.maxCostUsd ?? this.config.defaultBudget?.maxCostUsd,
8873
- timeoutMs: task.timeoutMs ?? configWithRosterDefaults.timeoutMs ?? this.config.defaultBudget?.timeoutMs
8924
+ maxIterations: rawMaxIterations ?? this.config.defaultBudget?.maxIterations ?? configWithRosterDefaults.maxIterations,
8925
+ maxToolCalls: rawMaxToolCalls ?? this.config.defaultBudget?.maxToolCalls ?? configWithRosterDefaults.maxToolCalls,
8926
+ maxTokens: rawMaxTokens ?? this.config.defaultBudget?.maxTokens ?? configWithRosterDefaults.maxTokens,
8927
+ maxCostUsd: rawMaxCostUsd ?? this.config.defaultBudget?.maxCostUsd ?? configWithRosterDefaults.maxCostUsd,
8928
+ timeoutMs: rawTimeoutMs ?? this.config.defaultBudget?.timeoutMs ?? configWithRosterDefaults.timeoutMs
8874
8929
  });
8875
8930
  subagent.activeBudget = budget;
8876
8931
  if (!this.runner) {
@@ -8941,7 +8996,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
8941
8996
  kind: "timeout",
8942
8997
  used: elapsed,
8943
8998
  limit,
8944
- timeoutMs: 3e4,
8999
+ timeoutMs: 6e4,
8945
9000
  extend: (extra) => resolveDecision({ extend: extra }),
8946
9001
  deny: () => resolveDecision("stop")
8947
9002
  });
@@ -10371,7 +10426,12 @@ var Director = class {
10371
10426
  }
10372
10427
  }
10373
10428
  }
10374
- const result = await this.coordinator.spawn(config);
10429
+ let result;
10430
+ try {
10431
+ result = await this.coordinator.spawn(config);
10432
+ } catch (err) {
10433
+ throw err;
10434
+ }
10375
10435
  if (this.fleetManager) {
10376
10436
  this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
10377
10437
  } else {
@@ -10819,7 +10879,7 @@ var Director = class {
10819
10879
  }
10820
10880
  };
10821
10881
  function createDelegateTool(opts) {
10822
- const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
10882
+ const defaultTimeoutMs = opts.defaultTimeoutMs ?? 30 * 60 * 1e3;
10823
10883
  const rosterIds = opts.roster ? Object.keys(opts.roster) : [];
10824
10884
  const inputSchema = {
10825
10885
  type: "object",
@@ -10835,7 +10895,7 @@ function createDelegateTool(opts) {
10835
10895
  },
10836
10896
  name: {
10837
10897
  type: "string",
10838
- description: "Display name for the subagent when not using a roster role. Required when `role` is omitted."
10898
+ description: "Display name for free-form subagents (not using a roster role). The subagent gets a large default budget (3h, 5000 iter, 15000 tool calls). Required when `role` is omitted."
10839
10899
  },
10840
10900
  provider: {
10841
10901
  type: "string",
@@ -10866,8 +10926,8 @@ function createDelegateTool(opts) {
10866
10926
  };
10867
10927
  return {
10868
10928
  name: "delegate",
10869
- description: "Hand a discrete piece of work to a dedicated subagent and wait for its result. The subagent has its own context, its own LLM call, and its own budget \u2014 use this when a task is self-contained, would otherwise blow up your context, or benefits from a specialized role (bug-hunter, security-scanner, refactor-planner, audit-log). YOU decide how big the budget is: pass `timeoutMs`, `maxIterations`, and `maxToolCalls` sized to the actual work. There is no hidden cap forcing a 3-minute / 80-iteration limit \u2014 if a monorepo audit needs 2 hours and 500 tool calls, ask for that. Call multiple delegates in parallel through the provider's parallel-tool-call surface to fan work out across roles.",
10870
- usageHint: "Set `task` to a complete instruction. Either pick `role` from the roster or pass `name` + `provider` + `model`. For non-trivial work, also pass `timeoutMs` (the wall-clock budget you actually need), `maxIterations`, and `maxToolCalls` \u2014 defaults are intentionally generous (4 hours) but the right values depend on scope. Returns the subagent's `TaskResult` \u2014 including the textual `result`, iteration count, tool count, and duration. Auto-promotes the host into director mode on first call.",
10929
+ description: "Hand a discrete piece of work to a dedicated subagent and wait for its result. The subagent has its own context, its own LLM call, and its own budget \u2014 use this when a task is self-contained, would otherwise blow up your context, or benefits from a specialized role (bug-hunter, security-scanner, refactor-planner, audit-log). For free-form coding tasks (not tied to a pre-defined role), pass `name` + `task` \u2014 the subagent runs as a general-purpose coding agent with a large default budget. YOU decide how big the budget is: pass `timeoutMs`, `maxIterations`, and `maxToolCalls` sized to the actual work. There is no hidden cap forcing a 3-minute / 80-iteration limit \u2014 if a monorepo audit needs 2 hours and 500 tool calls, ask for that. Call multiple delegates in parallel through the provider's parallel-tool-call surface to fan work out across roles.",
10930
+ usageHint: "Set `task` to a complete instruction. Either pick `role` from the roster (audit-log, bug-hunter, refactor-planner, security-scanner) or pass `name` to run a free-form coding agent. For non-trivial work, also pass `timeoutMs`, `maxIterations`, and `maxToolCalls`. Returns the subagent's `TaskResult` \u2014 including the textual `result`, iteration count, tool count, and duration. Auto-promotes the host into director mode on first call.",
10871
10931
  permission: "auto",
10872
10932
  mutating: false,
10873
10933
  inputSchema,
@@ -10915,6 +10975,7 @@ function createDelegateTool(opts) {
10915
10975
  model: i.model,
10916
10976
  systemPromptOverride: i.systemPromptOverride
10917
10977
  };
10978
+ cfg = applyRosterBudget({ ...cfg, name: i.name });
10918
10979
  }
10919
10980
  if (typeof i.maxIterations === "number" && i.maxIterations > 0) {
10920
10981
  cfg.maxIterations = i.maxIterations;
@@ -10922,7 +10983,7 @@ function createDelegateTool(opts) {
10922
10983
  if (typeof i.maxToolCalls === "number" && i.maxToolCalls > 0) {
10923
10984
  cfg.maxToolCalls = i.maxToolCalls;
10924
10985
  }
10925
- const SUBAGENT_TIMEOUT_BUFFER_MS = opts.subagentTimeoutBufferMs ?? 3e4;
10986
+ const SUBAGENT_TIMEOUT_BUFFER_MS = opts.subagentTimeoutBufferMs ?? 6e4;
10926
10987
  const desiredSubTimeout = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
10927
10988
  if (!cfg.timeoutMs || cfg.timeoutMs > desiredSubTimeout) {
10928
10989
  cfg.timeoutMs = desiredSubTimeout;
@@ -10959,6 +11020,7 @@ function createDelegateTool(opts) {
10959
11020
  const errorKind = result.error?.kind;
10960
11021
  const retryable = result.error?.retryable;
10961
11022
  const backoffMs = result.error?.backoffMs;
11023
+ const summary = buildDelegateSummary(i.role, result);
10962
11024
  return {
10963
11025
  ok: result.status === "success",
10964
11026
  status: result.status,
@@ -10974,7 +11036,10 @@ function createDelegateTool(opts) {
10974
11036
  toolCalls: result.toolCalls,
10975
11037
  durationMs: result.durationMs,
10976
11038
  ...partial ? { partial } : {},
10977
- ...hintForKind(errorKind, retryable, backoffMs, partial) ? { hint: hintForKind(errorKind, retryable, backoffMs, partial) } : {}
11039
+ ...hintForKind(errorKind, retryable, backoffMs, partial) ? { hint: hintForKind(errorKind, retryable, backoffMs, partial) } : {},
11040
+ // Summary is included so callers (TUI, CLI renderer) can surface
11041
+ // it as a chat history line — the LLM also sees it for continuity.
11042
+ summary
10978
11043
  };
10979
11044
  } catch (err) {
10980
11045
  return {
@@ -10987,10 +11052,11 @@ function createDelegateTool(opts) {
10987
11052
  };
10988
11053
  }
10989
11054
  function instantiateRosterConfig2(role, base) {
11055
+ const withBudget = applyRosterBudget({ ...base, role });
10990
11056
  return {
10991
- ...base,
10992
- // Roster entries are templates. Give each spawn a fresh id so
10993
- // parallel or repeated delegates can use the same role safely.
11057
+ ...withBudget,
11058
+ // Give each spawn a fresh id so parallel or repeated delegates
11059
+ // can use the same role safely.
10994
11060
  id: `${role}-${randomUUID().slice(0, 8)}`
10995
11061
  };
10996
11062
  }
@@ -11050,6 +11116,18 @@ ${partial.lastAssistantText}`;
11050
11116
  return retryable ? "Failure classified as retryable. Try again with the same input." : void 0;
11051
11117
  }
11052
11118
  }
11119
+ function buildDelegateSummary(role, result) {
11120
+ const roleLabel = role ?? "subagent";
11121
+ const ms = result.durationMs;
11122
+ const duration = ms < 6e4 ? `${Math.round(ms / 1e3)}s` : ms < 36e5 ? `${Math.round(ms / 6e4)}m` : `${(ms / 36e5).toFixed(1)}h`;
11123
+ if (result.status === "success") {
11124
+ const preview = typeof result.result === "string" ? result.result.trim().slice(0, 120).replace(/\n+/g, " ") : null;
11125
+ const tail = preview ? ` \u2014 ${preview}` : "";
11126
+ return `[${roleLabel}] done in ${duration} (${result.iterations} iter, ${result.toolCalls} tools)${tail}`;
11127
+ }
11128
+ const errLabel = result.error?.kind ?? result.status;
11129
+ return `[${roleLabel}] ${result.status} after ${duration} (${result.iterations} iter, ${result.toolCalls} tools) \u2014 ${errLabel}`;
11130
+ }
11053
11131
  async function readSubagentPartial(opts, subagentId) {
11054
11132
  if (!opts.sessionsRoot) return void 0;
11055
11133
  const candidates = [];
@@ -11715,6 +11793,10 @@ var TaskTracker = class {
11715
11793
  node.updatedAt = now;
11716
11794
  if (status === "completed") {
11717
11795
  node.completedAt = now;
11796
+ node.startedAt = node.startedAt ?? now;
11797
+ }
11798
+ if (status === "in_progress") {
11799
+ node.startedAt = now;
11718
11800
  }
11719
11801
  this.transitions.push({ from, to: status, timestamp: now, reason });
11720
11802
  if (status === "completed") {
@@ -11726,6 +11808,23 @@ var TaskTracker = class {
11726
11808
  this.graph.updatedAt = now;
11727
11809
  this.persist();
11728
11810
  }
11811
+ /**
11812
+ * Update node fields (title, description, priority, estimateHours, tags).
11813
+ * Does NOT change status. Use updateNodeStatus for status changes.
11814
+ */
11815
+ updateNode(id, patch) {
11816
+ if (!this.graph) throw new Error("No graph loaded");
11817
+ const node = this.graph.nodes.get(id);
11818
+ if (!node) throw new Error(`Node ${id} not found`);
11819
+ if (patch.title !== void 0) node.title = patch.title;
11820
+ if (patch.description !== void 0) node.description = patch.description;
11821
+ if (patch.priority !== void 0) node.priority = patch.priority;
11822
+ if (patch.estimateHours !== void 0) node.estimateHours = patch.estimateHours;
11823
+ if (patch.tags !== void 0) node.tags = patch.tags;
11824
+ node.updatedAt = Date.now();
11825
+ this.graph.updatedAt = node.updatedAt;
11826
+ this.persist();
11827
+ }
11729
11828
  getNode(id) {
11730
11829
  return this.graph?.nodes.get(id);
11731
11830
  }
@@ -17950,10 +18049,15 @@ var Agent = class {
17950
18049
  });
17951
18050
  let finalText = "";
17952
18051
  let iterations = 0;
18052
+ const delegateSummaries = [];
17953
18053
  let effectiveLimit = opts.maxIterations ?? this.maxIterations;
17954
18054
  const hasHardLimit = effectiveLimit > 0 && Number.isFinite(effectiveLimit);
17955
18055
  let recoveryRetries = 0;
17956
18056
  const autonomousContinue = opts.autonomousContinue ?? this.autonomousContinue;
18057
+ const onSubagentDone = ({ summary, ok }) => {
18058
+ delegateSummaries.push({ summary, ok });
18059
+ };
18060
+ const offSubagentDone = this.events.on("subagent.done", onSubagentDone);
17957
18061
  const diRunner = this.container.has(TOKENS.ProviderRunner) ? this.container.resolve(TOKENS.ProviderRunner) : null;
17958
18062
  const baseRunner = diRunner ? (ctx, req) => diRunner.run({
17959
18063
  provider: ctx.provider,
@@ -17975,116 +18079,122 @@ var Agent = class {
17975
18079
  tracer: this.tracer
17976
18080
  });
17977
18081
  const customRunner = this.extensions.wrapProviderRunner(baseRunner);
17978
- for (let i = 0; ; i++) {
17979
- iterations = i + 1;
17980
- if (controller.signal.aborted) {
17981
- return { status: "aborted", iterations };
17982
- }
17983
- if (autonomousContinue) {
17984
- consumeAutonomousContinue(this.ctx);
17985
- }
17986
- const limitCheck = await this.checkIterationLimit(
17987
- i,
17988
- effectiveLimit,
17989
- hasHardLimit,
17990
- iterations
17991
- );
17992
- effectiveLimit = limitCheck.limit;
17993
- if (limitCheck.exit) {
17994
- return { ...limitCheck.exit, finalText };
17995
- }
17996
- await this.extensions.runBeforeIteration(this.ctx, i);
17997
- this.events.emit("iteration.started", { ctx: this.ctx, index: i });
17998
- const req = await this.buildAndRunRequestPipeline(opts);
17999
- let res;
18000
- try {
18001
- res = await customRunner(this.ctx, req);
18002
- recoveryRetries = 0;
18003
- } catch (err) {
18082
+ try {
18083
+ for (let i = 0; ; i++) {
18084
+ iterations = i + 1;
18004
18085
  if (controller.signal.aborted) {
18005
- this.events.emit("error", { err: toError(err), phase: "provider" });
18006
- return { status: "aborted", iterations, error: toWrongStackError(err, "AGENT_ABORTED") };
18086
+ return { status: "aborted", iterations };
18007
18087
  }
18008
- const extDecision = await this.extensions.runOnError(this.ctx, err, "provider", i);
18009
- if (extDecision) {
18010
- if (extDecision.action === "fail") {
18088
+ if (autonomousContinue) {
18089
+ consumeAutonomousContinue(this.ctx);
18090
+ }
18091
+ const limitCheck = await this.checkIterationLimit(
18092
+ i,
18093
+ effectiveLimit,
18094
+ hasHardLimit,
18095
+ iterations,
18096
+ delegateSummaries
18097
+ );
18098
+ effectiveLimit = limitCheck.limit;
18099
+ if (limitCheck.exit) {
18100
+ return { ...limitCheck.exit, finalText };
18101
+ }
18102
+ await this.extensions.runBeforeIteration(this.ctx, i);
18103
+ this.events.emit("iteration.started", { ctx: this.ctx, index: i });
18104
+ const req = await this.buildAndRunRequestPipeline(opts);
18105
+ let res;
18106
+ try {
18107
+ res = await customRunner(this.ctx, req);
18108
+ recoveryRetries = 0;
18109
+ } catch (err) {
18110
+ if (controller.signal.aborted) {
18011
18111
  this.events.emit("error", { err: toError(err), phase: "provider" });
18012
- return { status: "failed", iterations, error: toWrongStackError(err) };
18112
+ return { status: "aborted", iterations, error: toWrongStackError(err, "AGENT_ABORTED") };
18013
18113
  }
18014
- if (extDecision.action === "continue") {
18015
- await this.extensions.runAfterIteration(this.ctx, i);
18016
- continue;
18114
+ const extDecision = await this.extensions.runOnError(this.ctx, err, "provider", i);
18115
+ if (extDecision) {
18116
+ if (extDecision.action === "fail") {
18117
+ this.events.emit("error", { err: toError(err), phase: "provider" });
18118
+ return { status: "failed", iterations, error: toWrongStackError(err), delegateSummaries };
18119
+ }
18120
+ if (extDecision.action === "continue") {
18121
+ await this.extensions.runAfterIteration(this.ctx, i);
18122
+ continue;
18123
+ }
18124
+ if (extDecision.action === "retry") {
18125
+ recoveryRetries++;
18126
+ if (recoveryRetries > 2) {
18127
+ this.events.emit("error", { err: toError(err), phase: "provider" });
18128
+ return { status: "failed", iterations, error: toWrongStackError(err), delegateSummaries };
18129
+ }
18130
+ if (extDecision.model) this.ctx.model = extDecision.model;
18131
+ this.logger.info("Extension requested retry; retrying turn");
18132
+ continue;
18133
+ }
18134
+ }
18135
+ const recovered = await this.errorHandler.recover(err, this.ctx);
18136
+ if (!recovered || recovered.action === "fail") {
18137
+ this.events.emit("error", { err: toError(err), phase: "provider" });
18138
+ return {
18139
+ status: "failed",
18140
+ iterations,
18141
+ error: toWrongStackError(recovered?.error ?? err),
18142
+ delegateSummaries
18143
+ };
18017
18144
  }
18018
- if (extDecision.action === "retry") {
18145
+ if (recovered.action === "retry") {
18019
18146
  recoveryRetries++;
18020
18147
  if (recoveryRetries > 2) {
18021
18148
  this.events.emit("error", { err: toError(err), phase: "provider" });
18022
18149
  return { status: "failed", iterations, error: toWrongStackError(err) };
18023
18150
  }
18024
- if (extDecision.model) this.ctx.model = extDecision.model;
18025
- this.logger.info("Extension requested retry; retrying turn");
18151
+ if (recovered.model) this.ctx.model = recovered.model;
18152
+ this.logger.info(`Recovered provider error via ${recovered.reason}; retrying turn`);
18026
18153
  continue;
18027
18154
  }
18155
+ recoveryRetries = 0;
18156
+ res = recovered.response;
18028
18157
  }
18029
- const recovered = await this.errorHandler.recover(err, this.ctx);
18030
- if (!recovered || recovered.action === "fail") {
18031
- this.events.emit("error", { err: toError(err), phase: "provider" });
18032
- return {
18033
- status: "failed",
18034
- iterations,
18035
- error: toWrongStackError(recovered?.error ?? err)
18036
- };
18158
+ const responseResult = await this.processResponse(res, req);
18159
+ if (responseResult.aborted) {
18160
+ return { status: "aborted", iterations, finalText: responseResult.finalText, delegateSummaries };
18037
18161
  }
18038
- if (recovered.action === "retry") {
18039
- recoveryRetries++;
18040
- if (recoveryRetries > 2) {
18041
- this.events.emit("error", { err: toError(err), phase: "provider" });
18042
- return { status: "failed", iterations, error: toWrongStackError(err) };
18162
+ if (responseResult.done) {
18163
+ return { status: "done", iterations, finalText: responseResult.finalText, delegateSummaries };
18164
+ }
18165
+ finalText = responseResult.finalText;
18166
+ const toolUses = res.content.filter(isToolUseBlock);
18167
+ if (toolUses.length === 0) {
18168
+ this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
18169
+ if (autonomousContinue && responseResult.directive === "continue") {
18170
+ await this.compactContextIfNeeded();
18171
+ await this.extensions.runAfterIteration(this.ctx, i);
18172
+ continue;
18043
18173
  }
18044
- if (recovered.model) this.ctx.model = recovered.model;
18045
- this.logger.info(`Recovered provider error via ${recovered.reason}; retrying turn`);
18046
- continue;
18174
+ if (autonomousContinue && responseResult.directive === "stop") {
18175
+ return { status: "done", iterations, finalText, delegateSummaries };
18176
+ }
18177
+ return { status: "done", iterations, finalText, delegateSummaries };
18047
18178
  }
18048
- recoveryRetries = 0;
18049
- res = recovered.response;
18050
- }
18051
- const responseResult = await this.processResponse(res, req);
18052
- if (responseResult.aborted) {
18053
- return { status: "aborted", iterations, finalText: responseResult.finalText };
18054
- }
18055
- if (responseResult.done) {
18056
- return { status: "done", iterations, finalText: responseResult.finalText };
18057
- }
18058
- finalText = responseResult.finalText;
18059
- const toolUses = res.content.filter(isToolUseBlock);
18060
- if (toolUses.length === 0) {
18061
- this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
18062
- if (autonomousContinue && responseResult.directive === "continue") {
18179
+ await this.executeTools(toolUses);
18180
+ if (autonomousContinue && consumeAutonomousContinue(this.ctx)) {
18181
+ this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
18063
18182
  await this.compactContextIfNeeded();
18064
18183
  await this.extensions.runAfterIteration(this.ctx, i);
18065
18184
  continue;
18066
18185
  }
18067
- if (autonomousContinue && responseResult.directive === "stop") {
18068
- return { status: "done", iterations, finalText };
18069
- }
18070
- return { status: "done", iterations, finalText };
18071
- }
18072
- await this.executeTools(toolUses);
18073
- if (autonomousContinue && consumeAutonomousContinue(this.ctx)) {
18074
18186
  this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
18075
18187
  await this.compactContextIfNeeded();
18076
18188
  await this.extensions.runAfterIteration(this.ctx, i);
18077
- continue;
18078
- }
18079
- this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
18080
- await this.compactContextIfNeeded();
18081
- await this.extensions.runAfterIteration(this.ctx, i);
18082
- if (autonomousContinue && responseResult.directive === "continue") {
18083
- continue;
18084
- }
18085
- if (autonomousContinue && responseResult.directive === "stop") {
18086
- return { status: "done", iterations, finalText };
18189
+ if (autonomousContinue && responseResult.directive === "continue") {
18190
+ continue;
18191
+ }
18192
+ if (autonomousContinue && responseResult.directive === "stop") {
18193
+ return { status: "done", iterations, finalText, delegateSummaries };
18194
+ }
18087
18195
  }
18196
+ } finally {
18197
+ offSubagentDone();
18088
18198
  }
18089
18199
  }
18090
18200
  /**
@@ -18093,7 +18203,7 @@ var Agent = class {
18093
18203
  * the loop should exit. Returns `{ limit }` with no result when the
18094
18204
  * iteration may proceed.
18095
18205
  */
18096
- async checkIterationLimit(iterationIndex, limit, hasHardLimit, currentIterations) {
18206
+ async checkIterationLimit(iterationIndex, limit, hasHardLimit, currentIterations, delegateSummaries) {
18097
18207
  if (hasHardLimit && iterationIndex >= limit) {
18098
18208
  const extendBy = await requestLimitExtension({
18099
18209
  events: this.events,
@@ -18106,7 +18216,7 @@ var Agent = class {
18106
18216
  this.logger.info(`Iteration limit extended by ${extendBy} (new limit: ${newLimit})`);
18107
18217
  return { limit: newLimit };
18108
18218
  }
18109
- return { limit, exit: { status: "max_iterations", iterations: currentIterations } };
18219
+ return { limit, exit: { status: "max_iterations", iterations: currentIterations, delegateSummaries } };
18110
18220
  }
18111
18221
  return { limit };
18112
18222
  }
@@ -18513,11 +18623,9 @@ var Context = class {
18513
18623
  return this._state;
18514
18624
  }
18515
18625
  /**
18516
- * Register a teardown hook tied to the current run's abort signal. The
18517
- * hook fires when the run aborts or ends normally Agent.run wires this
18518
- * through a RunController. Hooks registered before a run starts are stored
18519
- * and fired when the next run ends; there is no "immediate fire" when no
18520
- * run is active.
18626
+ * Register a teardown hook tied to the current run's abort signal.
18627
+ * Hooks registered before a run starts are stored and fired when the
18628
+ * next run ends; there is no immediate fire when no run is active.
18521
18629
  *
18522
18630
  * **Scope:** these hooks fire on the **whole agent run's** abort, not on
18523
18631
  * an individual tool call. For per-tool teardown of resources owned by
@@ -19096,7 +19204,7 @@ ${mem}`);
19096
19204
  };
19097
19205
 
19098
19206
  // src/registry/tool-registry.ts
19099
- var ToolRegistry = class {
19207
+ var ToolRegistry = class _ToolRegistry {
19100
19208
  tools = /* @__PURE__ */ new Map();
19101
19209
  register(tool, owner = "core") {
19102
19210
  if (this.tools.has(tool.name)) {
@@ -19218,6 +19326,15 @@ var ToolRegistry = class {
19218
19326
  clear() {
19219
19327
  this.tools.clear();
19220
19328
  }
19329
+ /**
19330
+ * Return a new ToolRegistry with the same registered tools and owners.
19331
+ * Useful for creating filtered copies in multi-agent scenarios.
19332
+ */
19333
+ clone() {
19334
+ const copy = new _ToolRegistry();
19335
+ for (const { tool, owner } of this.listWithOwner()) copy.register(tool, owner);
19336
+ return copy;
19337
+ }
19221
19338
  };
19222
19339
 
19223
19340
  // src/registry/provider-registry.ts
@@ -19282,9 +19399,7 @@ var SlashCommandRegistry = class {
19282
19399
  }
19283
19400
  }
19284
19401
  if (this.cmds.has(fullName)) {
19285
- if (!isPlugin) {
19286
- throw new Error(`Built-in slash command "${fullName}" is already registered.`);
19287
- }
19402
+ if (!isPlugin) return;
19288
19403
  this.cmds.set(fullName, { cmd, owner });
19289
19404
  for (const a of cmd.aliases ?? []) {
19290
19405
  this.cmds.set(`${owner}:${a}`, { cmd, owner });
@@ -19679,7 +19794,12 @@ async function unloadPlugins(loadedPlugins, opts) {
19679
19794
  for (const plugin of ordered) {
19680
19795
  if (typeof plugin.teardown !== "function") continue;
19681
19796
  try {
19682
- const api = pluginApiMap.get(plugin) ?? opts.apiFactory(plugin);
19797
+ const api = pluginApiMap.get(plugin);
19798
+ if (!api) {
19799
+ throw new Error(
19800
+ `Plugin "${plugin.name}" API not found in pluginApiMap \u2014 was setup() called?`
19801
+ );
19802
+ }
19683
19803
  await plugin.teardown(api);
19684
19804
  pluginApiMap.delete(plugin);
19685
19805
  opts.log.info(`Plugin "${plugin.name}" torn down`);