zidane 5.14.4 → 6.0.0
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/dist/{acp-24uU2WDs.js → acp-Irby04Us.js} +7 -8
- package/dist/{acp-24uU2WDs.js.map → acp-Irby04Us.js.map} +1 -1
- package/dist/acp-cli.js +7 -7
- package/dist/acp.d.ts +2 -3
- package/dist/acp.d.ts.map +1 -1
- package/dist/acp.js +1 -1
- package/dist/{agent-Cch2ayTt.js → agent-DkSmGkDM.js} +10871 -5411
- package/dist/agent-DkSmGkDM.js.map +1 -0
- package/dist/agent.d.ts +1 -2
- package/dist/agent.js +2 -2
- package/dist/{anthropic-RGqsJlC8.js → anthropic-CTHsdFG9.js} +46 -6
- package/dist/anthropic-CTHsdFG9.js.map +1 -0
- package/dist/{auth-C-ms5N2x.js → auth-BDSu_0t3.js} +13 -86
- package/dist/auth-BDSu_0t3.js.map +1 -0
- package/dist/chat/pure.d.ts +4 -4
- package/dist/chat/pure.js +4 -3
- package/dist/chat.d.ts +45 -8
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +9 -6
- package/dist/chat.js.map +1 -1
- package/dist/completion-core-CXYSVhZo.js +147 -0
- package/dist/completion-core-CXYSVhZo.js.map +1 -0
- package/dist/{context-breakdown-kO-pDsay.js → context-breakdown-Dzo25N45.js} +2 -23
- package/dist/context-breakdown-Dzo25N45.js.map +1 -0
- package/dist/contexts/daytona.d.ts +3 -3
- package/dist/contexts/daytona.js +1 -1
- package/dist/contexts/docker.d.ts +10 -2
- package/dist/contexts/docker.d.ts.map +1 -1
- package/dist/contexts/docker.js +142 -103
- package/dist/contexts/docker.js.map +1 -1
- package/dist/contexts/e2b.d.ts +2 -2
- package/dist/contexts/e2b.js +1 -1
- package/dist/contexts/sandbox.d.ts +1 -1
- package/dist/contexts/sandbox.js +4 -1
- package/dist/contexts/sandbox.js.map +1 -1
- package/dist/{contexts-Lzf5huID.js → contexts-Vone3qQQ.js} +26 -37
- package/dist/contexts-Vone3qQQ.js.map +1 -0
- package/dist/contexts.d.ts +3 -3
- package/dist/contexts.js +1 -1
- package/dist/errors.d.ts +1 -2
- package/dist/errors.js +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +3 -3
- package/dist/exec-file-size-PXkr-MGA.js +63 -0
- package/dist/exec-file-size-PXkr-MGA.js.map +1 -0
- package/dist/extensions.d.ts +2 -0
- package/dist/extensions.js +2 -0
- package/dist/generate-data-WL_yGVL6.js +324 -0
- package/dist/generate-data-WL_yGVL6.js.map +1 -0
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +15 -10
- package/dist/headless.js.map +1 -1
- package/dist/index-B8UtiZy7.d.ts +14967 -0
- package/dist/index-B8UtiZy7.d.ts.map +1 -0
- package/dist/{index-B3ciFUZx.d.ts → index-DsPQbyUn.d.ts} +2 -2
- package/dist/index-DsPQbyUn.d.ts.map +1 -0
- package/dist/index.d.ts +6 -12
- package/dist/index.js +20 -43
- package/dist/index.js.map +1 -1
- package/dist/{logger-Ktm-lj1s.js → logger-DBX9uYOw.js} +108 -5
- package/dist/logger-DBX9uYOw.js.map +1 -0
- package/dist/login-DOF8pMVM.js +539 -0
- package/dist/login-DOF8pMVM.js.map +1 -0
- package/dist/{mcp-Dn5W65Lv.js → mcp-CcvuVXB9.js} +2 -2
- package/dist/{mcp-Dn5W65Lv.js.map → mcp-CcvuVXB9.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/{messages-YZQLKaz2.js → messages-CwujiS74.js} +4 -4
- package/dist/messages-CwujiS74.js.map +1 -0
- package/dist/{openai-compat-0Y7jcncn.js → openai-compat-BvgYGSR1.js} +7 -6
- package/dist/{openai-compat-0Y7jcncn.js.map → openai-compat-BvgYGSR1.js.map} +1 -1
- package/dist/output/stream-json.d.ts +1 -2
- package/dist/output/stream-json.d.ts.map +1 -1
- package/dist/output/terminal.d.ts +1 -2
- package/dist/output/terminal.d.ts.map +1 -1
- package/dist/{glob-DluQFSw1.js → output-cap-kBUHBlnq.js} +50 -2
- package/dist/output-cap-kBUHBlnq.js.map +1 -0
- package/dist/presets.d.ts +1 -2
- package/dist/presets.js +1 -1
- package/dist/providers/anthropic.d.ts +2 -2
- package/dist/providers/anthropic.js +2 -2
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/arcee.js +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/baseten.js +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/cerebras.js +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/local.js +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai-compat.js +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +4 -4
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/openrouter.js +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +1 -1
- package/dist/{providers-DgmdYGKm.js → providers-z6LDMAr3.js} +3 -3
- package/dist/{providers-DgmdYGKm.js.map → providers-z6LDMAr3.js.map} +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +4 -4
- package/dist/{read-state-BymF41Jb.js → read-state-Dq_TXUX1.js} +2 -2
- package/dist/{read-state-BymF41Jb.js.map → read-state-Dq_TXUX1.js.map} +1 -1
- package/dist/{interpolate-BtIgcCuz.js → resolve-Cr8JSL_O.js} +562 -136
- package/dist/resolve-Cr8JSL_O.js.map +1 -0
- package/dist/restate.d.ts +2 -2
- package/dist/restate.js +2 -2
- package/dist/{sandbox-Dgy-m6UF.d.ts → sandbox-CgjG2VvQ.d.ts} +2 -2
- package/dist/{sandbox-Dgy-m6UF.d.ts.map → sandbox-CgjG2VvQ.d.ts.map} +1 -1
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/{session-CqrAFAKJ.js → session-CsdDIPY8.js} +108 -3
- package/dist/session-CsdDIPY8.js.map +1 -0
- package/dist/session.d.ts +2 -2
- package/dist/session.js +3 -3
- package/dist/skills-Bsb0rvWI.js +295 -0
- package/dist/skills-Bsb0rvWI.js.map +1 -0
- package/dist/skills.d.ts +2 -3
- package/dist/skills.js +3 -24
- package/dist/{errors-CkTvg97v.d.ts → timeout-8vSIRjzl.d.ts} +10 -2
- package/dist/timeout-8vSIRjzl.d.ts.map +1 -0
- package/dist/{timeout-kGGSXagK.js → timeout-BDetbuN6.js} +59 -2
- package/dist/timeout-BDetbuN6.js.map +1 -0
- package/dist/tool-formatters-DY0TDb1r.d.ts +378 -0
- package/dist/tool-formatters-DY0TDb1r.d.ts.map +1 -0
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools.d.ts +2 -3
- package/dist/tools.js +3 -4
- package/dist/{transcript-anchors-BHIgMlPq.d.ts → transcript-anchors-Cb-bDbNH.d.ts} +233 -640
- package/dist/transcript-anchors-Cb-bDbNH.d.ts.map +1 -0
- package/dist/{transcript-anchors-DsoBHg43.js → transcript-anchors-DSRPWjiH.js} +391 -961
- package/dist/transcript-anchors-DSRPWjiH.js.map +1 -0
- package/dist/tui.d.ts +335 -11
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +6375 -1892
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-Dw02ZoSQ.d.ts → turn-operations-CgMROLsr.d.ts} +29 -4
- package/dist/turn-operations-CgMROLsr.d.ts.map +1 -0
- package/dist/{turn-operations-tG0vuBNc.js → turn-operations-Dq9Kx7m2.js} +321 -81
- package/dist/turn-operations-Dq9Kx7m2.js.map +1 -0
- package/dist/{types-BqdTeBaC.d.ts → types-BDccavwj.d.ts} +42 -1
- package/dist/types-BDccavwj.d.ts.map +1 -0
- package/dist/{types-CyVGdbia.js → types-BIarq1qE.js} +90 -2
- package/dist/types-BIarq1qE.js.map +1 -0
- package/dist/types.d.ts +5 -6
- package/dist/types.js +1 -1
- package/dist/{xai-tPdbAGkq.js → xai-B_e6TOA8.js} +2 -2
- package/dist/{xai-tPdbAGkq.js.map → xai-B_e6TOA8.js.map} +1 -1
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/CHAT.md +7 -6
- package/docs/EXECUTION_CONTEXT.md +16 -0
- package/docs/EXTENSIONS.md +1063 -0
- package/docs/SKILL.md +16 -0
- package/docs/TUI.md +10 -3
- package/package.json +12 -3
- package/dist/agent-Cch2ayTt.js.map +0 -1
- package/dist/agent-EFWt6uQ_.d.ts +0 -6914
- package/dist/agent-EFWt6uQ_.d.ts.map +0 -1
- package/dist/anthropic-RGqsJlC8.js.map +0 -1
- package/dist/auth-C-ms5N2x.js.map +0 -1
- package/dist/context-breakdown-kO-pDsay.js.map +0 -1
- package/dist/contexts-Lzf5huID.js.map +0 -1
- package/dist/errors-CkTvg97v.d.ts.map +0 -1
- package/dist/glob-DluQFSw1.js.map +0 -1
- package/dist/glob-shell-rJMoCIGb.js +0 -21
- package/dist/glob-shell-rJMoCIGb.js.map +0 -1
- package/dist/index-B3ciFUZx.d.ts.map +0 -1
- package/dist/index-BtzE3Uaq.d.ts +0 -2738
- package/dist/index-BtzE3Uaq.d.ts.map +0 -1
- package/dist/index-tSzOaWNt.d.ts +0 -316
- package/dist/index-tSzOaWNt.d.ts.map +0 -1
- package/dist/interpolate-BtIgcCuz.js.map +0 -1
- package/dist/logger-DGiGf7DW.d.ts +0 -102
- package/dist/logger-DGiGf7DW.d.ts.map +0 -1
- package/dist/logger-Ktm-lj1s.js.map +0 -1
- package/dist/login-CCA-1lgK.js +0 -267
- package/dist/login-CCA-1lgK.js.map +0 -1
- package/dist/messages-YZQLKaz2.js.map +0 -1
- package/dist/policy-DcGlpaNs.d.ts +0 -129
- package/dist/policy-DcGlpaNs.d.ts.map +0 -1
- package/dist/presets-BQWYMHdd.js +0 -114
- package/dist/presets-BQWYMHdd.js.map +0 -1
- package/dist/run-summary-DkN8KsHM.d.ts +0 -249
- package/dist/run-summary-DkN8KsHM.d.ts.map +0 -1
- package/dist/session-CqrAFAKJ.js.map +0 -1
- package/dist/shell-quote-BmnhZmdM.js +0 -33
- package/dist/shell-quote-BmnhZmdM.js.map +0 -1
- package/dist/skills.js.map +0 -1
- package/dist/timeout-CpFm0jJ6.d.ts +0 -10
- package/dist/timeout-CpFm0jJ6.d.ts.map +0 -1
- package/dist/timeout-kGGSXagK.js.map +0 -1
- package/dist/tool-formatters-C6iLgc9f.d.ts +0 -1454
- package/dist/tool-formatters-C6iLgc9f.d.ts.map +0 -1
- package/dist/tools-CCd8e-HT.js +0 -1739
- package/dist/tools-CCd8e-HT.js.map +0 -1
- package/dist/transcript-anchors-BHIgMlPq.d.ts.map +0 -1
- package/dist/transcript-anchors-DsoBHg43.js.map +0 -1
- package/dist/turn-operations-Dw02ZoSQ.d.ts.map +0 -1
- package/dist/turn-operations-tG0vuBNc.js.map +0 -1
- package/dist/types-BqdTeBaC.d.ts.map +0 -1
- package/dist/types-CyVGdbia.js.map +0 -1
package/dist/contexts/docker.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import {
|
|
1
|
+
import { i as outputExceededMessage, n as appendCapped, t as DEFAULT_MAX_OUTPUT_BUFFER } from "../output-cap-kBUHBlnq.js";
|
|
2
|
+
import { i as globFilesViaShell, n as alwaysQuote, t as fileSizeViaExec } from "../exec-file-size-PXkr-MGA.js";
|
|
3
|
+
import { Buffer } from "node:buffer";
|
|
3
4
|
//#region src/contexts/docker.ts
|
|
5
|
+
/**
|
|
6
|
+
* `readFile` transport cap: headroom above the whole-read tool ceiling
|
|
7
|
+
* (256MB in tools/whole-read-guard.ts) so tool policy — not the docker
|
|
8
|
+
* transport — decides read limits, keeping refusal behavior consistent
|
|
9
|
+
* across contexts. Still bounded: a probe-failure path can't buffer
|
|
10
|
+
* more than this into host memory.
|
|
11
|
+
*/
|
|
12
|
+
const READ_FILE_MAX_BUFFER = 512 * 1024 * 1024;
|
|
4
13
|
const SINGLE_QUOTE_RE = /'/g;
|
|
5
14
|
/**
|
|
6
15
|
* dockerode surfaces HTTP errors with a `statusCode`. 304 = container
|
|
@@ -27,6 +36,131 @@ function createDockerContext(config) {
|
|
|
27
36
|
throw new Error("dockerode is required for Docker execution context. Install it with: bun add dockerode");
|
|
28
37
|
}
|
|
29
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* The exec transport with an explicit output cap. The public `exec`
|
|
41
|
+
* uses the shared 10MB default; `readFile` passes headroom above the
|
|
42
|
+
* whole-read tool ceiling so docker's EFFECTIVE read limit matches
|
|
43
|
+
* every other context (the fileSize probe refuses oversized files
|
|
44
|
+
* before this ever runs — the transport cap is the safety net for
|
|
45
|
+
* probe-failure paths, not the primary bound).
|
|
46
|
+
*/
|
|
47
|
+
async function execCapped(handle, command, options, maxBuffer) {
|
|
48
|
+
const ref = containers.get(handle.id);
|
|
49
|
+
if (!ref) throw new Error(`Container ${handle.id} not found`);
|
|
50
|
+
const execCwd = options?.cwd ?? handle.cwd;
|
|
51
|
+
const env = options?.env ? Object.entries(options.env).map(([k, v]) => `${k}=${v}`) : [];
|
|
52
|
+
const timeout = options?.timeout ?? defaultLimits?.timeout ?? 30;
|
|
53
|
+
const exec = await ref.container.exec({
|
|
54
|
+
Cmd: [
|
|
55
|
+
"timeout",
|
|
56
|
+
String(timeout),
|
|
57
|
+
"sh",
|
|
58
|
+
"-c",
|
|
59
|
+
command
|
|
60
|
+
],
|
|
61
|
+
WorkingDir: execCwd,
|
|
62
|
+
Env: env,
|
|
63
|
+
AttachStdout: true,
|
|
64
|
+
AttachStderr: true
|
|
65
|
+
});
|
|
66
|
+
const stream = await exec.start({ Detach: false });
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
const stdoutSlot = {
|
|
69
|
+
text: "",
|
|
70
|
+
bytes: 0
|
|
71
|
+
};
|
|
72
|
+
const stderrSlot = {
|
|
73
|
+
text: "",
|
|
74
|
+
bytes: 0
|
|
75
|
+
};
|
|
76
|
+
let resolved = false;
|
|
77
|
+
const overflow = () => {
|
|
78
|
+
if (resolved) return;
|
|
79
|
+
resolved = true;
|
|
80
|
+
try {
|
|
81
|
+
stream.destroy?.();
|
|
82
|
+
} catch {}
|
|
83
|
+
resolve({
|
|
84
|
+
stdout: stdoutSlot.text,
|
|
85
|
+
stderr: `${stderrSlot.text ? `${stderrSlot.text}\n` : ""}${outputExceededMessage(maxBuffer, "stream detached (server-side timeout reaps the process)")}`,
|
|
86
|
+
exitCode: 143
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
const cappedWrite = (slot) => (chunk) => {
|
|
90
|
+
if (appendCapped(slot, typeof chunk === "string" ? Buffer.from(chunk, "utf-8") : chunk, maxBuffer) !== "ok") overflow();
|
|
91
|
+
return true;
|
|
92
|
+
};
|
|
93
|
+
const stdoutSink = {
|
|
94
|
+
write: cappedWrite(stdoutSlot),
|
|
95
|
+
end() {},
|
|
96
|
+
on() {},
|
|
97
|
+
once() {},
|
|
98
|
+
emit() {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const stderrSink = {
|
|
103
|
+
write: cappedWrite(stderrSlot),
|
|
104
|
+
end() {},
|
|
105
|
+
on() {},
|
|
106
|
+
once() {},
|
|
107
|
+
emit() {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
try {
|
|
112
|
+
ref.docker.modem.demuxStream(stream, stdoutSink, stderrSink);
|
|
113
|
+
} catch {
|
|
114
|
+
stream.on("data", (chunk) => {
|
|
115
|
+
cappedWrite(stdoutSlot)(chunk);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const timer = setTimeout(() => {
|
|
119
|
+
if (resolved) return;
|
|
120
|
+
resolved = true;
|
|
121
|
+
try {
|
|
122
|
+
stream.destroy?.();
|
|
123
|
+
} catch {}
|
|
124
|
+
resolve({
|
|
125
|
+
stdout: stdoutSlot.text,
|
|
126
|
+
stderr: stderrSlot.text ? `${stderrSlot.text}\n[timeout]` : "[timeout]",
|
|
127
|
+
exitCode: 124
|
|
128
|
+
});
|
|
129
|
+
}, timeout * 1e3 + 1e3);
|
|
130
|
+
stream.on("end", async () => {
|
|
131
|
+
if (resolved) return;
|
|
132
|
+
resolved = true;
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
let exitCode;
|
|
135
|
+
let inspectNote = "";
|
|
136
|
+
try {
|
|
137
|
+
const inspect = await exec.inspect();
|
|
138
|
+
if (inspect.ExitCode == null) {
|
|
139
|
+
exitCode = -1;
|
|
140
|
+
inspectNote = "[exit code unavailable: exec still marked running by daemon]";
|
|
141
|
+
} else exitCode = inspect.ExitCode;
|
|
142
|
+
} catch (err) {
|
|
143
|
+
exitCode = -1;
|
|
144
|
+
inspectNote = `[exit code unavailable: exec inspect failed: ${err?.message ?? String(err)}]`;
|
|
145
|
+
}
|
|
146
|
+
resolve({
|
|
147
|
+
stdout: stdoutSlot.text,
|
|
148
|
+
stderr: inspectNote ? stderrSlot.text ? `${stderrSlot.text}\n${inspectNote}` : inspectNote : stderrSlot.text,
|
|
149
|
+
exitCode
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
stream.on("error", (err) => {
|
|
153
|
+
if (resolved) return;
|
|
154
|
+
resolved = true;
|
|
155
|
+
clearTimeout(timer);
|
|
156
|
+
resolve({
|
|
157
|
+
stdout: stdoutSlot.text,
|
|
158
|
+
stderr: stderrSlot.text ? `${stderrSlot.text}\n${err.message}` : err.message,
|
|
159
|
+
exitCode: 1
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
30
164
|
const ctx = {
|
|
31
165
|
type: "docker",
|
|
32
166
|
capabilities: {
|
|
@@ -124,108 +258,13 @@ function createDockerContext(config) {
|
|
|
124
258
|
return handle;
|
|
125
259
|
},
|
|
126
260
|
async exec(handle, command, options) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const timeout = options?.timeout ?? defaultLimits?.timeout ?? 30;
|
|
132
|
-
const exec = await ref.container.exec({
|
|
133
|
-
Cmd: [
|
|
134
|
-
"timeout",
|
|
135
|
-
String(timeout),
|
|
136
|
-
"sh",
|
|
137
|
-
"-c",
|
|
138
|
-
command
|
|
139
|
-
],
|
|
140
|
-
WorkingDir: execCwd,
|
|
141
|
-
Env: env,
|
|
142
|
-
AttachStdout: true,
|
|
143
|
-
AttachStderr: true
|
|
144
|
-
});
|
|
145
|
-
const stream = await exec.start({ Detach: false });
|
|
146
|
-
return new Promise((resolve) => {
|
|
147
|
-
let stdout = "";
|
|
148
|
-
let stderr = "";
|
|
149
|
-
let resolved = false;
|
|
150
|
-
const stdoutSink = {
|
|
151
|
-
write(chunk) {
|
|
152
|
-
stdout += typeof chunk === "string" ? chunk : chunk.toString("utf-8");
|
|
153
|
-
return true;
|
|
154
|
-
},
|
|
155
|
-
end() {},
|
|
156
|
-
on() {},
|
|
157
|
-
once() {},
|
|
158
|
-
emit() {
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
const stderrSink = {
|
|
163
|
-
write(chunk) {
|
|
164
|
-
stderr += typeof chunk === "string" ? chunk : chunk.toString("utf-8");
|
|
165
|
-
return true;
|
|
166
|
-
},
|
|
167
|
-
end() {},
|
|
168
|
-
on() {},
|
|
169
|
-
once() {},
|
|
170
|
-
emit() {
|
|
171
|
-
return true;
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
try {
|
|
175
|
-
ref.docker.modem.demuxStream(stream, stdoutSink, stderrSink);
|
|
176
|
-
} catch {
|
|
177
|
-
stream.on("data", (chunk) => {
|
|
178
|
-
stdout += chunk.toString("utf-8");
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
const timer = setTimeout(() => {
|
|
182
|
-
if (resolved) return;
|
|
183
|
-
resolved = true;
|
|
184
|
-
try {
|
|
185
|
-
stream.destroy?.();
|
|
186
|
-
} catch {}
|
|
187
|
-
resolve({
|
|
188
|
-
stdout,
|
|
189
|
-
stderr: stderr ? `${stderr}\n[timeout]` : "[timeout]",
|
|
190
|
-
exitCode: 124
|
|
191
|
-
});
|
|
192
|
-
}, timeout * 1e3 + 1e3);
|
|
193
|
-
stream.on("end", async () => {
|
|
194
|
-
if (resolved) return;
|
|
195
|
-
resolved = true;
|
|
196
|
-
clearTimeout(timer);
|
|
197
|
-
let exitCode;
|
|
198
|
-
let inspectNote = "";
|
|
199
|
-
try {
|
|
200
|
-
const inspect = await exec.inspect();
|
|
201
|
-
if (inspect.ExitCode == null) {
|
|
202
|
-
exitCode = -1;
|
|
203
|
-
inspectNote = "[exit code unavailable: exec still marked running by daemon]";
|
|
204
|
-
} else exitCode = inspect.ExitCode;
|
|
205
|
-
} catch (err) {
|
|
206
|
-
exitCode = -1;
|
|
207
|
-
inspectNote = `[exit code unavailable: exec inspect failed: ${err?.message ?? String(err)}]`;
|
|
208
|
-
}
|
|
209
|
-
resolve({
|
|
210
|
-
stdout,
|
|
211
|
-
stderr: inspectNote ? stderr ? `${stderr}\n${inspectNote}` : inspectNote : stderr,
|
|
212
|
-
exitCode
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
stream.on("error", (err) => {
|
|
216
|
-
if (resolved) return;
|
|
217
|
-
resolved = true;
|
|
218
|
-
clearTimeout(timer);
|
|
219
|
-
resolve({
|
|
220
|
-
stdout,
|
|
221
|
-
stderr: stderr ? `${stderr}\n${err.message}` : err.message,
|
|
222
|
-
exitCode: 1
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
});
|
|
261
|
+
return execCapped(handle, command, options, DEFAULT_MAX_OUTPUT_BUFFER);
|
|
262
|
+
},
|
|
263
|
+
async fileSize(handle, path) {
|
|
264
|
+
return fileSizeViaExec((command) => execCapped(handle, command, void 0, DEFAULT_MAX_OUTPUT_BUFFER), path);
|
|
226
265
|
},
|
|
227
266
|
async readFile(handle, path) {
|
|
228
|
-
const result = await
|
|
267
|
+
const result = await execCapped(handle, `cat ${alwaysQuote(path)}`, void 0, READ_FILE_MAX_BUFFER);
|
|
229
268
|
if (result.exitCode !== 0) throw new Error(`Failed to read file: ${result.stderr}`);
|
|
230
269
|
return result.stdout;
|
|
231
270
|
},
|
|
@@ -286,6 +325,6 @@ function buildBindSpecs(mounts) {
|
|
|
286
325
|
return mounts.map(formatBindMount);
|
|
287
326
|
}
|
|
288
327
|
//#endregion
|
|
289
|
-
export { buildBindSpecs, createDockerContext };
|
|
328
|
+
export { READ_FILE_MAX_BUFFER, buildBindSpecs, createDockerContext };
|
|
290
329
|
|
|
291
330
|
//# sourceMappingURL=docker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docker.js","names":[],"sources":["../../src/contexts/docker.ts"],"sourcesContent":["/**\n * Docker execution context.\n *\n * Runs tools inside a Docker container via dockerode.\n * Full isolation with configurable resource limits.\n *\n * Requires `dockerode` as an optional peer dependency.\n */\n\nimport type { Buffer } from 'node:buffer'\nimport type { ContextCapabilities, ExecResult, ExecutionContext, ExecutionHandle, ListFilesEntry, SpawnConfig } from './types'\nimport { alwaysQuote } from '../tools/shell-quote'\nimport { globFilesViaShell } from './glob'\n\nconst SINGLE_QUOTE_RE = /'/g\n\n/**\n * dockerode surfaces HTTP errors with a `statusCode`. 304 = container\n * already stopped, 404 = already gone — both are fine during teardown.\n */\nfunction isGoneOrAlreadyStopped(err: unknown): boolean {\n const code = (err as { statusCode?: number } | null)?.statusCode\n return code === 304 || code === 404\n}\n\ninterface ContainerRef {\n handle: ExecutionHandle\n container: any\n docker: any\n}\n\nexport function createDockerContext(config?: SpawnConfig): ExecutionContext {\n let counter = 0\n const containers = new Map<string, ContainerRef>()\n const defaultImage = config?.image ?? 'oven/bun:latest'\n const defaultCwd = config?.cwd ?? '/workspace'\n const defaultEnv = config?.env\n const defaultLimits = config?.limits\n const defaultMounts = config?.mounts ?? []\n const defaultName = config?.name\n\n async function getDockerode() {\n try {\n const Dockerode = (await import('dockerode')).default\n return new Dockerode()\n }\n catch {\n throw new Error('dockerode is required for Docker execution context. Install it with: bun add dockerode')\n }\n }\n\n const ctx: ExecutionContext = {\n type: 'docker',\n\n capabilities: {\n shell: true,\n filesystem: true,\n glob: true,\n network: true,\n gpu: false,\n } satisfies ContextCapabilities,\n\n async spawn(overrides?: SpawnConfig): Promise<ExecutionHandle> {\n const docker = await getDockerode()\n const id = `docker-${++counter}`\n const image = overrides?.image ?? defaultImage\n const cwd = overrides?.cwd ?? defaultCwd\n const mounts = [...defaultMounts, ...(overrides?.mounts ?? [])]\n const name = overrides?.name ?? defaultName\n\n // Pull image if not available\n try {\n await docker.getImage(image).inspect()\n }\n catch {\n await new Promise<void>((resolve, reject) => {\n docker.pull(image, (err: Error | null, stream: NodeJS.ReadableStream) => {\n if (err)\n return reject(err)\n docker.modem.followProgress(stream, (err2: Error | null) => {\n err2 ? reject(err2) : resolve()\n })\n })\n })\n }\n\n const limits = { ...defaultLimits, ...overrides?.limits }\n const hostConfig: Record<string, unknown> = {}\n\n if (limits?.memory) {\n hostConfig.Memory = limits.memory * 1024 * 1024\n }\n if (limits?.cpu) {\n hostConfig.NanoCpus = Number.parseFloat(limits.cpu) * 1e9\n }\n\n // Opt-in hardening (all OFF by default — see ContextHardening).\n const hardening = { ...config?.hardening, ...overrides?.hardening }\n if (hardening.dropAllCapabilities)\n hostConfig.CapDrop = ['ALL']\n if (hardening.noNewPrivileges)\n hostConfig.SecurityOpt = ['no-new-privileges']\n if (hardening.readonlyRootfs)\n hostConfig.ReadonlyRootfs = true\n if (hardening.pidsLimit != null)\n hostConfig.PidsLimit = hardening.pidsLimit\n\n const env = { ...defaultEnv, ...overrides?.env }\n\n const bindSpecs = buildBindSpecs(mounts)\n if (bindSpecs.length > 0) {\n hostConfig.Binds = bindSpecs\n }\n\n const portSpec = overrides?.ports ?? config?.ports\n const exposedPorts: Record<string, Record<string, never>> = {}\n const portBindings: Record<string, Array<{ HostPort: string }>> = {}\n\n if (portSpec?.length) {\n for (const p of portSpec) {\n const key = `${p.container}/${p.proto ?? 'tcp'}`\n exposedPorts[key] = {}\n portBindings[key] = [{ HostPort: p.host == null ? '' : String(p.host) }]\n }\n hostConfig.PortBindings = portBindings\n }\n\n const user = overrides?.user ?? config?.user\n const network = overrides?.network ?? config?.network\n if (network) {\n hostConfig.NetworkMode = network\n }\n\n const labels = { ...config?.labels, ...overrides?.labels }\n\n const container = await docker.createContainer({\n // Suffix with a short random token so a crashed run's leftover container\n // (same prefix) never blocks a fresh one with HTTP 409 name conflict.\n ...(name ? { name: `${name}-${counter}-${Math.random().toString(36).slice(2, 8)}` } : {}),\n Image: image,\n Cmd: ['sleep', 'infinity'],\n WorkingDir: cwd,\n Env: Object.entries(env).map(([k, v]) => `${k}=${v}`),\n HostConfig: hostConfig,\n ...(Object.keys(exposedPorts).length ? { ExposedPorts: exposedPorts } : {}),\n ...(user ? { User: user } : {}),\n ...(Object.keys(labels).length ? { Labels: labels } : {}),\n })\n\n try {\n await container.start()\n }\n catch (err) {\n // The container was created but never started — remove it so a\n // failed spawn doesn't leak a stopped container on the daemon.\n await container.remove({ force: true }).catch(() => {})\n throw err\n }\n\n const handle: ExecutionHandle = { id, type: 'docker', cwd }\n containers.set(id, { handle, container, docker })\n\n return handle\n },\n\n async exec(handle: ExecutionHandle, command: string, options?: { cwd?: string, env?: Record<string, string>, timeout?: number }): Promise<ExecResult> {\n const ref = containers.get(handle.id)\n if (!ref)\n throw new Error(`Container ${handle.id} not found`)\n\n const execCwd = options?.cwd ?? handle.cwd\n const env = options?.env\n ? Object.entries(options.env).map(([k, v]) => `${k}=${v}`)\n : []\n\n const timeout = options?.timeout ?? defaultLimits?.timeout ?? 30\n\n const exec = await ref.container.exec({\n // Wrap with coreutils `timeout` so a runaway command is killed\n // server-side too — the host-side timer below only detaches the\n // stream; without this the process would keep running in the\n // container until destroy(). Exec-array form avoids re-quoting the\n // command. `timeout` exits 124 on expiry, matching the host\n // sentinel. (Default image oven/bun is Debian-based → coreutils.)\n Cmd: ['timeout', String(timeout), 'sh', '-c', command],\n WorkingDir: execCwd,\n Env: env,\n AttachStdout: true,\n AttachStderr: true,\n })\n\n const stream = await exec.start({ Detach: false })\n\n return new Promise<ExecResult>((resolve) => {\n let stdout = ''\n let stderr = ''\n let resolved = false\n\n // Docker's exec stream is a multiplexed protocol — a single byte\n // stream that interleaves stdout/stderr frames. `demuxStream`\n // splits the two onto separate writable sinks. Without it, every\n // byte landed on `stdout` and `stderr` was permanently empty.\n const stdoutSink = {\n write(chunk: Buffer | string) {\n stdout += typeof chunk === 'string' ? chunk : chunk.toString('utf-8')\n return true\n },\n end() {},\n on() {},\n once() {},\n emit() { return true },\n }\n const stderrSink = {\n write(chunk: Buffer | string) {\n stderr += typeof chunk === 'string' ? chunk : chunk.toString('utf-8')\n return true\n },\n end() {},\n on() {},\n once() {},\n emit() { return true },\n }\n try {\n ref.docker.modem.demuxStream(stream, stdoutSink, stderrSink)\n }\n catch {\n // Older / different transports may not be multiplexed (TTY mode);\n // fall back to a raw stdout reader so we don't lose output entirely.\n stream.on('data', (chunk: Buffer) => {\n stdout += chunk.toString('utf-8')\n })\n }\n\n // Host-side fallback timer. The server-side `timeout` wrapper is\n // the primary kill mechanism; give it a grace second so the\n // stream-end path (which carries the real exit code) usually wins.\n const timer = setTimeout(() => {\n if (resolved)\n return\n resolved = true\n // Best-effort: detach the stream so we stop accumulating bytes.\n // The server-side `timeout` wrapper kills the process itself.\n try { stream.destroy?.() }\n catch { /* swallow */ }\n resolve({ stdout, stderr: stderr ? `${stderr}\\n[timeout]` : '[timeout]', exitCode: 124 })\n }, timeout * 1000 + 1000)\n\n stream.on('end', async () => {\n if (resolved)\n return\n resolved = true\n clearTimeout(timer)\n let exitCode: number\n let inspectNote = ''\n try {\n const inspect = await exec.inspect()\n // `ExitCode: null` means Docker hasn't recorded an exit yet —\n // the outcome is unknown, not success. Use a sentinel so\n // callers don't mistake it for a clean exit.\n if (inspect.ExitCode == null) {\n exitCode = -1\n inspectNote = '[exit code unavailable: exec still marked running by daemon]'\n }\n else {\n exitCode = inspect.ExitCode\n }\n }\n catch (err) {\n exitCode = -1\n inspectNote = `[exit code unavailable: exec inspect failed: ${(err as Error)?.message ?? String(err)}]`\n }\n resolve({\n stdout,\n stderr: inspectNote ? (stderr ? `${stderr}\\n${inspectNote}` : inspectNote) : stderr,\n exitCode,\n })\n })\n\n stream.on('error', (err: Error) => {\n if (resolved)\n return\n resolved = true\n clearTimeout(timer)\n resolve({ stdout, stderr: stderr ? `${stderr}\\n${err.message}` : err.message, exitCode: 1 })\n })\n })\n },\n\n // Paths are model-supplied — single-quote them (`alwaysQuote`) rather\n // than JSON.stringify: `$(…)` and backticks still expand inside the\n // double quotes JSON produces, which was a command-injection vector.\n async readFile(handle: ExecutionHandle, path: string): Promise<string> {\n const result = await ctx.exec(handle, `cat ${alwaysQuote(path)}`)\n if (result.exitCode !== 0)\n throw new Error(`Failed to read file: ${result.stderr}`)\n return result.stdout\n },\n\n async writeFile(handle: ExecutionHandle, path: string, content: string): Promise<void> {\n const escaped = content.replace(SINGLE_QUOTE_RE, String.raw`'\\''`)\n const result = await ctx.exec(handle, `mkdir -p \"$(dirname ${alwaysQuote(path)})\" && printf '%s' '${escaped}' > ${alwaysQuote(path)}`)\n if (result.exitCode !== 0)\n throw new Error(`Failed to write file: ${result.stderr}`)\n },\n\n async listFiles(handle: ExecutionHandle, path: string, options): Promise<ListFilesEntry[]> {\n if (options?.glob)\n return globFilesViaShell(path, command => ctx.exec(handle, command), options?.limit ?? 1000)\n const result = await ctx.exec(handle, `ls -1 ${alwaysQuote(path)}`)\n if (result.exitCode !== 0)\n return []\n return result.stdout.trim().split('\\n').filter(Boolean)\n },\n\n async getMappedPort(handle: ExecutionHandle, containerPort: number): Promise<number | null> {\n const ref = containers.get(handle.id)\n if (!ref)\n return null\n\n const info = await ref.container.inspect()\n const bindings = info?.NetworkSettings?.Ports ?? {}\n\n for (const proto of ['tcp', 'udp']) {\n const list = bindings[`${containerPort}/${proto}`]\n if (Array.isArray(list) && list.length > 0 && list[0]?.HostPort) {\n return Number.parseInt(list[0].HostPort, 10)\n }\n }\n\n return null\n },\n\n async destroy(handle: ExecutionHandle): Promise<void> {\n const ref = containers.get(handle.id)\n if (!ref)\n return\n\n try {\n try {\n await ref.container.stop({ t: 5 })\n }\n catch {\n // 304 (already stopped) / 404 (already gone) are expected here.\n // Any other stop failure must not prevent the remove({force})\n // below — force removal also works on running containers.\n }\n try {\n await ref.container.remove({ force: true })\n }\n catch (err) {\n if (!isGoneOrAlreadyStopped(err))\n throw err\n }\n }\n finally {\n containers.delete(handle.id)\n }\n },\n }\n\n return ctx\n}\n\n/**\n * Format one {@link ContextMount} as a dockerode `HostConfig.Binds` entry.\n * Supports read-only (`:ro`) and SELinux shared-label (`:z`) options; the two\n * are mutually exclusive (a read-only mount can't also be a shared rw mount).\n */\nfunction formatBindMount(mount: NonNullable<SpawnConfig['mounts']>[number]): string {\n if (!mount.source)\n throw new Error('Docker mount source is required')\n if (!mount.target || !mount.target.startsWith('/'))\n throw new Error(`Docker mount target must be an absolute path: ${mount.target}`)\n if (mount.readonly && mount.shared)\n throw new Error(`Docker mount cannot be both readonly and shared: ${mount.target}`)\n const opts = mount.readonly ? ':ro' : mount.shared ? ':z' : ''\n return `${mount.source}:${mount.target}${opts}`\n}\n\n/** Assemble dockerode `HostConfig.Binds` from the context's `mounts`. */\nexport function buildBindSpecs(mounts: NonNullable<SpawnConfig['mounts']>): string[] {\n return mounts.map(formatBindMount)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"docker.js","names":[],"sources":["../../src/contexts/docker.ts"],"sourcesContent":["/**\n * Docker execution context.\n *\n * Runs tools inside a Docker container via dockerode.\n * Full isolation with configurable resource limits.\n *\n * Requires `dockerode` as an optional peer dependency.\n */\n\nimport type { CappedSlot } from './output-cap'\nimport type { ContextCapabilities, ExecResult, ExecutionContext, ExecutionHandle, ListFilesEntry, SpawnConfig } from './types'\nimport { Buffer } from 'node:buffer'\nimport { alwaysQuote } from '../tools/shell-quote'\nimport { fileSizeViaExec } from './exec-file-size'\nimport { globFilesViaShell } from './glob'\nimport { appendCapped, DEFAULT_MAX_OUTPUT_BUFFER, outputExceededMessage } from './output-cap'\n\n/**\n * `readFile` transport cap: headroom above the whole-read tool ceiling\n * (256MB in tools/whole-read-guard.ts) so tool policy — not the docker\n * transport — decides read limits, keeping refusal behavior consistent\n * across contexts. Still bounded: a probe-failure path can't buffer\n * more than this into host memory.\n */\nexport const READ_FILE_MAX_BUFFER = 512 * 1024 * 1024\n\nconst SINGLE_QUOTE_RE = /'/g\n\n/**\n * dockerode surfaces HTTP errors with a `statusCode`. 304 = container\n * already stopped, 404 = already gone — both are fine during teardown.\n */\nfunction isGoneOrAlreadyStopped(err: unknown): boolean {\n const code = (err as { statusCode?: number } | null)?.statusCode\n return code === 304 || code === 404\n}\n\ninterface ContainerRef {\n handle: ExecutionHandle\n container: any\n docker: any\n}\n\nexport function createDockerContext(config?: SpawnConfig): ExecutionContext {\n let counter = 0\n const containers = new Map<string, ContainerRef>()\n const defaultImage = config?.image ?? 'oven/bun:latest'\n const defaultCwd = config?.cwd ?? '/workspace'\n const defaultEnv = config?.env\n const defaultLimits = config?.limits\n const defaultMounts = config?.mounts ?? []\n const defaultName = config?.name\n\n async function getDockerode() {\n try {\n const Dockerode = (await import('dockerode')).default\n return new Dockerode()\n }\n catch {\n throw new Error('dockerode is required for Docker execution context. Install it with: bun add dockerode')\n }\n }\n\n /**\n * The exec transport with an explicit output cap. The public `exec`\n * uses the shared 10MB default; `readFile` passes headroom above the\n * whole-read tool ceiling so docker's EFFECTIVE read limit matches\n * every other context (the fileSize probe refuses oversized files\n * before this ever runs — the transport cap is the safety net for\n * probe-failure paths, not the primary bound).\n */\n async function execCapped(handle: ExecutionHandle, command: string, options: { cwd?: string, env?: Record<string, string>, timeout?: number } | undefined, maxBuffer: number): Promise<ExecResult> {\n const ref = containers.get(handle.id)\n if (!ref)\n throw new Error(`Container ${handle.id} not found`)\n\n const execCwd = options?.cwd ?? handle.cwd\n const env = options?.env\n ? Object.entries(options.env).map(([k, v]) => `${k}=${v}`)\n : []\n\n const timeout = options?.timeout ?? defaultLimits?.timeout ?? 30\n\n const exec = await ref.container.exec({\n // Wrap with coreutils `timeout` so a runaway command is killed\n // server-side too — the host-side timer below only detaches the\n // stream; without this the process would keep running in the\n // container until destroy(). Exec-array form avoids re-quoting the\n // command. `timeout` exits 124 on expiry, matching the host\n // sentinel. (Default image oven/bun is Debian-based → coreutils.)\n Cmd: ['timeout', String(timeout), 'sh', '-c', command],\n WorkingDir: execCwd,\n Env: env,\n AttachStdout: true,\n AttachStderr: true,\n })\n\n const stream = await exec.start({ Detach: false })\n\n return new Promise<ExecResult>((resolve) => {\n // Byte-capped accumulation via the shared exec-transport primitive\n // (contexts/output-cap.ts) — the docker `readFile` is `cat` through\n // this very path, so an uncapped sink would buffer a container's\n // multi-GB file straight into HOST memory. On overflow the stream\n // is destroyed and the exec resolves exit 143 with a message\n // (mirroring the process context), so `readFile` THROWS instead of\n // silently returning truncated content — silent truncation would\n // corrupt `edit`'s read-modify-write into data loss.\n const stdoutSlot: CappedSlot = { text: '', bytes: 0 }\n const stderrSlot: CappedSlot = { text: '', bytes: 0 }\n let resolved = false\n\n const overflow = (): void => {\n if (resolved)\n return\n resolved = true\n try {\n stream.destroy?.()\n }\n catch { /* swallow */ }\n resolve({\n stdout: stdoutSlot.text,\n stderr: `${stderrSlot.text ? `${stderrSlot.text}\\n` : ''}${outputExceededMessage(maxBuffer, 'stream detached (server-side timeout reaps the process)')}`,\n exitCode: 143,\n })\n }\n\n const cappedWrite = (slot: CappedSlot) => (chunk: Buffer | string): boolean => {\n const buf = typeof chunk === 'string' ? Buffer.from(chunk, 'utf-8') : chunk\n if (appendCapped(slot, buf, maxBuffer) !== 'ok')\n overflow()\n return true\n }\n\n // Docker's exec stream is a multiplexed protocol — a single byte\n // stream that interleaves stdout/stderr frames. `demuxStream`\n // splits the two onto separate writable sinks. Without it, every\n // byte landed on `stdout` and `stderr` was permanently empty.\n const stdoutSink = {\n write: cappedWrite(stdoutSlot),\n end() {},\n on() {},\n once() {},\n emit() { return true },\n }\n const stderrSink = {\n write: cappedWrite(stderrSlot),\n end() {},\n on() {},\n once() {},\n emit() { return true },\n }\n try {\n ref.docker.modem.demuxStream(stream, stdoutSink, stderrSink)\n }\n catch {\n // Older / different transports may not be multiplexed (TTY mode);\n // fall back to a raw stdout reader so we don't lose output entirely.\n // Same cap: TTY mode is still a host-memory sink.\n stream.on('data', (chunk: Buffer) => {\n cappedWrite(stdoutSlot)(chunk)\n })\n }\n\n // Host-side fallback timer. The server-side `timeout` wrapper is\n // the primary kill mechanism; give it a grace second so the\n // stream-end path (which carries the real exit code) usually wins.\n const timer = setTimeout(() => {\n if (resolved)\n return\n resolved = true\n // Best-effort: detach the stream so we stop accumulating bytes.\n // The server-side `timeout` wrapper kills the process itself.\n try { stream.destroy?.() }\n catch { /* swallow */ }\n resolve({ stdout: stdoutSlot.text, stderr: stderrSlot.text ? `${stderrSlot.text}\\n[timeout]` : '[timeout]', exitCode: 124 })\n }, timeout * 1000 + 1000)\n\n stream.on('end', async () => {\n if (resolved)\n return\n resolved = true\n clearTimeout(timer)\n let exitCode: number\n let inspectNote = ''\n try {\n const inspect = await exec.inspect()\n // `ExitCode: null` means Docker hasn't recorded an exit yet —\n // the outcome is unknown, not success. Use a sentinel so\n // callers don't mistake it for a clean exit.\n if (inspect.ExitCode == null) {\n exitCode = -1\n inspectNote = '[exit code unavailable: exec still marked running by daemon]'\n }\n else {\n exitCode = inspect.ExitCode\n }\n }\n catch (err) {\n exitCode = -1\n inspectNote = `[exit code unavailable: exec inspect failed: ${(err as Error)?.message ?? String(err)}]`\n }\n resolve({\n stdout: stdoutSlot.text,\n stderr: inspectNote ? (stderrSlot.text ? `${stderrSlot.text}\\n${inspectNote}` : inspectNote) : stderrSlot.text,\n exitCode,\n })\n })\n\n stream.on('error', (err: Error) => {\n if (resolved)\n return\n resolved = true\n clearTimeout(timer)\n resolve({ stdout: stdoutSlot.text, stderr: stderrSlot.text ? `${stderrSlot.text}\\n${err.message}` : err.message, exitCode: 1 })\n })\n })\n }\n\n const ctx: ExecutionContext = {\n type: 'docker',\n\n capabilities: {\n shell: true,\n filesystem: true,\n glob: true,\n network: true,\n gpu: false,\n } satisfies ContextCapabilities,\n\n async spawn(overrides?: SpawnConfig): Promise<ExecutionHandle> {\n const docker = await getDockerode()\n const id = `docker-${++counter}`\n const image = overrides?.image ?? defaultImage\n const cwd = overrides?.cwd ?? defaultCwd\n const mounts = [...defaultMounts, ...(overrides?.mounts ?? [])]\n const name = overrides?.name ?? defaultName\n\n // Pull image if not available\n try {\n await docker.getImage(image).inspect()\n }\n catch {\n await new Promise<void>((resolve, reject) => {\n docker.pull(image, (err: Error | null, stream: NodeJS.ReadableStream) => {\n if (err)\n return reject(err)\n docker.modem.followProgress(stream, (err2: Error | null) => {\n err2 ? reject(err2) : resolve()\n })\n })\n })\n }\n\n const limits = { ...defaultLimits, ...overrides?.limits }\n const hostConfig: Record<string, unknown> = {}\n\n if (limits?.memory) {\n hostConfig.Memory = limits.memory * 1024 * 1024\n }\n if (limits?.cpu) {\n hostConfig.NanoCpus = Number.parseFloat(limits.cpu) * 1e9\n }\n\n // Opt-in hardening (all OFF by default — see ContextHardening).\n const hardening = { ...config?.hardening, ...overrides?.hardening }\n if (hardening.dropAllCapabilities)\n hostConfig.CapDrop = ['ALL']\n if (hardening.noNewPrivileges)\n hostConfig.SecurityOpt = ['no-new-privileges']\n if (hardening.readonlyRootfs)\n hostConfig.ReadonlyRootfs = true\n if (hardening.pidsLimit != null)\n hostConfig.PidsLimit = hardening.pidsLimit\n\n const env = { ...defaultEnv, ...overrides?.env }\n\n const bindSpecs = buildBindSpecs(mounts)\n if (bindSpecs.length > 0) {\n hostConfig.Binds = bindSpecs\n }\n\n const portSpec = overrides?.ports ?? config?.ports\n const exposedPorts: Record<string, Record<string, never>> = {}\n const portBindings: Record<string, Array<{ HostPort: string }>> = {}\n\n if (portSpec?.length) {\n for (const p of portSpec) {\n const key = `${p.container}/${p.proto ?? 'tcp'}`\n exposedPorts[key] = {}\n portBindings[key] = [{ HostPort: p.host == null ? '' : String(p.host) }]\n }\n hostConfig.PortBindings = portBindings\n }\n\n const user = overrides?.user ?? config?.user\n const network = overrides?.network ?? config?.network\n if (network) {\n hostConfig.NetworkMode = network\n }\n\n const labels = { ...config?.labels, ...overrides?.labels }\n\n const container = await docker.createContainer({\n // Suffix with a short random token so a crashed run's leftover container\n // (same prefix) never blocks a fresh one with HTTP 409 name conflict.\n ...(name ? { name: `${name}-${counter}-${Math.random().toString(36).slice(2, 8)}` } : {}),\n Image: image,\n Cmd: ['sleep', 'infinity'],\n WorkingDir: cwd,\n Env: Object.entries(env).map(([k, v]) => `${k}=${v}`),\n HostConfig: hostConfig,\n ...(Object.keys(exposedPorts).length ? { ExposedPorts: exposedPorts } : {}),\n ...(user ? { User: user } : {}),\n ...(Object.keys(labels).length ? { Labels: labels } : {}),\n })\n\n try {\n await container.start()\n }\n catch (err) {\n // The container was created but never started — remove it so a\n // failed spawn doesn't leak a stopped container on the daemon.\n await container.remove({ force: true }).catch(() => {})\n throw err\n }\n\n const handle: ExecutionHandle = { id, type: 'docker', cwd }\n containers.set(id, { handle, container, docker })\n\n return handle\n },\n\n async exec(handle: ExecutionHandle, command: string, options?: { cwd?: string, env?: Record<string, string>, timeout?: number }): Promise<ExecResult> {\n return execCapped(handle, command, options, DEFAULT_MAX_OUTPUT_BUFFER)\n },\n\n async fileSize(handle: ExecutionHandle, path: string): Promise<number | null> {\n // Shared exec-probed capability (see exec-file-size.ts) — the same\n // implementation as the sandbox contexts, for a CONSISTENT\n // whole-read experience: tools refuse >ceiling files with the same\n // message everywhere, instead of docker surfacing a transport\n // error at a different threshold.\n return fileSizeViaExec(command => execCapped(handle, command, undefined, DEFAULT_MAX_OUTPUT_BUFFER), path)\n },\n\n // Paths are model-supplied — single-quote them (`alwaysQuote`) rather\n // than JSON.stringify: `$(…)` and backticks still expand inside the\n // double quotes JSON produces, which was a command-injection vector.\n async readFile(handle: ExecutionHandle, path: string): Promise<string> {\n // Transport headroom ABOVE the whole-read tool ceiling (256MB,\n // enforced via the fileSize probe BEFORE this runs): without it,\n // docker's effective read limit would be the 10MB exec default —\n // inconsistent with every other context and surfaced as a\n // confusing transport error instead of the uniform refusal.\n const result = await execCapped(handle, `cat ${alwaysQuote(path)}`, undefined, READ_FILE_MAX_BUFFER)\n if (result.exitCode !== 0)\n throw new Error(`Failed to read file: ${result.stderr}`)\n return result.stdout\n },\n\n async writeFile(handle: ExecutionHandle, path: string, content: string): Promise<void> {\n const escaped = content.replace(SINGLE_QUOTE_RE, String.raw`'\\''`)\n const result = await ctx.exec(handle, `mkdir -p \"$(dirname ${alwaysQuote(path)})\" && printf '%s' '${escaped}' > ${alwaysQuote(path)}`)\n if (result.exitCode !== 0)\n throw new Error(`Failed to write file: ${result.stderr}`)\n },\n\n async listFiles(handle: ExecutionHandle, path: string, options): Promise<ListFilesEntry[]> {\n if (options?.glob)\n return globFilesViaShell(path, command => ctx.exec(handle, command), options?.limit ?? 1000)\n const result = await ctx.exec(handle, `ls -1 ${alwaysQuote(path)}`)\n if (result.exitCode !== 0)\n return []\n return result.stdout.trim().split('\\n').filter(Boolean)\n },\n\n async getMappedPort(handle: ExecutionHandle, containerPort: number): Promise<number | null> {\n const ref = containers.get(handle.id)\n if (!ref)\n return null\n\n const info = await ref.container.inspect()\n const bindings = info?.NetworkSettings?.Ports ?? {}\n\n for (const proto of ['tcp', 'udp']) {\n const list = bindings[`${containerPort}/${proto}`]\n if (Array.isArray(list) && list.length > 0 && list[0]?.HostPort) {\n return Number.parseInt(list[0].HostPort, 10)\n }\n }\n\n return null\n },\n\n async destroy(handle: ExecutionHandle): Promise<void> {\n const ref = containers.get(handle.id)\n if (!ref)\n return\n\n try {\n try {\n await ref.container.stop({ t: 5 })\n }\n catch {\n // 304 (already stopped) / 404 (already gone) are expected here.\n // Any other stop failure must not prevent the remove({force})\n // below — force removal also works on running containers.\n }\n try {\n await ref.container.remove({ force: true })\n }\n catch (err) {\n if (!isGoneOrAlreadyStopped(err))\n throw err\n }\n }\n finally {\n containers.delete(handle.id)\n }\n },\n }\n\n return ctx\n}\n\n/**\n * Format one {@link ContextMount} as a dockerode `HostConfig.Binds` entry.\n * Supports read-only (`:ro`) and SELinux shared-label (`:z`) options; the two\n * are mutually exclusive (a read-only mount can't also be a shared rw mount).\n */\nfunction formatBindMount(mount: NonNullable<SpawnConfig['mounts']>[number]): string {\n if (!mount.source)\n throw new Error('Docker mount source is required')\n if (!mount.target || !mount.target.startsWith('/'))\n throw new Error(`Docker mount target must be an absolute path: ${mount.target}`)\n if (mount.readonly && mount.shared)\n throw new Error(`Docker mount cannot be both readonly and shared: ${mount.target}`)\n const opts = mount.readonly ? ':ro' : mount.shared ? ':z' : ''\n return `${mount.source}:${mount.target}${opts}`\n}\n\n/** Assemble dockerode `HostConfig.Binds` from the context's `mounts`. */\nexport function buildBindSpecs(mounts: NonNullable<SpawnConfig['mounts']>): string[] {\n return mounts.map(formatBindMount)\n}\n"],"mappings":"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"}
|
package/dist/contexts/e2b.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Ll as Logger } from "../index-B8UtiZy7.js";
|
|
2
|
+
import { t as SandboxProvider } from "../sandbox-CgjG2VvQ.js";
|
|
3
3
|
|
|
4
4
|
//#region src/contexts/e2b.d.ts
|
|
5
5
|
interface E2BCommandResult {
|
package/dist/contexts/e2b.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as createLogger, t as consoleSink } from "../logger-DBX9uYOw.js";
|
|
2
2
|
//#region src/contexts/e2b.ts
|
|
3
3
|
/** Per-probe timeout for the readiness `true` command. */
|
|
4
4
|
const READINESS_PROBE_TIMEOUT_MS = 5e3;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as createSandboxContext, t as SandboxProvider } from "../sandbox-
|
|
1
|
+
import { n as createSandboxContext, t as SandboxProvider } from "../sandbox-CgjG2VvQ.js";
|
|
2
2
|
export { SandboxProvider, createSandboxContext };
|
package/dist/contexts/sandbox.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as globFilesViaShell, t as fileSizeViaExec } from "../exec-file-size-PXkr-MGA.js";
|
|
2
2
|
//#region src/contexts/sandbox.ts
|
|
3
3
|
function createSandboxContext(provider) {
|
|
4
4
|
const sandboxes = /* @__PURE__ */ new Map();
|
|
@@ -32,6 +32,9 @@ function createSandboxContext(provider) {
|
|
|
32
32
|
async readFile(handle, path) {
|
|
33
33
|
return provider.readFile(getSandboxId(handle), path);
|
|
34
34
|
},
|
|
35
|
+
async fileSize(handle, path) {
|
|
36
|
+
return fileSizeViaExec((command) => provider.exec(getSandboxId(handle), command), path);
|
|
37
|
+
},
|
|
35
38
|
async writeFile(handle, path, content) {
|
|
36
39
|
return provider.writeFile(getSandboxId(handle), path, content);
|
|
37
40
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.js","names":[],"sources":["../../src/contexts/sandbox.ts"],"sourcesContent":["/**\n * Remote sandbox execution context.\n *\n * Offloads execution to a remote sandbox API (e.g. Rivet, E2B).\n * Specific providers implement the SandboxProvider interface.\n */\n\nimport type { ContextCapabilities, ExecResult, ExecutionContext, ExecutionHandle, ListFilesOptions, SpawnConfig } from './types'\nimport { globFilesViaShell } from './glob-shell'\n\n// ---------------------------------------------------------------------------\n// Sandbox provider interface\n// ---------------------------------------------------------------------------\n\nexport interface SandboxProvider {\n name: string\n spawn: (config: SpawnConfig) => Promise<{ id: string, cwd: string }>\n exec: (sandboxId: string, command: string, options?: { cwd?: string, env?: Record<string, string>, timeout?: number }) => Promise<ExecResult>\n readFile: (sandboxId: string, path: string) => Promise<string>\n writeFile: (sandboxId: string, path: string, content: string) => Promise<void>\n listFiles: (sandboxId: string, path: string, options?: ListFilesOptions) => Promise<string[]>\n destroy: (sandboxId: string) => Promise<void>\n}\n\n// ---------------------------------------------------------------------------\n// Sandbox execution context\n// ---------------------------------------------------------------------------\n\nexport function createSandboxContext(provider: SandboxProvider): ExecutionContext {\n const sandboxes = new Map<string, string>()\n const destroyed = new Set<string>()\n\n // The handle id IS the sandbox id (set in `spawn` below), so the map is an\n // identity record of sandboxes this context instance spawned. A handle this\n // instance never spawned falls back to `handle.id` — the re-attach path for\n // durable hosts that journal the handle and hand it back via\n // `AgentOptions.handle` on a replay where `spawn` never re-ran. An\n // EXPLICITLY-destroyed handle still throws (a use-after-free guard, distinct\n // from never-spawned).\n function getSandboxId(handle: ExecutionHandle): string {\n if (destroyed.has(handle.id))\n throw new Error(`Sandbox ${handle.id} not found`)\n return sandboxes.get(handle.id) ?? handle.id\n }\n\n return {\n type: 'sandbox',\n\n capabilities: {\n shell: true,\n filesystem: true,\n glob: true,\n network: true,\n gpu: false,\n } satisfies ContextCapabilities,\n\n async spawn(config?: SpawnConfig): Promise<ExecutionHandle> {\n const result = await provider.spawn(config ?? {})\n const handle: ExecutionHandle = { id: result.id, type: 'sandbox', cwd: result.cwd }\n sandboxes.set(handle.id, result.id)\n return handle\n },\n\n async exec(handle: ExecutionHandle, command: string, options?): Promise<ExecResult> {\n return provider.exec(getSandboxId(handle), command, options)\n },\n\n async readFile(handle: ExecutionHandle, path: string): Promise<string> {\n return provider.readFile(getSandboxId(handle), path)\n },\n\n async writeFile(handle: ExecutionHandle, path: string, content: string): Promise<void> {\n return provider.writeFile(getSandboxId(handle), path, content)\n },\n\n async listFiles(handle: ExecutionHandle, path: string, options?: ListFilesOptions): Promise<string[]> {\n const sandboxId = getSandboxId(handle)\n if (options?.glob) {\n return globFilesViaShell(\n path,\n command => provider.exec(sandboxId, command, { cwd: handle.cwd }),\n options?.limit ?? 1000,\n )\n }\n return provider.listFiles(sandboxId, path, options)\n },\n\n async destroy(handle: ExecutionHandle): Promise<void> {\n const id = sandboxes.get(handle.id)\n if (!id)\n return\n await provider.destroy(id)\n sandboxes.delete(handle.id)\n // Mark as torn down so a later op on this exact handle fails loudly\n // (use-after-free) instead of silently re-attaching by id.\n destroyed.add(handle.id)\n },\n }\n}\n"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"sandbox.js","names":[],"sources":["../../src/contexts/sandbox.ts"],"sourcesContent":["/**\n * Remote sandbox execution context.\n *\n * Offloads execution to a remote sandbox API (e.g. Rivet, E2B).\n * Specific providers implement the SandboxProvider interface.\n */\n\nimport type { ContextCapabilities, ExecResult, ExecutionContext, ExecutionHandle, ListFilesOptions, SpawnConfig } from './types'\nimport { fileSizeViaExec } from './exec-file-size'\nimport { globFilesViaShell } from './glob-shell'\n\n// ---------------------------------------------------------------------------\n// Sandbox provider interface\n// ---------------------------------------------------------------------------\n\nexport interface SandboxProvider {\n name: string\n spawn: (config: SpawnConfig) => Promise<{ id: string, cwd: string }>\n exec: (sandboxId: string, command: string, options?: { cwd?: string, env?: Record<string, string>, timeout?: number }) => Promise<ExecResult>\n readFile: (sandboxId: string, path: string) => Promise<string>\n writeFile: (sandboxId: string, path: string, content: string) => Promise<void>\n listFiles: (sandboxId: string, path: string, options?: ListFilesOptions) => Promise<string[]>\n destroy: (sandboxId: string) => Promise<void>\n}\n\n// ---------------------------------------------------------------------------\n// Sandbox execution context\n// ---------------------------------------------------------------------------\n\nexport function createSandboxContext(provider: SandboxProvider): ExecutionContext {\n const sandboxes = new Map<string, string>()\n const destroyed = new Set<string>()\n\n // The handle id IS the sandbox id (set in `spawn` below), so the map is an\n // identity record of sandboxes this context instance spawned. A handle this\n // instance never spawned falls back to `handle.id` — the re-attach path for\n // durable hosts that journal the handle and hand it back via\n // `AgentOptions.handle` on a replay where `spawn` never re-ran. An\n // EXPLICITLY-destroyed handle still throws (a use-after-free guard, distinct\n // from never-spawned).\n function getSandboxId(handle: ExecutionHandle): string {\n if (destroyed.has(handle.id))\n throw new Error(`Sandbox ${handle.id} not found`)\n return sandboxes.get(handle.id) ?? handle.id\n }\n\n return {\n type: 'sandbox',\n\n capabilities: {\n shell: true,\n filesystem: true,\n glob: true,\n network: true,\n gpu: false,\n } satisfies ContextCapabilities,\n\n async spawn(config?: SpawnConfig): Promise<ExecutionHandle> {\n const result = await provider.spawn(config ?? {})\n const handle: ExecutionHandle = { id: result.id, type: 'sandbox', cwd: result.cwd }\n sandboxes.set(handle.id, result.id)\n return handle\n },\n\n async exec(handle: ExecutionHandle, command: string, options?): Promise<ExecResult> {\n return provider.exec(getSandboxId(handle), command, options)\n },\n\n async readFile(handle: ExecutionHandle, path: string): Promise<string> {\n return provider.readFile(getSandboxId(handle), path)\n },\n\n async fileSize(handle: ExecutionHandle, path: string): Promise<number | null> {\n // Shared exec-probed capability (see exec-file-size.ts). Probe\n // through the sandbox's shell rather than the provider's file API:\n // `readFile` buffers the WHOLE file into host memory, so the\n // whole-read guard needs the size BEFORE any transfer happens. One\n // exec round-trip per whole-read tool call (read_file / edit) — the\n // price of not ballooning the host on a multi-GB sandbox file.\n return fileSizeViaExec(command => provider.exec(getSandboxId(handle), command), path)\n },\n\n async writeFile(handle: ExecutionHandle, path: string, content: string): Promise<void> {\n return provider.writeFile(getSandboxId(handle), path, content)\n },\n\n async listFiles(handle: ExecutionHandle, path: string, options?: ListFilesOptions): Promise<string[]> {\n const sandboxId = getSandboxId(handle)\n if (options?.glob) {\n return globFilesViaShell(\n path,\n command => provider.exec(sandboxId, command, { cwd: handle.cwd }),\n options?.limit ?? 1000,\n )\n }\n return provider.listFiles(sandboxId, path, options)\n },\n\n async destroy(handle: ExecutionHandle): Promise<void> {\n const id = sandboxes.get(handle.id)\n if (!id)\n return\n await provider.destroy(id)\n sandboxes.delete(handle.id)\n // Mark as torn down so a later op on this exact handle fails loudly\n // (use-after-free) instead of silently re-attaching by id.\n destroyed.add(handle.id)\n },\n }\n}\n"],"mappings":";;AA6BA,SAAgB,qBAAqB,UAA6C;CAChF,MAAM,4BAAY,IAAI,IAAoB;CAC1C,MAAM,4BAAY,IAAI,IAAY;CASlC,SAAS,aAAa,QAAiC;EACrD,IAAI,UAAU,IAAI,OAAO,EAAE,GACzB,MAAM,IAAI,MAAM,WAAW,OAAO,GAAG,WAAW;EAClD,OAAO,UAAU,IAAI,OAAO,EAAE,KAAK,OAAO;CAC5C;CAEA,OAAO;EACL,MAAM;EAEN,cAAc;GACZ,OAAO;GACP,YAAY;GACZ,MAAM;GACN,SAAS;GACT,KAAK;EACP;EAEA,MAAM,MAAM,QAAgD;GAC1D,MAAM,SAAS,MAAM,SAAS,MAAM,UAAU,CAAC,CAAC;GAChD,MAAM,SAA0B;IAAE,IAAI,OAAO;IAAI,MAAM;IAAW,KAAK,OAAO;GAAI;GAClF,UAAU,IAAI,OAAO,IAAI,OAAO,EAAE;GAClC,OAAO;EACT;EAEA,MAAM,KAAK,QAAyB,SAAiB,SAA+B;GAClF,OAAO,SAAS,KAAK,aAAa,MAAM,GAAG,SAAS,OAAO;EAC7D;EAEA,MAAM,SAAS,QAAyB,MAA+B;GACrE,OAAO,SAAS,SAAS,aAAa,MAAM,GAAG,IAAI;EACrD;EAEA,MAAM,SAAS,QAAyB,MAAsC;GAO5E,OAAO,iBAAgB,YAAW,SAAS,KAAK,aAAa,MAAM,GAAG,OAAO,GAAG,IAAI;EACtF;EAEA,MAAM,UAAU,QAAyB,MAAc,SAAgC;GACrF,OAAO,SAAS,UAAU,aAAa,MAAM,GAAG,MAAM,OAAO;EAC/D;EAEA,MAAM,UAAU,QAAyB,MAAc,SAA+C;GACpG,MAAM,YAAY,aAAa,MAAM;GACrC,IAAI,SAAS,MACX,OAAO,kBACL,OACA,YAAW,SAAS,KAAK,WAAW,SAAS,EAAE,KAAK,OAAO,IAAI,CAAC,GAChE,SAAS,SAAS,GACpB;GAEF,OAAO,SAAS,UAAU,WAAW,MAAM,OAAO;EACpD;EAEA,MAAM,QAAQ,QAAwC;GACpD,MAAM,KAAK,UAAU,IAAI,OAAO,EAAE;GAClC,IAAI,CAAC,IACH;GACF,MAAM,SAAS,QAAQ,EAAE;GACzB,UAAU,OAAO,OAAO,EAAE;GAG1B,UAAU,IAAI,OAAO,EAAE;EACzB;CACF;AACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as lazyAsync } from "./lazy-DLOurOC_.js";
|
|
2
|
-
import { n as globLiteralPrefix, t as
|
|
2
|
+
import { a as globFilesLocal, i as outputExceededMessage, n as appendCapped, o as globLiteralPrefix, t as DEFAULT_MAX_OUTPUT_BUFFER } from "./output-cap-kBUHBlnq.js";
|
|
3
3
|
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
4
4
|
import { createWriteStream } from "node:fs";
|
|
5
5
|
import { mkdir, readFile, readdir, realpath, stat, writeFile } from "node:fs/promises";
|
|
@@ -32,7 +32,6 @@ const SUPPORTS_PROCESS_GROUPS = process.platform !== "win32";
|
|
|
32
32
|
* same buffer envelope. Output beyond this is truncated and a
|
|
33
33
|
* marker is appended to stderr.
|
|
34
34
|
*/
|
|
35
|
-
const DEFAULT_MAX_BUFFER = 10 * 1024 * 1024;
|
|
36
35
|
/**
|
|
37
36
|
* How long `destroy()` waits for a SIGTERM'd background task to settle
|
|
38
37
|
* before escalating to SIGKILL and abandoning the wait. Matches the MCP
|
|
@@ -233,7 +232,7 @@ function createProcessContext(config) {
|
|
|
233
232
|
exitCode: 143
|
|
234
233
|
};
|
|
235
234
|
const timeoutMs = (options?.timeout ?? config?.limits?.timeout ?? 30) * 1e3;
|
|
236
|
-
const maxBuffer =
|
|
235
|
+
const maxBuffer = DEFAULT_MAX_OUTPUT_BUFFER;
|
|
237
236
|
const spawnChild = await loadSpawn();
|
|
238
237
|
return new Promise((resolveP) => {
|
|
239
238
|
const child = spawnChild("/bin/sh", ["-c", command], {
|
|
@@ -250,44 +249,26 @@ function createProcessContext(config) {
|
|
|
250
249
|
],
|
|
251
250
|
detached: SUPPORTS_PROCESS_GROUPS
|
|
252
251
|
});
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
252
|
+
const stdoutSlot = {
|
|
253
|
+
text: "",
|
|
254
|
+
bytes: 0
|
|
255
|
+
};
|
|
256
|
+
const stderrSlot = {
|
|
257
|
+
text: "",
|
|
258
|
+
bytes: 0
|
|
259
|
+
};
|
|
257
260
|
let bufferTruncated = false;
|
|
258
261
|
let timedOut = false;
|
|
259
262
|
let killedByAbort = false;
|
|
260
263
|
let settled = false;
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
if (used >= maxBuffer) {
|
|
264
|
-
if (!bufferTruncated) {
|
|
265
|
-
bufferTruncated = true;
|
|
266
|
-
killProcessGroup(child, "SIGTERM");
|
|
267
|
-
}
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
const room = maxBuffer - used;
|
|
271
|
-
let piece = chunk;
|
|
272
|
-
if (chunk.length > room) {
|
|
273
|
-
let end = room;
|
|
274
|
-
while (end > 0 && (chunk[end] & 192) === 128) end--;
|
|
275
|
-
piece = chunk.subarray(0, end);
|
|
276
|
-
}
|
|
277
|
-
if (slot === "stdout") {
|
|
278
|
-
stdout += piece.toString("utf8");
|
|
279
|
-
stdoutBytes += piece.length;
|
|
280
|
-
} else {
|
|
281
|
-
stderr += piece.toString("utf8");
|
|
282
|
-
stderrBytes += piece.length;
|
|
283
|
-
}
|
|
284
|
-
if (chunk.length > room) {
|
|
264
|
+
const onChunk = (slot, chunk) => {
|
|
265
|
+
if (appendCapped(slot, chunk, maxBuffer) !== "ok" && !bufferTruncated) {
|
|
285
266
|
bufferTruncated = true;
|
|
286
267
|
killProcessGroup(child, "SIGTERM");
|
|
287
268
|
}
|
|
288
269
|
};
|
|
289
|
-
child.stdout?.on("data", (chunk) =>
|
|
290
|
-
child.stderr?.on("data", (chunk) =>
|
|
270
|
+
child.stdout?.on("data", (chunk) => onChunk(stdoutSlot, chunk));
|
|
271
|
+
child.stderr?.on("data", (chunk) => onChunk(stderrSlot, chunk));
|
|
291
272
|
const timeoutTimer = timeoutMs > 0 ? setTimeout(() => {
|
|
292
273
|
timedOut = true;
|
|
293
274
|
killProcessGroup(child, "SIGTERM");
|
|
@@ -305,9 +286,9 @@ function createProcessContext(config) {
|
|
|
305
286
|
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
306
287
|
if (exitGraceTimer) clearTimeout(exitGraceTimer);
|
|
307
288
|
if (userSignal) userSignal.removeEventListener("abort", onAbort);
|
|
308
|
-
const finalStderr = extraStderr ?
|
|
289
|
+
const finalStderr = extraStderr ? stderrSlot.text ? `${stderrSlot.text}\n${extraStderr}` : extraStderr : stderrSlot.text;
|
|
309
290
|
resolveP({
|
|
310
|
-
stdout,
|
|
291
|
+
stdout: stdoutSlot.text,
|
|
311
292
|
stderr: finalStderr,
|
|
312
293
|
exitCode
|
|
313
294
|
});
|
|
@@ -322,7 +303,7 @@ function createProcessContext(config) {
|
|
|
322
303
|
return;
|
|
323
304
|
}
|
|
324
305
|
if (bufferTruncated) {
|
|
325
|
-
settle(143,
|
|
306
|
+
settle(143, outputExceededMessage(maxBuffer, "process killed"));
|
|
326
307
|
return;
|
|
327
308
|
}
|
|
328
309
|
if (signal) {
|
|
@@ -349,6 +330,14 @@ function createProcessContext(config) {
|
|
|
349
330
|
async readFile(handle, path) {
|
|
350
331
|
return readFile(await resolveContained(handle, path), "utf-8");
|
|
351
332
|
},
|
|
333
|
+
async fileSize(handle, path) {
|
|
334
|
+
try {
|
|
335
|
+
const info = await stat(await resolveContained(handle, path));
|
|
336
|
+
return info.isFile() ? info.size : null;
|
|
337
|
+
} catch {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
},
|
|
352
341
|
async readFileBinary(handle, path) {
|
|
353
342
|
const buf = await readFile(await resolveContained(handle, path));
|
|
354
343
|
return new Uint8Array(buf);
|
|
@@ -687,4 +676,4 @@ function resolveDetachedTasksCapability(context) {
|
|
|
687
676
|
//#endregion
|
|
688
677
|
export { createProcessContext as n, resolveDetachedTasksCapability as t };
|
|
689
678
|
|
|
690
|
-
//# sourceMappingURL=contexts-
|
|
679
|
+
//# sourceMappingURL=contexts-Vone3qQQ.js.map
|