@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.
@@ -2,8 +2,8 @@
2
2
  "use strict";
3
3
 
4
4
  // src/code-runtime-cli.ts
5
- var import_node_os3 = require("os");
6
- var import_promises10 = require("fs/promises");
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 = 1;
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((resolve6) => {
399
- if (signal?.aborted) return resolve6();
400
- const timer = setTimeout(resolve6, ms);
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
- resolve6();
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((resolve6, reject) => {
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
- resolve6(stdout.trim() === "true");
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
- if (typeof repository !== "string" || !repository.includes("/") || typeof baseCommitSha !== "string" || !/^[0-9a-f]{40}$/.test(baseCommitSha) || typeof sourceTreeDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(sourceTreeDigest)) {
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, import_node_os2.tmpdir)()) {
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, import_promises6.mkdtemp)((0, import_node_path7.join)(tempRoot, "odla-code-source-"));
1453
- const sourceDir = (0, import_node_path7.join)(root, "source");
1454
- await (0, import_promises6.mkdir)(sourceDir);
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, import_node_path7.resolve)(sourceDir, file.path);
1465
- if (!target.startsWith(`${(0, import_node_path7.resolve)(sourceDir)}${import_node_path7.sep}`)) throw new TypeError("Code source path escapes its root");
1466
- await (0, import_promises6.mkdir)((0, import_node_path7.dirname)(target), { recursive: true });
1467
- await (0, import_promises6.writeFile)(target, file.content, { flag: "wx", mode: 420 });
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
- validateAlias(reference.alias);
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, import_node_path7.resolve)(sourceDir, path);
1480
- if (!target.startsWith(`${(0, import_node_path7.resolve)(sourceDir)}${import_node_path7.sep}`)) throw new TypeError("Code reference path escapes its root");
1481
- await (0, import_promises6.mkdir)((0, import_node_path7.dirname)(target), { recursive: true });
1482
- await (0, import_promises6.writeFile)(target, file.content, { flag: "wx", mode: 292 });
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, import_promises6.rm)(root, { recursive: true, force: true }) };
1889
+ return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
1486
1890
  } catch (cause) {
1487
- await (0, import_promises6.rm)(root, { recursive: true, force: true });
1891
+ await (0, import_promises8.rm)(root, { recursive: true, force: true });
1488
1892
  throw cause;
1489
1893
  }
1490
1894
  }
1491
- function validateAlias(alias) {
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
- validateAlias(reference.alias);
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, import_node_path7.resolve)(root, path);
1507
- if (!target.startsWith(`${(0, import_node_path7.resolve)(root)}${import_node_path7.sep}`)) throw new TypeError("Code reference path escapes its root");
1508
- await (0, import_promises6.mkdir)((0, import_node_path7.dirname)(target), { recursive: true });
1509
- await (0, import_promises6.writeFile)(target, file.content, { flag: "wx", mode: 292 });
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 === ".." || RESERVED2.has(part) || SECRET2.test(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 selected = await input.control.source(command.sessionId);
1545
- if (selected.repository !== metadata.repository || selected.commitSha !== metadata.baseCommitSha || selected.treeDigest !== metadata.sourceTreeDigest) {
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, selected.references ?? []);
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 response2 = await input.control.infer(command.sessionId, {
1900
- requestId: request.requestId,
1901
- interactionId: command.commandId,
1902
- call: request.call
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 import_promises9 = require("fs/promises");
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 import_promises7 = require("fs/promises");
2371
- var import_node_path8 = require("path");
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, import_promises7.readdir)(directory, { withFileTypes: true })) {
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, import_node_path8.resolve)(directory, entry.name);
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, import_node_path8.relative)(root, target).split("\\").join("/");
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, import_node_path8.resolve)(root));
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, import_promises7.readFile)((0, import_node_path8.resolve)(root, path));
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 import_promises8 = require("fs/promises");
2546
- var import_node_path9 = require("path");
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, import_promises8.readFile)((0, import_node_path9.join)(workspaceDir, path), "utf8");
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, import_promises9.stat)(target);
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, import_promises9.readFile)(target);
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((resolve6) => {
3154
- settle = resolve6;
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 { status: "running", message: resume ? "Theseus resumed from a portable checkpoint" : "Theseus started" };
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.role === "review",
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, import_promises10.readFile)(path, "utf8"));
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, import_node_os3.cpus)().length,
3554
- memoryBytes: (0, import_node_os3.totalmem)()
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));