@odla-ai/cli 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -403,12 +403,13 @@ async function scopedToken(platform, scope, options, doFetch, out) {
403
403
  const { token, expiresAt } = await requestToken2({
404
404
  endpoint: audience,
405
405
  email,
406
- label: options.label ?? `odla CLI admin AI (${scope})`,
406
+ label: options.label ?? `odla CLI (${scope})`,
407
407
  scopes: [scope],
408
408
  fetch: doFetch,
409
409
  onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
410
410
  const approvalUrl = verificationUriComplete ?? handshakeUrl(audience, userCode);
411
- out.log(`Review, then approve scoped request ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
411
+ const requestName = scope === "app:code:host:connect" ? "terminal connection" : "scoped request";
412
+ out.log(`Review, then approve ${requestName} ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
412
413
  if (approvalBrowser({ open: options.open }).open) {
413
414
  await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
414
415
  }
@@ -505,8 +506,8 @@ async function responseBody(response2) {
505
506
  }
506
507
  function apiError(status, body) {
507
508
  const error = isRecord(body) && isRecord(body.error) ? body.error : void 0;
508
- const message3 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
509
- return `read System AI admin changes failed (${status}): ${message3}`;
509
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
510
+ return `read System AI admin changes failed (${status}): ${message2}`;
510
511
  }
511
512
  function timestamp(value) {
512
513
  if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
@@ -598,8 +599,8 @@ async function responseBody2(res) {
598
599
  }
599
600
  function apiError2(action, status, body) {
600
601
  const error = isRecord2(body) && isRecord2(body.error) ? body.error : void 0;
601
- const message3 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
602
- return `${action} failed (${status}): ${message3}`;
602
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
603
+ return `${action} failed (${status}): ${message2}`;
603
604
  }
604
605
  function isRecord2(value) {
605
606
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -779,8 +780,8 @@ async function responseBody3(res) {
779
780
  }
780
781
  function apiError3(action, status, body) {
781
782
  const error = isRecord3(body) && isRecord3(body.error) ? body.error : void 0;
782
- const message3 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
783
- return `${action} failed (${status}): ${message3}`;
783
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
784
+ return `${action} failed (${status}): ${message2}`;
784
785
  }
785
786
  function isRecord3(value) {
786
787
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -1110,8 +1111,8 @@ var CAPABILITIES = {
1110
1111
  "connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"
1111
1112
  ]
1112
1113
  };
1113
- function printCapabilities(json2 = false, out = console) {
1114
- if (json2) {
1114
+ function printCapabilities(json = false, out = console) {
1115
+ if (json) {
1115
1116
  out.log(JSON.stringify(CAPABILITIES, null, 2));
1116
1117
  return;
1117
1118
  }
@@ -1160,8 +1161,8 @@ var CalendarRequestError = class extends Error {
1160
1161
  status;
1161
1162
  /** Machine-readable `error.code` from the response body, when present. */
1162
1163
  code;
1163
- constructor(message3, status, code) {
1164
- super(message3);
1164
+ constructor(message2, status, code) {
1165
+ super(message2);
1165
1166
  this.name = "CalendarRequestError";
1166
1167
  this.status = status;
1167
1168
  if (code !== void 0) this.code = code;
@@ -1309,8 +1310,8 @@ async function calendarJson(ctx, suffix, init) {
1309
1310
  const body = await response2.json().catch(() => ({}));
1310
1311
  if (!response2.ok) {
1311
1312
  const code = textField(body.error?.code, 128);
1312
- const message3 = textField(body.error?.message, 500);
1313
- const detail = `${code ? ` ${code}` : ""}${message3 ? `: ${message3}` : ""}`;
1313
+ const message2 = textField(body.error?.message, 500);
1314
+ const detail = `${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`;
1314
1315
  throw new CalendarRequestError(redactSecrets(`calendar request failed (${response2.status})${detail}`), response2.status, code);
1315
1316
  }
1316
1317
  return body;
@@ -1546,8 +1547,8 @@ function pollTimeout(value = 10 * 6e4) {
1546
1547
  function productionConsent(env, yes, action) {
1547
1548
  if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action} for "${env}" without --yes`);
1548
1549
  }
1549
- function printStatus(status, json2, out) {
1550
- if (json2) {
1550
+ function printStatus(status, json, out) {
1551
+ if (json) {
1551
1552
  out.log(JSON.stringify(status, null, 2));
1552
1553
  return;
1553
1554
  }
@@ -1583,8 +1584,8 @@ async function requestHostedSecurityJson(options, path, init, action) {
1583
1584
  const body = await response2.json().catch(() => ({}));
1584
1585
  if (!response2.ok) {
1585
1586
  const code = optionalHostedText(body.error?.code, "error code", 128);
1586
- const message3 = optionalHostedText(body.error?.message, "error message", 300);
1587
- throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message3 ? `: ${message3}` : ""}`);
1587
+ const message2 = optionalHostedText(body.error?.message, "error message", 300);
1588
+ throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
1588
1589
  }
1589
1590
  return body;
1590
1591
  }
@@ -1669,7 +1670,7 @@ function hostedSecurityCredential(value) {
1669
1670
  // src/security-hosted-github.ts
1670
1671
  async function connectGitHubSecuritySource(options) {
1671
1672
  const out = options.stdout ?? console;
1672
- const repository = githubRepositoryName(options.repository);
1673
+ const repository = options.repository === void 0 ? void 0 : githubRepositoryName(options.repository);
1673
1674
  const attempt = await requestHostedSecurityJson(
1674
1675
  options,
1675
1676
  "/registry/github/connect",
@@ -1678,7 +1679,7 @@ async function connectGitHubSecuritySource(options) {
1678
1679
  body: JSON.stringify({
1679
1680
  appId: hostedIdentifier(options.appId, "appId"),
1680
1681
  env: hostedIdentifier(options.env, "env"),
1681
- repository
1682
+ ...repository === void 0 ? {} : { repository }
1682
1683
  })
1683
1684
  },
1684
1685
  "start GitHub connection"
@@ -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,14 +2360,48 @@ 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 });
2180
2368
  throw error;
2181
2369
  }
2182
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 });
2397
+ throw error;
2398
+ }
2399
+ }
2400
+
2401
+ // ../harness/dist/chunk-QEW6NNAO.js
2402
+ import { createHash } from "crypto";
2403
+ import { readFile, readdir as readdir2 } from "fs/promises";
2404
+ import { relative as relative3, resolve as resolve4 } from "path";
2183
2405
 
2184
2406
  // ../camel/dist/chunk-7FHPOQVP.js
2185
2407
  var CamelError = class extends Error {
@@ -2199,8 +2421,8 @@ function canonicalJson(value) {
2199
2421
  }
2200
2422
  async function sha256Hex(value) {
2201
2423
  const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
2202
- const digest2 = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
2203
- return [...new Uint8Array(digest2)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2424
+ const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
2425
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2204
2426
  }
2205
2427
  function utf8Length(value) {
2206
2428
  if (typeof value === "string") return new TextEncoder().encode(value).byteLength;
@@ -2477,8 +2699,8 @@ function freeze(value) {
2477
2699
  })
2478
2700
  });
2479
2701
  }
2480
- function invalid2(message3) {
2481
- return new CamelError("state_conflict", message3);
2702
+ function invalid2(message2) {
2703
+ return new CamelError("state_conflict", message2);
2482
2704
  }
2483
2705
  var SHA4 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
2484
2706
  var REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
@@ -2513,23 +2735,20 @@ function validateSnapshot(snapshot, limits) {
2513
2735
  }
2514
2736
  }
2515
2737
 
2516
- // ../harness/dist/chunk-RVUUURK2.js
2517
- import { spawn as spawn3 } from "child_process";
2518
- import { lstat } from "fs/promises";
2519
- import { resolve as resolve4, sep } from "path";
2738
+ // ../harness/dist/chunk-QEW6NNAO.js
2739
+ import { spawn as spawn4 } from "child_process";
2740
+ import { lstat as lstat2 } from "fs/promises";
2741
+ import { resolve as resolve23, sep as sep3 } from "path";
2520
2742
  import { spawn as spawn23 } from "child_process";
2521
2743
  import { getgid as getgid2, getuid as getuid2 } from "process";
2522
2744
  import { randomUUID } from "crypto";
2523
2745
  import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
2524
2746
  import { createReadStream } from "fs";
2525
- import { lstat as lstat2 } from "fs/promises";
2526
- import { join as join3 } from "path";
2527
- import { createHash } from "crypto";
2528
- import { readFile, readdir as readdir2 } from "fs/promises";
2529
- import { relative as relative3, resolve as resolve22 } from "path";
2530
- import { mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm2, writeFile } from "fs/promises";
2531
- import { tmpdir as tmpdir2 } from "os";
2532
- import { dirname as dirname4, join as join23, resolve as resolve32, sep as sep2 } from "path";
2747
+ import { lstat as lstat22 } from "fs/promises";
2748
+ import { join as join4 } from "path";
2749
+ import { mkdir as mkdir3, mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile2 } from "fs/promises";
2750
+ import { tmpdir as tmpdir3 } from "os";
2751
+ import { dirname as dirname4, join as join23, resolve as resolve32, sep as sep22 } from "path";
2533
2752
  import { readFile as readFile2, readdir as readdir22, stat as stat2 } from "fs/promises";
2534
2753
  import { relative as relative22, resolve as resolve42 } from "path";
2535
2754
 
@@ -2809,8 +3028,34 @@ function looksLikeDestination(value) {
2809
3028
  return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
2810
3029
  }
2811
3030
 
2812
- // ../harness/dist/chunk-RVUUURK2.js
3031
+ // ../harness/dist/chunk-QEW6NNAO.js
2813
3032
  import { createHash as createHash3 } from "crypto";
3033
+ async function digestStagedWorkspace(root, limits) {
3034
+ const files = [];
3035
+ const walk = async (directory) => {
3036
+ const entries = await readdir2(directory, { withFileTypes: true });
3037
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
3038
+ if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
3039
+ const target = resolve4(directory, entry.name);
3040
+ if (entry.isDirectory()) await walk(target);
3041
+ else if (entry.isFile()) {
3042
+ files.push({ path: relative3(root, target).split("\\").join("/"), target });
3043
+ if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
3044
+ }
3045
+ }
3046
+ };
3047
+ await walk(resolve4(root));
3048
+ const hash = createHash("sha256");
3049
+ let bytes = 0;
3050
+ for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
3051
+ const content = await readFile(file.target);
3052
+ bytes += Buffer.byteLength(file.path) + content.byteLength;
3053
+ if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
3054
+ hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
3055
+ hash.update(content);
3056
+ }
3057
+ return `sha256:${hash.digest("hex")}`;
3058
+ }
2814
3059
  var CODE_RUNTIME_PROTOCOL_VERSION = 1;
2815
3060
  async function runCodeRuntimeHeartbeatLoop(options) {
2816
3061
  const heartbeatMs = options.heartbeatMs ?? 15e3;
@@ -2882,8 +3127,8 @@ function wait(ms, signal) {
2882
3127
  });
2883
3128
  }
2884
3129
  var CodeRuntimeControlError = class extends Error {
2885
- constructor(message3, status, code = "control_error") {
2886
- super(message3);
3130
+ constructor(message2, status, code = "control_error") {
3131
+ super(message2);
2887
3132
  this.status = status;
2888
3133
  this.code = code;
2889
3134
  }
@@ -2962,9 +3207,9 @@ function createCodeRuntimeControlClient(options) {
2962
3207
  }
2963
3208
  await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
2964
3209
  },
2965
- reportSessionFailure: async (sessionId, message3) => {
2966
- if (!message3.trim() || message3.length > 2e3) throw new TypeError("invalid Code session failure");
2967
- await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message3 });
3210
+ reportSessionFailure: async (sessionId, message2) => {
3211
+ if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
3212
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
2968
3213
  }
2969
3214
  };
2970
3215
  }
@@ -3032,9 +3277,9 @@ async function parseSource(value) {
3032
3277
  return { path: file.path, content: file.content };
3033
3278
  });
