@sagentlab/navarch-runtime 0.1.85 → 0.1.86
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 +17 -10
- package/dist/adapters/claude.cjs +13 -9
- package/dist/adapters/codex.cjs +5 -4
- package/dist/adapters/gemini.cjs +10 -3
- package/dist/adapters/opencode.cjs +18 -7
- package/dist/adapters/pi-local.cjs +2 -1
- package/dist/adapters/pi.cjs +10 -5
- package/dist/adapters/qoder.cjs +12 -6
- package/dist/sandbox.cjs +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -677,21 +677,22 @@ not the account's subscription charge or long-context/fast-mode uplifts.
|
|
|
677
677
|
|
|
678
678
|
|
|
679
679
|
The Codex CLI invocation was verified against `codex-cli 0.144.1` on
|
|
680
|
-
2026-07-18. The runtime uses `codex exec
|
|
680
|
+
2026-07-18. The runtime uses `codex exec - --json` and translates
|
|
681
681
|
the existing per-session MCP JSON into one-off `-c mcp_servers.*` overrides.
|
|
682
682
|
Machine and lease credentials are referenced through environment variables,
|
|
683
683
|
not placed in argv. The JSONL parser accepts the verified top-level
|
|
684
684
|
`item.completed` / `turn.completed` shape and retains the older `msg`
|
|
685
685
|
envelope as a compatibility fallback.
|
|
686
686
|
|
|
687
|
-
The Gemini adapter
|
|
688
|
-
|
|
687
|
+
The Gemini adapter runs headlessly with `--output-format stream-json` and the
|
|
688
|
+
prompt piped on stdin, which Gemini uses as the prompt when stdin is not a
|
|
689
|
+
TTY. It resets pre-tool assistant text, retains the final
|
|
689
690
|
post-tool answer as the report, and reads aggregate `input_tokens` and
|
|
690
691
|
`output_tokens` from the terminal `result` event. Gemini does not report USD
|
|
691
692
|
cost, so an unknown value remains absent rather than becoming zero.
|
|
692
693
|
|
|
693
|
-
The OpenCode adapter uses `opencode run
|
|
694
|
-
stdin
|
|
694
|
+
The OpenCode adapter uses `opencode run --format json` with the prompt on
|
|
695
|
+
stdin, and normalizes `text` and `step_finish` JSONL events into
|
|
695
696
|
the shared report/token/cost boundary. It translates the lease MCP document
|
|
696
697
|
to OpenCode's remote-server config with OAuth disabled and environment-backed
|
|
697
698
|
headers. Missing accounting fields stay absent rather than becoming a
|
|
@@ -716,6 +717,12 @@ ships that server as `dsh --profile acp`. This is intentionally not the
|
|
|
716
717
|
REST-based [Agent Communication Protocol](https://agentcommunicationprotocol.dev),
|
|
717
718
|
which is a different protocol now maintained as part of A2A.
|
|
718
719
|
|
|
720
|
+
Every CLI adapter writes the prompt to the agent's stdin, never argv. Linux
|
|
721
|
+
rejects any single argv or environment string over 128 KiB
|
|
722
|
+
(`MAX_ARG_STRLEN`), so an argv prompt made large review sessions fail with
|
|
723
|
+
`spawn E2BIG` before the agent started. Docker-mode sessions forward the same
|
|
724
|
+
stdin through `docker exec -i`.
|
|
725
|
+
|
|
719
726
|
## Architecture
|
|
720
727
|
|
|
721
728
|
```
|
|
@@ -733,12 +740,12 @@ cli.cts
|
|
|
733
740
|
into Docker when NAVARCH_SANDBOX_MODE=docker
|
|
734
741
|
4. selectAdapter(config.agentType) (adapters/index.cts) picks one AgentAdapter
|
|
735
742
|
(adapters/types.cts) by NAVARCH_AGENT, then .run(...):
|
|
736
|
-
- claudeCodeAdapter (adapters/claude.cts) — `claude -p
|
|
737
|
-
- codexAdapter (adapters/codex.cts) — `codex exec
|
|
738
|
-
- geminiAdapter (adapters/gemini.cts) — `gemini --
|
|
739
|
-
- openCodeAdapter (adapters/opencode.cts) — `opencode run
|
|
743
|
+
- claudeCodeAdapter (adapters/claude.cts) — `claude -p --mcp-config <path> < prompt`
|
|
744
|
+
- codexAdapter (adapters/codex.cts) — `codex exec - --json -c mcp_servers.*=... < prompt`
|
|
745
|
+
- geminiAdapter (adapters/gemini.cts) — `gemini --output-format stream-json < prompt`
|
|
746
|
+
- openCodeAdapter (adapters/opencode.cts) — `opencode run --format json < prompt`
|
|
740
747
|
- acpAdapter (adapters/acp.cts) — ACP v1 JSON-RPC/stdio; defaults to `dsh --profile acp`
|
|
741
|
-
- qoderAdapter (adapters/qoder.cts) — `qoder -p
|
|
748
|
+
- qoderAdapter (adapters/qoder.cts) — `qoder -p --output-format json < prompt`
|
|
742
749
|
heartbeating the lease every NAVARCH_LEASE_HEARTBEAT_INTERVAL_MS throughout either;
|
|
743
750
|
a failed heartbeat aborts the run (kills the process) and marks the outcome as lease-lost
|
|
744
751
|
5. mapExitCondition (exit-conditions.cts) → redact.cts scrubs the transcript → upload.cts PUTs it
|
package/dist/adapters/claude.cjs
CHANGED
|
@@ -13,8 +13,10 @@ function hasFlag(args, flag) {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Headless Claude Code adapter (project-plan.md §3.9 / implementation-plan.md
|
|
16
|
-
* WP-07): `claude -p
|
|
17
|
-
*
|
|
16
|
+
* WP-07): `claude -p --mcp-config platform-mcp.json --output-format json`
|
|
17
|
+
* with the context bundle on stdin (`claude -p` reads its prompt from stdin
|
|
18
|
+
* when none is given; Linux rejects any single argv string over 128 KiB, so
|
|
19
|
+
* an argv prompt failed `spawn E2BIG` before Claude started). Returns raw stdout/stderr/exit signals (plus
|
|
18
20
|
* best-effort tokensIn/tokensOut/costUsd parsed from the JSON result -- see
|
|
19
21
|
* exit-conditions.cts#parseClaudeJsonResult) for exit-conditions.cts to map
|
|
20
22
|
* onto a complete()/fail() call.
|
|
@@ -91,7 +93,7 @@ function addUsage(first, second) {
|
|
|
91
93
|
return first === undefined && second === undefined ? undefined : (first ?? 0) + (second ?? 0);
|
|
92
94
|
}
|
|
93
95
|
async function runClaudeAttempt(options, automaticFallback) {
|
|
94
|
-
const args = ["-p"
|
|
96
|
+
const args = ["-p"];
|
|
95
97
|
const hasSettingSources = options.extraArgs.some((arg) => arg === "--setting-sources" || arg.startsWith("--setting-sources="));
|
|
96
98
|
const hasExplicitPermissionMode = options.extraArgs.some((arg) => ["--permission-mode", "--permission-prompt-tool", "--dangerously-skip-permissions"].some((flag) => arg === flag || arg.startsWith(`${flag}=`)));
|
|
97
99
|
// Runtime sessions must not inherit an operator's personal or project-local
|
|
@@ -180,11 +182,11 @@ async function runOnHost(options, args) {
|
|
|
180
182
|
cwd: options.cwd,
|
|
181
183
|
env: { ...process.env, ...options.env },
|
|
182
184
|
});
|
|
183
|
-
// `claude -p`
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
child.stdin?.end();
|
|
185
|
+
// `claude -p` reads the whole prompt from stdin and starts at EOF. A
|
|
186
|
+
// child that exits before reading surfaces through `close`; ignore the
|
|
187
|
+
// resulting EPIPE here.
|
|
188
|
+
child.stdin?.on("error", () => undefined);
|
|
189
|
+
child.stdin?.end(options.prompt);
|
|
188
190
|
options.onProcessStarted?.(child.pid);
|
|
189
191
|
const timer = options.timeoutMs > 0 ? setTimeout(() => {
|
|
190
192
|
timedOut = true;
|
|
@@ -234,7 +236,9 @@ async function runViaDocker(options, args) {
|
|
|
234
236
|
if (options.signal?.aborted)
|
|
235
237
|
onAbort();
|
|
236
238
|
try {
|
|
237
|
-
|
|
239
|
+
// `-i` forwards the prompt to `claude -p` on stdin (see runOnHost).
|
|
240
|
+
const result = await runner.run("docker", ["exec", "-i", containerName, "sh", "-c", command], {
|
|
241
|
+
input: options.prompt,
|
|
238
242
|
timeoutMs: options.timeoutMs,
|
|
239
243
|
onActivity: options.onActivity,
|
|
240
244
|
signal: options.signal,
|
package/dist/adapters/codex.cjs
CHANGED
|
@@ -24,8 +24,8 @@ const kill_agent_cjs_1 = require("../kill-agent.cjs");
|
|
|
24
24
|
* - `exec <prompt>` — ASSUMED to be Codex CLI's non-interactive/headless
|
|
25
25
|
* subcommand (the `codex exec` "automation mode" analog of `claude -p`):
|
|
26
26
|
* runs the prompt to completion without the interactive TUI and exits,
|
|
27
|
-
* printing its result to stdout.
|
|
28
|
-
*
|
|
27
|
+
* printing its result to stdout. The prompt is `-` and arrives on stdin
|
|
28
|
+
* (through `docker exec -i` in sandbox mode): Linux rejects any single argv string over 128 KiB
|
|
29
29
|
* (MAX_ARG_STRLEN), so a large review prompt failed `spawn E2BIG` before
|
|
30
30
|
* Codex started (tobi-agent-5571, 2026-10-06).
|
|
31
31
|
* - `--json` emits top-level `thread.started`, `turn.started`,
|
|
@@ -48,7 +48,7 @@ const kill_agent_cjs_1 = require("../kill-agent.cjs");
|
|
|
48
48
|
* approval-bypass flag) without an adapter code change.
|
|
49
49
|
*/
|
|
50
50
|
async function runCodexAdapter(options) {
|
|
51
|
-
const args = ["exec",
|
|
51
|
+
const args = ["exec", "-"];
|
|
52
52
|
const env = { ...options.env };
|
|
53
53
|
if (options.mcpConfigPath)
|
|
54
54
|
args.push(...(await codexMcpArgs(options.mcpConfigPath, env)));
|
|
@@ -233,7 +233,8 @@ async function runViaDocker(options, args) {
|
|
|
233
233
|
// variable names in argv. This keeps generated MCP bearer/header values
|
|
234
234
|
// out of `ps` while making the host and Docker paths equivalent.
|
|
235
235
|
const forwardedEnv = Object.keys(options.env).flatMap((name) => ["--env", name]);
|
|
236
|
-
const result = await runner.run("docker", ["exec", ...forwardedEnv, containerName, "sh", "-c", command], {
|
|
236
|
+
const result = await runner.run("docker", ["exec", "-i", ...forwardedEnv, containerName, "sh", "-c", command], {
|
|
237
|
+
input: options.prompt,
|
|
237
238
|
timeoutMs: options.timeoutMs,
|
|
238
239
|
onActivity: options.onActivity,
|
|
239
240
|
signal: options.signal,
|
package/dist/adapters/gemini.cjs
CHANGED
|
@@ -14,9 +14,13 @@ const kill_agent_cjs_1 = require("../kill-agent.cjs");
|
|
|
14
14
|
* The CLI reads MCP servers from settings rather than accepting Claude's
|
|
15
15
|
* `--mcp-config` flag. The adapter converts the lease-scoped config into a
|
|
16
16
|
* temporary system-settings file and restricts the run to those server names.
|
|
17
|
+
*
|
|
18
|
+
* The prompt arrives on stdin rather than `--prompt`: Gemini runs headlessly
|
|
19
|
+
* whenever stdin is not a TTY and uses the piped text as the prompt, and
|
|
20
|
+
* Linux rejects any single argv string over 128 KiB (`spawn E2BIG`).
|
|
17
21
|
*/
|
|
18
22
|
async function runGeminiAdapter(options) {
|
|
19
|
-
const args = [
|
|
23
|
+
const args = [];
|
|
20
24
|
const env = { ...options.env };
|
|
21
25
|
if (!hasArg(options.extraArgs, "--output-format", "-o")) {
|
|
22
26
|
args.push("--output-format", "stream-json");
|
|
@@ -157,7 +161,9 @@ async function runOnHost(options, args) {
|
|
|
157
161
|
cwd: options.cwd,
|
|
158
162
|
env: { ...process.env, ...options.env },
|
|
159
163
|
});
|
|
160
|
-
child.
|
|
164
|
+
// Ignore EPIPE from a child that exits before reading; `close` reports it.
|
|
165
|
+
child.stdin?.on("error", () => undefined);
|
|
166
|
+
child.stdin?.end(options.prompt);
|
|
161
167
|
options.onProcessStarted?.(child.pid);
|
|
162
168
|
const timer = options.timeoutMs > 0 ? setTimeout(() => {
|
|
163
169
|
timedOut = true;
|
|
@@ -207,7 +213,8 @@ async function runViaDocker(options, args) {
|
|
|
207
213
|
onAbort();
|
|
208
214
|
try {
|
|
209
215
|
const forwardedEnv = Object.keys(options.env).flatMap((name) => ["--env", name]);
|
|
210
|
-
const result = await runner.run("docker", ["exec", ...forwardedEnv, containerName, "sh", "-c", command], {
|
|
216
|
+
const result = await runner.run("docker", ["exec", "-i", ...forwardedEnv, containerName, "sh", "-c", command], { input: options.prompt,
|
|
217
|
+
timeoutMs: options.timeoutMs,
|
|
211
218
|
onActivity: options.onActivity,
|
|
212
219
|
signal: options.signal,
|
|
213
220
|
env: { ...process.env, ...options.env },
|
|
@@ -16,7 +16,12 @@ const kill_agent_cjs_1 = require("../kill-agent.cjs");
|
|
|
16
16
|
* Headless OpenCode adapter, fixture-pinned to the OpenCode JSON/config
|
|
17
17
|
* contract shared by V1 and V2 (verified against 2.0.6):
|
|
18
18
|
*
|
|
19
|
-
* opencode run
|
|
19
|
+
* opencode run --format json [--model provider/model[#variant]] < prompt
|
|
20
|
+
*
|
|
21
|
+
* The prompt goes in on stdin, which `opencode run` reads as the message when
|
|
22
|
+
* none is given (verified against 2.0.17). Linux rejects any single argv
|
|
23
|
+
* string over 128 KiB (`spawn E2BIG`), and an argv prompt also let an
|
|
24
|
+
* agent's `pkill -f <word>` match the CLI itself (#1587).
|
|
20
25
|
*
|
|
21
26
|
* OpenCode merges configuration from several machine and project locations,
|
|
22
27
|
* so each Navarch session gets an isolated XDG/config directory, disables
|
|
@@ -56,7 +61,7 @@ async function runOpenCodeAdapter(options) {
|
|
|
56
61
|
const binaryKey = `${options.dockerExec ? "docker:" : ""}${options.bin ?? "opencode"}`;
|
|
57
62
|
const defaultLogLevel = !hasArg(options.extraArgs, "--log-level");
|
|
58
63
|
const logLevel = defaultLogLevel ? (openCodeLogLevels.get(binaryKey) ?? "ERROR") : null;
|
|
59
|
-
const args = ["run"
|
|
64
|
+
const args = ["run"];
|
|
60
65
|
if (/^\s*--standalone\b/m.test(help.stdout) && !hasArg(options.extraArgs, "--standalone")) {
|
|
61
66
|
args.push("--standalone");
|
|
62
67
|
}
|
|
@@ -76,7 +81,9 @@ async function runOpenCodeAdapter(options) {
|
|
|
76
81
|
// use the model's own default.
|
|
77
82
|
args.push("--model", options.reasoningEffort ? `${options.model}#${options.reasoningEffort}` : options.model);
|
|
78
83
|
}
|
|
79
|
-
const run = (runArgs) => runOptions.dockerExec
|
|
84
|
+
const run = (runArgs) => runOptions.dockerExec
|
|
85
|
+
? runViaDocker(runOptions, runArgs, options.prompt)
|
|
86
|
+
: runOnHost(runOptions, runArgs, options.prompt);
|
|
80
87
|
let raw = await run(args);
|
|
81
88
|
if (logLevel && raw.exitCode !== 0 && isLogLevelRejection(raw.stderr)) {
|
|
82
89
|
// The CLI rejected our default before starting any work, so one retry with
|
|
@@ -315,7 +322,8 @@ function capacityMonitor(onLimit) {
|
|
|
315
322
|
},
|
|
316
323
|
};
|
|
317
324
|
}
|
|
318
|
-
|
|
325
|
+
/** `input` is written to the child's stdin; the `run --help` probe sends none. */
|
|
326
|
+
async function runOnHost(options, args, input = "") {
|
|
319
327
|
return new Promise((resolve) => {
|
|
320
328
|
let stdout = "";
|
|
321
329
|
let stderr = "";
|
|
@@ -331,7 +339,9 @@ async function runOnHost(options, args) {
|
|
|
331
339
|
capacityError ??= detail;
|
|
332
340
|
(0, kill_agent_cjs_1.killAgent)(child);
|
|
333
341
|
});
|
|
334
|
-
child.
|
|
342
|
+
// Ignore EPIPE from a child that exits before reading; `close` reports it.
|
|
343
|
+
child.stdin?.on("error", () => undefined);
|
|
344
|
+
child.stdin?.end(input);
|
|
335
345
|
options.onProcessStarted?.(child.pid);
|
|
336
346
|
const timer = options.timeoutMs > 0 ? setTimeout(() => {
|
|
337
347
|
timedOut = true;
|
|
@@ -369,7 +379,7 @@ async function runOnHost(options, args) {
|
|
|
369
379
|
});
|
|
370
380
|
});
|
|
371
381
|
}
|
|
372
|
-
async function runViaDocker(options, args) {
|
|
382
|
+
async function runViaDocker(options, args, input) {
|
|
373
383
|
const { containerName, runner } = options.dockerExec;
|
|
374
384
|
const quoted = [options.bin, ...args].map(shellQuote).join(" ");
|
|
375
385
|
const command = `[ -f /tmp/session.env ] && . /tmp/session.env; cd repo 2>/dev/null; ${quoted}`;
|
|
@@ -394,7 +404,8 @@ async function runViaDocker(options, args) {
|
|
|
394
404
|
onAbort();
|
|
395
405
|
try {
|
|
396
406
|
const forwardedEnv = Object.keys(options.env).flatMap((name) => ["--env", name]);
|
|
397
|
-
const result = await runner.run("docker", ["exec", ...forwardedEnv, containerName, "sh", "-c", command], {
|
|
407
|
+
const result = await runner.run("docker", ["exec", ...(input === undefined ? [] : ["-i"]), ...forwardedEnv, containerName, "sh", "-c", command], { input,
|
|
408
|
+
timeoutMs: options.timeoutMs,
|
|
398
409
|
onActivity: options.onActivity,
|
|
399
410
|
signal: controller.signal,
|
|
400
411
|
onStdout: (chunk) => { streamed.stdout = true; monitor.push("stdout", chunk); },
|
|
@@ -14,7 +14,8 @@ async function runLocalPiAdapter(options) {
|
|
|
14
14
|
"--model", model.includes("/") ? model : `local/${model}`];
|
|
15
15
|
if (options.reasoningEffort)
|
|
16
16
|
args.push("--thinking", options.reasoningEffort === "max" ? "xhigh" : options.reasoningEffort);
|
|
17
|
-
|
|
17
|
+
// The prompt goes in on stdin (pi.cts runOnHost), never argv.
|
|
18
|
+
args.push("-p");
|
|
18
19
|
return (0, pi_cjs_1.attachPiOutput)(await (0, pi_cjs_1.runOnHost)(options, args, true));
|
|
19
20
|
}
|
|
20
21
|
exports.localPiAdapter = { agentType: "pi-local", run: runLocalPiAdapter };
|
package/dist/adapters/pi.cjs
CHANGED
|
@@ -19,9 +19,11 @@ const kill_agent_cjs_1 = require("../kill-agent.cjs");
|
|
|
19
19
|
/**
|
|
20
20
|
* Headless Pi coding-agent adapter (#885) — the process-side twin of the
|
|
21
21
|
* Cloudflare hosted executor (worker/hosted-pi/lifecycle.ts). It invokes
|
|
22
|
-
* `pi --mode json -p <prompt
|
|
22
|
+
* `pi --mode json -p < prompt` against DeepSeek V4 through a generated
|
|
23
23
|
* OpenAI-compatible custom-provider config and normalizes the JSON event
|
|
24
|
-
* stream into the common AdapterResult boundary.
|
|
24
|
+
* stream into the common AdapterResult boundary. `-p` is Pi's print flag and
|
|
25
|
+
* the prompt arrives on stdin, as in the hosted executor (#1587): Linux
|
|
26
|
+
* rejects any single argv string over 128 KiB (`spawn E2BIG`).
|
|
25
27
|
*
|
|
26
28
|
* Hosted Pi is control-plane provisioned (docs/navarch/runtime-adapter-contract.md
|
|
27
29
|
* — "Hosted Pi is not a fifth AgentAdapter" for BYO workers): config.cts
|
|
@@ -254,7 +256,7 @@ async function runPiAdapter(options) {
|
|
|
254
256
|
// than passing an invalid value that Pi warns about and ignores.
|
|
255
257
|
args.push("--thinking", options.reasoningEffort === "max" ? "xhigh" : options.reasoningEffort);
|
|
256
258
|
}
|
|
257
|
-
args.push("-p"
|
|
259
|
+
args.push("-p");
|
|
258
260
|
const raw = options.dockerExec
|
|
259
261
|
? await runViaDocker(options, args)
|
|
260
262
|
: await runOnHost(options, args);
|
|
@@ -325,7 +327,9 @@ async function runOnHost(options, args, local = false) {
|
|
|
325
327
|
cwd: options.cwd,
|
|
326
328
|
env: { ...process.env, ...env },
|
|
327
329
|
});
|
|
328
|
-
child.
|
|
330
|
+
// Ignore EPIPE from a child that exits before reading; `close` reports it.
|
|
331
|
+
child.stdin?.on("error", () => undefined);
|
|
332
|
+
child.stdin?.end(options.prompt);
|
|
329
333
|
options.onProcessStarted?.(child.pid);
|
|
330
334
|
const timer = options.timeoutMs > 0 ? setTimeout(() => {
|
|
331
335
|
timedOut = true;
|
|
@@ -400,7 +404,8 @@ async function runViaDocker(options, args) {
|
|
|
400
404
|
onAbort();
|
|
401
405
|
try {
|
|
402
406
|
const forwardedEnv = Object.keys(options.env).flatMap((name) => ["--env", name]);
|
|
403
|
-
const result = await runner.run("docker", ["exec", ...forwardedEnv, containerName, "sh", "-c", command], {
|
|
407
|
+
const result = await runner.run("docker", ["exec", "-i", ...forwardedEnv, containerName, "sh", "-c", command], {
|
|
408
|
+
input: options.prompt,
|
|
404
409
|
timeoutMs: options.timeoutMs,
|
|
405
410
|
onActivity: options.onActivity,
|
|
406
411
|
signal: options.signal,
|
package/dist/adapters/qoder.cjs
CHANGED
|
@@ -9,7 +9,10 @@ function hasFlag(args, flag) {
|
|
|
9
9
|
return args.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Headless Qoder CLI adapter: `qoder -p
|
|
12
|
+
* Headless Qoder CLI adapter: `qoder -p --output-format json < prompt`.
|
|
13
|
+
* Like `claude -p`, print mode reads the prompt from stdin when no query is
|
|
14
|
+
* given (verified against qodercli 1.1.64), which keeps large prompts clear
|
|
15
|
+
* of Linux's 128 KiB per-argv-string limit (`spawn E2BIG`).
|
|
13
16
|
* Qoder's print mode emits the same single-JSON result shape as Claude Code
|
|
14
17
|
* (verified against qodercli 1.1.56), so usage/cost parsing reuses
|
|
15
18
|
* parseClaudeJsonResult and its result text lands in the ordinary
|
|
@@ -24,7 +27,7 @@ async function runQoderAdapter(options) {
|
|
|
24
27
|
if (options.signal?.aborted) {
|
|
25
28
|
return { exitCode: null, timedOut: false, killedByLeaseLoss: true, stdout: "", stderr: "" };
|
|
26
29
|
}
|
|
27
|
-
const args = ["-p"
|
|
30
|
+
const args = ["-p"];
|
|
28
31
|
// Runtime sessions must not inherit an operator's personal or project-local
|
|
29
32
|
// Qoder hooks, for the same reason as the Claude adapter: they make
|
|
30
33
|
// execution machine-dependent and can fail otherwise-good sessions. The
|
|
@@ -81,9 +84,10 @@ async function runOnHost(options, args) {
|
|
|
81
84
|
cwd: options.cwd,
|
|
82
85
|
env: { ...process.env, ...options.env },
|
|
83
86
|
});
|
|
84
|
-
// `qoder -p`
|
|
85
|
-
//
|
|
86
|
-
child.stdin?.
|
|
87
|
+
// `qoder -p` reads the whole prompt from stdin and starts at EOF. A child
|
|
88
|
+
// that exits before reading surfaces through `close`; ignore the EPIPE.
|
|
89
|
+
child.stdin?.on("error", () => undefined);
|
|
90
|
+
child.stdin?.end(options.prompt);
|
|
87
91
|
options.onProcessStarted?.(child.pid);
|
|
88
92
|
const timer = options.timeoutMs > 0 ? setTimeout(() => {
|
|
89
93
|
timedOut = true;
|
|
@@ -130,7 +134,9 @@ async function runViaDocker(options, args) {
|
|
|
130
134
|
if (options.signal?.aborted)
|
|
131
135
|
onAbort();
|
|
132
136
|
try {
|
|
133
|
-
|
|
137
|
+
// `-i` forwards the prompt to `qoder -p` on stdin (see runOnHost).
|
|
138
|
+
const result = await runner.run("docker", ["exec", "-i", containerName, "sh", "-c", command], {
|
|
139
|
+
input: options.prompt,
|
|
134
140
|
timeoutMs: options.timeoutMs,
|
|
135
141
|
onActivity: options.onActivity,
|
|
136
142
|
signal: options.signal,
|
package/dist/sandbox.cjs
CHANGED
|
@@ -61,6 +61,9 @@ exports.nodeCommandRunner = {
|
|
|
61
61
|
cleanup();
|
|
62
62
|
resolve({ code: code ?? -1, stdout, stderr });
|
|
63
63
|
});
|
|
64
|
+
// A child that exits before reading all input (e.g. `docker exec -i`
|
|
65
|
+
// carrying an agent prompt) raises EPIPE here; `close` reports the exit.
|
|
66
|
+
child.stdin?.on("error", () => undefined);
|
|
64
67
|
if (opts.input !== undefined) {
|
|
65
68
|
child.stdin?.write(opts.input);
|
|
66
69
|
}
|
package/package.json
CHANGED