@odla-ai/harness 0.9.4 → 0.10.1
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/{chunk-XSUUHWNX.js → chunk-WM34GGTK.js} +552 -57
- package/dist/chunk-WM34GGTK.js.map +1 -0
- package/dist/code-runtime-cli.cjs +557 -63
- package/dist/code-runtime-cli.cjs.map +1 -1
- package/dist/code-runtime-cli.js +1 -1
- package/dist/node.cjs +563 -67
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +40 -4
- package/dist/node.d.ts +40 -4
- package/dist/node.js +3 -1
- package/dist/node.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-XSUUHWNX.js.map +0 -1
package/dist/node.cjs
CHANGED
|
@@ -56,6 +56,7 @@ __export(node_exports, {
|
|
|
56
56
|
installedDependencies: () => installedDependencies,
|
|
57
57
|
integrateSubGoals: () => integrateSubGoals,
|
|
58
58
|
isCheckpointEffectCompleted: () => isCheckpointEffectCompleted,
|
|
59
|
+
materializeCodeRuntimeArchive: () => materializeCodeRuntimeArchive,
|
|
59
60
|
materializeCodeRuntimeSource: () => materializeCodeRuntimeSource,
|
|
60
61
|
materializeCommandWorkspace: () => materializeCommandWorkspace,
|
|
61
62
|
materializeGitTree: () => materializeGitTree,
|
|
@@ -232,7 +233,7 @@ async function selectContainerEngine(requested = "auto", options = {}) {
|
|
|
232
233
|
throw new TypeError("no supported container engine found");
|
|
233
234
|
}
|
|
234
235
|
function inspectRootlessPodman() {
|
|
235
|
-
return new Promise((
|
|
236
|
+
return new Promise((resolve8, reject) => {
|
|
236
237
|
(0, import_node_child_process.execFile)(
|
|
237
238
|
"podman",
|
|
238
239
|
["info", "--format", "{{.Host.Security.Rootless}}"],
|
|
@@ -242,7 +243,7 @@ function inspectRootlessPodman() {
|
|
|
242
243
|
reject(new TypeError("could not verify that the active Podman service is rootless"));
|
|
243
244
|
return;
|
|
244
245
|
}
|
|
245
|
-
|
|
246
|
+
resolve8(stdout.trim() === "true");
|
|
246
247
|
}
|
|
247
248
|
);
|
|
248
249
|
});
|
|
@@ -1140,6 +1141,34 @@ function createCodeRuntimeControlClient(options) {
|
|
|
1140
1141
|
}
|
|
1141
1142
|
return value;
|
|
1142
1143
|
};
|
|
1144
|
+
const callRaw = async (path, body) => {
|
|
1145
|
+
const timeout = AbortSignal.timeout(modelRequestTimeoutMs);
|
|
1146
|
+
const signals = [options.signal, timeout].filter((item) => Boolean(item));
|
|
1147
|
+
const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
1148
|
+
let response2;
|
|
1149
|
+
try {
|
|
1150
|
+
response2 = await request(`${endpoint}${path}`, {
|
|
1151
|
+
method: "POST",
|
|
1152
|
+
headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json" },
|
|
1153
|
+
body: JSON.stringify(body),
|
|
1154
|
+
redirect: "error",
|
|
1155
|
+
signal
|
|
1156
|
+
});
|
|
1157
|
+
} catch (cause) {
|
|
1158
|
+
if (options.signal?.aborted) throw cause;
|
|
1159
|
+
throw new CodeRuntimeControlError("Code source archive is unavailable", 503, "transport_unavailable");
|
|
1160
|
+
}
|
|
1161
|
+
if (!response2.ok) {
|
|
1162
|
+
const value = await response2.json().catch(() => null);
|
|
1163
|
+
const problem = record2(record2(value)?.error);
|
|
1164
|
+
throw new CodeRuntimeControlError(
|
|
1165
|
+
typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
|
|
1166
|
+
response2.status,
|
|
1167
|
+
typeof problem?.code === "string" ? problem.code : void 0
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
return response2;
|
|
1171
|
+
};
|
|
1143
1172
|
return {
|
|
1144
1173
|
heartbeat: async (version, capabilities) => {
|
|
1145
1174
|
validateHeartbeat(version, capabilities);
|
|
@@ -1151,6 +1180,14 @@ function createCodeRuntimeControlClient(options) {
|
|
|
1151
1180
|
source: async (sessionId) => parseSource(
|
|
1152
1181
|
await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
1153
1182
|
),
|
|
1183
|
+
sourceArchive: async (sessionId, alias) => {
|
|
1184
|
+
if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias)) throw new TypeError("invalid Code source alias");
|
|
1185
|
+
const response2 = await callRaw(
|
|
1186
|
+
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source/archive`,
|
|
1187
|
+
{ alias }
|
|
1188
|
+
);
|
|
1189
|
+
return parseSourceArchiveResponse(response2, alias);
|
|
1190
|
+
},
|
|
1154
1191
|
infer: async (sessionId, inference) => {
|
|
1155
1192
|
const value = record2(await call(
|
|
1156
1193
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
@@ -1215,9 +1252,70 @@ function createCodeRuntimeControlClient(options) {
|
|
|
1215
1252
|
}
|
|
1216
1253
|
};
|
|
1217
1254
|
}
|
|
1255
|
+
var ARCHIVE_LIMIT_MAXIMA = {
|
|
1256
|
+
maxCompressedBytes: 64 * 1024 * 1024,
|
|
1257
|
+
maxDecompressedBytes: 96 * 1024 * 1024,
|
|
1258
|
+
maxEntries: 2e5,
|
|
1259
|
+
maxFiles: 1e5,
|
|
1260
|
+
maxFileBytes: 16 * 1024 * 1024,
|
|
1261
|
+
maxTotalFileBytes: 80 * 1024 * 1024,
|
|
1262
|
+
maxPathBytes: 4096,
|
|
1263
|
+
maxExtendedHeaderBytes: 32 * 1024
|
|
1264
|
+
};
|
|
1265
|
+
async function parseSourceArchiveResponse(response2, expectedAlias) {
|
|
1266
|
+
const alias = response2.headers.get("x-odla-source-alias") ?? "";
|
|
1267
|
+
const repository = response2.headers.get("x-odla-source-repository") ?? "";
|
|
1268
|
+
const commitSha = response2.headers.get("x-odla-source-commit") ?? "";
|
|
1269
|
+
if (alias !== expectedAlias || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository) || !/^[0-9a-f]{40}$/.test(commitSha)) {
|
|
1270
|
+
await response2.body?.cancel().catch(() => void 0);
|
|
1271
|
+
throw new CodeRuntimeControlError("invalid Code source archive identity", 502, "invalid_response");
|
|
1272
|
+
}
|
|
1273
|
+
const limits = Object.fromEntries(Object.entries(ARCHIVE_LIMIT_MAXIMA).map(([key, maximum]) => {
|
|
1274
|
+
const header = `x-odla-${key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}`;
|
|
1275
|
+
const value = response2.headers.get(header) ?? "";
|
|
1276
|
+
if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)) || Number(value) < 1 || Number(value) > maximum) {
|
|
1277
|
+
throw new CodeRuntimeControlError("invalid Code source archive limits", 502, "invalid_response");
|
|
1278
|
+
}
|
|
1279
|
+
return [key, Number(value)];
|
|
1280
|
+
}));
|
|
1281
|
+
const length = response2.headers.get("content-length");
|
|
1282
|
+
if (length && (!/^\d+$/.test(length) || Number(length) > limits.maxCompressedBytes)) {
|
|
1283
|
+
await response2.body?.cancel().catch(() => void 0);
|
|
1284
|
+
throw new CodeRuntimeControlError("Code source archive exceeds its byte bound", 502, "invalid_response");
|
|
1285
|
+
}
|
|
1286
|
+
const compressed = await readBoundedResponse(response2.body, limits.maxCompressedBytes);
|
|
1287
|
+
return { alias, repository, commitSha, compressed, limits };
|
|
1288
|
+
}
|
|
1289
|
+
async function readBoundedResponse(body, maximum) {
|
|
1290
|
+
if (!body) throw new CodeRuntimeControlError("Code source archive body is missing", 502, "invalid_response");
|
|
1291
|
+
const reader = body.getReader();
|
|
1292
|
+
const chunks = [];
|
|
1293
|
+
let size = 0;
|
|
1294
|
+
try {
|
|
1295
|
+
while (true) {
|
|
1296
|
+
const { done, value } = await reader.read();
|
|
1297
|
+
if (done) break;
|
|
1298
|
+
size += value.byteLength;
|
|
1299
|
+
if (size > maximum) {
|
|
1300
|
+
await reader.cancel().catch(() => void 0);
|
|
1301
|
+
throw new CodeRuntimeControlError("Code source archive exceeds its byte bound", 502, "invalid_response");
|
|
1302
|
+
}
|
|
1303
|
+
chunks.push(value);
|
|
1304
|
+
}
|
|
1305
|
+
} finally {
|
|
1306
|
+
reader.releaseLock();
|
|
1307
|
+
}
|
|
1308
|
+
const result = new Uint8Array(size);
|
|
1309
|
+
let offset = 0;
|
|
1310
|
+
for (const chunk of chunks) {
|
|
1311
|
+
result.set(chunk, offset);
|
|
1312
|
+
offset += chunk.byteLength;
|
|
1313
|
+
}
|
|
1314
|
+
return result;
|
|
1315
|
+
}
|
|
1218
1316
|
|
|
1219
1317
|
// src/code-runtime.ts
|
|
1220
|
-
var CODE_RUNTIME_PROTOCOL_VERSION =
|
|
1318
|
+
var CODE_RUNTIME_PROTOCOL_VERSION = 3;
|
|
1221
1319
|
async function runCodeRuntimeHeartbeatLoop(options) {
|
|
1222
1320
|
const heartbeatMs = options.heartbeatMs ?? 15e3;
|
|
1223
1321
|
if (!Number.isSafeInteger(heartbeatMs) || heartbeatMs < 1e3 || heartbeatMs > 3e5) {
|
|
@@ -1286,12 +1384,12 @@ function retryableControlFailure(value) {
|
|
|
1286
1384
|
return failure.status === 408 || failure.status === 425 || failure.status === 429 || failure.status >= 500;
|
|
1287
1385
|
}
|
|
1288
1386
|
function wait(ms, signal) {
|
|
1289
|
-
return new Promise((
|
|
1290
|
-
if (signal?.aborted) return
|
|
1291
|
-
const timer = setTimeout(
|
|
1387
|
+
return new Promise((resolve8) => {
|
|
1388
|
+
if (signal?.aborted) return resolve8();
|
|
1389
|
+
const timer = setTimeout(resolve8, ms);
|
|
1292
1390
|
signal?.addEventListener("abort", () => {
|
|
1293
1391
|
clearTimeout(timer);
|
|
1294
|
-
|
|
1392
|
+
resolve8();
|
|
1295
1393
|
}, { once: true });
|
|
1296
1394
|
});
|
|
1297
1395
|
}
|
|
@@ -1940,12 +2038,17 @@ function codeCommandMetadata(payload, resume) {
|
|
|
1940
2038
|
if (role !== "coding" && role !== "review" || typeof title !== "string" || typeof prompt !== "string") {
|
|
1941
2039
|
throw new TypeError(`invalid Code ${resume ? "resume" : "start"} metadata`);
|
|
1942
2040
|
}
|
|
2041
|
+
if (payload.readOnly !== void 0 && typeof payload.readOnly !== "boolean") {
|
|
2042
|
+
throw new TypeError(`invalid Code ${resume ? "resume" : "start"} read-only capability`);
|
|
2043
|
+
}
|
|
2044
|
+
const readOnly = role === "review" || payload.readOnly === true;
|
|
1943
2045
|
const planning = trusted?.planningInputDigest;
|
|
1944
2046
|
const attestation = trusted?.attestationDigest;
|
|
1945
2047
|
const repository = trusted?.repository;
|
|
1946
2048
|
const baseCommitSha = trusted?.commitSha;
|
|
1947
2049
|
const sourceTreeDigest = trusted?.treeDigest;
|
|
1948
|
-
|
|
2050
|
+
const hostMaterialized = record3(payload.sourceSet) !== null && sourceTreeDigest === void 0;
|
|
2051
|
+
if (typeof repository !== "string" || !repository.includes("/") || typeof baseCommitSha !== "string" || !/^[0-9a-f]{40}$/.test(baseCommitSha) || !hostMaterialized && (typeof sourceTreeDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(sourceTreeDigest))) {
|
|
1949
2052
|
throw new TypeError(`invalid Code ${resume ? "resume" : "start"} trusted base`);
|
|
1950
2053
|
}
|
|
1951
2054
|
if (!Number.isSafeInteger(maxTokensPerInteraction) || Number(maxTokensPerInteraction) < 4e3 || Number(maxTokensPerInteraction) > 2e5) {
|
|
@@ -1953,6 +2056,7 @@ function codeCommandMetadata(payload, resume) {
|
|
|
1953
2056
|
}
|
|
1954
2057
|
return {
|
|
1955
2058
|
role,
|
|
2059
|
+
readOnly,
|
|
1956
2060
|
title,
|
|
1957
2061
|
prompt,
|
|
1958
2062
|
maxTokensPerInteraction: Number(maxTokensPerInteraction),
|
|
@@ -1960,7 +2064,7 @@ function codeCommandMetadata(payload, resume) {
|
|
|
1960
2064
|
attestationDigest: typeof attestation === "string" ? attestation : "resume",
|
|
1961
2065
|
repository,
|
|
1962
2066
|
baseCommitSha,
|
|
1963
|
-
sourceTreeDigest
|
|
2067
|
+
sourceTreeDigest: typeof sourceTreeDigest === "string" ? sourceTreeDigest : null
|
|
1964
2068
|
};
|
|
1965
2069
|
}
|
|
1966
2070
|
function codeLocalSource(payload) {
|
|
@@ -2025,19 +2129,320 @@ async function prepareRuntimeLocalSource(input) {
|
|
|
2025
2129
|
}
|
|
2026
2130
|
|
|
2027
2131
|
// src/code-runtime-source.ts
|
|
2132
|
+
var import_promises10 = require("fs/promises");
|
|
2133
|
+
var import_node_os4 = require("os");
|
|
2134
|
+
var import_node_path10 = require("path");
|
|
2135
|
+
|
|
2136
|
+
// src/code-runtime-archive.ts
|
|
2137
|
+
var import_node_zlib = require("zlib");
|
|
2028
2138
|
var import_promises8 = require("fs/promises");
|
|
2029
2139
|
var import_node_os3 = require("os");
|
|
2030
2140
|
var import_node_path8 = require("path");
|
|
2031
2141
|
var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
2032
2142
|
var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
2143
|
+
var MAX_NUL_SHARE = 0.1;
|
|
2144
|
+
async function materializeCodeRuntimeArchive(archive, visiblePaths, tempRoot = (0, import_node_os3.tmpdir)()) {
|
|
2145
|
+
if (!archive.compressed.byteLength || archive.compressed.byteLength > archive.limits.maxCompressedBytes) {
|
|
2146
|
+
throw new TypeError("Code source archive exceeds its compressed byte bound");
|
|
2147
|
+
}
|
|
2148
|
+
let bytes;
|
|
2149
|
+
try {
|
|
2150
|
+
bytes = archive.compressed[0] === 31 && archive.compressed[1] === 139 ? (0, import_node_zlib.gunzipSync)(archive.compressed, { maxOutputLength: archive.limits.maxDecompressedBytes }) : archive.compressed;
|
|
2151
|
+
} catch {
|
|
2152
|
+
throw new TypeError("Code source archive is not a valid bounded gzip stream");
|
|
2153
|
+
}
|
|
2154
|
+
if (bytes.byteLength > archive.limits.maxDecompressedBytes) {
|
|
2155
|
+
throw new TypeError("Code source archive exceeds its decompressed byte bound");
|
|
2156
|
+
}
|
|
2157
|
+
const entries = parseTar(bytes, archive.limits);
|
|
2158
|
+
const root = await (0, import_promises8.mkdtemp)((0, import_node_path8.join)(tempRoot, "odla-code-archive-"));
|
|
2159
|
+
const sourceDir = (0, import_node_path8.join)(root, "source");
|
|
2160
|
+
await (0, import_promises8.mkdir)(sourceDir);
|
|
2161
|
+
let visible = 0;
|
|
2162
|
+
try {
|
|
2163
|
+
for (const entry of entries) {
|
|
2164
|
+
if (entry.directory || visiblePaths && !visiblePaths.has(entry.path)) continue;
|
|
2165
|
+
if (filteredPath(entry.path)) continue;
|
|
2166
|
+
let content;
|
|
2167
|
+
try {
|
|
2168
|
+
content = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(entry.bytes);
|
|
2169
|
+
} catch {
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
if (nulShare(content) > MAX_NUL_SHARE) continue;
|
|
2173
|
+
const target = (0, import_node_path8.resolve)(sourceDir, entry.path);
|
|
2174
|
+
if (!target.startsWith(`${(0, import_node_path8.resolve)(sourceDir)}${import_node_path8.sep}`)) throw new TypeError("Code source path escapes its root");
|
|
2175
|
+
await (0, import_promises8.mkdir)((0, import_node_path8.dirname)(target), { recursive: true });
|
|
2176
|
+
await (0, import_promises8.writeFile)(target, content, { flag: "wx", mode: 420 });
|
|
2177
|
+
visible += 1;
|
|
2178
|
+
}
|
|
2179
|
+
if (!visible && !visiblePaths) throw new TypeError("GitHub commit has no Code-visible text source");
|
|
2180
|
+
return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
|
|
2181
|
+
} catch (cause) {
|
|
2182
|
+
await (0, import_promises8.rm)(root, { recursive: true, force: true });
|
|
2183
|
+
throw cause;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
function parseTar(bytes, limits) {
|
|
2187
|
+
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
|
|
2188
|
+
const entries = [];
|
|
2189
|
+
const archivePaths = /* @__PURE__ */ new Set();
|
|
2190
|
+
let entryCount = 0;
|
|
2191
|
+
let fileCount = 0;
|
|
2192
|
+
let totalFileBytes = 0;
|
|
2193
|
+
let pendingPath;
|
|
2194
|
+
let pendingPaxPath;
|
|
2195
|
+
let offset = 0;
|
|
2196
|
+
let ended = false;
|
|
2197
|
+
while (offset + 512 <= bytes.byteLength) {
|
|
2198
|
+
const header = bytes.subarray(offset, offset + 512);
|
|
2199
|
+
offset += 512;
|
|
2200
|
+
if (zeroBlock(header)) {
|
|
2201
|
+
ended = true;
|
|
2202
|
+
assertZeroTail(bytes, offset);
|
|
2203
|
+
break;
|
|
2204
|
+
}
|
|
2205
|
+
entryCount += 1;
|
|
2206
|
+
if (entryCount > limits.maxEntries) throw new TypeError("Code source archive has too many entries");
|
|
2207
|
+
validateChecksum(header);
|
|
2208
|
+
const type = header[156] ?? 0;
|
|
2209
|
+
const size = tarNumber(header.subarray(124, 136));
|
|
2210
|
+
if (size > limits.maxDecompressedBytes || offset + aligned(size) > bytes.byteLength) {
|
|
2211
|
+
throw new TypeError("Code source archive entry is invalid or too large");
|
|
2212
|
+
}
|
|
2213
|
+
const payload = bytes.subarray(offset, offset + size);
|
|
2214
|
+
offset += aligned(size);
|
|
2215
|
+
if (type === 120 || type === 103) {
|
|
2216
|
+
if (size > limits.maxExtendedHeaderBytes) throw new TypeError("Code source archive extended header is too large");
|
|
2217
|
+
const pax = parsePax(payload, decoder, limits.maxPathBytes);
|
|
2218
|
+
if (pax.linkpath !== void 0 || pax.size !== void 0 || Object.keys(pax).some((key) => key.toLowerCase().includes("sparse"))) {
|
|
2219
|
+
throw new TypeError("Code source archive contains an unsupported entry");
|
|
2220
|
+
}
|
|
2221
|
+
if (type === 103 && pax.path !== void 0) throw new TypeError("Code source archive path is unsafe");
|
|
2222
|
+
if (type === 120 && pax.path !== void 0) {
|
|
2223
|
+
if (pendingPaxPath !== void 0 || pendingPath !== void 0) throw new TypeError("Code source archive is invalid");
|
|
2224
|
+
pendingPaxPath = pax.path;
|
|
2225
|
+
}
|
|
2226
|
+
continue;
|
|
2227
|
+
}
|
|
2228
|
+
if (type === 76) {
|
|
2229
|
+
if (size > limits.maxExtendedHeaderBytes || pendingPath !== void 0 || pendingPaxPath !== void 0) {
|
|
2230
|
+
throw new TypeError("Code source archive long path is invalid");
|
|
2231
|
+
}
|
|
2232
|
+
pendingPath = validateArchivePath(decode(payload, decoder).replace(/\0+$/, "").replace(/\n$/, ""), limits.maxPathBytes);
|
|
2233
|
+
continue;
|
|
2234
|
+
}
|
|
2235
|
+
if (type === 75) throw new TypeError("Code source archive contains an unsupported link");
|
|
2236
|
+
const path = validateArchivePath(pendingPaxPath ?? pendingPath ?? tarPath(header, decoder), limits.maxPathBytes);
|
|
2237
|
+
pendingPaxPath = void 0;
|
|
2238
|
+
pendingPath = void 0;
|
|
2239
|
+
const directory = type === 53;
|
|
2240
|
+
const regular = type === 0 || type === 48;
|
|
2241
|
+
if (!directory && !regular || directory && size !== 0 || archivePaths.has(path)) {
|
|
2242
|
+
throw new TypeError("Code source archive contains an invalid or duplicate entry");
|
|
2243
|
+
}
|
|
2244
|
+
archivePaths.add(path);
|
|
2245
|
+
if (regular) {
|
|
2246
|
+
fileCount += 1;
|
|
2247
|
+
totalFileBytes += size;
|
|
2248
|
+
if (fileCount > limits.maxFiles || size > limits.maxFileBytes || totalFileBytes > limits.maxTotalFileBytes) {
|
|
2249
|
+
throw new TypeError("Code source archive exceeds its file bounds");
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
entries.push({ path, directory, ...regular ? { bytes: payload } : {} });
|
|
2253
|
+
}
|
|
2254
|
+
if (!ended || pendingPath !== void 0 || pendingPaxPath !== void 0 || !entries.length || !fileCount) {
|
|
2255
|
+
throw new TypeError("Code source archive is incomplete");
|
|
2256
|
+
}
|
|
2257
|
+
return unwrapRepository(entries, limits.maxPathBytes);
|
|
2258
|
+
}
|
|
2259
|
+
function unwrapRepository(entries, maxPathBytes) {
|
|
2260
|
+
const root = entries[0].path.split("/")[0];
|
|
2261
|
+
if (!root || entries.some((entry) => entry.path !== root && !entry.path.startsWith(`${root}/`))) {
|
|
2262
|
+
throw new TypeError("Code source archive has no single repository root");
|
|
2263
|
+
}
|
|
2264
|
+
const output = [];
|
|
2265
|
+
const kinds = /* @__PURE__ */ new Map();
|
|
2266
|
+
for (const entry of entries) {
|
|
2267
|
+
if (entry.path === root) {
|
|
2268
|
+
if (!entry.directory) throw new TypeError("Code source archive root is not a directory");
|
|
2269
|
+
continue;
|
|
2270
|
+
}
|
|
2271
|
+
const path = entry.path.slice(root.length + 1);
|
|
2272
|
+
validateRepositoryPath(path, maxPathBytes);
|
|
2273
|
+
const parts = path.split("/");
|
|
2274
|
+
for (let index = 1; index < parts.length; index += 1) {
|
|
2275
|
+
if (kinds.get(parts.slice(0, index).join("/")) === "file") throw new TypeError("Code source archive path conflicts");
|
|
2276
|
+
}
|
|
2277
|
+
if (!entry.directory) {
|
|
2278
|
+
for (const existing of kinds.keys()) {
|
|
2279
|
+
if (existing.startsWith(`${path}/`)) throw new TypeError("Code source archive path conflicts");
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
if (kinds.has(path)) throw new TypeError("Code source archive repeats a path");
|
|
2283
|
+
kinds.set(path, entry.directory ? "directory" : "file");
|
|
2284
|
+
output.push({ ...entry, path });
|
|
2285
|
+
}
|
|
2286
|
+
return output.sort((left, right) => left.path.localeCompare(right.path));
|
|
2287
|
+
}
|
|
2288
|
+
function parsePax(bytes, decoder, maxPathBytes) {
|
|
2289
|
+
const result = {};
|
|
2290
|
+
let offset = 0;
|
|
2291
|
+
while (offset < bytes.byteLength) {
|
|
2292
|
+
const space = bytes.indexOf(32, offset);
|
|
2293
|
+
if (space < 0) throw new TypeError("Code source archive PAX header is invalid");
|
|
2294
|
+
const lengthText = ascii(bytes.subarray(offset, space));
|
|
2295
|
+
if (!/^[1-9][0-9]{0,8}$/.test(lengthText)) throw new TypeError("Code source archive PAX length is invalid");
|
|
2296
|
+
const length = Number(lengthText);
|
|
2297
|
+
const end = offset + length;
|
|
2298
|
+
if (!Number.isSafeInteger(length) || end > bytes.byteLength || bytes[end - 1] !== 10) {
|
|
2299
|
+
throw new TypeError("Code source archive PAX record is invalid");
|
|
2300
|
+
}
|
|
2301
|
+
const record5 = decode(bytes.subarray(space + 1, end - 1), decoder);
|
|
2302
|
+
const equals = record5.indexOf("=");
|
|
2303
|
+
if (equals < 1) throw new TypeError("Code source archive PAX field is invalid");
|
|
2304
|
+
const key = record5.slice(0, equals);
|
|
2305
|
+
if (Object.hasOwn(result, key)) throw new TypeError("Code source archive PAX field repeats");
|
|
2306
|
+
result[key] = record5.slice(equals + 1);
|
|
2307
|
+
offset = end;
|
|
2308
|
+
}
|
|
2309
|
+
if (result.path !== void 0) result.path = validateArchivePath(result.path, maxPathBytes);
|
|
2310
|
+
return result;
|
|
2311
|
+
}
|
|
2312
|
+
var aligned = (size) => Math.ceil(size / 512) * 512;
|
|
2313
|
+
var zeroBlock = (block) => block.every((value) => value === 0);
|
|
2314
|
+
function validateChecksum(header) {
|
|
2315
|
+
const expected = tarNumber(header.subarray(148, 156));
|
|
2316
|
+
let actual = 0;
|
|
2317
|
+
for (let index = 0; index < header.length; index += 1) actual += index >= 148 && index < 156 ? 32 : header[index];
|
|
2318
|
+
if (actual !== expected) throw new TypeError("Code source archive checksum is invalid");
|
|
2319
|
+
}
|
|
2320
|
+
function tarNumber(field) {
|
|
2321
|
+
if ((field[0] ?? 0) & 128) throw new TypeError("Code source archive numeric format is unsupported");
|
|
2322
|
+
const value = ascii(field).replaceAll("\0", "").trim();
|
|
2323
|
+
if (!value) return 0;
|
|
2324
|
+
if (!/^[0-7]+$/.test(value)) throw new TypeError("Code source archive number is invalid");
|
|
2325
|
+
const parsed = Number.parseInt(value, 8);
|
|
2326
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) throw new TypeError("Code source archive number is too large");
|
|
2327
|
+
return parsed;
|
|
2328
|
+
}
|
|
2329
|
+
function tarPath(header, decoder) {
|
|
2330
|
+
const name = decodeNul(header.subarray(0, 100), decoder);
|
|
2331
|
+
const prefix = decodeNul(header.subarray(345, 500), decoder);
|
|
2332
|
+
return prefix ? `${prefix}/${name}` : name;
|
|
2333
|
+
}
|
|
2334
|
+
function decodeNul(value, decoder) {
|
|
2335
|
+
const end = value.indexOf(0);
|
|
2336
|
+
return decode(end < 0 ? value : value.subarray(0, end), decoder);
|
|
2337
|
+
}
|
|
2338
|
+
function decode(value, decoder) {
|
|
2339
|
+
try {
|
|
2340
|
+
return decoder.decode(value);
|
|
2341
|
+
} catch {
|
|
2342
|
+
throw new TypeError("Code source archive text is invalid UTF-8");
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
function ascii(value) {
|
|
2346
|
+
let output = "";
|
|
2347
|
+
for (const byte of value) output += String.fromCharCode(byte);
|
|
2348
|
+
return output;
|
|
2349
|
+
}
|
|
2350
|
+
function validateArchivePath(input, maximum) {
|
|
2351
|
+
const path = input.endsWith("/") ? input.slice(0, -1) : input;
|
|
2352
|
+
validateRepositoryPath(path, maximum);
|
|
2353
|
+
return path;
|
|
2354
|
+
}
|
|
2355
|
+
function validateRepositoryPath(path, maximum) {
|
|
2356
|
+
if (!path || path.startsWith("/") || path.includes("\\") || /[\u0000-\u001f\u007f]/.test(path) || new TextEncoder().encode(path).byteLength > maximum || path.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
2357
|
+
throw new TypeError("Code source archive path is unsafe");
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
function assertZeroTail(bytes, offset) {
|
|
2361
|
+
for (let index = offset; index < bytes.byteLength; index += 1) {
|
|
2362
|
+
if (bytes[index] !== 0) throw new TypeError("Code source archive has data after its end marker");
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
function filteredPath(path) {
|
|
2366
|
+
const parts = path.split("/");
|
|
2367
|
+
return parts.some((part) => RESERVED2.has(part) || SECRET2.test(part));
|
|
2368
|
+
}
|
|
2369
|
+
function nulShare(content) {
|
|
2370
|
+
if (!content.length) return 0;
|
|
2371
|
+
let count = 0;
|
|
2372
|
+
for (let index = 0; index < content.length; index += 1) if (content.charCodeAt(index) === 0) count += 1;
|
|
2373
|
+
return count / content.length;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
// src/code-runtime-selected-source.ts
|
|
2377
|
+
var import_promises9 = require("fs/promises");
|
|
2378
|
+
var import_node_path9 = require("path");
|
|
2379
|
+
function selectedSourceSet(payload) {
|
|
2380
|
+
if (!payload.sourceSet) return null;
|
|
2381
|
+
const set = payload.sourceSet && typeof payload.sourceSet === "object" && !Array.isArray(payload.sourceSet) ? payload.sourceSet : null;
|
|
2382
|
+
if (!set) throw new TypeError("Code selected source set is invalid");
|
|
2383
|
+
const parse = (value, primary2) => {
|
|
2384
|
+
const item = value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2385
|
+
const alias = item?.alias;
|
|
2386
|
+
const repository = item?.repository;
|
|
2387
|
+
const commitSha = item?.commitSha;
|
|
2388
|
+
const materialization = item?.materialization ?? "registry_snapshot";
|
|
2389
|
+
if (typeof alias !== "string" || alias !== (primary2 ? "primary" : alias) || !/^[a-z][a-z0-9-]{0,39}$/.test(alias) || typeof repository !== "string" || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository) || typeof commitSha !== "string" || !/^[0-9a-f]{40}$/.test(commitSha) || !["registry_snapshot", "local_checkout", "host_archive"].includes(String(materialization))) {
|
|
2390
|
+
throw new TypeError("Code selected source identity is invalid");
|
|
2391
|
+
}
|
|
2392
|
+
const visiblePaths = item?.visiblePaths;
|
|
2393
|
+
if (visiblePaths !== void 0 && (!Array.isArray(visiblePaths) || visiblePaths.length > 2e3 || visiblePaths.some((path) => typeof path !== "string" || path.length > 4096))) {
|
|
2394
|
+
throw new TypeError("Code selected source visibility slice is invalid");
|
|
2395
|
+
}
|
|
2396
|
+
return {
|
|
2397
|
+
alias,
|
|
2398
|
+
repository,
|
|
2399
|
+
commitSha,
|
|
2400
|
+
materialization,
|
|
2401
|
+
...visiblePaths ? { visiblePaths } : {}
|
|
2402
|
+
};
|
|
2403
|
+
};
|
|
2404
|
+
if (!Array.isArray(set.references)) throw new TypeError("Code selected source references are invalid");
|
|
2405
|
+
const primary = parse(set.primary, true);
|
|
2406
|
+
const references = set.references.map((item) => parse(item, false));
|
|
2407
|
+
const aliases = /* @__PURE__ */ new Set([primary.alias, ...references.map((item) => item.alias)]);
|
|
2408
|
+
if (aliases.size !== references.length + 1) throw new TypeError("Code selected source aliases repeat");
|
|
2409
|
+
return { primary, references };
|
|
2410
|
+
}
|
|
2411
|
+
async function attachReferenceDirectories(workspace, references) {
|
|
2412
|
+
for (const reference of references) {
|
|
2413
|
+
validateAlias(reference.alias);
|
|
2414
|
+
for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
|
|
2415
|
+
const target = (0, import_node_path9.join)(root, ".odla-references", reference.alias);
|
|
2416
|
+
await (0, import_promises9.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
|
|
2417
|
+
await (0, import_promises9.cp)(reference.sourceDir, target, { recursive: true, errorOnExist: true, force: false });
|
|
2418
|
+
await makeTreeReadOnly(target);
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
function validateAlias(alias) {
|
|
2423
|
+
if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias) || alias === "primary") {
|
|
2424
|
+
throw new TypeError("Code reference alias is invalid");
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
async function makeTreeReadOnly(root) {
|
|
2428
|
+
for (const entry of await (0, import_promises9.readdir)(root, { withFileTypes: true })) {
|
|
2429
|
+
const target = (0, import_node_path9.join)(root, entry.name);
|
|
2430
|
+
if (entry.isDirectory()) await makeTreeReadOnly(target);
|
|
2431
|
+
else if (entry.isFile()) await (0, import_promises9.chmod)(target, 292);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
// src/code-runtime-source.ts
|
|
2436
|
+
var RESERVED3 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
2437
|
+
var SECRET3 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
2033
2438
|
var SOURCE_MAX_FILES = 1e5;
|
|
2034
2439
|
var SOURCE_MAX_BYTES = 80 * 1024 * 1024;
|
|
2035
2440
|
var SOURCE_SET_MAX_BYTES = 480 * 1024 * 1024;
|
|
2036
|
-
async function materializeCodeRuntimeSource(snapshot, tempRoot = (0,
|
|
2441
|
+
async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node_os4.tmpdir)()) {
|
|
2037
2442
|
if (!snapshot.files.length || snapshot.files.length > SOURCE_MAX_FILES) throw new TypeError("Code source file count is invalid");
|
|
2038
|
-
const root = await (0,
|
|
2039
|
-
const sourceDir = (0,
|
|
2040
|
-
await (0,
|
|
2443
|
+
const root = await (0, import_promises10.mkdtemp)((0, import_node_path10.join)(tempRoot, "odla-code-source-"));
|
|
2444
|
+
const sourceDir = (0, import_node_path10.join)(root, "source");
|
|
2445
|
+
await (0, import_promises10.mkdir)(sourceDir);
|
|
2041
2446
|
const seen = /* @__PURE__ */ new Set();
|
|
2042
2447
|
let bytes = 0;
|
|
2043
2448
|
try {
|
|
@@ -2047,13 +2452,13 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node
|
|
|
2047
2452
|
seen.add(file.path);
|
|
2048
2453
|
bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
|
|
2049
2454
|
if (bytes > SOURCE_MAX_BYTES) throw new TypeError("Code source exceeds its byte bound");
|
|
2050
|
-
const target = (0,
|
|
2051
|
-
if (!target.startsWith(`${(0,
|
|
2052
|
-
await (0,
|
|
2053
|
-
await (0,
|
|
2455
|
+
const target = (0, import_node_path10.resolve)(sourceDir, file.path);
|
|
2456
|
+
if (!target.startsWith(`${(0, import_node_path10.resolve)(sourceDir)}${import_node_path10.sep}`)) throw new TypeError("Code source path escapes its root");
|
|
2457
|
+
await (0, import_promises10.mkdir)((0, import_node_path10.dirname)(target), { recursive: true });
|
|
2458
|
+
await (0, import_promises10.writeFile)(target, file.content, { flag: "wx", mode: 420 });
|
|
2054
2459
|
}
|
|
2055
2460
|
for (const reference of snapshot.references ?? []) {
|
|
2056
|
-
|
|
2461
|
+
validateAlias2(reference.alias);
|
|
2057
2462
|
if (!reference.files.length || reference.files.length > SOURCE_MAX_FILES) throw new TypeError("Code reference file count is invalid");
|
|
2058
2463
|
for (const file of reference.files) {
|
|
2059
2464
|
validatePath(file.path);
|
|
@@ -2062,19 +2467,19 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node
|
|
|
2062
2467
|
seen.add(path);
|
|
2063
2468
|
bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
|
|
2064
2469
|
if (bytes > SOURCE_SET_MAX_BYTES) throw new TypeError("Code source set exceeds its byte bound");
|
|
2065
|
-
const target = (0,
|
|
2066
|
-
if (!target.startsWith(`${(0,
|
|
2067
|
-
await (0,
|
|
2068
|
-
await (0,
|
|
2470
|
+
const target = (0, import_node_path10.resolve)(sourceDir, path);
|
|
2471
|
+
if (!target.startsWith(`${(0, import_node_path10.resolve)(sourceDir)}${import_node_path10.sep}`)) throw new TypeError("Code reference path escapes its root");
|
|
2472
|
+
await (0, import_promises10.mkdir)((0, import_node_path10.dirname)(target), { recursive: true });
|
|
2473
|
+
await (0, import_promises10.writeFile)(target, file.content, { flag: "wx", mode: 292 });
|
|
2069
2474
|
}
|
|
2070
2475
|
}
|
|
2071
|
-
return { sourceDir, cleanup: () => (0,
|
|
2476
|
+
return { sourceDir, cleanup: () => (0, import_promises10.rm)(root, { recursive: true, force: true }) };
|
|
2072
2477
|
} catch (cause) {
|
|
2073
|
-
await (0,
|
|
2478
|
+
await (0, import_promises10.rm)(root, { recursive: true, force: true });
|
|
2074
2479
|
throw cause;
|
|
2075
2480
|
}
|
|
2076
2481
|
}
|
|
2077
|
-
function
|
|
2482
|
+
function validateAlias2(alias) {
|
|
2078
2483
|
if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias) || alias === "primary") {
|
|
2079
2484
|
throw new TypeError("Code reference alias is invalid");
|
|
2080
2485
|
}
|
|
@@ -2082,24 +2487,24 @@ function validateAlias(alias) {
|
|
|
2082
2487
|
async function attachCodeRuntimeReferences(workspace, references) {
|
|
2083
2488
|
let bytes = 0;
|
|
2084
2489
|
for (const reference of references) {
|
|
2085
|
-
|
|
2490
|
+
validateAlias2(reference.alias);
|
|
2086
2491
|
for (const file of reference.files) {
|
|
2087
2492
|
validatePath(file.path);
|
|
2088
2493
|
const path = `.odla-references/${reference.alias}/${file.path}`;
|
|
2089
2494
|
bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
|
|
2090
2495
|
if (bytes > SOURCE_SET_MAX_BYTES - SOURCE_MAX_BYTES) throw new TypeError("Code reference set exceeds its byte bound");
|
|
2091
2496
|
for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
|
|
2092
|
-
const target = (0,
|
|
2093
|
-
if (!target.startsWith(`${(0,
|
|
2094
|
-
await (0,
|
|
2095
|
-
await (0,
|
|
2497
|
+
const target = (0, import_node_path10.resolve)(root, path);
|
|
2498
|
+
if (!target.startsWith(`${(0, import_node_path10.resolve)(root)}${import_node_path10.sep}`)) throw new TypeError("Code reference path escapes its root");
|
|
2499
|
+
await (0, import_promises10.mkdir)((0, import_node_path10.dirname)(target), { recursive: true });
|
|
2500
|
+
await (0, import_promises10.writeFile)(target, file.content, { flag: "wx", mode: 292 });
|
|
2096
2501
|
}
|
|
2097
2502
|
}
|
|
2098
2503
|
}
|
|
2099
2504
|
}
|
|
2100
2505
|
function validatePath(path) {
|
|
2101
2506
|
const parts = path.split("/");
|
|
2102
|
-
if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." ||
|
|
2507
|
+
if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." || RESERVED3.has(part) || SECRET3.test(part))) {
|
|
2103
2508
|
throw new TypeError("Code source contains an unsafe path");
|
|
2104
2509
|
}
|
|
2105
2510
|
}
|
|
@@ -2127,12 +2532,12 @@ async function materializeCommandWorkspace(input) {
|
|
|
2127
2532
|
throw new TypeError("Code selected source set is invalid");
|
|
2128
2533
|
}
|
|
2129
2534
|
if (references.length) {
|
|
2130
|
-
const
|
|
2131
|
-
if (
|
|
2535
|
+
const selected2 = await input.control.source(command.sessionId);
|
|
2536
|
+
if (selected2.repository !== metadata.repository || selected2.commitSha !== metadata.baseCommitSha || selected2.treeDigest !== metadata.sourceTreeDigest) {
|
|
2132
2537
|
await prepared.workspace.cleanup();
|
|
2133
2538
|
throw new TypeError("Code local source does not match the selected GitHub primary source");
|
|
2134
2539
|
}
|
|
2135
|
-
await attachCodeRuntimeReferences(prepared.workspace,
|
|
2540
|
+
await attachCodeRuntimeReferences(prepared.workspace, selected2.references ?? []);
|
|
2136
2541
|
}
|
|
2137
2542
|
}
|
|
2138
2543
|
return {
|
|
@@ -2142,6 +2547,63 @@ async function materializeCommandWorkspace(input) {
|
|
|
2142
2547
|
requestedLocal
|
|
2143
2548
|
};
|
|
2144
2549
|
}
|
|
2550
|
+
const selected = selectedSourceSet(command.payload);
|
|
2551
|
+
if (selected?.primary.materialization === "host_archive") {
|
|
2552
|
+
if (!input.control.sourceArchive) {
|
|
2553
|
+
throw new TypeError("Code runtime protocol does not support host source materialization");
|
|
2554
|
+
}
|
|
2555
|
+
const sources = [selected.primary, ...selected.references];
|
|
2556
|
+
const materialized2 = [];
|
|
2557
|
+
try {
|
|
2558
|
+
for (const descriptor2 of sources) {
|
|
2559
|
+
if (descriptor2.materialization !== "host_archive") {
|
|
2560
|
+
throw new TypeError("Code selected source set mixes incompatible materialization modes");
|
|
2561
|
+
}
|
|
2562
|
+
const archive = await input.control.sourceArchive(command.sessionId, descriptor2.alias);
|
|
2563
|
+
if (archive.repository !== descriptor2.repository || archive.commitSha !== descriptor2.commitSha) {
|
|
2564
|
+
throw new TypeError("Code source archive does not match its selected repository and commit");
|
|
2565
|
+
}
|
|
2566
|
+
const source2 = await materializeCodeRuntimeArchive(
|
|
2567
|
+
archive,
|
|
2568
|
+
descriptor2.visiblePaths ? new Set(descriptor2.visiblePaths) : void 0
|
|
2569
|
+
);
|
|
2570
|
+
const treeDigest = await digestStagedWorkspace(source2.sourceDir, {
|
|
2571
|
+
maxFiles: archive.limits.maxFiles,
|
|
2572
|
+
maxBytes: archive.limits.maxTotalFileBytes
|
|
2573
|
+
});
|
|
2574
|
+
materialized2.push({ descriptor: descriptor2, source: source2, treeDigest });
|
|
2575
|
+
}
|
|
2576
|
+
const primary = materialized2[0];
|
|
2577
|
+
const workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
|
|
2578
|
+
trustedBaseDir: primary.source.sourceDir,
|
|
2579
|
+
trustedBaseCommitSha: primary.descriptor.commitSha,
|
|
2580
|
+
checkpoint: codeCheckpointPayload(command.payload)
|
|
2581
|
+
})).workspace : await stageWorkspace(primary.source.sourceDir, {
|
|
2582
|
+
maxFiles: SOURCE_MAX_FILES,
|
|
2583
|
+
maxBytes: SOURCE_SET_MAX_BYTES
|
|
2584
|
+
});
|
|
2585
|
+
try {
|
|
2586
|
+
await attachReferenceDirectories(workspace, materialized2.slice(1).map((item) => ({
|
|
2587
|
+
alias: item.descriptor.alias,
|
|
2588
|
+
sourceDir: item.source.sourceDir
|
|
2589
|
+
})));
|
|
2590
|
+
} catch (cause) {
|
|
2591
|
+
await workspace.cleanup();
|
|
2592
|
+
throw cause;
|
|
2593
|
+
}
|
|
2594
|
+
return {
|
|
2595
|
+
workspace,
|
|
2596
|
+
sourceDigest: primary.treeDigest,
|
|
2597
|
+
requestedLocal: null,
|
|
2598
|
+
sourceDigests: materialized2.map((item) => ({
|
|
2599
|
+
alias: item.descriptor.alias,
|
|
2600
|
+
treeDigest: item.treeDigest
|
|
2601
|
+
}))
|
|
2602
|
+
};
|
|
2603
|
+
} finally {
|
|
2604
|
+
await Promise.allSettled(materialized2.map((item) => item.source.cleanup()));
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2145
2607
|
const source = await input.control.source(command.sessionId);
|
|
2146
2608
|
const materialized = await materializeCodeRuntimeSource(source);
|
|
2147
2609
|
try {
|
|
@@ -2479,14 +2941,40 @@ async function sessionSkillsFor(options, command) {
|
|
|
2479
2941
|
}
|
|
2480
2942
|
|
|
2481
2943
|
// src/code-runtime-inference.ts
|
|
2944
|
+
var OVERLOAD_RETRY_DELAYS_MS = [2e3, 4e3, 8e3, 16e3];
|
|
2945
|
+
var RETRYABLE_CODES = /* @__PURE__ */ new Set(["control_plane_overloaded", "registry_overloaded", "transport_unavailable"]);
|
|
2946
|
+
function overloadedControlFailure(cause) {
|
|
2947
|
+
return cause instanceof CodeRuntimeControlError && cause.status === 503 && RETRYABLE_CODES.has(cause.code);
|
|
2948
|
+
}
|
|
2949
|
+
async function inferWithBackoff(infer, wait2, onRetry) {
|
|
2950
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
2951
|
+
try {
|
|
2952
|
+
return await infer();
|
|
2953
|
+
} catch (cause) {
|
|
2954
|
+
const delayMs = OVERLOAD_RETRY_DELAYS_MS[attempt];
|
|
2955
|
+
if (delayMs === void 0 || !overloadedControlFailure(cause)) throw cause;
|
|
2956
|
+
await onRetry(cause, delayMs);
|
|
2957
|
+
await wait2(delayMs);
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2482
2961
|
async function handleCodeRuntimeInference(input) {
|
|
2483
2962
|
const { command, request, state } = input;
|
|
2484
2963
|
const startedAt = Date.now();
|
|
2485
|
-
const
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2964
|
+
const wait2 = input.wait ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
|
|
2965
|
+
const response2 = await inferWithBackoff(
|
|
2966
|
+
() => input.control.infer(command.sessionId, {
|
|
2967
|
+
requestId: request.requestId,
|
|
2968
|
+
interactionId: command.commandId,
|
|
2969
|
+
call: request.call
|
|
2970
|
+
}),
|
|
2971
|
+
wait2,
|
|
2972
|
+
(cause, delayMs) => input.event({
|
|
2973
|
+
type: "diagnostic",
|
|
2974
|
+
level: "error",
|
|
2975
|
+
message: `Code control plane overloaded (${cause.code}); retrying the model call in ${delayMs / 1e3}s`
|
|
2976
|
+
}).catch(() => void 0)
|
|
2977
|
+
);
|
|
2490
2978
|
state.tokens += response2.receipt.inputTokens + response2.receipt.outputTokens;
|
|
2491
2979
|
const { costUsd } = response2.receipt;
|
|
2492
2980
|
if (costUsd === void 0) state.costKnown = false;
|
|
@@ -2949,12 +3437,12 @@ function response(request, ok, content, details) {
|
|
|
2949
3437
|
}
|
|
2950
3438
|
|
|
2951
3439
|
// src/code-tool-reads.ts
|
|
2952
|
-
var
|
|
3440
|
+
var import_promises13 = require("fs/promises");
|
|
2953
3441
|
|
|
2954
3442
|
// src/code-tool-discovery.ts
|
|
2955
3443
|
var import_node_child_process6 = require("child_process");
|
|
2956
|
-
var
|
|
2957
|
-
var
|
|
3444
|
+
var import_promises11 = require("fs/promises");
|
|
3445
|
+
var import_node_path11 = require("path");
|
|
2958
3446
|
var DEFAULT_MAX_FILES = 2e4;
|
|
2959
3447
|
var DEFAULT_MAX_RESULTS = 100;
|
|
2960
3448
|
var DEFAULT_MAX_FILE_BYTES = 512 * 1024;
|
|
@@ -2979,13 +3467,13 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
2979
3467
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
2980
3468
|
const paths2 = [];
|
|
2981
3469
|
const walk = async (directory) => {
|
|
2982
|
-
for (const entry of await (0,
|
|
3470
|
+
for (const entry of await (0, import_promises11.readdir)(directory, { withFileTypes: true })) {
|
|
2983
3471
|
if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
2984
3472
|
if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
|
|
2985
|
-
const target = (0,
|
|
3473
|
+
const target = (0, import_node_path11.resolve)(directory, entry.name);
|
|
2986
3474
|
if (entry.isDirectory()) await walk(target);
|
|
2987
3475
|
else if (entry.isFile()) {
|
|
2988
|
-
const path = (0,
|
|
3476
|
+
const path = (0, import_node_path11.relative)(root, target).split("\\").join("/");
|
|
2989
3477
|
try {
|
|
2990
3478
|
validateRelativePath(path);
|
|
2991
3479
|
} catch {
|
|
@@ -2996,7 +3484,7 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
|
2996
3484
|
}
|
|
2997
3485
|
}
|
|
2998
3486
|
};
|
|
2999
|
-
await walk((0,
|
|
3487
|
+
await walk((0, import_node_path11.resolve)(root));
|
|
3000
3488
|
return paths2.sort();
|
|
3001
3489
|
}
|
|
3002
3490
|
function listWorkspace(paths2, options = {}) {
|
|
@@ -3110,7 +3598,7 @@ async function fallbackSearch(root, scoped, options) {
|
|
|
3110
3598
|
if (matches.length >= options.maxResults) break;
|
|
3111
3599
|
let source;
|
|
3112
3600
|
try {
|
|
3113
|
-
source = await (0,
|
|
3601
|
+
source = await (0, import_promises11.readFile)((0, import_node_path11.resolve)(root, path));
|
|
3114
3602
|
} catch {
|
|
3115
3603
|
continue;
|
|
3116
3604
|
}
|
|
@@ -3128,15 +3616,15 @@ async function fallbackSearch(root, scoped, options) {
|
|
|
3128
3616
|
}
|
|
3129
3617
|
|
|
3130
3618
|
// src/code-tool-graph.ts
|
|
3131
|
-
var
|
|
3132
|
-
var
|
|
3619
|
+
var import_promises12 = require("fs/promises");
|
|
3620
|
+
var import_node_path12 = require("path");
|
|
3133
3621
|
var import_graph = require("@odla-ai/graph");
|
|
3134
3622
|
var import_code4 = require("@odla-ai/graph/code");
|
|
3135
3623
|
var cache = /* @__PURE__ */ new Map();
|
|
3136
3624
|
function workspaceGraphs(workspaceDir, paths2) {
|
|
3137
3625
|
const existing = cache.get(workspaceDir);
|
|
3138
3626
|
if (existing) return existing;
|
|
3139
|
-
const read2 = (path) => (0,
|
|
3627
|
+
const read2 = (path) => (0, import_promises12.readFile)((0, import_node_path12.join)(workspaceDir, path), "utf8");
|
|
3140
3628
|
const built = (async () => ({
|
|
3141
3629
|
// No knownTables: a staged workspace may not carry migrations, and a filter
|
|
3142
3630
|
// that silently drops every table is worse than an unfiltered one. Callers
|
|
@@ -3210,11 +3698,11 @@ async function read(context, request, options, policy, registry) {
|
|
|
3210
3698
|
const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
|
|
3211
3699
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
3212
3700
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
3213
|
-
const info = await (0,
|
|
3701
|
+
const info = await (0, import_promises13.stat)(target);
|
|
3214
3702
|
if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
|
|
3215
3703
|
throw new TypeError("file is not a bounded regular source file");
|
|
3216
3704
|
}
|
|
3217
|
-
const source = await (0,
|
|
3705
|
+
const source = await (0, import_promises13.readFile)(target);
|
|
3218
3706
|
if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
|
|
3219
3707
|
const lines = source.toString("utf8").split("\n");
|
|
3220
3708
|
const content = lines.slice(startLine - 1, endLine).join("\n");
|
|
@@ -3759,8 +4247,8 @@ var runtimeErrorMessage = (value) => value instanceof Error ? value.message : St
|
|
|
3759
4247
|
function codeRuntimeAcknowledgementGate(signal) {
|
|
3760
4248
|
let settle;
|
|
3761
4249
|
let settled = false;
|
|
3762
|
-
const ready = new Promise((
|
|
3763
|
-
settle =
|
|
4250
|
+
const ready = new Promise((resolve8) => {
|
|
4251
|
+
settle = resolve8;
|
|
3764
4252
|
});
|
|
3765
4253
|
const release = (run) => {
|
|
3766
4254
|
if (settled) return;
|
|
@@ -3869,7 +4357,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3869
4357
|
async #start(command, resume) {
|
|
3870
4358
|
if (this.#active.has(command.sessionId)) throw new TypeError("Code session is already active on this runtime");
|
|
3871
4359
|
const metadata = codeCommandMetadata(command.payload, resume);
|
|
3872
|
-
const { workspace, sourceDigest, localTrustedBaseDigest, requestedLocal } = await materializeCommandWorkspace({
|
|
4360
|
+
const { workspace, sourceDigest, sourceDigests, localTrustedBaseDigest, requestedLocal } = await materializeCommandWorkspace({
|
|
3873
4361
|
command,
|
|
3874
4362
|
metadata,
|
|
3875
4363
|
resume,
|
|
@@ -3885,11 +4373,12 @@ var TheseusRuntimeEngine = class {
|
|
|
3885
4373
|
acknowledged: false,
|
|
3886
4374
|
startGate,
|
|
3887
4375
|
role: metadata.role,
|
|
4376
|
+
readOnly: metadata.readOnly,
|
|
3888
4377
|
title: metadata.title,
|
|
3889
4378
|
maxTokensPerInteraction: metadata.maxTokensPerInteraction,
|
|
3890
4379
|
baseCommitSha: metadata.baseCommitSha,
|
|
3891
4380
|
repository: metadata.repository,
|
|
3892
|
-
sourceTreeDigest: metadata.sourceTreeDigest,
|
|
4381
|
+
sourceTreeDigest: metadata.sourceTreeDigest ?? sourceDigest,
|
|
3893
4382
|
trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
|
|
3894
4383
|
maxFiles: 2e4,
|
|
3895
4384
|
maxBytes: 512 * 1024 * 1024
|
|
@@ -3915,7 +4404,11 @@ var TheseusRuntimeEngine = class {
|
|
|
3915
4404
|
await this.#failure(command, active, detail);
|
|
3916
4405
|
return null;
|
|
3917
4406
|
});
|
|
3918
|
-
return {
|
|
4407
|
+
return {
|
|
4408
|
+
status: "running",
|
|
4409
|
+
message: resume ? "Theseus resumed from a portable checkpoint" : "Theseus started",
|
|
4410
|
+
...sourceDigests ? { sourceDigests } : {}
|
|
4411
|
+
};
|
|
3919
4412
|
}
|
|
3920
4413
|
/**
|
|
3921
4414
|
* Pursue a goal: attempt, judge with the clean verifier, re-prompt from what
|
|
@@ -3941,6 +4434,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3941
4434
|
event: (event) => this.#event(command, event, active.conversationRefs).then(() => void 0, () => void 0),
|
|
3942
4435
|
attempt: (prompt) => this.#runAttempt(command, {
|
|
3943
4436
|
role: active.role,
|
|
4437
|
+
readOnly: active.readOnly,
|
|
3944
4438
|
title: active.title,
|
|
3945
4439
|
prompt,
|
|
3946
4440
|
maxTokensPerInteraction: active.maxTokensPerInteraction,
|
|
@@ -3983,6 +4477,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3983
4477
|
await this.#takeOver(command, "prompt requires an active Code session");
|
|
3984
4478
|
active.done = this.#runAttempt(command, {
|
|
3985
4479
|
role: active.role,
|
|
4480
|
+
readOnly: active.readOnly,
|
|
3986
4481
|
title: active.title,
|
|
3987
4482
|
prompt,
|
|
3988
4483
|
maxTokensPerInteraction: active.maxTokensPerInteraction,
|
|
@@ -4030,7 +4525,7 @@ var TheseusRuntimeEngine = class {
|
|
|
4030
4525
|
workspaceDir: active.workspace.workspaceDir,
|
|
4031
4526
|
prompt: metadata.prompt,
|
|
4032
4527
|
signal: active.abort.signal,
|
|
4033
|
-
readOnly: metadata.
|
|
4528
|
+
readOnly: metadata.readOnly,
|
|
4034
4529
|
recipeIds: this.options.recipes.map((recipe2) => recipe2.id),
|
|
4035
4530
|
...extraSkills.length ? { extraSkills } : {}
|
|
4036
4531
|
});
|
|
@@ -4362,12 +4857,12 @@ function chooseStrategy(signals = {}) {
|
|
|
4362
4857
|
var feedbackIsActionable = actionable;
|
|
4363
4858
|
|
|
4364
4859
|
// src/code-recipe-dependencies.ts
|
|
4365
|
-
var
|
|
4366
|
-
var
|
|
4860
|
+
var import_promises14 = require("fs/promises");
|
|
4861
|
+
var import_node_path13 = require("path");
|
|
4367
4862
|
var RESERVED_MOUNTS = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage"]);
|
|
4368
4863
|
function withRecipeDependencies(executor, dependencies) {
|
|
4369
4864
|
const mountAs = dependencies.mountAs ?? "node_modules";
|
|
4370
|
-
if (!(0,
|
|
4865
|
+
if (!(0, import_node_path13.isAbsolute)(dependencies.source)) {
|
|
4371
4866
|
throw new TypeError("recipe dependency source must be an absolute path");
|
|
4372
4867
|
}
|
|
4373
4868
|
if (!RESERVED_MOUNTS.has(mountAs)) {
|
|
@@ -4375,24 +4870,24 @@ function withRecipeDependencies(executor, dependencies) {
|
|
|
4375
4870
|
}
|
|
4376
4871
|
return {
|
|
4377
4872
|
run: async (input) => {
|
|
4378
|
-
const target = (0,
|
|
4873
|
+
const target = (0, import_node_path13.join)(input.workspaceDir, mountAs);
|
|
4379
4874
|
let linked = false;
|
|
4380
4875
|
try {
|
|
4381
|
-
const existing = await (0,
|
|
4876
|
+
const existing = await (0, import_promises14.lstat)(target).catch(() => null);
|
|
4382
4877
|
if (!existing) {
|
|
4383
|
-
await (0,
|
|
4878
|
+
await (0, import_promises14.symlink)(dependencies.source, target, "dir");
|
|
4384
4879
|
linked = true;
|
|
4385
4880
|
}
|
|
4386
4881
|
return await executor.run(input);
|
|
4387
4882
|
} finally {
|
|
4388
|
-
if (linked) await (0,
|
|
4883
|
+
if (linked) await (0, import_promises14.rm)(target, { force: true, recursive: false }).catch(() => void 0);
|
|
4389
4884
|
}
|
|
4390
4885
|
}
|
|
4391
4886
|
};
|
|
4392
4887
|
}
|
|
4393
4888
|
async function installedDependencies(repoRoot) {
|
|
4394
|
-
const source = (0,
|
|
4395
|
-
const info = await (0,
|
|
4889
|
+
const source = (0, import_node_path13.join)(repoRoot, "node_modules");
|
|
4890
|
+
const info = await (0, import_promises14.lstat)(source).catch(() => null);
|
|
4396
4891
|
return info?.isDirectory() ? { source } : null;
|
|
4397
4892
|
}
|
|
4398
4893
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -4433,6 +4928,7 @@ async function installedDependencies(repoRoot) {
|
|
|
4433
4928
|
installedDependencies,
|
|
4434
4929
|
integrateSubGoals,
|
|
4435
4930
|
isCheckpointEffectCompleted,
|
|
4931
|
+
materializeCodeRuntimeArchive,
|
|
4436
4932
|
materializeCodeRuntimeSource,
|
|
4437
4933
|
materializeCommandWorkspace,
|
|
4438
4934
|
materializeGitTree,
|