3034
3279
  const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
3035
- const digest2 = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3036
- if (digest2 !== snapshot.treeDigest) throw invalid("source digest");
3037
- return { ...source, treeDigest: digest2 };
3280
+ const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3281
+ if (digest !== snapshot.treeDigest) throw invalid("source digest");
3282
+ return { ...source, treeDigest: digest };
3038
3283
  }
3039
3284
  function parseReview(value) {
3040
3285
  const review = record3(record3(value)?.review);
@@ -3093,9 +3338,9 @@ function validateRelativePath(path) {
3093
3338
  }
3094
3339
  function resolveCodePath(workspaceDir, path) {
3095
3340
  validateRelativePath(path);
3096
- const root = resolve4(workspaceDir);
3097
- const target = resolve4(root, path);
3098
- if (target !== root && !target.startsWith(`${root}${sep}`)) throw new TypeError("path escapes the staged workspace");
3341
+ const root = resolve23(workspaceDir);
3342
+ const target = resolve23(root, path);
3343
+ if (target !== root && !target.startsWith(`${root}${sep3}`)) throw new TypeError("path escapes the staged workspace");
3099
3344
  return target;
3100
3345
  }
3101
3346
  async function applyCodePatch(workspaceDir, patch2, paths) {
@@ -3103,7 +3348,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
3103
3348
  await gitApply(workspaceDir, patch2, false);
3104
3349
  for (const path of paths) {
3105
3350
  try {
3106
- const info = await lstat(resolveCodePath(workspaceDir, path));
3351
+ const info = await lstat2(resolveCodePath(workspaceDir, path));
3107
3352
  if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
3108
3353
  throw new TypeError("patch created a non-regular workspace entry");
3109
3354
  }
@@ -3115,7 +3360,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
3115
3360
  function gitApply(cwd, patch2, check) {
3116
3361
  return new Promise((accept, reject) => {
3117
3362
  const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
3118
- const child = spawn3("git", args, {
3363
+ const child = spawn4("git", args, {
3119
3364
  cwd,
3120
3365
  shell: false,
3121
3366
  stdio: ["pipe", "ignore", "pipe"],
@@ -3285,32 +3530,6 @@ function execute(engine, args, name, recipe2, signal) {
3285
3530
  });
3286
3531
  });
3287
3532
  }
3288
- async function digestStagedWorkspace(root, limits) {
3289
- const files = [];
3290
- const walk = async (directory) => {
3291
- const entries = await readdir2(directory, { withFileTypes: true });
3292
- for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
3293
- if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
3294
- const target = resolve22(directory, entry.name);
3295
- if (entry.isDirectory()) await walk(target);
3296
- else if (entry.isFile()) {
3297
- files.push({ path: relative3(root, target).split("\\").join("/"), target });
3298
- if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
3299
- }
3300
- }
3301
- };
3302
- await walk(resolve22(root));
3303
- const hash = createHash("sha256");
3304
- let bytes = 0;
3305
- for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
3306
- const content = await readFile(file.target);
3307
- bytes += Buffer.byteLength(file.path) + content.byteLength;
3308
- if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
3309
- hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
3310
- hash.update(content);
3311
- }
3312
- return `sha256:${hash.digest("hex")}`;
3313
- }
3314
3533
  var SHA3 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
3315
3534
  var DIGEST3 = /^sha256:[0-9a-f]{64}$/;
3316
3535
  var ID3 = /^[A-Za-z0-9._:-]{1,160}$/;
@@ -3419,8 +3638,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
3419
3638
  const receipts = [];
3420
3639
  for (const artifact of recipe2.expectedArtifacts ?? []) {
3421
3640
  try {
3422
- const path = join3(workspaceDir, artifact.path);
3423
- const info = await lstat2(path);
3641
+ const path = join4(workspaceDir, artifact.path);
3642
+ const info = await lstat22(path);
3424
3643
  if (!info.isFile() || info.isSymbolicLink()) {
3425
3644
  receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
3426
3645
  } else if (info.size > artifact.maximumBytes) {
@@ -3516,8 +3735,8 @@ async function prepareRuntimeCheckpoint(input) {
3516
3735
  policy: {
3517
3736
  policyId: "code.runtime",
3518
3737
  recipes: input.recipes,
3519
- maximumFiles: 1e4,
3520
- maximumBytes: 16 * 1024 * 1024
3738
+ maximumFiles: 2e4,
3739
+ maximumBytes: 512 * 1024 * 1024
3521
3740
  },
3522
3741
  recipeExecutor: input.recipeExecutor
3523
3742
  });
@@ -3599,11 +3818,11 @@ var CodeRuntimeCheckpointManager = class {
3599
3818
  };
3600
3819
  var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
3601
3820
  var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
3602
- async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir2()) {
3821
+ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir3()) {
3603
3822
  if (!snapshot.files.length || snapshot.files.length > 1e4) throw new TypeError("Code source file count is invalid");
3604
- const root = await mkdtemp2(join23(tempRoot, "odla-code-source-"));
3823
+ const root = await mkdtemp3(join23(tempRoot, "odla-code-source-"));
3605
3824
  const sourceDir = join23(root, "source");
3606
- await mkdir2(sourceDir);
3825
+ await mkdir3(sourceDir);
3607
3826
  const seen = /* @__PURE__ */ new Set();
3608
3827
  let bytes = 0;
3609
3828
  try {
@@ -3614,13 +3833,13 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir2()) {
3614
3833
  bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
3615
3834
  if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
3616
3835
  const target = resolve32(sourceDir, file.path);
3617
- if (!target.startsWith(`${resolve32(sourceDir)}${sep2}`)) throw new TypeError("Code source path escapes its root");
3618
- await mkdir2(dirname4(target), { recursive: true });
3619
- await writeFile(target, file.content, { flag: "wx", mode: 420 });
3836
+ if (!target.startsWith(`${resolve32(sourceDir)}${sep22}`)) throw new TypeError("Code source path escapes its root");
3837
+ await mkdir3(dirname4(target), { recursive: true });
3838
+ await writeFile2(target, file.content, { flag: "wx", mode: 420 });
3620
3839
  }
3621
- return { sourceDir, cleanup: () => rm2(root, { recursive: true, force: true }) };
3840
+ return { sourceDir, cleanup: () => rm3(root, { recursive: true, force: true }) };
3622
3841
  } catch (cause) {
3623
- await rm2(root, { recursive: true, force: true });
3842
+ await rm3(root, { recursive: true, force: true });
3624
3843
  throw cause;
3625
3844
  }
3626
3845
  }
@@ -3726,15 +3945,15 @@ async function environment(input, options, tool) {
3726
3945
  { id: "authority", value: `lease:${input.lease.leaseId}`, readers: input.readers },
3727
3946
  { id: "reader", value: options.readerId, readers: input.readers }
3728
3947
  ]);
3729
- const digest2 = await destinationRegistryDigest([input.workspaceId]);
3948
+ const digest = await destinationRegistryDigest([input.workspaceId]);
3730
3949
  const approvals = ["irreversible_mutation", "external_send", "financial", "secret_read"];
3731
3950
  if (options.recipeAuthorization === "exact_approval") approvals.push("code_execution");
3732
3951
  const policy = createEffectPolicy({
3733
- destinationRegistries: { [DESTINATIONS]: { digest: digest2, values: [input.workspaceId] } },
3952
+ destinationRegistries: { [DESTINATIONS]: { digest, values: [input.workspaceId] } },
3734
3953
  approvalEffects: approvals
3735
3954
  });
3736
3955
  const fixedArgs = {
3737
- workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest2 },
3956
+ workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest },
3738
3957
  authority: { role: "authority", value: ingress.control("authority") }
3739
3958
  };
3740
3959
  return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
@@ -3934,14 +4153,31 @@ function codeCommandMetadata(payload, resume) {
3934
4153
  }
3935
4154
  const planning = trusted?.planningInputDigest;
3936
4155
  const attestation = trusted?.attestationDigest;
4156
+ const repository = trusted?.repository;
4157
+ const baseCommitSha = trusted?.commitSha;
4158
+ const sourceTreeDigest = trusted?.treeDigest;
4159
+ if (typeof repository !== "string" || !repository.includes("/") || typeof baseCommitSha !== "string" || !/^[0-9a-f]{40}$/.test(baseCommitSha) || typeof sourceTreeDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(sourceTreeDigest)) {
4160
+ throw new TypeError(`invalid Code ${resume ? "resume" : "start"} trusted base`);
4161
+ }
3937
4162
  return {
3938
4163
  role,
3939
4164
  title,
3940
4165
  prompt,
3941
4166
  planningInputDigest: typeof planning === "string" && /^sha256:[0-9a-f]{64}$/.test(planning) ? planning : null,
3942
- attestationDigest: typeof attestation === "string" ? attestation : "resume"
4167
+ attestationDigest: typeof attestation === "string" ? attestation : "resume",
4168
+ repository,
4169
+ baseCommitSha,
4170
+ sourceTreeDigest
3943
4171
  };
3944
4172
  }
