@sagentlab/navarch-runtime 0.1.51 → 0.1.52
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/README.md +13 -3
- package/dist/adapter-capacity.cjs +9 -2
- package/dist/adapters/opencode.cjs +89 -20
- package/dist/cli.cjs +29 -27
- package/dist/exit-conditions.cjs +10 -0
- package/dist/sandbox.cjs +2 -0
- package/dist/session.cjs +5 -2
- package/dist/upload.cjs +3 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -369,7 +369,7 @@ unchanged across the deployment.
|
|
|
369
369
|
| `NAVARCH_SANDBOX_EGRESS_NETWORK` | _(unset)_ | Docker network that enforces a profile's egress allowlist. Docker cannot filter by domain itself, so an allowlist profile without this fails closed to `--network=none` and records the denial. |
|
|
370
370
|
| `NAVARCH_DOCKER_IMAGE` | `ghcr.io/sagentlab/navarch-sandbox-agent:0.1.0` | Version-pinned per-session image with Node 20, git, GitHub CLI, ripgrep, jq, SSH, and Claude Code 2.1.218. Override with an image tag or digest you control. |
|
|
371
371
|
| `NAVARCH_AGENT` | saved choice, then `claude-code` | Local choice of agent adapter: `claude-code`, `codex`, `gemini`, `opencode`, or `acp`. Overrides the choice saved by `connect`/`register`; `start --agent` has highest priority. |
|
|
372
|
-
| `NAVARCH_RUNTIMES` |
|
|
372
|
+
| `NAVARCH_RUNTIMES` | resolved local agent choice | Comma list of installed/authenticated adapters advertised to dispatch. The control plane chooses among these per project/task. |
|
|
373
373
|
| `NAVARCH_UPDATE_CHANNEL` | `stable` | Release channel advertised by the worker (`stable` or `canary`); the server-managed machine channel remains authoritative. |
|
|
374
374
|
| `NAVARCH_AUTO_UPDATE` | on under `supervise` | Set `off`, `false`, or `0` to report releases without staging or activating them. Automatic activation is always off under plain `start`. |
|
|
375
375
|
| `NAVARCH_CLAUDE_BIN` | `claude` | Path/name of the Claude Code CLI binary. |
|
|
@@ -601,6 +601,16 @@ to OpenCode's remote-server config with OAuth disabled and environment-backed
|
|
|
601
601
|
headers. Missing accounting fields stay absent rather than becoming a
|
|
602
602
|
fabricated zero.
|
|
603
603
|
|
|
604
|
+
OpenCode also runs with `--print-logs --log-level ERROR` by default (see its
|
|
605
|
+
[CLI flags](https://opencode.ai/docs/cli/#global-flags)), so provider errors
|
|
606
|
+
that are missing from assistant messages still reach the runtime. A rate or
|
|
607
|
+
usage limit in a provider error event or stream-error log stops the host
|
|
608
|
+
process or Docker container immediately. The runtime checkpoints local work,
|
|
609
|
+
records the error, and releases the lease with a failed session outcome.
|
|
610
|
+
Pending guidance does not restart that session. New claims on this machine
|
|
611
|
+
pause until the reported reset time (including relative resets such as
|
|
612
|
+
“in 23 days”), or for 15 minutes when no reset time is available.
|
|
613
|
+
|
|
604
614
|
The ACP adapter speaks the standard [Agent Client Protocol](https://agentclientprotocol.com)
|
|
605
615
|
v1 JSON-RPC transport over stdio. It creates one protocol session in the task
|
|
606
616
|
worktree, converts Navarch's HTTP/stdio MCP entries, consumes semantic message,
|
|
@@ -680,7 +690,7 @@ contract used by `api.cts`, including:
|
|
|
680
690
|
distinct from the per-lease heartbeat, needed because `machines.last_heartbeat_at`
|
|
681
691
|
/`status` must update even when no task is claimed.
|
|
682
692
|
3. **`POST /api/dispatch/:leaseId/transcript-upload-url`** — a signed
|
|
683
|
-
|
|
693
|
+
R2 upload URL for the session's redacted transcript. The
|
|
684
694
|
bucket is private; the legacy `public_url` response field is only an
|
|
685
695
|
attested locator returned at completion, not an anonymous read URL.
|
|
686
696
|
|
|
@@ -782,7 +792,7 @@ secrets absent from disk after exit"):
|
|
|
782
792
|
already-reassigned lease) — `session.cts` aborts the running adapter
|
|
783
793
|
process and still attempts a best-effort `complete()` call, which the
|
|
784
794
|
real server may reject; that path is untested against real semantics.
|
|
785
|
-
- Transcript upload against a real signed
|
|
795
|
+
- Transcript upload against a real signed R2 URL.
|
|
786
796
|
- The end-to-end "Connect an agent to a project" flow against a real
|
|
787
797
|
Supabase instance: an owner minting a token from `ConnectAgentPanel`
|
|
788
798
|
(or the onboarding wizard's Agent step), `navarch-runtime connect`
|
|
@@ -14,9 +14,9 @@ const MAX_RESET_SEARCH_MINUTES = 48 * 60;
|
|
|
14
14
|
function detectAdapterCapacityLimit(result, nowMs = Date.now()) {
|
|
15
15
|
const parsed = (0, exit_conditions_cjs_1.parseClaudeJsonResult)(result.stdout) ??
|
|
16
16
|
(0, exit_conditions_cjs_1.parseClaudeJsonResult)(result.stderr);
|
|
17
|
-
if (parsed?.api_error_status !== 429)
|
|
17
|
+
if (!result.capacityError && parsed?.api_error_status !== 429)
|
|
18
18
|
return null;
|
|
19
|
-
const detail = parsed
|
|
19
|
+
const detail = result.capacityError ?? parsed?.result ?? "";
|
|
20
20
|
if (!/\b(?:session|usage|rate)\s+limit\b|\btoo many requests\b/i.test(detail)) {
|
|
21
21
|
return null;
|
|
22
22
|
}
|
|
@@ -32,6 +32,13 @@ function detectAdapterCapacityLimit(result, nowMs = Date.now()) {
|
|
|
32
32
|
* the platform's IANA timezone database.
|
|
33
33
|
*/
|
|
34
34
|
function parseResetTime(detail, nowMs) {
|
|
35
|
+
const relative = detail.match(/\bresets?\s+in\s+(\d+(?:\.\d+)?)\s*(seconds?|minutes?|hours?|days?)\b/i);
|
|
36
|
+
if (relative) {
|
|
37
|
+
const units = { second: 1000, minute: 60_000, hour: 3_600_000, day: 86_400_000 };
|
|
38
|
+
const duration = Number(relative[1]) * units[relative[2].toLowerCase().replace(/s$/, "")];
|
|
39
|
+
if (Number.isFinite(duration) && duration > 0)
|
|
40
|
+
return nowMs + duration + RESET_GRACE_MS;
|
|
41
|
+
}
|
|
35
42
|
const match = detail.match(/\bresets?\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)\s*\(([^)]+)\)/i);
|
|
36
43
|
if (!match)
|
|
37
44
|
return null;
|
|
@@ -7,10 +7,10 @@ const node_fs_1 = require("node:fs");
|
|
|
7
7
|
const config_cjs_1 = require("../config.cjs");
|
|
8
8
|
const kill_agent_cjs_1 = require("../kill-agent.cjs");
|
|
9
9
|
/**
|
|
10
|
-
* Headless OpenCode adapter, fixture-pinned to the OpenCode
|
|
11
|
-
* contract:
|
|
10
|
+
* Headless OpenCode adapter, fixture-pinned to the OpenCode JSON/config
|
|
11
|
+
* contract shared by V1 and V2 (verified against 2.0.6):
|
|
12
12
|
*
|
|
13
|
-
* opencode run "<prompt>" --format json [--model provider/model]
|
|
13
|
+
* opencode run "<prompt>" --format json [--model provider/model[#variant]]
|
|
14
14
|
*
|
|
15
15
|
* OpenCode merges configuration from several machine and project locations,
|
|
16
16
|
* so each Navarch session gets an isolated XDG/config directory, disables
|
|
@@ -32,14 +32,18 @@ async function runOpenCodeAdapter(options) {
|
|
|
32
32
|
const env = { ...options.env };
|
|
33
33
|
await prepareOpenCodeConfig(options.mcpConfigPath, env);
|
|
34
34
|
const args = ["run", options.prompt];
|
|
35
|
+
// Provider failures may only appear in logs, absent from assistant events.
|
|
36
|
+
if (!hasArg(options.extraArgs, "--print-logs"))
|
|
37
|
+
args.push("--print-logs");
|
|
38
|
+
if (!hasArg(options.extraArgs, "--log-level"))
|
|
39
|
+
args.push("--log-level", "ERROR");
|
|
35
40
|
if (!hasArg(options.extraArgs, "--format"))
|
|
36
41
|
args.push("--format", "json");
|
|
37
42
|
args.push(...options.extraArgs);
|
|
38
43
|
if (options.model && options.model !== "default" && !hasArg(options.extraArgs, "--model", "-m")) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
args.push("--variant", options.reasoningEffort);
|
|
44
|
+
// OpenCode V2 selects a reasoning variant through the model reference
|
|
45
|
+
// (`provider/model#variant`) rather than a separate flag.
|
|
46
|
+
args.push("--model", options.reasoningEffort ? `${options.model}#${options.reasoningEffort}` : options.model);
|
|
43
47
|
}
|
|
44
48
|
const runOptions = { ...options, env };
|
|
45
49
|
const raw = runOptions.dockerExec
|
|
@@ -80,23 +84,22 @@ async function prepareOpenCodeConfig(mcpConfigPath, env) {
|
|
|
80
84
|
mcp[name] = {
|
|
81
85
|
type: "remote",
|
|
82
86
|
url,
|
|
83
|
-
|
|
87
|
+
disabled: false,
|
|
84
88
|
oauth: false,
|
|
85
89
|
...(Object.keys(headers).length > 0 ? { headers } : {}),
|
|
86
90
|
};
|
|
87
91
|
}
|
|
88
92
|
const config = {
|
|
89
93
|
$schema: "https://opencode.ai/config.json",
|
|
90
|
-
|
|
94
|
+
update: "disable",
|
|
91
95
|
share: "disabled",
|
|
92
|
-
|
|
93
|
-
mcp,
|
|
94
|
-
|
|
95
|
-
"*": "allow",
|
|
96
|
-
external_directory: "deny",
|
|
97
|
-
question: "deny",
|
|
98
|
-
|
|
99
|
-
},
|
|
96
|
+
plugins: [],
|
|
97
|
+
mcp: { servers: mcp },
|
|
98
|
+
permissions: [
|
|
99
|
+
{ action: "*", resource: "*", effect: "allow" },
|
|
100
|
+
{ action: "external_directory", resource: "*", effect: "deny" },
|
|
101
|
+
{ action: "question", resource: "*", effect: "deny" },
|
|
102
|
+
],
|
|
100
103
|
};
|
|
101
104
|
const configPath = `${mcpConfigPath}.opencode.json`;
|
|
102
105
|
const configDir = `${mcpConfigPath}.opencode-config`;
|
|
@@ -186,6 +189,42 @@ function parseOpenCodeEvents(stdout) {
|
|
|
186
189
|
function nonNegativeMetric(value) {
|
|
187
190
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
188
191
|
}
|
|
192
|
+
/** Inspect only provider error envelopes/logs, never tool output or assistant text. */
|
|
193
|
+
function capacityMonitor(onLimit) {
|
|
194
|
+
const pending = { stdout: "", stderr: "" };
|
|
195
|
+
const inspect = (line, stream) => {
|
|
196
|
+
let detail = "";
|
|
197
|
+
try {
|
|
198
|
+
const event = JSON.parse(line);
|
|
199
|
+
if (event.type === "error" || event.type === "session.error") {
|
|
200
|
+
const error = event.error ?? event.properties?.error;
|
|
201
|
+
detail = typeof error === "string" ? error : error?.data?.message ?? error?.message ?? "";
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// OpenCode's log format varies by version. Require a stream-error log
|
|
206
|
+
// to avoid confusing a tool's own HTTP 429 with provider exhaustion.
|
|
207
|
+
if (stream === "stderr" && /\bERROR\b/.test(line) && /stream error/i.test(line)) {
|
|
208
|
+
detail = line;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (typeof detail === "string" && /\b(?:rate|usage|session)\s+limit\b|\btoo many requests\b/i.test(detail)) {
|
|
212
|
+
onLimit(detail);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
return {
|
|
216
|
+
push(stream, chunk) {
|
|
217
|
+
const lines = (pending[stream] + chunk).split(/\r?\n/);
|
|
218
|
+
pending[stream] = lines.pop() ?? "";
|
|
219
|
+
for (const line of lines)
|
|
220
|
+
inspect(line, stream);
|
|
221
|
+
},
|
|
222
|
+
flush() {
|
|
223
|
+
inspect(pending.stdout, "stdout");
|
|
224
|
+
inspect(pending.stderr, "stderr");
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
189
228
|
async function runOnHost(options, args) {
|
|
190
229
|
return new Promise((resolve) => {
|
|
191
230
|
let stdout = "";
|
|
@@ -197,6 +236,11 @@ async function runOnHost(options, args) {
|
|
|
197
236
|
cwd: options.cwd,
|
|
198
237
|
env: { ...process.env, ...options.env },
|
|
199
238
|
});
|
|
239
|
+
let capacityError;
|
|
240
|
+
const monitor = capacityMonitor((detail) => {
|
|
241
|
+
capacityError ??= detail;
|
|
242
|
+
(0, kill_agent_cjs_1.killAgent)(child);
|
|
243
|
+
});
|
|
200
244
|
child.stdin?.end();
|
|
201
245
|
options.onProcessStarted?.(child.pid);
|
|
202
246
|
const timer = options.timeoutMs > 0 ? setTimeout(() => {
|
|
@@ -213,21 +257,25 @@ async function runOnHost(options, args) {
|
|
|
213
257
|
child.stdout.on("data", (data) => {
|
|
214
258
|
options.onActivity?.();
|
|
215
259
|
stdout += data.toString();
|
|
260
|
+
monitor.push("stdout", data.toString());
|
|
216
261
|
});
|
|
217
262
|
child.stderr.on("data", (data) => {
|
|
218
263
|
options.onActivity?.();
|
|
219
264
|
stderr += data.toString();
|
|
265
|
+
monitor.push("stderr", data.toString());
|
|
220
266
|
});
|
|
221
267
|
child.on("error", (error) => {
|
|
222
268
|
clearTimeout(timer);
|
|
223
269
|
options.signal?.removeEventListener("abort", onAbort);
|
|
224
270
|
stderr += `\n${String(error)}`;
|
|
225
|
-
|
|
271
|
+
monitor.flush();
|
|
272
|
+
resolve({ exitCode: null, timedOut, killedByLeaseLoss, stdout, stderr, capacityError });
|
|
226
273
|
});
|
|
227
274
|
child.on("close", (code) => {
|
|
228
275
|
clearTimeout(timer);
|
|
229
276
|
options.signal?.removeEventListener("abort", onAbort);
|
|
230
|
-
|
|
277
|
+
monitor.flush();
|
|
278
|
+
resolve({ exitCode: code, timedOut, killedByLeaseLoss, stdout, stderr, capacityError });
|
|
231
279
|
});
|
|
232
280
|
});
|
|
233
281
|
}
|
|
@@ -236,8 +284,19 @@ async function runViaDocker(options, args) {
|
|
|
236
284
|
const quoted = [options.bin, ...args].map(shellQuote).join(" ");
|
|
237
285
|
const command = `[ -f /tmp/session.env ] && . /tmp/session.env; cd repo 2>/dev/null; ${quoted}`;
|
|
238
286
|
let killedByLeaseLoss = false;
|
|
287
|
+
let capacityError;
|
|
288
|
+
const controller = new AbortController();
|
|
289
|
+
const streamed = { stdout: false, stderr: false };
|
|
290
|
+
const monitor = capacityMonitor((detail) => {
|
|
291
|
+
if (capacityError)
|
|
292
|
+
return;
|
|
293
|
+
capacityError = detail;
|
|
294
|
+
controller.abort();
|
|
295
|
+
runner.run("docker", ["kill", containerName], { timeoutMs: 10_000 }).catch(() => undefined);
|
|
296
|
+
});
|
|
239
297
|
const onAbort = () => {
|
|
240
298
|
killedByLeaseLoss = true;
|
|
299
|
+
controller.abort();
|
|
241
300
|
runner.run("docker", ["kill", containerName], { timeoutMs: 10_000 }).catch(() => undefined);
|
|
242
301
|
};
|
|
243
302
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
@@ -247,10 +306,19 @@ async function runViaDocker(options, args) {
|
|
|
247
306
|
const forwardedEnv = Object.keys(options.env).flatMap((name) => ["--env", name]);
|
|
248
307
|
const result = await runner.run("docker", ["exec", ...forwardedEnv, containerName, "sh", "-c", command], { timeoutMs: options.timeoutMs,
|
|
249
308
|
onActivity: options.onActivity,
|
|
250
|
-
signal:
|
|
309
|
+
signal: controller.signal,
|
|
310
|
+
onStdout: (chunk) => { streamed.stdout = true; monitor.push("stdout", chunk); },
|
|
311
|
+
onStderr: (chunk) => { streamed.stderr = true; monitor.push("stderr", chunk); },
|
|
251
312
|
env: { ...process.env, ...options.env },
|
|
252
313
|
});
|
|
314
|
+
// Also inspect runners that only return buffered output.
|
|
315
|
+
if (!streamed.stdout)
|
|
316
|
+
monitor.push("stdout", result.stdout);
|
|
317
|
+
if (!streamed.stderr)
|
|
318
|
+
monitor.push("stderr", result.stderr);
|
|
319
|
+
monitor.flush();
|
|
253
320
|
return {
|
|
321
|
+
capacityError,
|
|
254
322
|
exitCode: result.code,
|
|
255
323
|
timedOut: false,
|
|
256
324
|
killedByLeaseLoss,
|
|
@@ -260,6 +328,7 @@ async function runViaDocker(options, args) {
|
|
|
260
328
|
}
|
|
261
329
|
catch (error) {
|
|
262
330
|
return {
|
|
331
|
+
capacityError,
|
|
263
332
|
exitCode: null,
|
|
264
333
|
timedOut: false,
|
|
265
334
|
killedByLeaseLoss,
|
package/dist/cli.cjs
CHANGED
|
@@ -61,11 +61,17 @@ function superviseInvocation(configDir) {
|
|
|
61
61
|
return `${invocation("supervise")} --config-dir ${configDir}`;
|
|
62
62
|
}
|
|
63
63
|
/** Resolve CLI-local configuration without mutating the parent shell environment. */
|
|
64
|
-
function configFromFlags(flags) {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
function configFromFlags(flags, identity) {
|
|
65
|
+
const env = { ...process.env };
|
|
66
|
+
if (flags["config-dir"])
|
|
67
|
+
env.NAVARCH_CONFIG_DIR = node_path_1.default.resolve(flags["config-dir"]);
|
|
68
|
+
// Resolve the preferred adapter before deriving the advertised runtimes and
|
|
69
|
+
// applying host guards. Keep an explicit NAVARCH_RUNTIMES list intact.
|
|
70
|
+
const agentType = agentFromFlag(flags) ?? (env.NAVARCH_AGENT || identity?.agent_type);
|
|
71
|
+
if (agentType)
|
|
72
|
+
env.NAVARCH_AGENT = agentType;
|
|
73
|
+
const config = (0, config_cjs_1.loadRuntimeConfig)(env);
|
|
74
|
+
return { ...config, model: modelFromFlags(flags, config.agentType, identity) };
|
|
69
75
|
}
|
|
70
76
|
function agentFromFlag(flags) {
|
|
71
77
|
const value = flags.agent;
|
|
@@ -202,12 +208,7 @@ async function connectCommand(flags) {
|
|
|
202
208
|
async function startCommand(flags) {
|
|
203
209
|
const baseConfig = configFromFlags(flags);
|
|
204
210
|
const identity = await (0, machine_store_cjs_1.resolveMachineIdentity)(baseConfig.configDir, baseConfig.apiBase);
|
|
205
|
-
|
|
206
|
-
// wins, followed by NAVARCH_AGENT, the choice saved at connect/register
|
|
207
|
-
// time, and finally the backwards-compatible Claude Code default.
|
|
208
|
-
const agentType = agentFromFlag(flags) ??
|
|
209
|
-
(process.env.NAVARCH_AGENT ? baseConfig.agentType : identity.agent_type ?? baseConfig.agentType);
|
|
210
|
-
const config = { ...baseConfig, agentType, model: modelFromFlags(flags, agentType, identity) };
|
|
211
|
+
const config = configFromFlags(flags, identity);
|
|
211
212
|
const api = new api_cjs_1.NavarchApiClient({ baseUrl: identity.api_base, token: identity.token });
|
|
212
213
|
const capacity = new capacity_cjs_1.CapacityTracker(config.maxSessions);
|
|
213
214
|
const runtimeShutdown = new AbortController();
|
|
@@ -283,19 +284,17 @@ async function startCommand(flags) {
|
|
|
283
284
|
process.on("SIGTERM", shutdown);
|
|
284
285
|
}
|
|
285
286
|
async function superviseCommand(flags) {
|
|
286
|
-
const
|
|
287
|
-
const identity = await (0, machine_store_cjs_1.resolveMachineIdentity)(
|
|
287
|
+
const baseConfig = configFromFlags(flags);
|
|
288
|
+
const identity = await (0, machine_store_cjs_1.resolveMachineIdentity)(baseConfig.configDir, baseConfig.apiBase);
|
|
289
|
+
const config = configFromFlags(flags, identity);
|
|
288
290
|
// Resolve and pin the adapter before spawning the worker. The supervisor
|
|
289
291
|
// passes machine credentials through the environment, so the child no
|
|
290
292
|
// longer reads machine.json for identity fields (including agent_type).
|
|
291
293
|
// Without an explicit worker argument, any saved non-Claude selection
|
|
292
294
|
// would therefore fall back to Claude Code.
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const model = modelFromFlags(flags, agentType, identity);
|
|
297
|
-
if (model)
|
|
298
|
-
workerArgs.push("--model", model);
|
|
295
|
+
const workerArgs = ["--agent", config.agentType];
|
|
296
|
+
if (config.model)
|
|
297
|
+
workerArgs.push("--model", config.model);
|
|
299
298
|
// Pin the identity for this supervisor's lifetime. Without this snapshot, a
|
|
300
299
|
// replacement worker rereads machine.json after an automatic update and can
|
|
301
300
|
// silently become a different machine if another terminal reused the same
|
|
@@ -315,8 +314,17 @@ async function doctorCommand(flags) {
|
|
|
315
314
|
// start (config.cts). Report that as the diagnosis instead of rethrowing it
|
|
316
315
|
// as a bare CLI error.
|
|
317
316
|
let config;
|
|
317
|
+
let identity;
|
|
318
318
|
try {
|
|
319
319
|
config = configFromFlags(flags);
|
|
320
|
+
try {
|
|
321
|
+
identity = await (0, machine_store_cjs_1.resolveMachineIdentity)(config.configDir, config.apiBase);
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
// Unregistered machines can still inspect their environment configuration.
|
|
325
|
+
}
|
|
326
|
+
if (identity)
|
|
327
|
+
config = configFromFlags(flags, identity);
|
|
320
328
|
}
|
|
321
329
|
catch (err) {
|
|
322
330
|
console.log(`config: UNUSABLE — ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -333,18 +341,12 @@ async function doctorCommand(flags) {
|
|
|
333
341
|
console.log(`worktree_guard: ${config.worktreeGuard ? "on" : "off"}`);
|
|
334
342
|
console.log(`runtimes: ${config.runtimes?.join(", ") ?? config.agentType}`);
|
|
335
343
|
console.log(`docker: ${dockerOk ? "available" : "NOT AVAILABLE (docker-backed sessions will fail)"}`);
|
|
336
|
-
|
|
337
|
-
try {
|
|
338
|
-
identity = await (0, machine_store_cjs_1.resolveMachineIdentity)(config.configDir, config.apiBase);
|
|
339
|
-
}
|
|
340
|
-
catch {
|
|
344
|
+
if (!identity) {
|
|
341
345
|
console.log(`machine: not registered — run \`${invocation("register")}\``);
|
|
342
346
|
return;
|
|
343
347
|
}
|
|
344
|
-
const agentType = agentFromFlag(flags) ??
|
|
345
|
-
(process.env.NAVARCH_AGENT ? config.agentType : identity.agent_type ?? config.agentType);
|
|
346
348
|
console.log(`machine: ${identity.name} (${identity.machine_id})`);
|
|
347
|
-
console.log(`agent: ${agentType}`);
|
|
349
|
+
console.log(`agent: ${config.agentType}`);
|
|
348
350
|
}
|
|
349
351
|
function helpText() {
|
|
350
352
|
return `navarch-runtime — Navarch machine-side session manager
|
package/dist/exit-conditions.cjs
CHANGED
|
@@ -229,6 +229,16 @@ function mapExitCondition(result) {
|
|
|
229
229
|
evidenceUrls,
|
|
230
230
|
};
|
|
231
231
|
}
|
|
232
|
+
if (result.capacityError) {
|
|
233
|
+
return {
|
|
234
|
+
leaseOutcome: "failed",
|
|
235
|
+
exitStatus: "failed",
|
|
236
|
+
reportSummary: summarize(`Session stopped: provider capacity exhausted. ${result.capacityError}`) +
|
|
237
|
+
(result.checkpointPath ? ` Local recovery checkpoint: ${result.checkpointPath}` : "") +
|
|
238
|
+
(result.retainedWorktreePath ? ` Recovery worktree retained: ${result.retainedWorktreePath}` : ""),
|
|
239
|
+
evidenceUrls,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
232
242
|
if (result.timedOut) {
|
|
233
243
|
return {
|
|
234
244
|
leaseOutcome: "failed",
|
package/dist/sandbox.cjs
CHANGED
|
@@ -40,10 +40,12 @@ exports.nodeCommandRunner = {
|
|
|
40
40
|
child.stdout?.on("data", (d) => {
|
|
41
41
|
opts.onActivity?.();
|
|
42
42
|
stdout += d.toString();
|
|
43
|
+
opts.onStdout?.(d.toString());
|
|
43
44
|
});
|
|
44
45
|
child.stderr?.on("data", (d) => {
|
|
45
46
|
opts.onActivity?.();
|
|
46
47
|
stderr += d.toString();
|
|
48
|
+
opts.onStderr?.(d.toString());
|
|
47
49
|
});
|
|
48
50
|
child.on("error", (err) => {
|
|
49
51
|
if (settled)
|
package/dist/session.cjs
CHANGED
|
@@ -229,7 +229,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
229
229
|
return;
|
|
230
230
|
try {
|
|
231
231
|
result.checkpointPath = await gitWorktree.saveCheckpoint();
|
|
232
|
-
log.warn(`session ${leaseId} stopped for ${leaseLost ? "lease loss" : result.timeoutReason ?? "duration"}; recovery checkpoint: ${result.checkpointPath}`);
|
|
232
|
+
log.warn(`session ${leaseId} stopped for ${leaseLost ? "lease loss" : result.capacityError ? "provider capacity" : result.timeoutReason ?? "duration"}; recovery checkpoint: ${result.checkpointPath}`);
|
|
233
233
|
}
|
|
234
234
|
catch (error) {
|
|
235
235
|
retainWorktree = true;
|
|
@@ -481,6 +481,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
481
481
|
attempts.push(turnResult);
|
|
482
482
|
const capacityLimit = (0, adapter_capacity_cjs_1.detectAdapterCapacityLimit)(turnResult);
|
|
483
483
|
if (capacityLimit) {
|
|
484
|
+
await checkpointForRecovery(turnResult);
|
|
484
485
|
sessionOutcome.claimCooldownUntil = Math.max(sessionOutcome.claimCooldownUntil ?? 0, capacityLimit.retryAtMs);
|
|
485
486
|
}
|
|
486
487
|
// Close the small race between a naturally completed turn and the next
|
|
@@ -495,7 +496,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
495
496
|
log.warn(`session ${leaseId} stopped without completion because its lease is no longer active.`);
|
|
496
497
|
return sessionOutcome;
|
|
497
498
|
}
|
|
498
|
-
if (!leaseLost && !turnResult.timedOut && pendingGuidance.length > 0)
|
|
499
|
+
if (!capacityLimit && !leaseLost && !turnResult.timedOut && pendingGuidance.length > 0)
|
|
499
500
|
continue;
|
|
500
501
|
const result = {
|
|
501
502
|
...turnResult,
|
|
@@ -806,6 +807,8 @@ function verificationFailureReport(taskType, report) {
|
|
|
806
807
|
}
|
|
807
808
|
/** Rejection codes another agent turn in the same worktree can plausibly fix. */
|
|
808
809
|
const REMEDIABLE_REJECTION_CODES = new Set([
|
|
810
|
+
"review_fix_required",
|
|
811
|
+
"review_fix_unavailable",
|
|
809
812
|
"pr_required",
|
|
810
813
|
// A video-deliverable build that completed without registering its render:
|
|
811
814
|
// the file usually already exists in the worktree, so the remediation turn
|
package/dist/upload.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.uploadTranscript = uploadTranscript;
|
|
4
4
|
/**
|
|
5
|
-
* PUTs an already-redacted transcript to the
|
|
5
|
+
* PUTs an already-redacted transcript to the presigned R2 URL the
|
|
6
6
|
* control plane hands back (schema-design.md §4 sessions.transcript_url;
|
|
7
7
|
* implementation-plan.md WP-07). Kept as its own module so the storage
|
|
8
8
|
* mechanism is a one-function swap if it ever changes.
|
|
@@ -16,9 +16,8 @@ async function uploadTranscript(uploadUrl, content, fetchImpl = fetch) {
|
|
|
16
16
|
body: content,
|
|
17
17
|
});
|
|
18
18
|
if (!response.ok) {
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// from a bucket misconfiguration. Without it a 403 is unattributable.
|
|
19
|
+
// Include the bounded storage response to distinguish expired signatures
|
|
20
|
+
// from bucket misconfiguration.
|
|
22
21
|
throw new Error(`Transcript upload failed with ${response.status}: ${await readErrorBody(response)}`);
|
|
23
22
|
}
|
|
24
23
|
}
|
package/package.json
CHANGED