@wrongstack/core 0.299.0 → 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 (50) hide show
  1. package/dist/coordination/director.d.ts +8 -0
  2. package/dist/coordination/fleet-manager.d.ts +48 -3
  3. package/dist/coordination/ifleet-manager.d.ts +2 -0
  4. package/dist/coordination/index.js +120 -20
  5. package/dist/coordination/multi-agent-coordinator.d.ts +1 -0
  6. package/dist/core/fallback-model.d.ts +48 -0
  7. package/dist/core/index.d.ts +3 -2
  8. package/dist/core/index.js +226 -26
  9. package/dist/core/instruction-template.d.ts +80 -0
  10. package/dist/core/system-prompt-blocks.d.ts +10 -1
  11. package/dist/core/system-prompt-builder.d.ts +35 -1
  12. package/dist/defaults/index.js +238 -99
  13. package/dist/execution/autonomy-brain.d.ts +7 -0
  14. package/dist/execution/council-brain.d.ts +11 -0
  15. package/dist/execution/council-orchestrator.d.ts +23 -4
  16. package/dist/execution/council-prompts.d.ts +12 -1
  17. package/dist/execution/index.js +355 -138
  18. package/dist/fleet-notifier.d.ts +9 -2
  19. package/dist/hooks/index.js +8 -4
  20. package/dist/hq/index.js +18 -4
  21. package/dist/hq/protocol/fleet.d.ts +20 -0
  22. package/dist/hq/protocol.js +10 -0
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +1512 -707
  25. package/dist/kernel/events/brain-events.d.ts +9 -0
  26. package/dist/kernel/events/provider-events.d.ts +42 -1
  27. package/dist/models/index.js +1 -1
  28. package/dist/plugin/api.d.ts +6 -0
  29. package/dist/plugin/config.d.ts +55 -0
  30. package/dist/plugin/index.d.ts +1 -1
  31. package/dist/plugin/index.js +134 -21
  32. package/dist/security/index.d.ts +1 -1
  33. package/dist/security/index.js +157 -42
  34. package/dist/security/permission-helpers.d.ts +23 -6
  35. package/dist/security/permission-policy.d.ts +16 -0
  36. package/dist/security/totp.d.ts +14 -0
  37. package/dist/storage/director-state.d.ts +7 -0
  38. package/dist/storage/index.js +33 -8
  39. package/dist/tools/fallback-system-config-view-tool.d.ts +1 -1
  40. package/dist/tools/index.js +388 -102
  41. package/dist/types/council.d.ts +11 -0
  42. package/dist/types/index.d.ts +1 -1
  43. package/dist/types/multi-agent.d.ts +10 -0
  44. package/dist/types/one-shot-llm.d.ts +9 -0
  45. package/dist/types/plugin.d.ts +28 -0
  46. package/dist/worktree/index.js +4 -4
  47. package/instructions/system-lite.md +81 -3
  48. package/instructions/system-pro.md +275 -90
  49. package/instructions/system.md +228 -81
  50. package/package.json +3 -3
