@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
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
// src/code-runtime-cli.ts
|
|
5
|
-
var
|
|
6
|
-
var
|
|
5
|
+
var import_node_os4 = require("os");
|
|
6
|
+
var import_promises12 = require("fs/promises");
|
|
7
7
|
|
|
8
8
|
// src/code-runtime-client-validation.ts
|
|
9
9
|
var import_code = require("@odla-ai/camel/code");
|
|
@@ -249,6 +249,34 @@ function createCodeRuntimeControlClient(options) {
|
|
|
249
249
|
}
|
|
250
250
|
return value;
|
|
251
251
|
};
|
|
252
|
+
const callRaw = async (path, body) => {
|
|
253
|
+
const timeout = AbortSignal.timeout(modelRequestTimeoutMs);
|
|
254
|
+
const signals = [options.signal, timeout].filter((item) => Boolean(item));
|
|
255
|
+
const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
256
|
+
let response2;
|
|
257
|
+
try {
|
|
258
|
+
response2 = await request(`${endpoint}${path}`, {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json" },
|
|
261
|
+
body: JSON.stringify(body),
|
|
262
|
+
redirect: "error",
|
|
263
|
+
signal
|
|
264
|
+
});
|
|
265
|
+
} catch (cause) {
|
|
266
|
+
if (options.signal?.aborted) throw cause;
|
|
267
|
+
throw new CodeRuntimeControlError("Code source archive is unavailable", 503, "transport_unavailable");
|
|
268
|
+
}
|
|
269
|
+
if (!response2.ok) {
|
|
270
|
+
const value = await response2.json().catch(() => null);
|
|
271
|
+
const problem = record(record(value)?.error);
|
|
272
|
+
throw new CodeRuntimeControlError(
|
|
273
|
+
typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
|
|
274
|
+
response2.status,
|
|
275
|
+
typeof problem?.code === "string" ? problem.code : void 0
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
return response2;
|
|
279
|
+
};
|
|
252
280
|
return {
|
|
253
281
|
heartbeat: async (version, capabilities) => {
|
|
254
282
|
validateHeartbeat(version, capabilities);
|
|
@@ -260,6 +288,14 @@ function createCodeRuntimeControlClient(options) {
|
|
|
260
288
|
source: async (sessionId) => parseSource(
|
|
261
289
|
await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
262
290
|
),
|
|
291
|
+
sourceArchive: async (sessionId, alias) => {
|
|
292
|
+
if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias)) throw new TypeError("invalid Code source alias");
|
|
293
|
+
const response2 = await callRaw(
|
|
294
|
+
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source/archive`,
|
|
295
|
+
{ alias }
|
|
296
|
+
);
|
|
297
|
+
return parseSourceArchiveResponse(response2, alias);
|
|
298
|
+
},
|
|
263
299
|
infer: async (sessionId, inference) => {
|
|
264
300
|
const value = record(await call(
|
|
265
301
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
@@ -324,9 +360,70 @@ function createCodeRuntimeControlClient(options) {
|
|
|
324
360
|
}
|
|
325
361
|
};
|
|
326
362
|
}
|
|
363
|
+
var ARCHIVE_LIMIT_MAXIMA = {
|
|
364
|
+
maxCompressedBytes: 64 * 1024 * 1024,
|
|
365
|
+
maxDecompressedBytes: 96 * 1024 * 1024,
|
|
366
|
+
maxEntries: 2e5,
|
|
367
|
+
maxFiles: 1e5,
|
|
368
|
+
maxFileBytes: 16 * 1024 * 1024,
|
|
369
|
+
maxTotalFileBytes: 80 * 1024 * 1024,
|
|
370
|
+
maxPathBytes: 4096,
|
|
371
|
+
maxExtendedHeaderBytes: 32 * 1024
|
|
372
|
+
};
|
|
373
|
+
async function parseSourceArchiveResponse(response2, expectedAlias) {
|
|
374
|
+
const alias = response2.headers.get("x-odla-source-alias") ?? "";
|
|
375
|
+
const repository = response2.headers.get("x-odla-source-repository") ?? "";
|
|
376
|
+
const commitSha = response2.headers.get("x-odla-source-commit") ?? "";
|
|
377
|
+
if (alias !== expectedAlias || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository) || !/^[0-9a-f]{40}$/.test(commitSha)) {
|
|
378
|
+
await response2.body?.cancel().catch(() => void 0);
|
|
379
|
+
throw new CodeRuntimeControlError("invalid Code source archive identity", 502, "invalid_response");
|
|
380
|
+
}
|
|
381
|
+
const limits = Object.fromEntries(Object.entries(ARCHIVE_LIMIT_MAXIMA).map(([key, maximum]) => {
|
|
382
|
+
const header = `x-odla-${key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}`;
|
|
383
|
+
const value = response2.headers.get(header) ?? "";
|
|
384
|
+
if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)) || Number(value) < 1 || Number(value) > maximum) {
|
|
385
|
+
throw new CodeRuntimeControlError("invalid Code source archive limits", 502, "invalid_response");
|
|
386
|
+
}
|
|
387
|
+
return [key, Number(value)];
|
|
388
|
+
}));
|
|
389
|
+
const length = response2.headers.get("content-length");
|
|
390
|
+
if (length && (!/^\d+$/.test(length) || Number(length) > limits.maxCompressedBytes)) {
|
|
391
|
+
await response2.body?.cancel().catch(() => void 0);
|
|
392
|
+
throw new CodeRuntimeControlError("Code source archive exceeds its byte bound", 502, "invalid_response");
|
|
393
|
+
}
|
|
394
|
+
const compressed = await readBoundedResponse(response2.body, limits.maxCompressedBytes);
|
|
395
|
+
return { alias, repository, commitSha, compressed, limits };
|
|
396
|
+
}
|
|
397
|
+
async function readBoundedResponse(body, maximum) {
|
|
398
|
+
if (!body) throw new CodeRuntimeControlError("Code source archive body is missing", 502, "invalid_response");
|
|
399
|
+
const reader = body.getReader();
|
|
400
|
+
const chunks = [];
|
|
401
|
+
let size = 0;
|
|
402
|
+
try {
|
|
403
|
+
while (true) {
|
|
404
|
+
const { done, value } = await reader.read();
|
|
405
|
+
if (done) break;
|
|
406
|
+
size += value.byteLength;
|
|
407
|
+
if (size > maximum) {
|
|
408
|
+
await reader.cancel().catch(() => void 0);
|
|
409
|
+
throw new CodeRuntimeControlError("Code source archive exceeds its byte bound", 502, "invalid_response");
|
|
410
|
+
}
|
|
411
|
+
chunks.push(value);
|
|
412
|
+
}
|
|
413
|
+
} finally {
|
|
414
|
+
reader.releaseLock();
|
|
415
|
+
}
|
|
416
|
+
const result = new Uint8Array(size);
|
|
417
|
+
let offset = 0;
|
|
418
|
+
for (const chunk of chunks) {
|
|
419
|
+
result.set(chunk, offset);
|
|
420
|
+
offset += chunk.byteLength;
|
|
421
|
+
}
|
|
422
|
+
return result;
|
|
423
|
+
}
|
|
327
424
|
|
|
328
425
|
// src/code-runtime.ts
|
|
329
|
-
var CODE_RUNTIME_PROTOCOL_VERSION =
|
|
426
|
+
var CODE_RUNTIME_PROTOCOL_VERSION = 3;
|
|
330
427
|
async function runCodeRuntimeHeartbeatLoop(options) {
|
|
331
428
|
const heartbeatMs = options.heartbeatMs ?? 15e3;
|
|
332
429
|
if (!Number.isSafeInteger(heartbeatMs) || heartbeatMs < 1e3 || heartbeatMs > 3e5) {
|
|
@@ -395,12 +492,12 @@ function retryableControlFailure(value) {
|
|
|
395
492
|
return failure.status === 408 || failure.status === 425 || failure.status === 429 || failure.status >= 500;
|
|
396
493
|
}
|
|
397
494
|
function wait(ms, signal) {
|
|
398
|
-
return new Promise((
|
|
399
|
-
if (signal?.aborted) return
|
|
400
|
-
const timer = setTimeout(
|
|
495
|
+
return new Promise((resolve7) => {
|
|
496
|
+
if (signal?.aborted) return resolve7();
|
|
497
|
+
const timer = setTimeout(resolve7, ms);
|
|
401
498
|
signal?.addEventListener("abort", () => {
|
|
402
499
|
clearTimeout(timer);
|
|
403
|
-
|
|
500
|
+
resolve7();
|
|
404
501
|
}, { once: true });
|
|
405
502
|
});
|
|
406
503
|
}
|
|
@@ -846,7 +943,7 @@ async function selectContainerEngine(requested = "auto", options = {}) {
|
|
|
846
943
|
throw new TypeError("no supported container engine found");
|
|
847
944
|
}
|
|
848
945
|
function inspectRootlessPodman() {
|
|
849
|
-
return new Promise((
|
|
946
|
+
return new Promise((resolve7, reject) => {
|
|
850
947
|
(0, import_node_child_process3.execFile)(
|
|
851
948
|
"podman",
|
|
852
949
|
["info", "--format", "{{.Host.Security.Rootless}}"],
|
|
@@ -856,7 +953,7 @@ function inspectRootlessPodman() {
|
|
|
856
953
|
reject(new TypeError("could not verify that the active Podman service is rootless"));
|
|
857
954
|
return;
|
|
858
955
|
}
|
|
859
|
-
|
|
956
|
+
resolve7(stdout.trim() === "true");
|
|
860
957
|
}
|
|
861
958
|
);
|
|
862
959
|
});
|
|
@@ -1354,12 +1451,17 @@ function codeCommandMetadata(payload, resume) {
|
|
|
1354
1451
|
if (role !== "coding" && role !== "review" || typeof title !== "string" || typeof prompt !== "string") {
|
|
1355
1452
|
throw new TypeError(`invalid Code ${resume ? "resume" : "start"} metadata`);
|
|
1356
1453
|
}
|
|
1454
|
+
if (payload.readOnly !== void 0 && typeof payload.readOnly !== "boolean") {
|
|
1455
|
+
throw new TypeError(`invalid Code ${resume ? "resume" : "start"} read-only capability`);
|
|
1456
|
+
}
|
|
1457
|
+
const readOnly = role === "review" || payload.readOnly === true;
|
|
1357
1458
|
const planning = trusted?.planningInputDigest;
|
|
1358
1459
|
const attestation = trusted?.attestationDigest;
|
|
1359
1460
|
const repository = trusted?.repository;
|
|
1360
1461
|
const baseCommitSha = trusted?.commitSha;
|
|
1361
1462
|
const sourceTreeDigest = trusted?.treeDigest;
|
|
1362
|
-
|
|
1463
|
+
const hostMaterialized = record2(payload.sourceSet) !== null && sourceTreeDigest === void 0;
|
|
1464
|
+
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))) {
|
|
1363
1465
|
throw new TypeError(`invalid Code ${resume ? "resume" : "start"} trusted base`);
|
|
1364
1466
|
}
|
|
1365
1467
|
if (!Number.isSafeInteger(maxTokensPerInteraction) || Number(maxTokensPerInteraction) < 4e3 || Number(maxTokensPerInteraction) > 2e5) {
|
|
@@ -1367,6 +1469,7 @@ function codeCommandMetadata(payload, resume) {
|
|
|
1367
1469
|
}
|
|
1368
1470
|
return {
|
|
1369
1471
|
role,
|
|
1472
|
+
readOnly,
|
|
1370
1473
|
title,
|
|
1371
1474
|
prompt,
|
|
1372
1475
|
maxTokensPerInteraction: Number(maxTokensPerInteraction),
|
|
@@ -1374,7 +1477,7 @@ function codeCommandMetadata(payload, resume) {
|
|
|
1374
1477
|
attestationDigest: typeof attestation === "string" ? attestation : "resume",
|
|
1375
1478
|
repository,
|
|
1376
1479
|
baseCommitSha,
|
|
1377
|
-
sourceTreeDigest
|
|
1480
|
+
sourceTreeDigest: typeof sourceTreeDigest === "string" ? sourceTreeDigest : null
|
|
1378
1481
|
};
|
|
1379
1482
|
}
|
|
1380
1483
|
function codeLocalSource(payload) {
|
|
@@ -1439,19 +1542,320 @@ async function prepareRuntimeLocalSource(input) {
|
|
|
1439
1542
|
}
|
|
1440
1543
|
|
|
1441
1544
|
// src/code-runtime-source.ts
|
|
1545
|
+
var import_promises8 = require("fs/promises");
|
|
1546
|
+
var import_node_os3 = require("os");
|
|
1547
|
+
var import_node_path9 = require("path");
|
|
1548
|
+
|
|
1549
|
+
// src/code-runtime-archive.ts
|
|
1550
|
+
var import_node_zlib = require("zlib");
|
|
1442
1551
|
var import_promises6 = require("fs/promises");
|
|
1443
1552
|
var import_node_os2 = require("os");
|
|
1444
1553
|
var import_node_path7 = require("path");
|
|
1445
1554
|
var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
1446
1555
|
var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
1556
|
+
var MAX_NUL_SHARE = 0.1;
|
|
1557
|
+
async function materializeCodeRuntimeArchive(archive, visiblePaths, tempRoot = (0, import_node_os2.tmpdir)()) {
|
|
1558
|
+
if (!archive.compressed.byteLength || archive.compressed.byteLength > archive.limits.maxCompressedBytes) {
|
|
1559
|
+
throw new TypeError("Code source archive exceeds its compressed byte bound");
|
|
1560
|
+
}
|
|
1561
|
+
let bytes;
|
|
1562
|
+
try {
|
|
1563
|
+
bytes = archive.compressed[0] === 31 && archive.compressed[1] === 139 ? (0, import_node_zlib.gunzipSync)(archive.compressed, { maxOutputLength: archive.limits.maxDecompressedBytes }) : archive.compressed;
|
|
1564
|
+
} catch {
|
|
1565
|
+
throw new TypeError("Code source archive is not a valid bounded gzip stream");
|
|
1566
|
+
}
|
|
1567
|
+
if (bytes.byteLength > archive.limits.maxDecompressedBytes) {
|
|
1568
|
+
throw new TypeError("Code source archive exceeds its decompressed byte bound");
|
|
1569
|
+
}
|
|
1570
|
+
const entries = parseTar(bytes, archive.limits);
|
|
1571
|
+
const root = await (0, import_promises6.mkdtemp)((0, import_node_path7.join)(tempRoot, "odla-code-archive-"));
|
|
1572
|
+
const sourceDir = (0, import_node_path7.join)(root, "source");
|
|
1573
|
+
await (0, import_promises6.mkdir)(sourceDir);
|
|
1574
|
+
let visible = 0;
|
|
1575
|
+
try {
|
|
1576
|
+
for (const entry of entries) {
|
|
1577
|
+
if (entry.directory || visiblePaths && !visiblePaths.has(entry.path)) continue;
|
|
1578
|
+
if (filteredPath(entry.path)) continue;
|
|
1579
|
+
let content;
|
|
1580
|
+
try {
|
|
1581
|
+
content = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(entry.bytes);
|
|
1582
|
+
} catch {
|
|
1583
|
+
continue;
|
|
1584
|
+
}
|
|
1585
|
+
if (nulShare(content) > MAX_NUL_SHARE) continue;
|
|
1586
|
+
const target = (0, import_node_path7.resolve)(sourceDir, entry.path);
|
|
1587
|
+
if (!target.startsWith(`${(0, import_node_path7.resolve)(sourceDir)}${import_node_path7.sep}`)) throw new TypeError("Code source path escapes its root");
|
|
1588
|
+
await (0, import_promises6.mkdir)((0, import_node_path7.dirname)(target), { recursive: true });
|
|
1589
|
+
await (0, import_promises6.writeFile)(target, content, { flag: "wx", mode: 420 });
|
|
1590
|
+
visible += 1;
|
|
1591
|
+
}
|
|
1592
|
+
if (!visible && !visiblePaths) throw new TypeError("GitHub commit has no Code-visible text source");
|
|
1593
|
+
return { sourceDir, cleanup: () => (0, import_promises6.rm)(root, { recursive: true, force: true }) };
|
|
1594
|
+
} catch (cause) {
|
|
1595
|
+
await (0, import_promises6.rm)(root, { recursive: true, force: true });
|
|
1596
|
+
throw cause;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
function parseTar(bytes, limits) {
|
|
1600
|
+
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
|
|
1601
|
+
const entries = [];
|
|
1602
|
+
const archivePaths = /* @__PURE__ */ new Set();
|
|
1603
|
+
let entryCount = 0;
|
|
1604
|
+
let fileCount = 0;
|
|
1605
|
+
let totalFileBytes = 0;
|
|
1606
|
+
let pendingPath;
|
|
1607
|
+
let pendingPaxPath;
|
|
1608
|
+
let offset = 0;
|
|
1609
|
+
let ended = false;
|
|
1610
|
+
while (offset + 512 <= bytes.byteLength) {
|
|
1611
|
+
const header = bytes.subarray(offset, offset + 512);
|
|
1612
|
+
offset += 512;
|
|
1613
|
+
if (zeroBlock(header)) {
|
|
1614
|
+
ended = true;
|
|
1615
|
+
assertZeroTail(bytes, offset);
|
|
1616
|
+
break;
|
|
1617
|
+
}
|
|
1618
|
+
entryCount += 1;
|
|
1619
|
+
if (entryCount > limits.maxEntries) throw new TypeError("Code source archive has too many entries");
|
|
1620
|
+
validateChecksum(header);
|
|
1621
|
+
const type = header[156] ?? 0;
|
|
1622
|
+
const size = tarNumber(header.subarray(124, 136));
|
|
1623
|
+
if (size > limits.maxDecompressedBytes || offset + aligned(size) > bytes.byteLength) {
|
|
1624
|
+
throw new TypeError("Code source archive entry is invalid or too large");
|
|
1625
|
+
}
|
|
1626
|
+
const payload = bytes.subarray(offset, offset + size);
|
|
1627
|
+
offset += aligned(size);
|
|
1628
|
+
if (type === 120 || type === 103) {
|
|
1629
|
+
if (size > limits.maxExtendedHeaderBytes) throw new TypeError("Code source archive extended header is too large");
|
|
1630
|
+
const pax = parsePax(payload, decoder, limits.maxPathBytes);
|
|
1631
|
+
if (pax.linkpath !== void 0 || pax.size !== void 0 || Object.keys(pax).some((key) => key.toLowerCase().includes("sparse"))) {
|
|
1632
|
+
throw new TypeError("Code source archive contains an unsupported entry");
|
|
1633
|
+
}
|
|
1634
|
+
if (type === 103 && pax.path !== void 0) throw new TypeError("Code source archive path is unsafe");
|
|
1635
|
+
if (type === 120 && pax.path !== void 0) {
|
|
1636
|
+
if (pendingPaxPath !== void 0 || pendingPath !== void 0) throw new TypeError("Code source archive is invalid");
|
|
1637
|
+
pendingPaxPath = pax.path;
|
|
1638
|
+
}
|
|
1639
|
+
continue;
|
|
1640
|
+
}
|
|
1641
|
+
if (type === 76) {
|
|
1642
|
+
if (size > limits.maxExtendedHeaderBytes || pendingPath !== void 0 || pendingPaxPath !== void 0) {
|
|
1643
|
+
throw new TypeError("Code source archive long path is invalid");
|
|
1644
|
+
}
|
|
1645
|
+
pendingPath = validateArchivePath(decode(payload, decoder).replace(/\0+$/, "").replace(/\n$/, ""), limits.maxPathBytes);
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
if (type === 75) throw new TypeError("Code source archive contains an unsupported link");
|
|
1649
|
+
const path = validateArchivePath(pendingPaxPath ?? pendingPath ?? tarPath(header, decoder), limits.maxPathBytes);
|
|
1650
|
+
pendingPaxPath = void 0;
|
|
1651
|
+
pendingPath = void 0;
|
|
1652
|
+
const directory = type === 53;
|
|
1653
|
+
const regular = type === 0 || type === 48;
|
|
1654
|
+
if (!directory && !regular || directory && size !== 0 || archivePaths.has(path)) {
|
|
1655
|
+
throw new TypeError("Code source archive contains an invalid or duplicate entry");
|
|
1656
|
+
}
|
|
1657
|
+
archivePaths.add(path);
|
|
1658
|
+
if (regular) {
|
|
1659
|
+
fileCount += 1;
|
|
1660
|
+
totalFileBytes += size;
|
|
1661
|
+
if (fileCount > limits.maxFiles || size > limits.maxFileBytes || totalFileBytes > limits.maxTotalFileBytes) {
|
|
1662
|
+
throw new TypeError("Code source archive exceeds its file bounds");
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
entries.push({ path, directory, ...regular ? { bytes: payload } : {} });
|
|
1666
|
+
}
|
|
1667
|
+
if (!ended || pendingPath !== void 0 || pendingPaxPath !== void 0 || !entries.length || !fileCount) {
|
|
1668
|
+
throw new TypeError("Code source archive is incomplete");
|
|
1669
|
+
}
|
|
1670
|
+
return unwrapRepository(entries, limits.maxPathBytes);
|
|
1671
|
+
}
|
|
1672
|
+
function unwrapRepository(entries, maxPathBytes) {
|
|
1673
|
+
const root = entries[0].path.split("/")[0];
|
|
1674
|
+
if (!root || entries.some((entry) => entry.path !== root && !entry.path.startsWith(`${root}/`))) {
|
|
1675
|
+
throw new TypeError("Code source archive has no single repository root");
|
|
1676
|
+
}
|
|
1677
|
+
const output = [];
|
|
1678
|
+
const kinds = /* @__PURE__ */ new Map();
|
|
1679
|
+
for (const entry of entries) {
|
|
1680
|
+
if (entry.path === root) {
|
|
1681
|
+
if (!entry.directory) throw new TypeError("Code source archive root is not a directory");
|
|
1682
|
+
continue;
|
|
1683
|
+
}
|
|
1684
|
+
const path = entry.path.slice(root.length + 1);
|
|
1685
|
+
validateRepositoryPath(path, maxPathBytes);
|
|
1686
|
+
const parts = path.split("/");
|
|
1687
|
+
for (let index = 1; index < parts.length; index += 1) {
|
|
1688
|
+
if (kinds.get(parts.slice(0, index).join("/")) === "file") throw new TypeError("Code source archive path conflicts");
|
|
1689
|
+
}
|
|
1690
|
+
if (!entry.directory) {
|
|
1691
|
+
for (const existing of kinds.keys()) {
|
|
1692
|
+
if (existing.startsWith(`${path}/`)) throw new TypeError("Code source archive path conflicts");
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
if (kinds.has(path)) throw new TypeError("Code source archive repeats a path");
|
|
1696
|
+
kinds.set(path, entry.directory ? "directory" : "file");
|
|
1697
|
+
output.push({ ...entry, path });
|
|
1698
|
+
}
|
|
1699
|
+
return output.sort((left, right) => left.path.localeCompare(right.path));
|
|
1700
|
+
}
|
|
1701
|
+
function parsePax(bytes, decoder, maxPathBytes) {
|
|
1702
|
+
const result = {};
|
|
1703
|
+
let offset = 0;
|
|
1704
|
+
while (offset < bytes.byteLength) {
|
|
1705
|
+
const space = bytes.indexOf(32, offset);
|
|
1706
|
+
if (space < 0) throw new TypeError("Code source archive PAX header is invalid");
|
|
1707
|
+
const lengthText = ascii(bytes.subarray(offset, space));
|
|
1708
|
+
if (!/^[1-9][0-9]{0,8}$/.test(lengthText)) throw new TypeError("Code source archive PAX length is invalid");
|
|
1709
|
+
const length = Number(lengthText);
|
|
1710
|
+
const end = offset + length;
|
|
1711
|
+
if (!Number.isSafeInteger(length) || end > bytes.byteLength || bytes[end - 1] !== 10) {
|
|
1712
|
+
throw new TypeError("Code source archive PAX record is invalid");
|
|
1713
|
+
}
|
|
1714
|
+
const record4 = decode(bytes.subarray(space + 1, end - 1), decoder);
|
|
1715
|
+
const equals = record4.indexOf("=");
|
|
1716
|
+
if (equals < 1) throw new TypeError("Code source archive PAX field is invalid");
|
|
1717
|
+
const key = record4.slice(0, equals);
|
|
1718
|
+
if (Object.hasOwn(result, key)) throw new TypeError("Code source archive PAX field repeats");
|
|
1719
|
+
result[key] = record4.slice(equals + 1);
|
|
1720
|
+
offset = end;
|
|
1721
|
+
}
|
|
1722
|
+
if (result.path !== void 0) result.path = validateArchivePath(result.path, maxPathBytes);
|
|
1723
|
+
return result;
|
|
1724
|
+
}
|
|
1725
|
+
var aligned = (size) => Math.ceil(size / 512) * 512;
|
|
1726
|
+
var zeroBlock = (block) => block.every((value) => value === 0);
|
|
1727
|
+
function validateChecksum(header) {
|
|
1728
|
+
const expected = tarNumber(header.subarray(148, 156));
|
|
1729
|
+
let actual = 0;
|
|
1730
|
+
for (let index = 0; index < header.length; index += 1) actual += index >= 148 && index < 156 ? 32 : header[index];
|
|
1731
|
+
if (actual !== expected) throw new TypeError("Code source archive checksum is invalid");
|
|
1732
|
+
}
|
|
1733
|
+
function tarNumber(field) {
|
|
1734
|
+
if ((field[0] ?? 0) & 128) throw new TypeError("Code source archive numeric format is unsupported");
|
|
1735
|
+
const value = ascii(field).replaceAll("\0", "").trim();
|
|
1736
|
+
if (!value) return 0;
|
|
1737
|
+
if (!/^[0-7]+$/.test(value)) throw new TypeError("Code source archive number is invalid");
|
|
1738
|
+
const parsed = Number.parseInt(value, 8);
|
|
1739
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) throw new TypeError("Code source archive number is too large");
|
|
1740
|
+
return parsed;
|
|
1741
|
+
}
|
|
1742
|
+
function tarPath(header, decoder) {
|
|
1743
|
+
const name = decodeNul(header.subarray(0, 100), decoder);
|
|
1744
|
+
const prefix = decodeNul(header.subarray(345, 500), decoder);
|
|
1745
|
+
return prefix ? `${prefix}/${name}` : name;
|
|
1746
|
+
}
|
|
1747
|
+
function decodeNul(value, decoder) {
|
|
1748
|
+
const end = value.indexOf(0);
|
|
1749
|
+
return decode(end < 0 ? value : value.subarray(0, end), decoder);
|
|
1750
|
+
}
|
|
1751
|
+
function decode(value, decoder) {
|
|
1752
|
+
try {
|
|
1753
|
+
return decoder.decode(value);
|
|
1754
|
+
} catch {
|
|
1755
|
+
throw new TypeError("Code source archive text is invalid UTF-8");
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
function ascii(value) {
|
|
1759
|
+
let output = "";
|
|
1760
|
+
for (const byte of value) output += String.fromCharCode(byte);
|
|
1761
|
+
return output;
|
|
1762
|
+
}
|
|
1763
|
+
function validateArchivePath(input, maximum) {
|
|
1764
|
+
const path = input.endsWith("/") ? input.slice(0, -1) : input;
|
|
1765
|
+
validateRepositoryPath(path, maximum);
|
|
1766
|
+
return path;
|
|
1767
|
+
}
|
|
1768
|
+
function validateRepositoryPath(path, maximum) {
|
|
1769
|
+
if (!path || path.startsWith("/") || path.includes("\\") || /[\u0000-\u001f\u007f]/.test(path) || new TextEncoder().encode(path).byteLength > maximum || path.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
1770
|
+
throw new TypeError("Code source archive path is unsafe");
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
function assertZeroTail(bytes, offset) {
|
|
1774
|
+
for (let index = offset; index < bytes.byteLength; index += 1) {
|
|
1775
|
+
if (bytes[index] !== 0) throw new TypeError("Code source archive has data after its end marker");
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
function filteredPath(path) {
|
|
1779
|
+
const parts = path.split("/");
|
|
1780
|
+
return parts.some((part) => RESERVED2.has(part) || SECRET2.test(part));
|
|
1781
|
+
}
|
|
1782
|
+
function nulShare(content) {
|
|
1783
|
+
if (!content.length) return 0;
|
|
1784
|
+
let count = 0;
|
|
1785
|
+
for (let index = 0; index < content.length; index += 1) if (content.charCodeAt(index) === 0) count += 1;
|
|
1786
|
+
return count / content.length;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
// src/code-runtime-selected-source.ts
|
|
1790
|
+
var import_promises7 = require("fs/promises");
|
|
1791
|
+
var import_node_path8 = require("path");
|
|
1792
|
+
function selectedSourceSet(payload) {
|
|
1793
|
+
if (!payload.sourceSet) return null;
|
|
1794
|
+
const set = payload.sourceSet && typeof payload.sourceSet === "object" && !Array.isArray(payload.sourceSet) ? payload.sourceSet : null;
|
|
1795
|
+
if (!set) throw new TypeError("Code selected source set is invalid");
|
|
1796
|
+
const parse2 = (value, primary2) => {
|
|
1797
|
+
const item = value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1798
|
+
const alias = item?.alias;
|
|
1799
|
+
const repository = item?.repository;
|
|
1800
|
+
const commitSha = item?.commitSha;
|
|
1801
|
+
const materialization = item?.materialization ?? "registry_snapshot";
|
|
1802
|
+
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))) {
|
|
1803
|
+
throw new TypeError("Code selected source identity is invalid");
|
|
1804
|
+
}
|
|
1805
|
+
const visiblePaths = item?.visiblePaths;
|
|
1806
|
+
if (visiblePaths !== void 0 && (!Array.isArray(visiblePaths) || visiblePaths.length > 2e3 || visiblePaths.some((path) => typeof path !== "string" || path.length > 4096))) {
|
|
1807
|
+
throw new TypeError("Code selected source visibility slice is invalid");
|
|
1808
|
+
}
|
|
1809
|
+
return {
|
|
1810
|
+
alias,
|
|
1811
|
+
repository,
|
|
1812
|
+
commitSha,
|
|
1813
|
+
materialization,
|
|
1814
|
+
...visiblePaths ? { visiblePaths } : {}
|
|
1815
|
+
};
|
|
1816
|
+
};
|
|
1817
|
+
if (!Array.isArray(set.references)) throw new TypeError("Code selected source references are invalid");
|
|
1818
|
+
const primary = parse2(set.primary, true);
|
|
1819
|
+
const references = set.references.map((item) => parse2(item, false));
|
|
1820
|
+
const aliases = /* @__PURE__ */ new Set([primary.alias, ...references.map((item) => item.alias)]);
|
|
1821
|
+
if (aliases.size !== references.length + 1) throw new TypeError("Code selected source aliases repeat");
|
|
1822
|
+
return { primary, references };
|
|
1823
|
+
}
|
|
1824
|
+
async function attachReferenceDirectories(workspace, references) {
|
|
1825
|
+
for (const reference of references) {
|
|
1826
|
+
validateAlias(reference.alias);
|
|
1827
|
+
for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
|
|
1828
|
+
const target = (0, import_node_path8.join)(root, ".odla-references", reference.alias);
|
|
1829
|
+
await (0, import_promises7.mkdir)((0, import_node_path8.dirname)(target), { recursive: true });
|
|
1830
|
+
await (0, import_promises7.cp)(reference.sourceDir, target, { recursive: true, errorOnExist: true, force: false });
|
|
1831
|
+
await makeTreeReadOnly(target);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
function validateAlias(alias) {
|
|
1836
|
+
if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias) || alias === "primary") {
|
|
1837
|
+
throw new TypeError("Code reference alias is invalid");
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
async function makeTreeReadOnly(root) {
|
|
1841
|
+
for (const entry of await (0, import_promises7.readdir)(root, { withFileTypes: true })) {
|
|
1842
|
+
const target = (0, import_node_path8.join)(root, entry.name);
|
|
1843
|
+
if (entry.isDirectory()) await makeTreeReadOnly(target);
|
|
1844
|
+
else if (entry.isFile()) await (0, import_promises7.chmod)(target, 292);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
// src/code-runtime-source.ts
|
|
1849
|
+
var RESERVED3 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
1850
|
+
var SECRET3 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
1447
1851
|
var SOURCE_MAX_FILES = 1e5;
|
|
1448
1852
|
var SOURCE_MAX_BYTES = 80 * 1024 * 1024;
|
|
1449
1853
|
var SOURCE_SET_MAX_BYTES = 480 * 1024 * 1024;
|
|
1450
|
-
async function materializeCodeRuntimeSource(snapshot, tempRoot = (0,
|
|
1854
|
+
async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node_os3.tmpdir)()) {
|
|
1451
1855
|
if (!snapshot.files.length || snapshot.files.length > SOURCE_MAX_FILES) throw new TypeError("Code source file count is invalid");
|
|
1452
|
-
const root = await (0,
|
|
1453
|
-
const sourceDir = (0,
|
|
1454
|
-
await (0,
|
|
1856
|
+
const root = await (0, import_promises8.mkdtemp)((0, import_node_path9.join)(tempRoot, "odla-code-source-"));
|
|
1857
|
+
const sourceDir = (0, import_node_path9.join)(root, "source");
|
|
1858
|
+
await (0, import_promises8.mkdir)(sourceDir);
|
|
1455
1859
|
const seen = /* @__PURE__ */ new Set();
|
|
1456
1860
|
let bytes = 0;
|
|
1457
1861
|
try {
|
|
@@ -1461,13 +1865,13 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node
|
|
|
1461
1865
|
seen.add(file.path);
|
|
1462
1866
|
bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
|
|
1463
1867
|
if (bytes > SOURCE_MAX_BYTES) throw new TypeError("Code source exceeds its byte bound");
|
|
1464
|
-
const target = (0,
|
|
1465
|
-
if (!target.startsWith(`${(0,
|
|
1466
|
-
await (0,
|
|
1467
|
-
await (0,
|
|
1868
|
+
const target = (0, import_node_path9.resolve)(sourceDir, file.path);
|
|
1869
|
+
if (!target.startsWith(`${(0, import_node_path9.resolve)(sourceDir)}${import_node_path9.sep}`)) throw new TypeError("Code source path escapes its root");
|
|
1870
|
+
await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
|
|
1871
|
+
await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 420 });
|
|
1468
1872
|
}
|
|
1469
1873
|
for (const reference of snapshot.references ?? []) {
|
|
1470
|
-
|
|
1874
|
+
validateAlias2(reference.alias);
|
|
1471
1875
|
if (!reference.files.length || reference.files.length > SOURCE_MAX_FILES) throw new TypeError("Code reference file count is invalid");
|
|
1472
1876
|
for (const file of reference.files) {
|
|
1473
1877
|
validatePath(file.path);
|
|
@@ -1476,19 +1880,19 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node
|
|
|
1476
1880
|
seen.add(path);
|
|
1477
1881
|
bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
|
|
1478
1882
|
if (bytes > SOURCE_SET_MAX_BYTES) throw new TypeError("Code source set exceeds its byte bound");
|
|
1479
|
-
const target = (0,
|
|
1480
|
-
if (!target.startsWith(`${(0,
|
|
1481
|
-
await (0,
|
|
1482
|
-
await (0,
|
|
1883
|
+
const target = (0, import_node_path9.resolve)(sourceDir, path);
|
|
1884
|
+
if (!target.startsWith(`${(0, import_node_path9.resolve)(sourceDir)}${import_node_path9.sep}`)) throw new TypeError("Code reference path escapes its root");
|
|
1885
|
+
await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
|
|
1886
|
+
await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
|
|
1483
1887
|
}
|
|
1484
1888
|
}
|
|
1485
|
-
return { sourceDir, cleanup: () => (0,
|
|
1889
|
+
return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
|
|
1486
1890
|
} catch (cause) {
|
|
1487
|
-
await (0,
|
|
1891
|
+
await (0, import_promises8.rm)(root, { recursive: true, force: true });
|
|
1488
1892
|
throw cause;
|
|
1489
1893
|
}
|
|
1490
1894
|
}
|
|
1491
|
-
function
|
|
1895
|
+
function validateAlias2(alias) {
|
|
1492
1896
|
if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias) || alias === "primary") {
|
|
1493
1897
|
throw new TypeError("Code reference alias is invalid");
|
|
1494
1898
|
}
|
|
@@ -1496,24 +1900,24 @@ function validateAlias(alias) {
|
|
|
1496
1900
|
async function attachCodeRuntimeReferences(workspace, references) {
|
|
1497
1901
|
let bytes = 0;
|
|
1498
1902
|
for (const reference of references) {
|
|
1499
|
-
|
|
1903
|
+
validateAlias2(reference.alias);
|
|
1500
1904
|
for (const file of reference.files) {
|
|
1501
1905
|
validatePath(file.path);
|
|
1502
1906
|
const path = `.odla-references/${reference.alias}/${file.path}`;
|
|
1503
1907
|
bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
|
|
1504
1908
|
if (bytes > SOURCE_SET_MAX_BYTES - SOURCE_MAX_BYTES) throw new TypeError("Code reference set exceeds its byte bound");
|
|
1505
1909
|
for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
|
|
1506
|
-
const target = (0,
|
|
1507
|
-
if (!target.startsWith(`${(0,
|
|
1508
|
-
await (0,
|
|
1509
|
-
await (0,
|
|
1910
|
+
const target = (0, import_node_path9.resolve)(root, path);
|
|
1911
|
+
if (!target.startsWith(`${(0, import_node_path9.resolve)(root)}${import_node_path9.sep}`)) throw new TypeError("Code reference path escapes its root");
|
|
1912
|
+
await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
|
|
1913
|
+
await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
|
|
1510
1914
|
}
|
|
1511
1915
|
}
|
|
1512
1916
|
}
|
|
1513
1917
|
}
|
|
1514
1918
|
function validatePath(path) {
|
|
1515
1919
|
const parts = path.split("/");
|
|
1516
|
-
if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." ||
|
|
1920
|
+
if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." || RESERVED3.has(part) || SECRET3.test(part))) {
|
|
1517
1921
|
throw new TypeError("Code source contains an unsafe path");
|
|
1518
1922
|
}
|
|
1519
1923
|
}
|
|
@@ -1541,12 +1945,12 @@ async function materializeCommandWorkspace(input) {
|
|
|
1541
1945
|
throw new TypeError("Code selected source set is invalid");
|
|
1542
1946
|
}
|
|
1543
1947
|
if (references.length) {
|
|
1544
|
-
const
|
|
1545
|
-
if (
|
|
1948
|
+
const selected2 = await input.control.source(command.sessionId);
|
|
1949
|
+
if (selected2.repository !== metadata.repository || selected2.commitSha !== metadata.baseCommitSha || selected2.treeDigest !== metadata.sourceTreeDigest) {
|
|
1546
1950
|
await prepared.workspace.cleanup();
|
|
1547
1951
|
throw new TypeError("Code local source does not match the selected GitHub primary source");
|
|
1548
1952
|
}
|
|
1549
|
-
await attachCodeRuntimeReferences(prepared.workspace,
|
|
1953
|
+
await attachCodeRuntimeReferences(prepared.workspace, selected2.references ?? []);
|
|
1550
1954
|
}
|
|
1551
1955
|
}
|
|
1552
1956
|
return {
|
|
@@ -1556,6 +1960,63 @@ async function materializeCommandWorkspace(input) {
|
|
|
1556
1960
|
requestedLocal
|
|
1557
1961
|
};
|
|
1558
1962
|
}
|
|
1963
|
+
const selected = selectedSourceSet(command.payload);
|
|
1964
|
+
if (selected?.primary.materialization === "host_archive") {
|
|
1965
|
+
if (!input.control.sourceArchive) {
|
|
1966
|
+
throw new TypeError("Code runtime protocol does not support host source materialization");
|
|
1967
|
+
}
|
|
1968
|
+
const sources = [selected.primary, ...selected.references];
|
|
1969
|
+
const materialized2 = [];
|
|
1970
|
+
try {
|
|
1971
|
+
for (const descriptor2 of sources) {
|
|
1972
|
+
if (descriptor2.materialization !== "host_archive") {
|
|
1973
|
+
throw new TypeError("Code selected source set mixes incompatible materialization modes");
|
|
1974
|
+
}
|
|
1975
|
+
const archive = await input.control.sourceArchive(command.sessionId, descriptor2.alias);
|
|
1976
|
+
if (archive.repository !== descriptor2.repository || archive.commitSha !== descriptor2.commitSha) {
|
|
1977
|
+
throw new TypeError("Code source archive does not match its selected repository and commit");
|
|
1978
|
+
}
|
|
1979
|
+
const source2 = await materializeCodeRuntimeArchive(
|
|
1980
|
+
archive,
|
|
1981
|
+
descriptor2.visiblePaths ? new Set(descriptor2.visiblePaths) : void 0
|
|
1982
|
+
);
|
|
1983
|
+
const treeDigest = await digestStagedWorkspace(source2.sourceDir, {
|
|
1984
|
+
maxFiles: archive.limits.maxFiles,
|
|
1985
|
+
maxBytes: archive.limits.maxTotalFileBytes
|
|
1986
|
+
});
|
|
1987
|
+
materialized2.push({ descriptor: descriptor2, source: source2, treeDigest });
|
|
1988
|
+
}
|
|
1989
|
+
const primary = materialized2[0];
|
|
1990
|
+
const workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
|
|
1991
|
+
trustedBaseDir: primary.source.sourceDir,
|
|
1992
|
+
trustedBaseCommitSha: primary.descriptor.commitSha,
|
|
1993
|
+
checkpoint: codeCheckpointPayload(command.payload)
|
|
1994
|
+
})).workspace : await stageWorkspace(primary.source.sourceDir, {
|
|
1995
|
+
maxFiles: SOURCE_MAX_FILES,
|
|
1996
|
+
maxBytes: SOURCE_SET_MAX_BYTES
|
|
1997
|
+
});
|
|
1998
|
+
try {
|
|
1999
|
+
await attachReferenceDirectories(workspace, materialized2.slice(1).map((item) => ({
|
|
2000
|
+
alias: item.descriptor.alias,
|
|
2001
|
+
sourceDir: item.source.sourceDir
|
|
2002
|
+
})));
|
|
2003
|
+
} catch (cause) {
|
|
2004
|
+
await workspace.cleanup();
|
|
2005
|
+
throw cause;
|
|
2006
|
+
}
|
|
2007
|
+
return {
|
|
2008
|
+
workspace,
|
|
2009
|
+
sourceDigest: primary.treeDigest,
|
|
2010
|
+
requestedLocal: null,
|
|
2011
|
+
sourceDigests: materialized2.map((item) => ({
|
|
2012
|
+
alias: item.descriptor.alias,
|
|
2013
|
+
treeDigest: item.treeDigest
|
|
2014
|
+
}))
|
|
2015
|
+
};
|
|
2016
|
+
} finally {
|
|
2017
|
+
await Promise.allSettled(materialized2.map((item) => item.source.cleanup()));
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
1559
2020
|
const source = await input.control.source(command.sessionId);
|
|
1560
2021
|
const materialized = await materializeCodeRuntimeSource(source);
|
|
1561
2022
|
try {
|
|
@@ -1893,14 +2354,40 @@ async function sessionSkillsFor(options, command) {
|
|
|
1893
2354
|
}
|
|
1894
2355
|
|
|
1895
2356
|
// src/code-runtime-inference.ts
|
|
2357
|
+
var OVERLOAD_RETRY_DELAYS_MS = [2e3, 4e3, 8e3, 16e3];
|
|
2358
|
+
var RETRYABLE_CODES = /* @__PURE__ */ new Set(["control_plane_overloaded", "registry_overloaded", "transport_unavailable"]);
|
|
2359
|
+
function overloadedControlFailure(cause) {
|
|
2360
|
+
return cause instanceof CodeRuntimeControlError && cause.status === 503 && RETRYABLE_CODES.has(cause.code);
|
|
2361
|
+
}
|
|
2362
|
+
async function inferWithBackoff(infer, wait2, onRetry) {
|
|
2363
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
2364
|
+
try {
|
|
2365
|
+
return await infer();
|
|
2366
|
+
} catch (cause) {
|
|
2367
|
+
const delayMs = OVERLOAD_RETRY_DELAYS_MS[attempt];
|
|
2368
|
+
if (delayMs === void 0 || !overloadedControlFailure(cause)) throw cause;
|
|
2369
|
+
await onRetry(cause, delayMs);
|
|
2370
|
+
await wait2(delayMs);
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
1896
2374
|
async function handleCodeRuntimeInference(input) {
|
|
1897
2375
|
const { command, request, state } = input;
|
|
1898
2376
|
const startedAt = Date.now();
|
|
1899
|
-
const
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
2377
|
+
const wait2 = input.wait ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
2378
|
+
const response2 = await inferWithBackoff(
|
|
2379
|
+
() => input.control.infer(command.sessionId, {
|
|
2380
|
+
requestId: request.requestId,
|
|
2381
|
+
interactionId: command.commandId,
|
|
2382
|
+
call: request.call
|
|
2383
|
+
}),
|
|
2384
|
+
wait2,
|
|
2385
|
+
(cause, delayMs) => input.event({
|
|
2386
|
+
type: "diagnostic",
|
|
2387
|
+
level: "error",
|
|
2388
|
+
message: `Code control plane overloaded (${cause.code}); retrying the model call in ${delayMs / 1e3}s`
|
|
2389
|
+
}).catch(() => void 0)
|
|
2390
|
+
);
|
|
1904
2391
|
state.tokens += response2.receipt.inputTokens + response2.receipt.outputTokens;
|
|
1905
2392
|
const { costUsd } = response2.receipt;
|
|
1906
2393
|
if (costUsd === void 0) state.costKnown = false;
|
|
@@ -2363,12 +2850,12 @@ function response(request, ok, content, details) {
|
|
|
2363
2850
|
}
|
|
2364
2851
|
|
|
2365
2852
|
// src/code-tool-reads.ts
|
|
2366
|
-
var
|
|
2853
|
+
var import_promises11 = require("fs/promises");
|
|
2367
2854
|
|
|
2368
2855
|
// src/code-tool-discovery.ts
|
|
2369
2856
|
var import_node_child_process5 = require("child_process");
|
|
2370
|
-
var
|
|
2371
|
-
var
|
|
2857
|
+
var import_promises9 = require("fs/promises");
|
|
2858
|
+
var import_node_path10 = require("path");
|
|
2372
2859
|
var DEFAULT_MAX_FILES = 2e4;
|
|
2373
2860
|
var DEFAULT_MAX_RESULTS = 100;
|
|
2374
2861
|
var DEFAULT_MAX_FILE_BYTES = 512 * 1024;
|
|
@@ -2393,13 +2880,13 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
2393
2880
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
2394
2881
|
const paths2 = [];
|
|
2395
2882
|
const walk = async (directory) => {
|
|
2396
|
-
for (const entry of await (0,
|
|
2883
|
+
for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
|
|
2397
2884
|
if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
2398
2885
|
if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
|
|
2399
|
-
const target = (0,
|
|
2886
|
+
const target = (0, import_node_path10.resolve)(directory, entry.name);
|
|
2400
2887
|
if (entry.isDirectory()) await walk(target);
|
|
2401
2888
|
else if (entry.isFile()) {
|
|
2402
|
-
const path = (0,
|
|
2889
|
+
const path = (0, import_node_path10.relative)(root, target).split("\\").join("/");
|
|
2403
2890
|
try {
|
|
2404
2891
|
validateRelativePath(path);
|
|
2405
2892
|
} catch {
|
|
@@ -2410,7 +2897,7 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
|
2410
2897
|
}
|
|
2411
2898
|
}
|
|
2412
2899
|
};
|
|
2413
|
-
await walk((0,
|
|
2900
|
+
await walk((0, import_node_path10.resolve)(root));
|
|
2414
2901
|
return paths2.sort();
|
|
2415
2902
|
}
|
|
2416
2903
|
function listWorkspace(paths2, options = {}) {
|
|
@@ -2524,7 +3011,7 @@ async function fallbackSearch(root, scoped, options) {
|
|
|
2524
3011
|
if (matches.length >= options.maxResults) break;
|
|
2525
3012
|
let source;
|
|
2526
3013
|
try {
|
|
2527
|
-
source = await (0,
|
|
3014
|
+
source = await (0, import_promises9.readFile)((0, import_node_path10.resolve)(root, path));
|
|
2528
3015
|
} catch {
|
|
2529
3016
|
continue;
|
|
2530
3017
|
}
|
|
@@ -2542,15 +3029,15 @@ async function fallbackSearch(root, scoped, options) {
|
|
|
2542
3029
|
}
|
|
2543
3030
|
|
|
2544
3031
|
// src/code-tool-graph.ts
|
|
2545
|
-
var
|
|
2546
|
-
var
|
|
3032
|
+
var import_promises10 = require("fs/promises");
|
|
3033
|
+
var import_node_path11 = require("path");
|
|
2547
3034
|
var import_graph = require("@odla-ai/graph");
|
|
2548
3035
|
var import_code4 = require("@odla-ai/graph/code");
|
|
2549
3036
|
var cache = /* @__PURE__ */ new Map();
|
|
2550
3037
|
function workspaceGraphs(workspaceDir, paths2) {
|
|
2551
3038
|
const existing = cache.get(workspaceDir);
|
|
2552
3039
|
if (existing) return existing;
|
|
2553
|
-
const read2 = (path) => (0,
|
|
3040
|
+
const read2 = (path) => (0, import_promises10.readFile)((0, import_node_path11.join)(workspaceDir, path), "utf8");
|
|
2554
3041
|
const built = (async () => ({
|
|
2555
3042
|
// No knownTables: a staged workspace may not carry migrations, and a filter
|
|
2556
3043
|
// that silently drops every table is worse than an unfiltered one. Callers
|
|
@@ -2624,11 +3111,11 @@ async function read(context, request, options, policy, registry) {
|
|
|
2624
3111
|
const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
|
|
2625
3112
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2626
3113
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
2627
|
-
const info = await (0,
|
|
3114
|
+
const info = await (0, import_promises11.stat)(target);
|
|
2628
3115
|
if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
|
|
2629
3116
|
throw new TypeError("file is not a bounded regular source file");
|
|
2630
3117
|
}
|
|
2631
|
-
const source = await (0,
|
|
3118
|
+
const source = await (0, import_promises11.readFile)(target);
|
|
2632
3119
|
if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
|
|
2633
3120
|
const lines = source.toString("utf8").split("\n");
|
|
2634
3121
|
const content = lines.slice(startLine - 1, endLine).join("\n");
|
|
@@ -3150,8 +3637,8 @@ var runtimeErrorMessage = (value) => value instanceof Error ? value.message : St
|
|
|
3150
3637
|
function codeRuntimeAcknowledgementGate(signal) {
|
|
3151
3638
|
let settle;
|
|
3152
3639
|
let settled = false;
|
|
3153
|
-
const ready = new Promise((
|
|
3154
|
-
settle =
|
|
3640
|
+
const ready = new Promise((resolve7) => {
|
|
3641
|
+
settle = resolve7;
|
|
3155
3642
|
});
|
|
3156
3643
|
const release = (run) => {
|
|
3157
3644
|
if (settled) return;
|
|
@@ -3260,7 +3747,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3260
3747
|
async #start(command, resume) {
|
|
3261
3748
|
if (this.#active.has(command.sessionId)) throw new TypeError("Code session is already active on this runtime");
|
|
3262
3749
|
const metadata = codeCommandMetadata(command.payload, resume);
|
|
3263
|
-
const { workspace, sourceDigest, localTrustedBaseDigest, requestedLocal } = await materializeCommandWorkspace({
|
|
3750
|
+
const { workspace, sourceDigest, sourceDigests, localTrustedBaseDigest, requestedLocal } = await materializeCommandWorkspace({
|
|
3264
3751
|
command,
|
|
3265
3752
|
metadata,
|
|
3266
3753
|
resume,
|
|
@@ -3276,11 +3763,12 @@ var TheseusRuntimeEngine = class {
|
|
|
3276
3763
|
acknowledged: false,
|
|
3277
3764
|
startGate,
|
|
3278
3765
|
role: metadata.role,
|
|
3766
|
+
readOnly: metadata.readOnly,
|
|
3279
3767
|
title: metadata.title,
|
|
3280
3768
|
maxTokensPerInteraction: metadata.maxTokensPerInteraction,
|
|
3281
3769
|
baseCommitSha: metadata.baseCommitSha,
|
|
3282
3770
|
repository: metadata.repository,
|
|
3283
|
-
sourceTreeDigest: metadata.sourceTreeDigest,
|
|
3771
|
+
sourceTreeDigest: metadata.sourceTreeDigest ?? sourceDigest,
|
|
3284
3772
|
trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
|
|
3285
3773
|
maxFiles: 2e4,
|
|
3286
3774
|
maxBytes: 512 * 1024 * 1024
|
|
@@ -3306,7 +3794,11 @@ var TheseusRuntimeEngine = class {
|
|
|
3306
3794
|
await this.#failure(command, active, detail);
|
|
3307
3795
|
return null;
|
|
3308
3796
|
});
|
|
3309
|
-
return {
|
|
3797
|
+
return {
|
|
3798
|
+
status: "running",
|
|
3799
|
+
message: resume ? "Theseus resumed from a portable checkpoint" : "Theseus started",
|
|
3800
|
+
...sourceDigests ? { sourceDigests } : {}
|
|
3801
|
+
};
|
|
3310
3802
|
}
|
|
3311
3803
|
/**
|
|
3312
3804
|
* Pursue a goal: attempt, judge with the clean verifier, re-prompt from what
|
|
@@ -3332,6 +3824,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3332
3824
|
event: (event) => this.#event(command, event, active.conversationRefs).then(() => void 0, () => void 0),
|
|
3333
3825
|
attempt: (prompt) => this.#runAttempt(command, {
|
|
3334
3826
|
role: active.role,
|
|
3827
|
+
readOnly: active.readOnly,
|
|
3335
3828
|
title: active.title,
|
|
3336
3829
|
prompt,
|
|
3337
3830
|
maxTokensPerInteraction: active.maxTokensPerInteraction,
|
|
@@ -3374,6 +3867,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3374
3867
|
await this.#takeOver(command, "prompt requires an active Code session");
|
|
3375
3868
|
active.done = this.#runAttempt(command, {
|
|
3376
3869
|
role: active.role,
|
|
3870
|
+
readOnly: active.readOnly,
|
|
3377
3871
|
title: active.title,
|
|
3378
3872
|
prompt,
|
|
3379
3873
|
maxTokensPerInteraction: active.maxTokensPerInteraction,
|
|
@@ -3421,7 +3915,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3421
3915
|
workspaceDir: active.workspace.workspaceDir,
|
|
3422
3916
|
prompt: metadata.prompt,
|
|
3423
3917
|
signal: active.abort.signal,
|
|
3424
|
-
readOnly: metadata.
|
|
3918
|
+
readOnly: metadata.readOnly,
|
|
3425
3919
|
recipeIds: this.options.recipes.map((recipe2) => recipe2.id),
|
|
3426
3920
|
...extraSkills.length ? { extraSkills } : {}
|
|
3427
3921
|
});
|
|
@@ -3531,7 +4025,7 @@ function parse(argv) {
|
|
|
3531
4025
|
};
|
|
3532
4026
|
}
|
|
3533
4027
|
async function readPolicy(path) {
|
|
3534
|
-
const value = JSON.parse(await (0,
|
|
4028
|
+
const value = JSON.parse(await (0, import_promises12.readFile)(path, "utf8"));
|
|
3535
4029
|
if (!value || Object.keys(value).some((key) => !["recipes", "recipeAuthorization"].includes(key)) || !Array.isArray(value.recipes) || !value.recipes.length || value.recipeAuthorization !== void 0 && value.recipeAuthorization !== "registered_recipe" && value.recipeAuthorization !== "exact_approval") throw new TypeError("invalid Code build policy file");
|
|
3536
4030
|
const recipes = value.recipes;
|
|
3537
4031
|
for (const recipe2 of recipes) assertCodeBuildRecipe(recipe2);
|
|
@@ -3550,8 +4044,8 @@ async function main() {
|
|
|
3550
4044
|
platform: process.platform === "darwin" ? "macos" : "linux",
|
|
3551
4045
|
arch: process.arch,
|
|
3552
4046
|
engines: [engine],
|
|
3553
|
-
cpuCount: (0,
|
|
3554
|
-
memoryBytes: (0,
|
|
4047
|
+
cpuCount: (0, import_node_os4.cpus)().length,
|
|
4048
|
+
memoryBytes: (0, import_node_os4.totalmem)()
|
|
3555
4049
|
};
|
|
3556
4050
|
const controller = new AbortController();
|
|
3557
4051
|
for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => controller.abort(signal));
|