@sagentlab/navarch-runtime 0.1.75 → 0.1.77
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/agent-auth.cjs +76 -0
- package/dist/exit-conditions.cjs +23 -0
- package/dist/session.cjs +11 -1
- package/dist/update-coordinator.cjs +22 -2
- package/package.json +1 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectAgentAuthFailure = detectAgentAuthFailure;
|
|
4
|
+
const exit_conditions_cjs_1 = require("./exit-conditions.cjs");
|
|
5
|
+
/**
|
|
6
|
+
* Errors the coding agent CLIs print when their stored login cannot be used.
|
|
7
|
+
* Matched only against CLI-authored error channels (a structured error result,
|
|
8
|
+
* Codex error events, stderr), never the agent's own prose, so a task about a
|
|
9
|
+
* login page cannot pause the machine.
|
|
10
|
+
*
|
|
11
|
+
* - Claude Code: "Failed to authenticate: OAuth session expired and could not
|
|
12
|
+
* be refreshed", "OAuth token has expired", "Invalid API key · Please run
|
|
13
|
+
* /login", "Not logged in · Please run /login", 401 authentication_error.
|
|
14
|
+
* - Codex: "Your access token could not be refreshed because your refresh
|
|
15
|
+
* token has expired/was already used", "Not logged in", "Please run `codex
|
|
16
|
+
* login`", token_expired / refresh_token_* error codes.
|
|
17
|
+
*/
|
|
18
|
+
const AGENT_AUTH_PATTERNS = [
|
|
19
|
+
/\bfailed to authenticate\b/i,
|
|
20
|
+
/\boauth (?:session|token) (?:has )?expired\b/i,
|
|
21
|
+
/\bplease run \/login\b/i,
|
|
22
|
+
/\bnot logged in\b/i,
|
|
23
|
+
/\bcodex login\b/i,
|
|
24
|
+
/\baccess token could not be refreshed\b/i,
|
|
25
|
+
/\brefresh token (?:has expired|was already used|is invalid|expired)\b/i,
|
|
26
|
+
/\b(?:refresh_token_(?:expired|reused|invalidated)|token_expired)\b/i,
|
|
27
|
+
/\bauthentication_error\b/i,
|
|
28
|
+
];
|
|
29
|
+
function matchingLine(text) {
|
|
30
|
+
for (const line of text.split("\n")) {
|
|
31
|
+
const trimmed = line.trim();
|
|
32
|
+
if (trimmed && AGENT_AUTH_PATTERNS.some((pattern) => pattern.test(trimmed))) {
|
|
33
|
+
return trimmed.length > 300 ? `${trimmed.slice(0, 299)}…` : trimmed;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Detects a turn that ended because the coding agent CLI could not
|
|
40
|
+
* authenticate. Only failed turns qualify: a clean exit whose structured result
|
|
41
|
+
* is not an error did real work, whatever its transcript mentions.
|
|
42
|
+
*/
|
|
43
|
+
function detectAgentAuthFailure(result) {
|
|
44
|
+
if (result.killedByLeaseLoss || result.timedOut || result.capacityError)
|
|
45
|
+
return null;
|
|
46
|
+
const claude = (0, exit_conditions_cjs_1.parseClaudeJsonResult)(result.stdout) ?? (0, exit_conditions_cjs_1.parseClaudeJsonResult)(result.stderr);
|
|
47
|
+
if (claude) {
|
|
48
|
+
if (!claude.is_error && result.exitCode === 0)
|
|
49
|
+
return null;
|
|
50
|
+
if (claude.is_error && typeof claude.result === "string") {
|
|
51
|
+
const detail = matchingLine(claude.result);
|
|
52
|
+
if (detail)
|
|
53
|
+
return { detail };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const codexErrors = (0, exit_conditions_cjs_1.parseCodexJsonEvents)(result.stdout).flatMap((event) => {
|
|
57
|
+
const raw = event;
|
|
58
|
+
if (raw.type === "error" && typeof raw.message === "string")
|
|
59
|
+
return [raw.message];
|
|
60
|
+
if (raw.type === "turn.failed") {
|
|
61
|
+
const error = raw.error;
|
|
62
|
+
if (typeof error?.message === "string")
|
|
63
|
+
return [error.message];
|
|
64
|
+
}
|
|
65
|
+
return [];
|
|
66
|
+
});
|
|
67
|
+
if (result.exitCode === 0 && codexErrors.length === 0 && !claude?.is_error)
|
|
68
|
+
return null;
|
|
69
|
+
for (const message of codexErrors) {
|
|
70
|
+
const detail = matchingLine(message);
|
|
71
|
+
if (detail)
|
|
72
|
+
return { detail };
|
|
73
|
+
}
|
|
74
|
+
const detail = matchingLine(result.stderr);
|
|
75
|
+
return detail ? { detail } : null;
|
|
76
|
+
}
|
package/dist/exit-conditions.cjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AGENT_AUTH_FAILURE_PREFIX = void 0;
|
|
4
|
+
exports.agentAuthFailureReport = agentAuthFailureReport;
|
|
3
5
|
exports.parseClaudeJsonResult = parseClaudeJsonResult;
|
|
4
6
|
exports.extractUsageFromClaudeJson = extractUsageFromClaudeJson;
|
|
5
7
|
exports.parseCodexJsonEvents = parseCodexJsonEvents;
|
|
@@ -8,6 +10,19 @@ exports.extractFinalMessageFromCodexEvents = extractFinalMessageFromCodexEvents;
|
|
|
8
10
|
exports.leadingVerificationVerdict = leadingVerificationVerdict;
|
|
9
11
|
exports.leadWithReviewFixMarker = leadWithReviewFixMarker;
|
|
10
12
|
exports.mapExitCondition = mapExitCondition;
|
|
13
|
+
/**
|
|
14
|
+
* Leading text of every report for a turn the coding agent CLI refused because
|
|
15
|
+
* its own login on this machine is gone (agent-auth.cts). The control plane
|
|
16
|
+
* recognizes it -- and the raw CLI errors, for older runtimes -- and requeues
|
|
17
|
+
* the task without spending a retry while pausing this machine's claims until
|
|
18
|
+
* someone logs in again (lib/navarch/failure-attribution.ts
|
|
19
|
+
* `provider.agent_auth`).
|
|
20
|
+
*/
|
|
21
|
+
exports.AGENT_AUTH_FAILURE_PREFIX = "Coding agent authentication failed";
|
|
22
|
+
function agentAuthFailureReport(detail) {
|
|
23
|
+
return `${exports.AGENT_AUTH_FAILURE_PREFIX}: ${detail} ` +
|
|
24
|
+
"Log the coding agent in again on this machine (`claude /login` or `codex login`), then resume the agent.";
|
|
25
|
+
}
|
|
11
26
|
/**
|
|
12
27
|
* Best-effort parse of `claude -p --output-format json` stdout into the
|
|
13
28
|
* assumed result shape above. Tries the whole trimmed stdout first (the
|
|
@@ -260,6 +275,14 @@ function mapExitCondition(result) {
|
|
|
260
275
|
evidenceUrls,
|
|
261
276
|
};
|
|
262
277
|
}
|
|
278
|
+
if (result.authError) {
|
|
279
|
+
return {
|
|
280
|
+
leaseOutcome: "failed",
|
|
281
|
+
exitStatus: "failed",
|
|
282
|
+
reportSummary: summarize(agentAuthFailureReport(result.authError)),
|
|
283
|
+
evidenceUrls,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
263
286
|
if (result.timedOut) {
|
|
264
287
|
return {
|
|
265
288
|
leaseOutcome: "failed",
|
package/dist/session.cjs
CHANGED
|
@@ -26,6 +26,7 @@ const worktree_guard_cjs_1 = require("./worktree-guard.cjs");
|
|
|
26
26
|
const adapter_watchdog_cjs_1 = require("./adapter-watchdog.cjs");
|
|
27
27
|
const lease_heartbeat_cjs_1 = require("./lease-heartbeat.cjs");
|
|
28
28
|
const adapter_capacity_cjs_1 = require("./adapter-capacity.cjs");
|
|
29
|
+
const agent_auth_cjs_1 = require("./agent-auth.cjs");
|
|
29
30
|
const browser_evidence_cjs_1 = require("./browser-evidence.cjs");
|
|
30
31
|
/** Filename the generated platform MCP config is written under inside the session metadata directory. */
|
|
31
32
|
const MCP_CONFIG_FILENAME = "mcp-config.json";
|
|
@@ -551,6 +552,15 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
551
552
|
await checkpointForRecovery(turnResult);
|
|
552
553
|
sessionOutcome.claimCooldownUntil = Math.max(sessionOutcome.claimCooldownUntil ?? 0, capacityLimit.retryAtMs);
|
|
553
554
|
}
|
|
555
|
+
// A dead CLI login fails every task this machine claims in well under a
|
|
556
|
+
// second. Report it as such so the control plane requeues the task
|
|
557
|
+
// without spending a retry and pauses this machine until it is logged in
|
|
558
|
+
// again, instead of burning the task's budget into needs_human.
|
|
559
|
+
const authFailure = capacityLimit ? null : (0, agent_auth_cjs_1.detectAgentAuthFailure)(turnResult);
|
|
560
|
+
if (authFailure) {
|
|
561
|
+
turnResult.authError = authFailure.detail;
|
|
562
|
+
log.warn(`session ${leaseId}: coding agent authentication failed: ${authFailure.detail}`);
|
|
563
|
+
}
|
|
554
564
|
// Close the small race between a naturally completed turn and the next
|
|
555
565
|
// scheduled heartbeat. If guidance landed, run another turn before the
|
|
556
566
|
// lease can be completed. A terminal heartbeat response means the
|
|
@@ -563,7 +573,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
563
573
|
log.warn(`session ${leaseId} stopped without completion because its lease is no longer active.`);
|
|
564
574
|
return sessionOutcome;
|
|
565
575
|
}
|
|
566
|
-
if (!capacityLimit && !leaseLost && !turnResult.timedOut && pendingGuidance.length > 0)
|
|
576
|
+
if (!capacityLimit && !authFailure && !leaseLost && !turnResult.timedOut && pendingGuidance.length > 0)
|
|
567
577
|
continue;
|
|
568
578
|
const result = {
|
|
569
579
|
...turnResult,
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RuntimeUpdateCoordinator = void 0;
|
|
3
|
+
exports.RuntimeUpdateCoordinator = exports.UPDATE_RETRY_MAX_MS = exports.UPDATE_RETRY_BASE_MS = void 0;
|
|
4
4
|
const logger_cjs_1 = require("./logger.cjs");
|
|
5
5
|
const update_installer_cjs_1 = require("./update-installer.cjs");
|
|
6
6
|
const version_cjs_1 = require("./version.cjs");
|
|
7
7
|
const supervisor_cjs_1 = require("./supervisor.cjs");
|
|
8
8
|
const log = (0, logger_cjs_1.createLogger)("update");
|
|
9
|
+
// A failed stage reports `failed`, which sends a heartbeat at once, whose
|
|
10
|
+
// response repeats the same directive. Without a pause that loop re-runs
|
|
11
|
+
// `npm pack` several times a second (seen while npm's CDN still served the
|
|
12
|
+
// pre-publish packument for a freshly rolled-out version).
|
|
13
|
+
exports.UPDATE_RETRY_BASE_MS = 30_000;
|
|
14
|
+
exports.UPDATE_RETRY_MAX_MS = 10 * 60_000;
|
|
15
|
+
function directiveKey(directive) {
|
|
16
|
+
return `${directive.target_version}\0${directive.integrity}`;
|
|
17
|
+
}
|
|
9
18
|
class RuntimeUpdateCoordinator {
|
|
10
19
|
options;
|
|
11
20
|
targetInProgress = null;
|
|
12
21
|
restartInProgress = null;
|
|
13
22
|
warnedUnsupervised = false;
|
|
23
|
+
lastFailure = null;
|
|
14
24
|
constructor(options) {
|
|
15
25
|
this.options = options;
|
|
16
26
|
}
|
|
@@ -26,6 +36,9 @@ class RuntimeUpdateCoordinator {
|
|
|
26
36
|
}
|
|
27
37
|
if (this.targetInProgress)
|
|
28
38
|
return;
|
|
39
|
+
const key = directiveKey(directive);
|
|
40
|
+
if (this.lastFailure?.key === key && this.now() < this.lastFailure.retryAt)
|
|
41
|
+
return;
|
|
29
42
|
this.targetInProgress = directive.target_version;
|
|
30
43
|
void this.apply(directive);
|
|
31
44
|
}
|
|
@@ -57,6 +70,9 @@ class RuntimeUpdateCoordinator {
|
|
|
57
70
|
this.restartInProgress = null;
|
|
58
71
|
}
|
|
59
72
|
}
|
|
73
|
+
now() {
|
|
74
|
+
return (this.options.now ?? Date.now)();
|
|
75
|
+
}
|
|
60
76
|
async apply(directive) {
|
|
61
77
|
const { config, heartbeat, claimLoop, capacity } = this.options;
|
|
62
78
|
try {
|
|
@@ -73,7 +89,11 @@ class RuntimeUpdateCoordinator {
|
|
|
73
89
|
}
|
|
74
90
|
catch (error) {
|
|
75
91
|
const message = error instanceof Error ? error.message : String(error);
|
|
76
|
-
|
|
92
|
+
const key = directiveKey(directive);
|
|
93
|
+
const failures = this.lastFailure?.key === key ? this.lastFailure.failures + 1 : 1;
|
|
94
|
+
const delayMs = Math.min(exports.UPDATE_RETRY_BASE_MS * 2 ** (failures - 1), exports.UPDATE_RETRY_MAX_MS);
|
|
95
|
+
this.lastFailure = { key, failures, retryAt: this.now() + delayMs };
|
|
96
|
+
log.error(`runtime update failed: ${message} (retrying ${directive.target_version} in ${Math.round(delayMs / 1000)}s)`);
|
|
77
97
|
heartbeat.setUpdateState("failed", message.slice(0, 500));
|
|
78
98
|
claimLoop.start();
|
|
79
99
|
this.targetInProgress = null;
|
package/package.json
CHANGED