@@ -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
@@ -6758,16 +6758,21 @@ var BrainMonitor = class {
6758
6758
  this.activeRuns += 1;
6759
6759
  this.lastProgressAt = Date.now();
6760
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.
6761
6771
  this.opts.events.on("agent.run.completed", (e) => {
6762
6772
  const lsid = this.resolveLeaderSessionId();
6763
6773
  if (lsid && e.sessionId && e.sessionId !== lsid) return;
6764
6774
  this.activeRuns = Math.max(0, this.activeRuns - 1);
6765
6775
  }),
6766
- this.opts.events.on("agent.run.error", (e) => {
6767
- const lsid = this.resolveLeaderSessionId();
6768
- if (lsid && e.sessionId && e.sessionId !== lsid) return;
6769
- this.activeRuns = Math.max(0, this.activeRuns - 1);
6770
- }),
6771
6776
  this.opts.events.on("iteration.started", (e) => {
6772
6777
  const lsid = this.resolveLeaderSessionId();
6773
6778
  if (lsid && e.sessionId && e.sessionId !== lsid) return;
@@ -9783,6 +9788,21 @@ var DirectorStateCheckpoint = class {
9783
9788
  resume(snapshot) {
9784
9789
  this.snapshot = snapshot;
9785
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
+ }
9786
9806
  current() {
9787
9807
  return this.snapshot;
9788
9808
  }
@@ -13526,7 +13546,7 @@ async function spawn2(host, config, priceLookup) {
13526
13546
  if (host.spawnDepth >= maxSpawnDepth) {
13527
13547
  throw new FleetSpawnBudgetError("max_spawn_depth", maxSpawnDepth, host.spawnDepth);
13528
13548
  }
13529
- if (host.spawnCount >= host.maxSpawns) {
13549
+ if (host.spawnCount >= host.maxSpawns && !config.spawnBudgetExempt) {
13530
13550
  throw new FleetSpawnBudgetError("max_spawns", host.maxSpawns, host.spawnCount + 1);
13531
13551
  }
13532
13552
  if (host.maxFleetCostUsd < Number.POSITIVE_INFINITY) {
@@ -13577,7 +13597,9 @@ async function spawn2(host, config, priceLookup) {
13577
13597
  ...Number.isFinite(budget?.remainingSpawns ?? host.maxSpawns - host.spawnCount) ? {
13578
13598
  remainingSpawns: Math.max(
13579
13599
  0,
13580
- (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)
13581
13603
  )
13582
13604
  } : {},
13583
13605
  ...Number.isFinite(maxFleetTokens) ? { maxTokens: maxFleetTokens } : {},
@@ -13590,7 +13612,9 @@ async function spawn2(host, config, priceLookup) {
13590
13612
  if (host.fleetManager) {
13591
13613
  host.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
13592
13614
  } else {
13593
- host.spawnCount += 1;
13615
+ if (!config.spawnBudgetExempt) {
13616
+ host.spawnCount += 1;
13617
+ }
13594
13618
  host.subagentMeta.set(result.subagentId, {
13595
13619
  provider: config.provider,
13596
13620
  model: config.model
@@ -14515,8 +14539,17 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
14515
14539
  durationMs: 0
14516
14540
  };
14517
14541
  this.completedResults.push(synthetic);
14542
+ this.trimCompletedResults();
14518
14543
  this.emit("task.completed", { task, result: synthetic });
14519
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
+ }
14520
14553
  async runDispatched(subagentId, task) {
14521
14554
  const subagent = this.subagents.get(subagentId);
14522
14555
  if (!subagent) return;
@@ -14650,12 +14683,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
14650
14683
  }
14651
14684
  recordCompletion(result) {
14652
14685
  this.completedResults.push(result);
14653
- if (this.completedResults.length > _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS) {
14654
- this.completedResults.splice(
14655
- 0,
14656
- this.completedResults.length - _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS
14657
- );
14658
- }
14686
+ this.trimCompletedResults();
14659
14687
  this.totalIterations += result.iterations;
14660
14688
  if (this.inFlight > 0) {
14661
14689
  this.inFlight--;
@@ -19297,6 +19325,7 @@ var Director = class _Director {
19297
19325
  }
19298
19326
  setCheckpointState(snapshot) {
19299
19327
  setCheckpointState(this.checkpointHost(), snapshot);
19328
+ this.applyResumeBudget(snapshot);
19300
19329
  }
19301
19330
  async readSession(subagentId, tail) {
19302
19331
  return readDirectorSubagentSession({
@@ -19348,6 +19377,22 @@ var Director = class _Director {
19348
19377
  }
19349
19378
  resumeFromCheckpoint(snapshot) {
19350
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
+ );
19351
19396
  }
19352
19397
  };
19353
19398
 
@@ -19370,6 +19415,14 @@ var FleetManager = class {
19370
19415
  spawnDepth;
19371
19416
  /** Live spawn counter. */
19372
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;
19373
19426
  stateCheckpoint;
19374
19427
  sessionWriter;
19375
19428
  manifestTimer = null;
@@ -19473,11 +19526,11 @@ var FleetManager = class {
19473
19526
  * which cap was exceeded. Does NOT throw — the caller decides
19474
19527
  * how to surface the rejection.
19475
19528
  */
19476
- canSpawn(_config) {
19529
+ canSpawn(config) {
19477
19530
  if (this.spawnDepth >= this.maxSpawnDepth) {
19478
19531
  return { kind: "max_spawn_depth", limit: this.maxSpawnDepth, observed: this.spawnDepth };
19479
19532
  }
19480
- if (this.spawnCount >= this.maxSpawns) {
19533
+ if (!config.spawnBudgetExempt && this.spawnCount >= this.maxSpawns) {
19481
19534
  return { kind: "max_spawns", limit: this.maxSpawns, observed: this.spawnCount + 1 };
19482
19535
  }
19483
19536
  if (this.maxFleetCostUsd < Number.POSITIVE_INFINITY) {
@@ -19519,7 +19572,39 @@ var FleetManager = class {
19519
19572
  remainingTokens: Math.max(0, this.maxFleetTokens - usedTokens),
19520
19573
  maxCostUsd: this.maxFleetCostUsd,
19521
19574
  usedCostUsd,
19522
- 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
19523
19608
  };
19524
19609
  }
19525
19610
  setLeaderContextPressure(tokens) {
@@ -19573,8 +19658,10 @@ var FleetManager = class {
19573
19658
  return this._usedNicknames;
19574
19659
  }
19575
19660
  /**
19576
- * Records a spawn: increments counter, stores metadata, updates state checkpoint,
19577
- * 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
19578
19665
  * has successfully spawned the subagent.
19579
19666
  *
19580
19667
  * @param subagentId The subagent's id (from coordinator.spawn result)
@@ -19583,7 +19670,9 @@ var FleetManager = class {
19583
19670
  */
19584
19671
  recordSpawn(subagentId, config, priceLookup) {
19585
19672
  this.closing = false;
19586
- this.spawnCount += 1;
19673
+ if (!config.spawnBudgetExempt) {
19674
+ this.spawnCount += 1;
19675
+ }
19587
19676
  this.subagentMeta.set(subagentId, {
19588
19677
  provider: config.provider,
19589
19678
  model: config.model
@@ -26467,6 +26556,9 @@ var KnowledgeGraph = class _KnowledgeGraph {
26467
26556
  };
26468
26557
 
26469
26558
  // src/coordination/task-dag.ts
26559
+ function isTerminalDagStatus(status) {
26560
+ return status === "done" || status === "failed" || status === "skipped";
26561
+ }
26470
26562
  var TaskDAG = class {
26471
26563
  nodes = /* @__PURE__ */ new Map();
26472
26564
  handlers = /* @__PURE__ */ new Set();
@@ -26549,6 +26641,7 @@ var TaskDAG = class {
26549
26641
  complete(id, result) {
26550
26642
  const node = this.nodes.get(id);
26551
26643
  if (!node) return;
26644
+ if (isTerminalDagStatus(node.status)) return;
26552
26645
  node.status = "done";
26553
26646
  node.result = result;
26554
26647
  node.completedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -26574,6 +26667,7 @@ var TaskDAG = class {
26574
26667
  fail(id, error) {
26575
26668
  const node = this.nodes.get(id);
26576
26669
  if (!node) return;
26670
+ if (isTerminalDagStatus(node.status)) return;
26577
26671
  node.status = "failed";
26578
26672
  node.error = error;
26579
26673
  node.completedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -26602,6 +26696,7 @@ var TaskDAG = class {
26602
26696
  skip(id, reason) {
26603
26697
  const node = this.nodes.get(id);
26604
26698
  if (!node) return;
26699
+ if (isTerminalDagStatus(node.status)) return;
26605
26700
  node.status = "skipped";
26606
26701
  node.completedAt = (/* @__PURE__ */ new Date()).toISOString();
26607
26702
  this.invalidateCache();
@@ -26758,6 +26853,9 @@ var TaskDAG = class {
26758
26853
 
26759
26854
  // src/coordination/task-auctioneer.ts
26760
26855
  import { randomUUID as randomUUID18 } from "node:crypto";
26856
+ function isTerminalGoalStatus(status) {
26857
+ return status === "done" || status === "failed";
26858
+ }
26761
26859
  var TaskAuctioneer = class {
26762
26860
  graph;
26763
26861
  fleet;
@@ -26953,6 +27051,7 @@ Priority: ${goal.priority}`,
26953
27051
  async complete(taskId, _result) {
26954
27052
  const goal = this.graph.get(taskId);
26955
27053
  if (!goal) return;
27054
+ if (isTerminalGoalStatus(goal.status)) return;
26956
27055
  const agentId = goal.assignee ?? "unknown";
26957
27056
  this.agentTaskCount(agentId, -1);
26958
27057
  await this.graph.update(taskId, {
@@ -26993,6 +27092,7 @@ ${_result ?? "No result provided."}`
26993
27092
  async fail(taskId, error) {
26994
27093
  const goal = this.graph.get(taskId);
26995
27094
  if (!goal) return;
27095
+ if (isTerminalGoalStatus(goal.status)) return;
26996
27096
  const agentId = goal.assignee ?? "unknown";
26997
27097
  this.agentTaskCount(agentId, -1);
26998
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