@odla-ai/cli 0.14.1 → 0.16.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-4EM6NMET.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-4EM6NMET.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-4EM6NMET.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
  }
@@ -3031,10 +3276,28 @@ async function parseSource(value) {
3031
3276
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
3032
3277
  return { path: file.path, content: file.content };
3033
3278
  });
3279
+ const referencesValue = snapshot.references === void 0 ? [] : snapshot.references;
3280
+ if (!Array.isArray(referencesValue) || referencesValue.length > 5) throw invalid("reference sources");
3281
+ const aliases = /* @__PURE__ */ new Set();
3282
+ const references = [];
3283
+ for (const item of referencesValue) {
3284
+ const reference = record3(item);
3285
+ if (!reference || typeof reference.alias !== "string" || !/^[a-z][a-z0-9-]{0,39}$/.test(reference.alias) || aliases.has(reference.alias) || reference.alias === "primary" || typeof reference.repository !== "string" || typeof reference.commitSha !== "string" || typeof reference.treeDigest !== "string" || !Array.isArray(reference.files)) throw invalid("reference source");
3286
+ aliases.add(reference.alias);
3287
+ const referenceFiles = reference.files.map((entry) => {
3288
+ const file = record3(entry);
3289
+ if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
3290
+ return { path: file.path, content: file.content };
3291
+ });
3292
+ const source2 = { repository: reference.repository, commitSha: reference.commitSha, files: referenceFiles };
3293
+ const referenceDigest = await digestCodeRepositorySnapshot(source2, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3294
+ if (referenceDigest !== reference.treeDigest) throw invalid("reference source digest");
3295
+ references.push({ alias: reference.alias, ...source2, treeDigest: referenceDigest });
3296
+ }
3034
3297
  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 };
3298
+ const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3299
+ if (digest !== snapshot.treeDigest) throw invalid("source digest");
3300
+ return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
3038
3301
  }
