@wrongstack/core 0.298.3 → 0.300.0

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 (70) hide show
  1. package/dist/chronicle/index.js +4 -1
  2. package/dist/coordination/agents/index.js +4 -1
  3. package/dist/coordination/director.d.ts +8 -0
  4. package/dist/coordination/fleet-manager.d.ts +48 -3
  5. package/dist/coordination/ifleet-manager.d.ts +2 -0
  6. package/dist/coordination/index.js +127 -24
  7. package/dist/coordination/multi-agent-coordinator.d.ts +1 -0
  8. package/dist/core/fallback-model.d.ts +48 -0
  9. package/dist/core/index.d.ts +3 -2
  10. package/dist/core/index.js +288 -34
  11. package/dist/core/instruction-template.d.ts +80 -0
  12. package/dist/core/system-prompt-blocks.d.ts +10 -1
  13. package/dist/core/system-prompt-builder.d.ts +35 -1
  14. package/dist/defaults/index.js +358 -117
  15. package/dist/design/index.js +4 -1
  16. package/dist/execution/autonomy-brain.d.ts +7 -0
  17. package/dist/execution/council-brain.d.ts +17 -2
  18. package/dist/execution/council-orchestrator.d.ts +23 -4
  19. package/dist/execution/council-personas.d.ts +10 -0
  20. package/dist/execution/council-prompts.d.ts +12 -1
  21. package/dist/execution/index.d.ts +1 -1
  22. package/dist/execution/index.js +412 -145
  23. package/dist/fleet-notifier.d.ts +9 -2
  24. package/dist/goal/index.js +4 -1
  25. package/dist/hooks/index.js +140 -10
  26. package/dist/hq/exposure.d.ts +0 -11
  27. package/dist/hq/index.js +34 -8
  28. package/dist/hq/protocol/client.d.ts +14 -1
  29. package/dist/hq/protocol/fleet.d.ts +22 -0
  30. package/dist/hq/protocol.js +12 -1
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index.js +1718 -753
  33. package/dist/infrastructure/index.js +50 -2
  34. package/dist/infrastructure/mcp-servers.d.ts +35 -0
  35. package/dist/kernel/events/brain-events.d.ts +9 -0
  36. package/dist/kernel/events/provider-events.d.ts +49 -2
  37. package/dist/kernel/events/sdd-events.d.ts +2 -0
  38. package/dist/models/index.js +1 -1
  39. package/dist/plugin/api.d.ts +6 -0
  40. package/dist/plugin/config.d.ts +55 -0
  41. package/dist/plugin/index.d.ts +1 -1
  42. package/dist/plugin/index.js +138 -22
  43. package/dist/security/index.d.ts +1 -1
  44. package/dist/security/index.js +157 -42
  45. package/dist/security/permission-helpers.d.ts +23 -6
  46. package/dist/security/permission-policy.d.ts +16 -0
  47. package/dist/security/totp.d.ts +14 -0
  48. package/dist/storage/director-state.d.ts +7 -0
  49. package/dist/storage/index.js +46 -9
  50. package/dist/tools/council-tool.d.ts +1 -1
  51. package/dist/tools/fallback-system-config-view-tool.d.ts +1 -1
  52. package/dist/tools/index.js +449 -112
  53. package/dist/types/config/skills-fleet-brain.d.ts +4 -2
  54. package/dist/types/config/tools.d.ts +99 -0
  55. package/dist/types/council.d.ts +11 -0
  56. package/dist/types/index.d.ts +3 -2
  57. package/dist/types/index.js +3 -3
  58. package/dist/types/multi-agent.d.ts +10 -0
  59. package/dist/types/one-shot-llm.d.ts +31 -3
  60. package/dist/types/plugin.d.ts +28 -0
  61. package/dist/types/session.d.ts +5 -1
  62. package/dist/utils/index.js +4 -1
  63. package/dist/utils/wstack-paths.d.ts +2 -0
  64. package/dist/worktree/index.js +47 -25
  65. package/dist/worktree/worktree-manager.d.ts +16 -10
  66. package/instructions/coordination/subagent-baseline.md +8 -0
  67. package/instructions/system-lite.md +83 -3
  68. package/instructions/system-pro.md +286 -97
  69. package/instructions/system.md +236 -85
  70. package/package.json +3 -3
