stitchkit 0.87.1 → 0.89.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/CHANGELOG.md +190 -0
- package/dist/agent-runtime/child-blocking.d.ts +17 -0
- package/dist/agent-runtime/child-blocking.d.ts.map +1 -0
- package/dist/agent-runtime/child-budget-policy.d.ts +23 -0
- package/dist/agent-runtime/child-budget-policy.d.ts.map +1 -0
- package/dist/agent-runtime/children-contract.d.ts +155 -0
- package/dist/agent-runtime/children-contract.d.ts.map +1 -0
- package/dist/agent-runtime/children.d.ts +4 -99
- package/dist/agent-runtime/children.d.ts.map +1 -1
- package/dist/agent-runtime/coding-shell-process.d.ts +33 -0
- package/dist/agent-runtime/coding-shell-process.d.ts.map +1 -0
- package/dist/agent-runtime/coding-tool-contract.d.ts +18 -0
- package/dist/agent-runtime/coding-tool-contract.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-files.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-listing.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-paths.d.ts +1 -1
- package/dist/agent-runtime/coding-tool-paths.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-search-patch.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-shell.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-utf8.d.ts +28 -0
- package/dist/agent-runtime/coding-tool-utf8.d.ts.map +1 -0
- package/dist/agent-runtime/contained-files.d.ts +20 -0
- package/dist/agent-runtime/contained-files.d.ts.map +1 -1
- package/dist/agent-runtime/durability-contract.d.ts +176 -0
- package/dist/agent-runtime/durability-contract.d.ts.map +1 -0
- package/dist/agent-runtime/durability-json.d.ts +3 -0
- package/dist/agent-runtime/durability-json.d.ts.map +1 -0
- package/dist/agent-runtime/durability-ledger.d.ts +16 -0
- package/dist/agent-runtime/durability-ledger.d.ts.map +1 -0
- package/dist/agent-runtime/durability-scheduler.d.ts +29 -0
- package/dist/agent-runtime/durability-scheduler.d.ts.map +1 -0
- package/dist/agent-runtime/durability.d.ts +14 -0
- package/dist/agent-runtime/durability.d.ts.map +1 -0
- package/dist/agent-runtime/event-search.d.ts.map +1 -1
- package/dist/agent-runtime/harness-contract.d.ts +37 -0
- package/dist/agent-runtime/harness-contract.d.ts.map +1 -1
- package/dist/agent-runtime/harness.d.ts +8 -0
- package/dist/agent-runtime/harness.d.ts.map +1 -1
- package/dist/agent-runtime/models.d.ts +14 -0
- package/dist/agent-runtime/models.d.ts.map +1 -1
- package/dist/agent-runtime/observability.d.ts +8 -0
- package/dist/agent-runtime/observability.d.ts.map +1 -1
- package/dist/agent-runtime/owned-provider-stream.d.ts +16 -0
- package/dist/agent-runtime/owned-provider-stream.d.ts.map +1 -0
- package/dist/agent-runtime/prompt.d.ts +23 -0
- package/dist/agent-runtime/prompt.d.ts.map +1 -1
- package/dist/agent-runtime/run-execution.d.ts.map +1 -1
- package/dist/agent-runtime/runtime-internals.d.ts +16 -0
- package/dist/agent-runtime/runtime-internals.d.ts.map +1 -1
- package/dist/agent-runtime/runtime.d.ts +25 -0
- package/dist/agent-runtime/runtime.d.ts.map +1 -1
- package/dist/agent-runtime/sandbox-broker.d.ts +7 -0
- package/dist/agent-runtime/sandbox-broker.d.ts.map +1 -0
- package/dist/agent-runtime/sandbox-bubblewrap-args.d.ts +9 -0
- package/dist/agent-runtime/sandbox-bubblewrap-args.d.ts.map +1 -0
- package/dist/agent-runtime/sandbox-bubblewrap-storage.d.ts +22 -0
- package/dist/agent-runtime/sandbox-bubblewrap-storage.d.ts.map +1 -0
- package/dist/agent-runtime/sandbox-bubblewrap.d.ts +14 -0
- package/dist/agent-runtime/sandbox-bubblewrap.d.ts.map +1 -0
- package/dist/agent-runtime/sandbox-coding-adapter.d.ts +15 -0
- package/dist/agent-runtime/sandbox-coding-adapter.d.ts.map +1 -0
- package/dist/agent-runtime/sandbox-coding.d.ts +15 -0
- package/dist/agent-runtime/sandbox-coding.d.ts.map +1 -0
- package/dist/agent-runtime/sandbox-contract.d.ts +93 -0
- package/dist/agent-runtime/sandbox-contract.d.ts.map +1 -0
- package/dist/agent-runtime/sandbox-process-owner.d.ts +11 -0
- package/dist/agent-runtime/sandbox-process-owner.d.ts.map +1 -0
- package/dist/agent-runtime/sandbox-process.d.ts +8 -0
- package/dist/agent-runtime/sandbox-process.d.ts.map +1 -0
- package/dist/agent-runtime/sandbox-session.d.ts +5 -0
- package/dist/agent-runtime/sandbox-session.d.ts.map +1 -0
- package/dist/agent-runtime/sandbox.d.ts +19 -0
- package/dist/agent-runtime/sandbox.d.ts.map +1 -1
- package/dist/agent-runtime/schemas.d.ts +6 -0
- package/dist/agent-runtime/schemas.d.ts.map +1 -1
- package/dist/agent-runtime/sqlite-purge.d.ts.map +1 -1
- package/dist/agent-runtime/sqlite.d.ts.map +1 -1
- package/dist/agent-runtime/store-driver.d.ts +127 -0
- package/dist/agent-runtime/store-driver.d.ts.map +1 -1
- package/dist/agent-runtime/store-events.d.ts +106 -0
- package/dist/agent-runtime/store-events.d.ts.map +1 -1
- package/dist/agent-runtime/store-migrations/v2-to-v3.d.ts +14 -0
- package/dist/agent-runtime/store-migrations/v2-to-v3.d.ts.map +1 -0
- package/dist/agent-runtime/store.d.ts +115 -0
- package/dist/agent-runtime/store.d.ts.map +1 -1
- package/dist/agent-runtime-browser.js +5 -3
- package/dist/agent-runtime-coding-tools.js +11 -1236
- package/dist/agent-runtime-harness.d.ts +1 -1
- package/dist/agent-runtime-harness.d.ts.map +1 -1
- package/dist/agent-runtime-harness.js +69 -19
- package/dist/agent-runtime-openrouter.d.ts +3 -12
- package/dist/agent-runtime-openrouter.d.ts.map +1 -1
- package/dist/agent-runtime-openrouter.js +22 -9
- package/dist/agent-runtime-sandbox.d.ts +5 -0
- package/dist/agent-runtime-sandbox.d.ts.map +1 -0
- package/dist/agent-runtime-sandbox.js +797 -0
- package/dist/agent-runtime-sqlite-bun.js +5 -5
- package/dist/agent-runtime-sqlite-node.js +5 -5
- package/dist/agent-runtime.d.ts +6 -5
- package/dist/agent-runtime.d.ts.map +1 -1
- package/dist/agent-runtime.js +266 -69
- package/dist/application.js +26 -26
- package/dist/cli.js +8 -8
- package/dist/{index-rcgsnrr6.js → index-31jw8fjq.js} +3 -3
- package/dist/{index-9hw9eq48.js → index-7601qfg1.js} +14 -14
- package/dist/{index-3xnq72rz.js → index-7rkhw9ec.js} +14 -1
- package/dist/{index-0vfdh685.js → index-8c3a7m4g.js} +1 -1
- package/dist/{index-t7gh5w3v.js → index-bn2cjajt.js} +27 -6
- package/dist/{index-z2452tsw.js → index-bt14acb5.js} +2 -2
- package/dist/index-cp20z0n6.js +1351 -0
- package/dist/{index-9553432s.js → index-crykmqkr.js} +1 -1
- package/dist/{index-6g273pqx.js → index-fadnxwna.js} +89 -7
- package/dist/{index-p1b1y93x.js → index-gtj3zc53.js} +5 -1
- package/dist/{index-5wezxcxx.js → index-hrbajay7.js} +107 -5
- package/dist/{index-35aefxby.js → index-hw2chdvy.js} +768 -49
- package/dist/{index-0ydchz76.js → index-mnm4qp0p.js} +5 -5
- package/dist/{index-tg3m2ec5.js → index-mqv3b5rh.js} +4 -1
- package/dist/{index-1szetq6f.js → index-q3ve2d1k.js} +30 -8
- package/dist/{index-hkm6wysp.js → index-y64k3s0h.js} +1 -1
- package/dist/{index-z1m86vc8.js → index-zwkgfq1s.js} +97 -86
- package/dist/{index-wn9cb82y.js → index-zyvby92n.js} +12 -9
- package/dist/index.js +13 -13
- package/dist/server/index.js +9 -9
- package/dist/testing/agent-store-conformance.d.ts.map +1 -1
- package/dist/testing.js +69 -24
- package/dist/tool-invoker.js +6 -6
- package/dist/tools/agent.d.ts +6 -0
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/connections/define.d.ts +6 -0
- package/dist/tools/connections/define.d.ts.map +1 -0
- package/dist/tools/connections/errors.d.ts +50 -0
- package/dist/tools/connections/errors.d.ts.map +1 -0
- package/dist/tools/connections/http.d.ts +3 -0
- package/dist/tools/connections/http.d.ts.map +1 -0
- package/dist/tools/connections/index.d.ts +19 -0
- package/dist/tools/connections/index.d.ts.map +1 -0
- package/dist/tools/connections/index.js +1047 -0
- package/dist/tools/connections/instance.d.ts +3 -0
- package/dist/tools/connections/instance.d.ts.map +1 -0
- package/dist/tools/connections/json-rpc.d.ts +8 -0
- package/dist/tools/connections/json-rpc.d.ts.map +1 -0
- package/dist/tools/connections/limits.d.ts +36 -0
- package/dist/tools/connections/limits.d.ts.map +1 -0
- package/dist/tools/connections/mcp-client.d.ts +44 -0
- package/dist/tools/connections/mcp-client.d.ts.map +1 -0
- package/dist/tools/connections/mcp.d.ts +5 -0
- package/dist/tools/connections/mcp.d.ts.map +1 -0
- package/dist/tools/connections/mount.d.ts +12 -0
- package/dist/tools/connections/mount.d.ts.map +1 -0
- package/dist/tools/connections/openapi-document.d.ts +4 -0
- package/dist/tools/connections/openapi-document.d.ts.map +1 -0
- package/dist/tools/connections/openapi.d.ts +5 -0
- package/dist/tools/connections/openapi.d.ts.map +1 -0
- package/dist/tools/connections/runtime.d.ts +29 -0
- package/dist/tools/connections/runtime.d.ts.map +1 -0
- package/dist/tools/connections/schema-budget.d.ts +8 -0
- package/dist/tools/connections/schema-budget.d.ts.map +1 -0
- package/dist/tools/connections/sse-frames.d.ts +7 -0
- package/dist/tools/connections/sse-frames.d.ts.map +1 -0
- package/dist/tools/connections/sse-session.d.ts +26 -0
- package/dist/tools/connections/sse-session.d.ts.map +1 -0
- package/dist/tools/connections/ssrf.d.ts +15 -0
- package/dist/tools/connections/ssrf.d.ts.map +1 -0
- package/dist/tools/connections/types.d.ts +60 -0
- package/dist/tools/connections/types.d.ts.map +1 -0
- package/dist/tools/durability-context.d.ts +7 -0
- package/dist/tools/durability-context.d.ts.map +1 -0
- package/dist/tools/execute.d.ts +2 -0
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/registry.d.ts +38 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/runtime-tool.d.ts +4 -0
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools.d.ts +1 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +83 -26
- package/llms-full.txt +329 -14
- package/llms.txt +1 -0
- package/package.json +9 -1
- package/dist/{index-evcnmsc3.js → index-t7fkyxcr.js} +3 -3
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
import {
|
|
2
|
+
codingRefusal,
|
|
3
|
+
createAgentCodingTools
|
|
4
|
+
} from "./index-cp20z0n6.js";
|
|
5
|
+
import"./index-bn2cjajt.js";
|
|
6
|
+
import {
|
|
7
|
+
missingSandboxRestrictions,
|
|
8
|
+
probeAgentProcessSandbox
|
|
9
|
+
} from "./index-gskm1k5a.js";
|
|
10
|
+
import"./index-2bxcmqkb.js";
|
|
11
|
+
import"./index-6k1937bx.js";
|
|
12
|
+
import"./index-0w9abg87.js";
|
|
13
|
+
import"./index-kzfs85xp.js";
|
|
14
|
+
import"./index-kc6h6hg0.js";
|
|
15
|
+
import"./index-r159gjwy.js";
|
|
16
|
+
import"./index-cby4ar3v.js";
|
|
17
|
+
import"./index-77fekveh.js";
|
|
18
|
+
|
|
19
|
+
// src/agent-runtime/sandbox-bubblewrap.ts
|
|
20
|
+
import { mkdir as mkdir2, mkdtemp, realpath, rm as rm2 } from "node:fs/promises";
|
|
21
|
+
import { tmpdir } from "node:os";
|
|
22
|
+
import { join as join2, posix as posix2 } from "node:path";
|
|
23
|
+
import { z as z3 } from "zod";
|
|
24
|
+
|
|
25
|
+
// src/agent-runtime/sandbox-broker.ts
|
|
26
|
+
import { chmod, unlink } from "node:fs/promises";
|
|
27
|
+
import { createServer } from "node:http";
|
|
28
|
+
|
|
29
|
+
// src/agent-runtime/sandbox-contract.ts
|
|
30
|
+
import { z } from "zod";
|
|
31
|
+
var SandboxNetworkPolicySchema = z.union([
|
|
32
|
+
z.literal("allow-all"),
|
|
33
|
+
z.literal("deny-all"),
|
|
34
|
+
z.object({
|
|
35
|
+
allow: z.array(z.object({
|
|
36
|
+
origin: z.url().refine((value) => {
|
|
37
|
+
const u = new URL(value);
|
|
38
|
+
return ["http:", "https:"].includes(u.protocol) && !u.username && !u.password && u.pathname === "/" && !u.search && !u.hash;
|
|
39
|
+
}, "Expected an HTTP origin"),
|
|
40
|
+
headers: z.record(z.string().regex(/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/).refine((name) => ![
|
|
41
|
+
"host",
|
|
42
|
+
"connection",
|
|
43
|
+
"content-length",
|
|
44
|
+
"transfer-encoding",
|
|
45
|
+
"proxy-authorization",
|
|
46
|
+
"proxy-connection",
|
|
47
|
+
"upgrade",
|
|
48
|
+
"trailer",
|
|
49
|
+
"te"
|
|
50
|
+
].includes(name.toLowerCase()), "Routing and hop-by-hop headers cannot be brokered"), z.string().refine((value) => !/[\r\n\0]/.test(value), "Invalid header value")).optional()
|
|
51
|
+
}).strict())
|
|
52
|
+
}).strict().refine((policy) => new Set(policy.allow.map((entry) => new URL(entry.origin).host)).size === policy.allow.length, "Each gateway host must have exactly one origin")
|
|
53
|
+
]);
|
|
54
|
+
var SandboxStateSchema = z.object({
|
|
55
|
+
backend: z.string().min(1),
|
|
56
|
+
sessionId: z.string().min(1),
|
|
57
|
+
templateKey: z.string().min(1)
|
|
58
|
+
}).strict();
|
|
59
|
+
var SandboxCommandSchema = z.object({
|
|
60
|
+
executable: z.string().min(1),
|
|
61
|
+
args: z.array(z.string()).optional(),
|
|
62
|
+
cwd: z.string().optional(),
|
|
63
|
+
environment: z.record(z.string(), z.string()).optional(),
|
|
64
|
+
timeoutMs: z.number().int().positive().max(2147483647).optional(),
|
|
65
|
+
maxOutputBytes: z.number().int().positive().optional()
|
|
66
|
+
}).strict();
|
|
67
|
+
|
|
68
|
+
class SandboxError extends Error {
|
|
69
|
+
code;
|
|
70
|
+
constructor(code, message, options) {
|
|
71
|
+
super(message, options);
|
|
72
|
+
this.code = code;
|
|
73
|
+
this.name = "SandboxError";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/agent-runtime/sandbox-broker.ts
|
|
78
|
+
async function createSandboxBroker(socketPath, getPolicy, onError) {
|
|
79
|
+
const controllers = new Set;
|
|
80
|
+
const maxBytes = 1048576;
|
|
81
|
+
async function handle(req, res) {
|
|
82
|
+
const policy = getPolicy();
|
|
83
|
+
const rule = typeof policy === "object" ? policy.allow.find((entry) => new URL(entry.origin).host === req.headers.host) : undefined;
|
|
84
|
+
if (!rule || !req.url?.startsWith("/") || req.url.startsWith("//")) {
|
|
85
|
+
res.writeHead(403).end("Sandbox network denied");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (controllers.size >= 16) {
|
|
89
|
+
res.writeHead(503).end("Sandbox broker busy");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const controller = new AbortController;
|
|
93
|
+
controllers.add(controller);
|
|
94
|
+
const timer = setTimeout(() => {
|
|
95
|
+
controller.abort();
|
|
96
|
+
req.destroy();
|
|
97
|
+
}, 30000);
|
|
98
|
+
timer.unref();
|
|
99
|
+
const abort = () => controller.abort();
|
|
100
|
+
req.on("aborted", abort);
|
|
101
|
+
res.on("close", abort);
|
|
102
|
+
try {
|
|
103
|
+
const chunks = [];
|
|
104
|
+
let size = 0;
|
|
105
|
+
for await (const chunk of req) {
|
|
106
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
107
|
+
size += bytes.byteLength;
|
|
108
|
+
if (size > maxBytes)
|
|
109
|
+
throw new SandboxError("SANDBOX_LIMIT", "Sandbox request limit exceeded");
|
|
110
|
+
chunks.push(bytes);
|
|
111
|
+
}
|
|
112
|
+
const target = new URL(req.url, rule.origin);
|
|
113
|
+
if (target.origin !== new URL(rule.origin).origin)
|
|
114
|
+
throw new SandboxError("SANDBOX_NETWORK_DENIED", "Sandbox network denied");
|
|
115
|
+
const headers = new Headers;
|
|
116
|
+
for (const name of ["content-type", "accept"]) {
|
|
117
|
+
const value = req.headers[name];
|
|
118
|
+
if (typeof value === "string")
|
|
119
|
+
headers.set(name, value);
|
|
120
|
+
}
|
|
121
|
+
for (const [name, value] of Object.entries(rule.headers ?? {}))
|
|
122
|
+
headers.set(name, value);
|
|
123
|
+
const response = await fetch(target, {
|
|
124
|
+
method: req.method,
|
|
125
|
+
headers,
|
|
126
|
+
redirect: "manual",
|
|
127
|
+
signal: controller.signal,
|
|
128
|
+
...size > 0 && { body: Buffer.concat(chunks) }
|
|
129
|
+
});
|
|
130
|
+
if (response.status >= 300 && response.status < 400) {
|
|
131
|
+
await response.body?.cancel();
|
|
132
|
+
throw new SandboxError("SANDBOX_NETWORK_DENIED", "Sandbox redirects are denied");
|
|
133
|
+
}
|
|
134
|
+
const reader = response.body?.getReader();
|
|
135
|
+
const output = [];
|
|
136
|
+
size = 0;
|
|
137
|
+
try {
|
|
138
|
+
while (reader) {
|
|
139
|
+
const next = await reader.read();
|
|
140
|
+
if (next.done)
|
|
141
|
+
break;
|
|
142
|
+
size += next.value.byteLength;
|
|
143
|
+
if (size > maxBytes)
|
|
144
|
+
throw new SandboxError("SANDBOX_LIMIT", "Sandbox response limit exceeded");
|
|
145
|
+
output.push(next.value);
|
|
146
|
+
}
|
|
147
|
+
} finally {
|
|
148
|
+
await reader?.cancel();
|
|
149
|
+
}
|
|
150
|
+
const type = response.headers.get("content-type");
|
|
151
|
+
res.writeHead(response.status, type ? { "content-type": type } : {});
|
|
152
|
+
res.end(Buffer.concat(output));
|
|
153
|
+
} catch (cause) {
|
|
154
|
+
try {
|
|
155
|
+
onError(cause);
|
|
156
|
+
} catch (observerError) {
|
|
157
|
+
process.emitWarning(new Error("Sandbox diagnostic sink failed", {
|
|
158
|
+
cause: new AggregateError([cause, observerError])
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
if (!res.destroyed)
|
|
162
|
+
res.writeHead(502).end("Sandbox upstream request failed");
|
|
163
|
+
} finally {
|
|
164
|
+
clearTimeout(timer);
|
|
165
|
+
controllers.delete(controller);
|
|
166
|
+
req.off("aborted", abort);
|
|
167
|
+
res.off("close", abort);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const server = createServer((req, res) => {
|
|
171
|
+
handle(req, res);
|
|
172
|
+
});
|
|
173
|
+
server.on("connect", (_req, socket) => socket.end(`HTTP/1.1 403 Forbidden\r
|
|
174
|
+
Connection: close\r
|
|
175
|
+
\r
|
|
176
|
+
`));
|
|
177
|
+
server.requestTimeout = 30000;
|
|
178
|
+
server.headersTimeout = 5000;
|
|
179
|
+
await new Promise((resolve, reject) => {
|
|
180
|
+
server.once("error", reject);
|
|
181
|
+
server.listen(socketPath, resolve);
|
|
182
|
+
});
|
|
183
|
+
await chmod(socketPath, 384);
|
|
184
|
+
return {
|
|
185
|
+
abortRequests() {
|
|
186
|
+
for (const controller of controllers)
|
|
187
|
+
controller.abort();
|
|
188
|
+
},
|
|
189
|
+
async close() {
|
|
190
|
+
for (const controller of controllers)
|
|
191
|
+
controller.abort();
|
|
192
|
+
await new Promise((resolve, reject) => {
|
|
193
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
194
|
+
server.closeAllConnections();
|
|
195
|
+
});
|
|
196
|
+
await unlink(socketPath).catch((error) => {
|
|
197
|
+
if (!(error instanceof Error && ("code" in error) && error.code === "ENOENT"))
|
|
198
|
+
throw error;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/agent-runtime/sandbox-bubblewrap-args.ts
|
|
205
|
+
function bubblewrapArgs(input) {
|
|
206
|
+
const args = [
|
|
207
|
+
"--unshare-all",
|
|
208
|
+
"--die-with-parent",
|
|
209
|
+
"--new-session",
|
|
210
|
+
"--cap-drop",
|
|
211
|
+
"ALL",
|
|
212
|
+
"--ro-bind",
|
|
213
|
+
"/usr",
|
|
214
|
+
"/usr",
|
|
215
|
+
"--ro-bind",
|
|
216
|
+
"/lib",
|
|
217
|
+
"/lib",
|
|
218
|
+
"--ro-bind",
|
|
219
|
+
"/lib64",
|
|
220
|
+
"/lib64",
|
|
221
|
+
"--symlink",
|
|
222
|
+
"usr/bin",
|
|
223
|
+
"/bin",
|
|
224
|
+
"--symlink",
|
|
225
|
+
"usr/sbin",
|
|
226
|
+
"/sbin",
|
|
227
|
+
"--proc",
|
|
228
|
+
"/proc",
|
|
229
|
+
"--dev",
|
|
230
|
+
"/dev",
|
|
231
|
+
"--tmpfs",
|
|
232
|
+
"/tmp",
|
|
233
|
+
"--dir",
|
|
234
|
+
"/run",
|
|
235
|
+
"--bind",
|
|
236
|
+
input.workspace,
|
|
237
|
+
"/workspace",
|
|
238
|
+
"--ro-bind",
|
|
239
|
+
input.socket,
|
|
240
|
+
"/run/stitchkit-network.sock",
|
|
241
|
+
"--clearenv",
|
|
242
|
+
"--setenv",
|
|
243
|
+
"PATH",
|
|
244
|
+
"/usr/bin:/bin",
|
|
245
|
+
"--setenv",
|
|
246
|
+
"HOME",
|
|
247
|
+
"/workspace"
|
|
248
|
+
];
|
|
249
|
+
if (input.network === "allow-all")
|
|
250
|
+
args.push("--share-net", "--ro-bind", "/etc/resolv.conf", "/etc/resolv.conf");
|
|
251
|
+
for (const [key, value] of Object.entries(input.command.environment ?? {}))
|
|
252
|
+
args.push("--setenv", key, value);
|
|
253
|
+
args.push("--chdir", input.command.cwd ?? "/workspace", "--", input.command.executable, ...input.command.args ?? []);
|
|
254
|
+
return args;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/agent-runtime/sandbox-bubblewrap-storage.ts
|
|
258
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
259
|
+
import { cp, mkdir, open, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
260
|
+
import { dirname, join } from "node:path";
|
|
261
|
+
import { z as z2 } from "zod";
|
|
262
|
+
|
|
263
|
+
// src/agent-runtime/sandbox-session.ts
|
|
264
|
+
import { posix } from "node:path";
|
|
265
|
+
function sandboxPath(path) {
|
|
266
|
+
const relative = path.startsWith("/workspace/") ? path.slice(11) : path;
|
|
267
|
+
if (path === "/workspace" || path === ".")
|
|
268
|
+
return "/workspace";
|
|
269
|
+
if (!relative || relative.startsWith("/") || relative.includes("\\") || relative.includes("\x00") || relative.split("/").some((part) => part === ".." || part === "" || part === ".")) {
|
|
270
|
+
throw new SandboxError("SANDBOX_PATH", "Expected a canonical workspace-relative path");
|
|
271
|
+
}
|
|
272
|
+
return posix.join("/workspace", relative);
|
|
273
|
+
}
|
|
274
|
+
function createSandboxSession(id, driver, brokerSocket) {
|
|
275
|
+
const spawn = (command, options) => {
|
|
276
|
+
const parsed = SandboxCommandSchema.parse(command);
|
|
277
|
+
return driver.spawn({ ...parsed, cwd: sandboxPath(parsed.cwd ?? ".") }, options);
|
|
278
|
+
};
|
|
279
|
+
return {
|
|
280
|
+
id,
|
|
281
|
+
...brokerSocket && { brokerSocket },
|
|
282
|
+
resolvePath: sandboxPath,
|
|
283
|
+
readBinaryFile: (path) => driver.read(sandboxPath(path)),
|
|
284
|
+
readTextFile: async (path) => new TextDecoder("utf-8", { fatal: true }).decode(await driver.read(sandboxPath(path))),
|
|
285
|
+
writeBinaryFile: (path, bytes) => driver.write(sandboxPath(path), bytes),
|
|
286
|
+
writeTextFile: (path, text) => driver.write(sandboxPath(path), new TextEncoder().encode(text)),
|
|
287
|
+
removePath: (path) => {
|
|
288
|
+
const resolved = sandboxPath(path);
|
|
289
|
+
if (resolved === "/workspace")
|
|
290
|
+
throw new SandboxError("SANDBOX_PATH", "Cannot remove the workspace root");
|
|
291
|
+
return driver.remove(resolved);
|
|
292
|
+
},
|
|
293
|
+
spawn,
|
|
294
|
+
async run(command, options) {
|
|
295
|
+
const result = await (await spawn(command, options)).result;
|
|
296
|
+
return {
|
|
297
|
+
exitCode: result.exitCode,
|
|
298
|
+
stdout: new TextDecoder().decode(result.stdout),
|
|
299
|
+
stderr: new TextDecoder().decode(result.stderr)
|
|
300
|
+
};
|
|
301
|
+
},
|
|
302
|
+
setNetworkPolicy: (policy) => driver.setNetworkPolicy(SandboxNetworkPolicySchema.parse(policy))
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/agent-runtime/sandbox-bubblewrap-storage.ts
|
|
307
|
+
var Key = z2.string().regex(/^[a-f0-9]{64}$/);
|
|
308
|
+
var digest = (value) => createHash("sha256").update(value).digest("hex");
|
|
309
|
+
function sandboxStorage(root, name) {
|
|
310
|
+
const base = join(root, digest(name));
|
|
311
|
+
async function prewarm(input) {
|
|
312
|
+
const files = Object.entries(input.files ?? {}).map(([path, bytes]) => [path, Uint8Array.from(bytes)]).sort(([a], [b]) => a.localeCompare(b));
|
|
313
|
+
const key = digest(JSON.stringify([
|
|
314
|
+
name,
|
|
315
|
+
input.template,
|
|
316
|
+
files.map(([path, bytes]) => [
|
|
317
|
+
sandboxPath(path),
|
|
318
|
+
createHash("sha256").update(bytes).digest("hex")
|
|
319
|
+
])
|
|
320
|
+
]));
|
|
321
|
+
const directory = join(base, "templates", key);
|
|
322
|
+
await mkdir(join(base, "templates"), { recursive: true, mode: 448 });
|
|
323
|
+
try {
|
|
324
|
+
await readFile(join(directory, "ready"));
|
|
325
|
+
return { reused: true, templateKey: key };
|
|
326
|
+
} catch (error) {
|
|
327
|
+
if (!missing(error))
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
const temporary = join(base, "templates", randomUUID());
|
|
331
|
+
await mkdir(join(temporary, "data"), { recursive: true, mode: 448 });
|
|
332
|
+
try {
|
|
333
|
+
for (const [path, bytes] of files) {
|
|
334
|
+
const resolved = sandboxPath(path);
|
|
335
|
+
if (resolved === "/workspace")
|
|
336
|
+
throw new SandboxError("SANDBOX_PATH", "Template file cannot be the workspace root");
|
|
337
|
+
const target = join(temporary, "data", resolved.slice(11));
|
|
338
|
+
await mkdir(dirname(target), { recursive: true, mode: 448 });
|
|
339
|
+
await writeFile(target, bytes, { flag: "wx", mode: 384 });
|
|
340
|
+
}
|
|
341
|
+
await writeFile(join(temporary, "ready"), JSON.stringify({ backend: name, templateKey: key }));
|
|
342
|
+
try {
|
|
343
|
+
await rename(temporary, directory);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (!(error instanceof Error && ("code" in error) && ["EEXIST", "ENOTEMPTY"].includes(String(error.code))))
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
return { reused: false, templateKey: key };
|
|
349
|
+
} finally {
|
|
350
|
+
await rm(temporary, { recursive: true, force: true });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async function acquire(id) {
|
|
354
|
+
const directory = join(base, "sessions", Key.parse(id));
|
|
355
|
+
await mkdir(directory, { recursive: true, mode: 448 });
|
|
356
|
+
try {
|
|
357
|
+
const lease = await open(join(directory, "lease"), "wx", 384);
|
|
358
|
+
try {
|
|
359
|
+
await lease.writeFile(String(process.pid));
|
|
360
|
+
} catch (error) {
|
|
361
|
+
await rm(join(directory, "lease"), { force: true });
|
|
362
|
+
throw error;
|
|
363
|
+
} finally {
|
|
364
|
+
await lease.close();
|
|
365
|
+
}
|
|
366
|
+
} catch (error) {
|
|
367
|
+
if (error instanceof Error && "code" in error && error.code === "EEXIST")
|
|
368
|
+
throw new SandboxError("SANDBOX_BUSY", "Sandbox session already has an owner");
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
return { directory, release: () => rm(join(directory, "lease"), { force: true }) };
|
|
372
|
+
}
|
|
373
|
+
async function create(template, saved) {
|
|
374
|
+
Key.parse(template);
|
|
375
|
+
if (saved && (saved.backend !== name || saved.templateKey !== template))
|
|
376
|
+
throw new SandboxError("SANDBOX_STATE_MISMATCH", "Reconnect state does not match backend and template");
|
|
377
|
+
const templateDirectory = join(base, "templates", template);
|
|
378
|
+
const record = z2.object({ backend: z2.string(), templateKey: z2.string() }).parse(JSON.parse(await readFile(join(templateDirectory, "ready"), "utf8")));
|
|
379
|
+
if (record.backend !== name || record.templateKey !== template)
|
|
380
|
+
throw new SandboxError("SANDBOX_STATE_MISMATCH", "Template belongs to another backend");
|
|
381
|
+
const state = saved ? SandboxStateSchema.parse(saved) : { backend: name, sessionId: digest(randomUUID()), templateKey: template };
|
|
382
|
+
if (state.backend !== name || state.templateKey !== template)
|
|
383
|
+
throw new SandboxError("SANDBOX_STATE_MISMATCH", "Reconnect state does not match backend and template");
|
|
384
|
+
const lease = await acquire(state.sessionId);
|
|
385
|
+
try {
|
|
386
|
+
const statePath = join(lease.directory, "state.json");
|
|
387
|
+
if (saved) {
|
|
388
|
+
const existing = SandboxStateSchema.parse(JSON.parse(await readFile(statePath, "utf8")));
|
|
389
|
+
if (JSON.stringify(existing) !== JSON.stringify(state))
|
|
390
|
+
throw new SandboxError("SANDBOX_STATE_MISMATCH", "Reconnect identity does not match saved session");
|
|
391
|
+
} else {
|
|
392
|
+
await cp(join(templateDirectory, "data"), join(lease.directory, "workspace"), {
|
|
393
|
+
recursive: true
|
|
394
|
+
});
|
|
395
|
+
await writeFile(statePath, JSON.stringify(state), { flag: "wx", mode: 384 });
|
|
396
|
+
}
|
|
397
|
+
return { ...lease, workspace: join(lease.directory, "workspace"), state };
|
|
398
|
+
} catch (error) {
|
|
399
|
+
try {
|
|
400
|
+
if (!saved)
|
|
401
|
+
await rm(lease.directory, { recursive: true, force: true });
|
|
402
|
+
} finally {
|
|
403
|
+
await lease.release();
|
|
404
|
+
}
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return { prewarm, create, acquire };
|
|
409
|
+
}
|
|
410
|
+
function missing(error) {
|
|
411
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/agent-runtime/sandbox-coding.ts
|
|
415
|
+
import { relative, sep } from "node:path";
|
|
416
|
+
function sandboxCommandCwd(root, cwd) {
|
|
417
|
+
const path = relative(root, cwd).split(sep).join("/");
|
|
418
|
+
return sandboxPath(path || ".");
|
|
419
|
+
}
|
|
420
|
+
function createSandboxCodingTools(handle, config) {
|
|
421
|
+
const binding = handle.coding;
|
|
422
|
+
if (!binding)
|
|
423
|
+
throw new SandboxError("SANDBOX_UNAVAILABLE", "Backend does not expose a host coding workspace");
|
|
424
|
+
binding.assertActive();
|
|
425
|
+
const assertToolActive = () => {
|
|
426
|
+
try {
|
|
427
|
+
binding.assertActive();
|
|
428
|
+
} catch (error) {
|
|
429
|
+
if (error instanceof SandboxError)
|
|
430
|
+
codingRefusal("SANDBOX_UNAVAILABLE", "Sandbox session cannot execute this operation", {
|
|
431
|
+
details: { reason: error.code }
|
|
432
|
+
});
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
const { requiredRestrictions, ...options } = config;
|
|
437
|
+
return createAgentCodingTools({
|
|
438
|
+
...options,
|
|
439
|
+
root: binding.root,
|
|
440
|
+
sandbox: {
|
|
441
|
+
adapter: binding.adapter,
|
|
442
|
+
required: requiredRestrictions ?? [
|
|
443
|
+
"write-contained",
|
|
444
|
+
"process-contained",
|
|
445
|
+
"secrets-hidden"
|
|
446
|
+
]
|
|
447
|
+
},
|
|
448
|
+
authorize(input) {
|
|
449
|
+
assertToolActive();
|
|
450
|
+
return options.authorize(input);
|
|
451
|
+
},
|
|
452
|
+
authorizePath(input) {
|
|
453
|
+
assertToolActive();
|
|
454
|
+
return options.authorizePath?.(input) ?? true;
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/agent-runtime/sandbox-coding-adapter.ts
|
|
460
|
+
function createSandboxCodingAdapter(input) {
|
|
461
|
+
const { workspace, socket, executable, getPolicy, processes, assertActive } = input;
|
|
462
|
+
let policyRevision = 0;
|
|
463
|
+
const preparations = new WeakMap;
|
|
464
|
+
const grade = () => ({
|
|
465
|
+
grade: "full",
|
|
466
|
+
restrictions: getPolicy() === "deny-all" ? ["network-denied", "write-contained", "secrets-hidden", "process-contained"] : ["write-contained", "secrets-hidden", "process-contained"]
|
|
467
|
+
});
|
|
468
|
+
const adapter = {
|
|
469
|
+
probe: grade,
|
|
470
|
+
prepare(command) {
|
|
471
|
+
assertActive();
|
|
472
|
+
if (missingSandboxRestrictions(grade(), command.required ?? []).length)
|
|
473
|
+
throw new SandboxError("SANDBOX_NETWORK_DENIED", "Sandbox policy no longer satisfies required restrictions");
|
|
474
|
+
const environment = {};
|
|
475
|
+
preparations.set(environment, policyRevision);
|
|
476
|
+
return {
|
|
477
|
+
executable,
|
|
478
|
+
args: bubblewrapArgs({
|
|
479
|
+
workspace,
|
|
480
|
+
socket,
|
|
481
|
+
network: getPolicy(),
|
|
482
|
+
command: {
|
|
483
|
+
executable: command.executable,
|
|
484
|
+
args: [...command.args],
|
|
485
|
+
cwd: sandboxCommandCwd(workspace, command.cwd),
|
|
486
|
+
environment: { ...command.environment }
|
|
487
|
+
}
|
|
488
|
+
}),
|
|
489
|
+
environment
|
|
490
|
+
};
|
|
491
|
+
},
|
|
492
|
+
spawn(command) {
|
|
493
|
+
assertActive();
|
|
494
|
+
if (preparations.get(command.environment) !== policyRevision)
|
|
495
|
+
throw new SandboxError("SANDBOX_BUSY", "Sandbox policy changed after command preparation");
|
|
496
|
+
preparations.delete(command.environment);
|
|
497
|
+
return processes.spawn(command);
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
return {
|
|
501
|
+
adapter,
|
|
502
|
+
async refresh() {
|
|
503
|
+
policyRevision++;
|
|
504
|
+
await probeAgentProcessSandbox(adapter, { refresh: true });
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// src/agent-runtime/sandbox-process.ts
|
|
510
|
+
import { spawn } from "node:child_process";
|
|
511
|
+
function spawnSandboxProcess(executable, args, limits, options = {}, onSpawn) {
|
|
512
|
+
options.signal?.throwIfAborted();
|
|
513
|
+
const child = spawn(executable, args, {
|
|
514
|
+
env: {},
|
|
515
|
+
detached: true,
|
|
516
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
517
|
+
});
|
|
518
|
+
onSpawn?.(child);
|
|
519
|
+
const stdout = [];
|
|
520
|
+
const stderr = [];
|
|
521
|
+
let bytes = 0;
|
|
522
|
+
let failure;
|
|
523
|
+
let finished = false;
|
|
524
|
+
const kill = () => {
|
|
525
|
+
if (finished || child.pid === undefined)
|
|
526
|
+
return;
|
|
527
|
+
try {
|
|
528
|
+
process.kill(-child.pid, "SIGKILL");
|
|
529
|
+
} catch (error) {
|
|
530
|
+
if (!(error instanceof Error && ("code" in error) && error.code === "ESRCH"))
|
|
531
|
+
failure ??= error;
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
const abort = () => {
|
|
535
|
+
failure ??= options.signal?.reason ?? new Error("Sandbox command aborted");
|
|
536
|
+
kill();
|
|
537
|
+
};
|
|
538
|
+
const timer = setTimeout(() => {
|
|
539
|
+
failure ??= new SandboxError("SANDBOX_LIMIT", "Sandbox command deadline exceeded");
|
|
540
|
+
kill();
|
|
541
|
+
}, limits.timeoutMs);
|
|
542
|
+
timer.unref();
|
|
543
|
+
const result = new Promise((resolve, reject) => {
|
|
544
|
+
const collect = (target) => (chunk) => {
|
|
545
|
+
bytes += chunk.byteLength;
|
|
546
|
+
if (bytes > limits.maxOutputBytes) {
|
|
547
|
+
failure ??= new SandboxError("SANDBOX_LIMIT", "Sandbox output limit exceeded");
|
|
548
|
+
kill();
|
|
549
|
+
} else
|
|
550
|
+
target.push(chunk);
|
|
551
|
+
};
|
|
552
|
+
child.stdout.on("data", collect(stdout));
|
|
553
|
+
child.stderr.on("data", collect(stderr));
|
|
554
|
+
child.on("error", (error) => {
|
|
555
|
+
failure ??= new SandboxError("SANDBOX_UNAVAILABLE", "Sandbox process could not start", { cause: error });
|
|
556
|
+
});
|
|
557
|
+
child.stdin.on("error", (error) => {
|
|
558
|
+
if (!(("code" in error) && error.code === "EPIPE")) {
|
|
559
|
+
failure ??= error;
|
|
560
|
+
kill();
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
child.on("close", (code) => {
|
|
564
|
+
finished = true;
|
|
565
|
+
clearTimeout(timer);
|
|
566
|
+
options.signal?.removeEventListener("abort", abort);
|
|
567
|
+
if (failure !== undefined)
|
|
568
|
+
reject(failure);
|
|
569
|
+
else
|
|
570
|
+
resolve({
|
|
571
|
+
exitCode: code ?? 137,
|
|
572
|
+
stdout: Buffer.concat(stdout),
|
|
573
|
+
stderr: Buffer.concat(stderr)
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
result.catch(() => {
|
|
578
|
+
return;
|
|
579
|
+
});
|
|
580
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
581
|
+
if (options.signal?.aborted)
|
|
582
|
+
abort();
|
|
583
|
+
child.stdin.end(options.stdin);
|
|
584
|
+
return {
|
|
585
|
+
result,
|
|
586
|
+
async stop() {
|
|
587
|
+
kill();
|
|
588
|
+
await result.then(() => {
|
|
589
|
+
return;
|
|
590
|
+
}, () => {
|
|
591
|
+
return;
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// src/agent-runtime/sandbox-process-owner.ts
|
|
598
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
599
|
+
function sandboxProcessOwner(assertActive, maximum) {
|
|
600
|
+
const children = new Map;
|
|
601
|
+
const admit = () => {
|
|
602
|
+
assertActive();
|
|
603
|
+
if (children.size >= maximum)
|
|
604
|
+
throw new SandboxError("SANDBOX_BUSY", "Sandbox command concurrency limit reached");
|
|
605
|
+
};
|
|
606
|
+
const track = (child) => {
|
|
607
|
+
const closed = new Promise((resolve) => {
|
|
608
|
+
child.once("close", () => {
|
|
609
|
+
children.delete(child);
|
|
610
|
+
resolve();
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
children.set(child, closed);
|
|
614
|
+
};
|
|
615
|
+
return {
|
|
616
|
+
admit,
|
|
617
|
+
track,
|
|
618
|
+
get size() {
|
|
619
|
+
return children.size;
|
|
620
|
+
},
|
|
621
|
+
spawn(input) {
|
|
622
|
+
admit();
|
|
623
|
+
const child = spawn2(input.executable, [...input.args], {
|
|
624
|
+
cwd: input.cwd,
|
|
625
|
+
env: input.environment,
|
|
626
|
+
detached: true,
|
|
627
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
628
|
+
});
|
|
629
|
+
child.stdin.end();
|
|
630
|
+
track(child);
|
|
631
|
+
return child;
|
|
632
|
+
},
|
|
633
|
+
async stop() {
|
|
634
|
+
const pending = [...children];
|
|
635
|
+
for (const [child] of pending) {
|
|
636
|
+
if (child.pid === undefined)
|
|
637
|
+
continue;
|
|
638
|
+
try {
|
|
639
|
+
process.kill(-child.pid, "SIGKILL");
|
|
640
|
+
} catch (error) {
|
|
641
|
+
if (!(error instanceof Error && ("code" in error) && error.code === "ESRCH"))
|
|
642
|
+
throw error;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
await Promise.all(pending.map(([, closed]) => closed));
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// src/agent-runtime/sandbox-bubblewrap.ts
|
|
651
|
+
async function createBubblewrapSandboxBackend(config) {
|
|
652
|
+
if (process.platform !== "linux")
|
|
653
|
+
throw new SandboxError("SANDBOX_UNAVAILABLE", "Bubblewrap requires Linux");
|
|
654
|
+
const executable = config.executable ?? "/usr/bin/bwrap";
|
|
655
|
+
const name = z3.string().min(1).parse(config.name ?? "bubblewrap");
|
|
656
|
+
await mkdir2(config.stateDirectory, { recursive: true, mode: 448 });
|
|
657
|
+
const root = await realpath(config.stateDirectory);
|
|
658
|
+
const storage = sandboxStorage(root, name);
|
|
659
|
+
const maximum = z3.number().int().positive().max(1024).parse(config.maxConcurrentCommands ?? 8);
|
|
660
|
+
return {
|
|
661
|
+
name,
|
|
662
|
+
prewarm: storage.prewarm,
|
|
663
|
+
async create(input) {
|
|
664
|
+
let policy = SandboxNetworkPolicySchema.parse(input.network);
|
|
665
|
+
const stored = await storage.create(input.template, input.state);
|
|
666
|
+
async function abandon() {
|
|
667
|
+
try {
|
|
668
|
+
if (!input.state)
|
|
669
|
+
await rm2(stored.directory, { recursive: true, force: true });
|
|
670
|
+
} finally {
|
|
671
|
+
await stored.release();
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const temporary = await mkdtemp(join2(tmpdir(), "stitchkit-sandbox-")).catch(async (error) => {
|
|
675
|
+
await abandon();
|
|
676
|
+
throw error;
|
|
677
|
+
});
|
|
678
|
+
const socket = join2(temporary, "network.sock");
|
|
679
|
+
let active = true;
|
|
680
|
+
let deleted = false;
|
|
681
|
+
let stopPending;
|
|
682
|
+
const broker = await createSandboxBroker(socket, () => policy, config.onBrokerError).catch(async (error) => {
|
|
683
|
+
await abandon();
|
|
684
|
+
await rm2(temporary, { recursive: true, force: true });
|
|
685
|
+
throw error;
|
|
686
|
+
});
|
|
687
|
+
const assertActive = () => {
|
|
688
|
+
if (!active)
|
|
689
|
+
throw new SandboxError("SANDBOX_STOPPED", "Sandbox session is stopped");
|
|
690
|
+
};
|
|
691
|
+
const processes = sandboxProcessOwner(assertActive, maximum);
|
|
692
|
+
const coding = createSandboxCodingAdapter({
|
|
693
|
+
workspace: stored.workspace,
|
|
694
|
+
socket,
|
|
695
|
+
executable,
|
|
696
|
+
getPolicy: () => policy,
|
|
697
|
+
processes,
|
|
698
|
+
assertActive
|
|
699
|
+
});
|
|
700
|
+
const driver = {
|
|
701
|
+
async spawn(command, options) {
|
|
702
|
+
processes.admit();
|
|
703
|
+
const process2 = spawnSandboxProcess(executable, bubblewrapArgs({ workspace: stored.workspace, socket, command, network: policy }), {
|
|
704
|
+
timeoutMs: command.timeoutMs ?? 30000,
|
|
705
|
+
maxOutputBytes: command.maxOutputBytes ?? 1048576
|
|
706
|
+
}, options, processes.track);
|
|
707
|
+
return process2;
|
|
708
|
+
},
|
|
709
|
+
async read(path) {
|
|
710
|
+
return (await execute("/usr/bin/cat", ["--", path])).stdout;
|
|
711
|
+
},
|
|
712
|
+
async write(path, bytes) {
|
|
713
|
+
if (bytes.byteLength > 1048576)
|
|
714
|
+
throw new SandboxError("SANDBOX_LIMIT", "Sandbox file write limit exceeded");
|
|
715
|
+
await execute("/usr/bin/mkdir", ["-p", "--", posix2.dirname(path)]);
|
|
716
|
+
await execute("/usr/bin/tee", ["--", path], bytes);
|
|
717
|
+
},
|
|
718
|
+
async remove(path) {
|
|
719
|
+
await execute("/usr/bin/rm", ["-rf", "--", path]);
|
|
720
|
+
},
|
|
721
|
+
async setNetworkPolicy(next) {
|
|
722
|
+
assertActive();
|
|
723
|
+
if (processes.size)
|
|
724
|
+
throw new SandboxError("SANDBOX_BUSY", "Stop live commands before changing network policy");
|
|
725
|
+
broker.abortRequests();
|
|
726
|
+
policy = SandboxNetworkPolicySchema.parse(next);
|
|
727
|
+
await coding.refresh();
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
async function execute(binary, args, stdin) {
|
|
731
|
+
const result = await (await driver.spawn({ executable: binary, args }, { stdin })).result;
|
|
732
|
+
if (result.exitCode !== 0)
|
|
733
|
+
throw new SandboxError("SANDBOX_UNAVAILABLE", "Sandbox file operation failed");
|
|
734
|
+
return result;
|
|
735
|
+
}
|
|
736
|
+
function stop() {
|
|
737
|
+
if (stopPending)
|
|
738
|
+
return stopPending;
|
|
739
|
+
active = false;
|
|
740
|
+
stopPending = (async () => {
|
|
741
|
+
await processes.stop();
|
|
742
|
+
try {
|
|
743
|
+
await broker.close();
|
|
744
|
+
} finally {
|
|
745
|
+
try {
|
|
746
|
+
await rm2(temporary, { recursive: true, force: true });
|
|
747
|
+
} finally {
|
|
748
|
+
await stored.release();
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
})();
|
|
752
|
+
return stopPending;
|
|
753
|
+
}
|
|
754
|
+
try {
|
|
755
|
+
await execute("/usr/bin/true", []);
|
|
756
|
+
} catch (error) {
|
|
757
|
+
await stop();
|
|
758
|
+
if (!input.state)
|
|
759
|
+
await rm2(stored.directory, { recursive: true, force: true });
|
|
760
|
+
throw new SandboxError("SANDBOX_UNAVAILABLE", "Bubblewrap could not establish isolation", { cause: error });
|
|
761
|
+
}
|
|
762
|
+
return {
|
|
763
|
+
coding: { root: stored.workspace, adapter: coding.adapter, assertActive },
|
|
764
|
+
session: createSandboxSession(stored.state.sessionId, driver, "/run/stitchkit-network.sock"),
|
|
765
|
+
captureState() {
|
|
766
|
+
if (deleted)
|
|
767
|
+
throw new SandboxError("SANDBOX_STOPPED", "Sandbox session is deleted");
|
|
768
|
+
return { ...stored.state };
|
|
769
|
+
},
|
|
770
|
+
stop,
|
|
771
|
+
shutdown: stop,
|
|
772
|
+
async delete() {
|
|
773
|
+
if (deleted)
|
|
774
|
+
return;
|
|
775
|
+
await stop();
|
|
776
|
+
const lease = await storage.acquire(stored.state.sessionId);
|
|
777
|
+
try {
|
|
778
|
+
await rm2(lease.directory, { recursive: true, force: true });
|
|
779
|
+
deleted = true;
|
|
780
|
+
} catch (error) {
|
|
781
|
+
await lease.release();
|
|
782
|
+
throw error;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
export {
|
|
790
|
+
createSandboxSession,
|
|
791
|
+
createSandboxCodingTools,
|
|
792
|
+
createBubblewrapSandboxBackend,
|
|
793
|
+
SandboxStateSchema,
|
|
794
|
+
SandboxNetworkPolicySchema,
|
|
795
|
+
SandboxError,
|
|
796
|
+
SandboxCommandSchema
|
|
797
|
+
};
|