3039
3302
  function parseReview(value) {
3040
3303
  const review = record3(record3(value)?.review);
@@ -3093,9 +3356,9 @@ function validateRelativePath(path) {
3093
3356
  }
3094
3357
  function resolveCodePath(workspaceDir, path) {
3095
3358
  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");
3359
+ const root = resolve23(workspaceDir);
3360
+ const target = resolve23(root, path);
3361
+ if (target !== root && !target.startsWith(`${root}${sep3}`)) throw new TypeError("path escapes the staged workspace");
3099
3362
  return target;
3100
3363
  }
3101
3364
  async function applyCodePatch(workspaceDir, patch2, paths) {
@@ -3103,7 +3366,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
3103
3366
  await gitApply(workspaceDir, patch2, false);
3104
3367
  for (const path of paths) {
3105
3368
  try {
3106
- const info = await lstat(resolveCodePath(workspaceDir, path));
3369
+ const info = await lstat2(resolveCodePath(workspaceDir, path));
3107
3370
  if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
3108
3371
  throw new TypeError("patch created a non-regular workspace entry");
3109
3372
  }
@@ -3115,7 +3378,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
3115
3378
  function gitApply(cwd, patch2, check) {
3116
3379
  return new Promise((accept, reject) => {
3117
3380
  const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
3118
- const child = spawn3("git", args, {
3381
+ const child = spawn4("git", args, {
3119
3382
  cwd,
3120
3383
  shell: false,
3121
3384
  stdio: ["pipe", "ignore", "pipe"],
@@ -3285,32 +3548,6 @@ function execute(engine, args, name, recipe2, signal) {
3285
3548
  });
3286
3549
  });
3287
3550
  }
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
3551
  var SHA3 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
3315
3552
  var DIGEST3 = /^sha256:[0-9a-f]{64}$/;
3316
3553
  var ID3 = /^[A-Za-z0-9._:-]{1,160}$/;
@@ -3419,8 +3656,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
3419
3656
  const receipts = [];
3420
3657
  for (const artifact of recipe2.expectedArtifacts ?? []) {
3421
3658
  try {
3422
- const path = join3(workspaceDir, artifact.path);
3423
- const info = await lstat2(path);
3659
+ const path = join4(workspaceDir, artifact.path);
3660
+ const info = await lstat22(path);
3424
3661
  if (!info.isFile() || info.isSymbolicLink()) {
3425
3662
  receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
3426
3663
  } else if (info.size > artifact.maximumBytes) {
@@ -3516,8 +3753,8 @@ async function prepareRuntimeCheckpoint(input) {
3516
3753
  policy: {
3517
3754
  policyId: "code.runtime",
3518
3755
  recipes: input.recipes,
3519
- maximumFiles: 1e4,
3520
- maximumBytes: 16 * 1024 * 1024
3756
+ maximumFiles: 2e4,
3757
+ maximumBytes: 512 * 1024 * 1024
3521
3758
  },
3522
3759
  recipeExecutor: input.recipeExecutor
3523
3760
  });
@@ -3591,7 +3828,7 @@ var CodeRuntimeCheckpointManager = class {
3591
3828
  await this.options.event(command, {
3592
3829
  type: "message",
3593
3830
  actor: "system",
3594
- body: `Candidate ${candidate.candidateId} is ready for exact owner publication approval`
3831
+ body: command.payload.sourceSet ? `Candidate ${candidate.candidateId} was verified and delivered to the session PR branch` : `Candidate ${candidate.candidateId} is ready for legacy owner publication approval`
3595
3832
  }, pending.refs);
3596
3833
  this.#pending.delete(command.commandId);
3597
3834
  return true;
@@ -3599,11 +3836,11 @@ var CodeRuntimeCheckpointManager = class {
3599
3836
  };
3600
3837
  var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
3601
3838
  var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
3602
- async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir2()) {
3839
+ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir3()) {
3603
3840
  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-"));
3841
+ const root = await mkdtemp3(join23(tempRoot, "odla-code-source-"));
3605
3842
  const sourceDir = join23(root, "source");
3606
- await mkdir2(sourceDir);
3843
+ await mkdir3(sourceDir);
3607
3844
  const seen = /* @__PURE__ */ new Set();
3608
3845
  let bytes = 0;
3609
3846
  try {
@@ -3614,16 +3851,55 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir2()) {
3614
3851
  bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
3615
3852
  if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
3616
3853
  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 });
3854
+ if (!target.startsWith(`${resolve32(sourceDir)}${sep22}`)) throw new TypeError("Code source path escapes its root");
3855
+ await mkdir3(dirname4(target), { recursive: true });
3856
+ await writeFile2(target, file.content, { flag: "wx", mode: 420 });
3857
+ }
3858
+ for (const reference of snapshot.references ?? []) {
3859
+ validateAlias(reference.alias);
3860
+ if (!reference.files.length || reference.files.length > 1e4) throw new TypeError("Code reference file count is invalid");
3861
+ for (const file of reference.files) {
3862
+ validatePath(file.path);
3863
+ const path = `.odla-references/${reference.alias}/${file.path}`;
3864
+ if (seen.has(path)) throw new TypeError("Code reference repeats a path");
3865
+ seen.add(path);
3866
+ bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
3867
+ if (bytes > 80 * 1024 * 1024) throw new TypeError("Code source set exceeds its byte bound");
3868
+ const target = resolve32(sourceDir, path);
3869
+ if (!target.startsWith(`${resolve32(sourceDir)}${sep22}`)) throw new TypeError("Code reference path escapes its root");
3870
+ await mkdir3(dirname4(target), { recursive: true });
3871
+ await writeFile2(target, file.content, { flag: "wx", mode: 292 });
3872
+ }
3620
3873
  }
3621
- return { sourceDir, cleanup: () => rm2(root, { recursive: true, force: true }) };
3874
+ return { sourceDir, cleanup: () => rm3(root, { recursive: true, force: true }) };
3622
3875
  } catch (cause) {
3623
- await rm2(root, { recursive: true, force: true });
3876
+ await rm3(root, { recursive: true, force: true });
3624
3877
  throw cause;
3625
3878
  }
3626
3879
  }
3880
+ function validateAlias(alias) {
3881
+ if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias) || alias === "primary") {
3882
+ throw new TypeError("Code reference alias is invalid");
3883
+ }
3884
+ }
3885
+ async function attachCodeRuntimeReferences(workspace, references) {
3886
+ let bytes = 0;
3887
+ for (const reference of references) {
3888
+ validateAlias(reference.alias);
3889
+ for (const file of reference.files) {
3890
+ validatePath(file.path);
3891
+ const path = `.odla-references/${reference.alias}/${file.path}`;
3892
+ bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
3893
+ if (bytes > 64 * 1024 * 1024) throw new TypeError("Code reference set exceeds its byte bound");
3894
+ for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
3895
+ const target = resolve32(root, path);
3896
+ if (!target.startsWith(`${resolve32(root)}${sep22}`)) throw new TypeError("Code reference path escapes its root");
3897
+ await mkdir3(dirname4(target), { recursive: true });
3898
+ await writeFile2(target, file.content, { flag: "wx", mode: 292 });
3899
+ }
3900
+ }
3901
+ }
3902
+ }
3627
3903
  function validatePath(path) {
3628
3904
  const parts = path.split("/");
3629
3905
  if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." || RESERVED2.has(part) || SECRET2.test(part))) {
@@ -3726,15 +4002,15 @@ async function environment(input, options, tool) {
3726
4002
  { id: "authority", value: `lease:${input.lease.leaseId}`, readers: input.readers },
3727
4003
  { id: "reader", value: options.readerId, readers: input.readers }
3728
4004
  ]);
3729
- const digest2 = await destinationRegistryDigest([input.workspaceId]);
4005
+ const digest = await destinationRegistryDigest([input.workspaceId]);
3730
4006
  const approvals = ["irreversible_mutation", "external_send", "financial", "secret_read"];
3731
4007
  if (options.recipeAuthorization === "exact_approval") approvals.push("code_execution");
3732
4008
  const policy = createEffectPolicy({
3733
- destinationRegistries: { [DESTINATIONS]: { digest: digest2, values: [input.workspaceId] } },
4009
+ destinationRegistries: { [DESTINATIONS]: { digest, values: [input.workspaceId] } },
3734
4010
  approvalEffects: approvals
3735
4011
  });
3736
4012
  const fixedArgs = {
3737
- workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest2 },
4013
+ workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest },
3738
4014
  authority: { role: "authority", value: ingress.control("authority") }
3739
4015
  };
3740
4016
  return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
@@ -3822,6 +4098,9 @@ async function patch(context, request, options, policy) {
3822
4098
  exactKeys(request.input, ["patch"]);
3823
4099
  const value = stringField(request.input, "patch");
3824
4100
  const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
4101
+ if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
4102
+ throw new TypeError("patch targets a read-only reference source");
4103
+ }
3825
4104
  const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
3826
4105
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
3827
4106
  await applyCodePatch(context.workspaceDir, value, paths);
@@ -3907,6 +4186,9 @@ function validateOptions(options) {
3907
4186
  throw new TypeError("Code tool broker requires a reader and unique registered recipes");
3908
4187
  }
3909
4188
  for (const recipe2 of options.recipes) assertCodeBuildRecipe(recipe2);
4189
+ if (options.readOnlyPrefixes?.some((prefix) => !/^[A-Za-z0-9_.-]+$/.test(prefix) || prefix === "." || prefix === "..")) {
4190
+ throw new TypeError("Code tool broker read-only prefix is invalid");
4191
+ }
3910
4192
  }