4173
+ function codeLocalSource(payload) {
4174
+ const source = record22(payload.source);
4175
+ if (!source) return null;
4176
+ if (source.kind !== "local_checkout" || typeof source.repository !== "string" || typeof source.headCommitSha !== "string" || !/^[0-9a-f]{40}$/.test(source.headCommitSha) || typeof source.trustedBaseDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(source.trustedBaseDigest) || typeof source.developerPatchDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(source.developerPatchDigest) || typeof source.snapshotDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(source.snapshotDigest) || typeof source.modified !== "boolean" || !Number.isSafeInteger(source.fileCount) || Number(source.fileCount) < 1 || Number(source.fileCount) > 2e4 || !Number.isSafeInteger(source.byteCount) || Number(source.byteCount) < 1 || Number(source.byteCount) > 512 * 1024 * 1024 || !Number.isSafeInteger(source.capturedAt) || Number(source.capturedAt) < 1) {
4177
+ throw new TypeError("invalid local checkout source descriptor");
4178
+ }
4179
+ return source;
4180
+ }
3945
4181
  function codeCheckpointPayload(payload) {
3946
4182
  const value = payload.checkpoint;
3947
4183
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
@@ -3970,11 +4206,62 @@ function fakeCodeLease(command, metadata2) {
3970
4206
  };
3971
4207
  }
3972
4208
  var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4209
+ var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
4210
+ async function prepareRuntimeLocalSource(input) {
4211
+ const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
4212
+ if (!available || JSON.stringify(available.descriptor) !== JSON.stringify(descriptor2) || descriptor2.repository.toLowerCase() !== repository.toLowerCase() || descriptor2.headCommitSha !== baseCommitSha) {
4213
+ throw new TypeError("the session's local checkout snapshot is not available on this terminal");
4214
+ }
4215
+ const workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4216
+ trustedBaseDir: available.trustedBaseDir,
4217
+ trustedBaseCommitSha: baseCommitSha,
4218
+ checkpoint: codeCheckpointPayload(command.payload)
4219
+ })).workspace : await stageWorkspacePair(available.trustedBaseDir, available.sourceDir, SOURCE_LIMITS);
4220
+ const trustedBaseDigest = await digestStagedWorkspace(workspace.baselineDir, SOURCE_LIMITS);
4221
+ if (trustedBaseDigest !== descriptor2.trustedBaseDigest) {
4222
+ await workspace.cleanup();
4223
+ throw new TypeError("trusted Git base digest changed after connection");
4224
+ }
4225
+ if (!resume && await digestStagedWorkspace(workspace.workspaceDir, SOURCE_LIMITS) !== descriptor2.snapshotDigest) {
4226
+ await workspace.cleanup();
4227
+ throw new TypeError("local checkout snapshot digest changed after connection");
4228
+ }
4229
+ return { workspace, sourceDigest: descriptor2.snapshotDigest, trustedBaseDigest };
4230
+ }
4231
+ function createCodeRuntimeToolBroker(input, lease, role) {
4232
+ const broker = createCodeToolBroker({
4233
+ recipes: input.recipes,
4234
+ recipeExecutor: createContainerRecipeExecutor(input.engine),
4235
+ recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
4236
+ readerId: `code-session:${lease.task.taskId}`
4237
+ });
4238
+ return role === "coding" ? broker : { execute: (context, request) => request.tool === "sandbox.read" ? broker.execute(context, request) : Promise.resolve({ requestId: request.requestId, ok: false, content: "review sessions are read-only" }) };
4239
+ }
4240
+ async function appendCodeRuntimeEvent(control, command, event, refs) {
4241
+ const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
4242
+ refs.push(eventId);
4243
+ const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
4244
+ await control.appendSessionEvent(command.sessionId, eventId, bounded);
4245
+ }
4246
+ var digestRuntimeValue = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
4247
+ var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
4248
+ var runtimeRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4249
+ var safeRuntimeJson = (value) => {
4250
+ try {
4251
+ return JSON.stringify(value).slice(0, 1e4);
4252
+ } catch {
4253
+ return "[event]";
4254
+ }
4255
+ };
4256
+ function runtimeResultText(value) {
4257
+ const record32 = runtimeRecord(value);
4258
+ return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4259
+ }
3973
4260
  var CodePiRuntimeEngine = class {
3974
4261
  constructor(options) {
3975
4262
  this.options = options;
3976
4263
  this.#run = options.runAttempt ?? runContainerAttempt;
3977
- this.#buildPolicyDigest = digest(JSON.stringify(options.recipes));
4264
+ this.#buildPolicyDigest = digestRuntimeValue(JSON.stringify(options.recipes));
3978
4265
  this.#checkpoints = new CodeRuntimeCheckpointManager({
3979
4266
  control: options.control,
3980
4267
  recipes: options.recipes,
@@ -4009,19 +4296,35 @@ var CodePiRuntimeEngine = class {
4009
4296
  }
4010
4297
  async #start(command, resume) {
4011
4298
  if (this.#active.has(command.sessionId)) throw new TypeError("Code session is already active on this runtime");
4012
- const source = await this.options.control.source(command.sessionId);
4013
- const materialized = await materializeCodeRuntimeSource(source);
4299
+ const metadata2 = codeCommandMetadata(command.payload, resume);
4300
+ const requestedLocal = codeLocalSource(command.payload);
4014
4301
  let workspace;
4015
- try {
4016
- workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4017
- trustedBaseDir: materialized.sourceDir,
4018
- trustedBaseCommitSha: source.commitSha,
4019
- checkpoint: codeCheckpointPayload(command.payload)
4020
- })).workspace : await stageWorkspace(materialized.sourceDir);
4021
- } finally {
4022
- await materialized.cleanup();
4302
+ let sourceDigest;
4303
+ let localTrustedBaseDigest;
4304
+ if (requestedLocal) {
4305
+ const prepared = await prepareRuntimeLocalSource({
4306
+ command,
4307
+ descriptor: requestedLocal,
4308
+ available: this.options.localSource,
4309
+ repository: metadata2.repository,
4310
+ baseCommitSha: metadata2.baseCommitSha,
4311
+ resume
4312
+ });
4313
+ ({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
4314
+ } else {
4315
+ const source = await this.options.control.source(command.sessionId);
4316
+ const materialized = await materializeCodeRuntimeSource(source);
4317
+ try {
4318
+ workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4319
+ trustedBaseDir: materialized.sourceDir,
4320
+ trustedBaseCommitSha: source.commitSha,
4321
+ checkpoint: codeCheckpointPayload(command.payload)
4322
+ })).workspace : await stageWorkspace(materialized.sourceDir);
4323
+ } finally {
4324
+ await materialized.cleanup();
4325
+ }
4326
+ sourceDigest = source.treeDigest;
4023
4327
  }
