@sagentlab/navarch-runtime 0.1.22 → 0.1.23
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/exit-conditions.cjs +29 -0
- package/dist/session.cjs +34 -7
- package/package.json +1 -1
package/dist/exit-conditions.cjs
CHANGED
|
@@ -5,6 +5,7 @@ exports.extractUsageFromClaudeJson = extractUsageFromClaudeJson;
|
|
|
5
5
|
exports.parseCodexJsonEvents = parseCodexJsonEvents;
|
|
6
6
|
exports.extractUsageFromCodexEvents = extractUsageFromCodexEvents;
|
|
7
7
|
exports.extractFinalMessageFromCodexEvents = extractFinalMessageFromCodexEvents;
|
|
8
|
+
exports.leadingVerificationVerdict = leadingVerificationVerdict;
|
|
8
9
|
exports.mapExitCondition = mapExitCondition;
|
|
9
10
|
/**
|
|
10
11
|
* Best-effort parse of `claude -p --output-format json` stdout into the
|
|
@@ -158,6 +159,21 @@ function extractFinalMessageFromCodexEvents(events) {
|
|
|
158
159
|
}
|
|
159
160
|
return null;
|
|
160
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Parses the leading `Verification verdict:` line convention shared with the
|
|
164
|
+
* control plane (lib/navarch/verification-verdict.ts). Verify completions
|
|
165
|
+
* must open with `Verification verdict: PASS|FAIL|BLOCKED`, and the
|
|
166
|
+
* completion API rejects a completion whose verdict contradicts its lease
|
|
167
|
+
* outcome, keeping the lease active.
|
|
168
|
+
*/
|
|
169
|
+
function leadingVerificationVerdict(report) {
|
|
170
|
+
const firstLine = report
|
|
171
|
+
.split(/\r?\n/)
|
|
172
|
+
.map((line) => line.trim())
|
|
173
|
+
.find(Boolean);
|
|
174
|
+
const match = /^Verification verdict:\s*(PASS|FAIL|BLOCKED)\b/i.exec(firstLine ?? "");
|
|
175
|
+
return match?.[1] ? match[1].toLowerCase() : null;
|
|
176
|
+
}
|
|
161
177
|
function summarize(text, maxLen = 500) {
|
|
162
178
|
const trimmed = text.trim();
|
|
163
179
|
if (!trimmed)
|
|
@@ -234,6 +250,19 @@ function mapExitCondition(result) {
|
|
|
234
250
|
// parseClaudeJsonResult's doc comment).
|
|
235
251
|
const reportText = result.reportText ?? parsedJson?.result ?? result.stdout;
|
|
236
252
|
const reportSummary = summarize(reportText) || "Adapter completed with no report text.";
|
|
253
|
+
// A verify agent's FAIL/BLOCKED verdict is a successful agent run whose
|
|
254
|
+
// conclusion is that the delivery did not pass. The completion API only
|
|
255
|
+
// accepts those verdicts with a failed outcome, so map them to a failed
|
|
256
|
+
// lease instead of posting a "completed" the control plane must reject.
|
|
257
|
+
const verdict = leadingVerificationVerdict(reportText);
|
|
258
|
+
if (verdict === "fail" || verdict === "blocked") {
|
|
259
|
+
return {
|
|
260
|
+
leaseOutcome: "failed",
|
|
261
|
+
exitStatus: "failed",
|
|
262
|
+
reportSummary,
|
|
263
|
+
evidenceUrls,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
237
266
|
// Headless agent CLIs normally exit 0 after producing a final response,
|
|
238
267
|
// including when that response says the task could not start. Treat an
|
|
239
268
|
// explicit leading blocked verdict as a failed lease so the dispatcher can
|
package/dist/session.cjs
CHANGED
|
@@ -107,7 +107,7 @@ async function runSession(deps, claimed, sessionId) {
|
|
|
107
107
|
const failureSummary = `Project ${task.project_id} has no GitHub repository URL. Set it in Project settings before dispatching work.`;
|
|
108
108
|
await api.completeLease(leaseId, {
|
|
109
109
|
status: "failed",
|
|
110
|
-
report: failureSummary,
|
|
110
|
+
report: verificationFailureReport(task.task_type, failureSummary),
|
|
111
111
|
failure_summary: failureSummary,
|
|
112
112
|
evidence_urls: [],
|
|
113
113
|
cost: { tokens_in: 0, tokens_out: 0, cost_usd: 0 },
|
|
@@ -181,7 +181,7 @@ async function runSession(deps, claimed, sessionId) {
|
|
|
181
181
|
await api
|
|
182
182
|
.completeLease(leaseId, {
|
|
183
183
|
status: "failed",
|
|
184
|
-
report: "Docker sandbox unavailable on this machine.",
|
|
184
|
+
report: verificationFailureReport(task.task_type, "Docker sandbox unavailable on this machine."),
|
|
185
185
|
failure_summary: "Docker sandbox unavailable on this machine.",
|
|
186
186
|
evidence_urls: [],
|
|
187
187
|
cost: { tokens_in: 0, tokens_out: 0, cost_usd: 0 },
|
|
@@ -395,9 +395,12 @@ async function runSession(deps, claimed, sessionId) {
|
|
|
395
395
|
// redact it like every other warn in this block.
|
|
396
396
|
log.warn(`transcript upload failed for ${leaseId}: ${(0, redact_cjs_1.redactText)(String(err), knownSecrets)}`);
|
|
397
397
|
}
|
|
398
|
+
const redactedReport = (0, redact_cjs_1.redactText)(mapping.reportSummary, knownSecrets);
|
|
398
399
|
const completion = {
|
|
399
400
|
status: mapping.leaseOutcome,
|
|
400
|
-
report:
|
|
401
|
+
report: mapping.leaseOutcome === "failed"
|
|
402
|
+
? verificationFailureReport(task.task_type, redactedReport)
|
|
403
|
+
: redactedReport,
|
|
401
404
|
evidence_urls: mapping.evidenceUrls,
|
|
402
405
|
cost: {
|
|
403
406
|
...(result.tokensIn !== undefined ? { tokens_in: result.tokensIn } : {}),
|
|
@@ -448,9 +451,9 @@ async function runSession(deps, claimed, sessionId) {
|
|
|
448
451
|
await api.completeLease(leaseId, {
|
|
449
452
|
...completion,
|
|
450
453
|
status: "failed",
|
|
451
|
-
report: remediable
|
|
454
|
+
report: verificationFailureReport(task.task_type, remediable
|
|
452
455
|
? redactedRejection
|
|
453
|
-
: `${redactedRejection}\n\n---\n\n${completion.report}
|
|
456
|
+
: `${redactedRejection}\n\n---\n\n${completion.report}`),
|
|
454
457
|
failure_summary: redactedRejection,
|
|
455
458
|
exit_status: "failed",
|
|
456
459
|
});
|
|
@@ -482,9 +485,9 @@ async function runSession(deps, claimed, sessionId) {
|
|
|
482
485
|
await api
|
|
483
486
|
.completeLease(leaseId, {
|
|
484
487
|
status: "failed",
|
|
485
|
-
report: lastCompletion
|
|
488
|
+
report: verificationFailureReport(task.task_type, lastCompletion
|
|
486
489
|
? `${lastCompletion.report}\n\n---\n\n${failureSummary}`
|
|
487
|
-
: failureSummary,
|
|
490
|
+
: failureSummary),
|
|
488
491
|
failure_summary: failureSummary,
|
|
489
492
|
evidence_urls: lastCompletion?.evidence_urls ?? [],
|
|
490
493
|
cost: lastCompletion?.cost ?? { tokens_in: 0, tokens_out: 0, cost_usd: 0 },
|
|
@@ -525,6 +528,24 @@ function sumReportedUsage(attempts, key) {
|
|
|
525
528
|
});
|
|
526
529
|
return reported.length > 0 ? reported.reduce((sum, value) => sum + value, 0) : undefined;
|
|
527
530
|
}
|
|
531
|
+
/**
|
|
532
|
+
* The control plane refuses to release a verify lease whose failed completion
|
|
533
|
+
* does not open with `Verification verdict: FAIL` or `BLOCKED`
|
|
534
|
+
* (verification_verdict_invalid), and that gate applies to runtime-authored
|
|
535
|
+
* failure reports too. When the report carries no usable verdict — the agent
|
|
536
|
+
* crashed, timed out, exited non-zero, or opened with PASS despite the failed
|
|
537
|
+
* outcome — label it BLOCKED: the failure prevented an acceptance decision.
|
|
538
|
+
* Without this, the failure completion is itself rejected, the session
|
|
539
|
+
* crashes, and the lease dangles until expiry.
|
|
540
|
+
*/
|
|
541
|
+
function verificationFailureReport(taskType, report) {
|
|
542
|
+
if (taskType !== "verify")
|
|
543
|
+
return report;
|
|
544
|
+
const verdict = (0, exit_conditions_cjs_1.leadingVerificationVerdict)(report);
|
|
545
|
+
if (verdict === "fail" || verdict === "blocked")
|
|
546
|
+
return report;
|
|
547
|
+
return `Verification verdict: BLOCKED — the session ended before verification reached an acceptance decision.\n\n${report}`;
|
|
548
|
+
}
|
|
528
549
|
/** Rejection codes another agent turn in the same worktree can plausibly fix. */
|
|
529
550
|
const REMEDIABLE_REJECTION_CODES = new Set([
|
|
530
551
|
"pr_required",
|
|
@@ -536,6 +557,12 @@ const REMEDIABLE_REJECTION_CODES = new Set([
|
|
|
536
557
|
// The marker is already on the right head; the reviewer only has to relabel
|
|
537
558
|
// the body, which is exactly what a remediation turn can do.
|
|
538
559
|
"review_evidence_mislabeled",
|
|
560
|
+
// A verify agent that exited zero without the mandated leading
|
|
561
|
+
// `Verification verdict:` line only has to restate its report; the
|
|
562
|
+
// rejection prose spells out the exact format. Failed outcomes never reach
|
|
563
|
+
// remediation (see the leaseOutcome === "completed" gate) — their reports
|
|
564
|
+
// are verdict-labelled by verificationFailureReport before posting.
|
|
565
|
+
"verification_verdict_invalid",
|
|
539
566
|
]);
|
|
540
567
|
/**
|
|
541
568
|
* A 409 the control plane raised to reject *this* completion's contents (as
|
package/package.json
CHANGED