3911
4193
  function exactKeys(input, allowed) {
3912
4194
  if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
@@ -3934,14 +4216,31 @@ function codeCommandMetadata(payload, resume) {
3934
4216
  }
3935
4217
  const planning = trusted?.planningInputDigest;
3936
4218
  const attestation = trusted?.attestationDigest;
4219
+ const repository = trusted?.repository;
4220
+ const baseCommitSha = trusted?.commitSha;
4221
+ const sourceTreeDigest = trusted?.treeDigest;
4222
+ 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)) {
4223
+ throw new TypeError(`invalid Code ${resume ? "resume" : "start"} trusted base`);
4224
+ }
3937
4225
  return {
3938
4226
  role,
3939
4227
  title,
3940
4228
  prompt,
3941
4229
  planningInputDigest: typeof planning === "string" && /^sha256:[0-9a-f]{64}$/.test(planning) ? planning : null,
3942
- attestationDigest: typeof attestation === "string" ? attestation : "resume"
4230
+ attestationDigest: typeof attestation === "string" ? attestation : "resume",
4231
+ repository,
4232
+ baseCommitSha,
4233
+ sourceTreeDigest
3943
4234
  };
3944
4235
  }
4236
+ function codeLocalSource(payload) {
4237
+ const source = record22(payload.source);
4238
+ if (!source) return null;
4239
+ 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) {
4240
+ throw new TypeError("invalid local checkout source descriptor");
4241
+ }
4242
+ return source;
4243
+ }
3945
4244
  function codeCheckpointPayload(payload) {
3946
4245
  const value = payload.checkpoint;
3947
4246
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
@@ -3970,11 +4269,63 @@ function fakeCodeLease(command, metadata2) {
3970
4269
  };
3971
4270
  }
3972
4271
  var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4272
+ var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
4273
+ async function prepareRuntimeLocalSource(input) {
4274
+ const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
4275
+ if (!available || JSON.stringify(available.descriptor) !== JSON.stringify(descriptor2) || descriptor2.repository.toLowerCase() !== repository.toLowerCase() || descriptor2.headCommitSha !== baseCommitSha) {
4276
+ throw new TypeError("the session's local checkout snapshot is not available on this terminal");
4277
+ }
4278
+ const workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4279
+ trustedBaseDir: available.trustedBaseDir,
4280
+ trustedBaseCommitSha: baseCommitSha,
4281
+ checkpoint: codeCheckpointPayload(command.payload)
4282
+ })).workspace : await stageWorkspacePair(available.trustedBaseDir, available.sourceDir, SOURCE_LIMITS);
4283
+ const trustedBaseDigest = await digestStagedWorkspace(workspace.baselineDir, SOURCE_LIMITS);
4284
+ if (trustedBaseDigest !== descriptor2.trustedBaseDigest) {
4285
+ await workspace.cleanup();
4286
+ throw new TypeError("trusted Git base digest changed after connection");
4287
+ }
4288
+ if (!resume && await digestStagedWorkspace(workspace.workspaceDir, SOURCE_LIMITS) !== descriptor2.snapshotDigest) {
4289
+ await workspace.cleanup();
4290
+ throw new TypeError("local checkout snapshot digest changed after connection");
4291
+ }
4292
+ return { workspace, sourceDigest: descriptor2.snapshotDigest, trustedBaseDigest };
4293
+ }
4294
+ function createCodeRuntimeToolBroker(input, lease, role) {
4295
+ const broker = createCodeToolBroker({
4296
+ recipes: input.recipes,
4297
+ recipeExecutor: createContainerRecipeExecutor(input.engine),
4298
+ recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
4299
+ readerId: `code-session:${lease.task.taskId}`,
4300
+ readOnlyPrefixes: [".odla-references"]
4301
+ });
4302
+ 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" }) };
4303
+ }
4304
+ async function appendCodeRuntimeEvent(control, command, event, refs) {
4305
+ const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
4306
+ refs.push(eventId);
4307
+ const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
4308
+ await control.appendSessionEvent(command.sessionId, eventId, bounded);
4309
+ }
4310
+ var digestRuntimeValue = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
4311
+ var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
4312
+ var runtimeRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4313
+ var safeRuntimeJson = (value) => {
4314
+ try {
4315
+ return JSON.stringify(value).slice(0, 1e4);
4316
+ } catch {
4317
+ return "[event]";
4318
+ }
4319
+ };
4320
+ function runtimeResultText(value) {
4321
+ const record32 = runtimeRecord(value);
4322
+ return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4323
+ }
3973
4324
  var CodePiRuntimeEngine = class {
3974
4325
  constructor(options) {
3975
4326
  this.options = options;
3976
4327
  this.#run = options.runAttempt ?? runContainerAttempt;
3977
- this.#buildPolicyDigest = digest(JSON.stringify(options.recipes));
4328
+ this.#buildPolicyDigest = digestRuntimeValue(JSON.stringify(options.recipes));
3978
4329
  this.#checkpoints = new CodeRuntimeCheckpointManager({
3979
4330
  control: options.control,
3980
4331
  recipes: options.recipes,
@@ -4009,19 +4360,43 @@ var CodePiRuntimeEngine = class {
4009
4360
  }
4010
4361
  async #start(command, resume) {
4011
4362
  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);
4363
+ const metadata2 = codeCommandMetadata(command.payload, resume);
4364
+ const requestedLocal = codeLocalSource(command.payload);
4014
4365
  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();
4366
+ let sourceDigest;
4367
+ let localTrustedBaseDigest;
4368
+ if (requestedLocal) {
4369
+ const prepared = await prepareRuntimeLocalSource({
4370
+ command,
4371
+ descriptor: requestedLocal,
4372
+ available: this.options.localSource,
4373
+ repository: metadata2.repository,
4374
+ baseCommitSha: metadata2.baseCommitSha,
4375
+ resume
4376
+ });
4377
+ ({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
4378
+ if (command.payload.sourceSet) {
4379
+ const selected = await this.options.control.source(command.sessionId);
4380
+ if (selected.repository !== metadata2.repository || selected.commitSha !== metadata2.baseCommitSha || selected.treeDigest !== metadata2.sourceTreeDigest) {
4381
+ await workspace.cleanup();
4382
+ throw new TypeError("Code local source does not match the selected GitHub primary source");
4383
+ }
4384
+ await attachCodeRuntimeReferences(workspace, selected.references ?? []);
4385
+ }
4386
+ } else {
4387
+ const source = await this.options.control.source(command.sessionId);
4388
+ const materialized = await materializeCodeRuntimeSource(source);
4389
+ try {
4390
+ workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4391
+ trustedBaseDir: materialized.sourceDir,
4392
+ trustedBaseCommitSha: source.commitSha,
4393
+ checkpoint: codeCheckpointPayload(command.payload)
4394
+ })).workspace : await stageWorkspace(materialized.sourceDir);
4395
+ } finally {
4396
+ await materialized.cleanup();
4397
+ }
4398
+ sourceDigest = source.treeDigest;
4023
4399
  }