4024
- const metadata2 = codeCommandMetadata(command.payload, resume);
4025
4328
  const abort = new AbortController();
4026
4329
  const conversationRefs = [];
4027
4330
  const active = {
@@ -4031,21 +4334,30 @@ var CodePiRuntimeEngine = class {
4031
4334
  acknowledged: false,
4032
4335
  role: metadata2.role,
4033
4336
  title: metadata2.title,
4034
- baseCommitSha: source.commitSha,
4035
- trustedBaseDigest: await digestStagedWorkspace(workspace.baselineDir, {
4337
+ baseCommitSha: metadata2.baseCommitSha,
4338
+ repository: metadata2.repository,
4339
+ sourceTreeDigest: metadata2.sourceTreeDigest,
4340
+ trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
4036
4341
  maxFiles: 1e4,
4037
4342
  maxBytes: 16 * 1024 * 1024
4038
4343
  }),
4039
- planningInputDigest: metadata2.planningInputDigest ?? digest(
4040
- JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: source.treeDigest })
4344
+ planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
4345
+ JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })
4041
4346
  ),
4042
4347
  done: Promise.resolve(null)
4043
4348
  };
4044
4349
  this.#active.set(command.sessionId, active);
4350
+ if (requestedLocal) {
4351
+ await this.#event(command, {
4352
+ type: "message",
4353
+ actor: "system",
4354
+ body: `Source snapshot: local checkout ${requestedLocal.snapshotDigest} \xB7 ${requestedLocal.modified ? "modified" : "clean"} \xB7 Git ${requestedLocal.headCommitSha}`
4355
+ }, conversationRefs);
4356
+ }
4045
4357
  active.done = this.#runAttempt(command, metadata2, active).catch(async (cause) => {
4046
- await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` }, conversationRefs).catch(() => void 0);
4358
+ await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` }, conversationRefs).catch(() => void 0);
4047
4359
  await this.#event(command, { type: "status", status: "failed" }, conversationRefs).catch(() => void 0);
4048
- await this.#failure(command, active, message2(cause));
4360
+ await this.#failure(command, active, runtimeErrorMessage(cause));
4049
4361
  return null;
4050
4362
  });
4051
4363
  return { status: "running", message: resume ? "Pi resumed from a portable checkpoint" : "Pi started" };
@@ -4065,22 +4377,29 @@ var CodePiRuntimeEngine = class {
4065
4377
  title: active.title,
4066
4378
  prompt,
4067
4379
  planningInputDigest: active.planningInputDigest,
4068
- attestationDigest: "follow-up"
4380
+ attestationDigest: "follow-up",
4381
+ repository: active.repository,
4382
+ baseCommitSha: active.baseCommitSha,
4383
+ sourceTreeDigest: active.sourceTreeDigest
4069
4384
  }, active).catch(async (cause) => {
4070
4385
  await this.#event(
4071
4386
  command,
4072
- { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` },
4387
+ { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` },
4073
4388
  active.conversationRefs
4074
4389
  ).catch(() => void 0);
4075
4390
  await this.#event(command, { type: "status", status: "failed" }, active.conversationRefs).catch(() => void 0);
4076
- await this.#failure(command, active, message2(cause));
4391
+ await this.#failure(command, active, runtimeErrorMessage(cause));
4077
4392
  return null;
4078
4393
  });
4079
4394
  return { status: "running", message: "Pi accepted the owner prompt" };
4080
4395
  }
4081
4396
  async #runAttempt(command, metadata2, active) {
4082
4397
  const lease = fakeCodeLease(command, metadata2);
4083
- const broker = this.#toolBroker(lease, metadata2.role);
4398
+ const broker = createCodeRuntimeToolBroker({
4399
+ recipes: this.options.recipes,
4400
+ engine: this.options.engine,
4401
+ recipeAuthorization: this.options.recipeAuthorization
4402
+ }, lease, metadata2.role);
4084
4403
  const startedAt = Date.now();
4085
4404
  let completionSeen = false;
4086
4405
  const result = await this.#run({
@@ -4140,7 +4459,7 @@ var CodePiRuntimeEngine = class {
4140
4459
  return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
4141
4460
  }
4142
4461
  if (output.type === "event") {
4143
- const payload = object2(output.payload);
4462
+ const payload = runtimeRecord(output.payload);
4144
4463
  if (output.kind === "pi.started") {
4145
4464
  await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
4146
4465
  } else if (output.kind === "pi.thinking" && payload?.available === true && Number.isSafeInteger(payload.durationMs) && Number(payload.durationMs) >= 0) {
@@ -4153,12 +4472,12 @@ var CodePiRuntimeEngine = class {
4153
4472
  await this.#event(command, {
4154
4473
  type: "message",
4155
4474
  actor: "system",
4156
- body: `${output.kind}${output.payload === void 0 ? "" : ` ${json(output.payload)}`}`
4475
+ body: `${output.kind}${output.payload === void 0 ? "" : ` ${safeRuntimeJson(output.payload)}`}`
4157
4476
  }, active.conversationRefs);
4158
4477
  }
4159
4478
  } else if (output.type === "attempt.complete") {
4160
4479
  completionSeen = true;
4161
- const body = resultText(output.result) ?? `Pi ${output.status}.`;
4480
+ const body = runtimeResultText(output.result) ?? `Pi ${output.status}.`;
4162
4481
  await this.#event(command, {
4163
4482
  type: "message",
4164
4483
  actor: output.status === "completed" ? "agent" : "system",
@@ -4185,15 +4504,6 @@ var CodePiRuntimeEngine = class {
4185
4504
  }
4186
4505
  return result;
4187
4506
  }