@@ -582,7 +582,9 @@ function safeProfileName(name) {
582
582
  function activeProfileName(globalRoot) {
583
583
  try {
584
584
  const parsed = JSON.parse(fs2.readFileSync(path4.join(globalRoot, "config.json"), "utf8"));
585
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
585
+ return safeProfileName(
586
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
587
+ );
586
588
  } catch {
587
589
  return "default";
588
590
  }
@@ -662,6 +664,7 @@ function resolveWstackPaths(opts) {
662
664
  projectPlan: path4.join(projectDir, "plan.json"),
663
665
  projectAutophase: path4.join(projectDir, "autophase"),
664
666
  projectSddBoards: path4.join(projectDir, "sdd-boards"),
667
+ projectRequirementIntakes: path4.join(projectDir, "requirement-intakes"),
665
668
  syncConfig: path4.join(profileDir, "sync.json"),
666
669
  configHistoryDir: path4.join(globalRoot, "config-history"),
667
670
  projectStatus: (projectHash2) => path4.join(globalRoot, "projects", projectHash2, "status.json")
@@ -98,7 +98,9 @@ function safeProfileName(name) {
98
98
  function activeProfileName(globalRoot) {
99
99
  try {
100
100
  const parsed = JSON.parse(fs.readFileSync(path.join(globalRoot, "config.json"), "utf8"));
101
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
101
+ return safeProfileName(
102
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
103
+ );
102
104
  } catch {
103
105
  return "default";
104
106
  }
@@ -178,6 +180,7 @@ function resolveWstackPaths(opts) {
178
180
  projectPlan: path.join(projectDir, "plan.json"),
179
181
  projectAutophase: path.join(projectDir, "autophase"),
180
182
  projectSddBoards: path.join(projectDir, "sdd-boards"),
183
+ projectRequirementIntakes: path.join(projectDir, "requirement-intakes"),
181
184
  syncConfig: path.join(profileDir, "sync.json"),
182
185
  configHistoryDir: path.join(globalRoot, "config-history"),
183
186
  projectStatus: (projectHash2) => path.join(globalRoot, "projects", projectHash2, "status.json")
@@ -149,5 +149,13 @@ export declare class Director implements DirectorFleetHost, ICoordinator {
149
149
  acquireCheckpointLock(): Promise<boolean>;
150
150
  spawnCollab(options: CollabSessionOptions): Promise<CollabDebugReport>;
151
151
  resumeFromCheckpoint(snapshot: DirectorStateSnapshot): void;
152
+ /**
153
+ * After re-attaching checkpoint metadata, pin the live maxSpawns ceiling
154
+ * (profile/flag/env wins over historical checkpoint metadata). The
155
+ * historical cumulative spawn counter is deliberately NOT restored — the
156
+ * lifetime budget is scoped to this director run, so a restarted session
157
+ * resumes with a fresh budget rather than a possibly-exhausted counter.
158
+ */
159
+ private applyResumeBudget;
152
160
  }
153
161
  //# sourceMappingURL=director.d.ts.map
@@ -72,6 +72,14 @@ export declare class FleetManager implements IFleetManager {
72
72
  readonly spawnDepth: number;
73
73
  /** Live spawn counter. */
74
74
  private spawnCount;
75
+ /**
76
+ * Historical `maxSpawns` from the last restored checkpoint (set for any
77
+ * finite checkpoint ceiling, matching or not). Surfaced in
78
+ * `budgetSnapshot()` so operators can see resume reconciliation without
79
+ * reading the JSON file; `ceilingMismatch` carries the actual mismatch signal.
80
+ */
81
+ private checkpointMaxSpawnsAtResume;
82
+ private ceilingMismatchAtResume;
75
83
  private readonly stateCheckpoint;
76
84
  private readonly sessionWriter;
77
85
  private manifestTimer;
@@ -129,7 +137,7 @@ export declare class FleetManager implements IFleetManager {
129
137
  * which cap was exceeded. Does NOT throw — the caller decides
130
138
  * how to surface the rejection.
131
139
  */
132
- canSpawn(_config: SubagentConfig): {
140
+ canSpawn(config: SubagentConfig): {
133
141
  kind: 'max_spawns' | 'max_spawn_depth' | 'max_cost_usd' | 'max_tokens' | 'max_context_load';
134
142
  limit: number;
135
143
  observed: number;
@@ -144,6 +152,41 @@ export declare class FleetManager implements IFleetManager {
144
152
  maxCostUsd: number;
145
153
  usedCostUsd: number;
146
154
  remainingCostUsd: number;
155
+ /** Historical ceiling from the last restored checkpoint, when it differs. */
156
+ checkpointMaxSpawns?: number | undefined;
157
+ /** True when checkpoint metadata and the live ceiling disagree. */
158
+ ceilingMismatch?: boolean | undefined;
159
+ };
160
+ /**
161
+ * Re-attach the on-disk checkpoint after a resume while keeping the live
162
+ * `maxSpawns` ceiling from construction (profile / flag / env). The
163
+ * historical `spawnCount` is deliberately NOT restored: the lifetime
164
+ * budget is scoped to this director run, so a restarted session gets a
165
+ * fresh budget instead of inheriting a (possibly exhausted) counter from
166
+ * before the restart. The checkpoint ceiling is rewritten to the live
167
+ * value so subsequent writes do not reintroduce a stale limit.
168
+ */
169
+ restoreFromCheckpoint(snapshot: {
170
+ spawnCount: number;
171
+ maxSpawns?: number | undefined;
172
+ version?: 1 | undefined;
173
+ directorRunId?: string | undefined;
174
+ updatedAt?: string | undefined;
175
+ spawnDepth?: number | undefined;
176
+ maxSpawnDepth?: number | undefined;
177
+ directorBudget?: {
178
+ maxCostUsd?: number | undefined;
179
+ maxTokens?: number | undefined;
180
+ } | undefined;
181
+ subagents?: unknown[] | undefined;
182
+ tasks?: unknown[] | undefined;
183
+ usage?: unknown | undefined;
184
+ }): {
185
+ usedSpawns: number;
186
+ maxSpawns: number;
187
+ remainingSpawns: number;
188
+ checkpointMaxSpawns?: number | undefined;
189
+ ceilingMismatch: boolean;
147
190
  };
148
191
  setLeaderContextPressure(tokens: number): void;
149
192
  /** Test-only accessor: number of entries in the private subagent-meta map. */
@@ -175,8 +218,10 @@ export declare class FleetManager implements IFleetManager {
175
218
  */
176
219
  get usedNicknames(): ReadonlySet<string>;
177
220
  /**
178
- * Records a spawn: increments counter, stores metadata, updates state checkpoint,
179
- * and schedules a debounced manifest write. Call AFTER the coordinator
221
+ * Records a spawn: stores metadata, updates state checkpoint, and schedules
222
+ * a debounced manifest write. Increments the lifetime spawn counter UNLESS
223
+ * the config is `spawnBudgetExempt` (ephemeral Chimera reviewer/cascade
224
+ * spawns do not consume the leader's budget). Call AFTER the coordinator
180
225
  * has successfully spawned the subagent.
181
226
  *
182
227
  * @param subagentId The subagent's id (from coordinator.spawn result)
@@ -52,6 +52,8 @@ export interface IFleetManager {
52
52
  maxCostUsd: number;
53
53
  usedCostUsd: number;
54
54
  remainingCostUsd: number;
55
+ checkpointMaxSpawns?: number | undefined;
56
+ ceilingMismatch?: boolean | undefined;
55
57
  }) | undefined;
56
58
  /**
57
59
  * Update the leader agent's current context pressure (tokens used in the
@@ -1361,7 +1361,9 @@ function safeProfileName(name) {
1361
1361
  function activeProfileName(globalRoot) {
1362
1362
  try {
1363
1363
  const parsed = JSON.parse(fs.readFileSync(path.join(globalRoot, "config.json"), "utf8"));
1364
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
1364
+ return safeProfileName(
1365
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
1366
+ );
1365
1367
  } catch {
1366
1368
  return "default";
1367
1369
  }
@@ -1441,6 +1443,7 @@ function resolveWstackPaths(opts) {
1441
1443
  projectPlan: path.join(projectDir, "plan.json"),
1442
1444
  projectAutophase: path.join(projectDir, "autophase"),
1443
1445
  projectSddBoards: path.join(projectDir, "sdd-boards"),
1446
+ projectRequirementIntakes: path.join(projectDir, "requirement-intakes"),
1444
1447
  syncConfig: path.join(profileDir, "sync.json"),
1445
1448
  configHistoryDir: path.join(globalRoot, "config-history"),
1446
1449
  projectStatus: (projectHash2) => path.join(globalRoot, "projects", projectHash2, "status.json")
@@ -6755,16 +6758,21 @@ var BrainMonitor = class {
6755
6758
  this.activeRuns += 1;
6756
6759
  this.lastProgressAt = Date.now();
6757
6760
  }),
6761
+ // `agent.run.completed` is the only terminator. `Agent.run` emits it on
6762
+ // every exit path — the success path and, unconditionally, the catch
6763
+ // path — and emits `agent.run.error` *in addition* when a run fails
6764
+ // (`core/agent.ts:302` then `:310`). Decrementing on both meant one
6765
+ // failed run subtracted two, and the `Math.max(0, …)` clamp hid it
6766
+ // instead of letting the counter go visibly negative.
6767
+ //
6768
+ // The effect was that the watchdog below stopped watching: with two
6769
+ // concurrent runs where one fails, `activeRuns` reaches 0 while a run
6770
+ // is still live, and the stall check returns early on every tick.
6758
6771
  this.opts.events.on("agent.run.completed", (e) => {
6759
6772
  const lsid = this.resolveLeaderSessionId();
6760
6773
  if (lsid && e.sessionId && e.sessionId !== lsid) return;
6761
6774
  this.activeRuns = Math.max(0, this.activeRuns - 1);
6762
6775
  }),
6763
- this.opts.events.on("agent.run.error", (e) => {
6764
- const lsid = this.resolveLeaderSessionId();
6765
- if (lsid && e.sessionId && e.sessionId !== lsid) return;
6766
- this.activeRuns = Math.max(0, this.activeRuns - 1);
6767
- }),
6768
6776
  this.opts.events.on("iteration.started", (e) => {
6769
6777
  const lsid = this.resolveLeaderSessionId();
6770
6778
  if (lsid && e.sessionId && e.sessionId !== lsid) return;
@@ -9780,6 +9788,21 @@ var DirectorStateCheckpoint = class {
9780
9788
  resume(snapshot) {
9781
9789
  this.snapshot = snapshot;
9782
9790
  }
9791
+ /**
9792
+ * After resume, pin the live spawn ceiling from the current profile/flag
9793
+ * while preserving `spawnCount` (cumulative used budget). Checkpoint
9794
+ * metadata previously stored a historical `maxSpawns` that can diverge
9795
+ * from the live runtime ceiling — operators need the live value to win.
9796
+ */
9797
+ applyLiveMaxSpawns(maxSpawns) {
9798
+ if (this.snapshot.maxSpawns === maxSpawns) return;
9799
+ this.snapshot = {
9800
+ ...this.snapshot,
9801
+ maxSpawns
9802
+ };
9803
+ this.bumpUpdatedAt();
9804
+ this.schedule();
9805
+ }
9783
9806
  current() {
9784
9807
  return this.snapshot;
9785
9808
  }
@@ -12609,7 +12632,7 @@ var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not eno
12609
12632
  var ROUTE_SCOPED_QUOTA_RE = /\b(?:for|on)\s+(?:(?:this|the)\s+)?(?:route|model)\b|\b(?:route|model)(?:[-_\s]+[\w.-]+)?[-_\s]*(?:quota|limit)\b|\b(?:quota|limit).{0,24}\b(?:for|on)\s+(?:(?:this|the)\s+)?(?:route|model)\b/i;
12610
12633
 
12611
12634
  // src/types/provider.ts
12612
- var CONTEXT_OVERFLOW_RE = /context.length|context.window|maximum context|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|too many tokens|reduce the length|resulted in \d+ tokens|input.{0,12}too (?:large|long)|context_length_exceeded/i;
12635
+ var CONTEXT_OVERFLOW_RE = /context.length|context.window|maximum context|max.*tokens?.*exceeded|(?:prompt|request|input|messages?).{0,12}too (?:large|long)|exceeds the context|\btokens\b.*exceed|too many tokens|reduce the length|resulted in \d+ tokens|context_length_exceeded/i;
12613
12636
  var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
12614
12637
  var RATE_LIMIT_EXCEEDED_RE = /rate[-_\s]*limit[-_\s]*exceeded/i;
12615
12638
  function classifyProviderError(status, body, message) {
@@ -12619,7 +12642,7 @@ function classifyProviderError(status, body, message) {
12619
12642
  if (status === 408) return "timeout";
12620
12643
  if (status === 599) return "stream_hang";
12621
12644
  if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
12622
- if (status === 429 && body?.message && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
12645
+ if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
12623
12646
  return "quota_exhausted";
12624
12647
  }
12625
12648
  if (type === "rate_limit_error" || status === 429) return "rate_limit";
@@ -12721,7 +12744,7 @@ var ProviderError = class extends WrongStackError {
12721
12744
  const e = err;
12722
12745
  const name = e.name;
12723
12746
  if (typeof name !== "string" || !name.endsWith("Error")) return false;
12724
- return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string";
12747
+ return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string" && typeof e.describe === "function";
12725
12748
  }
12726
12749
  constructor(message, status, retryable, providerId, opts = {}) {
12727
12750
  const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
@@ -13523,7 +13546,7 @@ async function spawn2(host, config, priceLookup) {
13523
13546
  if (host.spawnDepth >= maxSpawnDepth) {
13524
13547
  throw new FleetSpawnBudgetError("max_spawn_depth", maxSpawnDepth, host.spawnDepth);
13525
13548
  }
13526
- if (host.spawnCount >= host.maxSpawns) {
13549
+ if (host.spawnCount >= host.maxSpawns && !config.spawnBudgetExempt) {
13527
13550
  throw new FleetSpawnBudgetError("max_spawns", host.maxSpawns, host.spawnCount + 1);
13528
13551
  }
13529
13552
  if (host.maxFleetCostUsd < Number.POSITIVE_INFINITY) {
@@ -13574,7 +13597,9 @@ async function spawn2(host, config, priceLookup) {
13574
13597
  ...Number.isFinite(budget?.remainingSpawns ?? host.maxSpawns - host.spawnCount) ? {
13575
13598
  remainingSpawns: Math.max(
13576
13599
  0,
13577
- (budget?.remainingSpawns ?? host.maxSpawns - host.spawnCount) - 1
13600
+ // Exempt spawns don't consume leader budget, so the reported
13601
+ // headroom is not decremented for them.
13602
+ (budget?.remainingSpawns ?? host.maxSpawns - host.spawnCount) - (config.spawnBudgetExempt ? 0 : 1)
13578
13603
  )
13579
13604
  } : {},
13580
13605
  ...Number.isFinite(maxFleetTokens) ? { maxTokens: maxFleetTokens } : {},
@@ -13587,7 +13612,9 @@ async function spawn2(host, config, priceLookup) {
13587
13612
  if (host.fleetManager) {
13588
13613
  host.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
13589
13614
  } else {
13590
- host.spawnCount += 1;
13615
+ if (!config.spawnBudgetExempt) {
13616
+ host.spawnCount += 1;
13617
+ }
13591
13618
  host.subagentMeta.set(result.subagentId, {
13592
13619
  provider: config.provider,
13593
13620
  model: config.model
@@ -14512,8 +14539,17 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
14512
14539
  durationMs: 0
14513
14540
  };
14514
14541
  this.completedResults.push(synthetic);
14542
+ this.trimCompletedResults();
14515
14543
  this.emit("task.completed", { task, result: synthetic });
14516
14544
  }
14545
+ trimCompletedResults() {
14546
+ if (this.completedResults.length > _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS) {
14547
+ this.completedResults.splice(
14548
+ 0,
14549
+ this.completedResults.length - _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS
14550
+ );
14551
+ }
14552
+ }
14517
14553
  async runDispatched(subagentId, task) {
14518
14554
  const subagent = this.subagents.get(subagentId);
14519
14555
  if (!subagent) return;
@@ -14647,12 +14683,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
14647
14683
  }
14648
14684
  recordCompletion(result) {
14649
14685
  this.completedResults.push(result);
14650
- if (this.completedResults.length > _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS) {
14651
- this.completedResults.splice(
14652
- 0,
14653
- this.completedResults.length - _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS
14654
- );
14655
- }
14686
+ this.trimCompletedResults();
14656
14687
  this.totalIterations += result.iterations;
14657
14688
  if (this.inFlight > 0) {
14658
14689
  this.inFlight--;
@@ -19294,6 +19325,7 @@ var Director = class _Director {
19294
19325
  }
19295
19326
  setCheckpointState(snapshot) {
19296
19327
  setCheckpointState(this.checkpointHost(), snapshot);
19328
+ this.applyResumeBudget(snapshot);
19297
19329
  }
19298
19330
  async readSession(subagentId, tail) {
19299
19331
  return readDirectorSubagentSession({
@@ -19345,6 +19377,22 @@ var Director = class _Director {
19345
19377
  }
19346
19378
  resumeFromCheckpoint(snapshot) {
19347
19379
  resumeFromCheckpoint(this.checkpointHost(), snapshot);
19380
+ this.applyResumeBudget(snapshot);
19381
+ }
19382
+ /**
19383
+ * After re-attaching checkpoint metadata, pin the live maxSpawns ceiling
19384
+ * (profile/flag/env wins over historical checkpoint metadata). The
19385
+ * historical cumulative spawn counter is deliberately NOT restored — the
19386
+ * lifetime budget is scoped to this director run, so a restarted session
19387
+ * resumes with a fresh budget rather than a possibly-exhausted counter.
19388
+ */
19389
+ applyResumeBudget(snapshot) {
19390
+ if (this.fleetManager) {
19391
+ this.fleetManager.restoreFromCheckpoint(snapshot);
19392
+ }
19393
+ this.stateCheckpoint?.applyLiveMaxSpawns(
19394
+ Number.isFinite(this.maxSpawns) ? this.maxSpawns : void 0
19395
+ );
19348
19396
  }
19349
19397
  };
19350
19398
 
@@ -19367,6 +19415,14 @@ var FleetManager = class {
19367
19415
  spawnDepth;
19368
19416
  /** Live spawn counter. */
19369
19417
  spawnCount = 0;
19418
+ /**
19419
+ * Historical `maxSpawns` from the last restored checkpoint (set for any
19420
+ * finite checkpoint ceiling, matching or not). Surfaced in
19421
+ * `budgetSnapshot()` so operators can see resume reconciliation without
19422
+ * reading the JSON file; `ceilingMismatch` carries the actual mismatch signal.
19423
+ */
19424
+ checkpointMaxSpawnsAtResume;
19425
+ ceilingMismatchAtResume = false;
19370
19426
  stateCheckpoint;
19371
19427
  sessionWriter;
19372
19428
  manifestTimer = null;
@@ -19470,11 +19526,11 @@ var FleetManager = class {
19470
19526
  * which cap was exceeded. Does NOT throw — the caller decides
19471
19527
  * how to surface the rejection.
19472
19528
  */
19473
- canSpawn(_config) {
19529
+ canSpawn(config) {
19474
19530
  if (this.spawnDepth >= this.maxSpawnDepth) {
19475
19531
  return { kind: "max_spawn_depth", limit: this.maxSpawnDepth, observed: this.spawnDepth };
19476
19532
  }
19477
- if (this.spawnCount >= this.maxSpawns) {
19533
+ if (!config.spawnBudgetExempt && this.spawnCount >= this.maxSpawns) {
19478
19534
  return { kind: "max_spawns", limit: this.maxSpawns, observed: this.spawnCount + 1 };
19479
19535
  }
19480
19536
  if (this.maxFleetCostUsd < Number.POSITIVE_INFINITY) {
@@ -19516,7 +19572,39 @@ var FleetManager = class {
19516
19572
  remainingTokens: Math.max(0, this.maxFleetTokens - usedTokens),
19517
19573
  maxCostUsd: this.maxFleetCostUsd,
19518
19574
  usedCostUsd,
19519
- remainingCostUsd: Math.max(0, this.maxFleetCostUsd - usedCostUsd)
19575
+ remainingCostUsd: Math.max(0, this.maxFleetCostUsd - usedCostUsd),
19576
+ ...this.checkpointMaxSpawnsAtResume !== void 0 ? { checkpointMaxSpawns: this.checkpointMaxSpawnsAtResume } : {},
19577
+ ...this.ceilingMismatchAtResume ? { ceilingMismatch: true } : {}
19578
+ };
19579
+ }
19580
+ /**
19581
+ * Re-attach the on-disk checkpoint after a resume while keeping the live
19582
+ * `maxSpawns` ceiling from construction (profile / flag / env). The
19583
+ * historical `spawnCount` is deliberately NOT restored: the lifetime
19584
+ * budget is scoped to this director run, so a restarted session gets a
19585
+ * fresh budget instead of inheriting a (possibly exhausted) counter from
19586
+ * before the restart. The checkpoint ceiling is rewritten to the live
19587
+ * value so subsequent writes do not reintroduce a stale limit.
19588
+ */
19589
+ restoreFromCheckpoint(snapshot) {
19590
+ const checkpointMax = typeof snapshot.maxSpawns === "number" && Number.isFinite(snapshot.maxSpawns) ? snapshot.maxSpawns : void 0;
19591
+ const ceilingMismatch = checkpointMax !== void 0 && checkpointMax !== this.maxSpawns;
19592
+ this.checkpointMaxSpawnsAtResume = checkpointMax;
19593
+ this.ceilingMismatchAtResume = ceilingMismatch;
19594
+ if (this.stateCheckpoint) {
19595
+ if (snapshot.version === 1 && snapshot.directorRunId && snapshot.updatedAt) {
19596
+ this.stateCheckpoint.resume(snapshot);
19597
+ }
19598
+ this.stateCheckpoint.applyLiveMaxSpawns(
19599
+ Number.isFinite(this.maxSpawns) ? this.maxSpawns : void 0
19600
+ );
19601
+ }
19602
+ return {
19603
+ usedSpawns: this.spawnCount,
19604
+ maxSpawns: this.maxSpawns,
19605
+ remainingSpawns: Math.max(0, this.maxSpawns - this.spawnCount),
19606
+ ...checkpointMax !== void 0 ? { checkpointMaxSpawns: checkpointMax } : {},
19607
+ ceilingMismatch
19520
19608
  };
19521
19609
  }
19522
19610
  setLeaderContextPressure(tokens) {
@@ -19570,8 +19658,10 @@ var FleetManager = class {
19570
19658
  return this._usedNicknames;
19571
19659
  }
19572
19660
  /**
19573
- * Records a spawn: increments counter, stores metadata, updates state checkpoint,
19574
- * and schedules a debounced manifest write. Call AFTER the coordinator
19661
+ * Records a spawn: stores metadata, updates state checkpoint, and schedules
19662
+ * a debounced manifest write. Increments the lifetime spawn counter UNLESS
19663
+ * the config is `spawnBudgetExempt` (ephemeral Chimera reviewer/cascade
19664
+ * spawns do not consume the leader's budget). Call AFTER the coordinator
19575
19665
  * has successfully spawned the subagent.
19576
19666
  *
19577
19667
  * @param subagentId The subagent's id (from coordinator.spawn result)
@@ -19580,7 +19670,9 @@ var FleetManager = class {
19580
19670
  */
19581
19671
  recordSpawn(subagentId, config, priceLookup) {
19582
19672
  this.closing = false;
19583
- this.spawnCount += 1;
19673
+ if (!config.spawnBudgetExempt) {
19674
+ this.spawnCount += 1;
19675
+ }
19584
19676
  this.subagentMeta.set(subagentId, {
19585
19677
  provider: config.provider,
19586
19678
  model: config.model
@@ -26464,6 +26556,9 @@ var KnowledgeGraph = class _KnowledgeGraph {
26464
26556
  };
26465
26557
 
26466
26558
  // src/coordination/task-dag.ts
26559
+ function isTerminalDagStatus(status) {
26560
+ return status === "done" || status === "failed" || status === "skipped";
26561
+ }
26467
26562
  var TaskDAG = class {
26468
26563
  nodes = /* @__PURE__ */ new Map();
26469
26564
  handlers = /* @__PURE__ */ new Set();
@@ -26546,6 +26641,7 @@ var TaskDAG = class {
26546
26641
  complete(id, result) {
26547
26642
  const node = this.nodes.get(id);
26548
26643
  if (!node) return;
26644
+ if (isTerminalDagStatus(node.status)) return;
26549
26645
  node.status = "done";
26550
26646
  node.result = result;
26551
26647
  node.completedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -26571,6 +26667,7 @@ var TaskDAG = class {
26571
26667
  fail(id, error) {
26572
26668
  const node = this.nodes.get(id);
26573
26669
  if (!node) return;
26670
+ if (isTerminalDagStatus(node.status)) return;
26574
26671
  node.status = "failed";
26575
26672
  node.error = error;
26576
26673
  node.completedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -26599,6 +26696,7 @@ var TaskDAG = class {
26599
26696
  skip(id, reason) {
26600
26697
  const node = this.nodes.get(id);
26601
26698
  if (!node) return;
26699
+ if (isTerminalDagStatus(node.status)) return;
26602
26700
  node.status = "skipped";
26603
26701
  node.completedAt = (/* @__PURE__ */ new Date()).toISOString();
26604
26702
  this.invalidateCache();
@@ -26755,6 +26853,9 @@ var TaskDAG = class {
26755
26853
 
26756
26854
  // src/coordination/task-auctioneer.ts
26757
26855
  import { randomUUID as randomUUID18 } from "node:crypto";
26856
+ function isTerminalGoalStatus(status) {
26857
+ return status === "done" || status === "failed";
26858
+ }
26758
26859
  var TaskAuctioneer = class {
26759
26860
  graph;
26760
26861
  fleet;
@@ -26950,6 +27051,7 @@ Priority: ${goal.priority}`,
26950
27051
  async complete(taskId, _result) {
26951
27052
  const goal = this.graph.get(taskId);
26952
27053
  if (!goal) return;
27054
+ if (isTerminalGoalStatus(goal.status)) return;
26953
27055
  const agentId = goal.assignee ?? "unknown";
26954
27056
  this.agentTaskCount(agentId, -1);
26955
27057
  await this.graph.update(taskId, {
@@ -26990,6 +27092,7 @@ ${_result ?? "No result provided."}`
26990
27092
  async fail(taskId, error) {
26991
27093
  const goal = this.graph.get(taskId);
26992
27094
  if (!goal) return;
27095
+ if (isTerminalGoalStatus(goal.status)) return;
26993
27096
  const agentId = goal.assignee ?? "unknown";
26994
27097
  this.agentTaskCount(agentId, -1);
26995
27098
  await this.graph.update(taskId, {
@@ -186,6 +186,7 @@ export declare class DefaultMultiAgentCoordinator extends EventEmitter implement
186
186
  * `awaitTasks()` caller. Pushes the result and fires the event directly.
187
187
  */
188
188
  private emitPendingAborted;
189
+ private trimCompletedResults;
189
190
  private runDispatched;
190
191
  private executeWithTimeout;
191
192
  private recordCompletion;
@@ -90,7 +90,55 @@ export interface FallbackModelDeps {
90
90
  * the fallback chain.
91
91
  */
92
92
  statusTracker?: ProviderModelStatusTracker | undefined;
93
+ /**
94
+ * When set, the fallback chain pauses BEFORE attempting any fallback entry
95
+ * and emits `provider.fallback_pending`. The gate waits for a choice
96
+ * (manual pick or auto-countdown) before proceeding. This lets the UI show
97
+ * a modal with a countdown and manual model selection on every fallback hop.
98
+ *
99
+ * Returns the chosen model reference, or `null` to auto-switch to the next
100
+ * candidate (countdown expired or user accepted the default).
101
+ */
102
+ fallbackGate?: FallbackGateFn | undefined;
103
+ /**
104
+ * Seconds the UI counts down before auto-switching. Default: 7.
105
+ */
106
+ fallbackGateSeconds?: number | undefined;
93
107
  }
108
+ /**
109
+ * Gate function invoked when the fallback chain is about to engage. It should
110
+ * emit `provider.fallback_pending` on the supplied `events` bus — carrying a
111
+ * unique `requestId` and a `timestamp` in the payload — wait for the UI's
112
+ * `provider.fallback_choice` emission echoing that `requestId` (or the
113
+ * countdown), and resolve with the selected model or `null` to accept the
114
+ * default next. See `createFallbackGate` in the CLI wiring for a reference
115
+ * implementation.
116
+ */
117
+ export type FallbackGateFn = (params: {
118
+ events: EventBus;
119
+ sessionId: string | undefined;
120
+ from: {
121
+ providerId: string;
122
+ model: string;
123
+ };
124
+ status: number;
125
+ candidates: Array<{
126
+ providerId: string;
127
+ model: string;
128
+ }>;
129
+ autoSwitchSeconds: number;
130
+ /**
131
+ * Correlation id owned by the caller (the fallback-model extension). The
132
+ * gate MUST echo it in `provider.fallback_pending` so the UI's choice
133
+ * reply and the eventual `provider.fallback` completion event share the
134
+ * same id — clients use it to match the modal to the right gate when
135
+ * parallel requests fail on the same primary.
136
+ */
137
+ requestId: string;
138
+ }) => Promise<{
139
+ providerId: string;
140
+ model: string;
141
+ } | null>;
94
142
  export declare function fallbackProfileChain(config: Config, profileName: string | undefined): string[];
95
143
  export declare function smartDefaultFallbackChain(config: Config): string[];
96
144
  /**
@@ -5,11 +5,12 @@ export { ConversationState, type ReadonlyConversationState, type StateChange, ty
5
5
  export { InputBuilder, type InputBuilderEvent, type InputBuilderOptions, } from './input-builder.js';
6
6
  export { buildBtwBlock, consumeBtwNotes, pendingBtwCount, setBtwNote, } from './btw.js';
7
7
  export { type ContinuationInput, type ContinuationSource, detectContinueIntent, type ResolvedContinuation, resolveContinuation, } from './continue-intent.js';
8
- export { createFallbackModelExtension, effectiveFallbackChain, fallbackProfileChain, smartDefaultFallbackChain, } from './fallback-model.js';
8
+ export { createFallbackModelExtension, effectiveFallbackChain, type FallbackGateFn, type FallbackModelDeps, fallbackProfileChain, smartDefaultFallbackChain, } from './fallback-model.js';
9
9
  export { FallbackProfileManager } from './fallback-profile-manager.js';
10
10
  export { formatModelRef, normalizeModelRef, parseModelRef, type ModelRef, } from './model-ref.js';
11
11
  export { DefaultSystemPromptBuilder, type DefaultSystemPromptBuilderOptions, type SystemBlockSource, } from './system-prompt-builder.js';
12
- export type { SystemInstructionVariant } from './instruction-bundle.js';
12
+ export { type InstructionBundle, type InstructionBundlePaths, loadInstructionBundle, type SystemInstructionVariant, } from './instruction-bundle.js';
13
+ export { type InstructionTemplateContext, renderInstructionLayer, } from './instruction-template.js';
13
14
  export { setQueuedMessagesSnapshot } from './queued-messages.js';
14
15
  export { runProviderWithRetry } from './provider-runner.js';
15
16
  //# sourceMappingURL=index.d.ts.map