4024
- const metadata2 = codeCommandMetadata(command.payload, resume);
4025
4400
  const abort = new AbortController();
4026
4401
  const conversationRefs = [];
4027
4402
  const active = {
@@ -4031,21 +4406,30 @@ var CodePiRuntimeEngine = class {
4031
4406
  acknowledged: false,
4032
4407
  role: metadata2.role,
4033
4408
  title: metadata2.title,
4034
- baseCommitSha: source.commitSha,
4035
- trustedBaseDigest: await digestStagedWorkspace(workspace.baselineDir, {
4036
- maxFiles: 1e4,
4037
- maxBytes: 16 * 1024 * 1024
4409
+ baseCommitSha: metadata2.baseCommitSha,
4410
+ repository: metadata2.repository,
4411
+ sourceTreeDigest: metadata2.sourceTreeDigest,
4412
+ trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
4413
+ maxFiles: 2e4,
4414
+ maxBytes: 512 * 1024 * 1024
4038
4415
  }),
4039
- planningInputDigest: metadata2.planningInputDigest ?? digest(
4040
- JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: source.treeDigest })
4416
+ planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
4417
+ JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })
4041
4418
  ),
4042
4419
  done: Promise.resolve(null)
4043
4420
  };
4044
4421
  this.#active.set(command.sessionId, active);
4422
+ if (requestedLocal) {
4423
+ await this.#event(command, {
4424
+ type: "message",
4425
+ actor: "system",
4426
+ body: `Source snapshot: local checkout ${requestedLocal.snapshotDigest} \xB7 ${requestedLocal.modified ? "modified" : "clean"} \xB7 Git ${requestedLocal.headCommitSha}`
4427
+ }, conversationRefs);
4428
+ }
4045
4429
  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);
4430
+ await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` }, conversationRefs).catch(() => void 0);
4047
4431
  await this.#event(command, { type: "status", status: "failed" }, conversationRefs).catch(() => void 0);
4048
- await this.#failure(command, active, message2(cause));
4432
+ await this.#failure(command, active, runtimeErrorMessage(cause));
4049
4433
  return null;
4050
4434
  });
4051
4435
  return { status: "running", message: resume ? "Pi resumed from a portable checkpoint" : "Pi started" };
@@ -4065,22 +4449,29 @@ var CodePiRuntimeEngine = class {
4065
4449
  title: active.title,
4066
4450
  prompt,
4067
4451
  planningInputDigest: active.planningInputDigest,
4068
- attestationDigest: "follow-up"
4452
+ attestationDigest: "follow-up",
4453
+ repository: active.repository,
4454
+ baseCommitSha: active.baseCommitSha,
4455
+ sourceTreeDigest: active.sourceTreeDigest
4069
4456
  }, active).catch(async (cause) => {
4070
4457
  await this.#event(
4071
4458
  command,
4072
- { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` },
4459
+ { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` },
4073
4460
  active.conversationRefs
4074
4461
  ).catch(() => void 0);
4075
4462
  await this.#event(command, { type: "status", status: "failed" }, active.conversationRefs).catch(() => void 0);
4076
- await this.#failure(command, active, message2(cause));
4463
+ await this.#failure(command, active, runtimeErrorMessage(cause));
4077
4464
  return null;
4078
4465
  });
4079
4466
  return { status: "running", message: "Pi accepted the owner prompt" };
4080
4467
  }
4081
4468
  async #runAttempt(command, metadata2, active) {
4082
4469
  const lease = fakeCodeLease(command, metadata2);
4083
- const broker = this.#toolBroker(lease, metadata2.role);
4470
+ const broker = createCodeRuntimeToolBroker({
4471
+ recipes: this.options.recipes,
4472
+ engine: this.options.engine,
4473
+ recipeAuthorization: this.options.recipeAuthorization
4474
+ }, lease, metadata2.role);
4084
4475
  const startedAt = Date.now();
4085
4476
  let completionSeen = false;
4086
4477
  const result = await this.#run({
@@ -4140,7 +4531,7 @@ var CodePiRuntimeEngine = class {
4140
4531
  return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
4141
4532
  }
4142
4533
  if (output.type === "event") {
4143
- const payload = object2(output.payload);
4534
+ const payload = runtimeRecord(output.payload);
4144
4535
  if (output.kind === "pi.started") {
4145
4536
  await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
4146
4537
  } else if (output.kind === "pi.thinking" && payload?.available === true && Number.isSafeInteger(payload.durationMs) && Number(payload.durationMs) >= 0) {
@@ -4153,12 +4544,12 @@ var CodePiRuntimeEngine = class {
4153
4544
  await this.#event(command, {
4154
4545
  type: "message",
4155
4546
  actor: "system",
4156
- body: `${output.kind}${output.payload === void 0 ? "" : ` ${json(output.payload)}`}`
4547
+ body: `${output.kind}${output.payload === void 0 ? "" : ` ${safeRuntimeJson(output.payload)}`}`
4157
4548
  }, active.conversationRefs);
4158
4549
  }
4159
4550
  } else if (output.type === "attempt.complete") {
4160
4551
  completionSeen = true;
4161
- const body = resultText(output.result) ?? `Pi ${output.status}.`;
4552
+ const body = runtimeResultText(output.result) ?? `Pi ${output.status}.`;
4162
4553
  await this.#event(command, {
4163
4554
  type: "message",
4164
4555
  actor: output.status === "completed" ? "agent" : "system",
@@ -4185,15 +4576,6 @@ var CodePiRuntimeEngine = class {
4185
4576
  }
4186
4577
  return result;
4187
4578
  }
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
4579
  async #checkpoint(command) {
4198
4580
  const active = this.#active.get(command.sessionId);
4199
4581
  if (!active) throw new TypeError("Code session workspace is not active on this runtime");
@@ -4208,26 +4590,9 @@ var CodePiRuntimeEngine = class {
4208
4590
  }
4209
4591
  }
4210
4592
  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]";
4593
+ await appendCodeRuntimeEvent(this.options.control, command, event, refs);
4225
4594
  }
4226
4595
  };
4227
- function resultText(value) {
4228
- const record32 = object2(value);
4229
- return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4230
- }
4231
4596
 
4232
4597
  // src/help.ts
4233
4598
  import { readFileSync as readFileSync3 } from "fs";
@@ -4345,24 +4710,90 @@ Safety:
4345
4710
  `);
