@nathapp/nax 0.82.0-canary.4 → 0.82.0-canary.5
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.
- package/dist/nax.js +599 -364
- package/package.json +2 -2
package/dist/nax.js
CHANGED
|
@@ -23676,8 +23676,9 @@ var init_client = __esm(() => {
|
|
|
23676
23676
|
});
|
|
23677
23677
|
|
|
23678
23678
|
// src/agents/native/errors.ts
|
|
23679
|
-
function toAdapterFailure(
|
|
23680
|
-
|
|
23679
|
+
function toAdapterFailure(protocolError) {
|
|
23680
|
+
const base = FAILURES[protocolError.kind] ?? UNKNOWN;
|
|
23681
|
+
return protocolError.retryAfter === undefined ? base : { ...base, retryAfterSeconds: protocolError.retryAfter };
|
|
23681
23682
|
}
|
|
23682
23683
|
var FAILURES, UNKNOWN;
|
|
23683
23684
|
var init_errors4 = __esm(() => {
|
|
@@ -24270,6 +24271,9 @@ function turnRetryDelayMs(err, retryIndex, config2, random = Math.random, remain
|
|
|
24270
24271
|
const delayMs = retryAfter !== undefined ? retryAfter * 1000 : backoffMs(retryIndex, config2.baseDelayMs, random);
|
|
24271
24272
|
return remainingMs === undefined ? delayMs : Math.min(delayMs, remainingMs);
|
|
24272
24273
|
}
|
|
24274
|
+
function turnRetryFitsBudget(delayMs, remainingMs) {
|
|
24275
|
+
return remainingMs === undefined || delayMs <= remainingMs;
|
|
24276
|
+
}
|
|
24273
24277
|
function abortError(signal) {
|
|
24274
24278
|
return signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
24275
24279
|
}
|
|
@@ -24303,7 +24307,10 @@ async function retryTransportFault(firstError, deps) {
|
|
|
24303
24307
|
if (deps.deadline?.expired() === true || deps.signal?.aborted === true)
|
|
24304
24308
|
throw err;
|
|
24305
24309
|
const fault = err;
|
|
24306
|
-
const
|
|
24310
|
+
const remainingMs = deps.deadline?.remainingMs();
|
|
24311
|
+
const delayMs = turnRetryDelayMs(fault, retryIndex, deps.config, random);
|
|
24312
|
+
if (!turnRetryFitsBudget(delayMs, remainingMs))
|
|
24313
|
+
throw fault;
|
|
24307
24314
|
deps.onRetry?.(retryIndex + 1, delayMs, fault);
|
|
24308
24315
|
try {
|
|
24309
24316
|
await abortableSleep(delayMs, sleep2, deps.signal);
|
|
@@ -24320,7 +24327,7 @@ async function retryTransportFault(firstError, deps) {
|
|
|
24320
24327
|
}
|
|
24321
24328
|
var RETRYABLE_KINDS;
|
|
24322
24329
|
var init_turn_retry = __esm(() => {
|
|
24323
|
-
RETRYABLE_KINDS = new Set(["transport", "overloaded"]);
|
|
24330
|
+
RETRYABLE_KINDS = new Set(["transport", "overloaded", "rate-limit"]);
|
|
24324
24331
|
});
|
|
24325
24332
|
|
|
24326
24333
|
// src/agents/native/session/turn-loop.ts
|
|
@@ -24682,7 +24689,7 @@ class NativeAgentAdapter {
|
|
|
24682
24689
|
output: "",
|
|
24683
24690
|
tokenUsage: { inputTokens: 0, outputTokens: 0 },
|
|
24684
24691
|
estimatedCostUsd: 0,
|
|
24685
|
-
adapterFailure: toAdapterFailure(err.protocolError
|
|
24692
|
+
adapterFailure: toAdapterFailure(err.protocolError)
|
|
24686
24693
|
};
|
|
24687
24694
|
}
|
|
24688
24695
|
throw err;
|
|
@@ -24789,7 +24796,7 @@ class NativeAgentAdapter {
|
|
|
24789
24796
|
});
|
|
24790
24797
|
markNativeTurnOutcome(handle.id, true);
|
|
24791
24798
|
if (isProtocolStreamError(err)) {
|
|
24792
|
-
const adapterFailure = toAdapterFailure(err.protocolError
|
|
24799
|
+
const adapterFailure = toAdapterFailure(err.protocolError);
|
|
24793
24800
|
const usage = readNativeTurnFailureUsage(err);
|
|
24794
24801
|
throw new SessionTurnError(err.protocolError.message, false, adapterFailure.retriable, usage?.tokenUsage, usage?.costUsd, undefined, adapterFailure);
|
|
24795
24802
|
}
|
|
@@ -29606,6 +29613,100 @@ var init_idle_watchdog = __esm(() => {
|
|
|
29606
29613
|
};
|
|
29607
29614
|
});
|
|
29608
29615
|
|
|
29616
|
+
// src/agents/retry/failure-policy.ts
|
|
29617
|
+
function failurePolicyFor(outcome) {
|
|
29618
|
+
return POLICIES[outcome];
|
|
29619
|
+
}
|
|
29620
|
+
function resolveCooldownExpiry(failure, now) {
|
|
29621
|
+
const { cooldown } = failurePolicyFor(failure.outcome);
|
|
29622
|
+
if (cooldown === "none")
|
|
29623
|
+
return null;
|
|
29624
|
+
if (cooldown === "run")
|
|
29625
|
+
return "run";
|
|
29626
|
+
const retryAfter = failure.retryAfterSeconds;
|
|
29627
|
+
const fromProvider = retryAfter !== undefined && Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter * 1000 : undefined;
|
|
29628
|
+
return now + (fromProvider ?? cooldown.ms);
|
|
29629
|
+
}
|
|
29630
|
+
var TRANSIENT_COOLDOWN_MS = 60000, POLICIES;
|
|
29631
|
+
var init_failure_policy = __esm(() => {
|
|
29632
|
+
POLICIES = Object.freeze({
|
|
29633
|
+
"fail-auth": { sameAgentRetry: "none", swap: "immediate", cooldown: "run", terminalBackoff: false },
|
|
29634
|
+
"fail-quota": { sameAgentRetry: "none", swap: "immediate", cooldown: "run", terminalBackoff: false },
|
|
29635
|
+
"fail-rate-limit": {
|
|
29636
|
+
sameAgentRetry: "none",
|
|
29637
|
+
swap: "immediate",
|
|
29638
|
+
cooldown: { ms: TRANSIENT_COOLDOWN_MS },
|
|
29639
|
+
terminalBackoff: true
|
|
29640
|
+
},
|
|
29641
|
+
"fail-service-down": {
|
|
29642
|
+
sameAgentRetry: "adapter-error",
|
|
29643
|
+
swap: "immediate",
|
|
29644
|
+
cooldown: { ms: TRANSIENT_COOLDOWN_MS },
|
|
29645
|
+
terminalBackoff: true
|
|
29646
|
+
},
|
|
29647
|
+
"fail-stale": {
|
|
29648
|
+
sameAgentRetry: "stale",
|
|
29649
|
+
swap: "immediate",
|
|
29650
|
+
cooldown: { ms: TRANSIENT_COOLDOWN_MS },
|
|
29651
|
+
terminalBackoff: true
|
|
29652
|
+
},
|
|
29653
|
+
"fail-timeout": { sameAgentRetry: "timeout", swap: "after-retry-lane", cooldown: "none", terminalBackoff: false },
|
|
29654
|
+
"fail-adapter-error": {
|
|
29655
|
+
sameAgentRetry: "adapter-error",
|
|
29656
|
+
swap: "quality-gated",
|
|
29657
|
+
cooldown: "none",
|
|
29658
|
+
terminalBackoff: false
|
|
29659
|
+
},
|
|
29660
|
+
"fail-quality": { sameAgentRetry: "none", swap: "quality-gated", cooldown: "none", terminalBackoff: false },
|
|
29661
|
+
"fail-unknown": { sameAgentRetry: "none", swap: "quality-gated", cooldown: "none", terminalBackoff: false },
|
|
29662
|
+
"fail-aborted": { sameAgentRetry: "none", swap: "never", cooldown: "none", terminalBackoff: false }
|
|
29663
|
+
});
|
|
29664
|
+
});
|
|
29665
|
+
|
|
29666
|
+
// src/agents/cooldown-store.ts
|
|
29667
|
+
class CooldownStore {
|
|
29668
|
+
_now;
|
|
29669
|
+
_entries = new Map;
|
|
29670
|
+
constructor(_now) {
|
|
29671
|
+
this._now = _now;
|
|
29672
|
+
}
|
|
29673
|
+
mark(agent, failure) {
|
|
29674
|
+
const expiresAt = resolveCooldownExpiry(failure, this._now());
|
|
29675
|
+
if (expiresAt === null)
|
|
29676
|
+
return;
|
|
29677
|
+
this._entries.set(agent, { failure, expiresAt });
|
|
29678
|
+
}
|
|
29679
|
+
isCooling(agent) {
|
|
29680
|
+
return this._live(agent) !== undefined;
|
|
29681
|
+
}
|
|
29682
|
+
failureFor(agent) {
|
|
29683
|
+
return this._live(agent)?.failure;
|
|
29684
|
+
}
|
|
29685
|
+
sweepTransient() {
|
|
29686
|
+
for (const [agent, entry] of this._entries) {
|
|
29687
|
+
if (entry.expiresAt !== "run")
|
|
29688
|
+
this._entries.delete(agent);
|
|
29689
|
+
}
|
|
29690
|
+
}
|
|
29691
|
+
clear() {
|
|
29692
|
+
this._entries.clear();
|
|
29693
|
+
}
|
|
29694
|
+
_live(agent) {
|
|
29695
|
+
const entry = this._entries.get(agent);
|
|
29696
|
+
if (!entry)
|
|
29697
|
+
return;
|
|
29698
|
+
if (entry.expiresAt === "run")
|
|
29699
|
+
return entry;
|
|
29700
|
+
if (entry.expiresAt > this._now())
|
|
29701
|
+
return entry;
|
|
29702
|
+
this._entries.delete(agent);
|
|
29703
|
+
return;
|
|
29704
|
+
}
|
|
29705
|
+
}
|
|
29706
|
+
var init_cooldown_store = __esm(() => {
|
|
29707
|
+
init_failure_policy();
|
|
29708
|
+
});
|
|
29709
|
+
|
|
29609
29710
|
// src/agents/hop-budget.ts
|
|
29610
29711
|
function resolveStartAgent(source, primary, fallbackEnabled, storyId, logger) {
|
|
29611
29712
|
if (!fallbackEnabled || !source.isUnavailable(primary))
|
|
@@ -29806,95 +29907,56 @@ var init_manager_dispatch = __esm(() => {
|
|
|
29806
29907
|
init_session_types();
|
|
29807
29908
|
});
|
|
29808
29909
|
|
|
29809
|
-
// src/agents/
|
|
29810
|
-
function
|
|
29811
|
-
|
|
29812
|
-
|
|
29813
|
-
|
|
29814
|
-
|
|
29815
|
-
|
|
29816
|
-
|
|
29817
|
-
|
|
29818
|
-
|
|
29819
|
-
|
|
29820
|
-
|
|
29821
|
-
|
|
29822
|
-
|
|
29823
|
-
|
|
29824
|
-
|
|
29825
|
-
|
|
29826
|
-
|
|
29827
|
-
|
|
29828
|
-
|
|
29829
|
-
|
|
29830
|
-
|
|
29831
|
-
|
|
29832
|
-
|
|
29833
|
-
|
|
29834
|
-
|
|
29835
|
-
|
|
29836
|
-
|
|
29837
|
-
|
|
29838
|
-
|
|
29839
|
-
|
|
29840
|
-
|
|
29841
|
-
|
|
29842
|
-
|
|
29843
|
-
|
|
29844
|
-
|
|
29845
|
-
|
|
29846
|
-
|
|
29847
|
-
const adapters = KNOWN_AGENT_NAMES.map(cachedAdapter);
|
|
29848
|
-
const allAdapters = [...testAdapters, ...adapters];
|
|
29849
|
-
const results = await Promise.all(allAdapters.map(async (agent) => ({ agent, installed: await agent.isInstalled() })));
|
|
29850
|
-
return results.filter((r) => r.installed).map((r) => r.agent);
|
|
29851
|
-
}
|
|
29852
|
-
async function checkAgentHealth() {
|
|
29853
|
-
const testAdapters = Array.from(_registryTestAdapters.values());
|
|
29854
|
-
const adapters = KNOWN_AGENT_NAMES.map(cachedAdapter);
|
|
29855
|
-
const allAdapters = [...testAdapters, ...adapters];
|
|
29856
|
-
return Promise.all(allAdapters.map(async (agent) => ({
|
|
29857
|
-
name: agent.name,
|
|
29858
|
-
displayName: agent.displayName,
|
|
29859
|
-
installed: await agent.isInstalled()
|
|
29860
|
-
})));
|
|
29861
|
-
}
|
|
29862
|
-
return { getAgent, getInstalledAgents: getInstalledAgents2, checkAgentHealth, protocol };
|
|
29910
|
+
// src/agents/retry/resolve-exhaustion.ts
|
|
29911
|
+
async function resolveExhaustion(input) {
|
|
29912
|
+
const { failure, retryStrategy, retryCtx, signal, sleep: sleep2, onExhausted } = input;
|
|
29913
|
+
if (signal?.aborted)
|
|
29914
|
+
return "cancelled";
|
|
29915
|
+
if (failure && failurePolicyFor(failure.outcome).terminalBackoff) {
|
|
29916
|
+
const decision = retryStrategy.shouldRetry(failure, input.attempt, retryCtx);
|
|
29917
|
+
if (decision.retry) {
|
|
29918
|
+
await sleep2(decision.delayMs, signal);
|
|
29919
|
+
return signal?.aborted ? "cancelled" : "retry";
|
|
29920
|
+
}
|
|
29921
|
+
}
|
|
29922
|
+
if (input.swapWasPossible)
|
|
29923
|
+
onExhausted(input.hopsSoFar);
|
|
29924
|
+
return "exhausted";
|
|
29925
|
+
}
|
|
29926
|
+
var init_resolve_exhaustion = __esm(() => {
|
|
29927
|
+
init_failure_policy();
|
|
29928
|
+
});
|
|
29929
|
+
|
|
29930
|
+
// src/agents/manager-exhaustion.ts
|
|
29931
|
+
function resolveManagerExhaustion(options) {
|
|
29932
|
+
return resolveExhaustion({
|
|
29933
|
+
failure: options.failure,
|
|
29934
|
+
attempt: options.attempt,
|
|
29935
|
+
hopsSoFar: options.hopsSoFar,
|
|
29936
|
+
swapWasPossible: options.swapWasPossible,
|
|
29937
|
+
retryStrategy: options.retryStrategy,
|
|
29938
|
+
retryCtx: {
|
|
29939
|
+
site: options.site,
|
|
29940
|
+
agentName: options.agent,
|
|
29941
|
+
stage: options.stage,
|
|
29942
|
+
storyId: options.storyId
|
|
29943
|
+
},
|
|
29944
|
+
signal: options.signal,
|
|
29945
|
+
sleep: options.sleep,
|
|
29946
|
+
onExhausted: options.onExhausted
|
|
29947
|
+
});
|
|
29863
29948
|
}
|
|
29864
|
-
var
|
|
29865
|
-
|
|
29866
|
-
init_logger2();
|
|
29867
|
-
init_adapter2();
|
|
29868
|
-
init_native();
|
|
29869
|
-
KNOWN_AGENT_NAMES = ["claude", "codex", "opencode", "gemini", "aider", "pi", NATIVE_AGENT];
|
|
29870
|
-
_registryTestAdapters = new Map;
|
|
29871
|
-
});
|
|
29872
|
-
|
|
29873
|
-
// src/agents/retry/default-strategy.ts
|
|
29874
|
-
var MAX_RETRIES = 3, defaultRetryStrategy;
|
|
29875
|
-
var init_default_strategy = __esm(() => {
|
|
29876
|
-
defaultRetryStrategy = {
|
|
29877
|
-
shouldRetry(failure, attempt, _ctx) {
|
|
29878
|
-
if (attempt >= MAX_RETRIES)
|
|
29879
|
-
return { retry: false };
|
|
29880
|
-
if (failure instanceof Error)
|
|
29881
|
-
return { retry: false };
|
|
29882
|
-
const af = failure;
|
|
29883
|
-
if (af.retriable === false)
|
|
29884
|
-
return { retry: false };
|
|
29885
|
-
if (af.outcome !== "fail-rate-limit" && af.outcome !== "fail-stale")
|
|
29886
|
-
return { retry: false };
|
|
29887
|
-
const delayMs = 2 ** (attempt + 1) * 1000;
|
|
29888
|
-
return { retry: true, delayMs };
|
|
29889
|
-
}
|
|
29890
|
-
};
|
|
29949
|
+
var init_manager_exhaustion = __esm(() => {
|
|
29950
|
+
init_resolve_exhaustion();
|
|
29891
29951
|
});
|
|
29892
29952
|
|
|
29893
29953
|
// src/agents/retry/hop-retry-policy.ts
|
|
29894
29954
|
function trySameAgentRetry(result, state, deps) {
|
|
29895
29955
|
const { staleRetryAttempts, timeoutRetryAttempts, adapterErrorRetries, currentRunOptions, tier } = state;
|
|
29896
29956
|
const { config: config2, requestRunOptions, signal } = deps;
|
|
29897
|
-
const
|
|
29957
|
+
const outcome = result.adapterFailure?.outcome;
|
|
29958
|
+
const lane = outcome ? failurePolicyFor(outcome).sameAgentRetry : "none";
|
|
29959
|
+
const isFailStale = lane === "stale";
|
|
29898
29960
|
const maxStaleRetries = resolveIdleWatchdogSettings(config2.agent?.idleWatchdog).maxRetryAttempts;
|
|
29899
29961
|
if (isFailStale && result.adapterFailure?.retriable && staleRetryAttempts < maxStaleRetries) {
|
|
29900
29962
|
const newAttempts = staleRetryAttempts + 1;
|
|
@@ -29910,7 +29972,7 @@ function trySameAgentRetry(result, state, deps) {
|
|
|
29910
29972
|
}
|
|
29911
29973
|
};
|
|
29912
29974
|
}
|
|
29913
|
-
const isFailTimeout =
|
|
29975
|
+
const isFailTimeout = lane === "timeout";
|
|
29914
29976
|
if (isFailTimeout && result.adapterFailure?.retriable) {
|
|
29915
29977
|
const timeoutConfig = extractTimeoutRetryConfig(config2);
|
|
29916
29978
|
if (timeoutRetryShouldRetry(timeoutRetryAttempts, timeoutConfig)) {
|
|
@@ -29929,7 +29991,7 @@ function trySameAgentRetry(result, state, deps) {
|
|
|
29929
29991
|
};
|
|
29930
29992
|
}
|
|
29931
29993
|
}
|
|
29932
|
-
const isFailAdapterError =
|
|
29994
|
+
const isFailAdapterError = lane === "adapter-error";
|
|
29933
29995
|
if (isFailAdapterError && !signal?.aborted) {
|
|
29934
29996
|
const runConfig = requestRunOptions.config ?? config2;
|
|
29935
29997
|
const maxAdapterRetries = result.adapterFailure?.retriable ? runConfig.execution?.sessionErrorRetryableMaxRetries ?? 3 : runConfig.execution?.sessionErrorMaxRetries ?? 1;
|
|
@@ -29992,23 +30054,25 @@ function describeRetryLogEvent(retryDecision, storyId, agent) {
|
|
|
29992
30054
|
var init_hop_retry_policy = __esm(() => {
|
|
29993
30055
|
init_config();
|
|
29994
30056
|
init_idle_watchdog();
|
|
30057
|
+
init_failure_policy();
|
|
29995
30058
|
});
|
|
29996
30059
|
|
|
29997
30060
|
// src/agents/swap-decision.ts
|
|
29998
30061
|
function decideSwap(failure, hopsSoFar, fallback) {
|
|
29999
30062
|
if (!failure)
|
|
30000
30063
|
return { swap: false, reason: "no-failure" };
|
|
30001
|
-
|
|
30064
|
+
const policy = failurePolicyFor(failure.outcome);
|
|
30065
|
+
if (policy.swap === "never")
|
|
30002
30066
|
return { swap: false, reason: "outcome-refused" };
|
|
30003
|
-
}
|
|
30004
30067
|
if (!fallback?.enabled)
|
|
30005
30068
|
return { swap: false, reason: "fallback-disabled" };
|
|
30006
30069
|
if (hopsSoFar >= (fallback.maxHopsPerStory ?? DEFAULT_MAX_HOPS)) {
|
|
30007
30070
|
return { swap: false, reason: "hop-cap-reached" };
|
|
30008
30071
|
}
|
|
30009
|
-
if (
|
|
30010
|
-
return { swap: true };
|
|
30011
|
-
|
|
30072
|
+
if (policy.swap === "quality-gated") {
|
|
30073
|
+
return fallback.onQualityFailure ? { swap: true } : { swap: false, reason: "quality-failure-declined" };
|
|
30074
|
+
}
|
|
30075
|
+
return { swap: true };
|
|
30012
30076
|
}
|
|
30013
30077
|
function normaliseFallbackTarget(value) {
|
|
30014
30078
|
return typeof value === "string" ? { agent: value } : { agent: value.agent, tier: value.tier };
|
|
@@ -30036,6 +30100,290 @@ function logSwapDecline(logger, reason, input) {
|
|
|
30036
30100
|
});
|
|
30037
30101
|
}
|
|
30038
30102
|
var DEFAULT_MAX_HOPS = 2;
|
|
30103
|
+
var init_swap_decision = __esm(() => {
|
|
30104
|
+
init_failure_policy();
|
|
30105
|
+
});
|
|
30106
|
+
|
|
30107
|
+
// src/agents/manager-run-fallback.ts
|
|
30108
|
+
async function runWithFallback(input) {
|
|
30109
|
+
const { request, config: config2, budget, logger } = input;
|
|
30110
|
+
const fallbacks = [];
|
|
30111
|
+
const primaryAgent = input.primaryAgentOverride ?? input.getDefault();
|
|
30112
|
+
const storyId = request.runOptions.storyId;
|
|
30113
|
+
const start = resolveStartAgent(input, primaryAgent, config2.agent?.fallback?.enabled, storyId, logger);
|
|
30114
|
+
let currentAgent = start.agent;
|
|
30115
|
+
let currentHopKind = "tier" in start ? { kind: "primary", tier: start.tier } : { kind: "primary" };
|
|
30116
|
+
let hopsSoFar = budget.spent(storyId);
|
|
30117
|
+
let rateLimitRetry = 0;
|
|
30118
|
+
let staleRetryAttempts = 0;
|
|
30119
|
+
let timeoutRetryAttempts = 0;
|
|
30120
|
+
let adapterErrorRetries = 0;
|
|
30121
|
+
let currentBundle = request.bundle;
|
|
30122
|
+
let currentRunOptions = request.runOptions;
|
|
30123
|
+
let finalPrompt;
|
|
30124
|
+
const startedAt = Date.now();
|
|
30125
|
+
const agentChain = [primaryAgent];
|
|
30126
|
+
let finalStatus = "error";
|
|
30127
|
+
let totalCostUsd = 0;
|
|
30128
|
+
try {
|
|
30129
|
+
while (true) {
|
|
30130
|
+
const hop = await executeHop(input, currentAgent, currentBundle, currentHopKind, currentRunOptions);
|
|
30131
|
+
const { result } = hop;
|
|
30132
|
+
const updatedBundle = hop.bundle ?? currentBundle;
|
|
30133
|
+
finalPrompt = hop.prompt ?? finalPrompt;
|
|
30134
|
+
totalCostUsd += result.estimatedCostUsd ?? 0;
|
|
30135
|
+
if (result.success) {
|
|
30136
|
+
finalStatus = "ok";
|
|
30137
|
+
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
30138
|
+
}
|
|
30139
|
+
const retry = trySameAgentRetry(result, { staleRetryAttempts, timeoutRetryAttempts, adapterErrorRetries, currentRunOptions, tier: currentHopKind.tier }, { config: config2, requestRunOptions: request.runOptions, signal: request.signal });
|
|
30140
|
+
if (retry) {
|
|
30141
|
+
({ staleRetryAttempts, timeoutRetryAttempts, adapterErrorRetries, currentRunOptions } = applyRetry(retry, {
|
|
30142
|
+
staleRetryAttempts,
|
|
30143
|
+
timeoutRetryAttempts,
|
|
30144
|
+
adapterErrorRetries,
|
|
30145
|
+
currentRunOptions
|
|
30146
|
+
}));
|
|
30147
|
+
recordRetry(input, fallbacks, retry, currentAgent, storyId);
|
|
30148
|
+
currentHopKind = retry.kind;
|
|
30149
|
+
continue;
|
|
30150
|
+
}
|
|
30151
|
+
if (request.noFallback) {
|
|
30152
|
+
finalStatus = "error";
|
|
30153
|
+
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
30154
|
+
}
|
|
30155
|
+
const swap = decideSwap(result.adapterFailure, hopsSoFar, config2.agent?.fallback);
|
|
30156
|
+
if (!swap.swap) {
|
|
30157
|
+
logSwapDecline(logger, swap.reason, {
|
|
30158
|
+
storyId,
|
|
30159
|
+
agent: currentAgent,
|
|
30160
|
+
hopsSoFar,
|
|
30161
|
+
failure: result.adapterFailure
|
|
30162
|
+
});
|
|
30163
|
+
if (result.adapterFailure?.outcome === "fail-stale") {
|
|
30164
|
+
logger?.warn("agent-manager", "fail-stale: no swap candidate, returning terminal failure", { storyId });
|
|
30165
|
+
finalStatus = "error";
|
|
30166
|
+
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
30167
|
+
}
|
|
30168
|
+
const outcome = await input.resolveExhaustion({
|
|
30169
|
+
failure: result.adapterFailure,
|
|
30170
|
+
hopsSoFar,
|
|
30171
|
+
attempt: rateLimitRetry,
|
|
30172
|
+
swapWasPossible: swap.reason === "hop-cap-reached",
|
|
30173
|
+
agent: currentAgent,
|
|
30174
|
+
site: "run",
|
|
30175
|
+
storyId,
|
|
30176
|
+
stage: request.runOptions.pipelineStage ?? "run",
|
|
30177
|
+
signal: request.signal
|
|
30178
|
+
});
|
|
30179
|
+
if (outcome === "retry") {
|
|
30180
|
+
rateLimitRetry += 1;
|
|
30181
|
+
continue;
|
|
30182
|
+
}
|
|
30183
|
+
finalStatus = outcome === "cancelled" ? "cancelled" : hopsSoFar > 0 ? "exhausted" : "error";
|
|
30184
|
+
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
30185
|
+
}
|
|
30186
|
+
const failure = result.adapterFailure ?? unknownFailure();
|
|
30187
|
+
input.markUnavailable(currentAgent, failure);
|
|
30188
|
+
const next = input.nextCandidate(primaryAgent, hopsSoFar, currentAgent);
|
|
30189
|
+
if (!next) {
|
|
30190
|
+
const outcome = await input.resolveExhaustion({
|
|
30191
|
+
failure,
|
|
30192
|
+
hopsSoFar,
|
|
30193
|
+
attempt: rateLimitRetry,
|
|
30194
|
+
swapWasPossible: true,
|
|
30195
|
+
agent: currentAgent,
|
|
30196
|
+
site: "run",
|
|
30197
|
+
storyId,
|
|
30198
|
+
stage: request.runOptions.pipelineStage ?? "run",
|
|
30199
|
+
signal: request.signal
|
|
30200
|
+
});
|
|
30201
|
+
if (outcome === "retry") {
|
|
30202
|
+
rateLimitRetry += 1;
|
|
30203
|
+
continue;
|
|
30204
|
+
}
|
|
30205
|
+
finalStatus = outcome === "cancelled" ? "cancelled" : "exhausted";
|
|
30206
|
+
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
30207
|
+
}
|
|
30208
|
+
hopsSoFar = budget.spend(storyId, hopsSoFar);
|
|
30209
|
+
rateLimitRetry = 0;
|
|
30210
|
+
currentBundle = updatedBundle;
|
|
30211
|
+
currentHopKind = { kind: "swap", failure, ...next.tier ? { tier: next.tier } : {} };
|
|
30212
|
+
const fallback = buildFallbackRecord({
|
|
30213
|
+
storyId,
|
|
30214
|
+
priorAgent: currentAgent,
|
|
30215
|
+
newAgent: next.agent,
|
|
30216
|
+
hop: hopsSoFar,
|
|
30217
|
+
failure,
|
|
30218
|
+
costUsd: result.estimatedCostUsd ?? 0
|
|
30219
|
+
});
|
|
30220
|
+
fallbacks.push(fallback);
|
|
30221
|
+
input.emitSwapAttempt(fallback);
|
|
30222
|
+
logger?.info("agent-manager", "Agent swap triggered", {
|
|
30223
|
+
storyId,
|
|
30224
|
+
fromAgent: currentAgent,
|
|
30225
|
+
toAgent: next.agent,
|
|
30226
|
+
hop: hopsSoFar
|
|
30227
|
+
});
|
|
30228
|
+
agentChain.push(next.agent);
|
|
30229
|
+
currentAgent = next.agent;
|
|
30230
|
+
}
|
|
30231
|
+
} finally {
|
|
30232
|
+
input.dispatchEvents.emitOperationCompleted({
|
|
30233
|
+
kind: "operation-completed",
|
|
30234
|
+
operation: "run-with-fallback",
|
|
30235
|
+
agentChain,
|
|
30236
|
+
hopCount: hopsSoFar,
|
|
30237
|
+
fallbackTriggered: fallbacks.length > 0,
|
|
30238
|
+
totalElapsedMs: Date.now() - startedAt,
|
|
30239
|
+
totalCostUsd,
|
|
30240
|
+
finalStatus,
|
|
30241
|
+
storyId,
|
|
30242
|
+
stage: request.runOptions.pipelineStage ?? "run",
|
|
30243
|
+
timestamp: Date.now(),
|
|
30244
|
+
...request.runOptions.callId !== undefined ? { callId: request.runOptions.callId } : {},
|
|
30245
|
+
...request.runOptions.scopeId !== undefined ? { scopeId: request.runOptions.scopeId } : {}
|
|
30246
|
+
});
|
|
30247
|
+
}
|
|
30248
|
+
}
|
|
30249
|
+
async function executeHop(input, agent, bundle, kind, options) {
|
|
30250
|
+
if (input.request.executeHop)
|
|
30251
|
+
return input.request.executeHop(agent, bundle, kind, options);
|
|
30252
|
+
if (!input.runHop)
|
|
30253
|
+
return { result: unboundResult(agent), bundle };
|
|
30254
|
+
const raw = await input.runHop(agent, options);
|
|
30255
|
+
const hop = "result" in raw && raw.result != null ? raw : { result: raw };
|
|
30256
|
+
return { ...hop, bundle };
|
|
30257
|
+
}
|
|
30258
|
+
function unboundResult(agent) {
|
|
30259
|
+
return {
|
|
30260
|
+
success: false,
|
|
30261
|
+
exitCode: 1,
|
|
30262
|
+
output: `AgentManager run hop is not wired for agent "${agent}"`,
|
|
30263
|
+
rateLimited: false,
|
|
30264
|
+
durationMs: 0,
|
|
30265
|
+
estimatedCostUsd: 0
|
|
30266
|
+
};
|
|
30267
|
+
}
|
|
30268
|
+
function unknownFailure() {
|
|
30269
|
+
return { category: "quality", outcome: "fail-unknown", retriable: false, message: "" };
|
|
30270
|
+
}
|
|
30271
|
+
function applyRetry(retry, state) {
|
|
30272
|
+
return {
|
|
30273
|
+
staleRetryAttempts: retry.outcome === "stale-retry" ? retry.staleRetryAttempts : state.staleRetryAttempts,
|
|
30274
|
+
timeoutRetryAttempts: retry.outcome === "timeout-retry" ? retry.timeoutRetryAttempts : state.timeoutRetryAttempts,
|
|
30275
|
+
adapterErrorRetries: retry.outcome === "adapter-error" ? retry.adapterErrorRetries : state.adapterErrorRetries,
|
|
30276
|
+
currentRunOptions: retry.outcome === "timeout-retry" ? retry.currentRunOptions : state.currentRunOptions,
|
|
30277
|
+
tier: state.tier
|
|
30278
|
+
};
|
|
30279
|
+
}
|
|
30280
|
+
function recordRetry(input, fallbacks, retry, agent, storyId) {
|
|
30281
|
+
const fallback = buildFallbackRecord({
|
|
30282
|
+
storyId,
|
|
30283
|
+
priorAgent: agent,
|
|
30284
|
+
newAgent: agent,
|
|
30285
|
+
hop: retry.kind.attempt,
|
|
30286
|
+
failure: retry.fallbackRecord,
|
|
30287
|
+
costUsd: retry.fallbackRecord.costUsd
|
|
30288
|
+
});
|
|
30289
|
+
const event = describeRetryLogEvent(retry, storyId, agent);
|
|
30290
|
+
if (event.recordFallback) {
|
|
30291
|
+
fallbacks.push(fallback);
|
|
30292
|
+
input.emitSwapAttempt(fallback);
|
|
30293
|
+
}
|
|
30294
|
+
input.logger?.[event.level]("agent-manager", event.message, event.fields);
|
|
30295
|
+
}
|
|
30296
|
+
var init_manager_run_fallback = __esm(() => {
|
|
30297
|
+
init_manager_dispatch();
|
|
30298
|
+
init_hop_retry_policy();
|
|
30299
|
+
init_swap_decision();
|
|
30300
|
+
});
|
|
30301
|
+
|
|
30302
|
+
// src/agents/registry.ts
|
|
30303
|
+
function adapterFor(name) {
|
|
30304
|
+
return name === NATIVE_AGENT ? new NativeAgentAdapter : new AcpAgentAdapter(name);
|
|
30305
|
+
}
|
|
30306
|
+
function buildAdapterList() {
|
|
30307
|
+
return [...Array.from(_registryTestAdapters.values()), ...KNOWN_AGENT_NAMES.map(adapterFor)];
|
|
30308
|
+
}
|
|
30309
|
+
function getAllAgents() {
|
|
30310
|
+
return buildAdapterList();
|
|
30311
|
+
}
|
|
30312
|
+
async function getInstalledAgents() {
|
|
30313
|
+
const allAdapters = buildAdapterList();
|
|
30314
|
+
const results = await Promise.all(allAdapters.map(async (agent) => ({ agent, installed: await agent.isInstalled() })));
|
|
30315
|
+
return results.filter((r) => r.installed).map((r) => r.agent);
|
|
30316
|
+
}
|
|
30317
|
+
function createAgentRegistry(config2) {
|
|
30318
|
+
const logger = getLogger();
|
|
30319
|
+
const adapterCache = new Map;
|
|
30320
|
+
const protocol = config2.agent?.protocol ?? "acp";
|
|
30321
|
+
logger?.info("agents", `Agent protocol: ${protocol}`, { protocol, hasConfig: !!config2.agent });
|
|
30322
|
+
function cachedAdapter(name) {
|
|
30323
|
+
let adapter = adapterCache.get(name);
|
|
30324
|
+
if (adapter === undefined) {
|
|
30325
|
+
adapter = name === NATIVE_AGENT ? new NativeAgentAdapter : new AcpAgentAdapter(name);
|
|
30326
|
+
adapterCache.set(name, adapter);
|
|
30327
|
+
logger?.debug("agents", `Created ${adapter.constructor.name} for ${name}`, { name });
|
|
30328
|
+
}
|
|
30329
|
+
return adapter;
|
|
30330
|
+
}
|
|
30331
|
+
function getAgent(name) {
|
|
30332
|
+
if (_registryTestAdapters.has(name))
|
|
30333
|
+
return _registryTestAdapters.get(name);
|
|
30334
|
+
if (!KNOWN_AGENT_NAMES.includes(name))
|
|
30335
|
+
return;
|
|
30336
|
+
return cachedAdapter(name);
|
|
30337
|
+
}
|
|
30338
|
+
async function getInstalledAgents2() {
|
|
30339
|
+
const testAdapters = Array.from(_registryTestAdapters.values());
|
|
30340
|
+
const adapters = KNOWN_AGENT_NAMES.map(cachedAdapter);
|
|
30341
|
+
const allAdapters = [...testAdapters, ...adapters];
|
|
30342
|
+
const results = await Promise.all(allAdapters.map(async (agent) => ({ agent, installed: await agent.isInstalled() })));
|
|
30343
|
+
return results.filter((r) => r.installed).map((r) => r.agent);
|
|
30344
|
+
}
|
|
30345
|
+
async function checkAgentHealth() {
|
|
30346
|
+
const testAdapters = Array.from(_registryTestAdapters.values());
|
|
30347
|
+
const adapters = KNOWN_AGENT_NAMES.map(cachedAdapter);
|
|
30348
|
+
const allAdapters = [...testAdapters, ...adapters];
|
|
30349
|
+
return Promise.all(allAdapters.map(async (agent) => ({
|
|
30350
|
+
name: agent.name,
|
|
30351
|
+
displayName: agent.displayName,
|
|
30352
|
+
installed: await agent.isInstalled()
|
|
30353
|
+
})));
|
|
30354
|
+
}
|
|
30355
|
+
return { getAgent, getInstalledAgents: getInstalledAgents2, checkAgentHealth, protocol };
|
|
30356
|
+
}
|
|
30357
|
+
var KNOWN_AGENT_NAMES, _registryTestAdapters;
|
|
30358
|
+
var init_registry2 = __esm(() => {
|
|
30359
|
+
init_logger2();
|
|
30360
|
+
init_adapter2();
|
|
30361
|
+
init_native();
|
|
30362
|
+
KNOWN_AGENT_NAMES = ["claude", "codex", "opencode", "gemini", "aider", "pi", NATIVE_AGENT];
|
|
30363
|
+
_registryTestAdapters = new Map;
|
|
30364
|
+
});
|
|
30365
|
+
|
|
30366
|
+
// src/agents/retry/default-strategy.ts
|
|
30367
|
+
var MAX_RETRIES = 3, defaultRetryStrategy;
|
|
30368
|
+
var init_default_strategy = __esm(() => {
|
|
30369
|
+
init_failure_policy();
|
|
30370
|
+
defaultRetryStrategy = {
|
|
30371
|
+
shouldRetry(failure, attempt, _ctx) {
|
|
30372
|
+
if (attempt >= MAX_RETRIES)
|
|
30373
|
+
return { retry: false };
|
|
30374
|
+
if (failure instanceof Error)
|
|
30375
|
+
return { retry: false };
|
|
30376
|
+
const af = failure;
|
|
30377
|
+
if (af.retriable === false)
|
|
30378
|
+
return { retry: false };
|
|
30379
|
+
if (!failurePolicyFor(af.outcome).terminalBackoff)
|
|
30380
|
+
return { retry: false };
|
|
30381
|
+
const retryAfterSeconds = af.retryAfterSeconds;
|
|
30382
|
+
const delayMs = retryAfterSeconds !== undefined && Number.isFinite(retryAfterSeconds) && retryAfterSeconds >= 0 ? retryAfterSeconds * 1000 : 2 ** (attempt + 1) * 1000;
|
|
30383
|
+
return { retry: true, delayMs };
|
|
30384
|
+
}
|
|
30385
|
+
};
|
|
30386
|
+
});
|
|
30039
30387
|
|
|
30040
30388
|
// src/agents/manager.ts
|
|
30041
30389
|
import { EventEmitter } from "events";
|
|
@@ -30043,7 +30391,7 @@ import { EventEmitter } from "events";
|
|
|
30043
30391
|
class AgentManager {
|
|
30044
30392
|
_config;
|
|
30045
30393
|
_registry;
|
|
30046
|
-
|
|
30394
|
+
_cooldowns = new CooldownStore(() => _agentManagerDeps.now());
|
|
30047
30395
|
_prunedFallback = new Set;
|
|
30048
30396
|
_budget = new StoryHopBudget;
|
|
30049
30397
|
_emitter = (() => {
|
|
@@ -30099,22 +30447,19 @@ class AgentManager {
|
|
|
30099
30447
|
return "claude";
|
|
30100
30448
|
}
|
|
30101
30449
|
isUnavailable(agent) {
|
|
30102
|
-
return this.
|
|
30450
|
+
return this._cooldowns.isCooling(agent);
|
|
30103
30451
|
}
|
|
30104
30452
|
markUnavailable(agent, reason) {
|
|
30105
|
-
this.
|
|
30453
|
+
this._cooldowns.mark(agent, reason);
|
|
30106
30454
|
this._emitter.emit("onAgentUnavailable", { agent, failure: reason });
|
|
30107
30455
|
}
|
|
30108
30456
|
reset() {
|
|
30109
|
-
this.
|
|
30457
|
+
this._cooldowns.clear();
|
|
30110
30458
|
this._prunedFallback.clear();
|
|
30111
30459
|
this._budget.clear();
|
|
30112
30460
|
}
|
|
30113
30461
|
resetTransientUnavailable() {
|
|
30114
|
-
|
|
30115
|
-
if (failure.outcome !== "fail-auth" && failure.outcome !== "fail-quota")
|
|
30116
|
-
this._unavailable.delete(agent);
|
|
30117
|
-
}
|
|
30462
|
+
this._cooldowns.sweepTransient();
|
|
30118
30463
|
}
|
|
30119
30464
|
async validateCredentials() {
|
|
30120
30465
|
const primary = this.getDefault();
|
|
@@ -30145,211 +30490,26 @@ class AgentManager {
|
|
|
30145
30490
|
shouldSwap(failure, hopsSoFar) {
|
|
30146
30491
|
return decideSwap(failure, hopsSoFar, this._config.agent?.fallback).swap;
|
|
30147
30492
|
}
|
|
30148
|
-
nextCandidate(current, _hopsSoFar) {
|
|
30149
|
-
|
|
30493
|
+
nextCandidate(current, _hopsSoFar, exclude) {
|
|
30494
|
+
const excluded = (candidate) => candidate === exclude || this._isExcluded(candidate);
|
|
30495
|
+
return availableCandidates(this._config.agent?.fallback?.map, current, excluded)[0] ?? null;
|
|
30150
30496
|
}
|
|
30151
30497
|
async runWithFallback(request, primaryAgentOverride) {
|
|
30152
|
-
|
|
30153
|
-
|
|
30154
|
-
|
|
30155
|
-
|
|
30156
|
-
|
|
30157
|
-
|
|
30158
|
-
|
|
30159
|
-
|
|
30160
|
-
|
|
30161
|
-
|
|
30162
|
-
|
|
30163
|
-
|
|
30164
|
-
|
|
30165
|
-
|
|
30166
|
-
|
|
30167
|
-
const _opStartMs = Date.now();
|
|
30168
|
-
const _agentChain = [primaryAgent];
|
|
30169
|
-
let _finalStatus = "error";
|
|
30170
|
-
let _totalCostUsd = 0;
|
|
30171
|
-
try {
|
|
30172
|
-
while (true) {
|
|
30173
|
-
let result;
|
|
30174
|
-
let updatedBundle = currentBundle;
|
|
30175
|
-
if (request.executeHop) {
|
|
30176
|
-
const hopOut = await request.executeHop(currentAgent, currentBundle, currentHopKind, currentRunOptions);
|
|
30177
|
-
result = hopOut.result;
|
|
30178
|
-
updatedBundle = hopOut.bundle ?? currentBundle;
|
|
30179
|
-
finalPrompt = hopOut.prompt ?? finalPrompt;
|
|
30180
|
-
} else {
|
|
30181
|
-
if (!this._runHop) {
|
|
30182
|
-
const unboundResult = {
|
|
30183
|
-
success: false,
|
|
30184
|
-
exitCode: 1,
|
|
30185
|
-
output: `AgentManager run hop is not wired for agent "${currentAgent}"`,
|
|
30186
|
-
rateLimited: false,
|
|
30187
|
-
durationMs: 0,
|
|
30188
|
-
estimatedCostUsd: 0
|
|
30189
|
-
};
|
|
30190
|
-
_finalStatus = "error";
|
|
30191
|
-
return { result: unboundResult, fallbacks, finalBundle: currentBundle, finalPrompt };
|
|
30192
|
-
}
|
|
30193
|
-
const rawHopOut = await this._runHop(currentAgent, currentRunOptions);
|
|
30194
|
-
const hopOut = "result" in rawHopOut && rawHopOut.result != null ? rawHopOut : { result: rawHopOut, prompt: undefined };
|
|
30195
|
-
result = hopOut.result;
|
|
30196
|
-
finalPrompt = hopOut.prompt ?? finalPrompt;
|
|
30197
|
-
}
|
|
30198
|
-
_totalCostUsd += result.estimatedCostUsd ?? 0;
|
|
30199
|
-
if (result.success) {
|
|
30200
|
-
_finalStatus = "ok";
|
|
30201
|
-
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
30202
|
-
}
|
|
30203
|
-
const isFailStale = result.adapterFailure?.outcome === "fail-stale";
|
|
30204
|
-
const retryState = {
|
|
30205
|
-
staleRetryAttempts,
|
|
30206
|
-
timeoutRetryAttempts,
|
|
30207
|
-
adapterErrorRetries,
|
|
30208
|
-
currentRunOptions,
|
|
30209
|
-
tier: "tier" in currentHopKind ? currentHopKind.tier : undefined
|
|
30210
|
-
};
|
|
30211
|
-
const retryDecision = trySameAgentRetry(result, retryState, {
|
|
30212
|
-
config: this._config,
|
|
30213
|
-
requestRunOptions: request.runOptions,
|
|
30214
|
-
signal: request.signal
|
|
30215
|
-
});
|
|
30216
|
-
if (retryDecision) {
|
|
30217
|
-
staleRetryAttempts = retryDecision.outcome === "stale-retry" ? retryDecision.staleRetryAttempts : staleRetryAttempts;
|
|
30218
|
-
timeoutRetryAttempts = retryDecision.outcome === "timeout-retry" ? retryDecision.timeoutRetryAttempts : timeoutRetryAttempts;
|
|
30219
|
-
adapterErrorRetries = retryDecision.outcome === "adapter-error" ? retryDecision.adapterErrorRetries : adapterErrorRetries;
|
|
30220
|
-
currentRunOptions = retryDecision.outcome === "timeout-retry" ? retryDecision.currentRunOptions : currentRunOptions;
|
|
30221
|
-
const retryHop = buildFallbackRecord({
|
|
30222
|
-
storyId: request.runOptions.storyId,
|
|
30223
|
-
priorAgent: currentAgent,
|
|
30224
|
-
newAgent: currentAgent,
|
|
30225
|
-
hop: retryDecision.kind.attempt,
|
|
30226
|
-
failure: retryDecision.fallbackRecord,
|
|
30227
|
-
costUsd: retryDecision.fallbackRecord.costUsd
|
|
30228
|
-
});
|
|
30229
|
-
const logEvent = describeRetryLogEvent(retryDecision, request.runOptions.storyId, currentAgent);
|
|
30230
|
-
if (logEvent.recordFallback) {
|
|
30231
|
-
fallbacks.push(retryHop);
|
|
30232
|
-
this._emitter.emit("onSwapAttempt", retryHop);
|
|
30233
|
-
}
|
|
30234
|
-
if (logEvent.level === "warn") {
|
|
30235
|
-
logger?.warn("agent-manager", logEvent.message, logEvent.fields);
|
|
30236
|
-
} else {
|
|
30237
|
-
logger?.info("agent-manager", logEvent.message, logEvent.fields);
|
|
30238
|
-
}
|
|
30239
|
-
currentHopKind = retryDecision.kind;
|
|
30240
|
-
continue;
|
|
30241
|
-
}
|
|
30242
|
-
if (request.noFallback) {
|
|
30243
|
-
_finalStatus = "error";
|
|
30244
|
-
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
30245
|
-
}
|
|
30246
|
-
const fb = this._config.agent?.fallback;
|
|
30247
|
-
const swapDecision = decideSwap(result.adapterFailure, hopsSoFar, fb);
|
|
30248
|
-
if (!swapDecision.swap) {
|
|
30249
|
-
logSwapDecline(logger, swapDecision.reason, {
|
|
30250
|
-
storyId: request.runOptions.storyId,
|
|
30251
|
-
agent: currentAgent,
|
|
30252
|
-
hopsSoFar,
|
|
30253
|
-
failure: result.adapterFailure
|
|
30254
|
-
});
|
|
30255
|
-
if (isFailStale) {
|
|
30256
|
-
logger?.warn("agent-manager", "fail-stale: no swap candidate, returning terminal failure", {
|
|
30257
|
-
storyId: request.runOptions.storyId
|
|
30258
|
-
});
|
|
30259
|
-
_finalStatus = "error";
|
|
30260
|
-
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
30261
|
-
}
|
|
30262
|
-
if (result.adapterFailure) {
|
|
30263
|
-
const retryCtx = {
|
|
30264
|
-
site: "run",
|
|
30265
|
-
agentName: currentAgent,
|
|
30266
|
-
stage: request.runOptions.pipelineStage ?? "run",
|
|
30267
|
-
storyId: request.runOptions.storyId
|
|
30268
|
-
};
|
|
30269
|
-
const decision = this._retryStrategy.shouldRetry(result.adapterFailure, rateLimitRetry, retryCtx);
|
|
30270
|
-
if (decision.retry) {
|
|
30271
|
-
if (request.signal?.aborted) {
|
|
30272
|
-
logger?.info("agent-manager", "Rate-limited backoff aborted \u2014 shutdown in progress", {
|
|
30273
|
-
storyId: request.runOptions.storyId
|
|
30274
|
-
});
|
|
30275
|
-
_finalStatus = "cancelled";
|
|
30276
|
-
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
30277
|
-
}
|
|
30278
|
-
rateLimitRetry += 1;
|
|
30279
|
-
logger?.info("agent-manager", "Rate-limited with no swap candidate \u2014 backing off", {
|
|
30280
|
-
storyId: request.runOptions.storyId,
|
|
30281
|
-
attempt: rateLimitRetry,
|
|
30282
|
-
backoffMs: decision.delayMs
|
|
30283
|
-
});
|
|
30284
|
-
await _agentManagerDeps.sleep(decision.delayMs, request.signal);
|
|
30285
|
-
if (request.signal?.aborted) {
|
|
30286
|
-
_finalStatus = "cancelled";
|
|
30287
|
-
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
30288
|
-
}
|
|
30289
|
-
continue;
|
|
30290
|
-
}
|
|
30291
|
-
}
|
|
30292
|
-
if (hopsSoFar > 0) {
|
|
30293
|
-
this._emitter.emit("onSwapExhausted", { storyId: request.runOptions.storyId, hops: hopsSoFar });
|
|
30294
|
-
_finalStatus = "exhausted";
|
|
30295
|
-
} else {
|
|
30296
|
-
_finalStatus = "error";
|
|
30297
|
-
}
|
|
30298
|
-
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
30299
|
-
}
|
|
30300
|
-
const adapterFailure = result.adapterFailure ?? {
|
|
30301
|
-
category: "quality",
|
|
30302
|
-
outcome: "fail-unknown",
|
|
30303
|
-
retriable: false,
|
|
30304
|
-
message: ""
|
|
30305
|
-
};
|
|
30306
|
-
this.markUnavailable(currentAgent, adapterFailure);
|
|
30307
|
-
const next = this.nextCandidate(primaryAgent, hopsSoFar);
|
|
30308
|
-
if (!next) {
|
|
30309
|
-
this._emitter.emit("onSwapExhausted", { storyId: request.runOptions.storyId, hops: hopsSoFar });
|
|
30310
|
-
_finalStatus = "exhausted";
|
|
30311
|
-
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
30312
|
-
}
|
|
30313
|
-
hopsSoFar = this._budget.spend(storyId, hopsSoFar);
|
|
30314
|
-
rateLimitRetry = 0;
|
|
30315
|
-
currentBundle = updatedBundle;
|
|
30316
|
-
currentHopKind = { kind: "swap", failure: adapterFailure, ..."tier" in next ? { tier: next.tier } : {} };
|
|
30317
|
-
const hop = buildFallbackRecord({
|
|
30318
|
-
storyId: request.runOptions.storyId,
|
|
30319
|
-
priorAgent: currentAgent,
|
|
30320
|
-
newAgent: next.agent,
|
|
30321
|
-
hop: hopsSoFar,
|
|
30322
|
-
failure: adapterFailure,
|
|
30323
|
-
costUsd: result.estimatedCostUsd ?? 0
|
|
30324
|
-
});
|
|
30325
|
-
fallbacks.push(hop);
|
|
30326
|
-
this._emitter.emit("onSwapAttempt", hop);
|
|
30327
|
-
logger?.info("agent-manager", "Agent swap triggered", {
|
|
30328
|
-
storyId: request.runOptions.storyId,
|
|
30329
|
-
fromAgent: currentAgent,
|
|
30330
|
-
toAgent: next.agent,
|
|
30331
|
-
hop: hopsSoFar
|
|
30332
|
-
});
|
|
30333
|
-
_agentChain.push(next.agent);
|
|
30334
|
-
currentAgent = next.agent;
|
|
30335
|
-
}
|
|
30336
|
-
} finally {
|
|
30337
|
-
this._dispatchEvents.emitOperationCompleted({
|
|
30338
|
-
kind: "operation-completed",
|
|
30339
|
-
operation: "run-with-fallback",
|
|
30340
|
-
agentChain: _agentChain,
|
|
30341
|
-
hopCount: hopsSoFar,
|
|
30342
|
-
fallbackTriggered: fallbacks.length > 0,
|
|
30343
|
-
totalElapsedMs: Date.now() - _opStartMs,
|
|
30344
|
-
totalCostUsd: _totalCostUsd,
|
|
30345
|
-
finalStatus: _finalStatus,
|
|
30346
|
-
storyId: request.runOptions.storyId,
|
|
30347
|
-
stage: request.runOptions.pipelineStage ?? "run",
|
|
30348
|
-
timestamp: Date.now(),
|
|
30349
|
-
...request.runOptions.callId !== undefined ? { callId: request.runOptions.callId } : {},
|
|
30350
|
-
...request.runOptions.scopeId !== undefined ? { scopeId: request.runOptions.scopeId } : {}
|
|
30351
|
-
});
|
|
30352
|
-
}
|
|
30498
|
+
return runWithFallback({
|
|
30499
|
+
request,
|
|
30500
|
+
primaryAgentOverride,
|
|
30501
|
+
config: this._config,
|
|
30502
|
+
budget: this._budget,
|
|
30503
|
+
runHop: this._runHop,
|
|
30504
|
+
dispatchEvents: this._dispatchEvents,
|
|
30505
|
+
logger: this._loggerOverride ?? getSafeLogger(),
|
|
30506
|
+
getDefault: () => this.getDefault(),
|
|
30507
|
+
isUnavailable: (agent) => this.isUnavailable(agent),
|
|
30508
|
+
markUnavailable: (agent, failure) => this.markUnavailable(agent, failure),
|
|
30509
|
+
nextCandidate: (current, hops, exclude) => this.nextCandidate(current, hops, exclude),
|
|
30510
|
+
resolveExhaustion: (options) => this._resolveExhaustion(options),
|
|
30511
|
+
emitSwapAttempt: (fallback) => this._emitter.emit("onSwapAttempt", fallback)
|
|
30512
|
+
});
|
|
30353
30513
|
}
|
|
30354
30514
|
async completeWithFallback(prompt, options, primaryAgentOverride) {
|
|
30355
30515
|
const logger = this._loggerOverride ?? getSafeLogger();
|
|
@@ -30359,6 +30519,7 @@ class AgentManager {
|
|
|
30359
30519
|
let currentTier;
|
|
30360
30520
|
let hopsSoFar = this._budget.spent(options.storyId);
|
|
30361
30521
|
let staleRetryAttempts = 0;
|
|
30522
|
+
let rateLimitRetry = 0;
|
|
30362
30523
|
const maxStaleRetries = resolveIdleWatchdogSettings(this._config.agent?.idleWatchdog).maxRetryAttempts;
|
|
30363
30524
|
const _opStartMs = Date.now();
|
|
30364
30525
|
const _agentChain = [primaryAgent];
|
|
@@ -30437,12 +30598,50 @@ class AgentManager {
|
|
|
30437
30598
|
hopsSoFar,
|
|
30438
30599
|
failure: result.adapterFailure
|
|
30439
30600
|
});
|
|
30601
|
+
const outcome = await this._resolveExhaustion({
|
|
30602
|
+
failure: result.adapterFailure,
|
|
30603
|
+
hopsSoFar,
|
|
30604
|
+
attempt: rateLimitRetry,
|
|
30605
|
+
swapWasPossible: dec.reason === "hop-cap-reached",
|
|
30606
|
+
agent: currentAgent,
|
|
30607
|
+
site: "complete",
|
|
30608
|
+
storyId: options.storyId,
|
|
30609
|
+
stage: options.pipelineStage ?? "run",
|
|
30610
|
+
signal: options.signal
|
|
30611
|
+
});
|
|
30612
|
+
if (outcome === "cancelled") {
|
|
30613
|
+
_finalStatus = "cancelled";
|
|
30614
|
+
return { result, fallbacks, ...currentTier !== undefined ? { finalTier: currentTier } : {} };
|
|
30615
|
+
}
|
|
30616
|
+
if (outcome === "retry") {
|
|
30617
|
+
rateLimitRetry += 1;
|
|
30618
|
+
continue;
|
|
30619
|
+
}
|
|
30440
30620
|
_finalStatus = hopsSoFar > 0 ? "exhausted" : "error";
|
|
30441
30621
|
return { result, fallbacks, ...currentTier !== undefined ? { finalTier: currentTier } : {} };
|
|
30442
30622
|
}
|
|
30443
30623
|
this.markUnavailable(currentAgent, result.adapterFailure);
|
|
30444
|
-
const next = this.nextCandidate(primaryAgent, hopsSoFar);
|
|
30624
|
+
const next = this.nextCandidate(primaryAgent, hopsSoFar, currentAgent);
|
|
30445
30625
|
if (!next) {
|
|
30626
|
+
const outcome = await this._resolveExhaustion({
|
|
30627
|
+
failure: result.adapterFailure,
|
|
30628
|
+
hopsSoFar,
|
|
30629
|
+
attempt: rateLimitRetry,
|
|
30630
|
+
swapWasPossible: true,
|
|
30631
|
+
agent: currentAgent,
|
|
30632
|
+
site: "complete",
|
|
30633
|
+
storyId: options.storyId,
|
|
30634
|
+
stage: options.pipelineStage ?? "run",
|
|
30635
|
+
signal: options.signal
|
|
30636
|
+
});
|
|
30637
|
+
if (outcome === "cancelled") {
|
|
30638
|
+
_finalStatus = "cancelled";
|
|
30639
|
+
return { result, fallbacks, ...currentTier !== undefined ? { finalTier: currentTier } : {} };
|
|
30640
|
+
}
|
|
30641
|
+
if (outcome === "retry") {
|
|
30642
|
+
rateLimitRetry += 1;
|
|
30643
|
+
continue;
|
|
30644
|
+
}
|
|
30446
30645
|
_finalStatus = "exhausted";
|
|
30447
30646
|
return { result, fallbacks, ...currentTier !== undefined ? { finalTier: currentTier } : {} };
|
|
30448
30647
|
}
|
|
@@ -30610,6 +30809,14 @@ class AgentManager {
|
|
|
30610
30809
|
async completeAs(agentName, prompt, options) {
|
|
30611
30810
|
return (await this.completeAsWithFallback(agentName, prompt, options)).result;
|
|
30612
30811
|
}
|
|
30812
|
+
_resolveExhaustion(options) {
|
|
30813
|
+
return resolveManagerExhaustion({
|
|
30814
|
+
...options,
|
|
30815
|
+
retryStrategy: this._retryStrategy,
|
|
30816
|
+
sleep: _agentManagerDeps.sleep,
|
|
30817
|
+
onExhausted: (hops) => this._emitter.emit("onSwapExhausted", { storyId: options.storyId, hops })
|
|
30818
|
+
});
|
|
30819
|
+
}
|
|
30613
30820
|
close() {
|
|
30614
30821
|
this._emitter.removeAllListeners();
|
|
30615
30822
|
}
|
|
@@ -30630,12 +30837,16 @@ var init_manager = __esm(() => {
|
|
|
30630
30837
|
init_idle_watchdog();
|
|
30631
30838
|
init_bun_deps();
|
|
30632
30839
|
init_complete_exception_classifier();
|
|
30840
|
+
init_cooldown_store();
|
|
30633
30841
|
init_manager_dispatch();
|
|
30842
|
+
init_manager_exhaustion();
|
|
30843
|
+
init_manager_run_fallback();
|
|
30634
30844
|
init_registry2();
|
|
30635
30845
|
init_default_strategy();
|
|
30636
|
-
|
|
30846
|
+
init_swap_decision();
|
|
30637
30847
|
_agentManagerDeps = {
|
|
30638
|
-
sleep: (ms, signal) => cancellableDelay(ms, signal)
|
|
30848
|
+
sleep: (ms, signal) => cancellableDelay(ms, signal),
|
|
30849
|
+
now: () => Date.now()
|
|
30639
30850
|
};
|
|
30640
30851
|
});
|
|
30641
30852
|
|
|
@@ -48637,7 +48848,7 @@ function turnResultToAgentResult(r) {
|
|
|
48637
48848
|
success: !r.adapterFailure,
|
|
48638
48849
|
exitCode: r.adapterFailure ? 1 : 0,
|
|
48639
48850
|
output: r.output,
|
|
48640
|
-
rateLimited:
|
|
48851
|
+
rateLimited: r.adapterFailure?.outcome === "fail-rate-limit",
|
|
48641
48852
|
durationMs: 0,
|
|
48642
48853
|
estimatedCostUsd: r.estimatedCostUsd ?? 0,
|
|
48643
48854
|
exactCostUsd: r.exactCostUsd,
|
|
@@ -49068,6 +49279,15 @@ function recordAgentFallbacks(ctx, fallbacks) {
|
|
|
49068
49279
|
}
|
|
49069
49280
|
store.set(ctx.storyId, [...fallbacks]);
|
|
49070
49281
|
}
|
|
49282
|
+
function recordAdapterFailure(ctx, failure) {
|
|
49283
|
+
if (!ctx.storyId)
|
|
49284
|
+
return;
|
|
49285
|
+
if (failure) {
|
|
49286
|
+
ctx.runtime.lastAdapterFailure.set(ctx.storyId, failure);
|
|
49287
|
+
return;
|
|
49288
|
+
}
|
|
49289
|
+
ctx.runtime.lastAdapterFailure.delete(ctx.storyId);
|
|
49290
|
+
}
|
|
49071
49291
|
var MAX_COMPLETE_RETRY_ATTEMPTS = 20, correlationSequence = 0;
|
|
49072
49292
|
var init_call_resolvers = __esm(() => {
|
|
49073
49293
|
init_retry();
|
|
@@ -49417,7 +49637,7 @@ async function callOp(ctx, op, input) {
|
|
|
49417
49637
|
}
|
|
49418
49638
|
return sendWithParseRetry(initialPrompt, bodyCtx);
|
|
49419
49639
|
};
|
|
49420
|
-
const
|
|
49640
|
+
const executeHop2 = _callOpDeps.buildHopCallback({
|
|
49421
49641
|
...hopCtx,
|
|
49422
49642
|
hopBody: effectiveHopBody,
|
|
49423
49643
|
hopBodyInput: input
|
|
@@ -49425,12 +49645,13 @@ async function callOp(ctx, op, input) {
|
|
|
49425
49645
|
const rawOutcome = await ctx.runtime.agentManager.runWithFallback({
|
|
49426
49646
|
runOptions,
|
|
49427
49647
|
signal: abortSignal,
|
|
49428
|
-
executeHop,
|
|
49648
|
+
executeHop: executeHop2,
|
|
49429
49649
|
noFallback: runOp.noFallback,
|
|
49430
49650
|
bundle: ctx.contextBundle
|
|
49431
49651
|
}, dispatchAgent);
|
|
49432
49652
|
const outcome = normalizeRunOutcome(rawOutcome);
|
|
49433
49653
|
recordAgentFallbacks(ctx, outcome.fallbacks);
|
|
49654
|
+
recordAdapterFailure(ctx, outcome.result.adapterFailure);
|
|
49434
49655
|
if (abortSignal?.aborted) {
|
|
49435
49656
|
throw new NaxError(`callOp[${op.name}]: aborted`, "CALL_OP_ABORTED", { stage: op.stage, storyId: ctx.storyId });
|
|
49436
49657
|
}
|
|
@@ -60907,7 +61128,7 @@ var package_default;
|
|
|
60907
61128
|
var init_package = __esm(() => {
|
|
60908
61129
|
package_default = {
|
|
60909
61130
|
name: "@nathapp/nax",
|
|
60910
|
-
version: "0.82.0-canary.
|
|
61131
|
+
version: "0.82.0-canary.5",
|
|
60911
61132
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
60912
61133
|
type: "module",
|
|
60913
61134
|
bin: {
|
|
@@ -60975,7 +61196,7 @@ var init_package = __esm(() => {
|
|
|
60975
61196
|
"spec:lint": "bun run scripts/spec-lint.ts"
|
|
60976
61197
|
},
|
|
60977
61198
|
dependencies: {
|
|
60978
|
-
"@nathapp/nax-ai": "0.1.
|
|
61199
|
+
"@nathapp/nax-ai": "0.1.10",
|
|
60979
61200
|
"@types/react": "^19.2.14",
|
|
60980
61201
|
chalk: "^5.6.2",
|
|
60981
61202
|
commander: "^13.1.0",
|
|
@@ -61028,8 +61249,8 @@ var init_version = __esm(() => {
|
|
|
61028
61249
|
NAX_VERSION = package_default.version;
|
|
61029
61250
|
NAX_COMMIT = (() => {
|
|
61030
61251
|
try {
|
|
61031
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
61032
|
-
return "
|
|
61252
|
+
if (/^[0-9a-f]{6,10}$/.test("363f5424"))
|
|
61253
|
+
return "363f5424";
|
|
61033
61254
|
} catch {}
|
|
61034
61255
|
try {
|
|
61035
61256
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -62971,6 +63192,7 @@ function createRuntime(config2, workdir, opts) {
|
|
|
62971
63192
|
const rectificationOscillations = new Map;
|
|
62972
63193
|
const reviewFindingRecurrences = new Map;
|
|
62973
63194
|
const agentFallbacks = new Map;
|
|
63195
|
+
const lastAdapterFailure = new Map;
|
|
62974
63196
|
const runtimeCrashRetries = new Map;
|
|
62975
63197
|
const storyFixHistory = createStoryFixHistory();
|
|
62976
63198
|
const mutationSummaries = new Map;
|
|
@@ -63002,6 +63224,7 @@ function createRuntime(config2, workdir, opts) {
|
|
|
63002
63224
|
rectificationOscillations,
|
|
63003
63225
|
reviewFindingRecurrences,
|
|
63004
63226
|
agentFallbacks,
|
|
63227
|
+
lastAdapterFailure,
|
|
63005
63228
|
runtimeCrashRetries,
|
|
63006
63229
|
storyFixHistory,
|
|
63007
63230
|
mutationSummaries,
|
|
@@ -85345,6 +85568,30 @@ var init_execution_helpers = __esm(() => {
|
|
|
85345
85568
|
init_errors();
|
|
85346
85569
|
});
|
|
85347
85570
|
|
|
85571
|
+
// src/execution/post-run-notifications.ts
|
|
85572
|
+
async function sendPostRunNotification(ctx, notification) {
|
|
85573
|
+
if (!ctx.interaction)
|
|
85574
|
+
return;
|
|
85575
|
+
try {
|
|
85576
|
+
await ctx.interaction.send({
|
|
85577
|
+
id: `${notification.idPrefix}-${ctx.story.id}-${Date.now()}`,
|
|
85578
|
+
type: "notify",
|
|
85579
|
+
featureName: ctx.featureDir ? ctx.featureDir.split("/").pop() ?? "unknown" : "unknown",
|
|
85580
|
+
storyId: ctx.story.id,
|
|
85581
|
+
stage: "execution",
|
|
85582
|
+
summary: notification.summary,
|
|
85583
|
+
detail: notification.detail,
|
|
85584
|
+
fallback: "continue",
|
|
85585
|
+
createdAt: Date.now()
|
|
85586
|
+
});
|
|
85587
|
+
} catch (err) {
|
|
85588
|
+
getLogger().warn("execution", notification.failureMessage, { storyId: ctx.story.id, error: errorMessage(err) });
|
|
85589
|
+
}
|
|
85590
|
+
}
|
|
85591
|
+
var init_post_run_notifications = __esm(() => {
|
|
85592
|
+
init_logger2();
|
|
85593
|
+
});
|
|
85594
|
+
|
|
85348
85595
|
// src/execution/post-run-review-summary.ts
|
|
85349
85596
|
function applyReviewsFailedOpen(ctx, phaseOutputs) {
|
|
85350
85597
|
const count = [phaseOutputs[semanticReviewOp.name], phaseOutputs[adversarialReviewOp.name]].filter((output) => output?.failOpen === true).length;
|
|
@@ -85504,10 +85751,12 @@ async function applyPostRunInspection(ctx, planResult, opts) {
|
|
|
85504
85751
|
const { capturedTokenUsage, capturedResponse, capturedCostUsd } = opts;
|
|
85505
85752
|
const isTdd = opts.tddMode !== null;
|
|
85506
85753
|
const implementerOutput = planResult.phaseOutputs[implementerOp.name];
|
|
85754
|
+
const lastFailure = ctx.runtime.lastAdapterFailure.get(ctx.story.id);
|
|
85755
|
+
const providerUnavailable = lastFailure?.outcome === "fail-rate-limit" || lastFailure?.outcome === "fail-quota" || lastFailure?.outcome === "fail-service-down";
|
|
85507
85756
|
const agentResult = {
|
|
85508
85757
|
success: implementerOutput?.success ?? false,
|
|
85509
85758
|
estimatedCostUsd: capturedCostUsd || planResult.phaseCosts[implementerOp.name] || 0,
|
|
85510
|
-
rateLimited:
|
|
85759
|
+
rateLimited: lastFailure?.outcome === "fail-rate-limit",
|
|
85511
85760
|
output: capturedResponse,
|
|
85512
85761
|
exitCode: implementerOutput?.success ? 0 : 1,
|
|
85513
85762
|
durationMs: implementerOutput?.durationMs ?? planResult.durationMs,
|
|
@@ -85619,7 +85868,7 @@ async function applyPostRunInspection(ctx, planResult, opts) {
|
|
|
85619
85868
|
if (Object.keys(tddIsolations).length > 0) {
|
|
85620
85869
|
ctx.tddIsolations = tddIsolations;
|
|
85621
85870
|
}
|
|
85622
|
-
const needsHumanReview = failureCategory === "session-failure";
|
|
85871
|
+
const needsHumanReview = failureCategory === "session-failure" && !providerUnavailable;
|
|
85623
85872
|
const combinedOutput = (agentResult.output ?? "") + (agentResult.stderr ?? "");
|
|
85624
85873
|
if (isTdd) {
|
|
85625
85874
|
const { cleanupVerdict: cleanupVerdict2 } = await Promise.resolve().then(() => (init_verdict(), exports_verdict));
|
|
@@ -85629,14 +85878,30 @@ async function applyPostRunInspection(ctx, planResult, opts) {
|
|
|
85629
85878
|
}
|
|
85630
85879
|
const rectOut = planResult.phaseOutputs.rectification;
|
|
85631
85880
|
ctx.rectifyAttempt = rectOut?.iterationCount ?? 0;
|
|
85632
|
-
return {
|
|
85881
|
+
return {
|
|
85882
|
+
agentResult,
|
|
85883
|
+
selfVerificationFailed,
|
|
85884
|
+
pauseReason,
|
|
85885
|
+
failureCategory,
|
|
85886
|
+
needsHumanReview,
|
|
85887
|
+
providerUnavailable,
|
|
85888
|
+
combinedOutput
|
|
85889
|
+
};
|
|
85633
85890
|
}
|
|
85634
85891
|
async function decideStageAction(ctx, planResult, inspection, opts) {
|
|
85635
85892
|
const logger = getLogger();
|
|
85636
85893
|
const isTdd = opts.tddMode !== null;
|
|
85637
85894
|
const isLiteMode = opts.tddMode?.isLite ?? false;
|
|
85638
85895
|
const shouldRollback = shouldRollbackTddFailure(opts.tddMode, inspection.failureCategory);
|
|
85639
|
-
const {
|
|
85896
|
+
const {
|
|
85897
|
+
agentResult,
|
|
85898
|
+
selfVerificationFailed,
|
|
85899
|
+
pauseReason,
|
|
85900
|
+
failureCategory,
|
|
85901
|
+
needsHumanReview,
|
|
85902
|
+
providerUnavailable,
|
|
85903
|
+
combinedOutput
|
|
85904
|
+
} = inspection;
|
|
85640
85905
|
if (isTdd && !planResult.success) {
|
|
85641
85906
|
ctx.tddFailureCategory = failureCategory;
|
|
85642
85907
|
}
|
|
@@ -85716,27 +85981,13 @@ Reason: ${breaker.reason}`,
|
|
|
85716
85981
|
}
|
|
85717
85982
|
if (pauseReason) {
|
|
85718
85983
|
logger.warn("execution", "Plan run produced pauseReason", { storyId: ctx.story.id, pauseReason });
|
|
85719
|
-
|
|
85720
|
-
|
|
85721
|
-
|
|
85722
|
-
|
|
85723
|
-
type: "notify",
|
|
85724
|
-
featureName: ctx.featureDir ? ctx.featureDir.split("/").pop() ?? "unknown" : "unknown",
|
|
85725
|
-
storyId: ctx.story.id,
|
|
85726
|
-
stage: "execution",
|
|
85727
|
-
summary: `Execution paused: ${ctx.story.id}`,
|
|
85728
|
-
detail: `Story: ${ctx.story.title}
|
|
85984
|
+
await sendPostRunNotification(ctx, {
|
|
85985
|
+
idPrefix: "pause",
|
|
85986
|
+
summary: `Execution paused: ${ctx.story.id}`,
|
|
85987
|
+
detail: `Story: ${ctx.story.title}
|
|
85729
85988
|
Reason: ${pauseReason}`,
|
|
85730
|
-
|
|
85731
|
-
|
|
85732
|
-
});
|
|
85733
|
-
} catch (notifyErr) {
|
|
85734
|
-
logger.warn("execution", "Failed to send pause notification", {
|
|
85735
|
-
storyId: ctx.story.id,
|
|
85736
|
-
error: String(notifyErr)
|
|
85737
|
-
});
|
|
85738
|
-
}
|
|
85739
|
-
}
|
|
85989
|
+
failureMessage: "Failed to send pause notification"
|
|
85990
|
+
});
|
|
85740
85991
|
return { action: "pause", reason: pauseReason };
|
|
85741
85992
|
}
|
|
85742
85993
|
if (isTdd && !planResult.success) {
|
|
@@ -85754,30 +86005,16 @@ Reason: ${pauseReason}`,
|
|
|
85754
86005
|
});
|
|
85755
86006
|
}
|
|
85756
86007
|
}
|
|
85757
|
-
if (needsHumanReview) {
|
|
86008
|
+
if (needsHumanReview && !providerUnavailable) {
|
|
85758
86009
|
logger.warn("execution", "Human review needed", { storyId: ctx.story.id, failureCategory });
|
|
85759
|
-
|
|
85760
|
-
|
|
85761
|
-
|
|
85762
|
-
|
|
85763
|
-
type: "notify",
|
|
85764
|
-
featureName: ctx.featureDir ? ctx.featureDir.split("/").pop() ?? "unknown" : "unknown",
|
|
85765
|
-
storyId: ctx.story.id,
|
|
85766
|
-
stage: "execution",
|
|
85767
|
-
summary: `Human review needed: ${ctx.story.id}`,
|
|
85768
|
-
detail: `Story: ${ctx.story.title}
|
|
86010
|
+
await sendPostRunNotification(ctx, {
|
|
86011
|
+
idPrefix: "human-review",
|
|
86012
|
+
summary: `Human review needed: ${ctx.story.id}`,
|
|
86013
|
+
detail: `Story: ${ctx.story.title}
|
|
85769
86014
|
Reason: Human review needed
|
|
85770
86015
|
Category: ${failureCategory ?? "unknown"}`,
|
|
85771
|
-
|
|
85772
|
-
|
|
85773
|
-
});
|
|
85774
|
-
} catch (notifyErr) {
|
|
85775
|
-
logger.warn("execution", "Failed to send human review notification", {
|
|
85776
|
-
storyId: ctx.story.id,
|
|
85777
|
-
error: String(notifyErr)
|
|
85778
|
-
});
|
|
85779
|
-
}
|
|
85780
|
-
}
|
|
86016
|
+
failureMessage: "Failed to send human review notification"
|
|
86017
|
+
});
|
|
85781
86018
|
return { action: "pause", reason: `Human review needed: ${failureCategory ?? "unknown"}` };
|
|
85782
86019
|
}
|
|
85783
86020
|
return routeTddFailure(failureCategory, isLiteMode, ctx);
|
|
@@ -85815,9 +86052,6 @@ Category: ${failureCategory ?? "unknown"}`,
|
|
|
85815
86052
|
stderrTail: stderrTail || undefined,
|
|
85816
86053
|
outputTail: outputTail || undefined
|
|
85817
86054
|
});
|
|
85818
|
-
if (agentResult.rateLimited) {
|
|
85819
|
-
logger.warn("execution", "Rate limited \u2014 will retry", { storyId: ctx.story.id });
|
|
85820
|
-
}
|
|
85821
86055
|
await cleanupSessionOnFailure(ctx);
|
|
85822
86056
|
const failedPhaseNames = Object.keys(failedPhases);
|
|
85823
86057
|
const reasonParts = [];
|
|
@@ -85852,6 +86086,7 @@ var init_post_run = __esm(() => {
|
|
|
85852
86086
|
init_rollback();
|
|
85853
86087
|
init_git();
|
|
85854
86088
|
init_oscillation_breaker();
|
|
86089
|
+
init_post_run_notifications();
|
|
85855
86090
|
init_post_run_review_summary();
|
|
85856
86091
|
init_recurrence_pause();
|
|
85857
86092
|
init_tdd_failure_category();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nathapp/nax",
|
|
3
|
-
"version": "0.82.0-canary.
|
|
3
|
+
"version": "0.82.0-canary.5",
|
|
4
4
|
"description": "AI Coding Agent Orchestrator — loops until done",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"spec:lint": "bun run scripts/spec-lint.ts"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@nathapp/nax-ai": "0.1.
|
|
71
|
+
"@nathapp/nax-ai": "0.1.10",
|
|
72
72
|
"@types/react": "^19.2.14",
|
|
73
73
|
"chalk": "^5.6.2",
|
|
74
74
|
"commander": "^13.1.0",
|