@odla-ai/cli 0.14.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -403,12 +403,13 @@ async function scopedToken(platform, scope, options, doFetch, out) {
403
403
  const { token, expiresAt } = await requestToken2({
404
404
  endpoint: audience,
405
405
  email,
406
- label: options.label ?? `odla CLI admin AI (${scope})`,
406
+ label: options.label ?? `odla CLI (${scope})`,
407
407
  scopes: [scope],
408
408
  fetch: doFetch,
409
409
  onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
410
410
  const approvalUrl = verificationUriComplete ?? handshakeUrl(audience, userCode);
411
- out.log(`Review, then approve scoped request ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
411
+ const requestName = scope === "app:code:host:connect" ? "terminal connection" : "scoped request";
412
+ out.log(`Review, then approve ${requestName} ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
412
413
  if (approvalBrowser({ open: options.open }).open) {
413
414
  await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
414
415
  }
@@ -505,8 +506,8 @@ async function responseBody(response2) {
505
506
  }
506
507
  function apiError(status, body) {
507
508
  const error = isRecord(body) && isRecord(body.error) ? body.error : void 0;
508
- const message3 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
509
- return `read System AI admin changes failed (${status}): ${message3}`;
509
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
510
+ return `read System AI admin changes failed (${status}): ${message2}`;
510
511
  }
511
512
  function timestamp(value) {
512
513
  if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
@@ -598,8 +599,8 @@ async function responseBody2(res) {
598
599
  }
599
600
  function apiError2(action, status, body) {
600
601
  const error = isRecord2(body) && isRecord2(body.error) ? body.error : void 0;
601
- const message3 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
602
- return `${action} failed (${status}): ${message3}`;
602
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
603
+ return `${action} failed (${status}): ${message2}`;
603
604
  }
604
605
  function isRecord2(value) {
605
606
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -779,8 +780,8 @@ async function responseBody3(res) {
779
780
  }
780
781
  function apiError3(action, status, body) {
781
782
  const error = isRecord3(body) && isRecord3(body.error) ? body.error : void 0;
782
- const message3 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
783
- return `${action} failed (${status}): ${message3}`;
783
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
784
+ return `${action} failed (${status}): ${message2}`;
784
785
  }
785
786
  function isRecord3(value) {
786
787
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -1110,8 +1111,8 @@ var CAPABILITIES = {
1110
1111
  "connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"
1111
1112
  ]
1112
1113
  };
1113
- function printCapabilities(json2 = false, out = console) {
1114
- if (json2) {
1114
+ function printCapabilities(json = false, out = console) {
1115
+ if (json) {
1115
1116
  out.log(JSON.stringify(CAPABILITIES, null, 2));
1116
1117
  return;
1117
1118
  }
@@ -1160,8 +1161,8 @@ var CalendarRequestError = class extends Error {
1160
1161
  status;
1161
1162
  /** Machine-readable `error.code` from the response body, when present. */
1162
1163
  code;
1163
- constructor(message3, status, code) {
1164
- super(message3);
1164
+ constructor(message2, status, code) {
1165
+ super(message2);
1165
1166
  this.name = "CalendarRequestError";
1166
1167
  this.status = status;
1167
1168
  if (code !== void 0) this.code = code;
@@ -1309,8 +1310,8 @@ async function calendarJson(ctx, suffix, init) {
1309
1310
  const body = await response2.json().catch(() => ({}));
1310
1311
  if (!response2.ok) {
1311
1312
  const code = textField(body.error?.code, 128);
1312
- const message3 = textField(body.error?.message, 500);
1313
- const detail = `${code ? ` ${code}` : ""}${message3 ? `: ${message3}` : ""}`;
1313
+ const message2 = textField(body.error?.message, 500);
1314
+ const detail = `${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`;
1314
1315
  throw new CalendarRequestError(redactSecrets(`calendar request failed (${response2.status})${detail}`), response2.status, code);
1315
1316
  }
1316
1317
  return body;
@@ -1546,8 +1547,8 @@ function pollTimeout(value = 10 * 6e4) {
1546
1547
  function productionConsent(env, yes, action) {
1547
1548
  if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action} for "${env}" without --yes`);
1548
1549
  }
1549
- function printStatus(status, json2, out) {
1550
- if (json2) {
1550
+ function printStatus(status, json, out) {
1551
+ if (json) {
1551
1552
  out.log(JSON.stringify(status, null, 2));
1552
1553
  return;
1553
1554
  }
@@ -1583,8 +1584,8 @@ async function requestHostedSecurityJson(options, path, init, action) {
1583
1584
  const body = await response2.json().catch(() => ({}));
1584
1585
  if (!response2.ok) {
1585
1586
  const code = optionalHostedText(body.error?.code, "error code", 128);
1586
- const message3 = optionalHostedText(body.error?.message, "error message", 300);
1587
- throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message3 ? `: ${message3}` : ""}`);
1587
+ const message2 = optionalHostedText(body.error?.message, "error message", 300);
1588
+ throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
1588
1589
  }
1589
1590
  return body;
1590
1591
  }
@@ -1773,6 +1774,27 @@ async function defaultReadOrigin(cwd) {
1773
1774
  return result.stdout;
1774
1775
  }
1775
1776
 
1777
+ // src/code-runtime-config.ts
1778
+ var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:e7858b4f0291543171b2659b47c5d00f97acc4767cafd9b74ae5228899e0cde4";
1779
+ var CODE_BUILD_RECIPES = Object.freeze([{
1780
+ id: "odla-code-contracts",
1781
+ image: "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd",
1782
+ command: [
1783
+ "node",
1784
+ "--test",
1785
+ "scripts/lib/directories.test.mjs",
1786
+ "scripts/lib/internal-deps.test.mjs",
1787
+ "scripts/lib/npm-audit.test.mjs",
1788
+ "scripts/lib/npm-release-contract.test.mjs",
1789
+ "scripts/lib/release-surfaces.test.mjs"
1790
+ ],
1791
+ timeoutMs: 12e4,
1792
+ maxOutputBytes: 1024 * 1024,
1793
+ cpus: 1,
1794
+ memory: "512m",
1795
+ pids: 128
1796
+ }]);
1797
+
1776
1798
  // src/code-connect.ts
1777
1799
  import { existsSync as existsSync4 } from "fs";
1778
1800
  import { cpus, hostname, totalmem } from "os";
@@ -1803,72 +1825,77 @@ function parseAgentOutput(line) {
1803
1825
  } catch {
1804
1826
  throw new HarnessProtocolError("agent emitted invalid JSON");
1805
1827
  }
1806
- const message3 = record2(value);
1807
- if (!message3 || message3.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
1828
+ const message2 = record2(value);
1829
+ if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
1808
1830
  throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
1809
1831
  }
1810
- if (message3.type === "event") {
1832
+ if (message2.type === "event") {
1811
1833
  return {
1812
1834
  protocolVersion: HARNESS_PROTOCOL_VERSION,
1813
1835
  type: "event",
1814
- kind: boundedText(message3.kind, "event.kind", 120),
1815
- ...message3.payload === void 0 ? {} : { payload: message3.payload }
1836
+ kind: boundedText(message2.kind, "event.kind", 120),
1837
+ ...message2.payload === void 0 ? {} : { payload: message2.payload }
1816
1838
  };
1817
1839
  }
1818
- if (message3.type === "inference.request") {
1819
- const call = record2(message3.call);
1840
+ if (message2.type === "inference.request") {
1841
+ const call = record2(message2.call);
1820
1842
  if (!call || !Array.isArray(call.messages) || !Number.isSafeInteger(call.maxTokens)) {
1821
1843
  throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
1822
1844
  }
1823
1845
  return {
1824
1846
  protocolVersion: HARNESS_PROTOCOL_VERSION,
1825
1847
  type: "inference.request",
1826
- requestId: boundedText(message3.requestId, "requestId", 180),
1848
+ requestId: boundedText(message2.requestId, "requestId", 180),
1827
1849
  call
1828
1850
  };
1829
1851
  }
1830
- if (message3.type === "tool.request") {
1831
- const input = record2(message3.input);
1832
- const tool = String(message3.tool);
1852
+ if (message2.type === "tool.request") {
1853
+ const input = record2(message2.input);
1854
+ const tool = String(message2.tool);
1833
1855
  if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
1834
1856
  throw new HarnessProtocolError("tool.request requires a registered tool and object input");
1835
1857
  }
1836
1858
  return {
1837
1859
  protocolVersion: HARNESS_PROTOCOL_VERSION,
1838
1860
  type: "tool.request",
1839
- requestId: boundedText(message3.requestId, "requestId", 180),
1861
+ requestId: boundedText(message2.requestId, "requestId", 180),
1840
1862
  tool,
1841
1863
  input
1842
1864
  };
1843
1865
  }
1844
- if (message3.type === "attempt.complete") {
1845
- if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(message3.status))) {
1866
+ if (message2.type === "attempt.complete") {
1867
+ if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(message2.status))) {
1846
1868
  throw new HarnessProtocolError("attempt.complete.status is invalid");
1847
1869
  }
1848
1870
  return {
1849
1871
  protocolVersion: HARNESS_PROTOCOL_VERSION,
1850
1872
  type: "attempt.complete",
1851
- status: message3.status,
1852
- ...message3.result === void 0 ? {} : { result: message3.result }
1873
+ status: message2.status,
1874
+ ...message2.result === void 0 ? {} : { result: message2.result }
1853
1875
  };
1854
1876
  }
1855
1877
  throw new HarnessProtocolError("agent message type is unsupported");
1856
1878
  }
1857
- function encodeAgentInput(message3) {
1858
- return `${JSON.stringify(message3)}
1879
+ function encodeAgentInput(message2) {
1880
+ return `${JSON.stringify(message2)}
1859
1881
  `;
1860
1882
  }
1861
1883
 
1862
- // ../harness/dist/chunk-FNYUIJUN.js
1884
+ // ../harness/dist/chunk-TA2FKK27.js
1863
1885
  import { execFile as execFile2, spawn as spawn2 } from "child_process";
1864
1886
  import { constants } from "fs";
1865
1887
  import { access } from "fs/promises";
1866
1888
  import { delimiter, join as join2 } from "path";
1867
1889
  import { getgid, getuid } from "process";
1868
- import { chmod, copyFile, mkdir, mkdtemp, readdir, realpath, rm, stat } from "fs/promises";
1890
+ import { mkdir, mkdtemp, realpath, rm, writeFile } from "fs/promises";
1869
1891
  import { tmpdir } from "os";
1870
- import { basename, join as join22, relative as relative2, resolve as resolve3 } from "path";
1892
+ import { join as join22, resolve as resolve3, sep } from "path";
1871
1893
  import { spawn as spawn22 } from "child_process";
1894
+ import { isAbsolute as isAbsolute3 } from "path";
1895
+ import { chmod, copyFile, lstat, mkdir as mkdir2, mkdtemp as mkdtemp2, readdir, realpath as realpath2, rm as rm2, stat } from "fs/promises";
1896
+ import { tmpdir as tmpdir2 } from "os";
1897
+ import { basename, join as join3, relative as relative2, resolve as resolve22, sep as sep2 } from "path";
1898
+ import { spawn as spawn3 } from "child_process";
1872
1899
  var DIGEST_IMAGE = /^[a-z0-9][a-z0-9._/-]*(?::[a-zA-Z0-9._-]+)?@sha256:[0-9a-f]{64}$/;
1873
1900
  function assertPinnedImage(image) {
1874
1901
  if (!DIGEST_IMAGE.test(image)) throw new TypeError("container image must be pinned by sha256 digest");
@@ -1912,7 +1939,7 @@ async function selectContainerEngine(requested = "auto", options = {}) {
1912
1939
  throw new TypeError("no supported container engine found");
1913
1940
  }
1914
1941
  function inspectRootlessPodman() {
1915
- return new Promise((resolve23, reject) => {
1942
+ return new Promise((resolve33, reject) => {
1916
1943
  execFile2(
1917
1944
  "podman",
1918
1945
  ["info", "--format", "{{.Host.Security.Rootless}}"],
@@ -1922,7 +1949,7 @@ function inspectRootlessPodman() {
1922
1949
  reject(new TypeError("could not verify that the active Podman service is rootless"));
1923
1950
  return;
1924
1951
  }
1925
- resolve23(stdout.trim() === "true");
1952
+ resolve33(stdout.trim() === "true");
1926
1953
  }
1927
1954
  );
1928
1955
  });
@@ -2033,9 +2060,9 @@ async function runContainerAttempt(options) {
2033
2060
  if (bytes.byteLength > 1e6) throw new Error("agent message exceeds 1 MB");
2034
2061
  const line = bytes.toString("utf8");
2035
2062
  if (!line.trim()) return;
2036
- const message3 = parseAgentOutput(line);
2037
- if (message3.type === "attempt.complete") complete = message3;
2038
- const response2 = await options.onMessage(message3);
2063
+ const message2 = parseAgentOutput(line);
2064
+ if (message2.type === "attempt.complete") complete = message2;
2065
+ const response2 = await options.onMessage(message2);
2039
2066
  if (response2 && !child.stdin.destroyed) child.stdin.write(encodeAgentInput(response2));
2040
2067
  };
2041
2068
  try {
@@ -2083,16 +2110,129 @@ async function runContainerAttempt(options) {
2083
2110
  options.signal?.removeEventListener("abort", abort);
2084
2111
  }
2085
2112
  }
2086
- var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
2087
- var SECRET_FILE = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
2113
+ var SKIP_WORKSPACE_DIRS = /* @__PURE__ */ new Set([
2114
+ ".git",
2115
+ ".odla",
2116
+ ".wrangler",
2117
+ "node_modules",
2118
+ "dist",
2119
+ "coverage"
2120
+ ]);
2121
+ var SECRET_WORKSPACE_FILE = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
2122
+ function allowedWorkspacePath(relativePath) {
2123
+ const parts = relativePath.split("/");
2124
+ return !isAbsolute3(relativePath) && !relativePath.includes("\\") && !relativePath.includes("\0") && !parts.some((part) => !part || part === "." || part === ".." || SKIP_WORKSPACE_DIRS.has(part)) && !SECRET_WORKSPACE_FILE.test(parts.at(-1) ?? "");
2125
+ }
2126
+ async function gitOutput(cwd, args, maxBytes) {
2127
+ const child = spawn22("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: false });
2128
+ const stdout = [];
2129
+ const stderr = [];
2130
+ let bytes = 0;
2131
+ child.stdout.on("data", (chunk) => {
2132
+ bytes += chunk.byteLength;
2133
+ if (bytes > maxBytes) child.kill("SIGKILL");
2134
+ else stdout.push(chunk);
2135
+ });
2136
+ child.stderr.on("data", (chunk) => {
2137
+ if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
2138
+ });
2139
+ const code = await new Promise((accept, reject) => {
2140
+ child.once("error", reject);
2141
+ child.once("exit", accept);
2142
+ });
2143
+ if (bytes > maxBytes) throw new Error(`git output exceeds ${maxBytes} bytes`);
2144
+ if (code !== 0) throw new Error(`git command failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
2145
+ return Buffer.concat(stdout);
2146
+ }
2147
+ async function gitBlobs(cwd, entries, maxBytes) {
2148
+ const child = spawn22("git", ["cat-file", "--batch"], { cwd, stdio: ["pipe", "pipe", "pipe"], shell: false });
2149
+ const stdout = [];
2150
+ const stderr = [];
2151
+ let bytes = 0;
2152
+ child.stdout.on("data", (chunk) => {
2153
+ bytes += chunk.byteLength;
2154
+ if (bytes > maxBytes + entries.length * 100) child.kill("SIGKILL");
2155
+ else stdout.push(chunk);
2156
+ });
2157
+ child.stderr.on("data", (chunk) => {
2158
+ if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
2159
+ });
2160
+ child.stdin.end(`${entries.map((entry) => entry.hash).join("\n")}
2161
+ `);
2162
+ const code = await new Promise((accept, reject) => {
2163
+ child.once("error", reject);
2164
+ child.once("exit", accept);
2165
+ });
2166
+ if (bytes > maxBytes + entries.length * 100) throw new Error(`Git tree exceeds ${maxBytes} bytes`);
2167
+ if (code !== 0) throw new Error(`git object read failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
2168
+ const output = Buffer.concat(stdout);
2169
+ const blobs = [];
2170
+ let offset = 0;
2171
+ let total = 0;
2172
+ for (const entry of entries) {
2173
+ const newline = output.indexOf(10, offset);
2174
+ if (newline < 0) throw new Error("git object output is truncated");
2175
+ const match = /^([0-9a-f]{40,64}) blob ([0-9]+)$/.exec(output.subarray(offset, newline).toString("utf8"));
2176
+ if (!match || match[1] !== entry.hash) throw new Error("git object output does not match its inventory");
2177
+ const length = Number(match[2]);
2178
+ if (!Number.isSafeInteger(length) || length < 0 || newline + 1 + length >= output.length) {
2179
+ throw new Error("git object output has an invalid length");
2180
+ }
2181
+ const content = output.subarray(newline + 1, newline + 1 + length);
2182
+ if (output[newline + 1 + length] !== 10) throw new Error("git object output is malformed");
2183
+ total += content.byteLength;
2184
+ if (total > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2185
+ blobs.push(content);
2186
+ offset = newline + 1 + length + 1;
2187
+ }
2188
+ return blobs;
2189
+ }
2190
+ async function materializeGitTree(source, commitSha, options = {}) {
2191
+ if (!/^[0-9a-f]{40}$/.test(commitSha)) throw new TypeError("Git tree requires an exact commit SHA");
2192
+ const sourceDir = await realpath(resolve3(source));
2193
+ const maxFiles = options.maxFiles ?? 2e4;
2194
+ const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
2195
+ const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
2196
+ const entries = inventory.flatMap((record5) => {
2197
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record5);
2198
+ return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
2199
+ });
2200
+ if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
2201
+ const root = await mkdtemp(join22(options.tempRoot ?? tmpdir(), "odla-git-tree-"));
2202
+ const targetRoot = join22(root, "source");
2203
+ await mkdir(targetRoot);
2204
+ let byteCount = 0;
2205
+ try {
2206
+ const blobs = await gitBlobs(sourceDir, entries, maxBytes);
2207
+ for (const [index, entry] of entries.entries()) {
2208
+ const content = blobs[index];
2209
+ byteCount += content.byteLength;
2210
+ if (byteCount > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2211
+ const target = resolve3(targetRoot, entry.path);
2212
+ if (!target.startsWith(`${resolve3(targetRoot)}${sep}`)) throw new TypeError("Git tree path escapes workspace");
2213
+ await mkdir(resolve3(target, ".."), { recursive: true });
2214
+ await writeFile(target, content, { flag: "wx", mode: entry.mode === "100755" ? 493 : 420 });
2215
+ }
2216
+ return {
2217
+ root,
2218
+ sourceDir: targetRoot,
2219
+ fileCount: entries.length,
2220
+ byteCount,
2221
+ cleanup: () => rm(root, { recursive: true, force: true })
2222
+ };
2223
+ } catch (error) {
2224
+ await rm(root, { recursive: true, force: true });
2225
+ throw error;
2226
+ }
2227
+ }
2088
2228
  async function sourceFiles(sourceDir, maxFiles, maxBytes) {
2089
2229
  const files = [];
2090
2230
  let bytes = 0;
2091
2231
  const walk = async (dir) => {
2092
2232
  for (const entry of await readdir(dir, { withFileTypes: true })) {
2093
- if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue;
2094
- if (!entry.isDirectory() && SECRET_FILE.test(entry.name)) continue;
2095
- const path = join22(dir, entry.name);
2233
+ if (entry.isDirectory() && SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
2234
+ if (!entry.isDirectory() && SECRET_WORKSPACE_FILE.test(entry.name)) continue;
2235
+ const path = join3(dir, entry.name);
2096
2236
  if (entry.isSymbolicLink()) continue;
2097
2237
  if (entry.isDirectory()) {
2098
2238
  await walk(path);
@@ -2114,16 +2254,62 @@ async function sourceFiles(sourceDir, maxFiles, maxBytes) {
2114
2254
  await walk(sourceDir);
2115
2255
  return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
2116
2256
  }
2257
+ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
2258
+ const child = spawn3("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
2259
+ cwd: sourceDir,
2260
+ stdio: ["ignore", "pipe", "pipe"],
2261
+ shell: false
2262
+ });
2263
+ const stdout = [];
2264
+ const stderr = [];
2265
+ let outputBytes = 0;
2266
+ child.stdout.on("data", (chunk) => {
2267
+ outputBytes += chunk.byteLength;
2268
+ if (outputBytes > 8 * 1024 * 1024) child.kill("SIGKILL");
2269
+ else stdout.push(chunk);
2270
+ });
2271
+ child.stderr.on("data", (chunk) => {
2272
+ if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
2273
+ });
2274
+ const code = await new Promise((accept, reject) => {
2275
+ child.once("error", reject);
2276
+ child.once("exit", accept);
2277
+ });
2278
+ if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
2279
+ if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
2280
+ const paths = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
2281
+ if (paths.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
2282
+ const root = resolve22(sourceDir);
2283
+ const files = [];
2284
+ let bytes = 0;
2285
+ for (const relativePath of paths) {
2286
+ if (!allowedWorkspacePath(relativePath)) continue;
2287
+ const source = resolve22(root, relativePath);
2288
+ if (!source.startsWith(`${root}${sep2}`)) throw new TypeError("git file path escapes workspace");
2289
+ let metadata2;
2290
+ try {
2291
+ metadata2 = await lstat(source);
2292
+ } catch (error) {
2293
+ if (error.code === "ENOENT") continue;
2294
+ throw error;
2295
+ }
2296
+ if (metadata2.isSymbolicLink() || !metadata2.isFile()) continue;
2297
+ bytes += metadata2.size;
2298
+ if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2299
+ files.push({ source, relativePath, mode: metadata2.mode & 511, bytes: metadata2.size });
2300
+ }
2301
+ return files;
2302
+ }
2117
2303
  async function copyTree(files, destination) {
2118
2304
  for (const file of files) {
2119
- const target = join22(destination, file.relativePath);
2120
- await mkdir(resolve3(target, ".."), { recursive: true });
2305
+ const target = join3(destination, file.relativePath);
2306
+ await mkdir2(resolve22(target, ".."), { recursive: true });
2121
2307
  await copyFile(file.source, target);
2122
2308
  await chmod(target, file.mode);
2123
2309
  }
2124
2310
  }
2125
2311
  async function captureGitDiff(root, maxBytes) {
2126
- const child = spawn22("git", [
2312
+ const child = spawn3("git", [
2127
2313
  "diff",
2128
2314
  "--no-index",
2129
2315
  "--binary",
@@ -2156,15 +2342,17 @@ async function captureGitDiff(root, maxBytes) {
2156
2342
  return Buffer.concat(stdout).toString("utf8").replaceAll("a/baseline/", "a/").replaceAll("b/workspace/", "b/").replaceAll("--- a/baseline", "--- a").replaceAll("+++ b/workspace", "+++ b");
2157
2343
  }
2158
2344
  async function stageWorkspace(source, options = {}) {
2159
- const sourceDir = await realpath(resolve3(source));
2345
+ const sourceDir = await realpath2(resolve22(source));
2160
2346
  const sourceStat = await stat(sourceDir);
2161
2347
  if (!sourceStat.isDirectory()) throw new TypeError("workspace source must be a directory");
2162
- const root = await mkdtemp(join22(options.tempRoot ?? tmpdir(), "odla-harness-"));
2163
- const baselineDir = join22(root, "baseline");
2164
- const workspaceDir = join22(root, "workspace");
2165
- await Promise.all([mkdir(baselineDir), mkdir(workspaceDir)]);
2348
+ const root = await mkdtemp2(join3(options.tempRoot ?? tmpdir2(), "odla-harness-"));
2349
+ const baselineDir = join3(root, "baseline");
2350
+ const workspaceDir = join3(root, "workspace");
2351
+ await Promise.all([mkdir2(baselineDir), mkdir2(workspaceDir)]);
2166
2352
  try {
2167
- const files = await sourceFiles(sourceDir, options.maxFiles ?? 2e4, options.maxBytes ?? 512 * 1024 * 1024);
2353
+ const maxFiles = options.maxFiles ?? 2e4;
2354
+ const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
2355
+ const files = options.gitTrackedAndUnignored ? await gitSourceFiles(sourceDir, maxFiles, maxBytes) : await sourceFiles(sourceDir, maxFiles, maxBytes);
2168
2356
  await Promise.all([copyTree(files, baselineDir), copyTree(files, workspaceDir)]);
2169
2357
  return {
2170
2358
  root,
@@ -2172,15 +2360,49 @@ async function stageWorkspace(source, options = {}) {
2172
2360
  workspaceDir,
2173
2361
  fileCount: files.length,
2174
2362
  byteCount: files.reduce((sum, file) => sum + file.bytes, 0),
2175
- patch: (maxBytes) => captureGitDiff(root, maxBytes),
2176
- cleanup: () => rm(root, { recursive: true, force: true })
2363
+ patch: (maxBytes2) => captureGitDiff(root, maxBytes2),
2364
+ cleanup: () => rm2(root, { recursive: true, force: true })
2177
2365
  };
2178
2366
  } catch (error) {
2179
- await rm(root, { recursive: true, force: true });
2367
+ await rm2(root, { recursive: true, force: true });
2368
+ throw error;
2369
+ }
2370
+ }
2371
+ async function stageWorkspacePair(baselineSource, workspaceSource, options = {}) {
2372
+ const baselineDirSource = await realpath2(resolve22(baselineSource));
2373
+ const workspaceDirSource = await realpath2(resolve22(workspaceSource));
2374
+ const maxFiles = options.maxFiles ?? 2e4;
2375
+ const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
2376
+ const [baselineFiles, workspaceFiles] = await Promise.all([
2377
+ sourceFiles(baselineDirSource, maxFiles, maxBytes),
2378
+ sourceFiles(workspaceDirSource, maxFiles, maxBytes)
2379
+ ]);
2380
+ const root = await mkdtemp2(join3(options.tempRoot ?? tmpdir2(), "odla-harness-"));
2381
+ const baselineDir = join3(root, "baseline");
2382
+ const workspaceDir = join3(root, "workspace");
2383
+ await Promise.all([mkdir2(baselineDir), mkdir2(workspaceDir)]);
2384
+ try {
2385
+ await Promise.all([copyTree(baselineFiles, baselineDir), copyTree(workspaceFiles, workspaceDir)]);
2386
+ return {
2387
+ root,
2388
+ baselineDir,
2389
+ workspaceDir,
2390
+ fileCount: workspaceFiles.length,
2391
+ byteCount: workspaceFiles.reduce((sum, file) => sum + file.bytes, 0),
2392
+ patch: (maxPatchBytes) => captureGitDiff(root, maxPatchBytes),
2393
+ cleanup: () => rm2(root, { recursive: true, force: true })
2394
+ };
2395
+ } catch (error) {
2396
+ await rm2(root, { recursive: true, force: true });
2180
2397
  throw error;
2181
2398
  }
2182
2399
  }
2183
2400
 
2401
+ // ../harness/dist/chunk-QEW6NNAO.js
2402
+ import { createHash } from "crypto";
2403
+ import { readFile, readdir as readdir2 } from "fs/promises";
2404
+ import { relative as relative3, resolve as resolve4 } from "path";
2405
+
2184
2406
  // ../camel/dist/chunk-7FHPOQVP.js
2185
2407
  var CamelError = class extends Error {
2186
2408
  code;
@@ -2199,8 +2421,8 @@ function canonicalJson(value) {
2199
2421
  }
2200
2422
  async function sha256Hex(value) {
2201
2423
  const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
2202
- const digest2 = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
2203
- return [...new Uint8Array(digest2)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2424
+ const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
2425
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2204
2426
  }
2205
2427
  function utf8Length(value) {
2206
2428
  if (typeof value === "string") return new TextEncoder().encode(value).byteLength;
@@ -2477,8 +2699,8 @@ function freeze(value) {
2477
2699
  })
2478
2700
  });
2479
2701
  }
2480
- function invalid2(message3) {
2481
- return new CamelError("state_conflict", message3);
2702
+ function invalid2(message2) {
2703
+ return new CamelError("state_conflict", message2);
2482
2704
  }
2483
2705
  var SHA4 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
2484
2706
  var REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
@@ -2513,23 +2735,20 @@ function validateSnapshot(snapshot, limits) {
2513
2735
  }
2514
2736
  }
2515
2737
 
2516
- // ../harness/dist/chunk-RVUUURK2.js
2517
- import { spawn as spawn3 } from "child_process";
2518
- import { lstat } from "fs/promises";
2519
- import { resolve as resolve4, sep } from "path";
2738
+ // ../harness/dist/chunk-QEW6NNAO.js
2739
+ import { spawn as spawn4 } from "child_process";
2740
+ import { lstat as lstat2 } from "fs/promises";
2741
+ import { resolve as resolve23, sep as sep3 } from "path";
2520
2742
  import { spawn as spawn23 } from "child_process";
2521
2743
  import { getgid as getgid2, getuid as getuid2 } from "process";
2522
2744
  import { randomUUID } from "crypto";
2523
2745
  import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
2524
2746
  import { createReadStream } from "fs";
2525
- import { lstat as lstat2 } from "fs/promises";
2526
- import { join as join3 } from "path";
2527
- import { createHash } from "crypto";
2528
- import { readFile, readdir as readdir2 } from "fs/promises";
2529
- import { relative as relative3, resolve as resolve22 } from "path";
2530
- import { mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm2, writeFile } from "fs/promises";
2531
- import { tmpdir as tmpdir2 } from "os";
2532
- import { dirname as dirname4, join as join23, resolve as resolve32, sep as sep2 } from "path";
2747
+ import { lstat as lstat22 } from "fs/promises";
2748
+ import { join as join4 } from "path";
2749
+ import { mkdir as mkdir3, mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile2 } from "fs/promises";
2750
+ import { tmpdir as tmpdir3 } from "os";
2751
+ import { dirname as dirname4, join as join23, resolve as resolve32, sep as sep22 } from "path";
2533
2752
  import { readFile as readFile2, readdir as readdir22, stat as stat2 } from "fs/promises";
2534
2753
  import { relative as relative22, resolve as resolve42 } from "path";
2535
2754
 
@@ -2809,8 +3028,34 @@ function looksLikeDestination(value) {
2809
3028
  return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
2810
3029
  }
2811
3030
 
2812
- // ../harness/dist/chunk-RVUUURK2.js
3031
+ // ../harness/dist/chunk-QEW6NNAO.js
2813
3032
  import { createHash as createHash3 } from "crypto";
3033
+ async function digestStagedWorkspace(root, limits) {
3034
+ const files = [];
3035
+ const walk = async (directory) => {
3036
+ const entries = await readdir2(directory, { withFileTypes: true });
3037
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
3038
+ if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
3039
+ const target = resolve4(directory, entry.name);
3040
+ if (entry.isDirectory()) await walk(target);
3041
+ else if (entry.isFile()) {
3042
+ files.push({ path: relative3(root, target).split("\\").join("/"), target });
3043
+ if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
3044
+ }
3045
+ }
3046
+ };
3047
+ await walk(resolve4(root));
3048
+ const hash = createHash("sha256");
3049
+ let bytes = 0;
3050
+ for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
3051
+ const content = await readFile(file.target);
3052
+ bytes += Buffer.byteLength(file.path) + content.byteLength;
3053
+ if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
3054
+ hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
3055
+ hash.update(content);
3056
+ }
3057
+ return `sha256:${hash.digest("hex")}`;
3058
+ }
2814
3059
  var CODE_RUNTIME_PROTOCOL_VERSION = 1;
2815
3060
  async function runCodeRuntimeHeartbeatLoop(options) {
2816
3061
  const heartbeatMs = options.heartbeatMs ?? 15e3;
@@ -2882,8 +3127,8 @@ function wait(ms, signal) {
2882
3127
  });
2883
3128
  }
2884
3129
  var CodeRuntimeControlError = class extends Error {
2885
- constructor(message3, status, code = "control_error") {
2886
- super(message3);
3130
+ constructor(message2, status, code = "control_error") {
3131
+ super(message2);
2887
3132
  this.status = status;
2888
3133
  this.code = code;
2889
3134
  }
@@ -2962,9 +3207,9 @@ function createCodeRuntimeControlClient(options) {
2962
3207
  }
2963
3208
  await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
2964
3209
  },
2965
- reportSessionFailure: async (sessionId, message3) => {
2966
- if (!message3.trim() || message3.length > 2e3) throw new TypeError("invalid Code session failure");
2967
- await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message3 });
3210
+ reportSessionFailure: async (sessionId, message2) => {
3211
+ if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
3212
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
2968
3213
  }
2969
3214
  };
2970
3215
  }
@@ -3032,9 +3277,9 @@ async function parseSource(value) {
3032
3277
  return { path: file.path, content: file.content };
3033
3278
  });
3034
3279
  const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
3035
- const digest2 = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3036
- if (digest2 !== snapshot.treeDigest) throw invalid("source digest");
3037
- return { ...source, treeDigest: digest2 };
3280
+ const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3281
+ if (digest !== snapshot.treeDigest) throw invalid("source digest");
3282
+ return { ...source, treeDigest: digest };
3038
3283
  }
3039
3284
  function parseReview(value) {
3040
3285
  const review = record3(record3(value)?.review);
@@ -3093,9 +3338,9 @@ function validateRelativePath(path) {
3093
3338
  }
3094
3339
  function resolveCodePath(workspaceDir, path) {
3095
3340
  validateRelativePath(path);
3096
- const root = resolve4(workspaceDir);
3097
- const target = resolve4(root, path);
3098
- if (target !== root && !target.startsWith(`${root}${sep}`)) throw new TypeError("path escapes the staged workspace");
3341
+ const root = resolve23(workspaceDir);
3342
+ const target = resolve23(root, path);
3343
+ if (target !== root && !target.startsWith(`${root}${sep3}`)) throw new TypeError("path escapes the staged workspace");
3099
3344
  return target;
3100
3345
  }
3101
3346
  async function applyCodePatch(workspaceDir, patch2, paths) {
@@ -3103,7 +3348,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
3103
3348
  await gitApply(workspaceDir, patch2, false);
3104
3349
  for (const path of paths) {
3105
3350
  try {
3106
- const info = await lstat(resolveCodePath(workspaceDir, path));
3351
+ const info = await lstat2(resolveCodePath(workspaceDir, path));
3107
3352
  if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
3108
3353
  throw new TypeError("patch created a non-regular workspace entry");
3109
3354
  }
@@ -3115,7 +3360,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
3115
3360
  function gitApply(cwd, patch2, check) {
3116
3361
  return new Promise((accept, reject) => {
3117
3362
  const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
3118
- const child = spawn3("git", args, {
3363
+ const child = spawn4("git", args, {
3119
3364
  cwd,
3120
3365
  shell: false,
3121
3366
  stdio: ["pipe", "ignore", "pipe"],
@@ -3285,32 +3530,6 @@ function execute(engine, args, name, recipe2, signal) {
3285
3530
  });
3286
3531
  });
3287
3532
  }
3288
- async function digestStagedWorkspace(root, limits) {
3289
- const files = [];
3290
- const walk = async (directory) => {
3291
- const entries = await readdir2(directory, { withFileTypes: true });
3292
- for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
3293
- if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
3294
- const target = resolve22(directory, entry.name);
3295
- if (entry.isDirectory()) await walk(target);
3296
- else if (entry.isFile()) {
3297
- files.push({ path: relative3(root, target).split("\\").join("/"), target });
3298
- if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
3299
- }
3300
- }
3301
- };
3302
- await walk(resolve22(root));
3303
- const hash = createHash("sha256");
3304
- let bytes = 0;
3305
- for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
3306
- const content = await readFile(file.target);
3307
- bytes += Buffer.byteLength(file.path) + content.byteLength;
3308
- if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
3309
- hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
3310
- hash.update(content);
3311
- }
3312
- return `sha256:${hash.digest("hex")}`;
3313
- }
3314
3533
  var SHA3 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
3315
3534
  var DIGEST3 = /^sha256:[0-9a-f]{64}$/;
3316
3535
  var ID3 = /^[A-Za-z0-9._:-]{1,160}$/;
@@ -3419,8 +3638,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
3419
3638
  const receipts = [];
3420
3639
  for (const artifact of recipe2.expectedArtifacts ?? []) {
3421
3640
  try {
3422
- const path = join3(workspaceDir, artifact.path);
3423
- const info = await lstat2(path);
3641
+ const path = join4(workspaceDir, artifact.path);
3642
+ const info = await lstat22(path);
3424
3643
  if (!info.isFile() || info.isSymbolicLink()) {
3425
3644
  receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
3426
3645
  } else if (info.size > artifact.maximumBytes) {
@@ -3516,8 +3735,8 @@ async function prepareRuntimeCheckpoint(input) {
3516
3735
  policy: {
3517
3736
  policyId: "code.runtime",
3518
3737
  recipes: input.recipes,
3519
- maximumFiles: 1e4,
3520
- maximumBytes: 16 * 1024 * 1024
3738
+ maximumFiles: 2e4,
3739
+ maximumBytes: 512 * 1024 * 1024
3521
3740
  },
3522
3741
  recipeExecutor: input.recipeExecutor
3523
3742
  });
@@ -3599,11 +3818,11 @@ var CodeRuntimeCheckpointManager = class {
3599
3818
  };
3600
3819
  var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
3601
3820
  var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
3602
- async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir2()) {
3821
+ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir3()) {
3603
3822
  if (!snapshot.files.length || snapshot.files.length > 1e4) throw new TypeError("Code source file count is invalid");
3604
- const root = await mkdtemp2(join23(tempRoot, "odla-code-source-"));
3823
+ const root = await mkdtemp3(join23(tempRoot, "odla-code-source-"));
3605
3824
  const sourceDir = join23(root, "source");
3606
- await mkdir2(sourceDir);
3825
+ await mkdir3(sourceDir);
3607
3826
  const seen = /* @__PURE__ */ new Set();
3608
3827
  let bytes = 0;
3609
3828
  try {
@@ -3614,13 +3833,13 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir2()) {
3614
3833
  bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
3615
3834
  if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
3616
3835
  const target = resolve32(sourceDir, file.path);
3617
- if (!target.startsWith(`${resolve32(sourceDir)}${sep2}`)) throw new TypeError("Code source path escapes its root");
3618
- await mkdir2(dirname4(target), { recursive: true });
3619
- await writeFile(target, file.content, { flag: "wx", mode: 420 });
3836
+ if (!target.startsWith(`${resolve32(sourceDir)}${sep22}`)) throw new TypeError("Code source path escapes its root");
3837
+ await mkdir3(dirname4(target), { recursive: true });
3838
+ await writeFile2(target, file.content, { flag: "wx", mode: 420 });
3620
3839
  }
3621
- return { sourceDir, cleanup: () => rm2(root, { recursive: true, force: true }) };
3840
+ return { sourceDir, cleanup: () => rm3(root, { recursive: true, force: true }) };
3622
3841
  } catch (cause) {
3623
- await rm2(root, { recursive: true, force: true });
3842
+ await rm3(root, { recursive: true, force: true });
3624
3843
  throw cause;
3625
3844
  }
3626
3845
  }
@@ -3726,15 +3945,15 @@ async function environment(input, options, tool) {
3726
3945
  { id: "authority", value: `lease:${input.lease.leaseId}`, readers: input.readers },
3727
3946
  { id: "reader", value: options.readerId, readers: input.readers }
3728
3947
  ]);
3729
- const digest2 = await destinationRegistryDigest([input.workspaceId]);
3948
+ const digest = await destinationRegistryDigest([input.workspaceId]);
3730
3949
  const approvals = ["irreversible_mutation", "external_send", "financial", "secret_read"];
3731
3950
  if (options.recipeAuthorization === "exact_approval") approvals.push("code_execution");
3732
3951
  const policy = createEffectPolicy({
3733
- destinationRegistries: { [DESTINATIONS]: { digest: digest2, values: [input.workspaceId] } },
3952
+ destinationRegistries: { [DESTINATIONS]: { digest, values: [input.workspaceId] } },
3734
3953
  approvalEffects: approvals
3735
3954
  });
3736
3955
  const fixedArgs = {
3737
- workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest2 },
3956
+ workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest },
3738
3957
  authority: { role: "authority", value: ingress.control("authority") }
3739
3958
  };
3740
3959
  return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
@@ -3934,14 +4153,31 @@ function codeCommandMetadata(payload, resume) {
3934
4153
  }
3935
4154
  const planning = trusted?.planningInputDigest;
3936
4155
  const attestation = trusted?.attestationDigest;
4156
+ const repository = trusted?.repository;
4157
+ const baseCommitSha = trusted?.commitSha;
4158
+ const sourceTreeDigest = trusted?.treeDigest;
4159
+ 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)) {
4160
+ throw new TypeError(`invalid Code ${resume ? "resume" : "start"} trusted base`);
4161
+ }
3937
4162
  return {
3938
4163
  role,
3939
4164
  title,
3940
4165
  prompt,
3941
4166
  planningInputDigest: typeof planning === "string" && /^sha256:[0-9a-f]{64}$/.test(planning) ? planning : null,
3942
- attestationDigest: typeof attestation === "string" ? attestation : "resume"
4167
+ attestationDigest: typeof attestation === "string" ? attestation : "resume",
4168
+ repository,
4169
+ baseCommitSha,
4170
+ sourceTreeDigest
3943
4171
  };
3944
4172
  }
4173
+ function codeLocalSource(payload) {
4174
+ const source = record22(payload.source);
4175
+ if (!source) return null;
4176
+ if (source.kind !== "local_checkout" || typeof source.repository !== "string" || typeof source.headCommitSha !== "string" || !/^[0-9a-f]{40}$/.test(source.headCommitSha) || typeof source.trustedBaseDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(source.trustedBaseDigest) || typeof source.developerPatchDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(source.developerPatchDigest) || typeof source.snapshotDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(source.snapshotDigest) || typeof source.modified !== "boolean" || !Number.isSafeInteger(source.fileCount) || Number(source.fileCount) < 1 || Number(source.fileCount) > 2e4 || !Number.isSafeInteger(source.byteCount) || Number(source.byteCount) < 1 || Number(source.byteCount) > 512 * 1024 * 1024 || !Number.isSafeInteger(source.capturedAt) || Number(source.capturedAt) < 1) {
4177
+ throw new TypeError("invalid local checkout source descriptor");
4178
+ }
4179
+ return source;
4180
+ }
3945
4181
  function codeCheckpointPayload(payload) {
3946
4182
  const value = payload.checkpoint;
3947
4183
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
@@ -3970,11 +4206,62 @@ function fakeCodeLease(command, metadata2) {
3970
4206
  };
3971
4207
  }
3972
4208
  var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4209
+ var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
4210
+ async function prepareRuntimeLocalSource(input) {
4211
+ const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
4212
+ if (!available || JSON.stringify(available.descriptor) !== JSON.stringify(descriptor2) || descriptor2.repository.toLowerCase() !== repository.toLowerCase() || descriptor2.headCommitSha !== baseCommitSha) {
4213
+ throw new TypeError("the session's local checkout snapshot is not available on this terminal");
4214
+ }
4215
+ const workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4216
+ trustedBaseDir: available.trustedBaseDir,
4217
+ trustedBaseCommitSha: baseCommitSha,
4218
+ checkpoint: codeCheckpointPayload(command.payload)
4219
+ })).workspace : await stageWorkspacePair(available.trustedBaseDir, available.sourceDir, SOURCE_LIMITS);
4220
+ const trustedBaseDigest = await digestStagedWorkspace(workspace.baselineDir, SOURCE_LIMITS);
4221
+ if (trustedBaseDigest !== descriptor2.trustedBaseDigest) {
4222
+ await workspace.cleanup();
4223
+ throw new TypeError("trusted Git base digest changed after connection");
4224
+ }
4225
+ if (!resume && await digestStagedWorkspace(workspace.workspaceDir, SOURCE_LIMITS) !== descriptor2.snapshotDigest) {
4226
+ await workspace.cleanup();
4227
+ throw new TypeError("local checkout snapshot digest changed after connection");
4228
+ }
4229
+ return { workspace, sourceDigest: descriptor2.snapshotDigest, trustedBaseDigest };
4230
+ }
4231
+ function createCodeRuntimeToolBroker(input, lease, role) {
4232
+ const broker = createCodeToolBroker({
4233
+ recipes: input.recipes,
4234
+ recipeExecutor: createContainerRecipeExecutor(input.engine),
4235
+ recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
4236
+ readerId: `code-session:${lease.task.taskId}`
4237
+ });
4238
+ return role === "coding" ? broker : { execute: (context, request) => request.tool === "sandbox.read" ? broker.execute(context, request) : Promise.resolve({ requestId: request.requestId, ok: false, content: "review sessions are read-only" }) };
4239
+ }
4240
+ async function appendCodeRuntimeEvent(control, command, event, refs) {
4241
+ const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
4242
+ refs.push(eventId);
4243
+ const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
4244
+ await control.appendSessionEvent(command.sessionId, eventId, bounded);
4245
+ }
4246
+ var digestRuntimeValue = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
4247
+ var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
4248
+ var runtimeRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4249
+ var safeRuntimeJson = (value) => {
4250
+ try {
4251
+ return JSON.stringify(value).slice(0, 1e4);
4252
+ } catch {
4253
+ return "[event]";
4254
+ }
4255
+ };
4256
+ function runtimeResultText(value) {
4257
+ const record32 = runtimeRecord(value);
4258
+ return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4259
+ }
3973
4260
  var CodePiRuntimeEngine = class {
3974
4261
  constructor(options) {
3975
4262
  this.options = options;
3976
4263
  this.#run = options.runAttempt ?? runContainerAttempt;
3977
- this.#buildPolicyDigest = digest(JSON.stringify(options.recipes));
4264
+ this.#buildPolicyDigest = digestRuntimeValue(JSON.stringify(options.recipes));
3978
4265
  this.#checkpoints = new CodeRuntimeCheckpointManager({
3979
4266
  control: options.control,
3980
4267
  recipes: options.recipes,
@@ -4009,19 +4296,35 @@ var CodePiRuntimeEngine = class {
4009
4296
  }
4010
4297
  async #start(command, resume) {
4011
4298
  if (this.#active.has(command.sessionId)) throw new TypeError("Code session is already active on this runtime");
4012
- const source = await this.options.control.source(command.sessionId);
4013
- const materialized = await materializeCodeRuntimeSource(source);
4299
+ const metadata2 = codeCommandMetadata(command.payload, resume);
4300
+ const requestedLocal = codeLocalSource(command.payload);
4014
4301
  let workspace;
4015
- try {
4016
- workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4017
- trustedBaseDir: materialized.sourceDir,
4018
- trustedBaseCommitSha: source.commitSha,
4019
- checkpoint: codeCheckpointPayload(command.payload)
4020
- })).workspace : await stageWorkspace(materialized.sourceDir);
4021
- } finally {
4022
- await materialized.cleanup();
4302
+ let sourceDigest;
4303
+ let localTrustedBaseDigest;
4304
+ if (requestedLocal) {
4305
+ const prepared = await prepareRuntimeLocalSource({
4306
+ command,
4307
+ descriptor: requestedLocal,
4308
+ available: this.options.localSource,
4309
+ repository: metadata2.repository,
4310
+ baseCommitSha: metadata2.baseCommitSha,
4311
+ resume
4312
+ });
4313
+ ({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
4314
+ } else {
4315
+ const source = await this.options.control.source(command.sessionId);
4316
+ const materialized = await materializeCodeRuntimeSource(source);
4317
+ try {
4318
+ workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4319
+ trustedBaseDir: materialized.sourceDir,
4320
+ trustedBaseCommitSha: source.commitSha,
4321
+ checkpoint: codeCheckpointPayload(command.payload)
4322
+ })).workspace : await stageWorkspace(materialized.sourceDir);
4323
+ } finally {
4324
+ await materialized.cleanup();
4325
+ }
4326
+ sourceDigest = source.treeDigest;
4023
4327
  }
4024
- const metadata2 = codeCommandMetadata(command.payload, resume);
4025
4328
  const abort = new AbortController();
4026
4329
  const conversationRefs = [];
4027
4330
  const active = {
@@ -4031,21 +4334,30 @@ var CodePiRuntimeEngine = class {
4031
4334
  acknowledged: false,
4032
4335
  role: metadata2.role,
4033
4336
  title: metadata2.title,
4034
- baseCommitSha: source.commitSha,
4035
- trustedBaseDigest: await digestStagedWorkspace(workspace.baselineDir, {
4337
+ baseCommitSha: metadata2.baseCommitSha,
4338
+ repository: metadata2.repository,
4339
+ sourceTreeDigest: metadata2.sourceTreeDigest,
4340
+ trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
4036
4341
  maxFiles: 1e4,
4037
4342
  maxBytes: 16 * 1024 * 1024
4038
4343
  }),
4039
- planningInputDigest: metadata2.planningInputDigest ?? digest(
4040
- JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: source.treeDigest })
4344
+ planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
4345
+ JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })
4041
4346
  ),
4042
4347
  done: Promise.resolve(null)
4043
4348
  };
4044
4349
  this.#active.set(command.sessionId, active);
4350
+ if (requestedLocal) {
4351
+ await this.#event(command, {
4352
+ type: "message",
4353
+ actor: "system",
4354
+ body: `Source snapshot: local checkout ${requestedLocal.snapshotDigest} \xB7 ${requestedLocal.modified ? "modified" : "clean"} \xB7 Git ${requestedLocal.headCommitSha}`
4355
+ }, conversationRefs);
4356
+ }
4045
4357
  active.done = this.#runAttempt(command, metadata2, active).catch(async (cause) => {
4046
- await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` }, conversationRefs).catch(() => void 0);
4358
+ await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` }, conversationRefs).catch(() => void 0);
4047
4359
  await this.#event(command, { type: "status", status: "failed" }, conversationRefs).catch(() => void 0);
4048
- await this.#failure(command, active, message2(cause));
4360
+ await this.#failure(command, active, runtimeErrorMessage(cause));
4049
4361
  return null;
4050
4362
  });
4051
4363
  return { status: "running", message: resume ? "Pi resumed from a portable checkpoint" : "Pi started" };
@@ -4065,22 +4377,29 @@ var CodePiRuntimeEngine = class {
4065
4377
  title: active.title,
4066
4378
  prompt,
4067
4379
  planningInputDigest: active.planningInputDigest,
4068
- attestationDigest: "follow-up"
4380
+ attestationDigest: "follow-up",
4381
+ repository: active.repository,
4382
+ baseCommitSha: active.baseCommitSha,
4383
+ sourceTreeDigest: active.sourceTreeDigest
4069
4384
  }, active).catch(async (cause) => {
4070
4385
  await this.#event(
4071
4386
  command,
4072
- { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` },
4387
+ { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` },
4073
4388
  active.conversationRefs
4074
4389
  ).catch(() => void 0);
4075
4390
  await this.#event(command, { type: "status", status: "failed" }, active.conversationRefs).catch(() => void 0);
4076
- await this.#failure(command, active, message2(cause));
4391
+ await this.#failure(command, active, runtimeErrorMessage(cause));
4077
4392
  return null;
4078
4393
  });
4079
4394
  return { status: "running", message: "Pi accepted the owner prompt" };
4080
4395
  }
4081
4396
  async #runAttempt(command, metadata2, active) {
4082
4397
  const lease = fakeCodeLease(command, metadata2);
4083
- const broker = this.#toolBroker(lease, metadata2.role);
4398
+ const broker = createCodeRuntimeToolBroker({
4399
+ recipes: this.options.recipes,
4400
+ engine: this.options.engine,
4401
+ recipeAuthorization: this.options.recipeAuthorization
4402
+ }, lease, metadata2.role);
4084
4403
  const startedAt = Date.now();
4085
4404
  let completionSeen = false;
4086
4405
  const result = await this.#run({
@@ -4140,7 +4459,7 @@ var CodePiRuntimeEngine = class {
4140
4459
  return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
4141
4460
  }
4142
4461
  if (output.type === "event") {
4143
- const payload = object2(output.payload);
4462
+ const payload = runtimeRecord(output.payload);
4144
4463
  if (output.kind === "pi.started") {
4145
4464
  await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
4146
4465
  } else if (output.kind === "pi.thinking" && payload?.available === true && Number.isSafeInteger(payload.durationMs) && Number(payload.durationMs) >= 0) {
@@ -4153,12 +4472,12 @@ var CodePiRuntimeEngine = class {
4153
4472
  await this.#event(command, {
4154
4473
  type: "message",
4155
4474
  actor: "system",
4156
- body: `${output.kind}${output.payload === void 0 ? "" : ` ${json(output.payload)}`}`
4475
+ body: `${output.kind}${output.payload === void 0 ? "" : ` ${safeRuntimeJson(output.payload)}`}`
4157
4476
  }, active.conversationRefs);
4158
4477
  }
4159
4478
  } else if (output.type === "attempt.complete") {
4160
4479
  completionSeen = true;
4161
- const body = resultText(output.result) ?? `Pi ${output.status}.`;
4480
+ const body = runtimeResultText(output.result) ?? `Pi ${output.status}.`;
4162
4481
  await this.#event(command, {
4163
4482
  type: "message",
4164
4483
  actor: output.status === "completed" ? "agent" : "system",
@@ -4185,15 +4504,6 @@ var CodePiRuntimeEngine = class {
4185
4504
  }
4186
4505
  return result;
4187
4506
  }
4188
- #toolBroker(lease, role) {
4189
- const broker = createCodeToolBroker({
4190
- recipes: this.options.recipes,
4191
- recipeExecutor: createContainerRecipeExecutor(this.options.engine),
4192
- recipeAuthorization: this.options.recipeAuthorization ?? "registered_recipe",
4193
- readerId: `code-session:${lease.task.taskId}`
4194
- });
4195
- return role === "coding" ? broker : { execute: (context, request) => request.tool === "sandbox.read" ? broker.execute(context, request) : Promise.resolve({ requestId: request.requestId, ok: false, content: "review sessions are read-only" }) };
4196
- }
4197
4507
  async #checkpoint(command) {
4198
4508
  const active = this.#active.get(command.sessionId);
4199
4509
  if (!active) throw new TypeError("Code session workspace is not active on this runtime");
@@ -4208,26 +4518,9 @@ var CodePiRuntimeEngine = class {
4208
4518
  }
4209
4519
  }
4210
4520
  async #event(command, event, refs) {
4211
- const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
4212
- refs.push(eventId);
4213
- const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
4214
- await this.options.control.appendSessionEvent(command.sessionId, eventId, bounded);
4215
- }
4216
- };
4217
- var object2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4218
- var digest = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
4219
- var message2 = (value) => value instanceof Error ? value.message : String(value);
4220
- var json = (value) => {
4221
- try {
4222
- return JSON.stringify(value).slice(0, 1e4);
4223
- } catch {
4224
- return "[event]";
4521
+ await appendCodeRuntimeEvent(this.options.control, command, event, refs);
4225
4522
  }
4226
4523
  };
4227
- function resultText(value) {
4228
- const record32 = object2(value);
4229
- return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4230
- }
4231
4524
 
4232
4525
  // src/help.ts
4233
4526
  import { readFileSync as readFileSync3 } from "fs";
@@ -4345,24 +4638,90 @@ Safety:
4345
4638
  `);
4346
4639
  }
4347
4640
 
4641
+ // src/code-local-source.ts
4642
+ import { execFile as execFile3 } from "child_process";
4643
+ import { createHash as createHash4 } from "crypto";
4644
+ var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
4645
+ async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
4646
+ const headCommitSha = await readHead(cwd);
4647
+ const staged = await stageWorkspace(cwd, { ...SOURCE_LIMITS2, gitTrackedAndUnignored: true });
4648
+ let trusted;
4649
+ try {
4650
+ trusted = await materializeGitTree(cwd, headCommitSha, SOURCE_LIMITS2);
4651
+ const comparison = await stageWorkspacePair(trusted.sourceDir, staged.baselineDir, SOURCE_LIMITS2);
4652
+ let developerPatch;
4653
+ try {
4654
+ developerPatch = await comparison.patch(4 * 1024 * 1024);
4655
+ } finally {
4656
+ await comparison.cleanup();
4657
+ }
4658
+ const descriptor2 = {
4659
+ kind: "local_checkout",
4660
+ repository,
4661
+ headCommitSha,
4662
+ trustedBaseDigest: await digestStagedWorkspace(trusted.sourceDir, SOURCE_LIMITS2),
4663
+ developerPatchDigest: digestText(developerPatch),
4664
+ snapshotDigest: await digestStagedWorkspace(staged.baselineDir, SOURCE_LIMITS2),
4665
+ modified: developerPatch.length > 0,
4666
+ fileCount: staged.fileCount,
4667
+ byteCount: staged.byteCount,
4668
+ capturedAt: Date.now()
4669
+ };
4670
+ return {
4671
+ descriptor: descriptor2,
4672
+ trustedBaseDir: trusted.sourceDir,
4673
+ sourceDir: staged.baselineDir,
4674
+ cleanup: async () => {
4675
+ await Promise.all([staged.cleanup(), trusted.cleanup()]);
4676
+ }
4677
+ };
4678
+ } catch (error) {
4679
+ await Promise.all([staged.cleanup(), trusted?.cleanup()]);
4680
+ throw error;
4681
+ }
4682
+ }
4683
+ async function readGitHead(cwd) {
4684
+ const value = await new Promise((accept, reject) => {
4685
+ execFile3("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
4686
+ if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
4687
+ else accept(stdout.trim());
4688
+ });
4689
+ });
4690
+ if (!/^[0-9a-f]{40}$/.test(value)) throw new Error("code connect could not resolve the checkout HEAD commit");
4691
+ return value;
4692
+ }
4693
+ function digestText(value) {
4694
+ return `sha256:${createHash4("sha256").update(value).digest("hex")}`;
4695
+ }
4696
+
4697
+ // src/code-images.ts
4698
+ import { execFile as execFile4 } from "child_process";
4699
+ async function prepareCodeImages(engine, images) {
4700
+ for (const image of images) {
4701
+ await new Promise((accept, reject) => {
4702
+ const args = engine === "container" ? ["image", "pull", image] : ["pull", image];
4703
+ execFile4(engine, args, { encoding: "utf8", maxBuffer: 2 * 1024 * 1024, timeout: 10 * 6e4 }, (error) => {
4704
+ if (error) reject(new Error(`could not prepare pinned Code image ${image}`));
4705
+ else accept();
4706
+ });
4707
+ });
4708
+ }
4709
+ }
4710
+
4348
4711
  // src/code-connect.ts
4349
- var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:713db58428bc05cb486167c5747de8fd826669c9b283ff78e0139ac194c3d058";
4350
- var CODE_BUILD_RECIPES = Object.freeze([{
4351
- id: "node-test",
4352
- image: "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd",
4353
- command: ["node", "--test"],
4354
- timeoutMs: 12e4,
4355
- maxOutputBytes: 1024 * 1024,
4356
- cpus: 1,
4357
- memory: "512m",
4358
- pids: 128
4359
- }]);
4360
4712
  async function codeConnect(options) {
4361
4713
  const cwd = options.cwd ?? process.cwd();
4362
4714
  const configPath = resolve5(cwd, options.configPath);
4363
4715
  const cfg = existsSync4(configPath) ? await loadProjectConfig(configPath) : null;
4364
- const repository = cfg ? null : await inferGitHubRepository(cwd, options.readGitOrigin);
4365
- const platform = cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai";
4716
+ const requestedAppId = options.appId?.trim();
4717
+ if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
4718
+ throw new Error("--app-id must be a valid odla app id");
4719
+ }
4720
+ if (requestedAppId && cfg && requestedAppId !== cfg.app.id) {
4721
+ throw new Error(`--app-id ${requestedAppId} does not match ${options.configPath} (${cfg.app.id})`);
4722
+ }
4723
+ const appId = requestedAppId ?? cfg?.app.id;
4724
+ const platform = (options.platform ?? cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai").replace(/\/+$/, "");
4366
4725
  const appEnv = options.env ?? (cfg ? cfg.envs.includes("dev") ? "dev" : cfg.envs[0] : "dev");
4367
4726
  if (appEnv !== "dev" && appEnv !== "prod" || cfg && !cfg.envs.includes(appEnv)) {
4368
4727
  throw new Error(cfg ? `Code env "${appEnv ?? ""}" must be dev or prod and declared in ${options.configPath}` : `Code env "${appEnv ?? ""}" must be dev or prod`);
@@ -4381,53 +4740,77 @@ async function codeConnect(options) {
4381
4740
  const out = options.stdout ?? console;
4382
4741
  const doFetch = options.fetch ?? fetch;
4383
4742
  const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
4743
+ await (options.prepareImages ?? prepareCodeImages)(engine, [
4744
+ CODE_PI_IMAGE,
4745
+ ...new Set(CODE_BUILD_RECIPES.map((recipe2) => recipe2.image))
4746
+ ]);
4384
4747
  const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
4385
4748
  const hostName = (options.name ?? hostname()).trim();
4386
4749
  if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
4387
- const approval = await (options.getToken ?? getScopedPlatformToken)({
4388
- platform,
4389
- scope: "platform:code:host:connect",
4390
- email: options.email,
4391
- open: options.open,
4392
- fetch: doFetch,
4393
- stdout: out,
4394
- openApprovalUrl: options.openApprovalUrl,
4395
- cache: false,
4396
- label: `odla CLI Code host for ${cfg?.app.id ?? repository}/${appEnv}`
4397
- });
4398
- const target = cfg ? { appId: cfg.app.id } : { repository };
4399
- const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
4400
- method: "POST",
4401
- headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
4402
- body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
4403
- redirect: "error",
4404
- signal: options.signal
4405
- });
4406
- const raw = await response2.json().catch(() => null);
4407
- if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
4408
- const connection = parseConnection(raw, cfg?.app.id, appEnv);
4409
- const capabilities = {
4410
- protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
4411
- platform: hostPlatform,
4412
- arch: process.arch,
4413
- engines: [engine],
4414
- cpuCount: cpus().length,
4415
- memoryBytes: totalmem()
4416
- };
4417
- out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
4418
- out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
4419
- out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
4420
- await (options.runRuntime ?? runCodeRuntime)({
4421
- endpoint: platform,
4422
- token: connection.token,
4423
- engine,
4424
- capabilities,
4425
- heartbeatMs,
4426
- once: options.once === true,
4427
- signal: options.signal,
4428
- stdout: out,
4429
- fetch: doFetch
4430
- });
4750
+ const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
4751
+ const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
4752
+ cwd,
4753
+ repository,
4754
+ options.readGitHead
4755
+ );
4756
+ try {
4757
+ const descriptor2 = localSource.descriptor;
4758
+ const approval = await (options.getToken ?? getScopedPlatformToken)({
4759
+ platform,
4760
+ scope: "app:code:host:connect",
4761
+ email: options.email,
4762
+ open: options.open,
4763
+ fetch: doFetch,
4764
+ stdout: out,
4765
+ openApprovalUrl: options.openApprovalUrl,
4766
+ cache: false,
4767
+ label: `Terminal connection for ${appId ?? repository}/${appEnv}`
4768
+ });
4769
+ const target = appId ? { appId } : { repository };
4770
+ const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
4771
+ method: "POST",
4772
+ headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
4773
+ body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
4774
+ redirect: "error",
4775
+ signal: options.signal
4776
+ });
4777
+ const raw = await response2.json().catch(() => null);
4778
+ if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
4779
+ const connection = parseConnection(raw, appId, appEnv);
4780
+ const capabilities = {
4781
+ protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
4782
+ platform: hostPlatform,
4783
+ arch: process.arch,
4784
+ engines: [engine],
4785
+ cpuCount: cpus().length,
4786
+ memoryBytes: totalmem(),
4787
+ source: descriptor2,
4788
+ images: {
4789
+ ready: true,
4790
+ pi: CODE_PI_IMAGE,
4791
+ recipes: CODE_BUILD_RECIPES.map((recipe2) => ({ id: recipe2.id, image: recipe2.image }))
4792
+ }
4793
+ };
4794
+ out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
4795
+ out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
4796
+ out.log(`source: local checkout \xB7 ${descriptor2.headCommitSha.slice(0, 10)} \xB7 ${descriptor2.fileCount} files \xB7 staged copy`);
4797
+ out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
4798
+ out.log(`Studio: ${platform}/studio/apps/${encodeURIComponent(connection.binding.appId)}/${connection.binding.env}/code/sessions`);
4799
+ await (options.runRuntime ?? runCodeRuntime)({
4800
+ endpoint: platform,
4801
+ token: connection.token,
4802
+ engine,
4803
+ capabilities,
4804
+ localSource,
4805
+ heartbeatMs,
4806
+ once: options.once === true,
4807
+ signal: options.signal,
4808
+ stdout: out,
4809
+ fetch: doFetch
4810
+ });
4811
+ } finally {
4812
+ await localSource.cleanup();
4813
+ }
4431
4814
  }
4432
4815
  async function runCodeRuntime(input) {
4433
4816
  const controller = new AbortController();
@@ -4450,7 +4833,8 @@ async function runCodeRuntime(input) {
4450
4833
  engine: input.engine,
4451
4834
  image: CODE_PI_IMAGE,
4452
4835
  recipes: CODE_BUILD_RECIPES,
4453
- recipeAuthorization: "registered_recipe"
4836
+ recipeAuthorization: "registered_recipe",
4837
+ localSource: input.localSource
4454
4838
  });
4455
4839
  const reconciler = new CodeRuntimeReconciler(control, commandEngine);
4456
4840
  try {
@@ -4491,8 +4875,8 @@ function parseConnection(value, appId, appEnv) {
4491
4875
  return root;
4492
4876
  }
4493
4877
  function apiFailure(action, status, value) {
4494
- const message3 = record4(record4(value)?.error)?.message;
4495
- return `${action} failed (${status})${typeof message3 === "string" ? `: ${message3}` : ""}`;
4878
+ const message2 = record4(record4(value)?.error)?.message;
4879
+ return `${action} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
4496
4880
  }
4497
4881
  function record4(value) {
4498
4882
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -4501,14 +4885,14 @@ function record4(value) {
4501
4885
  // src/doctor-checks.ts
4502
4886
  import { execFileSync } from "child_process";
4503
4887
  import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
4504
- import { join as join5, resolve as resolve6 } from "path";
4888
+ import { join as join6, resolve as resolve6 } from "path";
4505
4889
 
4506
4890
  // src/wrangler.ts
4507
- import { spawn as spawn4 } from "child_process";
4891
+ import { spawn as spawn5 } from "child_process";
4508
4892
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
4509
- import { join as join4 } from "path";
4893
+ import { join as join5 } from "path";
4510
4894
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
4511
- const child = spawn4(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4895
+ const child = spawn5(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4512
4896
  let stdout = "";
4513
4897
  let stderr = "";
4514
4898
  child.stdout.on("data", (chunk) => stdout += chunk.toString());
@@ -4520,7 +4904,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
4520
4904
  var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
4521
4905
  function findWranglerConfig(rootDir) {
4522
4906
  for (const name of WRANGLER_CONFIG_FILES) {
4523
- const path = join4(rootDir, name);
4907
+ const path = join5(rootDir, name);
4524
4908
  if (existsSync5(path)) return path;
4525
4909
  }
4526
4910
  return null;
@@ -4629,7 +5013,7 @@ function wranglerWarnings(rootDir) {
4629
5013
  const dir = resolve6(rootDir, assets.directory);
4630
5014
  if (dir === resolve6(rootDir)) {
4631
5015
  warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
4632
- } else if (existsSync6(join5(dir, "node_modules"))) {
5016
+ } else if (existsSync6(join6(dir, "node_modules"))) {
4633
5017
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
4634
5018
  }
4635
5019
  }
@@ -4694,7 +5078,7 @@ function calendarProjectWarnings(rootDir) {
4694
5078
  }
4695
5079
  function readPackageJson(rootDir) {
4696
5080
  try {
4697
- return JSON.parse(readFileSync5(join5(rootDir, "package.json"), "utf8"));
5081
+ return JSON.parse(readFileSync5(join6(rootDir, "package.json"), "utf8"));
4698
5082
  } catch {
4699
5083
  return null;
4700
5084
  }
@@ -5261,8 +5645,8 @@ async function readRegistryApp(cfg, token, doFetch) {
5261
5645
  });
5262
5646
  if (res.status === 404) return null;
5263
5647
  if (!res.ok) return null;
5264
- const json2 = await res.json();
5265
- return json2.app ?? null;
5648
+ const json = await res.json();
5649
+ return json.app ?? null;
5266
5650
  }
5267
5651
  async function postJson2(doFetch, url, bearer, body) {
5268
5652
  const res = await doFetch(url, {
@@ -5428,10 +5812,10 @@ async function provision(options) {
5428
5812
  stdout: out
5429
5813
  });
5430
5814
  } catch (error) {
5431
- const message3 = error instanceof Error ? error.message : String(error);
5815
+ const message2 = error instanceof Error ? error.message : String(error);
5432
5816
  const consent = env === "prod" || env === "production" ? " --yes" : "";
5433
5817
  throw new Error(
5434
- `${message3}
5818
+ `${message2}
5435
5819
  ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}${consent}" without issuing or rotating again`,
5436
5820
  { cause: error }
5437
5821
  );
@@ -5547,7 +5931,7 @@ async function resolveVaultWrite(options) {
5547
5931
  }
5548
5932
 
5549
5933
  // src/security.ts
5550
- import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve8, sep as sep3 } from "path";
5934
+ import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve8, sep as sep4 } from "path";
5551
5935
  import {
5552
5936
  cloudflareAppProfile,
5553
5937
  createPlatformSecurityReasoners,
@@ -5568,7 +5952,7 @@ async function runHostedSecurity(options) {
5568
5952
  const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
5569
5953
  const target = resolve8(options.target ?? cfg?.rootDir ?? ".");
5570
5954
  const output = resolve8(options.out ?? resolve8(target, ".odla/security/hosted"));
5571
- const outputRelative = relative4(target, output).split(sep3).join("/");
5955
+ const outputRelative = relative4(target, output).split(sep4).join("/");
5572
5956
  if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
5573
5957
  const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
5574
5958
  const tokenRequest = {
@@ -5580,7 +5964,7 @@ async function runHostedSecurity(options) {
5580
5964
  };
5581
5965
  const token = await injectedToken(options, tokenRequest);
5582
5966
  const snapshot = await snapshotDirectory(target, {
5583
- exclude: !outputRelative.startsWith("../") && !isAbsolute3(outputRelative) ? [outputRelative] : []
5967
+ exclude: !outputRelative.startsWith("../") && !isAbsolute4(outputRelative) ? [outputRelative] : []
5584
5968
  });
5585
5969
  const hosted = await createPlatformSecurityReasoners({
5586
5970
  platform,
@@ -5741,8 +6125,8 @@ async function startHostedSecurityJob(options) {
5741
6125
  "start hosted security job"
5742
6126
  );
5743
6127
  } catch (error) {
5744
- const message3 = error instanceof Error ? error.message : String(error);
5745
- if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message3)) {
6128
+ const message2 = error instanceof Error ? error.message : String(error);
6129
+ if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message2)) {
5746
6130
  throw new Error("Hosted security plan or execution intent changed or became unavailable after review; fetch a fresh intent and explicitly acknowledge its digest before enqueueing again", { cause: error });
5747
6131
  }
5748
6132
  throw error;
@@ -5823,7 +6207,7 @@ function securityExecutionDigest(value) {
5823
6207
  // src/skill.ts
5824
6208
  import { existsSync as existsSync8, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync, writeFileSync as writeFileSync3 } from "fs";
5825
6209
  import { homedir } from "os";
5826
- import { dirname as dirname6, isAbsolute as isAbsolute4, join as join6, relative as relative5, resolve as resolve9, sep as sep4 } from "path";
6210
+ import { dirname as dirname6, isAbsolute as isAbsolute5, join as join7, relative as relative5, resolve as resolve9, sep as sep5 } from "path";
5827
6211
  import { fileURLToPath } from "url";
5828
6212
 
5829
6213
  // src/skill-adapters.ts
@@ -5899,12 +6283,12 @@ function installSkill(options = {}) {
5899
6283
  plans.set(target, { target, content, boundary, managedMerge });
5900
6284
  };
5901
6285
  const planSkillTree = (targetDir2, boundary = root) => {
5902
- for (const rel of files) plan(join6(targetDir2, rel), readFileSync6(join6(sourceDir, rel), "utf8"), false, boundary);
6286
+ for (const rel of files) plan(join7(targetDir2, rel), readFileSync6(join7(sourceDir, rel), "utf8"), false, boundary);
5903
6287
  };
5904
6288
  let targetDir;
5905
6289
  if (options.global) {
5906
- const claudeRoot = join6(home, ".claude", "skills");
5907
- const codexRoot = resolve9(options.codexHomeDir ?? process.env.CODEX_HOME ?? join6(home, ".codex"), "skills");
6290
+ const claudeRoot = join7(home, ".claude", "skills");
6291
+ const codexRoot = resolve9(options.codexHomeDir ?? process.env.CODEX_HOME ?? join7(home, ".codex"), "skills");
5908
6292
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
5909
6293
  for (const harness of harnesses) {
5910
6294
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
@@ -5912,35 +6296,35 @@ function installSkill(options = {}) {
5912
6296
  rememberTarget(harness, skillRoot);
5913
6297
  }
5914
6298
  } else {
5915
- const sharedRoot = join6(root, ".agents", "skills");
6299
+ const sharedRoot = join7(root, ".agents", "skills");
5916
6300
  planSkillTree(sharedRoot);
5917
- const claudeRoot = join6(root, ".claude", "skills");
6301
+ const claudeRoot = join7(root, ".claude", "skills");
5918
6302
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
5919
6303
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
5920
6304
  if (harnesses.includes("claude")) {
5921
6305
  for (const skill of skillNames(files)) {
5922
- const canonical = readFileSync6(join6(sourceDir, skill, "SKILL.md"), "utf8");
5923
- plan(join6(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
6306
+ const canonical = readFileSync6(join7(sourceDir, skill, "SKILL.md"), "utf8");
6307
+ plan(join7(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
5924
6308
  }
5925
6309
  rememberTarget("claude", claudeRoot);
5926
6310
  }
5927
6311
  if (harnesses.includes("cursor")) {
5928
- const cursorRule = join6(root, ".cursor", "rules", "odla.mdc");
6312
+ const cursorRule = join7(root, ".cursor", "rules", "odla.mdc");
5929
6313
  plan(cursorRule, CURSOR_RULE);
5930
6314
  rememberTarget("cursor", cursorRule);
5931
6315
  }
5932
6316
  if (harnesses.includes("agents")) {
5933
- const agentsFile = join6(root, "AGENTS.md");
6317
+ const agentsFile = join7(root, "AGENTS.md");
5934
6318
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5935
6319
  rememberTarget("agents", agentsFile);
5936
6320
  }
5937
6321
  if (harnesses.includes("copilot")) {
5938
- const copilotFile = join6(root, ".github", "copilot-instructions.md");
6322
+ const copilotFile = join7(root, ".github", "copilot-instructions.md");
5939
6323
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5940
6324
  rememberTarget("copilot", copilotFile);
5941
6325
  }
5942
6326
  if (harnesses.includes("gemini")) {
5943
- const geminiFile = join6(root, "GEMINI.md");
6327
+ const geminiFile = join7(root, "GEMINI.md");
5944
6328
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5945
6329
  rememberTarget("gemini", geminiFile);
5946
6330
  }
@@ -5996,7 +6380,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
5996
6380
  };
5997
6381
  }
5998
6382
  function pathsUnder(root, paths) {
5999
- return [...paths].map((path) => relative5(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute4(path)).sort();
6383
+ return [...paths].map((path) => relative5(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep5}`) && !isAbsolute5(path)).sort();
6000
6384
  }
6001
6385
  function normalizeHarnesses(values, global) {
6002
6386
  const requested = values?.length ? values : ["claude"];
@@ -6042,12 +6426,12 @@ function managedFileContent(path, block, force, boundary) {
6042
6426
  }
6043
6427
  function symlinkedComponent(boundary, target) {
6044
6428
  const rel = relative5(boundary, target);
6045
- if (rel === ".." || rel.startsWith(`..${sep4}`) || isAbsolute4(rel)) {
6429
+ if (rel === ".." || rel.startsWith(`..${sep5}`) || isAbsolute5(rel)) {
6046
6430
  throw new Error(`agent setup target escapes its install root: ${target}`);
6047
6431
  }
6048
6432
  let current = boundary;
6049
- for (const part of rel.split(sep4).filter(Boolean)) {
6050
- current = join6(current, part);
6433
+ for (const part of rel.split(sep5).filter(Boolean)) {
6434
+ current = join7(current, part);
6051
6435
  try {
6052
6436
  if (lstatSync(current).isSymbolicLink()) return current;
6053
6437
  } catch (error) {
@@ -6064,7 +6448,7 @@ function listFiles(dir) {
6064
6448
  const results = [];
6065
6449
  const walk = (current) => {
6066
6450
  for (const entry of readdirSync(current, { withFileTypes: true })) {
6067
- const path = join6(current, entry.name);
6451
+ const path = join7(current, entry.name);
6068
6452
  if (entry.isDirectory()) walk(path);
6069
6453
  else results.push(relative5(dir, path));
6070
6454
  }
@@ -6555,6 +6939,8 @@ async function codeCommand(parsed, dependencies) {
6555
6939
  assertArgs(parsed, [
6556
6940
  "config",
6557
6941
  "env",
6942
+ "platform",
6943
+ "app-id",
6558
6944
  "email",
6559
6945
  "open",
6560
6946
  "name",
@@ -6570,6 +6956,8 @@ async function codeCommand(parsed, dependencies) {
6570
6956
  await (dependencies.codeConnect ?? codeConnect)({
6571
6957
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
6572
6958
  env: stringOpt(parsed.options.env),
6959
+ platform: stringOpt(parsed.options.platform),
6960
+ appId: stringOpt(parsed.options["app-id"]),
6573
6961
  email: stringOpt(parsed.options.email),
6574
6962
  open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
6575
6963
  name: stringOpt(parsed.options.name),
@@ -6627,12 +7015,12 @@ async function hostedSecurityContext(parsed, dependencies) {
6627
7015
  );
6628
7016
  return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
6629
7017
  }
6630
- async function interactiveConfirmation(message3, dependencies) {
6631
- if (dependencies.confirm) return dependencies.confirm(message3);
7018
+ async function interactiveConfirmation(message2, dependencies) {
7019
+ if (dependencies.confirm) return dependencies.confirm(message2);
6632
7020
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
6633
7021
  const prompt = createInterface({ input: process.stdin, output: process.stdout });
6634
7022
  try {
6635
- const answer = await prompt.question(`${message3} [y/N] `);
7023
+ const answer = await prompt.question(`${message2} [y/N] `);
6636
7024
  return /^y(?:es)?$/i.test(answer.trim());
6637
7025
  } finally {
6638
7026
  prompt.close();
@@ -7286,4 +7674,4 @@ export {
7286
7674
  exitCodeFor,
7287
7675
  runCli
7288
7676
  };
7289
- //# sourceMappingURL=chunk-AN6KZMR5.js.map
7677
+ //# sourceMappingURL=chunk-E3CCYDIT.js.map