4188
- #toolBroker(lease, role) {
4189
- const broker = createCodeToolBroker({
4190
- recipes: this.options.recipes,
4191
- recipeExecutor: createContainerRecipeExecutor(this.options.engine),
4192
- recipeAuthorization: this.options.recipeAuthorization ?? "registered_recipe",
4193
- readerId: `code-session:${lease.task.taskId}`
4194
- });
4195
- return role === "coding" ? broker : { execute: (context, request) => request.tool === "sandbox.read" ? broker.execute(context, request) : Promise.resolve({ requestId: request.requestId, ok: false, content: "review sessions are read-only" }) };
4196
- }
4197
4507
  async #checkpoint(command) {
4198
4508
  const active = this.#active.get(command.sessionId);
4199
4509
  if (!active) throw new TypeError("Code session workspace is not active on this runtime");
@@ -4208,26 +4518,9 @@ var CodePiRuntimeEngine = class {
4208
4518
  }
4209
4519
  }
4210
4520
  async #event(command, event, refs) {
4211
- const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
4212
- refs.push(eventId);
4213
- const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
4214
- await this.options.control.appendSessionEvent(command.sessionId, eventId, bounded);
4215
- }
4216
- };
4217
- var object2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4218
- var digest = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
4219
- var message2 = (value) => value instanceof Error ? value.message : String(value);
4220
- var json = (value) => {
4221
- try {
4222
- return JSON.stringify(value).slice(0, 1e4);
4223
- } catch {
4224
- return "[event]";
4521
+ await appendCodeRuntimeEvent(this.options.control, command, event, refs);
4225
4522
  }
4226
4523
  };
4227
- function resultText(value) {
4228
- const record32 = object2(value);
4229
- return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4230
- }
4231
4524
 
4232
4525
  // src/help.ts
4233
4526
  import { readFileSync as readFileSync3 } from "fs";
@@ -4345,24 +4638,90 @@ Safety:
4345
4638
  `);
4346
4639
  }
4347
4640
 
4641
+ // src/code-local-source.ts
4642
+ import { execFile as execFile3 } from "child_process";
4643
+ import { createHash as createHash4 } from "crypto";
4644
+ var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
4645
+ async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
4646
+ const headCommitSha = await readHead(cwd);
4647
+ const staged = await stageWorkspace(cwd, { ...SOURCE_LIMITS2, gitTrackedAndUnignored: true });
4648
+ let trusted;
4649
+ try {
4650
+ trusted = await materializeGitTree(cwd, headCommitSha, SOURCE_LIMITS2);
4651
+ const comparison = await stageWorkspacePair(trusted.sourceDir, staged.baselineDir, SOURCE_LIMITS2);
4652
+ let developerPatch;
4653
+ try {
4654
+ developerPatch = await comparison.patch(4 * 1024 * 1024);
4655
+ } finally {
4656
+ await comparison.cleanup();
4657
+ }
4658
+ const descriptor2 = {
4659
+ kind: "local_checkout",
4660
+ repository,
4661
+ headCommitSha,
4662
+ trustedBaseDigest: await digestStagedWorkspace(trusted.sourceDir, SOURCE_LIMITS2),
4663
+ developerPatchDigest: digestText(developerPatch),
4664
+ snapshotDigest: await digestStagedWorkspace(staged.baselineDir, SOURCE_LIMITS2),
4665
+ modified: developerPatch.length > 0,
4666
+ fileCount: staged.fileCount,
4667
+ byteCount: staged.byteCount,
4668
+ capturedAt: Date.now()
4669
+ };
4670
+ return {
4671
+ descriptor: descriptor2,
4672
+ trustedBaseDir: trusted.sourceDir,
4673
+ sourceDir: staged.baselineDir,
4674
+ cleanup: async () => {
4675
+ await Promise.all([staged.cleanup(), trusted.cleanup()]);
4676
+ }
4677
+ };
4678
+ } catch (error) {
4679
+ await Promise.all([staged.cleanup(), trusted?.cleanup()]);
4680
+ throw error;
4681
+ }
4682
+ }
4683
+ async function readGitHead(cwd) {
4684
+ const value = await new Promise((accept, reject) => {
4685
+ execFile3("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
4686
+ if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
4687
+ else accept(stdout.trim());
4688
+ });
4689
+ });
4690
+ if (!/^[0-9a-f]{40}$/.test(value)) throw new Error("code connect could not resolve the checkout HEAD commit");
4691
+ return value;
4692
+ }
4693
+ function digestText(value) {
4694
+ return `sha256:${createHash4("sha256").update(value).digest("hex")}`;
4695
+ }
4696
+
4697
+ // src/code-images.ts
4698
+ import { execFile as execFile4 } from "child_process";
4699
+ async function prepareCodeImages(engine, images) {
4700
+ for (const image of images) {
4701
+ await new Promise((accept, reject) => {
4702
+ const args = engine === "container" ? ["image", "pull", image] : ["pull", image];
4703
+ execFile4(engine, args, { encoding: "utf8", maxBuffer: 2 * 1024 * 1024, timeout: 10 * 6e4 }, (error) => {
4704
+ if (error) reject(new Error(`could not prepare pinned Code image ${image}`));
4705
+ else accept();
4706
+ });
4707
+ });
4708
+ }
4709
+ }
4710
+
4348
4711
  // src/code-connect.ts
4349
- var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:713db58428bc05cb486167c5747de8fd826669c9b283ff78e0139ac194c3d058";
4350
- var CODE_BUILD_RECIPES = Object.freeze([{
4351
- id: "node-test",
4352
- image: "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd",
4353
- command: ["node", "--test"],
4354
- timeoutMs: 12e4,
4355
- maxOutputBytes: 1024 * 1024,
4356
- cpus: 1,
4357
- memory: "512m",
4358
- pids: 128
4359
- }]);
4360
4712
  async function codeConnect(options) {
4361
4713
  const cwd = options.cwd ?? process.cwd();
4362
4714
  const configPath = resolve5(cwd, options.configPath);
4363
4715
  const cfg = existsSync4(configPath) ? await loadProjectConfig(configPath) : null;
4364
- const repository = cfg ? null : await inferGitHubRepository(cwd, options.readGitOrigin);
4365
- const platform = cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai";
4716
+ const requestedAppId = options.appId?.trim();
4717
+ if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
4718
+ throw new Error("--app-id must be a valid odla app id");
4719
+ }
4720
+ if (requestedAppId && cfg && requestedAppId !== cfg.app.id) {
4721
+ throw new Error(`--app-id ${requestedAppId} does not match ${options.configPath} (${cfg.app.id})`);
4722
+ }
4723
+ const appId = requestedAppId ?? cfg?.app.id;
4724
+ const platform = (options.platform ?? cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai").replace(/\/+$/, "");
4366
4725
  const appEnv = options.env ?? (cfg ? cfg.envs.includes("dev") ? "dev" : cfg.envs[0] : "dev");
4367
4726
  if (appEnv !== "dev" && appEnv !== "prod" || cfg && !cfg.envs.includes(appEnv)) {
4368
4727
  throw new Error(cfg ? `Code env "${appEnv ?? ""}" must be dev or prod and declared in ${options.configPath}` : `Code env "${appEnv ?? ""}" must be dev or prod`);
@@ -4381,53 +4740,77 @@ async function codeConnect(options) {
4381
4740
  const out = options.stdout ?? console;
4382
4741
  const doFetch = options.fetch ?? fetch;
4383
4742
  const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
4743
+ await (options.prepareImages ?? prepareCodeImages)(engine, [
4744
+ CODE_PI_IMAGE,
4745
+ ...new Set(CODE_BUILD_RECIPES.map((recipe2) => recipe2.image))
4746
+ ]);
4384
4747
  const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
4385
4748
  const hostName = (options.name ?? hostname()).trim();
4386
4749
  if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
4387
- const approval = await (options.getToken ?? getScopedPlatformToken)({
4388
- platform,
4389
- scope: "platform:code:host:connect",
4390
- email: options.email,
4391
- open: options.open,
4392
- fetch: doFetch,
4393
- stdout: out,
4394
- openApprovalUrl: options.openApprovalUrl,
4395
- cache: false,
4396
- label: `odla CLI Code host for ${cfg?.app.id ?? repository}/${appEnv}`
4397
- });
4398
- const target = cfg ? { appId: cfg.app.id } : { repository };
4399
- const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
4400
- method: "POST",
4401
- headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
4402
- body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
4403
- redirect: "error",
4404
- signal: options.signal
4405
- });
4406
- const raw = await response2.json().catch(() => null);
4407
- if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
4408
- const connection = parseConnection(raw, cfg?.app.id, appEnv);
4409
- const capabilities = {
4410
- protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
4411
- platform: hostPlatform,
4412
- arch: process.arch,
4413
- engines: [engine],
4414
- cpuCount: cpus().length,
4415
- memoryBytes: totalmem()
4416
- };
4417
- out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
4418
- out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
4419
- out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
4420
- await (options.runRuntime ?? runCodeRuntime)({
4421
- endpoint: platform,
4422
- token: connection.token,
4423
- engine,
4424
- capabilities,
4425
- heartbeatMs,
4426
- once: options.once === true,
4427
- signal: options.signal,
4428
- stdout: out,
4429
- fetch: doFetch
4430
- });
4750
+ const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
4751
+ const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
4752
+ cwd,
4753
+ repository,
4754
+ options.readGitHead
4755
+ );
4756
+ try {
4757
+ const descriptor2 = localSource.descriptor;
4758
+ const approval = await (options.getToken ?? getScopedPlatformToken)({
4759
+ platform,
4760
+ scope: "app:code:host:connect",
4761
+ email: options.email,
4762
+ open: options.open,
4763
+ fetch: doFetch,
4764
+ stdout: out,
4765
+ openApprovalUrl: options.openApprovalUrl,
4766
+ cache: false,
4767
+ label: `Terminal connection for ${appId ?? repository}/${appEnv}`
4768
+ });
4769
+ const target = appId ? { appId } : { repository };
4770
+ const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
4771
+ method: "POST",
4772
+ headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
4773
+ body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
4774
+ redirect: "error",
4775
+ signal: options.signal
4776
+ });
4777
+ const raw = await response2.json().catch(() => null);
4778
+ if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
4779
+ const connection = parseConnection(raw, appId, appEnv);
4780
+ const capabilities = {
4781
+ protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
4782
+ platform: hostPlatform,
4783
+ arch: process.arch,
4784
+ engines: [engine],
4785
+ cpuCount: cpus().length,
4786
+ memoryBytes: totalmem(),
4787
+ source: descriptor2,
4788
+ images: {
4789
+ ready: true,
4790
+ pi: CODE_PI_IMAGE,
4791
+ recipes: CODE_BUILD_RECIPES.map((recipe2) => ({ id: recipe2.id, image: recipe2.image }))
4792
+ }
4793
+ };
4794
+ out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
4795
+ out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
4796
+ out.log(`source: local checkout \xB7 ${descriptor2.headCommitSha.slice(0, 10)} \xB7 ${descriptor2.fileCount} files \xB7 staged copy`);
4797
+ out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
4798
+ out.log(`Studio: ${platform}/studio/apps/${encodeURIComponent(connection.binding.appId)}/${connection.binding.env}/code/sessions`);
4799
+ await (options.runRuntime ?? runCodeRuntime)({
4800
+ endpoint: platform,
4801
+ token: connection.token,
4802
+ engine,
4803
+ capabilities,
4804
+ localSource,
4805
+ heartbeatMs,
4806
+ once: options.once === true,
4807
+ signal: options.signal,
4808
+ stdout: out,
4809
+ fetch: doFetch
4810
+ });
4811
+ } finally {
4812
+ await localSource.cleanup();
4813
+ }
4431
4814
  }
4432
4815
  async function runCodeRuntime(input) {
4433
4816
  const controller = new AbortController();
@@ -4450,7 +4833,8 @@ async function runCodeRuntime(input) {
4450
4833
  engine: input.engine,
4451
4834
  image: CODE_PI_IMAGE,
4452
4835
  recipes: CODE_BUILD_RECIPES,
4453
- recipeAuthorization: "registered_recipe"
4836
+ recipeAuthorization: "registered_recipe",
4837
+ localSource: input.localSource
4454
4838
  });
4455
4839
  const reconciler = new CodeRuntimeReconciler(control, commandEngine);
4456
4840
  try {
@@ -4491,8 +4875,8 @@ function parseConnection(value, appId, appEnv) {
4491
4875
  return root;
4492
4876
  }
4493
4877
  function apiFailure(action, status, value) {
4494
- const message3 = record4(record4(value)?.error)?.message;
4495
- return `${action} failed (${status})${typeof message3 === "string" ? `: ${message3}` : ""}`;
4878
+ const message2 = record4(record4(value)?.error)?.message;
4879
+ return `${action} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
4496
4880
  }