4346
4711
  }
4347
4712
 
4713
+ // src/code-local-source.ts
4714
+ import { execFile as execFile3 } from "child_process";
4715
+ import { createHash as createHash4 } from "crypto";
4716
+ var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
4717
+ async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
4718
+ const headCommitSha = await readHead(cwd);
4719
+ const staged = await stageWorkspace(cwd, { ...SOURCE_LIMITS2, gitTrackedAndUnignored: true });
4720
+ let trusted;
4721
+ try {
4722
+ trusted = await materializeGitTree(cwd, headCommitSha, SOURCE_LIMITS2);
4723
+ const comparison = await stageWorkspacePair(trusted.sourceDir, staged.baselineDir, SOURCE_LIMITS2);
4724
+ let developerPatch;
4725
+ try {
4726
+ developerPatch = await comparison.patch(4 * 1024 * 1024);
4727
+ } finally {
4728
+ await comparison.cleanup();
4729
+ }
4730
+ const descriptor2 = {
4731
+ kind: "local_checkout",
4732
+ repository,
4733
+ headCommitSha,
4734
+ trustedBaseDigest: await digestStagedWorkspace(trusted.sourceDir, SOURCE_LIMITS2),
4735
+ developerPatchDigest: digestText(developerPatch),
4736
+ snapshotDigest: await digestStagedWorkspace(staged.baselineDir, SOURCE_LIMITS2),
4737
+ modified: developerPatch.length > 0,
4738
+ fileCount: staged.fileCount,
4739
+ byteCount: staged.byteCount,
4740
+ capturedAt: Date.now()
4741
+ };
4742
+ return {
4743
+ descriptor: descriptor2,
4744
+ trustedBaseDir: trusted.sourceDir,
4745
+ sourceDir: staged.baselineDir,
4746
+ cleanup: async () => {
4747
+ await Promise.all([staged.cleanup(), trusted.cleanup()]);
4748
+ }
4749
+ };
4750
+ } catch (error) {
4751
+ await Promise.all([staged.cleanup(), trusted?.cleanup()]);
4752
+ throw error;
4753
+ }
4754
+ }
4755
+ async function readGitHead(cwd) {
4756
+ const value = await new Promise((accept, reject) => {
4757
+ execFile3("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
4758
+ if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
4759
+ else accept(stdout.trim());
4760
+ });
4761
+ });
4762
+ if (!/^[0-9a-f]{40}$/.test(value)) throw new Error("code connect could not resolve the checkout HEAD commit");
4763
+ return value;
4764
+ }
4765
+ function digestText(value) {
4766
+ return `sha256:${createHash4("sha256").update(value).digest("hex")}`;
4767
+ }
4768
+
4769
+ // src/code-images.ts
4770
+ import { execFile as execFile4 } from "child_process";
4771
+ async function prepareCodeImages(engine, images) {
4772
+ for (const image of images) {
4773
+ await new Promise((accept, reject) => {
4774
+ const args = engine === "container" ? ["image", "pull", image] : ["pull", image];
4775
+ execFile4(engine, args, { encoding: "utf8", maxBuffer: 2 * 1024 * 1024, timeout: 10 * 6e4 }, (error) => {
4776
+ if (error) reject(new Error(`could not prepare pinned Code image ${image}`));
4777
+ else accept();
4778
+ });
4779
+ });
4780
+ }
4781
+ }
4782
+
4348
4783
  // 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
