@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 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 `acp`. Overrides the choice saved by `connect`/`register`; `start --agent` has highest priority. |
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
@@ -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 selects a reasoning variant through the model reference
45
- // (`provider/model#variant`) rather than a separate flag.
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 'acp'.");
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,
@@ -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
- * crashed, timed out, exited non-zero, or opened with PASS despite the failed
796
- * outcome — label it BLOCKED: the failure prevented an acceptance decision.
797
- * Without this, the failure completion is itself rejected, the session
798
- * crashes, and the lease dangles until expiry.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sagentlab/navarch-runtime",
3
- "version": "0.1.52",
3
+ "version": "0.1.54",
4
4
  "description": "Navarch machine-side session manager: claims delivery tasks and runs them through local coding agents.",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",