@sagentlab/navarch-runtime 0.1.9 → 0.1.10
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/adapters/codex.cjs +6 -2
- package/dist/cli.cjs +14 -1
- package/dist/exit-conditions.cjs +17 -3
- package/dist/session.cjs +109 -60
- package/package.json +1 -1
package/dist/adapters/codex.cjs
CHANGED
|
@@ -136,7 +136,11 @@ function tomlKey(value) {
|
|
|
136
136
|
function safeEnvSegment(value) {
|
|
137
137
|
return value.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
138
138
|
}
|
|
139
|
-
/**
|
|
139
|
+
/**
|
|
140
|
+
* Parses stdout for `codex exec --json` usage/final-message events and folds
|
|
141
|
+
* them onto the raw result. Codex currently reports token counts but no USD
|
|
142
|
+
* cost, so costUsd remains absent unless the stream explicitly provides one.
|
|
143
|
+
*/
|
|
140
144
|
function attachUsage(result) {
|
|
141
145
|
const events = (0, exit_conditions_cjs_1.parseCodexJsonEvents)(result.stdout);
|
|
142
146
|
if (events.length === 0)
|
|
@@ -147,7 +151,7 @@ function attachUsage(result) {
|
|
|
147
151
|
...result,
|
|
148
152
|
tokensIn: usage.tokensIn,
|
|
149
153
|
tokensOut: usage.tokensOut,
|
|
150
|
-
costUsd: usage.costUsd,
|
|
154
|
+
...(usage.costUsd !== undefined ? { costUsd: usage.costUsd } : {}),
|
|
151
155
|
...(reportText !== undefined ? { reportText } : {}),
|
|
152
156
|
};
|
|
153
157
|
}
|
package/dist/cli.cjs
CHANGED
|
@@ -19,6 +19,19 @@ const update_coordinator_cjs_1 = require("./update-coordinator.cjs");
|
|
|
19
19
|
const supervisor_cjs_1 = require("./supervisor.cjs");
|
|
20
20
|
const log = (0, logger_cjs_1.createLogger)("cli");
|
|
21
21
|
const PACKAGE_NAME = "@sagentlab/navarch-runtime";
|
|
22
|
+
/** Include a control-plane response's safe error detail in top-level CLI failures. */
|
|
23
|
+
function describeCliError(err) {
|
|
24
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25
|
+
if (!err || typeof err !== "object" || !("body" in err))
|
|
26
|
+
return message;
|
|
27
|
+
const body = err.body;
|
|
28
|
+
const detail = body && typeof body === "object" && "error" in body && typeof body.error === "string"
|
|
29
|
+
? body.error
|
|
30
|
+
: typeof body === "string"
|
|
31
|
+
? body
|
|
32
|
+
: undefined;
|
|
33
|
+
return detail && !message.includes(detail) ? `${message}: ${detail}` : message;
|
|
34
|
+
}
|
|
22
35
|
/**
|
|
23
36
|
* How to tell the user to re-invoke this CLI, matching however THEY launched
|
|
24
37
|
* it. The bare `navarch-runtime` bin only exists on PATH after a global install
|
|
@@ -305,7 +318,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
305
318
|
}
|
|
306
319
|
}
|
|
307
320
|
catch (err) {
|
|
308
|
-
log.error(
|
|
321
|
+
log.error(describeCliError(err));
|
|
309
322
|
process.exitCode = 1;
|
|
310
323
|
}
|
|
311
324
|
}
|
package/dist/exit-conditions.cjs
CHANGED
|
@@ -95,7 +95,7 @@ function parseCodexJsonEvents(stdout) {
|
|
|
95
95
|
function extractUsageFromCodexEvents(events) {
|
|
96
96
|
let tokensIn = 0;
|
|
97
97
|
let tokensOut = 0;
|
|
98
|
-
let costUsd
|
|
98
|
+
let costUsd;
|
|
99
99
|
for (const event of events) {
|
|
100
100
|
if (event.type === "turn.completed" && event.usage) {
|
|
101
101
|
tokensIn = event.usage.input_tokens ?? 0;
|
|
@@ -104,8 +104,22 @@ function extractUsageFromCodexEvents(events) {
|
|
|
104
104
|
costUsd = event.usage.total_cost_usd;
|
|
105
105
|
}
|
|
106
106
|
if (event.msg?.type === "token_count") {
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
const nested = event.msg.info?.total_token_usage;
|
|
108
|
+
tokensIn = nested
|
|
109
|
+
? (nested.input_tokens ?? 0)
|
|
110
|
+
: (event.msg.input_tokens ?? 0) + (event.msg.cached_input_tokens ?? 0);
|
|
111
|
+
tokensOut = nested?.output_tokens ?? event.msg.output_tokens ?? 0;
|
|
112
|
+
if (typeof nested?.total_cost_usd === "number")
|
|
113
|
+
costUsd = nested.total_cost_usd;
|
|
114
|
+
}
|
|
115
|
+
if (event.type === "event_msg" && event.payload?.type === "token_count") {
|
|
116
|
+
const total = event.payload.info?.total_token_usage;
|
|
117
|
+
if (total) {
|
|
118
|
+
tokensIn = total.input_tokens ?? 0;
|
|
119
|
+
tokensOut = total.output_tokens ?? 0;
|
|
120
|
+
if (typeof total.total_cost_usd === "number")
|
|
121
|
+
costUsd = total.total_cost_usd;
|
|
122
|
+
}
|
|
109
123
|
}
|
|
110
124
|
if (typeof event.msg?.total_cost_usd === "number") {
|
|
111
125
|
costUsd = event.msg.total_cost_usd;
|
package/dist/session.cjs
CHANGED
|
@@ -7,6 +7,7 @@ exports.runSession = runSession;
|
|
|
7
7
|
exports.toEnvMap = toEnvMap;
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const node_fs_1 = require("node:fs");
|
|
10
|
+
const api_cjs_1 = require("./api.cjs");
|
|
10
11
|
const sandbox_cjs_1 = require("./sandbox.cjs");
|
|
11
12
|
const index_cjs_1 = require("./adapters/index.cjs");
|
|
12
13
|
const exit_conditions_cjs_1 = require("./exit-conditions.cjs");
|
|
@@ -21,6 +22,7 @@ const worktree_guard_cjs_1 = require("./worktree-guard.cjs");
|
|
|
21
22
|
/** Filename the generated platform MCP config is written under inside the session metadata directory. */
|
|
22
23
|
const MCP_CONFIG_FILENAME = "mcp-config.json";
|
|
23
24
|
const GIT_CREDENTIAL_HELPER_FILENAME = "git-credential-navarch.cjs";
|
|
25
|
+
const PR_REQUIRED_COMPLETION_RETRIES = 2;
|
|
24
26
|
const log = (0, logger_cjs_1.createLogger)("session");
|
|
25
27
|
/**
|
|
26
28
|
* Runs one claimed task end to end (implementation-plan.md WP-07):
|
|
@@ -250,7 +252,8 @@ async function runSession(deps, claimed, sessionId) {
|
|
|
250
252
|
const bin = config.agentType === "codex" ? config.codexBin : config.claudeBin;
|
|
251
253
|
const extraArgs = config.agentType === "codex" ? config.codexExtraArgs : config.claudeExtraArgs;
|
|
252
254
|
const attempts = [];
|
|
253
|
-
let
|
|
255
|
+
let nextPrompt = null;
|
|
256
|
+
let prRequiredCompletionRetries = 0;
|
|
254
257
|
while (true) {
|
|
255
258
|
// Guidance can arrive while the worktree/sandbox is being prepared.
|
|
256
259
|
// It is already included in deliveredGuidance, so clear the pending
|
|
@@ -259,9 +262,11 @@ async function runSession(deps, claimed, sessionId) {
|
|
|
259
262
|
activeAbortController = new AbortController();
|
|
260
263
|
if (leaseLost)
|
|
261
264
|
activeAbortController.abort();
|
|
262
|
-
const runPrompt =
|
|
263
|
-
|
|
264
|
-
|
|
265
|
+
const runPrompt = nextPrompt ??
|
|
266
|
+
(deliveredGuidance.length > (bundle.guidance?.length ?? 0)
|
|
267
|
+
? (0, prompt_cjs_1.renderGuidanceCorrectionPrompt)(promptText, deliveredGuidance)
|
|
268
|
+
: promptText);
|
|
269
|
+
nextPrompt = null;
|
|
265
270
|
await node_fs_1.promises.writeFile(node_path_1.default.join(workDir, "prompt.md"), runPrompt, "utf8");
|
|
266
271
|
const turnResult = await adapter.run({
|
|
267
272
|
prompt: runPrompt,
|
|
@@ -286,66 +291,93 @@ async function runSession(deps, claimed, sessionId) {
|
|
|
286
291
|
await pollLease();
|
|
287
292
|
if (!leaseLost && pendingGuidance.length > 0)
|
|
288
293
|
continue;
|
|
289
|
-
result = {
|
|
294
|
+
const result = {
|
|
290
295
|
...turnResult,
|
|
291
|
-
tokensIn: attempts
|
|
292
|
-
tokensOut: attempts
|
|
293
|
-
costUsd: attempts
|
|
296
|
+
tokensIn: sumReportedUsage(attempts, "tokensIn"),
|
|
297
|
+
tokensOut: sumReportedUsage(attempts, "tokensOut"),
|
|
298
|
+
costUsd: sumReportedUsage(attempts, "costUsd"),
|
|
294
299
|
};
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
try {
|
|
299
|
-
const prUrl = await (0, github_pr_cjs_1.findHeadBranchPullRequestUrl)({
|
|
300
|
-
repository: bundle.repository?.full_name ?? task.repo,
|
|
301
|
-
headBranch: gitWorktree.branch,
|
|
302
|
-
githubToken,
|
|
300
|
+
const mapping = (0, exit_conditions_cjs_1.mapExitCondition)({
|
|
301
|
+
...result,
|
|
302
|
+
killedByLeaseLoss: leaseLost || result.killedByLeaseLoss,
|
|
303
303
|
});
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
304
|
+
try {
|
|
305
|
+
const prUrl = await (0, github_pr_cjs_1.findHeadBranchPullRequestUrl)({
|
|
306
|
+
repository: bundle.repository?.full_name ?? task.repo,
|
|
307
|
+
headBranch: gitWorktree.branch,
|
|
308
|
+
githubToken,
|
|
309
|
+
});
|
|
310
|
+
if (prUrl)
|
|
311
|
+
mapping.evidenceUrls.push(prUrl);
|
|
312
|
+
}
|
|
313
|
+
catch (err) {
|
|
314
|
+
// Evidence discovery is best-effort: a GitHub outage or token scope
|
|
315
|
+
// mismatch must not turn an otherwise valid completion into a crash.
|
|
316
|
+
log.warn(`head-branch PR lookup failed for ${leaseId}: ${String(err)}`);
|
|
317
|
+
}
|
|
318
|
+
const knownSecrets = registry.list();
|
|
319
|
+
if (mapping.leaseOutcome === "failed") {
|
|
320
|
+
log.warn(`adapter failed for ${leaseId}: ${(0, redact_cjs_1.redactText)(mapping.reportSummary, knownSecrets)}`);
|
|
321
|
+
}
|
|
322
|
+
const transcript = attempts
|
|
323
|
+
.flatMap((attempt, index) => [
|
|
324
|
+
`# agent turn ${index + 1} stdout`,
|
|
325
|
+
(0, redact_cjs_1.redactText)(attempt.stdout, knownSecrets),
|
|
326
|
+
"",
|
|
327
|
+
`# agent turn ${index + 1} stderr`,
|
|
328
|
+
(0, redact_cjs_1.redactText)(attempt.stderr, knownSecrets),
|
|
329
|
+
"",
|
|
330
|
+
])
|
|
331
|
+
.join("\n");
|
|
332
|
+
let transcriptUrl;
|
|
333
|
+
try {
|
|
334
|
+
const { upload_url, public_url } = await api.getTranscriptUploadUrl(leaseId);
|
|
335
|
+
await (0, upload_cjs_1.uploadTranscript)(upload_url, transcript);
|
|
336
|
+
transcriptUrl = public_url;
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
log.warn(`transcript upload failed for ${leaseId}: ${String(err)}`);
|
|
340
|
+
}
|
|
341
|
+
const completion = {
|
|
342
|
+
status: mapping.leaseOutcome,
|
|
343
|
+
report: (0, redact_cjs_1.redactText)(mapping.reportSummary, knownSecrets),
|
|
344
|
+
evidence_urls: mapping.evidenceUrls,
|
|
345
|
+
cost: {
|
|
346
|
+
...(result.tokensIn !== undefined ? { tokens_in: result.tokensIn } : {}),
|
|
347
|
+
...(result.tokensOut !== undefined ? { tokens_out: result.tokensOut } : {}),
|
|
348
|
+
...(result.costUsd !== undefined ? { cost_usd: result.costUsd } : {}),
|
|
349
|
+
},
|
|
350
|
+
transcript_url: transcriptUrl,
|
|
351
|
+
exit_status: mapping.exitStatus,
|
|
352
|
+
agent_type: config.agentType,
|
|
353
|
+
...executionReport,
|
|
354
|
+
};
|
|
355
|
+
try {
|
|
356
|
+
await api.completeLease(leaseId, completion);
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
const rejection = mapping.leaseOutcome === "completed" ? prRequiredRejectionMessage(err) : null;
|
|
361
|
+
if (!rejection)
|
|
362
|
+
throw err;
|
|
363
|
+
const redactedRejection = (0, redact_cjs_1.redactText)(rejection, knownSecrets);
|
|
364
|
+
if (prRequiredCompletionRetries < PR_REQUIRED_COMPLETION_RETRIES) {
|
|
365
|
+
prRequiredCompletionRetries += 1;
|
|
366
|
+
log.warn(`completion for ${leaseId} requires a pull request; restarting agent turn ${prRequiredCompletionRetries}/${PR_REQUIRED_COMPLETION_RETRIES} in the same worktree.`);
|
|
367
|
+
nextPrompt = redactedRejection;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
log.warn(`completion for ${leaseId} still requires a pull request after ${PR_REQUIRED_COMPLETION_RETRIES} retries; failing with the control-plane rejection.`);
|
|
371
|
+
await api.completeLease(leaseId, {
|
|
372
|
+
...completion,
|
|
373
|
+
status: "failed",
|
|
374
|
+
report: redactedRejection,
|
|
375
|
+
failure_summary: redactedRejection,
|
|
376
|
+
exit_status: "failed",
|
|
377
|
+
});
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
334
380
|
}
|
|
335
|
-
await api.completeLease(leaseId, {
|
|
336
|
-
status: mapping.leaseOutcome,
|
|
337
|
-
report: (0, redact_cjs_1.redactText)(mapping.reportSummary, knownSecrets),
|
|
338
|
-
evidence_urls: mapping.evidenceUrls,
|
|
339
|
-
cost: {
|
|
340
|
-
tokens_in: result.tokensIn ?? 0,
|
|
341
|
-
tokens_out: result.tokensOut ?? 0,
|
|
342
|
-
cost_usd: result.costUsd ?? 0,
|
|
343
|
-
},
|
|
344
|
-
transcript_url: transcriptUrl,
|
|
345
|
-
exit_status: mapping.exitStatus,
|
|
346
|
-
agent_type: config.agentType,
|
|
347
|
-
...executionReport,
|
|
348
|
-
});
|
|
349
381
|
}
|
|
350
382
|
catch (err) {
|
|
351
383
|
log.error(`session ${leaseId} threw before completing: ${String(err)}`);
|
|
@@ -373,6 +405,23 @@ async function runSession(deps, claimed, sessionId) {
|
|
|
373
405
|
await node_fs_1.promises.rm(workDir, { recursive: true, force: true }).catch(() => undefined);
|
|
374
406
|
}
|
|
375
407
|
}
|
|
408
|
+
function sumReportedUsage(attempts, key) {
|
|
409
|
+
const reported = attempts.flatMap((attempt) => {
|
|
410
|
+
const value = attempt[key];
|
|
411
|
+
return value === undefined ? [] : [value];
|
|
412
|
+
});
|
|
413
|
+
return reported.length > 0 ? reported.reduce((sum, value) => sum + value, 0) : undefined;
|
|
414
|
+
}
|
|
415
|
+
function prRequiredRejectionMessage(err) {
|
|
416
|
+
if (!(err instanceof api_cjs_1.NavarchApiError) || err.status !== 409)
|
|
417
|
+
return null;
|
|
418
|
+
if (typeof err.body !== "object" || err.body === null || Array.isArray(err.body))
|
|
419
|
+
return null;
|
|
420
|
+
const body = err.body;
|
|
421
|
+
return body.code === "pr_required" && typeof body.error === "string"
|
|
422
|
+
? body.error
|
|
423
|
+
: null;
|
|
424
|
+
}
|
|
376
425
|
/** Uppercases + sanitizes secret names into shell-safe env var names for injectEnv(). */
|
|
377
426
|
function toEnvMap(secrets, credentialRefreshOrAuthorName, gitAuthorNameOrEmail = "sagentlab", gitAuthorEmail = "z@sagentlab.com") {
|
|
378
427
|
// Keep the existing `(secrets, authorName, authorEmail)` call shape while
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sagentlab/navarch-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Navarch machine-side session manager: registers a machine, claims tasks from the control-plane dispatcher, runs them via the Claude Code or Codex adapter, and reports results back.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"license": "MIT",
|