4497
4881
  function record4(value) {
4498
4882
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -4501,14 +4885,14 @@ function record4(value) {
4501
4885
  // src/doctor-checks.ts
4502
4886
  import { execFileSync } from "child_process";
4503
4887
  import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
4504
- import { join as join5, resolve as resolve6 } from "path";
4888
+ import { join as join6, resolve as resolve6 } from "path";
4505
4889
 
4506
4890
  // src/wrangler.ts
4507
- import { spawn as spawn4 } from "child_process";
4891
+ import { spawn as spawn5 } from "child_process";
4508
4892
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
4509
- import { join as join4 } from "path";
4893
+ import { join as join5 } from "path";
4510
4894
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
4511
- const child = spawn4(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4895
+ const child = spawn5(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4512
4896
  let stdout = "";
4513
4897
  let stderr = "";
4514
4898
  child.stdout.on("data", (chunk) => stdout += chunk.toString());
@@ -4520,7 +4904,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
4520
4904
  var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
4521
4905
  function findWranglerConfig(rootDir) {
4522
4906
  for (const name of WRANGLER_CONFIG_FILES) {
4523
- const path = join4(rootDir, name);
4907
+ const path = join5(rootDir, name);
4524
4908
  if (existsSync5(path)) return path;
4525
4909
  }
4526
4910
  return null;
@@ -4629,7 +5013,7 @@ function wranglerWarnings(rootDir) {
4629
5013
  const dir = resolve6(rootDir, assets.directory);
4630
5014
  if (dir === resolve6(rootDir)) {
4631
5015
  warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
4632
- } else if (existsSync6(join5(dir, "node_modules"))) {
5016
+ } else if (existsSync6(join6(dir, "node_modules"))) {
4633
5017
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
4634
5018
  }
4635
5019
  }
@@ -4694,7 +5078,7 @@ function calendarProjectWarnings(rootDir) {
4694
5078
  }
4695
5079
  function readPackageJson(rootDir) {
4696
5080
  try {
4697
- return JSON.parse(readFileSync5(join5(rootDir, "package.json"), "utf8"));
5081
+ return JSON.parse(readFileSync5(join6(rootDir, "package.json"), "utf8"));
4698
5082
  } catch {
4699
5083
  return null;
4700
5084
  }
@@ -5079,7 +5463,7 @@ function assertWranglerConfig(cfg) {
5079
5463
  }
5080
5464
 
5081
5465
  // src/provision.ts
5082
- import { createAppsClient, tenantIdFor as tenantIdFor2 } from "@odla-ai/apps";
5466
+ import { createAppsClient, tenantIdFor as tenantIdFor3 } from "@odla-ai/apps";
5083
5467
  import { putSecret } from "@odla-ai/ai";
5084
5468
  import process8 from "process";
5085
5469
 
@@ -5232,6 +5616,7 @@ async function safeText3(res) {
5232
5616
 
5233
5617
  // src/provision-helpers.ts
5234
5618
  import { DEFAULT_SECRET_NAMES } from "@odla-ai/ai";
5619
+ import { tenantIdFor as tenantIdFor2 } from "@odla-ai/apps";
5235
5620
  function serviceRank(service) {
5236
5621
  if (service === "db") return 0;
5237
5622
  if (service === "calendar") return 2;
@@ -5241,6 +5626,54 @@ function defaultSecretName(provider) {
5241
5626
  const names = DEFAULT_SECRET_NAMES;
5242
5627
  return names[provider] ?? `${provider}_api_key`;
5243
5628
  }
5629
+ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
5630
+ const tenantId = tenantIdFor2(cfg.app.id, env);
5631
+ const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
5632
+ headers: { authorization: `Bearer ${token}` }
5633
+ });
5634
+ if (res.ok || res.status === 404) return;
5635
+ if (res.status === 403) {
5636
+ throw new Error(
5637
+ `${env}: you are not an owner of "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; ask an existing owner to run "odla-ai app owners add <your-email>", then re-run provision`
5638
+ );
5639
+ }
5640
+ throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText4(res)}`);
5641
+ }
5642
+ async function readRegistryApp(cfg, token, doFetch) {
5643
+ const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}`, {
5644
+ headers: { authorization: `Bearer ${token}` }
5645
+ });
5646
+ if (res.status === 404) return null;
5647
+ if (!res.ok) return null;
5648
+ const json = await res.json();
5649
+ return json.app ?? null;
5650
+ }
5651
+ async function postJson2(doFetch, url, bearer, body) {
5652
+ const res = await doFetch(url, {
5653
+ method: "POST",
5654
+ headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
5655
+ body: JSON.stringify(body)
5656
+ });
5657
+ if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText4(res)}`);
5658
+ }
5659
+ function normalizeClerkConfig(value) {
5660
+ if (!value) return null;
5661
+ if (typeof value === "string") {
5662
+ const publishableKey2 = envValue(value);
5663
+ return publishableKey2 ? { publishableKey: publishableKey2 } : null;
5664
+ }
5665
+ if (typeof value !== "object") return null;
5666
+ const cfg = value;
5667
+ const publishableKey = envValue(cfg.publishableKey);
5668
+ return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
5669
+ }
5670
+ async function safeText4(res) {
5671
+ try {
5672
+ return redactSecrets((await res.text()).slice(0, 500));
5673
+ } catch {
5674
+ return "";
5675
+ }
5676
+ }
5244
5677
 
5245
5678
  // src/provision.ts
5246
5679
  async function provision(options) {
@@ -5320,6 +5753,9 @@ async function provision(options) {
5320
5753
  await apps.createApp({ name: cfg.app.name, appId: cfg.app.id });
5321
5754
  out.log(`app: created ${cfg.app.id}`);
5322
5755
  }
5756
+ for (const env of cfg.envs) {
5757
+ await assertTenantAdminAccess(doFetch, cfg, env, token);
5758
+ }
5323
5759
  const serviceOrder = [...cfg.services].sort((a, b) => serviceRank(a) - serviceRank(b));
5324
5760
  for (const env of cfg.envs) {
5325
5761
  for (const service of serviceOrder) {
@@ -5353,7 +5789,7 @@ async function provision(options) {
5353
5789
  }
5354
5790
  }
5355
5791
  for (const env of cfg.envs) {
5356
- const tenantId = tenantIdFor2(cfg.app.id, env);
5792
+ const tenantId = tenantIdFor3(cfg.app.id, env);
5357
5793
  credentials = await provisionEnvCredentials({
5358
5794
  cfg,
5359
5795
  env,
@@ -5376,10 +5812,10 @@ async function provision(options) {
5376
5812
  stdout: out
5377
5813
  });
5378
5814
  } catch (error) {
5379
- const message3 = error instanceof Error ? error.message : String(error);
5815
+ const message2 = error instanceof Error ? error.message : String(error);
5380
5816
  const consent = env === "prod" || env === "production" ? " --yes" : "";
5381
5817
  throw new Error(
5382
- `${message3}
5818
+ `${message2}
5383
5819
  ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}${consent}" without issuing or rotating again`,
5384
5820
  { cause: error }
5385
5821
  );
@@ -5436,45 +5872,10 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
5436
5872
  }
5437
5873
  }
