@nathapp/nax 0.82.0-canary.6 → 0.82.0-canary.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 (2) hide show
  1. package/dist/nax.js +761 -529
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -22256,7 +22256,8 @@ var init_schemas_infra = __esm(() => {
22256
22256
  });
22257
22257
  FallbackTargetSchema = exports_external.union([
22258
22258
  exports_external.string().min(1),
22259
- exports_external.object({ agent: exports_external.string().min(1), tier: exports_external.string().min(1) })
22259
+ exports_external.object({ agent: exports_external.string().min(1), tier: exports_external.string().min(1) }),
22260
+ exports_external.object({ agent: exports_external.string().min(1), model: exports_external.string().min(1) })
22260
22261
  ]);
22261
22262
  AgentFallbackConfigSchema = exports_external.object({
22262
22263
  enabled: exports_external.boolean().default(false),
@@ -29993,36 +29994,76 @@ function resolveCooldownExpiry(failure, now) {
29993
29994
  var TRANSIENT_COOLDOWN_MS = 60000, POLICIES;
29994
29995
  var init_failure_policy = __esm(() => {
29995
29996
  POLICIES = Object.freeze({
29996
- "fail-auth": { sameAgentRetry: "none", swap: "immediate", cooldown: "run", terminalBackoff: false },
29997
- "fail-quota": { sameAgentRetry: "none", swap: "immediate", cooldown: "run", terminalBackoff: false },
29997
+ "fail-auth": {
29998
+ sameAgentRetry: "none",
29999
+ swap: "immediate",
30000
+ cooldown: "run",
30001
+ cooldownScope: "agent",
30002
+ terminalBackoff: false
30003
+ },
30004
+ "fail-quota": {
30005
+ sameAgentRetry: "none",
30006
+ swap: "immediate",
30007
+ cooldown: "run",
30008
+ cooldownScope: "agent",
30009
+ terminalBackoff: false
30010
+ },
29998
30011
  "fail-rate-limit": {
29999
30012
  sameAgentRetry: "none",
30000
30013
  swap: "immediate",
30001
30014
  cooldown: { ms: TRANSIENT_COOLDOWN_MS },
30015
+ cooldownScope: "model",
30002
30016
  terminalBackoff: true
30003
30017
  },
30004
30018
  "fail-service-down": {
30005
30019
  sameAgentRetry: "adapter-error",
30006
30020
  swap: "immediate",
30007
30021
  cooldown: { ms: TRANSIENT_COOLDOWN_MS },
30022
+ cooldownScope: "model",
30008
30023
  terminalBackoff: true
30009
30024
  },
30010
30025
  "fail-stale": {
30011
30026
  sameAgentRetry: "stale",
30012
30027
  swap: "immediate",
30013
30028
  cooldown: { ms: TRANSIENT_COOLDOWN_MS },
30029
+ cooldownScope: "model",
30014
30030
  terminalBackoff: true
30015
30031
  },
30016
- "fail-timeout": { sameAgentRetry: "timeout", swap: "after-retry-lane", cooldown: "none", terminalBackoff: false },
30032
+ "fail-timeout": {
30033
+ sameAgentRetry: "timeout",
30034
+ swap: "after-retry-lane",
30035
+ cooldown: "none",
30036
+ cooldownScope: "model",
30037
+ terminalBackoff: false
30038
+ },
30017
30039
  "fail-adapter-error": {
30018
30040
  sameAgentRetry: "adapter-error",
30019
30041
  swap: "quality-gated",
30020
30042
  cooldown: "none",
30043
+ cooldownScope: "model",
30044
+ terminalBackoff: false
30045
+ },
30046
+ "fail-quality": {
30047
+ sameAgentRetry: "none",
30048
+ swap: "quality-gated",
30049
+ cooldown: "none",
30050
+ cooldownScope: "model",
30051
+ terminalBackoff: false
30052
+ },
30053
+ "fail-unknown": {
30054
+ sameAgentRetry: "none",
30055
+ swap: "quality-gated",
30056
+ cooldown: "none",
30057
+ cooldownScope: "model",
30021
30058
  terminalBackoff: false
30022
30059
  },
30023
- "fail-quality": { sameAgentRetry: "none", swap: "quality-gated", cooldown: "none", terminalBackoff: false },
30024
- "fail-unknown": { sameAgentRetry: "none", swap: "quality-gated", cooldown: "none", terminalBackoff: false },
30025
- "fail-aborted": { sameAgentRetry: "none", swap: "never", cooldown: "none", terminalBackoff: false }
30060
+ "fail-aborted": {
30061
+ sameAgentRetry: "none",
30062
+ swap: "never",
30063
+ cooldown: "none",
30064
+ cooldownScope: "model",
30065
+ terminalBackoff: false
30066
+ }
30026
30067
  });
30027
30068
  });
30028
30069
 
@@ -30033,43 +30074,81 @@ class CooldownStore {
30033
30074
  constructor(_now) {
30034
30075
  this._now = _now;
30035
30076
  }
30036
- mark(agent, failure) {
30077
+ mark(agent, failure, tier, modelId) {
30037
30078
  const expiresAt = resolveCooldownExpiry(failure, this._now());
30038
30079
  if (expiresAt === null)
30039
30080
  return;
30040
- this._entries.set(agent, { failure, expiresAt });
30081
+ const key = failurePolicyFor(failure.outcome).cooldownScope === "agent" ? agent : identityKey(agent, tier, modelId);
30082
+ this._entries.set(key, { failure, expiresAt });
30041
30083
  }
30042
- isCooling(agent) {
30043
- return this._live(agent) !== undefined;
30084
+ isCooling(agent, tier, modelId) {
30085
+ return this._live(agent, tier, modelId) !== undefined;
30044
30086
  }
30045
- failureFor(agent) {
30046
- return this._live(agent)?.failure;
30087
+ failureFor(agent, tier, modelId) {
30088
+ return this._live(agent, tier, modelId)?.failure;
30047
30089
  }
30048
30090
  sweepTransient() {
30049
- for (const [agent, entry] of this._entries) {
30091
+ for (const [key, entry] of this._entries) {
30050
30092
  if (entry.expiresAt !== "run")
30051
- this._entries.delete(agent);
30093
+ this._entries.delete(key);
30052
30094
  }
30053
30095
  }
30054
30096
  clear() {
30055
30097
  this._entries.clear();
30056
30098
  }
30057
- _live(agent) {
30058
- const entry = this._entries.get(agent);
30099
+ _live(agent, tier, modelId) {
30100
+ const narrow = identityKey(agent, tier, modelId);
30101
+ const own = this._at(narrow);
30102
+ if (own || narrow === agent)
30103
+ return own;
30104
+ const agentWide = this._at(agent);
30105
+ return agentWide && failurePolicyFor(agentWide.failure.outcome).cooldownScope === "agent" ? agentWide : undefined;
30106
+ }
30107
+ _at(key) {
30108
+ const entry = this._entries.get(key);
30059
30109
  if (!entry)
30060
30110
  return;
30061
30111
  if (entry.expiresAt === "run")
30062
30112
  return entry;
30063
30113
  if (entry.expiresAt > this._now())
30064
30114
  return entry;
30065
- this._entries.delete(agent);
30115
+ this._entries.delete(key);
30066
30116
  return;
30067
30117
  }
30068
30118
  }
30119
+ function identityKey(agent, tier, modelId) {
30120
+ if (modelId)
30121
+ return `${agent}::model::${modelId}`;
30122
+ return tier ? `${agent}::${tier}` : agent;
30123
+ }
30069
30124
  var init_cooldown_store = __esm(() => {
30070
30125
  init_failure_policy();
30071
30126
  });
30072
30127
 
30128
+ // src/agents/fallback-model-identity.ts
30129
+ function resolveFallbackModelId(models, agent, tier, defaultAgent) {
30130
+ if (!tier || !models)
30131
+ return;
30132
+ try {
30133
+ const def = resolveModelForAgent(models, agent, tier, defaultAgent);
30134
+ return `${def.provider}/${def.model}`;
30135
+ } catch {
30136
+ return;
30137
+ }
30138
+ }
30139
+ function resolveFallbackDispatchTarget(models, defaultAgent, target) {
30140
+ if (target.model === undefined || !models)
30141
+ return target;
30142
+ const aliased = MODEL_SHORTHAND_TIERS[target.model.toLowerCase()] ?? target.model;
30143
+ const membership = resolveTierMembership(models, target.agent, aliased, defaultAgent);
30144
+ if (membership.isTier)
30145
+ return { agent: target.agent, tier: aliased };
30146
+ return target;
30147
+ }
30148
+ var init_fallback_model_identity = __esm(() => {
30149
+ init_config();
30150
+ });
30151
+
30073
30152
  // src/agents/hop-budget.ts
30074
30153
  function resolveStartAgent(source, primary, fallbackEnabled, storyId, logger) {
30075
30154
  if (!fallbackEnabled || !source.isUnavailable(primary))
@@ -30247,9 +30326,11 @@ function buildCompleteCallPreamble(input) {
30247
30326
  })
30248
30327
  };
30249
30328
  }
30250
- function resolveHopCompleteOptions(options, currentAgent, primaryAgent, tier) {
30329
+ function resolveHopCompleteOptions(options, currentAgent, primaryAgent, tier, model) {
30251
30330
  if (currentAgent === primaryAgent)
30252
30331
  return options;
30332
+ if (model !== undefined)
30333
+ return { ...options, modelDef: resolveModel(model) };
30253
30334
  return { ...options, modelDef: options.modelDefFor?.(currentAgent, tier) ?? options.modelDef };
30254
30335
  }
30255
30336
  function resolveFinalDispatch(options, primaryAgent, fallbacks, finalTier) {
@@ -30324,7 +30405,7 @@ var init_manager_exhaustion = __esm(() => {
30324
30405
 
30325
30406
  // src/agents/retry/hop-retry-policy.ts
30326
30407
  function trySameAgentRetry(result, state, deps) {
30327
- const { staleRetryAttempts, timeoutRetryAttempts, adapterErrorRetries, currentRunOptions, tier } = state;
30408
+ const { staleRetryAttempts, timeoutRetryAttempts, adapterErrorRetries, currentRunOptions, tier, model } = state;
30328
30409
  const { config: config2, requestRunOptions, signal } = deps;
30329
30410
  const outcome = result.adapterFailure?.outcome;
30330
30411
  const lane = outcome ? failurePolicyFor(outcome).sameAgentRetry : "none";
@@ -30335,7 +30416,12 @@ function trySameAgentRetry(result, state, deps) {
30335
30416
  return {
30336
30417
  outcome: "stale-retry",
30337
30418
  staleRetryAttempts: newAttempts,
30338
- kind: { kind: "stale-retry", attempt: newAttempts, ...tier !== undefined ? { tier } : {} },
30419
+ kind: {
30420
+ kind: "stale-retry",
30421
+ attempt: newAttempts,
30422
+ ...tier !== undefined ? { tier } : {},
30423
+ ...model !== undefined ? { model } : {}
30424
+ },
30339
30425
  fallbackRecord: {
30340
30426
  outcome: result.adapterFailure?.outcome ?? "fail-stale",
30341
30427
  category: result.adapterFailure?.category ?? "availability",
@@ -30352,7 +30438,12 @@ function trySameAgentRetry(result, state, deps) {
30352
30438
  return {
30353
30439
  outcome: "timeout-retry",
30354
30440
  timeoutRetryAttempts: newAttempts,
30355
- kind: { kind: "timeout-retry", attempt: newAttempts, ...tier !== undefined ? { tier } : {} },
30441
+ kind: {
30442
+ kind: "timeout-retry",
30443
+ attempt: newAttempts,
30444
+ ...tier !== undefined ? { tier } : {},
30445
+ ...model !== undefined ? { model } : {}
30446
+ },
30356
30447
  currentRunOptions: resolveTimeoutRetryOptions(currentRunOptions, timeoutConfig, config2.execution, requestRunOptions),
30357
30448
  fallbackRecord: {
30358
30449
  outcome: result.adapterFailure?.outcome ?? "fail-timeout",
@@ -30372,7 +30463,12 @@ function trySameAgentRetry(result, state, deps) {
30372
30463
  return {
30373
30464
  outcome: "adapter-error",
30374
30465
  adapterErrorRetries: newAttempts,
30375
- kind: { kind: "stale-retry", attempt: newAttempts, ...tier !== undefined ? { tier } : {} },
30466
+ kind: {
30467
+ kind: "stale-retry",
30468
+ attempt: newAttempts,
30469
+ ...tier !== undefined ? { tier } : {},
30470
+ ...model !== undefined ? { model } : {}
30471
+ },
30376
30472
  fallbackRecord: {
30377
30473
  outcome: result.adapterFailure?.outcome ?? "fail-adapter-error",
30378
30474
  category: result.adapterFailure?.category ?? "availability",
@@ -30447,10 +30543,14 @@ function decideSwap(failure, hopsSoFar, fallback) {
30447
30543
  return { swap: true };
30448
30544
  }
30449
30545
  function normaliseFallbackTarget(value) {
30450
- return typeof value === "string" ? { agent: value } : { agent: value.agent, tier: value.tier };
30546
+ if (typeof value === "string")
30547
+ return { agent: value };
30548
+ if ("tier" in value)
30549
+ return { agent: value.agent, tier: value.tier };
30550
+ return { agent: value.agent, model: value.model };
30451
30551
  }
30452
- function availableCandidates(map2, agent, isExcluded) {
30453
- return (map2?.[agent] ?? []).map(normaliseFallbackTarget).filter((candidate) => !isExcluded(candidate.agent));
30552
+ function availableCandidates(map2, agent, isExcluded, resolve9 = (target) => target) {
30553
+ return (map2?.[agent] ?? []).map(normaliseFallbackTarget).map(resolve9).filter((candidate) => !isExcluded(candidate.agent, candidate.tier));
30454
30554
  }
30455
30555
  function credentialCandidates(map2, primary) {
30456
30556
  const candidates = new Set([primary]);
@@ -30484,7 +30584,11 @@ async function runWithFallback(input) {
30484
30584
  const storyId = request.runOptions.storyId;
30485
30585
  const start = resolveStartAgent(input, primaryAgent, config2.agent?.fallback?.enabled, storyId, logger);
30486
30586
  let currentAgent = start.agent;
30487
- let currentHopKind = "tier" in start ? { kind: "primary", tier: start.tier } : { kind: "primary" };
30587
+ let currentHopKind = {
30588
+ kind: "primary",
30589
+ ..."tier" in start && start.tier !== undefined ? { tier: start.tier } : {},
30590
+ ..."model" in start && start.model !== undefined ? { model: start.model } : {}
30591
+ };
30488
30592
  let hopsSoFar = budget.spent(storyId);
30489
30593
  let rateLimitRetry = 0;
30490
30594
  let staleRetryAttempts = 0;
@@ -30508,7 +30612,14 @@ async function runWithFallback(input) {
30508
30612
  finalStatus = "ok";
30509
30613
  return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
30510
30614
  }
30511
- const retry = trySameAgentRetry(result, { staleRetryAttempts, timeoutRetryAttempts, adapterErrorRetries, currentRunOptions, tier: currentHopKind.tier }, { config: config2, requestRunOptions: request.runOptions, signal: request.signal });
30615
+ const retry = trySameAgentRetry(result, {
30616
+ staleRetryAttempts,
30617
+ timeoutRetryAttempts,
30618
+ adapterErrorRetries,
30619
+ currentRunOptions,
30620
+ tier: currentHopKind.tier,
30621
+ model: currentHopKind.model
30622
+ }, { config: config2, requestRunOptions: request.runOptions, signal: request.signal });
30512
30623
  if (retry) {
30513
30624
  ({ staleRetryAttempts, timeoutRetryAttempts, adapterErrorRetries, currentRunOptions } = applyRetry(retry, {
30514
30625
  staleRetryAttempts,
@@ -30556,8 +30667,9 @@ async function runWithFallback(input) {
30556
30667
  return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
30557
30668
  }
30558
30669
  const failure = result.adapterFailure ?? unknownFailure();
30559
- input.markUnavailable(currentAgent, failure);
30560
- const next = input.nextCandidate(primaryAgent, hopsSoFar, currentAgent);
30670
+ const currentTier = currentHopKind.tier;
30671
+ input.markUnavailable(currentAgent, failure, currentTier);
30672
+ const next = input.nextCandidate(primaryAgent, hopsSoFar, currentAgent, currentTier);
30561
30673
  if (!next) {
30562
30674
  const outcome = await input.resolveExhaustion({
30563
30675
  failure,
@@ -30580,7 +30692,12 @@ async function runWithFallback(input) {
30580
30692
  hopsSoFar = budget.spend(storyId, hopsSoFar);
30581
30693
  rateLimitRetry = 0;
30582
30694
  currentBundle = updatedBundle;
30583
- currentHopKind = { kind: "swap", failure, ...next.tier ? { tier: next.tier } : {} };
30695
+ currentHopKind = {
30696
+ kind: "swap",
30697
+ failure,
30698
+ ...next.tier ? { tier: next.tier } : {},
30699
+ ...next.model ? { model: next.model } : {}
30700
+ };
30584
30701
  const fallback = buildFallbackRecord({
30585
30702
  storyId,
30586
30703
  priorAgent: currentAgent,
@@ -30780,6 +30897,7 @@ class AgentManager {
30780
30897
  _dispatchEvents;
30781
30898
  _pidRegistry;
30782
30899
  _retryStrategy;
30900
+ _models;
30783
30901
  events;
30784
30902
  constructor(config2, registry3, opts) {
30785
30903
  this._config = config2;
@@ -30792,6 +30910,7 @@ class AgentManager {
30792
30910
  this._runHop = opts?.runHop;
30793
30911
  this._dispatchEvents = opts?.dispatchEvents ?? new DispatchEventBus;
30794
30912
  this._retryStrategy = opts?.retryStrategy ?? defaultRetryStrategy;
30913
+ this._models = opts?.models;
30795
30914
  this.events = {
30796
30915
  on: (event, listener) => {
30797
30916
  this._emitter.on(event, listener);
@@ -30818,12 +30937,12 @@ class AgentManager {
30818
30937
  return fromAgent;
30819
30938
  return "claude";
30820
30939
  }
30821
- isUnavailable(agent) {
30822
- return this._cooldowns.isCooling(agent);
30940
+ isUnavailable(agent, tier) {
30941
+ return this._cooldowns.isCooling(agent, tier, resolveFallbackModelId(this._models, agent, tier, this.getDefault()));
30823
30942
  }
30824
- markUnavailable(agent, reason) {
30825
- this._cooldowns.mark(agent, reason);
30826
- this._emitter.emit("onAgentUnavailable", { agent, failure: reason });
30943
+ markUnavailable(agent, reason, tier) {
30944
+ this._cooldowns.mark(agent, reason, tier, resolveFallbackModelId(this._models, agent, tier, this.getDefault()));
30945
+ this._emitter.emit("onAgentUnavailable", { agent, tier, failure: reason });
30827
30946
  }
30828
30947
  reset() {
30829
30948
  this._cooldowns.clear();
@@ -30855,16 +30974,18 @@ class AgentManager {
30855
30974
  this._prunedFallback.add(name);
30856
30975
  }
30857
30976
  }
30858
- _isExcluded = (c) => this._prunedFallback.has(c) || this.isUnavailable(c);
30977
+ _isExcluded = (c, t) => this._prunedFallback.has(c) || this.isUnavailable(c, t);
30978
+ _resolveTarget = (t) => resolveFallbackDispatchTarget(this._models, this.getDefault(), t);
30859
30979
  resolveFallbackChain(agent, _failure) {
30860
- return availableCandidates(this._config.agent?.fallback?.map, agent, this._isExcluded);
30980
+ return availableCandidates(this._config.agent?.fallback?.map, agent, this._isExcluded, this._resolveTarget);
30861
30981
  }
30862
30982
  shouldSwap(failure, hopsSoFar) {
30863
30983
  return decideSwap(failure, hopsSoFar, this._config.agent?.fallback).swap;
30864
30984
  }
30865
- nextCandidate(current, _hopsSoFar, exclude) {
30866
- const excluded = (candidate) => candidate === exclude || this._isExcluded(candidate);
30867
- return availableCandidates(this._config.agent?.fallback?.map, current, excluded)[0] ?? null;
30985
+ nextCandidate(current, _hopsSoFar, exclude, excludeTier) {
30986
+ const excluded = (c, t) => c === exclude && t === excludeTier || this._isExcluded(c, t);
30987
+ const map2 = this._config.agent?.fallback?.map;
30988
+ return availableCandidates(map2, current, excluded, this._resolveTarget)[0] ?? null;
30868
30989
  }
30869
30990
  async runWithFallback(request, primaryAgentOverride) {
30870
30991
  return runWithFallback({
@@ -30876,9 +30997,9 @@ class AgentManager {
30876
30997
  dispatchEvents: this._dispatchEvents,
30877
30998
  logger: this._loggerOverride ?? getSafeLogger(),
30878
30999
  getDefault: () => this.getDefault(),
30879
- isUnavailable: (agent) => this.isUnavailable(agent),
30880
- markUnavailable: (agent, failure) => this.markUnavailable(agent, failure),
30881
- nextCandidate: (current, hops, exclude) => this.nextCandidate(current, hops, exclude),
31000
+ isUnavailable: (agent, tier) => this.isUnavailable(agent, tier),
31001
+ markUnavailable: (agent, failure, tier) => this.markUnavailable(agent, failure, tier),
31002
+ nextCandidate: (current, hops, exclude, excludeTier) => this.nextCandidate(current, hops, exclude, excludeTier),
30882
31003
  resolveExhaustion: (options) => this._resolveExhaustion(options),
30883
31004
  emitSwapAttempt: (fallback) => this._emitter.emit("onSwapAttempt", fallback)
30884
31005
  });
@@ -30889,6 +31010,7 @@ class AgentManager {
30889
31010
  const primaryAgent = primaryAgentOverride ?? this.getDefault();
30890
31011
  let currentAgent = primaryAgent;
30891
31012
  let currentTier;
31013
+ let currentModel;
30892
31014
  let hopsSoFar = this._budget.spent(options.storyId);
30893
31015
  let staleRetryAttempts = 0;
30894
31016
  let rateLimitRetry = 0;
@@ -30899,7 +31021,7 @@ class AgentManager {
30899
31021
  let _totalCostUsd = 0;
30900
31022
  try {
30901
31023
  while (true) {
30902
- const hopOptions = resolveHopCompleteOptions(options, currentAgent, primaryAgent, currentTier);
31024
+ const hopOptions = resolveHopCompleteOptions(options, currentAgent, primaryAgent, currentTier, currentModel);
30903
31025
  const adapter = this._resolveRegistry().getAgent(currentAgent);
30904
31026
  if (!adapter) {
30905
31027
  _finalStatus = "error";
@@ -30994,8 +31116,8 @@ class AgentManager {
30994
31116
  _finalStatus = hopsSoFar > 0 ? "exhausted" : "error";
30995
31117
  return { result, fallbacks, ...currentTier !== undefined ? { finalTier: currentTier } : {} };
30996
31118
  }
30997
- this.markUnavailable(currentAgent, result.adapterFailure);
30998
- const next = this.nextCandidate(primaryAgent, hopsSoFar, currentAgent);
31119
+ this.markUnavailable(currentAgent, result.adapterFailure, currentTier);
31120
+ const next = this.nextCandidate(primaryAgent, hopsSoFar, currentAgent, currentTier);
30999
31121
  if (!next) {
31000
31122
  const outcome = await this._resolveExhaustion({
31001
31123
  failure: result.adapterFailure,
@@ -31037,7 +31159,7 @@ class AgentManager {
31037
31159
  hop: hopsSoFar
31038
31160
  });
31039
31161
  _agentChain.push(next.agent);
31040
- [currentAgent, currentTier] = [next.agent, next.tier];
31162
+ [currentAgent, currentTier, currentModel] = [next.agent, next.tier, next.model];
31041
31163
  }
31042
31164
  } finally {
31043
31165
  this._dispatchEvents.emitOperationCompleted({
@@ -31218,6 +31340,7 @@ var init_manager = __esm(() => {
31218
31340
  init_bun_deps();
31219
31341
  init_complete_exception_classifier();
31220
31342
  init_cooldown_store();
31343
+ init_fallback_model_identity();
31221
31344
  init_manager_dispatch();
31222
31345
  init_manager_exhaustion();
31223
31346
  init_manager_run_fallback();
@@ -45070,6 +45193,292 @@ function computePollutionMetrics(manifests) {
45070
45193
  };
45071
45194
  }
45072
45195
 
45196
+ // src/runtime/cost-aggregator.ts
45197
+ import { mkdirSync as mkdirSync2 } from "fs";
45198
+ import { join as join22 } from "path";
45199
+ function totalSpendUsd(snap) {
45200
+ return snap.totalCostUsd + snap.totalErrorCostUsd;
45201
+ }
45202
+ function storySpendUsd(costAggregator, storyId, fallbackUsd) {
45203
+ const snap = costAggregator?.byStory()[storyId];
45204
+ if (snap === undefined)
45205
+ return { cost: fallbackUsd, errorCostUsd: 0 };
45206
+ return { cost: totalSpendUsd(snap), errorCostUsd: snap.totalErrorCostUsd };
45207
+ }
45208
+ function makeCorrelationId() {
45209
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
45210
+ }
45211
+ function createNoOpCostAggregator() {
45212
+ return {
45213
+ record() {},
45214
+ recordError() {},
45215
+ recordOperationSummary() {},
45216
+ snapshot() {
45217
+ return EMPTY_SNAPSHOT;
45218
+ },
45219
+ byAgent() {
45220
+ return {};
45221
+ },
45222
+ byStage() {
45223
+ return {};
45224
+ },
45225
+ byStory() {
45226
+ return {};
45227
+ },
45228
+ byCall() {
45229
+ return {};
45230
+ },
45231
+ byScope() {
45232
+ return {};
45233
+ },
45234
+ openScope(scopeId) {
45235
+ return {
45236
+ scopeId: scopeId ?? makeCorrelationId(),
45237
+ snapshot: () => EMPTY_SNAPSHOT,
45238
+ close: () => {}
45239
+ };
45240
+ },
45241
+ async drain() {}
45242
+ };
45243
+ }
45244
+ function emptySnap() {
45245
+ return {
45246
+ totalCostUsd: 0,
45247
+ totalEstimatedCostUsd: 0,
45248
+ totalExactCostUsd: 0,
45249
+ totalInputTokens: 0,
45250
+ totalOutputTokens: 0,
45251
+ totalCacheReadTokens: 0,
45252
+ totalCacheWriteTokens: 0,
45253
+ tokenUsageCount: 0,
45254
+ callCount: 0,
45255
+ errorCount: 0,
45256
+ totalErrorCostUsd: 0
45257
+ };
45258
+ }
45259
+ function accumulate(snap, e) {
45260
+ const tokens = e.tokens;
45261
+ return {
45262
+ totalCostUsd: snap.totalCostUsd + e.costUsd,
45263
+ totalEstimatedCostUsd: snap.totalEstimatedCostUsd + e.estimatedCostUsd,
45264
+ totalExactCostUsd: snap.totalExactCostUsd + e.exactCostUsd,
45265
+ totalInputTokens: snap.totalInputTokens + (tokens?.input ?? 0),
45266
+ totalOutputTokens: snap.totalOutputTokens + (tokens?.output ?? 0),
45267
+ totalCacheReadTokens: (snap.totalCacheReadTokens ?? 0) + (tokens?.cacheRead ?? 0),
45268
+ totalCacheWriteTokens: (snap.totalCacheWriteTokens ?? 0) + (tokens?.cacheWrite ?? 0),
45269
+ tokenUsageCount: (snap.tokenUsageCount ?? 0) + (tokens ? 1 : 0),
45270
+ callCount: snap.callCount + 1,
45271
+ errorCount: snap.errorCount,
45272
+ totalErrorCostUsd: snap.totalErrorCostUsd
45273
+ };
45274
+ }
45275
+ function accumulateError(snap, e) {
45276
+ return {
45277
+ ...snap,
45278
+ errorCount: snap.errorCount + 1,
45279
+ totalErrorCostUsd: snap.totalErrorCostUsd + (e?.costUsd ?? 0)
45280
+ };
45281
+ }
45282
+
45283
+ class CostAggregator {
45284
+ _runId;
45285
+ _drainDir;
45286
+ _events = [];
45287
+ _errors = [];
45288
+ _draining = false;
45289
+ _inFlightEvents = [];
45290
+ _inFlightErrors = [];
45291
+ _openScopes = new Set;
45292
+ constructor(_runId, _drainDir) {
45293
+ this._runId = _runId;
45294
+ this._drainDir = _drainDir;
45295
+ }
45296
+ record(event) {
45297
+ if (this._draining) {
45298
+ this._inFlightEvents.push(event);
45299
+ return;
45300
+ }
45301
+ this._events.push(event);
45302
+ }
45303
+ recordError(event) {
45304
+ if (this._draining) {
45305
+ this._inFlightErrors.push(event);
45306
+ return;
45307
+ }
45308
+ this._errors.push(event);
45309
+ }
45310
+ recordOperationSummary(_event) {}
45311
+ snapshot() {
45312
+ const allEvents = [...this._events, ...this._inFlightEvents];
45313
+ const allErrors = [...this._errors, ...this._inFlightErrors];
45314
+ const eventReduced = allEvents.reduce(accumulate, emptySnap());
45315
+ return allErrors.reduce((snap, e) => accumulateError(snap, e), eventReduced);
45316
+ }
45317
+ byAgent() {
45318
+ const m = {};
45319
+ for (const e of this._events)
45320
+ m[e.agentName] = accumulate(m[e.agentName] ?? emptySnap(), e);
45321
+ for (const e of this._inFlightEvents)
45322
+ m[e.agentName] = accumulate(m[e.agentName] ?? emptySnap(), e);
45323
+ for (const e of this._errors)
45324
+ m[e.agentName] = accumulateError(m[e.agentName] ?? emptySnap(), e);
45325
+ for (const e of this._inFlightErrors)
45326
+ m[e.agentName] = accumulateError(m[e.agentName] ?? emptySnap(), e);
45327
+ return m;
45328
+ }
45329
+ byStage() {
45330
+ const m = {};
45331
+ for (const e of this._events) {
45332
+ const k = e.stage ?? "unknown";
45333
+ m[k] = accumulate(m[k] ?? emptySnap(), e);
45334
+ }
45335
+ for (const e of this._inFlightEvents) {
45336
+ const k = e.stage ?? "unknown";
45337
+ m[k] = accumulate(m[k] ?? emptySnap(), e);
45338
+ }
45339
+ for (const e of this._errors) {
45340
+ const k = e.stage ?? "unknown";
45341
+ m[k] = accumulateError(m[k] ?? emptySnap(), e);
45342
+ }
45343
+ for (const e of this._inFlightErrors) {
45344
+ const k = e.stage ?? "unknown";
45345
+ m[k] = accumulateError(m[k] ?? emptySnap(), e);
45346
+ }
45347
+ return m;
45348
+ }
45349
+ byStory() {
45350
+ const m = {};
45351
+ for (const e of this._events) {
45352
+ const k = e.storyId ?? "unknown";
45353
+ m[k] = accumulate(m[k] ?? emptySnap(), e);
45354
+ }
45355
+ for (const e of this._inFlightEvents) {
45356
+ const k = e.storyId ?? "unknown";
45357
+ m[k] = accumulate(m[k] ?? emptySnap(), e);
45358
+ }
45359
+ for (const e of this._errors) {
45360
+ const k = e.storyId ?? "unknown";
45361
+ m[k] = accumulateError(m[k] ?? emptySnap(), e);
45362
+ }
45363
+ for (const e of this._inFlightErrors) {
45364
+ const k = e.storyId ?? "unknown";
45365
+ m[k] = accumulateError(m[k] ?? emptySnap(), e);
45366
+ }
45367
+ return m;
45368
+ }
45369
+ byCall() {
45370
+ const m = {};
45371
+ for (const e of [...this._events, ...this._inFlightEvents]) {
45372
+ if (e.callId === undefined)
45373
+ continue;
45374
+ m[e.callId] = accumulate(m[e.callId] ?? emptySnap(), e);
45375
+ }
45376
+ for (const e of [...this._errors, ...this._inFlightErrors]) {
45377
+ if (e.callId === undefined)
45378
+ continue;
45379
+ m[e.callId] = accumulateError(m[e.callId] ?? emptySnap(), e);
45380
+ }
45381
+ return m;
45382
+ }
45383
+ byScope() {
45384
+ const m = {};
45385
+ for (const e of [...this._events, ...this._inFlightEvents]) {
45386
+ if (e.scopeId === undefined)
45387
+ continue;
45388
+ m[e.scopeId] = accumulate(m[e.scopeId] ?? emptySnap(), e);
45389
+ }
45390
+ for (const e of [...this._errors, ...this._inFlightErrors]) {
45391
+ if (e.scopeId === undefined)
45392
+ continue;
45393
+ m[e.scopeId] = accumulateError(m[e.scopeId] ?? emptySnap(), e);
45394
+ }
45395
+ return m;
45396
+ }
45397
+ openScope(scopeId) {
45398
+ const id = scopeId ?? _costAggDeps.newCorrelationId();
45399
+ this._openScopes.add(id);
45400
+ let closed = false;
45401
+ return {
45402
+ scopeId: id,
45403
+ snapshot: () => {
45404
+ const matching = [...this._events, ...this._inFlightEvents].filter((e) => e.scopeId === id);
45405
+ const matchingErrors = [...this._errors, ...this._inFlightErrors].filter((e) => e.scopeId === id);
45406
+ const eventSnapshot = matching.reduce(accumulate, emptySnap());
45407
+ return matchingErrors.reduce((snap, e) => accumulateError(snap, e), eventSnapshot);
45408
+ },
45409
+ close: () => {
45410
+ if (closed)
45411
+ return;
45412
+ closed = true;
45413
+ this._openScopes.delete(id);
45414
+ }
45415
+ };
45416
+ }
45417
+ async drain() {
45418
+ const openScopeCount = this._openScopes.size;
45419
+ if (openScopeCount > 0) {
45420
+ _costAggDeps.getSafeLogger()?.warn("cost-aggregator", "drain called with open scopes", { openScopeCount });
45421
+ }
45422
+ this._draining = true;
45423
+ try {
45424
+ let committed = [...this._events.splice(0), ...this._errors.splice(0)];
45425
+ if (committed.length === 0 && this._inFlightEvents.length === 0 && this._inFlightErrors.length === 0) {
45426
+ return;
45427
+ }
45428
+ mkdirSync2(this._drainDir, { recursive: true });
45429
+ const path5 = join22(this._drainDir, `${this._runId}.jsonl`);
45430
+ let first = true;
45431
+ let pass = 0;
45432
+ while (first || this._inFlightEvents.length > 0 || this._inFlightErrors.length > 0) {
45433
+ if (++pass > MAX_DRAIN_PASSES) {
45434
+ _costAggDeps.getSafeLogger()?.warn("cost-aggregator", "drain exceeded max passes \u2014 late events keep arriving", {
45435
+ pass,
45436
+ inFlightEvents: this._inFlightEvents.length,
45437
+ inFlightErrors: this._inFlightErrors.length
45438
+ });
45439
+ break;
45440
+ }
45441
+ first = false;
45442
+ const late = [...this._inFlightEvents.splice(0), ...this._inFlightErrors.splice(0)];
45443
+ committed = [...committed, ...late].sort((a, b) => a.ts - b.ts);
45444
+ await _costAggDeps.write(path5, `${committed.map((e) => JSON.stringify(e)).join(`
45445
+ `)}
45446
+ `);
45447
+ }
45448
+ const committedEvents = committed.filter((e) => !(("kind" in e) && e.kind === "error"));
45449
+ const committedErrors = committed.filter((e) => ("kind" in e) && e.kind === "error");
45450
+ this._events.length = 0;
45451
+ this._events.push(...committedEvents);
45452
+ this._errors.length = 0;
45453
+ this._errors.push(...committedErrors);
45454
+ } finally {
45455
+ this._draining = false;
45456
+ }
45457
+ }
45458
+ }
45459
+ var MAX_DRAIN_PASSES = 20, EMPTY_SNAPSHOT, _costAggDeps;
45460
+ var init_cost_aggregator = __esm(() => {
45461
+ init_logger2();
45462
+ EMPTY_SNAPSHOT = {
45463
+ totalCostUsd: 0,
45464
+ totalEstimatedCostUsd: 0,
45465
+ totalExactCostUsd: 0,
45466
+ totalInputTokens: 0,
45467
+ totalOutputTokens: 0,
45468
+ totalCacheReadTokens: 0,
45469
+ totalCacheWriteTokens: 0,
45470
+ tokenUsageCount: 0,
45471
+ callCount: 0,
45472
+ errorCount: 0,
45473
+ totalErrorCostUsd: 0
45474
+ };
45475
+ _costAggDeps = {
45476
+ write: (path5, data) => Bun.write(path5, data),
45477
+ getSafeLogger: () => getSafeLogger(),
45478
+ newCorrelationId: makeCorrelationId
45479
+ };
45480
+ });
45481
+
45073
45482
  // src/utils/file-lock.ts
45074
45483
  import { readFile as readFile6, stat as stat4, unlink as unlink3, writeFile as writeFile5 } from "fs/promises";
45075
45484
  function unlinkQuiet(path5) {
@@ -45382,7 +45791,7 @@ async function collectStoryMetrics(ctx, storyStartTime) {
45382
45791
  attempts,
45383
45792
  finalTier,
45384
45793
  success: agentResult?.success || false,
45385
- cost: costSnapshot?.totalCostUsd ?? 0,
45794
+ cost: costSnapshot !== undefined ? totalSpendUsd(costSnapshot) : 0,
45386
45795
  durationMs: agentResult?.durationMs || 0,
45387
45796
  firstPassSuccess,
45388
45797
  startedAt: storyStartTime,
@@ -45393,7 +45802,8 @@ async function collectStoryMetrics(ctx, storyStartTime) {
45393
45802
  reviewsFailedOpen: ctx.reviewsFailedOpen ?? 0,
45394
45803
  ...tokens !== undefined ? { tokens, tokenAttribution: "direct" } : {},
45395
45804
  ...contextMetrics !== undefined && { context: contextMetrics },
45396
- ...fallbackHops.length > 0 && { fallback: { hops: fallbackHops } }
45805
+ ...fallbackHops.length > 0 && { fallback: { hops: fallbackHops } },
45806
+ ...costSnapshot?.totalErrorCostUsd > 0 ? { errorCostUsd: costSnapshot.totalErrorCostUsd } : {}
45397
45807
  };
45398
45808
  }
45399
45809
  function collectBatchMetrics(ctx, storyStartTime) {
@@ -45401,9 +45811,8 @@ function collectBatchMetrics(ctx, storyStartTime) {
45401
45811
  const routing = ctx.routing;
45402
45812
  const agentResult = ctx.agentResult;
45403
45813
  const batchSnapshot = ctx.runtime.costAggregator.byStory()[ctx.story.id];
45404
- const batchTotal = batchSnapshot?.totalCostUsd ?? 0;
45814
+ const errorCostPerStory = (batchSnapshot?.totalErrorCostUsd ?? 0) / stories.length;
45405
45815
  const totalDuration = agentResult?.durationMs || 0;
45406
- const costPerStory = batchTotal / stories.length;
45407
45816
  const durationPerStory = totalDuration / stories.length;
45408
45817
  const tokensPerStory = tokensFromSnapshot(batchSnapshot, stories.length);
45409
45818
  const batchAgentUsed = routing.agent ?? ctx.agentManager?.getDefault() ?? resolveDefaultAgent(ctx.config);
@@ -45425,7 +45834,8 @@ function collectBatchMetrics(ctx, storyStartTime) {
45425
45834
  attempts: 1,
45426
45835
  finalTier: routing.modelTier,
45427
45836
  success: true,
45428
- cost: costPerStory,
45837
+ cost: (batchSnapshot !== undefined ? totalSpendUsd(batchSnapshot) : 0) / stories.length,
45838
+ ...errorCostPerStory > 0 ? { errorCostUsd: errorCostPerStory } : {},
45429
45839
  durationMs: durationPerStory,
45430
45840
  firstPassSuccess: true,
45431
45841
  startedAt: storyStartTime,
@@ -45515,6 +45925,7 @@ var init_tracker = __esm(() => {
45515
45925
  init_schema();
45516
45926
  init_manifest_store();
45517
45927
  init_logger2();
45928
+ init_cost_aggregator();
45518
45929
  init_json_file();
45519
45930
  init_path_file_lock();
45520
45931
  });
@@ -45807,7 +46218,7 @@ var init_static_rules_budget_notice = __esm(() => {
45807
46218
 
45808
46219
  // src/context/engine/providers/static-rules.ts
45809
46220
  import { createHash as createHash11 } from "crypto";
45810
- import { join as join22, relative as relative11 } from "path";
46221
+ import { join as join23, relative as relative11 } from "path";
45811
46222
  function contentHash87(content) {
45812
46223
  return createHash11("sha256").update(content).digest("hex").slice(0, 8);
45813
46224
  }
@@ -46119,7 +46530,7 @@ ${section.content}`;
46119
46530
  const existingCandidates = [];
46120
46531
  for (const fileName of LEGACY_CANDIDATE_FILES) {
46121
46532
  try {
46122
- if (await _staticRulesDeps.fileExists(join22(rootDir, fileName))) {
46533
+ if (await _staticRulesDeps.fileExists(join23(rootDir, fileName))) {
46123
46534
  existingCandidates.push(fileName);
46124
46535
  }
46125
46536
  } catch {}
@@ -46135,11 +46546,11 @@ ${section.content}`;
46135
46546
  for (const fileName of LEGACY_CANDIDATE_FILES) {
46136
46547
  legacySources.push({
46137
46548
  sourceId: fileName,
46138
- filePath: join22(rootDir, fileName),
46549
+ filePath: join23(rootDir, fileName),
46139
46550
  heading: fileName
46140
46551
  });
46141
46552
  }
46142
- const rulesDir = join22(rootDir, LEGACY_RULES_DIR);
46553
+ const rulesDir = join23(rootDir, LEGACY_RULES_DIR);
46143
46554
  const nestedRulePaths = _staticRulesDeps.globInDir(rulesDir);
46144
46555
  for (const filePath of nestedRulePaths) {
46145
46556
  const normalized = normalizePath2(filePath);
@@ -46202,7 +46613,7 @@ var init_static_rules = __esm(() => {
46202
46613
  fileExists: async (path6) => Bun.file(path6).exists(),
46203
46614
  globInDir: (dir) => {
46204
46615
  try {
46205
- return [...new Bun.Glob("**/*.md").scanSync({ cwd: dir, absolute: false })].sort().map((f) => join22(dir, f));
46616
+ return [...new Bun.Glob("**/*.md").scanSync({ cwd: dir, absolute: false })].sort().map((f) => join23(dir, f));
46206
46617
  } catch {
46207
46618
  return [];
46208
46619
  }
@@ -46570,7 +46981,7 @@ var init_provider_weights_cache = __esm(() => {
46570
46981
  });
46571
46982
 
46572
46983
  // src/context/engine/providers/plugin-loader.ts
46573
- import { isAbsolute as isAbsolute8, join as join23, resolve as resolve15 } from "path";
46984
+ import { isAbsolute as isAbsolute8, join as join24, resolve as resolve15 } from "path";
46574
46985
  function isInitialisable(p) {
46575
46986
  return typeof p.init === "function";
46576
46987
  }
@@ -46598,7 +47009,7 @@ function resolveModuleSpecifier(specifier, workdir) {
46598
47009
  }
46599
47010
  if (specifier.startsWith("./") || specifier.startsWith("../")) {
46600
47011
  const resolvedWorkdir = resolve15(workdir);
46601
- const resolved = resolve15(join23(workdir, specifier));
47012
+ const resolved = resolve15(join24(workdir, specifier));
46602
47013
  if (resolved !== resolvedWorkdir && !resolved.startsWith(`${resolvedWorkdir}/`)) {
46603
47014
  throw new Error(`Plugin module path escapes project workdir: "${specifier}" resolves to "${resolved}" (workdir: "${resolvedWorkdir}")`);
46604
47015
  }
@@ -46740,7 +47151,7 @@ var init_plugin_cache = __esm(() => {
46740
47151
 
46741
47152
  // src/context/engine/stage-assembler.ts
46742
47153
  import { readdir as readdir2 } from "fs/promises";
46743
- import { isAbsolute as isAbsolute9, join as join24, resolve as resolve16 } from "path";
47154
+ import { isAbsolute as isAbsolute9, join as join25, resolve as resolve16 } from "path";
46744
47155
  function dedupeScratchDirs(dirs) {
46745
47156
  return [...new Set(dirs.filter((dir) => Boolean(dir)))];
46746
47157
  }
@@ -46751,7 +47162,7 @@ async function discoverSessionScratchDirsOnDisk(projectDir, featureName, storyId
46751
47162
  const logger = getLogger();
46752
47163
  let sessionsRoot;
46753
47164
  try {
46754
- sessionsRoot = join24(featureDir(projectDir, featureName), "sessions");
47165
+ sessionsRoot = join25(featureDir(projectDir, featureName), "sessions");
46755
47166
  } catch (err) {
46756
47167
  logger.debug("context-v2", "discoverSessionScratchDirsOnDisk: invalid featureName \u2014 treating as no sessions", {
46757
47168
  storyId,
@@ -46769,7 +47180,7 @@ async function discoverSessionScratchDirsOnDisk(projectDir, featureName, storyId
46769
47180
  const cutoff = _stageAssemblerDeps.now() - ttlMs;
46770
47181
  const found = [];
46771
47182
  for (const entry of entries) {
46772
- const descriptorPath = join24(sessionsRoot, entry, "descriptor.json");
47183
+ const descriptorPath = join25(sessionsRoot, entry, "descriptor.json");
46773
47184
  try {
46774
47185
  const parsed = await _stageAssemblerDeps.readDescriptor(descriptorPath);
46775
47186
  if (!parsed || parsed.storyId !== storyId || !parsed.scratchDir)
@@ -47148,7 +47559,7 @@ var init_feature_context_filter = __esm(() => {
47148
47559
  });
47149
47560
 
47150
47561
  // src/context/feature-resolver.ts
47151
- import { join as join25 } from "path";
47562
+ import { join as join26 } from "path";
47152
47563
  var {Glob: Glob2 } = globalThis.Bun;
47153
47564
  function disposeFeatureResolver(workdir) {
47154
47565
  if (workdir !== undefined) {
@@ -47200,7 +47611,7 @@ async function buildIndex(workdir) {
47200
47611
  }
47201
47612
  async function tryResolveFromActiveFeature(story, workdir, activeFeature) {
47202
47613
  const logger = getLogger();
47203
- const prdPath = join25(featureDir(workdir, activeFeature), "prd.json");
47614
+ const prdPath = join26(featureDir(workdir, activeFeature), "prd.json");
47204
47615
  try {
47205
47616
  const raw = await _resolverDeps2.readFile(prdPath);
47206
47617
  const prd = JSON.parse(raw);
@@ -47252,7 +47663,7 @@ var init_feature_resolver = __esm(() => {
47252
47663
 
47253
47664
  // src/context/greenfield.ts
47254
47665
  import { readdir as readdir3 } from "fs/promises";
47255
- import { join as join26, relative as relative13 } from "path";
47666
+ import { join as join27, relative as relative13 } from "path";
47256
47667
  async function gitLsFiles2(workdir) {
47257
47668
  try {
47258
47669
  const proc = _greenfieldDeps.spawn(["git", "ls-files"], {
@@ -47280,7 +47691,7 @@ async function* walkFiles(root) {
47280
47691
  for (const entry of entries) {
47281
47692
  if (entry.isSymbolicLink())
47282
47693
  continue;
47283
- const full = join26(dir, entry.name);
47694
+ const full = join27(dir, entry.name);
47284
47695
  if (entry.isDirectory()) {
47285
47696
  if (IGNORE_DIRS.has(entry.name))
47286
47697
  continue;
@@ -47347,7 +47758,7 @@ var init_greenfield = __esm(() => {
47347
47758
  });
47348
47759
 
47349
47760
  // src/context/providers/feature-context.ts
47350
- import { join as join27 } from "path";
47761
+ import { join as join28 } from "path";
47351
47762
  function formatForInjection(content, featureId) {
47352
47763
  return `${INJECTION_HEADER}
47353
47764
 
@@ -47364,7 +47775,7 @@ class FeatureContextProvider {
47364
47775
  const featureId = await _featureContextDeps.resolveFeatureId(story, workdir, activeFeature);
47365
47776
  if (!featureId)
47366
47777
  return null;
47367
- const contextPath = join27(featureDir(workdir, featureId), "context.md");
47778
+ const contextPath = join28(featureDir(workdir, featureId), "context.md");
47368
47779
  try {
47369
47780
  const exists = await _featureContextDeps.fileExists(contextPath);
47370
47781
  if (!exists)
@@ -47579,13 +47990,13 @@ __export(exports_loader, {
47579
47990
  _promptLoaderDeps: () => _promptLoaderDeps,
47580
47991
  loadOverride: () => loadOverride
47581
47992
  });
47582
- import { join as join28 } from "path";
47993
+ import { join as join29 } from "path";
47583
47994
  async function loadOverride(role, workdir, config2) {
47584
47995
  const overridePath = config2.prompts?.overrides?.[role];
47585
47996
  if (!overridePath) {
47586
47997
  return null;
47587
47998
  }
47588
- const absolutePath = join28(workdir, overridePath);
47999
+ const absolutePath = join29(workdir, overridePath);
47589
48000
  if (!await _promptLoaderDeps.fileExists(absolutePath)) {
47590
48001
  return null;
47591
48002
  }
@@ -47884,7 +48295,7 @@ function composeSections(input) {
47884
48295
  const unslotted = [input.role, input.task].filter((s) => !!s.content.trim());
47885
48296
  return [...slotted, ...unslotted];
47886
48297
  }
47887
- function join29(sections2) {
48298
+ function join30(sections2) {
47888
48299
  return sections2.map((s) => s.content).join(SECTION_SEP);
47889
48300
  }
47890
48301
  var init_compose2 = __esm(() => {
@@ -48186,11 +48597,11 @@ var init_generator_helpers = () => {};
48186
48597
 
48187
48598
  // src/acceptance/generator.ts
48188
48599
  import { existsSync as existsSync6 } from "fs";
48189
- import { join as join30 } from "path";
48600
+ import { join as join31 } from "path";
48190
48601
  function resolvePytestBin(packageDir) {
48191
48602
  if (packageDir) {
48192
48603
  for (const venvDir of [".venv", "venv", "env"]) {
48193
- const candidate = join30(packageDir, venvDir, "bin", "pytest");
48604
+ const candidate = join31(packageDir, venvDir, "bin", "pytest");
48194
48605
  if (existsSync6(candidate))
48195
48606
  return candidate;
48196
48607
  }
@@ -48450,7 +48861,6 @@ var init_acceptance_refine = __esm(() => {
48450
48861
  kind: "complete",
48451
48862
  name: "acceptance-refine",
48452
48863
  stage: "acceptance",
48453
- jsonMode: true,
48454
48864
  config: acceptanceConfigSelector,
48455
48865
  retry: { preset: "transient-network", maxAttempts: 2, baseDelayMs: 0 },
48456
48866
  model: (_input, ctx) => ctx.config.acceptance.model,
@@ -49187,9 +49597,9 @@ var init_autofix_test_writer_strategy = __esm(() => {
49187
49597
  });
49188
49598
 
49189
49599
  // src/agents/exec-package-name.ts
49190
- import { join as join31 } from "path";
49600
+ import { join as join32 } from "path";
49191
49601
  async function resolvePackageName(root) {
49192
- const pkgJson = Bun.file(join31(root, "package.json"));
49602
+ const pkgJson = Bun.file(join32(root, "package.json"));
49193
49603
  if (await pkgJson.exists()) {
49194
49604
  try {
49195
49605
  const parsed = await pkgJson.json();
@@ -49197,14 +49607,14 @@ async function resolvePackageName(root) {
49197
49607
  return parsed.name;
49198
49608
  } catch {}
49199
49609
  }
49200
- const cargoToml = Bun.file(join31(root, "Cargo.toml"));
49610
+ const cargoToml = Bun.file(join32(root, "Cargo.toml"));
49201
49611
  if (await cargoToml.exists()) {
49202
49612
  const content = await cargoToml.text();
49203
49613
  const match = content.match(/^\[package\][^[]*name\s*=\s*"([^"]+)"/ms);
49204
49614
  if (match?.[1] !== undefined && match[1].length > 0)
49205
49615
  return match[1];
49206
49616
  }
49207
- const pyproject = Bun.file(join31(root, "pyproject.toml"));
49617
+ const pyproject = Bun.file(join32(root, "pyproject.toml"));
49208
49618
  if (await pyproject.exists()) {
49209
49619
  const content = await pyproject.text();
49210
49620
  const match = content.match(/^\[project\][^[]*name\s*=\s*"([^"]+)"/ms);
@@ -49338,6 +49748,9 @@ function turnResultToAgentResult(r) {
49338
49748
  function hopTier(hopKind, effectiveTier) {
49339
49749
  return "tier" in hopKind ? hopKind.tier ?? effectiveTier : effectiveTier;
49340
49750
  }
49751
+ function hopModelId(hopKind) {
49752
+ return "model" in hopKind ? hopKind.model : undefined;
49753
+ }
49341
49754
  function buildHopCallback(ctx, sessionId, _initialOptions) {
49342
49755
  const {
49343
49756
  sessionManager,
@@ -49457,7 +49870,8 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
49457
49870
  sessionName,
49458
49871
  attempt: hopKind.attempt
49459
49872
  });
49460
- const modelDef = pinnedModelDef ?? resolveModelForAgent(config2.models, agentName, hopTier(hopKind, effectiveTier), defaultAgent);
49873
+ const hopPin = hopModelId(hopKind);
49874
+ const modelDef = pinnedModelDef ?? (hopPin ? resolveModel(hopPin) : resolveModelForAgent(config2.models, agentName, hopTier(hopKind, effectiveTier), defaultAgent));
49461
49875
  handle = await sessionManager.openSession(sessionName, {
49462
49876
  agentName,
49463
49877
  role: resolvedRunOptions.sessionRole ?? "implementer",
@@ -49465,7 +49879,7 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
49465
49879
  pipelineStage: stage,
49466
49880
  config: config2,
49467
49881
  modelDef,
49468
- ...pinnedModelDef !== undefined ? {} : { modelTier: hopTier(hopKind, effectiveTier) },
49882
+ ...pinnedModelDef !== undefined || hopPin ? {} : { modelTier: hopTier(hopKind, effectiveTier) },
49469
49883
  timeoutSeconds: resolvedRunOptions.timeoutSeconds ?? config2.execution?.sessionTimeoutSeconds ?? DEFAULT_CONFIG.execution.sessionTimeoutSeconds,
49470
49884
  featureName,
49471
49885
  storyId: story.id,
@@ -49476,7 +49890,9 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
49476
49890
  } else {
49477
49891
  const pinned = hopKind.kind === "primary" && pinnedModelDef !== undefined;
49478
49892
  const tier = hopTier(hopKind, effectiveTier);
49479
- const modelDef = hopKind.kind === "primary" ? pinnedModelDef ?? resolveModelForAgent(config2.models, agentName, tier, defaultAgent) : resolveModelForAgent(config2.models, agentName, tier, defaultAgent);
49893
+ const hopPin = hopModelId(hopKind);
49894
+ const resolveForHop = () => hopPin ? resolveModel(hopPin) : resolveModelForAgent(config2.models, agentName, tier, defaultAgent);
49895
+ const modelDef = hopKind.kind === "primary" ? pinnedModelDef ?? resolveForHop() : resolveForHop();
49480
49896
  handle = await sessionManager.openSession(sessionName, {
49481
49897
  agentName,
49482
49898
  role: resolvedRunOptions.sessionRole ?? "implementer",
@@ -49484,7 +49900,7 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
49484
49900
  pipelineStage: stage,
49485
49901
  config: config2,
49486
49902
  modelDef,
49487
- ...pinned ? {} : { modelTier: tier },
49903
+ ...pinned || hopPin && !pinnedModelDef ? {} : { modelTier: tier },
49488
49904
  timeoutSeconds: resolvedRunOptions.timeoutSeconds ?? config2.execution?.sessionTimeoutSeconds ?? DEFAULT_CONFIG.execution.sessionTimeoutSeconds,
49489
49905
  featureName,
49490
49906
  storyId: story.id,
@@ -49585,14 +50001,14 @@ var init_build_hop_callback = __esm(() => {
49585
50001
  });
49586
50002
 
49587
50003
  // src/runtime/packages.ts
49588
- import { isAbsolute as isAbsolute10, join as join32, relative as relative14 } from "path";
50004
+ import { isAbsolute as isAbsolute10, join as join33, relative as relative14 } from "path";
49589
50005
  function packageWorkdir(view) {
49590
50006
  const { packageDir, repoRoot } = view;
49591
50007
  if (!packageDir)
49592
50008
  return repoRoot;
49593
50009
  if (!repoRoot || isAbsolute10(packageDir))
49594
50010
  return packageDir;
49595
- return join32(repoRoot, packageDir);
50011
+ return join33(repoRoot, packageDir);
49596
50012
  }
49597
50013
  function createPackageView(config2, packageDir, repoRoot, hasOverride) {
49598
50014
  const memo = new Map;
@@ -49880,7 +50296,7 @@ async function callOp(ctx, op, input) {
49880
50296
  const slicedConfig = ctx.packageView.select(selector);
49881
50297
  const buildCtx = { packageView: ctx.packageView, config: slicedConfig };
49882
50298
  const sections2 = composeSections(op.build(input, buildCtx));
49883
- const prompt = join29(sections2);
50299
+ const prompt = join30(sections2);
49884
50300
  const timeoutMs = resolveTimeoutMs(op, input, buildCtx);
49885
50301
  const callId = ctx.callId ?? newCorrelationId();
49886
50302
  const abortSignal = ctx.signal ?? ctx.runtime.signal;
@@ -49899,7 +50315,6 @@ async function callOp(ctx, op, input) {
49899
50315
  modelDef: resolved.modelDef,
49900
50316
  modelDefFor: (agent, tier) => agent === dispatchAgent && tier === undefined ? resolved.modelDef : resolveModelForAgent(effectiveModels, agent, tier ?? effectiveTier, defaultAgent),
49901
50317
  ...resolved.modelTier !== undefined ? { modelTier: resolved.modelTier } : {},
49902
- jsonMode: completeOp.jsonMode ?? false,
49903
50318
  pipelineStage: op.stage,
49904
50319
  storyId: ctx.storyId,
49905
50320
  workdir: ctx.packageDir,
@@ -50838,7 +51253,6 @@ var init_classify_route = __esm(() => {
50838
51253
  kind: "complete",
50839
51254
  name: "classify-route",
50840
51255
  stage: "run",
50841
- jsonMode: true,
50842
51256
  config: routingConfigSelector,
50843
51257
  model: (_input, ctx) => ctx.config.routing.llm?.model ?? "balanced",
50844
51258
  retry: (_input, ctx) => ({
@@ -50875,7 +51289,6 @@ ${storyBody}`, overridable: false }
50875
51289
  kind: "complete",
50876
51290
  name: "classify-route-batch",
50877
51291
  stage: "run",
50878
- jsonMode: true,
50879
51292
  config: routingConfigSelector,
50880
51293
  model: (_input, ctx) => ctx.config.routing.llm?.model ?? "balanced",
50881
51294
  retry: (_input, ctx) => ({
@@ -51399,7 +51812,6 @@ var init_debate_judge = __esm(() => {
51399
51812
  kind: "complete",
51400
51813
  name: "debate-judge",
51401
51814
  stage: "review",
51402
- jsonMode: false,
51403
51815
  config: debateConfigSelector,
51404
51816
  model: (input) => ({ agent: input.resolverAgent, model: input.resolverModel }),
51405
51817
  timeoutMs: (input) => (input.timeoutSeconds ?? 600) * 1000,
@@ -51485,7 +51897,6 @@ var init_debate_propose = __esm(() => {
51485
51897
  kind: "complete",
51486
51898
  name: "debate-propose",
51487
51899
  stage: "review",
51488
- jsonMode: false,
51489
51900
  config: debateConfigSelector,
51490
51901
  model: (input) => {
51491
51902
  const debater = input.debaters[input.debaterIndex];
@@ -51513,7 +51924,6 @@ var init_debate_rebut = __esm(() => {
51513
51924
  kind: "complete",
51514
51925
  name: "debate-rebut",
51515
51926
  stage: "review",
51516
- jsonMode: false,
51517
51927
  config: debateConfigSelector,
51518
51928
  model: (input) => {
51519
51929
  const debater = input.debaters[input.debaterIndex];
@@ -51578,7 +51988,6 @@ var init_debate_synthesis = __esm(() => {
51578
51988
  kind: "complete",
51579
51989
  name: "debate-synthesis",
51580
51990
  stage: "review",
51581
- jsonMode: false,
51582
51991
  config: debateConfigSelector,
51583
51992
  model: (input) => ({ agent: input.resolverAgent, model: input.resolverModel }),
51584
51993
  timeoutMs: (input) => (input.timeoutSeconds ?? 600) * 1000,
@@ -51686,7 +52095,6 @@ var init_decompose2 = __esm(() => {
51686
52095
  kind: "complete",
51687
52096
  name: "decompose",
51688
52097
  stage: "plan",
51689
- jsonMode: false,
51690
52098
  config: decomposeConfigSelector,
51691
52099
  model: (_input, ctx) => ctx.config.plan.model,
51692
52100
  timeoutMs: (_input, ctx) => (ctx.config.plan.decomposeTimeoutSeconds ?? ctx.config.plan.timeoutSeconds ?? 600) * 1000,
@@ -52886,7 +53294,7 @@ __export(exports_command_resolver, {
52886
53294
  _commandResolverDeps: () => _commandResolverDeps,
52887
53295
  resolveQualityTestCommands: () => resolveQualityTestCommands
52888
53296
  });
52889
- import { join as join33 } from "path";
53297
+ import { join as join34 } from "path";
52890
53298
  async function resolveQualityTestCommands(config2, workdir, storyWorkdir) {
52891
53299
  const rawTestCommand = config2.review?.commands?.test ?? config2.quality?.commands?.test;
52892
53300
  const rawScopedTemplate = config2.quality?.commands?.testScoped;
@@ -52913,7 +53321,7 @@ var init_command_resolver = __esm(() => {
52913
53321
  _commandResolverDeps = {
52914
53322
  readPackageName: async (dir) => {
52915
53323
  try {
52916
- const content = await Bun.file(join33(dir, "package.json")).json();
53324
+ const content = await Bun.file(join34(dir, "package.json")).json();
52917
53325
  return typeof content.name === "string" ? content.name : null;
52918
53326
  } catch {
52919
53327
  return null;
@@ -52929,7 +53337,7 @@ __export(exports_command_defaults, {
52929
53337
  clearCommandDefaultsCache: () => clearCommandDefaultsCache,
52930
53338
  resolveDefaultQualityCommands: () => resolveDefaultQualityCommands
52931
53339
  });
52932
- import { join as join34 } from "path";
53340
+ import { join as join35 } from "path";
52933
53341
  function clearCommandDefaultsCache() {
52934
53342
  memo.clear();
52935
53343
  }
@@ -52961,18 +53369,18 @@ async function compute(packageDir) {
52961
53369
  }
52962
53370
  async function pythonDefaults(packageDir) {
52963
53371
  const deps = _commandDefaultsDeps;
52964
- const pyproject = await deps.readText(join34(packageDir, "pyproject.toml"));
53372
+ const pyproject = await deps.readText(join35(packageDir, "pyproject.toml"));
52965
53373
  let prefix = "";
52966
- if (await deps.fileExists(join34(packageDir, "uv.lock")) || pyproject.includes("[tool.uv]")) {
53374
+ if (await deps.fileExists(join35(packageDir, "uv.lock")) || pyproject.includes("[tool.uv]")) {
52967
53375
  prefix = "uv run ";
52968
- } else if (await deps.fileExists(join34(packageDir, "poetry.lock")) || pyproject.includes("[tool.poetry]")) {
53376
+ } else if (await deps.fileExists(join35(packageDir, "poetry.lock")) || pyproject.includes("[tool.poetry]")) {
52969
53377
  prefix = "poetry run ";
52970
53378
  }
52971
53379
  const result = { test: `${prefix}pytest` };
52972
- if (pyproject.includes("[tool.mypy]") || await deps.fileExists(join34(packageDir, "mypy.ini"))) {
53380
+ if (pyproject.includes("[tool.mypy]") || await deps.fileExists(join35(packageDir, "mypy.ini"))) {
52973
53381
  result.typecheck = `${prefix}mypy .`;
52974
53382
  }
52975
- if (pyproject.includes("[tool.ruff]") || await deps.fileExists(join34(packageDir, "ruff.toml")) || await deps.fileExists(join34(packageDir, ".ruff.toml"))) {
53383
+ if (pyproject.includes("[tool.ruff]") || await deps.fileExists(join35(packageDir, "ruff.toml")) || await deps.fileExists(join35(packageDir, ".ruff.toml"))) {
52976
53384
  result.lint = `${prefix}ruff check .`;
52977
53385
  }
52978
53386
  return result;
@@ -52980,7 +53388,7 @@ async function pythonDefaults(packageDir) {
52980
53388
  async function jsDefaults(packageDir) {
52981
53389
  const deps = _commandDefaultsDeps;
52982
53390
  const pm = await detectJsPackageManager(packageDir);
52983
- const pkg = await deps.readJson(join34(packageDir, "package.json"));
53391
+ const pkg = await deps.readJson(join35(packageDir, "package.json"));
52984
53392
  const scripts = pkg?.scripts ?? {};
52985
53393
  const result = {};
52986
53394
  if (typeof scripts.test === "string" && scripts.test.trim().length > 0) {
@@ -52990,24 +53398,24 @@ async function jsDefaults(packageDir) {
52990
53398
  }
52991
53399
  if (typeof scripts.typecheck === "string") {
52992
53400
  result.typecheck = `${pm} run typecheck`;
52993
- } else if (await deps.fileExists(join34(packageDir, "tsconfig.json"))) {
53401
+ } else if (await deps.fileExists(join35(packageDir, "tsconfig.json"))) {
52994
53402
  result.typecheck = `${pm === "bun" ? "bunx" : `${pm} exec`} tsc --noEmit`;
52995
53403
  }
52996
- if (await deps.fileExists(join34(packageDir, "biome.json"))) {
53404
+ if (await deps.fileExists(join35(packageDir, "biome.json"))) {
52997
53405
  result.lint = `${pm === "bun" ? "bunx" : `${pm} exec`} biome check .`;
52998
- } else if (await deps.fileExists(join34(packageDir, ".eslintrc")) || await deps.fileExists(join34(packageDir, ".eslintrc.json")) || await deps.fileExists(join34(packageDir, ".eslintrc.js")) || await deps.fileExists(join34(packageDir, "eslint.config.js"))) {
53406
+ } else if (await deps.fileExists(join35(packageDir, ".eslintrc")) || await deps.fileExists(join35(packageDir, ".eslintrc.json")) || await deps.fileExists(join35(packageDir, ".eslintrc.js")) || await deps.fileExists(join35(packageDir, "eslint.config.js"))) {
52999
53407
  result.lint = `${pm === "bun" ? "bunx" : `${pm} exec`} eslint .`;
53000
53408
  }
53001
53409
  return result;
53002
53410
  }
53003
53411
  async function detectJsPackageManager(packageDir) {
53004
53412
  const deps = _commandDefaultsDeps;
53005
- if (await deps.fileExists(join34(packageDir, "bun.lock")) || await deps.fileExists(join34(packageDir, "bun.lockb"))) {
53413
+ if (await deps.fileExists(join35(packageDir, "bun.lock")) || await deps.fileExists(join35(packageDir, "bun.lockb"))) {
53006
53414
  return "bun";
53007
53415
  }
53008
- if (await deps.fileExists(join34(packageDir, "pnpm-lock.yaml")))
53416
+ if (await deps.fileExists(join35(packageDir, "pnpm-lock.yaml")))
53009
53417
  return "pnpm";
53010
- if (await deps.fileExists(join34(packageDir, "yarn.lock")))
53418
+ if (await deps.fileExists(join35(packageDir, "yarn.lock")))
53011
53419
  return "yarn";
53012
53420
  return "npm";
53013
53421
  }
@@ -53045,13 +53453,13 @@ __export(exports_runners, {
53045
53453
  verifyAssets: () => verifyAssets
53046
53454
  });
53047
53455
  import { existsSync as existsSync7 } from "fs";
53048
- import { join as join35 } from "path";
53456
+ import { join as join36 } from "path";
53049
53457
  async function verifyAssets(workingDirectory, expectedFiles) {
53050
53458
  if (!expectedFiles || expectedFiles.length === 0)
53051
53459
  return { success: true, missingFiles: [] };
53052
53460
  const missingFiles = [];
53053
53461
  for (const file2 of expectedFiles) {
53054
- if (!existsSync7(join35(workingDirectory, file2)))
53462
+ if (!existsSync7(join36(workingDirectory, file2)))
53055
53463
  missingFiles.push(file2);
53056
53464
  }
53057
53465
  if (missingFiles.length > 0) {
@@ -54972,16 +55380,16 @@ var init_classify2 = __esm(() => {
54972
55380
  });
54973
55381
 
54974
55382
  // src/verification/mutation/journal.ts
54975
- import { join as join36 } from "path";
55383
+ import { join as join37 } from "path";
54976
55384
  function journalDir(repoRoot) {
54977
- return join36(repoRoot, JOURNAL_DIRNAME);
55385
+ return join37(repoRoot, JOURNAL_DIRNAME);
54978
55386
  }
54979
55387
  function journalFileName(storyId) {
54980
55388
  const safe = storyId.replace(/[^A-Za-z0-9._-]/g, "_");
54981
55389
  return `${safe || "unknown"}.json`;
54982
55390
  }
54983
55391
  function journalPathFor(repoRoot, storyId) {
54984
- return join36(journalDir(repoRoot), journalFileName(storyId));
55392
+ return join37(journalDir(repoRoot), journalFileName(storyId));
54985
55393
  }
54986
55394
  async function mayHaveJournal(candidateRoots) {
54987
55395
  const { stat: stat6 } = await import("fs/promises");
@@ -55028,7 +55436,7 @@ async function restoreInFlight(repoRoot) {
55028
55436
  for (const name of names) {
55029
55437
  if (!name.endsWith(".json"))
55030
55438
  continue;
55031
- const path8 = join36(dir, name);
55439
+ const path8 = join37(dir, name);
55032
55440
  const entry = await readEntry(path8);
55033
55441
  if (!entry) {
55034
55442
  await unlink4(path8).catch(() => {});
@@ -55058,7 +55466,7 @@ var JOURNAL_DIRNAME;
55058
55466
  var init_journal = __esm(() => {
55059
55467
  init_realpath();
55060
55468
  init_apply();
55061
- JOURNAL_DIRNAME = join36(".nax", "mutation-journal");
55469
+ JOURNAL_DIRNAME = join37(".nax", "mutation-journal");
55062
55470
  });
55063
55471
 
55064
55472
  // src/verification/mutation/operators.ts
@@ -55281,7 +55689,7 @@ var init_mutation = __esm(() => {
55281
55689
  });
55282
55690
 
55283
55691
  // src/operations/mutation-check.ts
55284
- import { isAbsolute as isAbsolute11, join as join37 } from "path";
55692
+ import { isAbsolute as isAbsolute11, join as join38 } from "path";
55285
55693
  async function sweepLeftoverMutants(repoRoot, storyId) {
55286
55694
  const logger = getLogger();
55287
55695
  try {
@@ -55391,7 +55799,7 @@ var init_mutation_check = __esm(() => {
55391
55799
  const anchor = input.packagePrefix && input.repoRoot ? input.repoRoot : await deps.getGitRoot(input.workdir) ?? input.workdir;
55392
55800
  const scopeRoot = input.repoRoot ?? input.workdir;
55393
55801
  const absoluteChangedFiles = changedFiles2.map((f) => {
55394
- const path8 = isAbsolute11(f) ? f : join37(anchor, f);
55802
+ const path8 = isAbsolute11(f) ? f : join38(anchor, f);
55395
55803
  return { path: path8, resolved: realOrRaw(path8) };
55396
55804
  }).filter(({ resolved }) => isInside(scopeRoot, resolved));
55397
55805
  const rawRangeMap = await deps.getChangedLineRanges(input.workdir, input.storyGitRef);
@@ -55898,7 +56306,7 @@ var init_plan_draft = __esm(() => {
55898
56306
  });
55899
56307
 
55900
56308
  // src/operations/plan-refine.ts
55901
- import { join as join38 } from "path";
56309
+ import { join as join39 } from "path";
55902
56310
  function hasToken(text, tokens) {
55903
56311
  const lower = text.toLowerCase();
55904
56312
  return tokens.some((token) => lower.includes(token));
@@ -55976,7 +56384,7 @@ async function normalizeStoryFiles(story, workdir, fileExists, upstreamProduced)
55976
56384
  for (const entry of contextFiles) {
55977
56385
  const filePath = typeof entry === "string" ? entry : entry.path;
55978
56386
  const factId = typeof entry === "string" ? undefined : entry.factId;
55979
- if (expected.has(filePath) || await fileExists(join38(workdir, filePath))) {
56387
+ if (expected.has(filePath) || await fileExists(join39(workdir, filePath))) {
55980
56388
  kept.push(entry);
55981
56389
  continue;
55982
56390
  }
@@ -56739,14 +57147,14 @@ var init_typecheck_check = __esm(() => {
56739
57147
  });
56740
57148
 
56741
57149
  // src/operations/validate-mock-structure-files.ts
56742
- import { isAbsolute as isAbsolute12, join as join39, relative as relative15 } from "path";
57150
+ import { isAbsolute as isAbsolute12, join as join40, relative as relative15 } from "path";
56743
57151
  function resolutionCandidates(file2, packageDir, repoRoot) {
56744
57152
  if (isAbsolute12(file2))
56745
57153
  return [file2];
56746
- const viaPackageDir = join39(packageDir, file2);
57154
+ const viaPackageDir = join40(packageDir, file2);
56747
57155
  if (repoRoot === undefined)
56748
57156
  return [viaPackageDir];
56749
- const viaRepoRoot = join39(repoRoot, file2);
57157
+ const viaRepoRoot = join40(repoRoot, file2);
56750
57158
  return viaRepoRoot === viaPackageDir ? [viaPackageDir] : [viaPackageDir, viaRepoRoot];
56751
57159
  }
56752
57160
  async function resolvePackageRelative(file2, opts) {
@@ -57661,20 +58069,67 @@ var init_operations = __esm(() => {
57661
58069
  });
57662
58070
 
57663
58071
  // src/findings/cycle-cost.ts
57664
- function ledgerCostFor(ctx, callId) {
58072
+ function ledgerSpendFor(ctx, callId) {
57665
58073
  try {
57666
- return ctx.runtime.costAggregator.byCall()[callId]?.totalCostUsd ?? 0;
58074
+ const snap = ctx.runtime.costAggregator.byCall()[callId];
58075
+ if (snap === undefined)
58076
+ return { costUsd: 0, errorCostUsd: 0 };
58077
+ return { costUsd: totalSpendUsd(snap), errorCostUsd: snap.totalErrorCostUsd };
57667
58078
  } catch (err) {
57668
58079
  getSafeLogger()?.debug("findings.cycle", "cost ledger read failed; reporting 0 for this dispatch", {
57669
58080
  storyId: ctx.storyId,
57670
58081
  callId,
57671
58082
  error: errorMessage(err)
57672
58083
  });
57673
- return 0;
58084
+ return { costUsd: 0, errorCostUsd: 0 };
57674
58085
  }
57675
58086
  }
57676
58087
  var init_cycle_cost = __esm(() => {
57677
58088
  init_logger2();
58089
+ init_cost_aggregator();
58090
+ });
58091
+
58092
+ // src/findings/cycle-dispatch.ts
58093
+ async function dispatchStrategy(strategy, ctx, findingsBefore, priorIterations, deps) {
58094
+ const { dispatchCallId } = deps;
58095
+ const relevantFindings = findingsBefore.filter((f) => strategy.appliesTo(f));
58096
+ const input = strategy.buildInput(relevantFindings, priorIterations, ctx);
58097
+ const fixCtx = {
58098
+ ...ctx,
58099
+ callId: dispatchCallId,
58100
+ fixStrategy: { name: strategy.name, findingsBefore: findingsBefore.length },
58101
+ ...strategy.sessionRole ? { sessionOverride: { role: strategy.sessionRole } } : {}
58102
+ };
58103
+ let output;
58104
+ try {
58105
+ output = await deps.callOp(fixCtx, strategy.fixOp, input);
58106
+ } catch (err) {
58107
+ const spend2 = ledgerSpendFor(fixCtx, dispatchCallId);
58108
+ deps.logger?.warn("findings.cycle", "dispatch threw \u2014 spend recorded here, not on an iteration", {
58109
+ ...deps.logCtx,
58110
+ strategyName: strategy.name,
58111
+ op: strategy.fixOp.name,
58112
+ callId: dispatchCallId,
58113
+ costUsd: spend2.costUsd,
58114
+ errorCostUsd: spend2.errorCostUsd,
58115
+ error: errorMessage(err)
58116
+ });
58117
+ throw err;
58118
+ }
58119
+ const extracted = await (strategy.extractApplied?.(output, input) ?? {});
58120
+ const spend = ledgerSpendFor(fixCtx, dispatchCallId);
58121
+ return {
58122
+ strategyName: strategy.name,
58123
+ op: strategy.fixOp.name,
58124
+ targetFiles: extracted.targetFiles ?? [],
58125
+ summary: extracted.summary ?? "",
58126
+ ...extracted.unresolved ? { unresolved: extracted.unresolved } : {},
58127
+ costUsd: extracted.costUsd !== undefined ? extracted.costUsd + spend.errorCostUsd : spend.costUsd,
58128
+ ...spend.errorCostUsd > 0 ? { errorCostUsd: spend.errorCostUsd } : {}
58129
+ };
58130
+ }
58131
+ var init_cycle_dispatch = __esm(() => {
58132
+ init_cycle_cost();
57678
58133
  });
57679
58134
 
57680
58135
  // src/findings/types.ts
@@ -57708,6 +58163,7 @@ function recordIteration(cycle, input, ctx, logger) {
57708
58163
  const findingRecurrenceKeysBefore = input.findingsBefore.map(findingRecurrenceKey);
57709
58164
  const findingRecurrenceKeysAfter = input.findingsAfter.map(findingRecurrenceKey);
57710
58165
  const costUsd = input.fixesApplied.reduce((sum, fa) => sum + (fa.costUsd ?? 0), 0);
58166
+ const errorCostUsd = input.fixesApplied.reduce((sum, fa) => sum + (fa.errorCostUsd ?? 0), 0);
57711
58167
  const seenTargetFiles = new Set;
57712
58168
  const fixTargetFiles = [];
57713
58169
  for (const fa of input.fixesApplied) {
@@ -57733,7 +58189,8 @@ function recordIteration(cycle, input, ctx, logger) {
57733
58189
  findingRecurrenceKeysBefore,
57734
58190
  findingRecurrenceKeysAfter,
57735
58191
  ...hasFixes ? { fixTargetFiles, fixSummaries } : {},
57736
- ...costUsd > 0 ? { costUsd } : {}
58192
+ ...costUsd > 0 ? { costUsd } : {},
58193
+ ...errorCostUsd > 0 ? { errorCostUsd } : {}
57737
58194
  };
57738
58195
  cycle.iterations.push(iteration);
57739
58196
  logger?.info("findings.cycle", "iteration completed", {
@@ -57750,7 +58207,8 @@ function recordIteration(cycle, input, ctx, logger) {
57750
58207
  findingRecurrenceKeysBefore,
57751
58208
  findingRecurrenceKeysAfter,
57752
58209
  ...hasFixes ? { fixTargetFiles, fixSummaries } : {},
57753
- ...costUsd > 0 ? { costUsd } : {}
58210
+ ...costUsd > 0 ? { costUsd } : {},
58211
+ ...errorCostUsd > 0 ? { errorCostUsd } : {}
57754
58212
  });
57755
58213
  return iteration;
57756
58214
  }
@@ -57942,25 +58400,12 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
57942
58400
  const findingsBefore = [...cycle.findings];
57943
58401
  const fixesApplied = [];
57944
58402
  for (const strategy of group) {
57945
- const relevantFindings = findingsBefore.filter((f) => strategy.appliesTo(f));
57946
- const input = strategy.buildInput(relevantFindings, cycle.iterations, ctx);
57947
- const dispatchCallId = newCorrelationId();
57948
- const fixCtx = {
57949
- ...ctx,
57950
- callId: dispatchCallId,
57951
- fixStrategy: { name: strategy.name, findingsBefore: findingsBefore.length },
57952
- ...strategy.sessionRole ? { sessionOverride: { role: strategy.sessionRole } } : {}
57953
- };
57954
- const output = await doCallOp(fixCtx, strategy.fixOp, input);
57955
- const extracted = await (strategy.extractApplied?.(output, input) ?? {});
57956
- fixesApplied.push({
57957
- strategyName: strategy.name,
57958
- op: strategy.fixOp.name,
57959
- targetFiles: extracted.targetFiles ?? [],
57960
- summary: extracted.summary ?? "",
57961
- ...extracted.unresolved ? { unresolved: extracted.unresolved } : {},
57962
- costUsd: extracted.costUsd ?? ledgerCostFor(fixCtx, dispatchCallId)
57963
- });
58403
+ fixesApplied.push(await dispatchStrategy(strategy, ctx, findingsBefore, cycle.iterations, {
58404
+ callOp: doCallOp,
58405
+ dispatchCallId: newCorrelationId(),
58406
+ logger,
58407
+ logCtx
58408
+ }));
57964
58409
  }
57965
58410
  const unresolvedFas = fixesApplied.filter((fa) => fa.unresolved);
57966
58411
  if (unresolvedFas.length > 0) {
@@ -58174,7 +58619,7 @@ var _cycleDeps;
58174
58619
  var init_cycle = __esm(() => {
58175
58620
  init_logger2();
58176
58621
  init_operations();
58177
- init_cycle_cost();
58622
+ init_cycle_dispatch();
58178
58623
  init_cycle_iteration_log();
58179
58624
  init_cycle_retirement();
58180
58625
  init_types8();
@@ -59161,7 +59606,7 @@ var init_config_descriptions = __esm(() => {
59161
59606
 
59162
59607
  // src/cli/config-get.ts
59163
59608
  import { existsSync as existsSync8 } from "fs";
59164
- import { join as join40 } from "path";
59609
+ import { join as join41 } from "path";
59165
59610
  async function loadConfigFile(path9) {
59166
59611
  if (!existsSync8(path9))
59167
59612
  return null;
@@ -59183,7 +59628,7 @@ async function loadProjectConfig() {
59183
59628
  const projectDir = findProjectDir();
59184
59629
  if (!projectDir)
59185
59630
  return null;
59186
- const projectPath = join40(projectDir, "config.json");
59631
+ const projectPath = join41(projectDir, "config.json");
59187
59632
  return await loadConfigFile(projectPath);
59188
59633
  }
59189
59634
  var init_config_get = __esm(() => {
@@ -59193,11 +59638,11 @@ var init_config_get = __esm(() => {
59193
59638
  });
59194
59639
 
59195
59640
  // src/cli/config-profile.ts
59196
- import { mkdirSync as mkdirSync2, readdirSync as readdirSync2 } from "fs";
59197
- import { join as join41 } from "path";
59641
+ import { mkdirSync as mkdirSync3, readdirSync as readdirSync2 } from "fs";
59642
+ import { join as join42 } from "path";
59198
59643
  async function profileListCommand(startDir) {
59199
- const globalProfilesDir = join41(globalConfigDir(), "profiles");
59200
- const projectProfilesDir = join41(projectConfigDir(startDir), "profiles");
59644
+ const globalProfilesDir = join42(globalConfigDir(), "profiles");
59645
+ const projectProfilesDir = join42(projectConfigDir(startDir), "profiles");
59201
59646
  const globalProfiles = scanProfileDir(globalProfilesDir);
59202
59647
  const projectProfiles = scanProfileDir(projectProfilesDir);
59203
59648
  const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
@@ -59260,7 +59705,7 @@ function maskProfileValue(value) {
59260
59705
  return value;
59261
59706
  }
59262
59707
  async function profileUseCommand(profileName, startDir) {
59263
- const configPath = join41(projectConfigDir(startDir), "config.json");
59708
+ const configPath = join42(projectConfigDir(startDir), "config.json");
59264
59709
  const configFile = Bun.file(configPath);
59265
59710
  let existing = {};
59266
59711
  if (await configFile.exists()) {
@@ -59281,13 +59726,13 @@ async function profileCurrentCommand(startDir) {
59281
59726
  }
59282
59727
  async function profileCreateCommand(profileName, startDir) {
59283
59728
  validateProfileName(profileName);
59284
- const profilesDir = join41(projectConfigDir(startDir), "profiles");
59285
- const profilePath = join41(profilesDir, `${profileName}.json`);
59729
+ const profilesDir = join42(projectConfigDir(startDir), "profiles");
59730
+ const profilePath = join42(profilesDir, `${profileName}.json`);
59286
59731
  const profileFile = Bun.file(profilePath);
59287
59732
  if (await profileFile.exists()) {
59288
59733
  throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
59289
59734
  }
59290
- mkdirSync2(profilesDir, { recursive: true });
59735
+ mkdirSync3(profilesDir, { recursive: true });
59291
59736
  await Bun.write(profilePath, "{}");
59292
59737
  return profilePath;
59293
59738
  }
@@ -59305,7 +59750,7 @@ var init_config_profile = __esm(() => {
59305
59750
 
59306
59751
  // src/cli/config-display.ts
59307
59752
  import { existsSync as existsSync9 } from "fs";
59308
- import { join as join42 } from "path";
59753
+ import { join as join43 } from "path";
59309
59754
  async function configCommand(config2, options = {}) {
59310
59755
  const { explain = false, diff = false } = options;
59311
59756
  if (explain && diff) {
@@ -59363,7 +59808,7 @@ async function configCommand(config2, options = {}) {
59363
59808
  function determineConfigSources() {
59364
59809
  const globalPath = globalConfigPath();
59365
59810
  const projectDir = findProjectDir();
59366
- const projectPath = projectDir ? join42(projectDir, "config.json") : null;
59811
+ const projectPath = projectDir ? join43(projectDir, "config.json") : null;
59367
59812
  return {
59368
59813
  global: fileExists(globalPath) ? globalPath : null,
59369
59814
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -59918,7 +60363,7 @@ var init_context2 = __esm(() => {
59918
60363
 
59919
60364
  // src/cli/context-fragments.ts
59920
60365
  import { existsSync as existsSync11 } from "fs";
59921
- import { join as join43 } from "path";
60366
+ import { join as join44 } from "path";
59922
60367
  function listDependentStoryIds(prd, startId) {
59923
60368
  const forward = new Map;
59924
60369
  for (const story of prd.userStories) {
@@ -60003,7 +60448,7 @@ function resolveProjectDir(dir) {
60003
60448
  return dir ?? process.cwd();
60004
60449
  }
60005
60450
  function featurePrdPath2(projectDir, featureId) {
60006
- return join43(featureDir(projectDir, featureId), "prd.json");
60451
+ return join44(featureDir(projectDir, featureId), "prd.json");
60007
60452
  }
60008
60453
  async function fragmentsInspectCommand(options) {
60009
60454
  const projectDir = _contextFragmentsDeps.projectDirFor(resolveProjectDir(options.dir));
@@ -60621,7 +61066,7 @@ var init_acceptance2 = __esm(() => {
60621
61066
 
60622
61067
  // src/cli/features-acceptance.ts
60623
61068
  import { existsSync as existsSync12 } from "fs";
60624
- import { join as join44, relative as relative16 } from "path";
61069
+ import { join as join45, relative as relative16 } from "path";
60625
61070
  async function resolveFeatureAcceptance(featureName, workdir) {
60626
61071
  let enabled = true;
60627
61072
  try {
@@ -60629,13 +61074,13 @@ async function resolveFeatureAcceptance(featureName, workdir) {
60629
61074
  if (!naxDir) {
60630
61075
  return { status: "no-prd", enabled, groups: [] };
60631
61076
  }
60632
- const repoRoot = join44(naxDir, "..");
61077
+ const repoRoot = join45(naxDir, "..");
60633
61078
  const config2 = await loadConfig(workdir);
60634
61079
  enabled = config2.acceptance?.enabled ?? true;
60635
61080
  if (!enabled) {
60636
61081
  return { status: "disabled", enabled: false, groups: [] };
60637
61082
  }
60638
- const prdPath = join44(naxDir, "features", featureName, "prd.json");
61083
+ const prdPath = join45(naxDir, "features", featureName, "prd.json");
60639
61084
  if (!existsSync12(prdPath)) {
60640
61085
  return { status: "no-prd", enabled, groups: [] };
60641
61086
  }
@@ -60694,7 +61139,7 @@ function validateFeatureName(feature) {
60694
61139
 
60695
61140
  // src/cli/features-resolve.ts
60696
61141
  import { existsSync as existsSync13, readdirSync as readdirSync3 } from "fs";
60697
- import { join as join45, relative as relative17 } from "path";
61142
+ import { join as join46, relative as relative17 } from "path";
60698
61143
  async function resolveTestPatterns(workdir) {
60699
61144
  try {
60700
61145
  const config2 = await loadConfig(workdir);
@@ -60712,10 +61157,10 @@ async function isNonEmptyFile(absolutePath) {
60712
61157
  }
60713
61158
  async function searchSpecSource(naxDir, repoRoot, name) {
60714
61159
  const candidates = [
60715
- { abs: join45(naxDir, "features", name, "spec.md"), kind: "markdown" },
60716
- { abs: join45(naxDir, "specs", `${name}.md`), kind: "markdown" }
61160
+ { abs: join46(naxDir, "features", name, "spec.md"), kind: "markdown" },
61161
+ { abs: join46(naxDir, "specs", `${name}.md`), kind: "markdown" }
60717
61162
  ];
60718
- const docsSpecExact = join45(repoRoot, "docs", "specs", `SPEC-${name}.md`);
61163
+ const docsSpecExact = join46(repoRoot, "docs", "specs", `SPEC-${name}.md`);
60719
61164
  candidates.push({ abs: docsSpecExact, kind: "markdown" });
60720
61165
  const checked = candidates.map((c) => relative17(repoRoot, c.abs));
60721
61166
  for (const { abs, kind } of candidates.slice(0, 2)) {
@@ -60729,11 +61174,11 @@ async function searchSpecSource(naxDir, repoRoot, name) {
60729
61174
  if (await isNonEmptyFile(docsSpecExact)) {
60730
61175
  return { source: { kind: "markdown", path: relative17(repoRoot, docsSpecExact) }, checked };
60731
61176
  }
60732
- const docsSpecsDir = join45(repoRoot, "docs", "specs");
61177
+ const docsSpecsDir = join46(repoRoot, "docs", "specs");
60733
61178
  if (existsSync13(docsSpecsDir)) {
60734
61179
  const glob = new Bun.Glob(`*${name}*.md`);
60735
61180
  for (const match of glob.scanSync({ cwd: docsSpecsDir, absolute: false })) {
60736
- const abs = join45(docsSpecsDir, match);
61181
+ const abs = join46(docsSpecsDir, match);
60737
61182
  if (await isNonEmptyFile(abs)) {
60738
61183
  const relPath = relative17(repoRoot, abs);
60739
61184
  if (!checked.includes(relPath))
@@ -60742,7 +61187,7 @@ async function searchSpecSource(naxDir, repoRoot, name) {
60742
61187
  }
60743
61188
  }
60744
61189
  }
60745
- const prdAbs = join45(naxDir, "features", name, "prd.json");
61190
+ const prdAbs = join46(naxDir, "features", name, "prd.json");
60746
61191
  const prdRel = relative17(repoRoot, prdAbs);
60747
61192
  if (!checked.includes(prdRel))
60748
61193
  checked.push(prdRel);
@@ -60752,14 +61197,14 @@ async function searchSpecSource(naxDir, repoRoot, name) {
60752
61197
  return { source: null, checked };
60753
61198
  }
60754
61199
  function discoverCandidates(naxDir) {
60755
- const featuresDir2 = join45(naxDir, "features");
61200
+ const featuresDir2 = join46(naxDir, "features");
60756
61201
  if (!existsSync13(featuresDir2))
60757
61202
  return [];
60758
61203
  return readdirSync3(featuresDir2, { withFileTypes: true }).filter((e) => {
60759
61204
  if (!e.isDirectory())
60760
61205
  return false;
60761
- const dir = join45(featuresDir2, e.name);
60762
- return existsSync13(join45(dir, "prd.json")) || existsSync13(join45(dir, "spec.md"));
61206
+ const dir = join46(featuresDir2, e.name);
61207
+ return existsSync13(join46(dir, "prd.json")) || existsSync13(join46(dir, "spec.md"));
60763
61208
  }).map((e) => e.name).sort();
60764
61209
  }
60765
61210
  async function resolveFeatureSpec(name, workdir) {
@@ -60770,9 +61215,9 @@ async function resolveFeatureSpec(name, workdir) {
60770
61215
  message: `not a nax repo: no .nax/config.json found from ${workdir}`
60771
61216
  };
60772
61217
  }
60773
- const repoRoot = join45(naxDir, "..");
61218
+ const repoRoot = join46(naxDir, "..");
60774
61219
  if (name !== undefined && (name.startsWith("./") || name.startsWith("/") || name.endsWith(".md"))) {
60775
- const abs = name.startsWith("/") ? name : join45(workdir, name);
61220
+ const abs = name.startsWith("/") ? name : join46(workdir, name);
60776
61221
  if (!existsSync13(abs)) {
60777
61222
  return {
60778
61223
  status: "missing",
@@ -60812,7 +61257,7 @@ async function resolveFeatureSpec(name, workdir) {
60812
61257
  message: `resolved spec: ${source2.path}`
60813
61258
  };
60814
61259
  }
60815
- const featureDir2 = join45(naxDir, "features", name);
61260
+ const featureDir2 = join46(naxDir, "features", name);
60816
61261
  if (existsSync13(featureDir2)) {
60817
61262
  return {
60818
61263
  status: "missing",
@@ -60875,9 +61320,9 @@ var init_features_resolve = __esm(() => {
60875
61320
 
60876
61321
  // src/context/injector.ts
60877
61322
  import { existsSync as existsSync14 } from "fs";
60878
- import { join as join46 } from "path";
61323
+ import { join as join47 } from "path";
60879
61324
  async function detectNode(workdir) {
60880
- const pkgPath = join46(workdir, "package.json");
61325
+ const pkgPath = join47(workdir, "package.json");
60881
61326
  if (!existsSync14(pkgPath))
60882
61327
  return null;
60883
61328
  try {
@@ -60894,7 +61339,7 @@ async function detectNode(workdir) {
60894
61339
  }
60895
61340
  }
60896
61341
  async function detectGo(workdir) {
60897
- const goMod = join46(workdir, "go.mod");
61342
+ const goMod = join47(workdir, "go.mod");
60898
61343
  if (!existsSync14(goMod))
60899
61344
  return null;
60900
61345
  try {
@@ -60918,7 +61363,7 @@ async function detectGo(workdir) {
60918
61363
  }
60919
61364
  }
60920
61365
  async function detectRust(workdir) {
60921
- const cargoPath = join46(workdir, "Cargo.toml");
61366
+ const cargoPath = join47(workdir, "Cargo.toml");
60922
61367
  if (!existsSync14(cargoPath))
60923
61368
  return null;
60924
61369
  try {
@@ -60934,8 +61379,8 @@ async function detectRust(workdir) {
60934
61379
  }
60935
61380
  }
60936
61381
  async function detectPython(workdir) {
60937
- const pyproject = join46(workdir, "pyproject.toml");
60938
- const requirements = join46(workdir, "requirements.txt");
61382
+ const pyproject = join47(workdir, "pyproject.toml");
61383
+ const requirements = join47(workdir, "requirements.txt");
60939
61384
  if (!existsSync14(pyproject) && !existsSync14(requirements))
60940
61385
  return null;
60941
61386
  try {
@@ -60954,7 +61399,7 @@ async function detectPython(workdir) {
60954
61399
  }
60955
61400
  }
60956
61401
  async function detectPhp(workdir) {
60957
- const composerPath = join46(workdir, "composer.json");
61402
+ const composerPath = join47(workdir, "composer.json");
60958
61403
  if (!existsSync14(composerPath))
60959
61404
  return null;
60960
61405
  try {
@@ -60967,7 +61412,7 @@ async function detectPhp(workdir) {
60967
61412
  }
60968
61413
  }
60969
61414
  async function detectRuby(workdir) {
60970
- const gemfile = join46(workdir, "Gemfile");
61415
+ const gemfile = join47(workdir, "Gemfile");
60971
61416
  if (!existsSync14(gemfile))
60972
61417
  return null;
60973
61418
  try {
@@ -60979,9 +61424,9 @@ async function detectRuby(workdir) {
60979
61424
  }
60980
61425
  }
60981
61426
  async function detectJvm(workdir) {
60982
- const pom = join46(workdir, "pom.xml");
60983
- const gradle = join46(workdir, "build.gradle");
60984
- const gradleKts = join46(workdir, "build.gradle.kts");
61427
+ const pom = join47(workdir, "pom.xml");
61428
+ const gradle = join47(workdir, "build.gradle");
61429
+ const gradleKts = join47(workdir, "build.gradle.kts");
60985
61430
  if (!existsSync14(pom) && !existsSync14(gradle) && !existsSync14(gradleKts))
60986
61431
  return null;
60987
61432
  try {
@@ -60989,7 +61434,7 @@ async function detectJvm(workdir) {
60989
61434
  const content2 = await Bun.file(pom).text();
60990
61435
  const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
60991
61436
  const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
60992
- const lang2 = existsSync14(join46(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
61437
+ const lang2 = existsSync14(join47(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
60993
61438
  return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
60994
61439
  }
60995
61440
  const gradleFile = existsSync14(gradleKts) ? gradleKts : gradle;
@@ -61243,7 +61688,7 @@ var init_windsurf = __esm(() => {
61243
61688
 
61244
61689
  // src/context/generator.ts
61245
61690
  import { existsSync as existsSync15 } from "fs";
61246
- import { join as join47, relative as relative18 } from "path";
61691
+ import { join as join48, relative as relative18 } from "path";
61247
61692
  async function loadContextContent(options, config2) {
61248
61693
  if (!_generatorDeps.existsSync(options.contextPath)) {
61249
61694
  throw new Error(`Context file not found: ${options.contextPath}`);
@@ -61261,7 +61706,7 @@ async function generateFor(agent, options, config2) {
61261
61706
  try {
61262
61707
  const context = await loadContextContent(options, config2);
61263
61708
  const content = generator.generate(context);
61264
- const outputPath = join47(options.outputDir, generator.outputFile);
61709
+ const outputPath = join48(options.outputDir, generator.outputFile);
61265
61710
  validateFilePath(outputPath, options.outputDir);
61266
61711
  if (!options.dryRun) {
61267
61712
  await _generatorDeps.writeFile(outputPath, content);
@@ -61279,7 +61724,7 @@ async function generateAll(options, config2, agentFilter) {
61279
61724
  for (const [agentKey, generator] of entries) {
61280
61725
  try {
61281
61726
  const content = generator.generate(context);
61282
- const outputPath = join47(options.outputDir, generator.outputFile);
61727
+ const outputPath = join48(options.outputDir, generator.outputFile);
61283
61728
  validateFilePath(outputPath, options.outputDir);
61284
61729
  if (!options.dryRun) {
61285
61730
  await _generatorDeps.writeFile(outputPath, content);
@@ -61299,7 +61744,7 @@ async function discoverPackages(repoRoot) {
61299
61744
  const glob = new Bun.Glob(pattern);
61300
61745
  for await (const match of glob.scan({ cwd: repoRoot, dot: true })) {
61301
61746
  const pkgRelative = match.replace(/^\.nax\/mono\//, "").replace(/\/context\.md$/, "");
61302
- const pkgAbsolute = join47(repoRoot, pkgRelative);
61747
+ const pkgAbsolute = join48(repoRoot, pkgRelative);
61303
61748
  if (!seen.has(pkgAbsolute)) {
61304
61749
  seen.add(pkgAbsolute);
61305
61750
  packages.push(pkgAbsolute);
@@ -61331,14 +61776,14 @@ async function discoverWorkspacePackages2(repoRoot) {
61331
61776
  }
61332
61777
  }
61333
61778
  }
61334
- const turboPath = join47(repoRoot, "turbo.json");
61779
+ const turboPath = join48(repoRoot, "turbo.json");
61335
61780
  try {
61336
61781
  const turbo = JSON.parse(await _generatorDeps.readTextFile(turboPath));
61337
61782
  if (Array.isArray(turbo.packages)) {
61338
61783
  await resolveGlobs(turbo.packages);
61339
61784
  }
61340
61785
  } catch {}
61341
- const pkgPath = join47(repoRoot, "package.json");
61786
+ const pkgPath = join48(repoRoot, "package.json");
61342
61787
  try {
61343
61788
  const pkg = JSON.parse(await _generatorDeps.readTextFile(pkgPath));
61344
61789
  const ws = pkg.workspaces;
@@ -61346,7 +61791,7 @@ async function discoverWorkspacePackages2(repoRoot) {
61346
61791
  if (patterns.length > 0)
61347
61792
  await resolveGlobs(patterns);
61348
61793
  } catch {}
61349
- const pnpmPath = join47(repoRoot, "pnpm-workspace.yaml");
61794
+ const pnpmPath = join48(repoRoot, "pnpm-workspace.yaml");
61350
61795
  try {
61351
61796
  const raw = await _generatorDeps.readTextFile(pnpmPath);
61352
61797
  const lines = raw.split(`
@@ -61372,7 +61817,7 @@ async function discoverWorkspacePackages2(repoRoot) {
61372
61817
  async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
61373
61818
  const resolvedRepoRoot = repoRoot ?? packageDir;
61374
61819
  const relativePkgPath = relative18(resolvedRepoRoot, packageDir);
61375
- const contextPath = join47(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
61820
+ const contextPath = join48(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
61376
61821
  if (!_generatorDeps.existsSync(contextPath)) {
61377
61822
  return [
61378
61823
  {
@@ -61441,7 +61886,7 @@ var init_generator2 = __esm(() => {
61441
61886
 
61442
61887
  // src/cli/generate.ts
61443
61888
  import { existsSync as existsSync16 } from "fs";
61444
- import { join as join48 } from "path";
61889
+ import { join as join49 } from "path";
61445
61890
  async function generateCommand(options) {
61446
61891
  const workdir = options.dir ?? process.cwd();
61447
61892
  const dryRun = options.dryRun ?? false;
@@ -61486,7 +61931,7 @@ async function generateCommand(options) {
61486
61931
  if (!isRelativeAndSafe(options.package)) {
61487
61932
  throw new NaxError(`generateCommand: package "${options.package}" is not a safe relative path (must be non-empty, relative, and free of ".." segments)`, "INVALID_PACKAGE_PATH", { stage: "generate", package: options.package });
61488
61933
  }
61489
- const packageDir = join48(workdir, options.package);
61934
+ const packageDir = join49(workdir, options.package);
61490
61935
  if (dryRun) {
61491
61936
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
61492
61937
  }
@@ -61506,8 +61951,8 @@ async function generateCommand(options) {
61506
61951
  process.exit(1);
61507
61952
  return;
61508
61953
  }
61509
- const contextPath = options.context ? join48(workdir, options.context) : join48(workdir, ".nax/context.md");
61510
- const outputDir = options.output ? join48(workdir, options.output) : workdir;
61954
+ const contextPath = options.context ? join49(workdir, options.context) : join49(workdir, ".nax/context.md");
61955
+ const outputDir = options.output ? join49(workdir, options.output) : workdir;
61511
61956
  const autoInject = !options.noAutoInject;
61512
61957
  if (!existsSync16(contextPath)) {
61513
61958
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -61621,11 +62066,11 @@ var init_generate = __esm(() => {
61621
62066
  });
61622
62067
 
61623
62068
  // src/utils/nax-project-root.ts
61624
- import { dirname as dirname11, join as join49, resolve as resolve19 } from "path";
62069
+ import { dirname as dirname11, join as join50, resolve as resolve19 } from "path";
61625
62070
  async function findNaxProjectRoot(startDir) {
61626
62071
  let dir = resolve19(startDir);
61627
62072
  for (let depth = 0;depth < MAX_NAX_WALK_DEPTH; depth++) {
61628
- if (await _naxProjectRootDeps.exists(join49(dir, ".nax", "config.json"))) {
62073
+ if (await _naxProjectRootDeps.exists(join50(dir, ".nax", "config.json"))) {
61629
62074
  return dir;
61630
62075
  }
61631
62076
  const parent = dirname11(dir);
@@ -61649,7 +62094,7 @@ var package_default;
61649
62094
  var init_package = __esm(() => {
61650
62095
  package_default = {
61651
62096
  name: "@nathapp/nax",
61652
- version: "0.82.0-canary.6",
62097
+ version: "0.82.0-canary.7",
61653
62098
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
61654
62099
  type: "module",
61655
62100
  bin: {
@@ -61770,8 +62215,8 @@ var init_version = __esm(() => {
61770
62215
  NAX_VERSION = package_default.version;
61771
62216
  NAX_COMMIT = (() => {
61772
62217
  try {
61773
- if (/^[0-9a-f]{6,10}$/.test("4addbf38"))
61774
- return "4addbf38";
62218
+ if (/^[0-9a-f]{6,10}$/.test("21dacecf"))
62219
+ return "21dacecf";
61775
62220
  } catch {}
61776
62221
  try {
61777
62222
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -61791,7 +62236,7 @@ var init_version = __esm(() => {
61791
62236
 
61792
62237
  // src/review/review-audit.ts
61793
62238
  import { mkdir as mkdir9 } from "fs/promises";
61794
- import { join as join50 } from "path";
62239
+ import { join as join51 } from "path";
61795
62240
  function auditKey(reviewer, storyId) {
61796
62241
  return `${reviewer}:${storyId ?? "_feature"}`;
61797
62242
  }
@@ -61827,15 +62272,15 @@ function toPersistedEntry(entry, epochMs) {
61827
62272
  async function persistReviewAudit(entry) {
61828
62273
  let resolvedDir;
61829
62274
  if (entry.outputDir) {
61830
- resolvedDir = join50(entry.outputDir, "review-audit", entry.featureName ?? "_unknown");
62275
+ resolvedDir = join51(entry.outputDir, "review-audit", entry.featureName ?? "_unknown");
61831
62276
  } else {
61832
62277
  const projectRoot = entry.projectDir ?? await _reviewAuditDeps.findNaxProjectRoot(entry.workdir);
61833
- resolvedDir = join50(projectRoot, ".nax", "review-audit", entry.featureName ?? "_unknown");
62278
+ resolvedDir = join51(projectRoot, ".nax", "review-audit", entry.featureName ?? "_unknown");
61834
62279
  }
61835
62280
  await _reviewAuditDeps.mkdir(resolvedDir);
61836
62281
  const epochMs = _reviewAuditDeps.now();
61837
62282
  const filename = `${epochMs}-${entry.sessionName}.json`;
61838
- await _reviewAuditDeps.writeFile(join50(resolvedDir, filename), toPersistedEntry(entry, epochMs));
62283
+ await _reviewAuditDeps.writeFile(join51(resolvedDir, filename), toPersistedEntry(entry, epochMs));
61839
62284
  }
61840
62285
  function createNoOpReviewAuditor() {
61841
62286
  return {
@@ -61954,283 +62399,6 @@ var init_agent_stream_events = __esm(() => {
61954
62399
  init_logger2();
61955
62400
  });
61956
62401
 
61957
- // src/runtime/cost-aggregator.ts
61958
- import { mkdirSync as mkdirSync3 } from "fs";
61959
- import { join as join51 } from "path";
61960
- function makeCorrelationId() {
61961
- return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
61962
- }
61963
- function createNoOpCostAggregator() {
61964
- return {
61965
- record() {},
61966
- recordError() {},
61967
- recordOperationSummary() {},
61968
- snapshot() {
61969
- return EMPTY_SNAPSHOT;
61970
- },
61971
- byAgent() {
61972
- return {};
61973
- },
61974
- byStage() {
61975
- return {};
61976
- },
61977
- byStory() {
61978
- return {};
61979
- },
61980
- byCall() {
61981
- return {};
61982
- },
61983
- byScope() {
61984
- return {};
61985
- },
61986
- openScope(scopeId) {
61987
- return {
61988
- scopeId: scopeId ?? makeCorrelationId(),
61989
- snapshot: () => EMPTY_SNAPSHOT,
61990
- close: () => {}
61991
- };
61992
- },
61993
- async drain() {}
61994
- };
61995
- }
61996
- function emptySnap() {
61997
- return {
61998
- totalCostUsd: 0,
61999
- totalEstimatedCostUsd: 0,
62000
- totalExactCostUsd: 0,
62001
- totalInputTokens: 0,
62002
- totalOutputTokens: 0,
62003
- totalCacheReadTokens: 0,
62004
- totalCacheWriteTokens: 0,
62005
- tokenUsageCount: 0,
62006
- callCount: 0,
62007
- errorCount: 0,
62008
- totalErrorCostUsd: 0
62009
- };
62010
- }
62011
- function accumulate(snap, e) {
62012
- const tokens = e.tokens;
62013
- return {
62014
- totalCostUsd: snap.totalCostUsd + e.costUsd,
62015
- totalEstimatedCostUsd: snap.totalEstimatedCostUsd + e.estimatedCostUsd,
62016
- totalExactCostUsd: snap.totalExactCostUsd + e.exactCostUsd,
62017
- totalInputTokens: snap.totalInputTokens + (tokens?.input ?? 0),
62018
- totalOutputTokens: snap.totalOutputTokens + (tokens?.output ?? 0),
62019
- totalCacheReadTokens: (snap.totalCacheReadTokens ?? 0) + (tokens?.cacheRead ?? 0),
62020
- totalCacheWriteTokens: (snap.totalCacheWriteTokens ?? 0) + (tokens?.cacheWrite ?? 0),
62021
- tokenUsageCount: (snap.tokenUsageCount ?? 0) + (tokens ? 1 : 0),
62022
- callCount: snap.callCount + 1,
62023
- errorCount: snap.errorCount,
62024
- totalErrorCostUsd: snap.totalErrorCostUsd
62025
- };
62026
- }
62027
- function accumulateError(snap, e) {
62028
- return {
62029
- ...snap,
62030
- errorCount: snap.errorCount + 1,
62031
- totalErrorCostUsd: snap.totalErrorCostUsd + (e?.costUsd ?? 0)
62032
- };
62033
- }
62034
-
62035
- class CostAggregator {
62036
- _runId;
62037
- _drainDir;
62038
- _events = [];
62039
- _errors = [];
62040
- _draining = false;
62041
- _inFlightEvents = [];
62042
- _inFlightErrors = [];
62043
- _openScopes = new Set;
62044
- constructor(_runId, _drainDir) {
62045
- this._runId = _runId;
62046
- this._drainDir = _drainDir;
62047
- }
62048
- record(event) {
62049
- if (this._draining) {
62050
- this._inFlightEvents.push(event);
62051
- return;
62052
- }
62053
- this._events.push(event);
62054
- }
62055
- recordError(event) {
62056
- if (this._draining) {
62057
- this._inFlightErrors.push(event);
62058
- return;
62059
- }
62060
- this._errors.push(event);
62061
- }
62062
- recordOperationSummary(_event) {}
62063
- snapshot() {
62064
- const allEvents = [...this._events, ...this._inFlightEvents];
62065
- const allErrors = [...this._errors, ...this._inFlightErrors];
62066
- const eventReduced = allEvents.reduce(accumulate, emptySnap());
62067
- return allErrors.reduce((snap, e) => accumulateError(snap, e), eventReduced);
62068
- }
62069
- byAgent() {
62070
- const m = {};
62071
- for (const e of this._events)
62072
- m[e.agentName] = accumulate(m[e.agentName] ?? emptySnap(), e);
62073
- for (const e of this._inFlightEvents)
62074
- m[e.agentName] = accumulate(m[e.agentName] ?? emptySnap(), e);
62075
- for (const e of this._errors)
62076
- m[e.agentName] = accumulateError(m[e.agentName] ?? emptySnap(), e);
62077
- for (const e of this._inFlightErrors)
62078
- m[e.agentName] = accumulateError(m[e.agentName] ?? emptySnap(), e);
62079
- return m;
62080
- }
62081
- byStage() {
62082
- const m = {};
62083
- for (const e of this._events) {
62084
- const k = e.stage ?? "unknown";
62085
- m[k] = accumulate(m[k] ?? emptySnap(), e);
62086
- }
62087
- for (const e of this._inFlightEvents) {
62088
- const k = e.stage ?? "unknown";
62089
- m[k] = accumulate(m[k] ?? emptySnap(), e);
62090
- }
62091
- for (const e of this._errors) {
62092
- const k = e.stage ?? "unknown";
62093
- m[k] = accumulateError(m[k] ?? emptySnap(), e);
62094
- }
62095
- for (const e of this._inFlightErrors) {
62096
- const k = e.stage ?? "unknown";
62097
- m[k] = accumulateError(m[k] ?? emptySnap(), e);
62098
- }
62099
- return m;
62100
- }
62101
- byStory() {
62102
- const m = {};
62103
- for (const e of this._events) {
62104
- const k = e.storyId ?? "unknown";
62105
- m[k] = accumulate(m[k] ?? emptySnap(), e);
62106
- }
62107
- for (const e of this._inFlightEvents) {
62108
- const k = e.storyId ?? "unknown";
62109
- m[k] = accumulate(m[k] ?? emptySnap(), e);
62110
- }
62111
- for (const e of this._errors) {
62112
- const k = e.storyId ?? "unknown";
62113
- m[k] = accumulateError(m[k] ?? emptySnap(), e);
62114
- }
62115
- for (const e of this._inFlightErrors) {
62116
- const k = e.storyId ?? "unknown";
62117
- m[k] = accumulateError(m[k] ?? emptySnap(), e);
62118
- }
62119
- return m;
62120
- }
62121
- byCall() {
62122
- const m = {};
62123
- for (const e of [...this._events, ...this._inFlightEvents]) {
62124
- if (e.callId === undefined)
62125
- continue;
62126
- m[e.callId] = accumulate(m[e.callId] ?? emptySnap(), e);
62127
- }
62128
- for (const e of [...this._errors, ...this._inFlightErrors]) {
62129
- if (e.callId === undefined)
62130
- continue;
62131
- m[e.callId] = accumulateError(m[e.callId] ?? emptySnap(), e);
62132
- }
62133
- return m;
62134
- }
62135
- byScope() {
62136
- const m = {};
62137
- for (const e of [...this._events, ...this._inFlightEvents]) {
62138
- if (e.scopeId === undefined)
62139
- continue;
62140
- m[e.scopeId] = accumulate(m[e.scopeId] ?? emptySnap(), e);
62141
- }
62142
- for (const e of [...this._errors, ...this._inFlightErrors]) {
62143
- if (e.scopeId === undefined)
62144
- continue;
62145
- m[e.scopeId] = accumulateError(m[e.scopeId] ?? emptySnap(), e);
62146
- }
62147
- return m;
62148
- }
62149
- openScope(scopeId) {
62150
- const id = scopeId ?? _costAggDeps.newCorrelationId();
62151
- this._openScopes.add(id);
62152
- let closed = false;
62153
- return {
62154
- scopeId: id,
62155
- snapshot: () => {
62156
- const matching = [...this._events, ...this._inFlightEvents].filter((e) => e.scopeId === id);
62157
- const matchingErrors = [...this._errors, ...this._inFlightErrors].filter((e) => e.scopeId === id);
62158
- const eventSnapshot = matching.reduce(accumulate, emptySnap());
62159
- return matchingErrors.reduce((snap, e) => accumulateError(snap, e), eventSnapshot);
62160
- },
62161
- close: () => {
62162
- if (closed)
62163
- return;
62164
- closed = true;
62165
- this._openScopes.delete(id);
62166
- }
62167
- };
62168
- }
62169
- async drain() {
62170
- const openScopeCount = this._openScopes.size;
62171
- if (openScopeCount > 0) {
62172
- _costAggDeps.getSafeLogger()?.warn("cost-aggregator", "drain called with open scopes", { openScopeCount });
62173
- }
62174
- this._draining = true;
62175
- try {
62176
- let committed = [...this._events.splice(0), ...this._errors.splice(0)];
62177
- if (committed.length === 0 && this._inFlightEvents.length === 0 && this._inFlightErrors.length === 0) {
62178
- return;
62179
- }
62180
- mkdirSync3(this._drainDir, { recursive: true });
62181
- const path11 = join51(this._drainDir, `${this._runId}.jsonl`);
62182
- let first = true;
62183
- let pass = 0;
62184
- while (first || this._inFlightEvents.length > 0 || this._inFlightErrors.length > 0) {
62185
- if (++pass > MAX_DRAIN_PASSES) {
62186
- _costAggDeps.getSafeLogger()?.warn("cost-aggregator", "drain exceeded max passes \u2014 late events keep arriving", {
62187
- pass,
62188
- inFlightEvents: this._inFlightEvents.length,
62189
- inFlightErrors: this._inFlightErrors.length
62190
- });
62191
- break;
62192
- }
62193
- first = false;
62194
- const late = [...this._inFlightEvents.splice(0), ...this._inFlightErrors.splice(0)];
62195
- committed = [...committed, ...late].sort((a, b) => a.ts - b.ts);
62196
- await _costAggDeps.write(path11, `${committed.map((e) => JSON.stringify(e)).join(`
62197
- `)}
62198
- `);
62199
- }
62200
- const committedEvents = committed.filter((e) => !(("kind" in e) && e.kind === "error"));
62201
- const committedErrors = committed.filter((e) => ("kind" in e) && e.kind === "error");
62202
- this._events.length = 0;
62203
- this._events.push(...committedEvents);
62204
- this._errors.length = 0;
62205
- this._errors.push(...committedErrors);
62206
- } finally {
62207
- this._draining = false;
62208
- }
62209
- }
62210
- }
62211
- var MAX_DRAIN_PASSES = 20, EMPTY_SNAPSHOT, _costAggDeps;
62212
- var init_cost_aggregator = __esm(() => {
62213
- init_logger2();
62214
- EMPTY_SNAPSHOT = {
62215
- totalCostUsd: 0,
62216
- totalEstimatedCostUsd: 0,
62217
- totalExactCostUsd: 0,
62218
- totalInputTokens: 0,
62219
- totalOutputTokens: 0,
62220
- totalCacheReadTokens: 0,
62221
- totalCacheWriteTokens: 0,
62222
- tokenUsageCount: 0,
62223
- callCount: 0,
62224
- errorCount: 0,
62225
- totalErrorCostUsd: 0
62226
- };
62227
- _costAggDeps = {
62228
- write: (path11, data) => Bun.write(path11, data),
62229
- getSafeLogger: () => getSafeLogger(),
62230
- newCorrelationId: makeCorrelationId
62231
- };
62232
- });
62233
-
62234
62402
  // src/runtime/middleware/agent-stream-logging.ts
62235
62403
  function attachAgentStreamLogging(bus, runId) {
62236
62404
  const activeCalls = new Map;
@@ -63653,6 +63821,8 @@ __export(exports_runtime, {
63653
63821
  projectOutputDir: () => projectOutputDir,
63654
63822
  readProjectIdentity: () => readProjectIdentity,
63655
63823
  resolveIdleWatchdogSettings: () => resolveIdleWatchdogSettings,
63824
+ storySpendUsd: () => storySpendUsd,
63825
+ totalSpendUsd: () => totalSpendUsd,
63656
63826
  writeProjectIdentity: () => writeProjectIdentity
63657
63827
  });
63658
63828
  import { basename as basename9, join as join53 } from "path";
@@ -63705,7 +63875,8 @@ function createRuntime(config2, workdir, opts) {
63705
63875
  runId,
63706
63876
  sendPrompt: (handle, prompt, sendOpts) => sessionManager.sendPrompt(handle, prompt, sendOpts),
63707
63877
  runHop: createSessionRunHop(sessionManager, () => agentManager),
63708
- dispatchEvents
63878
+ dispatchEvents,
63879
+ models: config2.models
63709
63880
  };
63710
63881
  if (opts?.agentManager instanceof AgentManager) {
63711
63882
  opts.agentManager.configureRuntime({ ...agentManagerOpts, pidRegistry });
@@ -66115,7 +66286,10 @@ class DebateRunner {
66115
66286
  resolverCallContext: { ...this.ctx, scopeId: resolverScope.scopeId }
66116
66287
  };
66117
66288
  const result = await runner(ctxWithScopes, prompt);
66118
- return { ...result, totalCostUsd: debaterScope.snapshot().totalCostUsd + resolverScope.snapshot().totalCostUsd };
66289
+ return {
66290
+ ...result,
66291
+ totalCostUsd: totalSpendUsd(debaterScope.snapshot()) + totalSpendUsd(resolverScope.snapshot())
66292
+ };
66119
66293
  } finally {
66120
66294
  debaterScope.close();
66121
66295
  resolverScope.close();
@@ -66153,7 +66327,7 @@ class DebateRunner {
66153
66327
  debaters: resolved.map((r) => r.debater.agent)
66154
66328
  });
66155
66329
  const concurrencyLimit = this.config?.debate?.maxConcurrentDebaters ?? 2;
66156
- const scopeTotal = () => prePhaseScope.snapshot().totalCostUsd + debaterScope.snapshot().totalCostUsd + resolverScope.snapshot().totalCostUsd + verifierScope.snapshot().totalCostUsd;
66330
+ const scopeTotal = () => totalSpendUsd(prePhaseScope.snapshot()) + totalSpendUsd(debaterScope.snapshot()) + totalSpendUsd(resolverScope.snapshot()) + totalSpendUsd(verifierScope.snapshot());
66157
66331
  let taskContext = prompt;
66158
66332
  let preDebateManifestSection;
66159
66333
  if (config2.preDebatePhase) {
@@ -71238,7 +71412,8 @@ function collectFixCycleIteration(context, entry, data) {
71238
71412
  outcome,
71239
71413
  findingsBefore: numberValue(data.findingsBefore, 0),
71240
71414
  findingsAfter: numberValue(data.findingsAfter, 0),
71241
- costUsd: numberValue(data.costUsd, 0)
71415
+ costUsd: numberValue(data.costUsd, 0),
71416
+ errorCostUsd: numberValue(data.errorCostUsd, 0)
71242
71417
  }
71243
71418
  };
71244
71419
  }
@@ -76410,7 +76585,8 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
76410
76585
  throw err;
76411
76586
  } finally {
76412
76587
  const snapshot = scope.snapshot();
76413
- phaseCosts[opName] = (phaseCosts[opName] ?? 0) + snapshot.totalCostUsd;
76588
+ const phaseSpend = totalSpendUsd(snapshot);
76589
+ phaseCosts[opName] = (phaseCosts[opName] ?? 0) + phaseSpend;
76414
76590
  scope.close();
76415
76591
  if (ctx.storyId) {
76416
76592
  const phaseDetails = buildPhaseDetails(opName, phaseOutputs[opName], isThreeSession, ctx.packageView.config, advIterationBefore, ctx.fixStrategy);
@@ -76420,7 +76596,8 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
76420
76596
  phase: opName,
76421
76597
  outcome,
76422
76598
  durationMs: Date.now() - phaseStartedAt,
76423
- costUsd: snapshot.totalCostUsd,
76599
+ costUsd: phaseSpend,
76600
+ ...snapshot.totalErrorCostUsd > 0 ? { errorCostUsd: snapshot.totalErrorCostUsd } : {},
76424
76601
  ...ctx.phaseTelemetry ? {
76425
76602
  tier: ctx.phaseTelemetry.tier,
76426
76603
  testStrategy: ctx.phaseTelemetry.testStrategy,
@@ -76525,6 +76702,7 @@ var init_run_phase = __esm(() => {
76525
76702
  init_operations();
76526
76703
  init_pipeline2();
76527
76704
  init_review2();
76705
+ init_runtime2();
76528
76706
  init_git();
76529
76707
  init_resume_plan();
76530
76708
  init_non_blocking_fix();
@@ -78319,11 +78497,13 @@ async function handleNoTierAvailable(ctx, failureCategory) {
78319
78497
  if (ctx.featureDir) {
78320
78498
  await appendProgress(ctx.featureDir, ctx.story.id, "paused", `${ctx.story.title} \u2014 Execution stopped (needs human review)`);
78321
78499
  }
78500
+ const spend2 = storySpendUsd(ctx.runtime?.costAggregator, ctx.story.id, ctx.totalCost);
78322
78501
  pipelineEventBus.emit({
78323
78502
  type: "story:paused",
78324
78503
  storyId: ctx.story.id,
78325
78504
  reason: pauseReason,
78326
- cost: ctx.runtime?.costAggregator.byStory()[ctx.story.id]?.totalCostUsd ?? ctx.totalCost
78505
+ cost: spend2.cost,
78506
+ ...spend2.errorCostUsd > 0 ? { errorCostUsd: spend2.errorCostUsd } : {}
78327
78507
  });
78328
78508
  return { outcome: "paused", prdDirty: true, prd: pausedPrd };
78329
78509
  }
@@ -78336,13 +78516,15 @@ async function handleNoTierAvailable(ctx, failureCategory) {
78336
78516
  if (ctx.featureDir) {
78337
78517
  await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 Execution failed`);
78338
78518
  }
78519
+ const spend = storySpendUsd(ctx.runtime?.costAggregator, ctx.story.id, ctx.totalCost);
78339
78520
  pipelineEventBus.emit({
78340
78521
  type: "story:failed",
78341
78522
  storyId: ctx.story.id,
78342
78523
  story: { id: ctx.story.id, title: ctx.story.title, status: ctx.story.status, attempts: ctx.story.attempts },
78343
78524
  reason: "Execution failed",
78344
78525
  countsTowardEscalation: true,
78345
- cost: ctx.runtime?.costAggregator.byStory()[ctx.story.id]?.totalCostUsd ?? ctx.totalCost
78526
+ cost: spend.cost,
78527
+ ...spend.errorCostUsd > 0 ? { errorCostUsd: spend.errorCostUsd } : {}
78346
78528
  });
78347
78529
  return { outcome: "failed", prdDirty: true, prd: failedPrd };
78348
78530
  }
@@ -78362,11 +78544,13 @@ async function handleMaxAttemptsReached(ctx, failureCategory) {
78362
78544
  if (ctx.featureDir) {
78363
78545
  await appendProgress(ctx.featureDir, ctx.story.id, "paused", `${ctx.story.title} \u2014 Max attempts reached (needs human review)`);
78364
78546
  }
78547
+ const spend2 = storySpendUsd(ctx.runtime?.costAggregator, ctx.story.id, ctx.totalCost);
78365
78548
  pipelineEventBus.emit({
78366
78549
  type: "story:paused",
78367
78550
  storyId: ctx.story.id,
78368
78551
  reason: pauseReason,
78369
- cost: ctx.runtime?.costAggregator.byStory()[ctx.story.id]?.totalCostUsd ?? ctx.totalCost
78552
+ cost: spend2.cost,
78553
+ ...spend2.errorCostUsd > 0 ? { errorCostUsd: spend2.errorCostUsd } : {}
78370
78554
  });
78371
78555
  return { outcome: "paused", prdDirty: true, prd: pausedPrd };
78372
78556
  }
@@ -78380,13 +78564,15 @@ async function handleMaxAttemptsReached(ctx, failureCategory) {
78380
78564
  if (ctx.featureDir) {
78381
78565
  await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 Max attempts reached`);
78382
78566
  }
78567
+ const spend = storySpendUsd(ctx.runtime?.costAggregator, ctx.story.id, ctx.totalCost);
78383
78568
  pipelineEventBus.emit({
78384
78569
  type: "story:failed",
78385
78570
  storyId: ctx.story.id,
78386
78571
  story: { id: ctx.story.id, title: ctx.story.title, status: ctx.story.status, attempts: ctx.story.attempts },
78387
78572
  reason: "Max attempts reached",
78388
78573
  countsTowardEscalation: true,
78389
- cost: ctx.runtime?.costAggregator.byStory()[ctx.story.id]?.totalCostUsd ?? ctx.totalCost
78574
+ cost: spend.cost,
78575
+ ...spend.errorCostUsd > 0 ? { errorCostUsd: spend.errorCostUsd } : {}
78390
78576
  });
78391
78577
  return { outcome: "failed", prdDirty: true, prd: failedPrd };
78392
78578
  }
@@ -78394,6 +78580,7 @@ var init_tier_outcome = __esm(() => {
78394
78580
  init_logger2();
78395
78581
  init_pipeline2();
78396
78582
  init_prd();
78583
+ init_runtime2();
78397
78584
  init_progress();
78398
78585
  init_quote_integrity();
78399
78586
  init_tier_escalation();
@@ -78536,6 +78723,7 @@ async function preIterationTierCheck(story, routing, config2, prd, prdPath, feat
78536
78723
  await appendProgress(featureDir2, story.id, "failed", `${story.title} \u2014 All tiers exhausted`);
78537
78724
  }
78538
78725
  const failedStory = failedPrd.userStories.find((s) => s.id === story.id) ?? story;
78726
+ const spend = storySpendUsd(runtime?.costAggregator, story.id, totalCost);
78539
78727
  pipelineEventBus.emit({
78540
78728
  type: "story:failed",
78541
78729
  storyId: story.id,
@@ -78544,7 +78732,8 @@ async function preIterationTierCheck(story, routing, config2, prd, prdPath, feat
78544
78732
  countsTowardEscalation: true,
78545
78733
  feature,
78546
78734
  attempts: failedStory.attempts,
78547
- cost: runtime?.costAggregator.byStory()[story.id]?.totalCostUsd ?? totalCost
78735
+ cost: spend.cost,
78736
+ ...spend.errorCostUsd > 0 ? { errorCostUsd: spend.errorCostUsd } : {}
78548
78737
  });
78549
78738
  return { shouldSkipIteration: true, prdDirty: true, prd: failedPrd };
78550
78739
  }
@@ -78669,6 +78858,7 @@ var init_tier_escalation = __esm(() => {
78669
78858
  init_logger2();
78670
78859
  init_pipeline2();
78671
78860
  init_prd();
78861
+ init_runtime2();
78672
78862
  init_escalation();
78673
78863
  init_progress();
78674
78864
  init_quote_integrity();
@@ -79873,6 +80063,7 @@ async function failStoryAfterMerge(ctx, prd, reason) {
79873
80063
  if (ctx.featureDir) {
79874
80064
  await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 ${reason}`);
79875
80065
  }
80066
+ const spend = storySpendUsd(ctx.runtime.costAggregator, ctx.story.id, ctx.totalCost);
79876
80067
  pipelineEventBus.emit({
79877
80068
  type: "story:failed",
79878
80069
  storyId: ctx.story.id,
@@ -79881,7 +80072,8 @@ async function failStoryAfterMerge(ctx, prd, reason) {
79881
80072
  countsTowardEscalation: false,
79882
80073
  feature: ctx.feature,
79883
80074
  attempts: ctx.story.attempts,
79884
- cost: ctx.runtime.costAggregator.byStory()[ctx.story.id]?.totalCostUsd ?? ctx.totalCost
80075
+ cost: spend.cost,
80076
+ ...spend.errorCostUsd > 0 ? { errorCostUsd: spend.errorCostUsd } : {}
79885
80077
  });
79886
80078
  }
79887
80079
  function filterOutputFiles(files) {
@@ -80014,11 +80206,13 @@ async function handlePipelineFailure(ctx, pipelineResult) {
80014
80206
  if (hasWorktree(ctx.workdir, ctx.story.id)) {
80015
80207
  await removeWorktreeDirectory(ctx.workdir, ctx.story.id);
80016
80208
  }
80209
+ const spend = storySpendUsd(ctx.runtime.costAggregator, ctx.story.id, ctx.totalCost);
80017
80210
  pipelineEventBus.emit({
80018
80211
  type: "story:paused",
80019
80212
  storyId: ctx.story.id,
80020
80213
  reason: pipelineResult.reason || "Pipeline paused",
80021
- cost: ctx.runtime.costAggregator.byStory()[ctx.story.id]?.totalCostUsd ?? ctx.totalCost
80214
+ cost: spend.cost,
80215
+ ...spend.errorCostUsd > 0 ? { errorCostUsd: spend.errorCostUsd } : {}
80022
80216
  });
80023
80217
  break;
80024
80218
  }
@@ -80031,7 +80225,7 @@ async function handlePipelineFailure(ctx, pipelineResult) {
80031
80225
  });
80032
80226
  prdDirty = true;
80033
80227
  break;
80034
- case "fail":
80228
+ case "fail": {
80035
80229
  markStoryFailed(prd, ctx.story.id, pipelineResult.context.tddFailureCategory, pipelineResult.stoppedAtStage, ctx.statusWriter);
80036
80230
  await savePRD(prd, ctx.prdPath);
80037
80231
  prdDirty = true;
@@ -80046,6 +80240,7 @@ async function handlePipelineFailure(ctx, pipelineResult) {
80046
80240
  if (ctx.featureDir) {
80047
80241
  await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 ${pipelineResult.reason}`);
80048
80242
  }
80243
+ const spend = storySpendUsd(ctx.runtime.costAggregator, ctx.story.id, ctx.totalCost);
80049
80244
  pipelineEventBus.emit({
80050
80245
  type: "story:failed",
80051
80246
  storyId: ctx.story.id,
@@ -80054,7 +80249,8 @@ async function handlePipelineFailure(ctx, pipelineResult) {
80054
80249
  countsTowardEscalation: true,
80055
80250
  feature: ctx.feature,
80056
80251
  attempts: ctx.story.attempts,
80057
- cost: ctx.runtime.costAggregator.byStory()[ctx.story.id]?.totalCostUsd ?? ctx.totalCost
80252
+ cost: spend.cost,
80253
+ ...spend.errorCostUsd > 0 ? { errorCostUsd: spend.errorCostUsd } : {}
80058
80254
  });
80059
80255
  if (ctx.story.attempts !== undefined && ctx.story.attempts >= ctx.config.execution.rectification.maxAttemptsTotal) {
80060
80256
  await pipelineEventBus.emitAsync({
@@ -80066,6 +80262,7 @@ async function handlePipelineFailure(ctx, pipelineResult) {
80066
80262
  });
80067
80263
  }
80068
80264
  break;
80265
+ }
80069
80266
  case "escalate": {
80070
80267
  const runtimeCrashResult = pipelineResult.context.tddFailureCategory === "runtime-crash" ? { status: "RUNTIME_CRASH", success: false } : undefined;
80071
80268
  const escalationResult = await _resultHandlerDeps.handleTierEscalation({
@@ -80106,6 +80303,7 @@ var init_pipeline_result_handler = __esm(() => {
80106
80303
  init_pipeline2();
80107
80304
  init_logger2();
80108
80305
  init_prd();
80306
+ init_runtime2();
80109
80307
  init_bun_deps();
80110
80308
  init_git();
80111
80309
  init_worktree();
@@ -81127,7 +81325,7 @@ function isExecutionFailure(story) {
81127
81325
  return story != null && (story.status === "failed" || story.status === "regression-failed");
81128
81326
  }
81129
81327
  function synthesizeBackfillMetric(args) {
81130
- const { storyId, story, totalCostUsd, config: config2, defaultAgent, timestamp, fallbackHops, runtimeCrashes } = args;
81328
+ const { storyId, story, totalCostUsd, errorCostUsd, config: config2, defaultAgent, timestamp, fallbackHops, runtimeCrashes } = args;
81131
81329
  if (story != null && isExecutionFailure(story)) {
81132
81330
  const tier = story.routing?.modelTier ?? "balanced";
81133
81331
  const agent = story.routing?.agent ?? defaultAgent;
@@ -81149,6 +81347,7 @@ function synthesizeBackfillMetric(args) {
81149
81347
  finalTier,
81150
81348
  success: false,
81151
81349
  cost: totalCostUsd,
81350
+ ...errorCostUsd > 0 ? { errorCostUsd } : {},
81152
81351
  durationMs: 0,
81153
81352
  firstPassSuccess: false,
81154
81353
  startedAt: timestamp,
@@ -81167,6 +81366,7 @@ function synthesizeBackfillMetric(args) {
81167
81366
  finalTier: "balanced",
81168
81367
  success: story?.passes ?? false,
81169
81368
  cost: totalCostUsd,
81369
+ ...errorCostUsd > 0 ? { errorCostUsd } : {},
81170
81370
  durationMs: 0,
81171
81371
  firstPassSuccess: story?.passes ?? false,
81172
81372
  startedAt: timestamp,
@@ -81210,7 +81410,9 @@ function applyBackfill(input) {
81210
81410
  stories
81211
81411
  });
81212
81412
  for (const storyId of domain2) {
81213
- const totalCostUsd = aggByStory[storyId]?.totalCostUsd ?? 0;
81413
+ const snap = aggByStory[storyId];
81414
+ const errorCostUsd = snap?.totalErrorCostUsd ?? 0;
81415
+ const totalCostUsd = (snap?.totalCostUsd ?? 0) + errorCostUsd;
81214
81416
  const fallbackHops = toFallbackHops(agentFallbacks.get(storyId), storyId);
81215
81417
  const runtimeCrashes = runtimeCrashRetries.get(storyId) ?? 0;
81216
81418
  const story = storyById.get(storyId);
@@ -81223,6 +81425,7 @@ function applyBackfill(input) {
81223
81425
  storyId,
81224
81426
  story,
81225
81427
  totalCostUsd,
81428
+ errorCostUsd,
81226
81429
  config: config2,
81227
81430
  defaultAgent,
81228
81431
  timestamp,
@@ -81233,7 +81436,11 @@ function applyBackfill(input) {
81233
81436
  }
81234
81437
  const existing = allStoryMetrics[existingIdx];
81235
81438
  if (totalCostUsd > (existing.cost ?? 0)) {
81236
- allStoryMetrics[existingIdx] = { ...existing, cost: totalCostUsd };
81439
+ allStoryMetrics[existingIdx] = {
81440
+ ...existing,
81441
+ cost: totalCostUsd,
81442
+ ...errorCostUsd > 0 ? { errorCostUsd } : {}
81443
+ };
81237
81444
  }
81238
81445
  }
81239
81446
  }
@@ -82167,7 +82374,9 @@ async function handleRunCompletion(options) {
82167
82374
  });
82168
82375
  }
82169
82376
  const aggSnap = options.runtime.costAggregator.snapshot();
82170
- const reportedTotal = aggSnap.totalCostUsd;
82377
+ const reportedTotal = totalSpendUsd(aggSnap);
82378
+ const errorCostUsd = aggSnap.totalErrorCostUsd;
82379
+ const errorCostField = errorCostUsd > 0 ? { errorCostUsd } : {};
82171
82380
  const aggByStage = options.runtime.costAggregator.byStage();
82172
82381
  const aggByStory = options.runtime.costAggregator.byStory();
82173
82382
  applyBackfill({
@@ -82200,6 +82409,7 @@ async function handleRunCompletion(options) {
82200
82409
  pausedStories: finalCounts.paused,
82201
82410
  durationMs,
82202
82411
  totalCost: reportedTotal,
82412
+ ...errorCostField,
82203
82413
  ...fallbackAggregate && { fallback: fallbackAggregate }
82204
82414
  });
82205
82415
  await pipelineEventBus.drain();
@@ -82209,6 +82419,7 @@ async function handleRunCompletion(options) {
82209
82419
  startedAt,
82210
82420
  completedAt: runCompletedAt,
82211
82421
  totalCost: reportedTotal,
82422
+ ...errorCostField,
82212
82423
  totalStories: allStoryMetrics.length,
82213
82424
  storiesCompleted,
82214
82425
  storiesFailed: finalCounts.failed,
@@ -82273,6 +82484,7 @@ async function handleRunCompletion(options) {
82273
82484
  storiesFailed: finalCounts.failed,
82274
82485
  storiesPending: finalCounts.pending,
82275
82486
  totalCost: reportedTotal,
82487
+ ...errorCostField,
82276
82488
  ...contextCostUsd > 0 && { contextCostUsd },
82277
82489
  ...Object.keys(aggByStage).length > 0 && { costByStage: aggByStage },
82278
82490
  ...Object.keys(aggByStory).length > 0 && { costByStory: aggByStory },
@@ -82307,6 +82519,7 @@ var init_run_completion = __esm(() => {
82307
82519
  init_pipeline2();
82308
82520
  init_prd();
82309
82521
  init_project();
82522
+ init_runtime2();
82310
82523
  init_session2();
82311
82524
  init_detect();
82312
82525
  init_verification();
@@ -88898,6 +89111,7 @@ var init_phase = __esm(() => {
88898
89111
  init_forge();
88899
89112
  init_logger2();
88900
89113
  init_pipeline2();
89114
+ init_runtime2();
88901
89115
  init_config3();
88902
89116
  init_context3();
88903
89117
  init_machine();
@@ -88911,7 +89125,7 @@ var init_phase = __esm(() => {
88911
89125
  runFinishMachine,
88912
89126
  sendTelegramNotify,
88913
89127
  now: () => new Date().toISOString(),
88914
- snapshotCost: (runtime) => runtime.costAggregator.snapshot().totalCostUsd
89128
+ snapshotCost: (runtime) => totalSpendUsd(runtime.costAggregator.snapshot())
88915
89129
  };
88916
89130
  });
88917
89131
 
@@ -89592,6 +89806,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime, projectKey) {
89592
89806
  status: "completed",
89593
89807
  runElapsedMs: ev.runElapsedMs,
89594
89808
  cost: ev.cost ?? 0,
89809
+ ...ev.errorCostUsd !== undefined ? { errorCostUsd: ev.errorCostUsd } : {},
89595
89810
  tier: ev.modelTier ?? "balanced",
89596
89811
  testStrategy: ev.testStrategy ?? "test-after"
89597
89812
  });
@@ -89613,7 +89828,8 @@ function wireReporters(bus, pluginRegistry, runId, startTime, projectKey) {
89613
89828
  storyId: ev.storyId,
89614
89829
  status: "failed",
89615
89830
  runElapsedMs: Date.now() - startTime,
89616
- cost: 0,
89831
+ cost: ev.cost ?? 0,
89832
+ ...ev.errorCostUsd !== undefined ? { errorCostUsd: ev.errorCostUsd } : {},
89617
89833
  tier: "balanced",
89618
89834
  testStrategy: "test-after"
89619
89835
  });
@@ -89635,7 +89851,8 @@ function wireReporters(bus, pluginRegistry, runId, startTime, projectKey) {
89635
89851
  storyId: ev.storyId,
89636
89852
  status: "paused",
89637
89853
  runElapsedMs: Date.now() - startTime,
89638
- cost: 0,
89854
+ cost: ev.cost ?? 0,
89855
+ ...ev.errorCostUsd !== undefined ? { errorCostUsd: ev.errorCostUsd } : {},
89639
89856
  tier: "balanced",
89640
89857
  testStrategy: "test-after"
89641
89858
  });
@@ -89675,6 +89892,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime, projectKey) {
89675
89892
  runId,
89676
89893
  totalDurationMs: Date.now() - startTime,
89677
89894
  totalCost: ev.totalCost ?? 0,
89895
+ ...ev.errorCostUsd !== undefined ? { errorCostUsd: ev.errorCostUsd } : {},
89678
89896
  storySummary: {
89679
89897
  completed: ev.passedStories,
89680
89898
  failed: ev.failedStories,
@@ -89698,6 +89916,30 @@ var init_reporters = __esm(() => {
89698
89916
  init_logger2();
89699
89917
  });
89700
89918
 
89919
+ // src/execution/cost-guard.ts
89920
+ async function enforceCostLimit(ctx, totalCost2, costLimit, storyId) {
89921
+ const enforcedCost = Math.max(totalCost2, totalSpendUsd(ctx.runtime.costAggregator.snapshot()));
89922
+ if (enforcedCost < costLimit)
89923
+ return { stop: false, enforcedCost };
89924
+ const shouldProceed = ctx.interactionChain && isTriggerEnabled("cost-exceeded", ctx.config) ? await checkCostExceeded({ featureName: ctx.feature, cost: enforcedCost, limit: costLimit }, ctx.config, ctx.interactionChain) : false;
89925
+ if (!shouldProceed) {
89926
+ pipelineEventBus.emit({
89927
+ type: "run:paused",
89928
+ reason: `Cost limit reached: $${enforcedCost.toFixed(2)}`,
89929
+ ...storyId !== undefined ? { storyId } : {},
89930
+ cost: enforcedCost
89931
+ });
89932
+ return { stop: true, enforcedCost };
89933
+ }
89934
+ pipelineEventBus.emit({ type: "run:resumed", feature: ctx.feature });
89935
+ return { stop: false, enforcedCost };
89936
+ }
89937
+ var init_cost_guard = __esm(() => {
89938
+ init_runtime2();
89939
+ init_triggers();
89940
+ init_event_bus();
89941
+ });
89942
+
89701
89943
  // src/execution/cost-warning.ts
89702
89944
  async function maybeSendCostWarning(ctx, enforcedCost, costLimit, warningSent) {
89703
89945
  if (warningSent || !ctx.interactionChain || !isTriggerEnabled("cost-warning", ctx.config))
@@ -90231,23 +90473,6 @@ async function closeStoryIfTerminal(ctx, storyId, iter) {
90231
90473
  await closeStorySessions(ctx.sessionManager, storyId, ctx.agentGetFn);
90232
90474
  ctx.agentManager?.resetTransientUnavailable?.();
90233
90475
  }
90234
- async function enforceCostLimit(ctx, totalCost2, costLimit, storyId) {
90235
- const enforcedCost = Math.max(totalCost2, ctx.runtime.costAggregator.snapshot().totalCostUsd);
90236
- if (enforcedCost < costLimit)
90237
- return { stop: false, enforcedCost };
90238
- const shouldProceed = ctx.interactionChain && isTriggerEnabled("cost-exceeded", ctx.config) ? await checkCostExceeded({ featureName: ctx.feature, cost: enforcedCost, limit: costLimit }, ctx.config, ctx.interactionChain) : false;
90239
- if (!shouldProceed) {
90240
- pipelineEventBus.emit({
90241
- type: "run:paused",
90242
- reason: `Cost limit reached: $${enforcedCost.toFixed(2)}`,
90243
- ...storyId !== undefined ? { storyId } : {},
90244
- cost: enforcedCost
90245
- });
90246
- return { stop: true, enforcedCost };
90247
- }
90248
- pipelineEventBus.emit({ type: "run:resumed", feature: ctx.feature });
90249
- return { stop: false, enforcedCost };
90250
- }
90251
90476
  async function executeUnified(ctx, initialPrd) {
90252
90477
  const logger = getSafeLogger();
90253
90478
  let prd = initialPrd;
@@ -90666,7 +90891,7 @@ async function executeUnified(ctx, initialPrd) {
90666
90891
  iter.prdDirty
90667
90892
  ];
90668
90893
  await closeStoryIfTerminal(ctx, selection.story.id, iter);
90669
- warningSent = await maybeSendCostWarning(ctx, Math.max(totalCost2, ctx.runtime.costAggregator.snapshot().totalCostUsd), costLimit, warningSent);
90894
+ warningSent = await maybeSendCostWarning(ctx, Math.max(totalCost2, totalSpendUsd(ctx.runtime.costAggregator.snapshot())), costLimit, warningSent);
90670
90895
  if (iter.prdDirty) {
90671
90896
  prd = await loadPRD(ctx.prdPath);
90672
90897
  prdDirty = false;
@@ -90740,6 +90965,7 @@ function reconcileBatchOutcome(prd, batchResult) {
90740
90965
  var TERMINAL_ACTIONS, _prevRunUnsubscribers, _unifiedExecutorDeps;
90741
90966
  var init_unified_executor = __esm(() => {
90742
90967
  init_pipeline2();
90968
+ init_runtime2();
90743
90969
  init_triggers();
90744
90970
  init_logger2();
90745
90971
  init_metrics();
@@ -90754,6 +90980,7 @@ var init_unified_executor = __esm(() => {
90754
90980
  init_routing();
90755
90981
  init_bun_deps();
90756
90982
  init_path_filters();
90983
+ init_cost_guard();
90757
90984
  init_cost_warning();
90758
90985
  init_crash_recovery();
90759
90986
  init_deferred_review();
@@ -91442,6 +91669,7 @@ var init_completion = __esm(() => {
91442
91669
  init_logger2();
91443
91670
  init_metrics();
91444
91671
  init_prd();
91672
+ init_runtime2();
91445
91673
  init_git();
91446
91674
  init_verification();
91447
91675
  init_event_bus();
@@ -91452,7 +91680,8 @@ var init_completion = __esm(() => {
91452
91680
  async execute(ctx) {
91453
91681
  const logger = getLogger();
91454
91682
  const isBatch = ctx.stories.length > 1;
91455
- const sessionCost = ctx.runtime.costAggregator.byStory()[ctx.story.id]?.totalCostUsd ?? 0;
91683
+ const sessionSpend = storySpendUsd(ctx.runtime.costAggregator, ctx.story.id, 0);
91684
+ const sessionCost = sessionSpend.cost;
91456
91685
  const persistPrd2 = ctx.skipPrdPersistence !== true;
91457
91686
  const prdPath = ctx.prdPath ?? join104(ctx.featureDir ?? featureDir(ctx.workdir, "unknown"), "prd.json");
91458
91687
  const storyStartTime = ctx.storyStartTime || new Date().toISOString();
@@ -91497,6 +91726,7 @@ var init_completion = __esm(() => {
91497
91726
  markStoryPassed(ctx.prd, completedStory.id);
91498
91727
  }
91499
91728
  const costPerStory = sessionCost / ctx.stories.length;
91729
+ const errorCostPerStory = sessionSpend.errorCostUsd / ctx.stories.length;
91500
91730
  logger.info("completion", "Story passed", {
91501
91731
  storyId: completedStory.id,
91502
91732
  cost: costPerStory
@@ -91518,6 +91748,7 @@ var init_completion = __esm(() => {
91518
91748
  passed: true,
91519
91749
  runElapsedMs: storyMetric?.durationMs ?? 0,
91520
91750
  cost: costPerStory,
91751
+ ...errorCostPerStory > 0 ? { errorCostUsd: errorCostPerStory } : {},
91521
91752
  modelTier: ctx.routing?.modelTier,
91522
91753
  testStrategy: ctx.routing?.testStrategy
91523
91754
  });
@@ -93886,6 +94117,7 @@ async function displayLastRunMetrics(workdir) {
93886
94117
  storiesCompleted: lastRun.storiesCompleted,
93887
94118
  storiesFailed: lastRun.storiesFailed,
93888
94119
  totalCost: lastRun.totalCost,
94120
+ ...lastRun.errorCostUsd !== undefined ? { errorCostUsd: lastRun.errorCostUsd } : {},
93889
94121
  avgCostPerStory: reportedLastRun?.avgCostPerStory ?? 0
93890
94122
  });
93891
94123
  const sortedStories = [...lastRun.stories].sort((a, b) => b.cost - a.cost);