4784
  async function codeConnect(options) {
4361
4785
  const cwd = options.cwd ?? process.cwd();
4362
4786
  const configPath = resolve5(cwd, options.configPath);
4363
4787
  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";
4788
+ const requestedAppId = options.appId?.trim();
4789
+ if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
4790
+ throw new Error("--app-id must be a valid odla app id");
4791
+ }
4792
+ if (requestedAppId && cfg && requestedAppId !== cfg.app.id) {
4793
+ throw new Error(`--app-id ${requestedAppId} does not match ${options.configPath} (${cfg.app.id})`);
4794
+ }
4795
+ const appId = requestedAppId ?? cfg?.app.id;
4796
+ const platform = (options.platform ?? cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai").replace(/\/+$/, "");
4366
4797
  const appEnv = options.env ?? (cfg ? cfg.envs.includes("dev") ? "dev" : cfg.envs[0] : "dev");
4367
4798
  if (appEnv !== "dev" && appEnv !== "prod" || cfg && !cfg.envs.includes(appEnv)) {
4368
4799
  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 +4812,77 @@ async function codeConnect(options) {
4381
4812
  const out = options.stdout ?? console;
4382
4813
  const doFetch = options.fetch ?? fetch;
4383
4814
  const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
4815
+ await (options.prepareImages ?? prepareCodeImages)(engine, [
4816
+ CODE_PI_IMAGE,
4817
+ ...new Set(CODE_BUILD_RECIPES.map((recipe2) => recipe2.image))
4818
+ ]);
4384
4819
  const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
4385
4820
  const hostName = (options.name ?? hostname()).trim();
4386
4821
  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
- });
4822
+ const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
4823
+ const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
4824
+ cwd,
4825
+ repository,
4826
+ options.readGitHead
4827
+ );
4828
+ try {
4829
+ const descriptor2 = localSource.descriptor;
4830
+ const approval = await (options.getToken ?? getScopedPlatformToken)({
4831
+ platform,
4832
+ scope: "app:code:host:connect",
4833
+ email: options.email,
4834
+ open: options.open,
4835
+ fetch: doFetch,
4836
+ stdout: out,
4837
+ openApprovalUrl: options.openApprovalUrl,
4838
+ cache: false,
4839
+ label: `Terminal connection for ${appId ?? repository}/${appEnv}`
4840
+ });
4841
+ const target = appId ? { appId } : { repository };
4842
+ const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
4843
+ method: "POST",
4844
+ headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
4845
+ body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
4846
+ redirect: "error",
4847
+ signal: options.signal
4848
+ });
4849
+ const raw = await response2.json().catch(() => null);
4850
+ if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
4851
+ const connection = parseConnection(raw, appId, appEnv);
4852
+ const capabilities = {
4853
+ protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
4854
+ platform: hostPlatform,
4855
+ arch: process.arch,
4856
+ engines: [engine],
4857
+ cpuCount: cpus().length,
4858
+ memoryBytes: totalmem(),
4859
+ source: descriptor2,
4860
+ images: {
4861
+ ready: true,
4862
+ pi: CODE_PI_IMAGE,
4863
+ recipes: CODE_BUILD_RECIPES.map((recipe2) => ({ id: recipe2.id, image: recipe2.image }))
4864
+ }
4865
+ };
4866
+ out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
4867
+ out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
4868
+ out.log(`source: local checkout \xB7 ${descriptor2.headCommitSha.slice(0, 10)} \xB7 ${descriptor2.fileCount} files \xB7 staged copy`);
4869
+ out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
4870
+ out.log(`Studio: ${platform}/studio/apps/${encodeURIComponent(connection.binding.appId)}/${connection.binding.env}/code/sessions`);
4871
+ await (options.runRuntime ?? runCodeRuntime)({
4872
+ endpoint: platform,
4873
+ token: connection.token,
4874
+ engine,
4875
+ capabilities,
4876
+ localSource,
4877
+ heartbeatMs,
4878
+ once: options.once === true,
4879
+ signal: options.signal,
4880
+ stdout: out,
4881
+ fetch: doFetch
4882
+ });
4883
+ } finally {
4884
+ await localSource.cleanup();
4885
+ }
4431
4886
  }
4432
4887
  async function runCodeRuntime(input) {
4433
4888
  const controller = new AbortController();
@@ -4450,7 +4905,8 @@ async function runCodeRuntime(input) {
4450
4905
  engine: input.engine,
4451
4906
  image: CODE_PI_IMAGE,
4452
4907
  recipes: CODE_BUILD_RECIPES,
4453
- recipeAuthorization: "registered_recipe"
4908
+ recipeAuthorization: "registered_recipe",
4909
+ localSource: input.localSource
4454
4910
  });
4455
4911
  const reconciler = new CodeRuntimeReconciler(control, commandEngine);