5438
5874
  }
5439
- async function readRegistryApp(cfg, token, doFetch) {
5440
- const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}`, {
5441
- headers: { authorization: `Bearer ${token}` }
5442
- });
5443
- if (res.status === 404) return null;
5444
- if (!res.ok) return null;
5445
- const json2 = await res.json();
5446
- return json2.app ?? null;
5447
- }
5448
- async function postJson2(doFetch, url, bearer, body) {
5449
- const res = await doFetch(url, {
5450
- method: "POST",
5451
- headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
5452
- body: JSON.stringify(body)
5453
- });
5454
- if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText4(res)}`);
5455
- }
5456
- function normalizeClerkConfig(value) {
5457
- if (!value) return null;
5458
- if (typeof value === "string") {
5459
- const publishableKey2 = envValue(value);
5460
- return publishableKey2 ? { publishableKey: publishableKey2 } : null;
5461
- }
5462
- if (typeof value !== "object") return null;
5463
- const cfg = value;
5464
- const publishableKey = envValue(cfg.publishableKey);
5465
- return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
5466
- }
5467
- async function safeText4(res) {
5468
- try {
5469
- return redactSecrets((await res.text()).slice(0, 500));
5470
- } catch {
5471
- return "";
5472
- }
5473
- }
5474
5875
 
5475
5876
  // src/secrets-set.ts
5476
5877
  import { putSecret as putSecret2 } from "@odla-ai/ai";
