@sagentlab/navarch-runtime 0.1.52 → 0.1.54
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 +14 -6
- package/dist/adapters/index.cjs +6 -1
- package/dist/adapters/opencode.cjs +5 -2
- package/dist/adapters/qoder.cjs +166 -0
- package/dist/cli.cjs +1 -1
- package/dist/config.cjs +4 -1
- package/dist/machine-store.cjs +2 -1
- package/dist/session.cjs +23 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -152,10 +152,10 @@ The from-source flow — `git clone` + `./install.sh` + `node bin/navarch.cjs
|
|
|
152
152
|
|
|
153
153
|
| Command | Purpose |
|
|
154
154
|
|---|---|
|
|
155
|
-
| `register --token <t> --name <n> [--agent claude-code\|codex\|gemini\|opencode\|acp] […]` | Registers this machine, saves its local agent choice, and prints the token once. |
|
|
156
|
-
| `connect --token <t> --name <n> [--agent claude-code\|codex\|gemini\|opencode\|acp] [--project <id>] […]` | Connects this machine to one project, saves its local agent choice, and prints the token once. |
|
|
157
|
-
| `start [--agent claude-code\|codex\|gemini\|opencode\|acp]` | Runs the daemon. A start-time agent choice overrides the saved choice. |
|
|
158
|
-
| `supervise [--agent claude-code\|codex\|gemini\|opencode\|acp]` | Runs the daemon under the update supervisor, enabling drain-safe automatic updates and rollback. |
|
|
155
|
+
| `register --token <t> --name <n> [--agent claude-code\|codex\|gemini\|opencode\|acp\|qoder] […]` | Registers this machine, saves its local agent choice, and prints the token once. |
|
|
156
|
+
| `connect --token <t> --name <n> [--agent claude-code\|codex\|gemini\|opencode\|acp\|qoder] [--project <id>] […]` | Connects this machine to one project, saves its local agent choice, and prints the token once. |
|
|
157
|
+
| `start [--agent claude-code\|codex\|gemini\|opencode\|acp\|qoder]` | Runs the daemon. A start-time agent choice overrides the saved choice. |
|
|
158
|
+
| `supervise [--agent claude-code\|codex\|gemini\|opencode\|acp\|qoder]` | Runs the daemon under the update supervisor, enabling drain-safe automatic updates and rollback. |
|
|
159
159
|
| `doctor` | Prints resolved config + Docker/registration status; no side effects. |
|
|
160
160
|
|
|
161
161
|
### Running multiple agents on one machine
|
|
@@ -368,7 +368,7 @@ unchanged across the deployment.
|
|
|
368
368
|
| `NAVARCH_SANDBOX_PROFILE` | `trusted-development` | Named security profile for Docker sessions (`src/sandbox-profile.cts`): `trusted-development` (image-default user, uncapped, open egress), `untrusted-code` (non-root, 2 CPU / 4g / 512 PIDs, deny-by-default egress, read-only shared git), `elevated-verification` (non-root, 4 CPU / 8g / 2048 PIDs, egress limited to GitHub plus package registries). The default is the exact pre-profile flag set. |
|
|
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
|
-
| `NAVARCH_AGENT` | saved choice, then `claude-code` | Local choice of agent adapter: `claude-code`, `codex`, `gemini`, `opencode`, or `
|
|
371
|
+
| `NAVARCH_AGENT` | saved choice, then `claude-code` | Local choice of agent adapter: `claude-code`, `codex`, `gemini`, `opencode`, `acp`, or `qoder`. Overrides the choice saved by `connect`/`register`; `start --agent` has highest priority. |
|
|
372
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`. |
|
|
@@ -382,6 +382,8 @@ unchanged across the deployment.
|
|
|
382
382
|
| `NAVARCH_OPENCODE_EXTRA_ARGS` | — | Comma list of extra CLI args appended after `run`, the prompt, and the generated `--format json` argument. Explicit `--format`, `--model`, or `--variant` values replace the corresponding per-session default. |
|
|
383
383
|
| `NAVARCH_ACP_BIN` | `dsh` | Path/name of an Agent Client Protocol v1 stdio server. DeepSeek Harness is the default implementation. |
|
|
384
384
|
| `NAVARCH_ACP_EXTRA_ARGS` | `--profile,acp` | Comma list of arguments used to start the ACP server. Override this together with `NAVARCH_ACP_BIN` for another ACP-compatible coding agent. |
|
|
385
|
+
| `NAVARCH_QODER_BIN` | `qoder` | Path/name of the Qoder CLI binary. |
|
|
386
|
+
| `NAVARCH_QODER_EXTRA_ARGS` | — | Comma list of extra CLI args appended after the unattended defaults (`--setting-sources`, `--permission-mode auto`, `--output-format json`). An explicit `--output-format`, `--permission-mode`, `--settings`, `--model`, or `--reasoning-effort` here replaces the per-session default. Qoder selects its own account models; pin one via `--model` here or `connect --model` to override. |
|
|
385
387
|
| `NAVARCH_MCP_CONFIG_PATH` | — | Path to the platform MCP config passed as `--mcp-config`. |
|
|
386
388
|
| `NAVARCH_WORKTREE_GUARD` | on | Host-mode sessions get an adapter-native per-session worktree boundary guard (see below). Set `off` to disable it for every runtime on the machine — required to run OpenCode on the host. |
|
|
387
389
|
| `NAVARCH_GUARD_EXTRA_ROOTS` | — | `path.delimiter`-separated (`:` on POSIX) extra directories the worktree guard allows beyond the session worktree, shared bare repo, and temp dirs. |
|
|
@@ -514,10 +516,15 @@ export NAVARCH_AGENT=opencode
|
|
|
514
516
|
# Override NAVARCH_ACP_BIN / NAVARCH_ACP_EXTRA_ARGS for another ACP v1 server.
|
|
515
517
|
export NAVARCH_AGENT=acp
|
|
516
518
|
|
|
519
|
+
# Qoder — requires an authenticated `qoder` CLI (or NAVARCH_QODER_BIN pointing
|
|
520
|
+
# at it). Model selection is account-owned; NAVARCH_QODER_EXTRA_ARGS or
|
|
521
|
+
# `connect --model` can pin one explicitly.
|
|
522
|
+
export NAVARCH_AGENT=qoder
|
|
523
|
+
|
|
517
524
|
# Advanced compatibility mode: advertise every installed adapter. On a guarded
|
|
518
525
|
# host, `opencode` is dropped from this list (see the boundary notes above);
|
|
519
526
|
# the rest are advertised as written.
|
|
520
|
-
export NAVARCH_RUNTIMES=claude-code,codex,gemini,opencode,acp
|
|
527
|
+
export NAVARCH_RUNTIMES=claude-code,codex,gemini,opencode,acp,qoder
|
|
521
528
|
```
|
|
522
529
|
|
|
523
530
|
For the legacy single-runtime setting, priority is `start --agent` →
|
|
@@ -642,6 +649,7 @@ cli.cts
|
|
|
642
649
|
- geminiAdapter (adapters/gemini.cts) — `gemini --prompt <prompt> --output-format stream-json`
|
|
643
650
|
- openCodeAdapter (adapters/opencode.cts) — `opencode run <prompt> --format json`
|
|
644
651
|
- acpAdapter (adapters/acp.cts) — ACP v1 JSON-RPC/stdio; defaults to `dsh --profile acp`
|
|
652
|
+
- qoderAdapter (adapters/qoder.cts) — `qoder -p <prompt> --output-format json`
|
|
645
653
|
heartbeating the lease every NAVARCH_LEASE_HEARTBEAT_INTERVAL_MS throughout either;
|
|
646
654
|
a failed heartbeat aborts the run (kills the process) and marks the outcome as lease-lost
|
|
647
655
|
5. mapExitCondition (exit-conditions.cts) → redact.cts scrubs the transcript → upload.cts PUTs it
|
package/dist/adapters/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runAcpAdapter = exports.acpAdapter = exports.runPiAdapter = exports.piAdapter = exports.runOpenCodeAdapter = exports.openCodeAdapter = exports.runGeminiAdapter = exports.geminiAdapter = exports.runCodexAdapter = exports.codexAdapter = exports.runClaudeCodeAdapter = exports.claudeCodeAdapter = void 0;
|
|
3
|
+
exports.runQoderAdapter = exports.qoderAdapter = exports.runAcpAdapter = exports.acpAdapter = exports.runPiAdapter = exports.piAdapter = exports.runOpenCodeAdapter = exports.openCodeAdapter = exports.runGeminiAdapter = exports.geminiAdapter = exports.runCodexAdapter = exports.codexAdapter = exports.runClaudeCodeAdapter = exports.claudeCodeAdapter = void 0;
|
|
4
4
|
exports.selectAdapter = selectAdapter;
|
|
5
5
|
const claude_cjs_1 = require("./claude.cjs");
|
|
6
6
|
Object.defineProperty(exports, "claudeCodeAdapter", { enumerable: true, get: function () { return claude_cjs_1.claudeCodeAdapter; } });
|
|
@@ -20,6 +20,9 @@ Object.defineProperty(exports, "runPiAdapter", { enumerable: true, get: function
|
|
|
20
20
|
const acp_cjs_1 = require("./acp.cjs");
|
|
21
21
|
Object.defineProperty(exports, "acpAdapter", { enumerable: true, get: function () { return acp_cjs_1.acpAdapter; } });
|
|
22
22
|
Object.defineProperty(exports, "runAcpAdapter", { enumerable: true, get: function () { return acp_cjs_1.runAcpAdapter; } });
|
|
23
|
+
const qoder_cjs_1 = require("./qoder.cjs");
|
|
24
|
+
Object.defineProperty(exports, "qoderAdapter", { enumerable: true, get: function () { return qoder_cjs_1.qoderAdapter; } });
|
|
25
|
+
Object.defineProperty(exports, "runQoderAdapter", { enumerable: true, get: function () { return qoder_cjs_1.runQoderAdapter; } });
|
|
23
26
|
/**
|
|
24
27
|
* Picks the AgentAdapter (adapters/types.cts) session.cts should run a
|
|
25
28
|
* session with, keyed off config.cts's `agentType` (NAVARCH_AGENT). This is
|
|
@@ -37,6 +40,8 @@ function selectAdapter(agentType) {
|
|
|
37
40
|
return opencode_cjs_1.openCodeAdapter;
|
|
38
41
|
case "acp":
|
|
39
42
|
return acp_cjs_1.acpAdapter;
|
|
43
|
+
case "qoder":
|
|
44
|
+
return qoder_cjs_1.qoderAdapter;
|
|
40
45
|
case "claude-code":
|
|
41
46
|
return claude_cjs_1.claudeCodeAdapter;
|
|
42
47
|
case "pi":
|
|
@@ -41,8 +41,11 @@ async function runOpenCodeAdapter(options) {
|
|
|
41
41
|
args.push("--format", "json");
|
|
42
42
|
args.push(...options.extraArgs);
|
|
43
43
|
if (options.model && options.model !== "default" && !hasArg(options.extraArgs, "--model", "-m")) {
|
|
44
|
-
// OpenCode V2
|
|
45
|
-
// (`provider/model#variant`)
|
|
44
|
+
// OpenCode V2 folds the reasoning variant into the model reference
|
|
45
|
+
// (`provider/model#variant`) and hard-fails on a variant the model does
|
|
46
|
+
// not advertise. The claim route clamps the effort to the model's
|
|
47
|
+
// advertised variants, so a present value is always valid; absent means
|
|
48
|
+
// use the model's own default.
|
|
46
49
|
args.push("--model", options.reasoningEffort ? `${options.model}#${options.reasoningEffort}` : options.model);
|
|
47
50
|
}
|
|
48
51
|
const runOptions = { ...options, env };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.qoderAdapter = void 0;
|
|
4
|
+
exports.runQoderAdapter = runQoderAdapter;
|
|
5
|
+
const node_child_process_1 = require("node:child_process");
|
|
6
|
+
const exit_conditions_cjs_1 = require("../exit-conditions.cjs");
|
|
7
|
+
const kill_agent_cjs_1 = require("../kill-agent.cjs");
|
|
8
|
+
function hasFlag(args, flag) {
|
|
9
|
+
return args.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Headless Qoder CLI adapter: `qoder -p "<prompt>" --output-format json`.
|
|
13
|
+
* Qoder's print mode emits the same single-JSON result shape as Claude Code
|
|
14
|
+
* (verified against qodercli 1.1.56), so usage/cost parsing reuses
|
|
15
|
+
* parseClaudeJsonResult and its result text lands in the ordinary
|
|
16
|
+
* exit-conditions path.
|
|
17
|
+
*
|
|
18
|
+
* Model selection is account-owned (`qoder --list-models`), so the claim-time
|
|
19
|
+
* `"default"` sentinel (execution-policy.ts / session.cts) is passed through
|
|
20
|
+
* as no `--model` flag; an explicit pin from `--model` at enroll or
|
|
21
|
+
* NAVARCH_QODER_EXTRA_ARGS still reaches the CLI.
|
|
22
|
+
*/
|
|
23
|
+
async function runQoderAdapter(options) {
|
|
24
|
+
if (options.signal?.aborted) {
|
|
25
|
+
return { exitCode: null, timedOut: false, killedByLeaseLoss: true, stdout: "", stderr: "" };
|
|
26
|
+
}
|
|
27
|
+
const args = ["-p", options.prompt];
|
|
28
|
+
// Runtime sessions must not inherit an operator's personal or project-local
|
|
29
|
+
// Qoder hooks, for the same reason as the Claude adapter: they make
|
|
30
|
+
// execution machine-dependent and can fail otherwise-good sessions. The
|
|
31
|
+
// empty source list still permits an explicit --settings file below.
|
|
32
|
+
if (!hasFlag(options.extraArgs, "--setting-sources")) {
|
|
33
|
+
args.push("--setting-sources", "");
|
|
34
|
+
}
|
|
35
|
+
// Unattended sessions route permission decisions through Qoder's native
|
|
36
|
+
// auto classifier rather than prompting or bypassing checks.
|
|
37
|
+
if (!["--permission-mode", "--permission-prompt-tool", "--dangerously-skip-permissions"].some((flag) => hasFlag(options.extraArgs, flag))) {
|
|
38
|
+
args.push("--permission-mode", "auto");
|
|
39
|
+
}
|
|
40
|
+
if (options.settingsPath && !hasFlag(options.extraArgs, "--settings")) {
|
|
41
|
+
args.push("--settings", options.settingsPath);
|
|
42
|
+
}
|
|
43
|
+
if (options.mcpConfigPath) {
|
|
44
|
+
args.push("--mcp-config", options.mcpConfigPath);
|
|
45
|
+
}
|
|
46
|
+
if (!hasFlag(options.extraArgs, "--output-format")) {
|
|
47
|
+
args.push("--output-format", "json");
|
|
48
|
+
}
|
|
49
|
+
args.push(...options.extraArgs);
|
|
50
|
+
if (options.model && options.model !== "default" && !hasFlag(options.extraArgs, "--model")) {
|
|
51
|
+
args.push("--model", options.model);
|
|
52
|
+
}
|
|
53
|
+
if (options.reasoningEffort && !hasFlag(options.extraArgs, "--reasoning-effort")) {
|
|
54
|
+
args.push("--reasoning-effort", options.reasoningEffort);
|
|
55
|
+
}
|
|
56
|
+
const raw = options.dockerExec ? await runViaDocker(options, args) : await runOnHost(options, args);
|
|
57
|
+
return attachUsage(raw);
|
|
58
|
+
}
|
|
59
|
+
/** Parses stdout for `qoder -p --output-format json` usage and folds it onto the raw result (best-effort). */
|
|
60
|
+
function attachUsage(result) {
|
|
61
|
+
const parsed = (0, exit_conditions_cjs_1.parseClaudeJsonResult)(result.stdout);
|
|
62
|
+
if (!parsed)
|
|
63
|
+
return result;
|
|
64
|
+
const usage = (0, exit_conditions_cjs_1.extractUsageFromClaudeJson)(parsed);
|
|
65
|
+
return {
|
|
66
|
+
...result,
|
|
67
|
+
tokensIn: usage.tokensIn,
|
|
68
|
+
tokensOut: usage.tokensOut,
|
|
69
|
+
cacheHitTokensIn: usage.cacheHitTokensIn,
|
|
70
|
+
costUsd: usage.costUsd,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async function runOnHost(options, args) {
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
let stdout = "";
|
|
76
|
+
let stderr = "";
|
|
77
|
+
let timedOut = false;
|
|
78
|
+
let killedByLeaseLoss = false;
|
|
79
|
+
const child = (0, node_child_process_1.spawn)(options.bin, args, {
|
|
80
|
+
detached: process.platform !== "win32",
|
|
81
|
+
cwd: options.cwd,
|
|
82
|
+
env: { ...process.env, ...options.env },
|
|
83
|
+
});
|
|
84
|
+
// `qoder -p` checks stdin for a piped prompt; close it so the child does
|
|
85
|
+
// not wait when the complete prompt was supplied as an argument.
|
|
86
|
+
child.stdin?.end();
|
|
87
|
+
options.onProcessStarted?.(child.pid);
|
|
88
|
+
const timer = options.timeoutMs > 0 ? setTimeout(() => {
|
|
89
|
+
timedOut = true;
|
|
90
|
+
(0, kill_agent_cjs_1.killAgent)(child);
|
|
91
|
+
}, options.timeoutMs) : undefined;
|
|
92
|
+
const onAbort = () => {
|
|
93
|
+
killedByLeaseLoss = true;
|
|
94
|
+
(0, kill_agent_cjs_1.killAgent)(child);
|
|
95
|
+
};
|
|
96
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
97
|
+
if (options.signal?.aborted)
|
|
98
|
+
onAbort();
|
|
99
|
+
child.stdout.on("data", (d) => {
|
|
100
|
+
options.onActivity?.();
|
|
101
|
+
stdout += d.toString();
|
|
102
|
+
});
|
|
103
|
+
child.stderr.on("data", (d) => {
|
|
104
|
+
options.onActivity?.();
|
|
105
|
+
stderr += d.toString();
|
|
106
|
+
});
|
|
107
|
+
child.on("error", (err) => {
|
|
108
|
+
clearTimeout(timer);
|
|
109
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
110
|
+
stderr += `\n${String(err)}`;
|
|
111
|
+
resolve({ exitCode: null, timedOut, killedByLeaseLoss, stdout, stderr });
|
|
112
|
+
});
|
|
113
|
+
child.on("close", (code) => {
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
116
|
+
resolve({ exitCode: code, timedOut, killedByLeaseLoss, stdout, stderr });
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async function runViaDocker(options, args) {
|
|
121
|
+
const { containerName, runner } = options.dockerExec;
|
|
122
|
+
const quoted = [options.bin, ...args].map(shellQuote).join(" ");
|
|
123
|
+
const command = `[ -f /tmp/session.env ] && . /tmp/session.env; cd repo 2>/dev/null; ${quoted}`;
|
|
124
|
+
let killedByLeaseLoss = false;
|
|
125
|
+
const onAbort = () => {
|
|
126
|
+
killedByLeaseLoss = true;
|
|
127
|
+
runner.run("docker", ["kill", containerName], { timeoutMs: 10_000 }).catch(() => undefined);
|
|
128
|
+
};
|
|
129
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
130
|
+
if (options.signal?.aborted)
|
|
131
|
+
onAbort();
|
|
132
|
+
try {
|
|
133
|
+
const result = await runner.run("docker", ["exec", containerName, "sh", "-c", command], {
|
|
134
|
+
timeoutMs: options.timeoutMs,
|
|
135
|
+
onActivity: options.onActivity,
|
|
136
|
+
signal: options.signal,
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
exitCode: result.code,
|
|
140
|
+
timedOut: false,
|
|
141
|
+
killedByLeaseLoss,
|
|
142
|
+
stdout: result.stdout,
|
|
143
|
+
stderr: result.stderr,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
return {
|
|
148
|
+
exitCode: null,
|
|
149
|
+
timedOut: false,
|
|
150
|
+
killedByLeaseLoss,
|
|
151
|
+
stdout: "",
|
|
152
|
+
stderr: String(err),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function shellQuote(value) {
|
|
160
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
161
|
+
}
|
|
162
|
+
/** The AgentAdapter (adapters/types.cts) wrapper session.cts selects via NAVARCH_AGENT=qoder. */
|
|
163
|
+
exports.qoderAdapter = {
|
|
164
|
+
agentType: "qoder",
|
|
165
|
+
run: runQoderAdapter,
|
|
166
|
+
};
|
package/dist/cli.cjs
CHANGED
|
@@ -78,7 +78,7 @@ function agentFromFlag(flags) {
|
|
|
78
78
|
if (value === undefined)
|
|
79
79
|
return undefined;
|
|
80
80
|
if (!(0, config_cjs_1.isRuntimeAgentType)(value)) {
|
|
81
|
-
throw new Error("--agent must be one of 'claude-code', 'codex', 'gemini', 'opencode', or '
|
|
81
|
+
throw new Error("--agent must be one of 'claude-code', 'codex', 'gemini', 'opencode', 'acp', or 'qoder'.");
|
|
82
82
|
}
|
|
83
83
|
return value;
|
|
84
84
|
}
|
package/dist/config.cjs
CHANGED
|
@@ -17,7 +17,8 @@ function isRuntimeAgentType(value) {
|
|
|
17
17
|
value === "codex" ||
|
|
18
18
|
value === "gemini" ||
|
|
19
19
|
value === "opencode" ||
|
|
20
|
-
value === "acp"
|
|
20
|
+
value === "acp" ||
|
|
21
|
+
value === "qoder");
|
|
21
22
|
}
|
|
22
23
|
function envInt(env, name, fallback) {
|
|
23
24
|
const raw = env[name];
|
|
@@ -123,6 +124,8 @@ function loadRuntimeConfig(env = process.env) {
|
|
|
123
124
|
opencodeExtraArgs: envList(env, "NAVARCH_OPENCODE_EXTRA_ARGS", []),
|
|
124
125
|
acpBin: env.NAVARCH_ACP_BIN ?? "dsh",
|
|
125
126
|
acpExtraArgs: envList(env, "NAVARCH_ACP_EXTRA_ARGS", ["--profile", "acp"]),
|
|
127
|
+
qoderBin: env.NAVARCH_QODER_BIN ?? "qoder",
|
|
128
|
+
qoderExtraArgs: envList(env, "NAVARCH_QODER_EXTRA_ARGS", []),
|
|
126
129
|
gitAuthorName: env.NAVARCH_GIT_AUTHOR_NAME ?? "sagentlab",
|
|
127
130
|
gitAuthorEmail: env.NAVARCH_GIT_AUTHOR_EMAIL ?? "z@sagentlab.com",
|
|
128
131
|
mcpConfigPath: env.NAVARCH_MCP_CONFIG_PATH ?? null,
|
package/dist/machine-store.cjs
CHANGED
|
@@ -52,7 +52,8 @@ async function resolveMachineIdentity(configDir, apiBaseFallback) {
|
|
|
52
52
|
process.env.NAVARCH_AGENT === "claude-code" ||
|
|
53
53
|
process.env.NAVARCH_AGENT === "gemini" ||
|
|
54
54
|
process.env.NAVARCH_AGENT === "opencode" ||
|
|
55
|
-
process.env.NAVARCH_AGENT === "acp"
|
|
55
|
+
process.env.NAVARCH_AGENT === "acp" ||
|
|
56
|
+
process.env.NAVARCH_AGENT === "qoder"
|
|
56
57
|
? process.env.NAVARCH_AGENT
|
|
57
58
|
: undefined,
|
|
58
59
|
};
|
package/dist/session.cjs
CHANGED
|
@@ -44,7 +44,7 @@ function resolveSessionExecution(config, claimed) {
|
|
|
44
44
|
profile: claimed.task.execution_profile ?? "standard",
|
|
45
45
|
model: runtime === "codex" ? "gpt-6-astra"
|
|
46
46
|
: runtime === "gemini" ? "auto"
|
|
47
|
-
: runtime === "opencode" || runtime === "acp" ? "default"
|
|
47
|
+
: runtime === "opencode" || runtime === "acp" || runtime === "qoder" ? "default"
|
|
48
48
|
: "claude-opus-5",
|
|
49
49
|
reasoning_effort: "medium",
|
|
50
50
|
};
|
|
@@ -94,7 +94,7 @@ async function runSession(deps, claimed, sessionId) {
|
|
|
94
94
|
await api
|
|
95
95
|
.completeLease(leaseId, {
|
|
96
96
|
status: "failed",
|
|
97
|
-
report: verificationFailureReport(task.task_type, failureSummary),
|
|
97
|
+
report: verificationFailureReport(task.task_type, failureSummary, "crashed"),
|
|
98
98
|
failure_summary: failureSummary,
|
|
99
99
|
failed_before_start: true,
|
|
100
100
|
evidence_urls: [],
|
|
@@ -174,7 +174,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
174
174
|
const failureSummary = `Project ${task.project_id} has no GitHub repository URL. Set it in Project settings before dispatching work.`;
|
|
175
175
|
await api.completeLease(leaseId, {
|
|
176
176
|
status: "failed",
|
|
177
|
-
report: verificationFailureReport(task.task_type, failureSummary),
|
|
177
|
+
report: verificationFailureReport(task.task_type, failureSummary, "crashed"),
|
|
178
178
|
failure_summary: failureSummary,
|
|
179
179
|
failed_before_start: true,
|
|
180
180
|
evidence_urls: [],
|
|
@@ -294,7 +294,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
294
294
|
await api
|
|
295
295
|
.completeLease(leaseId, {
|
|
296
296
|
status: "failed",
|
|
297
|
-
report: verificationFailureReport(task.task_type, "Docker sandbox unavailable on this machine."),
|
|
297
|
+
report: verificationFailureReport(task.task_type, "Docker sandbox unavailable on this machine.", "crashed"),
|
|
298
298
|
failure_summary: "Docker sandbox unavailable on this machine.",
|
|
299
299
|
failed_before_start: true,
|
|
300
300
|
evidence_urls: [],
|
|
@@ -616,7 +616,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
616
616
|
const completion = {
|
|
617
617
|
status: mapping.leaseOutcome,
|
|
618
618
|
report: mapping.leaseOutcome === "failed"
|
|
619
|
-
? verificationFailureReport(task.task_type, redactedReport)
|
|
619
|
+
? verificationFailureReport(task.task_type, redactedReport, mapping.exitStatus)
|
|
620
620
|
: redactedReport,
|
|
621
621
|
evidence_urls: mapping.evidenceUrls,
|
|
622
622
|
...(deliveryFailureSummary
|
|
@@ -674,7 +674,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
674
674
|
status: "failed",
|
|
675
675
|
report: verificationFailureReport(task.task_type, remediable
|
|
676
676
|
? redactedRejection
|
|
677
|
-
: `${redactedRejection}\n\n---\n\n${completion.report}
|
|
677
|
+
: `${redactedRejection}\n\n---\n\n${completion.report}`, "failed"),
|
|
678
678
|
failure_summary: redactedRejection,
|
|
679
679
|
exit_status: "failed",
|
|
680
680
|
});
|
|
@@ -708,7 +708,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
708
708
|
status: "failed",
|
|
709
709
|
report: verificationFailureReport(task.task_type, lastCompletion
|
|
710
710
|
? `${lastCompletion.report}\n\n---\n\n${failureSummary}`
|
|
711
|
-
: failureSummary),
|
|
711
|
+
: failureSummary, "crashed"),
|
|
712
712
|
failure_summary: failureSummary,
|
|
713
713
|
failed_before_start: !adapterStarted,
|
|
714
714
|
evidence_urls: lastCompletion?.evidence_urls ?? [],
|
|
@@ -763,6 +763,8 @@ function adapterCommand(config, runtime) {
|
|
|
763
763
|
return { bin: config.opencodeBin, extraArgs: config.opencodeExtraArgs };
|
|
764
764
|
case "acp":
|
|
765
765
|
return { bin: config.acpBin, extraArgs: config.acpExtraArgs };
|
|
766
|
+
case "qoder":
|
|
767
|
+
return { bin: config.qoderBin, extraArgs: config.qoderExtraArgs };
|
|
766
768
|
}
|
|
767
769
|
}
|
|
768
770
|
/**
|
|
@@ -792,14 +794,23 @@ function sumReportedUsage(attempts, key) {
|
|
|
792
794
|
* does not open with `Verification verdict: FAIL` or `BLOCKED`
|
|
793
795
|
* (verification_verdict_invalid), and that gate applies to runtime-authored
|
|
794
796
|
* failure reports too. When the report carries no usable verdict — the agent
|
|
795
|
-
*
|
|
796
|
-
*
|
|
797
|
-
*
|
|
798
|
-
*
|
|
797
|
+
* exited non-zero, or opened with PASS despite the failed outcome — label it
|
|
798
|
+
* BLOCKED: the failure prevented an acceptance decision. Without this, the
|
|
799
|
+
* failure completion is itself rejected, the session crashes, and the lease
|
|
800
|
+
* dangles until expiry.
|
|
801
|
+
*
|
|
802
|
+
* A `killed` or `crashed` exit is left unlabelled: the control plane accepts
|
|
803
|
+
* those verdict-less as the runtime's own failure and routes them through
|
|
804
|
+
* failure attribution (an infrastructure retry). A BLOCKED label there would
|
|
805
|
+
* misreport a transient timeout as a verifier verdict and escalate it to a
|
|
806
|
+
* human; the control plane discards such a label, so posting it only muddies
|
|
807
|
+
* the session report.
|
|
799
808
|
*/
|
|
800
|
-
function verificationFailureReport(taskType, report) {
|
|
809
|
+
function verificationFailureReport(taskType, report, exitStatus) {
|
|
801
810
|
if (taskType !== "verify")
|
|
802
811
|
return report;
|
|
812
|
+
if (exitStatus === "killed" || exitStatus === "crashed")
|
|
813
|
+
return report;
|
|
803
814
|
const verdict = (0, exit_conditions_cjs_1.leadingVerificationVerdict)(report);
|
|
804
815
|
if (verdict === "fail" || verdict === "blocked")
|
|
805
816
|
return report;
|
package/package.json
CHANGED