4456
4912
  try {
@@ -4491,8 +4947,8 @@ function parseConnection(value, appId, appEnv) {
4491
4947
  return root;
4492
4948
  }
4493
4949
  function apiFailure(action, status, value) {
4494
- const message3 = record4(record4(value)?.error)?.message;
4495
- return `${action} failed (${status})${typeof message3 === "string" ? `: ${message3}` : ""}`;
4950
+ const message2 = record4(record4(value)?.error)?.message;
4951
+ return `${action} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
4496
4952
  }
4497
4953
  function record4(value) {
4498
4954
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -4501,14 +4957,14 @@ function record4(value) {
4501
4957
  // src/doctor-checks.ts
4502
4958
  import { execFileSync } from "child_process";
4503
4959
  import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
4504
- import { join as join5, resolve as resolve6 } from "path";
4960
+ import { join as join6, resolve as resolve6 } from "path";
4505
4961
 
4506
4962
  // src/wrangler.ts
4507
- import { spawn as spawn4 } from "child_process";
4963
+ import { spawn as spawn5 } from "child_process";
4508
4964
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
4509
- import { join as join4 } from "path";
4965
+ import { join as join5 } from "path";
4510
4966
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
4511
- const child = spawn4(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4967
+ const child = spawn5(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4512
4968
  let stdout = "";
4513
4969
  let stderr = "";
4514
4970
  child.stdout.on("data", (chunk) => stdout += chunk.toString());
@@ -4520,7 +4976,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
4520
4976
  var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
4521
4977
  function findWranglerConfig(rootDir) {
4522
4978
  for (const name of WRANGLER_CONFIG_FILES) {
4523
- const path = join4(rootDir, name);
4979
+ const path = join5(rootDir, name);
4524
4980
  if (existsSync5(path)) return path;
4525
4981
  }
4526
4982
  return null;
@@ -4629,7 +5085,7 @@ function wranglerWarnings(rootDir) {
4629
5085
  const dir = resolve6(rootDir, assets.directory);
4630
5086
  if (dir === resolve6(rootDir)) {
4631
5087
  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"))) {
5088
+ } else if (existsSync6(join6(dir, "node_modules"))) {
4633
5089
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
4634
5090
  }
4635
5091
  }
@@ -4694,7 +5150,7 @@ function calendarProjectWarnings(rootDir) {
4694
5150
  }
4695
5151
  function readPackageJson(rootDir) {
4696
5152
  try {
4697
- return JSON.parse(readFileSync5(join5(rootDir, "package.json"), "utf8"));
5153
+ return JSON.parse(readFileSync5(join6(rootDir, "package.json"), "utf8"));
4698
5154
  } catch {
4699
5155
  return null;
4700
5156
  }
@@ -5261,8 +5717,8 @@ async function readRegistryApp(cfg, token, doFetch) {
5261
5717
  });
5262
5718
  if (res.status === 404) return null;
5263
5719
  if (!res.ok) return null;
5264
- const json2 = await res.json();
5265
- return json2.app ?? null;
5720
+ const json = await res.json();
5721
+ return json.app ?? null;
5266
5722
  }
5267
5723
  async function postJson2(doFetch, url, bearer, body) {
5268
5724
  const res = await doFetch(url, {
@@ -5428,10 +5884,10 @@ async function provision(options) {
5428
5884
  stdout: out
5429
5885
  });
5430
5886
  } catch (error) {
5431
- const message3 = error instanceof Error ? error.message : String(error);
5887
+ const message2 = error instanceof Error ? error.message : String(error);
5432
5888
  const consent = env === "prod" || env === "production" ? " --yes" : "";
5433
5889
  throw new Error(
5434
- `${message3}
5890
+ `${message2}
5435
5891
  ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}${consent}" without issuing or rotating again`,
5436
5892
  { cause: error }
5437
5893
  );
@@ -5547,7 +6003,7 @@ async function resolveVaultWrite(options) {
5547
6003
  }
5548
6004
 
5549
6005
  // src/security.ts
5550
- import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve8, sep as sep3 } from "path";
6006
+ import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve8, sep as sep4 } from "path";
5551
6007
  import {
5552
6008
  cloudflareAppProfile,
5553
6009
  createPlatformSecurityReasoners,
@@ -5568,7 +6024,7 @@ async function runHostedSecurity(options) {
5568
6024
  const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
5569
6025
  const target = resolve8(options.target ?? cfg?.rootDir ?? ".");
5570
6026
  const output = resolve8(options.out ?? resolve8(target, ".odla/security/hosted"));
5571
- const outputRelative = relative4(target, output).split(sep3).join("/");
6027
+ const outputRelative = relative4(target, output).split(sep4).join("/");
5572
6028
  if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
5573
6029
  const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
5574
6030
  const tokenRequest = {
@@ -5580,7 +6036,7 @@ async function runHostedSecurity(options) {
5580
6036
  };
5581
6037
  const token = await injectedToken(options, tokenRequest);
5582
6038
  const snapshot = await snapshotDirectory(target, {
5583
- exclude: !outputRelative.startsWith("../") && !isAbsolute3(outputRelative) ? [outputRelative] : []
6039
+ exclude: !outputRelative.startsWith("../") && !isAbsolute4(outputRelative) ? [outputRelative] : []
5584
6040
  });
5585
6041
  const hosted = await createPlatformSecurityReasoners({
5586
6042
  platform,
@@ -5741,8 +6197,8 @@ async function startHostedSecurityJob(options) {
5741
6197
  "start hosted security job"
5742
6198
  );
5743
6199
  } 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)) {
6200
+ const message2 = error instanceof Error ? error.message : String(error);
6201
+ if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message2)) {
5746
6202
  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
6203
  }
5748
6204
  throw error;
@@ -5823,7 +6279,7 @@ function securityExecutionDigest(value) {
5823
6279
  // src/skill.ts
5824
6280
  import { existsSync as existsSync8, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync, writeFileSync as writeFileSync3 } from "fs";
5825
6281
  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";
6282
+ import { dirname as dirname6, isAbsolute as isAbsolute5, join as join7, relative as relative5, resolve as resolve9, sep as sep5 } from "path";
5827
6283
  import { fileURLToPath } from "url";
5828
6284
 
5829
6285
  // src/skill-adapters.ts
@@ -5899,12 +6355,12 @@ function installSkill(options = {}) {
5899
6355
  plans.set(target, { target, content, boundary, managedMerge });
5900
6356
  };
5901
6357
  const planSkillTree = (targetDir2, boundary = root) => {
5902
- for (const rel of files) plan(join6(targetDir2, rel), readFileSync6(join6(sourceDir, rel), "utf8"), false, boundary);
6358
+ for (const rel of files) plan(join7(targetDir2, rel), readFileSync6(join7(sourceDir, rel), "utf8"), false, boundary);
5903
6359
  };
5904
6360
  let targetDir;
5905
6361
  if (options.global) {
5906
- const claudeRoot = join6(home, ".claude", "skills");
5907
- const codexRoot = resolve9(options.codexHomeDir ?? process.env.CODEX_HOME ?? join6(home, ".codex"), "skills");
6362
+ const claudeRoot = join7(home, ".claude", "skills");
6363
+ const codexRoot = resolve9(options.codexHomeDir ?? process.env.CODEX_HOME ?? join7(home, ".codex"), "skills");
5908
6364
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
5909
6365
  for (const harness of harnesses) {
5910
6366
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
@@ -5912,35 +6368,35 @@ function installSkill(options = {}) {
5912
6368
  rememberTarget(harness, skillRoot);
5913
6369
  }
5914
6370
  } else {
5915
- const sharedRoot = join6(root, ".agents", "skills");
6371
+ const sharedRoot = join7(root, ".agents", "skills");
5916
6372
  planSkillTree(sharedRoot);
5917
- const claudeRoot = join6(root, ".claude", "skills");
6373
+ const claudeRoot = join7(root, ".claude", "skills");
5918
6374
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
5919
6375
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
5920
6376
  if (harnesses.includes("claude")) {
5921
6377
  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));
6378
+ const canonical = readFileSync6(join7(sourceDir, skill, "SKILL.md"), "utf8");
6379
+ plan(join7(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
5924
6380
  }
5925
6381
  rememberTarget("claude", claudeRoot);
5926
6382
  }
5927
6383
  if (harnesses.includes("cursor")) {
5928
- const cursorRule = join6(root, ".cursor", "rules", "odla.mdc");
6384
+ const cursorRule = join7(root, ".cursor", "rules", "odla.mdc");
5929
6385
  plan(cursorRule, CURSOR_RULE);
5930
6386
  rememberTarget("cursor", cursorRule);
5931
6387
  }
5932
6388
  if (harnesses.includes("agents")) {
5933
- const agentsFile = join6(root, "AGENTS.md");
6389
+ const agentsFile = join7(root, "AGENTS.md");
5934
6390
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5935
6391
  rememberTarget("agents", agentsFile);
5936
6392
  }
5937
6393
  if (harnesses.includes("copilot")) {
5938
- const copilotFile = join6(root, ".github", "copilot-instructions.md");
6394
+ const copilotFile = join7(root, ".github", "copilot-instructions.md");
5939
6395
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5940
6396
  rememberTarget("copilot", copilotFile);
5941
6397
  }
5942
6398
  if (harnesses.includes("gemini")) {
5943
- const geminiFile = join6(root, "GEMINI.md");
6399
+ const geminiFile = join7(root, "GEMINI.md");
5944
6400
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5945
6401
  rememberTarget("gemini", geminiFile);
5946
6402
  }
@@ -5996,7 +6452,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
5996
6452
  };
5997
6453
  }
5998
6454
  function pathsUnder(root, paths) {
5999
- return [...paths].map((path) => relative5(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute4(path)).sort();
6455
+ return [...paths].map((path) => relative5(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep5}`) && !isAbsolute5(path)).sort();
6000
6456
  }
6001
6457
  function normalizeHarnesses(values, global) {
6002
6458
  const requested = values?.length ? values : ["claude"];
@@ -6042,12 +6498,12 @@ function managedFileContent(path, block, force, boundary) {
6042
6498
  }
6043
6499
  function symlinkedComponent(boundary, target) {
6044
6500
  const rel = relative5(boundary, target);
6045
- if (rel === ".." || rel.startsWith(`..${sep4}`) || isAbsolute4(rel)) {
6501
+ if (rel === ".." || rel.startsWith(`..${sep5}`) || isAbsolute5(rel)) {
6046
6502
  throw new Error(`agent setup target escapes its install root: ${target}`);
6047
6503
  }
6048
6504
  let current = boundary;
6049
- for (const part of rel.split(sep4).filter(Boolean)) {
6050
- current = join6(current, part);
6505
+ for (const part of rel.split(sep5).filter(Boolean)) {
6506
+ current = join7(current, part);
6051
6507
  try {
6052
6508
  if (lstatSync(current).isSymbolicLink()) return current;
6053
6509
  } catch (error) {
@@ -6064,7 +6520,7 @@ function listFiles(dir) {
6064
6520
  const results = [];
6065
6521
  const walk = (current) => {
6066
6522
  for (const entry of readdirSync(current, { withFileTypes: true })) {
6067
- const path = join6(current, entry.name);
6523
+ const path = join7(current, entry.name);
6068
6524
  if (entry.isDirectory()) walk(path);
6069
6525
  else results.push(relative5(dir, path));
6070
6526
  }
@@ -6555,6 +7011,8 @@ async function codeCommand(parsed, dependencies) {
6555
7011
  assertArgs(parsed, [
6556
7012
  "config",
6557
7013
  "env",
7014
+ "platform",
7015
+ "app-id",
6558
7016
  "email",
6559
7017
  "open",
6560
7018
  "name",
@@ -6570,6 +7028,8 @@ async function codeCommand(parsed, dependencies) {
6570
7028
  await (dependencies.codeConnect ?? codeConnect)({
6571
7029
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
6572
7030
  env: stringOpt(parsed.options.env),
7031
+ platform: stringOpt(parsed.options.platform),
7032
+ appId: stringOpt(parsed.options["app-id"]),
6573
7033
  email: stringOpt(parsed.options.email),
6574
7034
  open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
6575
7035
  name: stringOpt(parsed.options.name),
@@ -6627,12 +7087,12 @@ async function hostedSecurityContext(parsed, dependencies) {
6627
7087
  );
6628
7088
  return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
6629
7089
  }
6630
- async function interactiveConfirmation(message3, dependencies) {
6631
- if (dependencies.confirm) return dependencies.confirm(message3);
7090
+ async function interactiveConfirmation(message2, dependencies) {
7091
+ if (dependencies.confirm) return dependencies.confirm(message2);
6632
7092
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
6633
7093
  const prompt = createInterface({ input: process.stdin, output: process.stdout });
6634
7094
  try {
6635
- const answer = await prompt.question(`${message3} [y/N] `);
7095
+ const answer = await prompt.question(`${message2} [y/N] `);
6636
7096
  return /^y(?:es)?$/i.test(answer.trim());
6637
7097
  } finally {
6638
7098
  prompt.close();
@@ -7286,4 +7746,4 @@ export {
7286
7746
  exitCodeFor,
7287
7747
  runCli
7288
7748
  };
7289
- //# sourceMappingURL=chunk-AN6KZMR5.js.map
7749
+ //# sourceMappingURL=chunk-S3OYXHDG.js.map