@sagentlab/navarch-runtime 0.1.85 → 0.1.87
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 +28 -10
- package/dist/adapters/claude.cjs +13 -9
- package/dist/adapters/codex.cjs +5 -4
- package/dist/adapters/gemini.cjs +49 -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,30 @@ 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
|
-
|
|
694
|
-
|
|
694
|
+
Gemini's native sandbox launcher converts piped stdin back into a `--prompt`
|
|
695
|
+
argument for its child. The adapter rejects prompts of 128 KiB or more when
|
|
696
|
+
that sandbox is enabled, with an explicit diagnostic; reducing context or
|
|
697
|
+
selecting another runtime preserves the worktree boundary. Navarch Docker
|
|
698
|
+
mode disables the nested Gemini sandbox by default and can still pipe larger
|
|
699
|
+
prompts. All Gemini runs reject prompts above 8 MiB because Gemini's stdin
|
|
700
|
+
reader silently truncates input beyond that limit. These checks use UTF-8 bytes.
|
|
701
|
+
|
|
702
|
+
The OpenCode adapter uses `opencode run --format json` with the prompt on
|
|
703
|
+
stdin, and normalizes `text` and `step_finish` JSONL events into
|
|
695
704
|
the shared report/token/cost boundary. It translates the lease MCP document
|
|
696
705
|
to OpenCode's remote-server config with OAuth disabled and environment-backed
|
|
697
706
|
headers. Missing accounting fields stay absent rather than becoming a
|
|
@@ -716,6 +725,15 @@ ships that server as `dsh --profile acp`. This is intentionally not the
|
|
|
716
725
|
REST-based [Agent Communication Protocol](https://agentcommunicationprotocol.dev),
|
|
717
726
|
which is a different protocol now maintained as part of A2A.
|
|
718
727
|
|
|
728
|
+
Every CLI adapter writes the prompt to the agent's stdin, never argv. Linux
|
|
729
|
+
rejects any single argv or environment string over 128 KiB
|
|
730
|
+
(`MAX_ARG_STRLEN`), so an argv prompt made large review sessions fail with
|
|
731
|
+
`spawn E2BIG` before the agent started. Docker-mode sessions forward the same
|
|
732
|
+
stdin through `docker exec -i`.
|
|
733
|
+
Gemini's own sandbox relaunch is an exception to this transport guarantee,
|
|
734
|
+
as described above; moving the initial spawn to stdin does not remove its
|
|
735
|
+
internal argv limit.
|
|
736
|
+
|
|
719
737
|
## Architecture
|
|
720
738
|
|
|
721
739
|
```
|
|
@@ -733,12 +751,12 @@ cli.cts
|
|
|
733
751
|
into Docker when NAVARCH_SANDBOX_MODE=docker
|
|
734
752
|
4. selectAdapter(config.agentType) (adapters/index.cts) picks one AgentAdapter
|
|
735
753
|
(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
|
|
754
|
+
- claudeCodeAdapter (adapters/claude.cts) — `claude -p --mcp-config <path> < prompt`
|
|
755
|
+
- codexAdapter (adapters/codex.cts) — `codex exec - --json -c mcp_servers.*=... < prompt`
|
|
756
|
+
- geminiAdapter (adapters/gemini.cts) — `gemini --output-format stream-json < prompt`
|
|
757
|
+
- openCodeAdapter (adapters/opencode.cts) — `opencode run --format json < prompt`
|
|
740
758
|
- acpAdapter (adapters/acp.cts) — ACP v1 JSON-RPC/stdio; defaults to `dsh --profile acp`
|
|
741
|
-
- qoderAdapter (adapters/qoder.cts) — `qoder -p
|
|
759
|
+
- qoderAdapter (adapters/qoder.cts) — `qoder -p --output-format json < prompt`
|
|
742
760
|
heartbeating the lease every NAVARCH_LEASE_HEARTBEAT_INTERVAL_MS throughout either;
|
|
743
761
|
a failed heartbeat aborts the run (kills the process) and marks the outcome as lease-lost
|
|
744
762
|
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,23 @@ 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. Its
|
|
20
|
+
* native sandbox puts stdin back into argv, so that path still has Linux's
|
|
21
|
+
* 128 KiB per-string limit. Gemini also caps stdin at 8 MiB.
|
|
17
22
|
*/
|
|
18
23
|
async function runGeminiAdapter(options) {
|
|
19
|
-
|
|
24
|
+
if (options.signal?.aborted) {
|
|
25
|
+
return { exitCode: null, timedOut: false, killedByLeaseLoss: true, stdout: "", stderr: "" };
|
|
26
|
+
}
|
|
27
|
+
const promptBytes = Buffer.byteLength(options.prompt, "utf8");
|
|
28
|
+
// Gemini's readStdin() silently truncates at 8 MiB. Reject before any
|
|
29
|
+
// settings writes or process launch rather than execute a partial task.
|
|
30
|
+
if (promptBytes > 8 * 1024 * 1024) {
|
|
31
|
+
return inputFailure(`Gemini CLI stdin is limited to 8 MiB; the task prompt is ${promptBytes} bytes. Reduce the context bundle or select another runtime.`);
|
|
32
|
+
}
|
|
33
|
+
const args = [];
|
|
20
34
|
const env = { ...options.env };
|
|
21
35
|
if (!hasArg(options.extraArgs, "--output-format", "-o")) {
|
|
22
36
|
args.push("--output-format", "stream-json");
|
|
@@ -53,12 +67,41 @@ async function runGeminiAdapter(options) {
|
|
|
53
67
|
args.push(...options.extraArgs);
|
|
54
68
|
if (options.model)
|
|
55
69
|
args.push("--model", options.model);
|
|
70
|
+
// Gemini's sandbox launcher reads stdin and injects it back into --prompt
|
|
71
|
+
// for its child process (packages/cli/src/gemini.tsx injectStdinIntoArgs).
|
|
72
|
+
// Moving our first spawn's prompt to stdin cannot fix that second spawn.
|
|
73
|
+
// Check the effective flag, including operator overrides, without disabling
|
|
74
|
+
// the worktree guard. Even on macOS a Docker sandbox may launch on Linux.
|
|
75
|
+
if (geminiSandboxEnabled(args) && promptBytes >= 128 * 1024) {
|
|
76
|
+
return inputFailure(`Gemini CLI re-encodes stdin as --prompt when launching its native sandbox; the ${promptBytes}-byte task prompt exceeds Linux's argv limit. Reduce the context bundle or select another runtime that supports large prompts in its sandbox.`);
|
|
77
|
+
}
|
|
56
78
|
const runOptions = { ...options, env };
|
|
57
79
|
const raw = runOptions.dockerExec
|
|
58
80
|
? await runViaDocker(runOptions, args)
|
|
59
81
|
: await runOnHost(runOptions, args);
|
|
60
82
|
return attachGeminiOutput(raw);
|
|
61
83
|
}
|
|
84
|
+
function inputFailure(stderr) {
|
|
85
|
+
return { exitCode: 1, timedOut: false, killedByLeaseLoss: false, stdout: "", stderr };
|
|
86
|
+
}
|
|
87
|
+
function geminiSandboxEnabled(args) {
|
|
88
|
+
// There is always a generated --sandbox flag unless extraArgs supplies one.
|
|
89
|
+
// Like yargs, later explicit values override earlier ones.
|
|
90
|
+
let enabled = false;
|
|
91
|
+
for (let index = 0; index < args.length; index++) {
|
|
92
|
+
const arg = args[index];
|
|
93
|
+
if (arg === "--sandbox" || arg === "-s") {
|
|
94
|
+
enabled = args[index + 1] !== "false";
|
|
95
|
+
}
|
|
96
|
+
else if (arg.startsWith("--sandbox=") || arg.startsWith("-s=")) {
|
|
97
|
+
enabled = arg.slice(arg.indexOf("=") + 1) !== "false";
|
|
98
|
+
}
|
|
99
|
+
else if (arg === "--no-sandbox") {
|
|
100
|
+
enabled = false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return enabled;
|
|
104
|
+
}
|
|
62
105
|
function hasArg(args, ...flags) {
|
|
63
106
|
return args.some((arg) => flags.some((flag) => arg === flag || arg.startsWith(`${flag}=`)));
|
|
64
107
|
}
|
|
@@ -157,7 +200,9 @@ async function runOnHost(options, args) {
|
|
|
157
200
|
cwd: options.cwd,
|
|
158
201
|
env: { ...process.env, ...options.env },
|
|
159
202
|
});
|
|
160
|
-
child.
|
|
203
|
+
// Ignore EPIPE from a child that exits before reading; `close` reports it.
|
|
204
|
+
child.stdin?.on("error", () => undefined);
|
|
205
|
+
child.stdin?.end(options.prompt);
|
|
161
206
|
options.onProcessStarted?.(child.pid);
|
|
162
207
|
const timer = options.timeoutMs > 0 ? setTimeout(() => {
|
|
163
208
|
timedOut = true;
|
|
@@ -207,7 +252,8 @@ async function runViaDocker(options, args) {
|
|
|
207
252
|
onAbort();
|
|
208
253
|
try {
|
|
209
254
|
const forwardedEnv = Object.keys(options.env).flatMap((name) => ["--env", name]);
|
|
210
|
-
const result = await runner.run("docker", ["exec", ...forwardedEnv, containerName, "sh", "-c", command], {
|
|
255
|
+
const result = await runner.run("docker", ["exec", "-i", ...forwardedEnv, containerName, "sh", "-c", command], { input: options.prompt,
|
|
256
|
+
timeoutMs: options.timeoutMs,
|
|
211
257
|
onActivity: options.onActivity,
|
|
212
258
|
signal: options.signal,
|
|
213
259
|
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