5477
- import { tenantIdFor as tenantIdFor3 } from "@odla-ai/apps";
5878
+ import { tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
5478
5879
  var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
5479
5880
  async function secretsSet(options) {
5480
5881
  const name = (options.name ?? "").trim();
@@ -5526,11 +5927,11 @@ async function resolveVaultWrite(options) {
5526
5927
  throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
5527
5928
  }
5528
5929
  const value = await secretInputValue(options, "secret");
5529
- return { cfg, tenantId: tenantIdFor3(cfg.app.id, options.env), value, doFetch, out };
5930
+ return { cfg, tenantId: tenantIdFor4(cfg.app.id, options.env), value, doFetch, out };
5530
5931
  }
5531
5932
 
5532
5933
  // src/security.ts
5533
- import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve8, sep as sep3 } from "path";
5934
+ import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve8, sep as sep4 } from "path";
5534
5935
  import {
5535
5936
  cloudflareAppProfile,
5536
5937
  createPlatformSecurityReasoners,
@@ -5551,7 +5952,7 @@ async function runHostedSecurity(options) {
5551
5952
  const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
5552
5953
  const target = resolve8(options.target ?? cfg?.rootDir ?? ".");
5553
5954
  const output = resolve8(options.out ?? resolve8(target, ".odla/security/hosted"));
5554
- const outputRelative = relative4(target, output).split(sep3).join("/");
5955
+ const outputRelative = relative4(target, output).split(sep4).join("/");
5555
5956
  if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
5556
5957
  const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
5557
5958
  const tokenRequest = {
@@ -5563,7 +5964,7 @@ async function runHostedSecurity(options) {
5563
5964
  };
5564
5965
  const token = await injectedToken(options, tokenRequest);
5565
5966
  const snapshot = await snapshotDirectory(target, {
5566
- exclude: !outputRelative.startsWith("../") && !isAbsolute3(outputRelative) ? [outputRelative] : []
5967
+ exclude: !outputRelative.startsWith("../") && !isAbsolute4(outputRelative) ? [outputRelative] : []
5567
5968
  });
5568
5969
  const hosted = await createPlatformSecurityReasoners({
5569
5970
  platform,
@@ -5724,8 +6125,8 @@ async function startHostedSecurityJob(options) {
5724
6125
  "start hosted security job"
5725
6126
  );
5726
6127
  } catch (error) {
5727
- const message3 = error instanceof Error ? error.message : String(error);
5728
- if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message3)) {
6128
+ const message2 = error instanceof Error ? error.message : String(error);
6129
+ if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message2)) {
5729
6130
  throw new Error("Hosted security plan or execution intent changed or became unavailable after review; fetch a fresh intent and explicitly acknowledge its digest before enqueueing again", { cause: error });
5730
6131
  }
5731
6132
  throw error;
@@ -5806,7 +6207,7 @@ function securityExecutionDigest(value) {
5806
6207
  // src/skill.ts
5807
6208
  import { existsSync as existsSync8, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync, writeFileSync as writeFileSync3 } from "fs";
5808
6209
  import { homedir } from "os";
5809
- import { dirname as dirname6, isAbsolute as isAbsolute4, join as join6, relative as relative5, resolve as resolve9, sep as sep4 } from "path";
6210
+ import { dirname as dirname6, isAbsolute as isAbsolute5, join as join7, relative as relative5, resolve as resolve9, sep as sep5 } from "path";
5810
6211
  import { fileURLToPath } from "url";
5811
6212
 
5812
6213
  // src/skill-adapters.ts
@@ -5882,12 +6283,12 @@ function installSkill(options = {}) {
5882
6283
  plans.set(target, { target, content, boundary, managedMerge });
5883
6284
  };
5884
6285
  const planSkillTree = (targetDir2, boundary = root) => {
5885
- for (const rel of files) plan(join6(targetDir2, rel), readFileSync6(join6(sourceDir, rel), "utf8"), false, boundary);
6286
+ for (const rel of files) plan(join7(targetDir2, rel), readFileSync6(join7(sourceDir, rel), "utf8"), false, boundary);
5886
6287
  };
5887
6288
  let targetDir;
5888
6289
  if (options.global) {
5889
- const claudeRoot = join6(home, ".claude", "skills");
5890
- const codexRoot = resolve9(options.codexHomeDir ?? process.env.CODEX_HOME ?? join6(home, ".codex"), "skills");
6290
+ const claudeRoot = join7(home, ".claude", "skills");
6291
+ const codexRoot = resolve9(options.codexHomeDir ?? process.env.CODEX_HOME ?? join7(home, ".codex"), "skills");
5891
6292
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
5892
6293
  for (const harness of harnesses) {
5893
6294
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
@@ -5895,35 +6296,35 @@ function installSkill(options = {}) {
5895
6296
  rememberTarget(harness, skillRoot);
5896
6297
  }
5897
6298
  } else {
5898
- const sharedRoot = join6(root, ".agents", "skills");
6299
+ const sharedRoot = join7(root, ".agents", "skills");
5899
6300
  planSkillTree(sharedRoot);
5900
- const claudeRoot = join6(root, ".claude", "skills");
6301
+ const claudeRoot = join7(root, ".claude", "skills");
5901
6302
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
5902
6303
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
5903
6304
  if (harnesses.includes("claude")) {
5904
6305
  for (const skill of skillNames(files)) {
5905
- const canonical = readFileSync6(join6(sourceDir, skill, "SKILL.md"), "utf8");
5906
- plan(join6(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
6306
+ const canonical = readFileSync6(join7(sourceDir, skill, "SKILL.md"), "utf8");
6307
+ plan(join7(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
5907
6308
  }
5908
6309
  rememberTarget("claude", claudeRoot);
5909
6310
  }
5910
6311
  if (harnesses.includes("cursor")) {
5911
- const cursorRule = join6(root, ".cursor", "rules", "odla.mdc");
6312
+ const cursorRule = join7(root, ".cursor", "rules", "odla.mdc");
5912
6313
  plan(cursorRule, CURSOR_RULE);
5913
6314
  rememberTarget("cursor", cursorRule);
5914
6315
  }
5915
6316
  if (harnesses.includes("agents")) {
5916
- const agentsFile = join6(root, "AGENTS.md");
6317
+ const agentsFile = join7(root, "AGENTS.md");
5917
6318
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5918
6319
  rememberTarget("agents", agentsFile);
5919
6320
  }
5920
6321
  if (harnesses.includes("copilot")) {
5921
- const copilotFile = join6(root, ".github", "copilot-instructions.md");
6322
+ const copilotFile = join7(root, ".github", "copilot-instructions.md");
5922
6323
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5923
6324
  rememberTarget("copilot", copilotFile);
5924
6325
  }
5925
6326
  if (harnesses.includes("gemini")) {
5926
- const geminiFile = join6(root, "GEMINI.md");
6327
+ const geminiFile = join7(root, "GEMINI.md");
5927
6328
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5928
6329
  rememberTarget("gemini", geminiFile);
5929
6330
  }
@@ -5979,7 +6380,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
5979
6380
  };
5980
6381
  }
5981
6382
  function pathsUnder(root, paths) {
5982
- return [...paths].map((path) => relative5(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute4(path)).sort();
6383
+ return [...paths].map((path) => relative5(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep5}`) && !isAbsolute5(path)).sort();
5983
6384
  }
5984
6385
  function normalizeHarnesses(values, global) {
5985
6386
  const requested = values?.length ? values : ["claude"];
@@ -6025,12 +6426,12 @@ function managedFileContent(path, block, force, boundary) {
6025
6426
  }
6026
6427
  function symlinkedComponent(boundary, target) {
6027
6428
  const rel = relative5(boundary, target);
6028
- if (rel === ".." || rel.startsWith(`..${sep4}`) || isAbsolute4(rel)) {
6429
+ if (rel === ".." || rel.startsWith(`..${sep5}`) || isAbsolute5(rel)) {
6029
6430
  throw new Error(`agent setup target escapes its install root: ${target}`);
6030
6431
  }
6031
6432
  let current = boundary;
6032
- for (const part of rel.split(sep4).filter(Boolean)) {
6033
- current = join6(current, part);
6433
+ for (const part of rel.split(sep5).filter(Boolean)) {
6434
+ current = join7(current, part);
6034
6435
  try {
6035
6436
  if (lstatSync(current).isSymbolicLink()) return current;
6036
6437
  } catch (error) {
@@ -6047,7 +6448,7 @@ function listFiles(dir) {
6047
6448
  const results = [];
6048
6449
  const walk = (current) => {
6049
6450
  for (const entry of readdirSync(current, { withFileTypes: true })) {
6050
- const path = join6(current, entry.name);
6451
+ const path = join7(current, entry.name);
6051
6452
  if (entry.isDirectory()) walk(path);
6052
6453
  else results.push(relative5(dir, path));
6053
6454
  }
@@ -6538,6 +6939,8 @@ async function codeCommand(parsed, dependencies) {
6538
6939
  assertArgs(parsed, [
6539
6940
  "config",
6540
6941
  "env",
6942
+ "platform",
6943
+ "app-id",
6541
6944
  "email",
6542
6945
  "open",
6543
6946
  "name",
@@ -6553,6 +6956,8 @@ async function codeCommand(parsed, dependencies) {
6553
6956
  await (dependencies.codeConnect ?? codeConnect)({
6554
6957
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
6555
6958
  env: stringOpt(parsed.options.env),
6959
+ platform: stringOpt(parsed.options.platform),
6960
+ appId: stringOpt(parsed.options["app-id"]),
6556
6961
  email: stringOpt(parsed.options.email),
6557
6962
  open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
6558
6963
  name: stringOpt(parsed.options.name),
@@ -6610,12 +7015,12 @@ async function hostedSecurityContext(parsed, dependencies) {
6610
7015
  );
6611
7016
  return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
6612
7017
  }
6613
- async function interactiveConfirmation(message3, dependencies) {
6614
- if (dependencies.confirm) return dependencies.confirm(message3);
7018
+ async function interactiveConfirmation(message2, dependencies) {
7019
+ if (dependencies.confirm) return dependencies.confirm(message2);
6615
7020
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
6616
7021
  const prompt = createInterface({ input: process.stdin, output: process.stdout });
6617
7022
  try {
6618
- const answer = await prompt.question(`${message3} [y/N] `);
7023
+ const answer = await prompt.question(`${message2} [y/N] `);
6619
7024
  return /^y(?:es)?$/i.test(answer.trim());
6620
7025
  } finally {
6621
7026
  prompt.close();
@@ -6966,16 +7371,16 @@ async function githubSecurityCommand(parsed, dependencies) {
6966
7371
  }
6967
7372
  assertArgs(parsed, ["config", "env", "platform", "repo", "email", "open"], 3);
6968
7373
  const context = await hostedSecurityContext(parsed, dependencies);
6969
- const repository = stringOpt(parsed.options.repo) ?? await inferGitHubRepository(process.cwd(), dependencies.readGitOrigin);
7374
+ const repository = stringOpt(parsed.options.repo) ?? await inferGitHubRepository(process.cwd(), dependencies.readGitOrigin).catch(() => void 0);
6970
7375
  const connection = await connectGitHubSecuritySource({
6971
7376
  ...context,
6972
- repository,
7377
+ ...repository === void 0 ? {} : { repository },
6973
7378
  open: parsed.options.open !== false,
6974
7379
  openInstallUrl: dependencies.openUrl ?? openUrl,
6975
7380
  wait: dependencies.pollWait,
6976
7381
  stdout: context.stdout
6977
7382
  });
6978
- context.stdout.log(`github: connected ${connection.repository ?? repository} (${connection.sourceId ?? "source pending"})`);
7383
+ context.stdout.log(`github: connected ${connection.repository ?? repository ?? "app repository"} (${connection.sourceId ?? "source pending"})`);
6979
7384
  context.stdout.log("github: odla.ai stores the installation; no PAT or GitHub token is written locally");
6980
7385
  }
6981
7386
  async function listSecuritySources(parsed, dependencies) {
@@ -7269,4 +7674,4 @@ export {
7269
7674
  exitCodeFor,
7270
7675
  runCli
7271
7676
  };
7272
- //# sourceMappingURL=chunk-3AC74CD2.js.map
7677
+ //# sourceMappingURL=chunk-E3CCYDIT.js.map