@odla-ai/cli 0.14.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -456,12 +456,13 @@ async function scopedToken(platform, scope, options, doFetch, out) {
456
456
  const { token, expiresAt } = await (0, import_db2.requestToken)({
457
457
  endpoint: audience,
458
458
  email,
459
- label: options.label ?? `odla CLI admin AI (${scope})`,
459
+ label: options.label ?? `odla CLI (${scope})`,
460
460
  scopes: [scope],
461
461
  fetch: doFetch,
462
462
  onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
463
463
  const approvalUrl = verificationUriComplete ?? handshakeUrl(audience, userCode);
464
- out.log(`Review, then approve scoped request ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
464
+ const requestName = scope === "app:code:host:connect" ? "terminal connection" : "scoped request";
465
+ out.log(`Review, then approve ${requestName} ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
465
466
  if (approvalBrowser({ open: options.open }).open) {
466
467
  await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
467
468
  }
@@ -523,8 +524,8 @@ async function responseBody(response2) {
523
524
  }
524
525
  function apiError(status, body) {
525
526
  const error = isRecord(body) && isRecord(body.error) ? body.error : void 0;
526
- const message3 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
527
- return `read System AI admin changes failed (${status}): ${message3}`;
527
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
528
+ return `read System AI admin changes failed (${status}): ${message2}`;
528
529
  }
529
530
  function timestamp(value) {
530
531
  if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
@@ -625,8 +626,8 @@ async function responseBody2(res) {
625
626
  }
626
627
  function apiError2(action, status, body) {
627
628
  const error = isRecord2(body) && isRecord2(body.error) ? body.error : void 0;
628
- const message3 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
629
- return `${action} failed (${status}): ${message3}`;
629
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
630
+ return `${action} failed (${status}): ${message2}`;
630
631
  }
631
632
  function isRecord2(value) {
632
633
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -806,8 +807,8 @@ async function responseBody3(res) {
806
807
  }
807
808
  function apiError3(action, status, body) {
808
809
  const error = isRecord3(body) && isRecord3(body.error) ? body.error : void 0;
809
- const message3 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
810
- return `${action} failed (${status}): ${message3}`;
810
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
811
+ return `${action} failed (${status}): ${message2}`;
811
812
  }
812
813
  function isRecord3(value) {
813
814
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -1487,8 +1488,8 @@ var CAPABILITIES = {
1487
1488
  "connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"
1488
1489
  ]
1489
1490
  };
1490
- function printCapabilities(json2 = false, out = console) {
1491
- if (json2) {
1491
+ function printCapabilities(json = false, out = console) {
1492
+ if (json) {
1492
1493
  out.log(JSON.stringify(CAPABILITIES, null, 2));
1493
1494
  return;
1494
1495
  }
@@ -1518,8 +1519,8 @@ var CalendarRequestError = class extends Error {
1518
1519
  status;
1519
1520
  /** Machine-readable `error.code` from the response body, when present. */
1520
1521
  code;
1521
- constructor(message3, status, code) {
1522
- super(message3);
1522
+ constructor(message2, status, code) {
1523
+ super(message2);
1523
1524
  this.name = "CalendarRequestError";
1524
1525
  this.status = status;
1525
1526
  if (code !== void 0) this.code = code;
@@ -1686,8 +1687,8 @@ async function calendarJson(ctx, suffix, init) {
1686
1687
  const body = await response2.json().catch(() => ({}));
1687
1688
  if (!response2.ok) {
1688
1689
  const code = textField(body.error?.code, 128);
1689
- const message3 = textField(body.error?.message, 500);
1690
- const detail = `${code ? ` ${code}` : ""}${message3 ? `: ${message3}` : ""}`;
1690
+ const message2 = textField(body.error?.message, 500);
1691
+ const detail = `${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`;
1691
1692
  throw new CalendarRequestError(redactSecrets(`calendar request failed (${response2.status})${detail}`), response2.status, code);
1692
1693
  }
1693
1694
  return body;
@@ -1923,8 +1924,8 @@ function pollTimeout(value = 10 * 6e4) {
1923
1924
  function productionConsent(env, yes, action) {
1924
1925
  if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action} for "${env}" without --yes`);
1925
1926
  }
1926
- function printStatus(status, json2, out) {
1927
- if (json2) {
1927
+ function printStatus(status, json, out) {
1928
+ if (json) {
1928
1929
  out.log(JSON.stringify(status, null, 2));
1929
1930
  return;
1930
1931
  }
@@ -1966,63 +1967,63 @@ function parseAgentOutput(line) {
1966
1967
  } catch {
1967
1968
  throw new HarnessProtocolError("agent emitted invalid JSON");
1968
1969
  }
1969
- const message3 = record2(value);
1970
- if (!message3 || message3.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
1970
+ const message2 = record2(value);
1971
+ if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
1971
1972
  throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
1972
1973
  }
1973
- if (message3.type === "event") {
1974
+ if (message2.type === "event") {
1974
1975
  return {
1975
1976
  protocolVersion: HARNESS_PROTOCOL_VERSION,
1976
1977
  type: "event",
1977
- kind: boundedText(message3.kind, "event.kind", 120),
1978
- ...message3.payload === void 0 ? {} : { payload: message3.payload }
1978
+ kind: boundedText(message2.kind, "event.kind", 120),
1979
+ ...message2.payload === void 0 ? {} : { payload: message2.payload }
1979
1980
  };
1980
1981
  }
1981
- if (message3.type === "inference.request") {
1982
- const call = record2(message3.call);
1982
+ if (message2.type === "inference.request") {
1983
+ const call = record2(message2.call);
1983
1984
  if (!call || !Array.isArray(call.messages) || !Number.isSafeInteger(call.maxTokens)) {
1984
1985
  throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
1985
1986
  }
1986
1987
  return {
1987
1988
  protocolVersion: HARNESS_PROTOCOL_VERSION,
1988
1989
  type: "inference.request",
1989
- requestId: boundedText(message3.requestId, "requestId", 180),
1990
+ requestId: boundedText(message2.requestId, "requestId", 180),
1990
1991
  call
1991
1992
  };
1992
1993
  }
1993
- if (message3.type === "tool.request") {
1994
- const input = record2(message3.input);
1995
- const tool = String(message3.tool);
1994
+ if (message2.type === "tool.request") {
1995
+ const input = record2(message2.input);
1996
+ const tool = String(message2.tool);
1996
1997
  if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
1997
1998
  throw new HarnessProtocolError("tool.request requires a registered tool and object input");
1998
1999
  }
1999
2000
  return {
2000
2001
  protocolVersion: HARNESS_PROTOCOL_VERSION,
2001
2002
  type: "tool.request",
2002
- requestId: boundedText(message3.requestId, "requestId", 180),
2003
+ requestId: boundedText(message2.requestId, "requestId", 180),
2003
2004
  tool,
2004
2005
  input
2005
2006
  };
2006
2007
  }
2007
- if (message3.type === "attempt.complete") {
2008
- if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(message3.status))) {
2008
+ if (message2.type === "attempt.complete") {
2009
+ if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(message2.status))) {
2009
2010
  throw new HarnessProtocolError("attempt.complete.status is invalid");
2010
2011
  }
2011
2012
  return {
2012
2013
  protocolVersion: HARNESS_PROTOCOL_VERSION,
2013
2014
  type: "attempt.complete",
2014
- status: message3.status,
2015
- ...message3.result === void 0 ? {} : { result: message3.result }
2015
+ status: message2.status,
2016
+ ...message2.result === void 0 ? {} : { result: message2.result }
2016
2017
  };
2017
2018
  }
2018
2019
  throw new HarnessProtocolError("agent message type is unsupported");
2019
2020
  }
2020
- function encodeAgentInput(message3) {
2021
- return `${JSON.stringify(message3)}
2021
+ function encodeAgentInput(message2) {
2022
+ return `${JSON.stringify(message2)}
2022
2023
  `;
2023
2024
  }
2024
2025
 
2025
- // ../harness/dist/chunk-FNYUIJUN.js
2026
+ // ../harness/dist/chunk-TA2FKK27.js
2026
2027
  var import_child_process = require("child_process");
2027
2028
  var import_fs = require("fs");
2028
2029
  var import_promises2 = require("fs/promises");
@@ -2032,6 +2033,11 @@ var import_promises3 = require("fs/promises");
2032
2033
  var import_os = require("os");
2033
2034
  var import_path2 = require("path");
2034
2035
  var import_child_process2 = require("child_process");
2036
+ var import_path3 = require("path");
2037
+ var import_promises4 = require("fs/promises");
2038
+ var import_os2 = require("os");
2039
+ var import_path4 = require("path");
2040
+ var import_child_process3 = require("child_process");
2035
2041
  var DIGEST_IMAGE = /^[a-z0-9][a-z0-9._/-]*(?::[a-zA-Z0-9._-]+)?@sha256:[0-9a-f]{64}$/;
2036
2042
  function assertPinnedImage(image) {
2037
2043
  if (!DIGEST_IMAGE.test(image)) throw new TypeError("container image must be pinned by sha256 digest");
@@ -2075,7 +2081,7 @@ async function selectContainerEngine(requested = "auto", options = {}) {
2075
2081
  throw new TypeError("no supported container engine found");
2076
2082
  }
2077
2083
  function inspectRootlessPodman() {
2078
- return new Promise((resolve23, reject) => {
2084
+ return new Promise((resolve33, reject) => {
2079
2085
  (0, import_child_process.execFile)(
2080
2086
  "podman",
2081
2087
  ["info", "--format", "{{.Host.Security.Rootless}}"],
@@ -2085,7 +2091,7 @@ function inspectRootlessPodman() {
2085
2091
  reject(new TypeError("could not verify that the active Podman service is rootless"));
2086
2092
  return;
2087
2093
  }
2088
- resolve23(stdout.trim() === "true");
2094
+ resolve33(stdout.trim() === "true");
2089
2095
  }
2090
2096
  );
2091
2097
  });
@@ -2196,9 +2202,9 @@ async function runContainerAttempt(options) {
2196
2202
  if (bytes.byteLength > 1e6) throw new Error("agent message exceeds 1 MB");
2197
2203
  const line = bytes.toString("utf8");
2198
2204
  if (!line.trim()) return;
2199
- const message3 = parseAgentOutput(line);
2200
- if (message3.type === "attempt.complete") complete = message3;
2201
- const response2 = await options.onMessage(message3);
2205
+ const message2 = parseAgentOutput(line);
2206
+ if (message2.type === "attempt.complete") complete = message2;
2207
+ const response2 = await options.onMessage(message2);
2202
2208
  if (response2 && !child.stdin.destroyed) child.stdin.write(encodeAgentInput(response2));
2203
2209
  };
2204
2210
  try {
@@ -2246,29 +2252,142 @@ async function runContainerAttempt(options) {
2246
2252
  options.signal?.removeEventListener("abort", abort);
2247
2253
  }
2248
2254
  }
2249
- var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
2250
- var SECRET_FILE = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
2255
+ var SKIP_WORKSPACE_DIRS = /* @__PURE__ */ new Set([
2256
+ ".git",
2257
+ ".odla",
2258
+ ".wrangler",
2259
+ "node_modules",
2260
+ "dist",
2261
+ "coverage"
2262
+ ]);
2263
+ var SECRET_WORKSPACE_FILE = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
2264
+ function allowedWorkspacePath(relativePath) {
2265
+ const parts = relativePath.split("/");
2266
+ return !(0, import_path3.isAbsolute)(relativePath) && !relativePath.includes("\\") && !relativePath.includes("\0") && !parts.some((part) => !part || part === "." || part === ".." || SKIP_WORKSPACE_DIRS.has(part)) && !SECRET_WORKSPACE_FILE.test(parts.at(-1) ?? "");
2267
+ }
2268
+ async function gitOutput(cwd, args, maxBytes) {
2269
+ const child = (0, import_child_process2.spawn)("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: false });
2270
+ const stdout = [];
2271
+ const stderr = [];
2272
+ let bytes = 0;
2273
+ child.stdout.on("data", (chunk) => {
2274
+ bytes += chunk.byteLength;
2275
+ if (bytes > maxBytes) child.kill("SIGKILL");
2276
+ else stdout.push(chunk);
2277
+ });
2278
+ child.stderr.on("data", (chunk) => {
2279
+ if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
2280
+ });
2281
+ const code = await new Promise((accept, reject) => {
2282
+ child.once("error", reject);
2283
+ child.once("exit", accept);
2284
+ });
2285
+ if (bytes > maxBytes) throw new Error(`git output exceeds ${maxBytes} bytes`);
2286
+ if (code !== 0) throw new Error(`git command failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
2287
+ return Buffer.concat(stdout);
2288
+ }
2289
+ async function gitBlobs(cwd, entries, maxBytes) {
2290
+ const child = (0, import_child_process2.spawn)("git", ["cat-file", "--batch"], { cwd, stdio: ["pipe", "pipe", "pipe"], shell: false });
2291
+ const stdout = [];
2292
+ const stderr = [];
2293
+ let bytes = 0;
2294
+ child.stdout.on("data", (chunk) => {
2295
+ bytes += chunk.byteLength;
2296
+ if (bytes > maxBytes + entries.length * 100) child.kill("SIGKILL");
2297
+ else stdout.push(chunk);
2298
+ });
2299
+ child.stderr.on("data", (chunk) => {
2300
+ if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
2301
+ });
2302
+ child.stdin.end(`${entries.map((entry) => entry.hash).join("\n")}
2303
+ `);
2304
+ const code = await new Promise((accept, reject) => {
2305
+ child.once("error", reject);
2306
+ child.once("exit", accept);
2307
+ });
2308
+ if (bytes > maxBytes + entries.length * 100) throw new Error(`Git tree exceeds ${maxBytes} bytes`);
2309
+ if (code !== 0) throw new Error(`git object read failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
2310
+ const output = Buffer.concat(stdout);
2311
+ const blobs = [];
2312
+ let offset = 0;
2313
+ let total = 0;
2314
+ for (const entry of entries) {
2315
+ const newline = output.indexOf(10, offset);
2316
+ if (newline < 0) throw new Error("git object output is truncated");
2317
+ const match = /^([0-9a-f]{40,64}) blob ([0-9]+)$/.exec(output.subarray(offset, newline).toString("utf8"));
2318
+ if (!match || match[1] !== entry.hash) throw new Error("git object output does not match its inventory");
2319
+ const length = Number(match[2]);
2320
+ if (!Number.isSafeInteger(length) || length < 0 || newline + 1 + length >= output.length) {
2321
+ throw new Error("git object output has an invalid length");
2322
+ }
2323
+ const content = output.subarray(newline + 1, newline + 1 + length);
2324
+ if (output[newline + 1 + length] !== 10) throw new Error("git object output is malformed");
2325
+ total += content.byteLength;
2326
+ if (total > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2327
+ blobs.push(content);
2328
+ offset = newline + 1 + length + 1;
2329
+ }
2330
+ return blobs;
2331
+ }
2332
+ async function materializeGitTree(source, commitSha, options = {}) {
2333
+ if (!/^[0-9a-f]{40}$/.test(commitSha)) throw new TypeError("Git tree requires an exact commit SHA");
2334
+ const sourceDir = await (0, import_promises3.realpath)((0, import_path2.resolve)(source));
2335
+ const maxFiles = options.maxFiles ?? 2e4;
2336
+ const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
2337
+ const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
2338
+ const entries = inventory.flatMap((record5) => {
2339
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record5);
2340
+ return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
2341
+ });
2342
+ if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
2343
+ const root = await (0, import_promises3.mkdtemp)((0, import_path2.join)(options.tempRoot ?? (0, import_os.tmpdir)(), "odla-git-tree-"));
2344
+ const targetRoot = (0, import_path2.join)(root, "source");
2345
+ await (0, import_promises3.mkdir)(targetRoot);
2346
+ let byteCount = 0;
2347
+ try {
2348
+ const blobs = await gitBlobs(sourceDir, entries, maxBytes);
2349
+ for (const [index, entry] of entries.entries()) {
2350
+ const content = blobs[index];
2351
+ byteCount += content.byteLength;
2352
+ if (byteCount > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2353
+ const target = (0, import_path2.resolve)(targetRoot, entry.path);
2354
+ if (!target.startsWith(`${(0, import_path2.resolve)(targetRoot)}${import_path2.sep}`)) throw new TypeError("Git tree path escapes workspace");
2355
+ await (0, import_promises3.mkdir)((0, import_path2.resolve)(target, ".."), { recursive: true });
2356
+ await (0, import_promises3.writeFile)(target, content, { flag: "wx", mode: entry.mode === "100755" ? 493 : 420 });
2357
+ }
2358
+ return {
2359
+ root,
2360
+ sourceDir: targetRoot,
2361
+ fileCount: entries.length,
2362
+ byteCount,
2363
+ cleanup: () => (0, import_promises3.rm)(root, { recursive: true, force: true })
2364
+ };
2365
+ } catch (error) {
2366
+ await (0, import_promises3.rm)(root, { recursive: true, force: true });
2367
+ throw error;
2368
+ }
2369
+ }
2251
2370
  async function sourceFiles(sourceDir, maxFiles, maxBytes) {
2252
2371
  const files = [];
2253
2372
  let bytes = 0;
2254
2373
  const walk = async (dir) => {
2255
- for (const entry of await (0, import_promises3.readdir)(dir, { withFileTypes: true })) {
2256
- if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue;
2257
- if (!entry.isDirectory() && SECRET_FILE.test(entry.name)) continue;
2258
- const path = (0, import_path2.join)(dir, entry.name);
2374
+ for (const entry of await (0, import_promises4.readdir)(dir, { withFileTypes: true })) {
2375
+ if (entry.isDirectory() && SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
2376
+ if (!entry.isDirectory() && SECRET_WORKSPACE_FILE.test(entry.name)) continue;
2377
+ const path = (0, import_path4.join)(dir, entry.name);
2259
2378
  if (entry.isSymbolicLink()) continue;
2260
2379
  if (entry.isDirectory()) {
2261
2380
  await walk(path);
2262
2381
  continue;
2263
2382
  }
2264
2383
  if (!entry.isFile()) continue;
2265
- const metadata2 = await (0, import_promises3.stat)(path);
2384
+ const metadata2 = await (0, import_promises4.stat)(path);
2266
2385
  bytes += metadata2.size;
2267
2386
  if (files.length + 1 > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
2268
2387
  if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2269
2388
  files.push({
2270
2389
  source: path,
2271
- relativePath: (0, import_path2.relative)(sourceDir, path),
2390
+ relativePath: (0, import_path4.relative)(sourceDir, path),
2272
2391
  mode: metadata2.mode & 511,
2273
2392
  bytes: metadata2.size
2274
2393
  });
@@ -2277,16 +2396,62 @@ async function sourceFiles(sourceDir, maxFiles, maxBytes) {
2277
2396
  await walk(sourceDir);
2278
2397
  return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
2279
2398
  }
2399
+ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
2400
+ const child = (0, import_child_process3.spawn)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
2401
+ cwd: sourceDir,
2402
+ stdio: ["ignore", "pipe", "pipe"],
2403
+ shell: false
2404
+ });
2405
+ const stdout = [];
2406
+ const stderr = [];
2407
+ let outputBytes = 0;
2408
+ child.stdout.on("data", (chunk) => {
2409
+ outputBytes += chunk.byteLength;
2410
+ if (outputBytes > 8 * 1024 * 1024) child.kill("SIGKILL");
2411
+ else stdout.push(chunk);
2412
+ });
2413
+ child.stderr.on("data", (chunk) => {
2414
+ if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
2415
+ });
2416
+ const code = await new Promise((accept, reject) => {
2417
+ child.once("error", reject);
2418
+ child.once("exit", accept);
2419
+ });
2420
+ if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
2421
+ if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
2422
+ const paths = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
2423
+ if (paths.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
2424
+ const root = (0, import_path4.resolve)(sourceDir);
2425
+ const files = [];
2426
+ let bytes = 0;
2427
+ for (const relativePath of paths) {
2428
+ if (!allowedWorkspacePath(relativePath)) continue;
2429
+ const source = (0, import_path4.resolve)(root, relativePath);
2430
+ if (!source.startsWith(`${root}${import_path4.sep}`)) throw new TypeError("git file path escapes workspace");
2431
+ let metadata2;
2432
+ try {
2433
+ metadata2 = await (0, import_promises4.lstat)(source);
2434
+ } catch (error) {
2435
+ if (error.code === "ENOENT") continue;
2436
+ throw error;
2437
+ }
2438
+ if (metadata2.isSymbolicLink() || !metadata2.isFile()) continue;
2439
+ bytes += metadata2.size;
2440
+ if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2441
+ files.push({ source, relativePath, mode: metadata2.mode & 511, bytes: metadata2.size });
2442
+ }
2443
+ return files;
2444
+ }
2280
2445
  async function copyTree(files, destination) {
2281
2446
  for (const file of files) {
2282
- const target = (0, import_path2.join)(destination, file.relativePath);
2283
- await (0, import_promises3.mkdir)((0, import_path2.resolve)(target, ".."), { recursive: true });
2284
- await (0, import_promises3.copyFile)(file.source, target);
2285
- await (0, import_promises3.chmod)(target, file.mode);
2447
+ const target = (0, import_path4.join)(destination, file.relativePath);
2448
+ await (0, import_promises4.mkdir)((0, import_path4.resolve)(target, ".."), { recursive: true });
2449
+ await (0, import_promises4.copyFile)(file.source, target);
2450
+ await (0, import_promises4.chmod)(target, file.mode);
2286
2451
  }
2287
2452
  }
2288
2453
  async function captureGitDiff(root, maxBytes) {
2289
- const child = (0, import_child_process2.spawn)("git", [
2454
+ const child = (0, import_child_process3.spawn)("git", [
2290
2455
  "diff",
2291
2456
  "--no-index",
2292
2457
  "--binary",
@@ -2319,15 +2484,17 @@ async function captureGitDiff(root, maxBytes) {
2319
2484
  return Buffer.concat(stdout).toString("utf8").replaceAll("a/baseline/", "a/").replaceAll("b/workspace/", "b/").replaceAll("--- a/baseline", "--- a").replaceAll("+++ b/workspace", "+++ b");
2320
2485
  }
2321
2486
  async function stageWorkspace(source, options = {}) {
2322
- const sourceDir = await (0, import_promises3.realpath)((0, import_path2.resolve)(source));
2323
- const sourceStat = await (0, import_promises3.stat)(sourceDir);
2487
+ const sourceDir = await (0, import_promises4.realpath)((0, import_path4.resolve)(source));
2488
+ const sourceStat = await (0, import_promises4.stat)(sourceDir);
2324
2489
  if (!sourceStat.isDirectory()) throw new TypeError("workspace source must be a directory");
2325
- const root = await (0, import_promises3.mkdtemp)((0, import_path2.join)(options.tempRoot ?? (0, import_os.tmpdir)(), "odla-harness-"));
2326
- const baselineDir = (0, import_path2.join)(root, "baseline");
2327
- const workspaceDir = (0, import_path2.join)(root, "workspace");
2328
- await Promise.all([(0, import_promises3.mkdir)(baselineDir), (0, import_promises3.mkdir)(workspaceDir)]);
2490
+ const root = await (0, import_promises4.mkdtemp)((0, import_path4.join)(options.tempRoot ?? (0, import_os2.tmpdir)(), "odla-harness-"));
2491
+ const baselineDir = (0, import_path4.join)(root, "baseline");
2492
+ const workspaceDir = (0, import_path4.join)(root, "workspace");
2493
+ await Promise.all([(0, import_promises4.mkdir)(baselineDir), (0, import_promises4.mkdir)(workspaceDir)]);
2329
2494
  try {
2330
- const files = await sourceFiles(sourceDir, options.maxFiles ?? 2e4, options.maxBytes ?? 512 * 1024 * 1024);
2495
+ const maxFiles = options.maxFiles ?? 2e4;
2496
+ const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
2497
+ const files = options.gitTrackedAndUnignored ? await gitSourceFiles(sourceDir, maxFiles, maxBytes) : await sourceFiles(sourceDir, maxFiles, maxBytes);
2331
2498
  await Promise.all([copyTree(files, baselineDir), copyTree(files, workspaceDir)]);
2332
2499
  return {
2333
2500
  root,
@@ -2335,15 +2502,49 @@ async function stageWorkspace(source, options = {}) {
2335
2502
  workspaceDir,
2336
2503
  fileCount: files.length,
2337
2504
  byteCount: files.reduce((sum, file) => sum + file.bytes, 0),
2338
- patch: (maxBytes) => captureGitDiff(root, maxBytes),
2339
- cleanup: () => (0, import_promises3.rm)(root, { recursive: true, force: true })
2505
+ patch: (maxBytes2) => captureGitDiff(root, maxBytes2),
2506
+ cleanup: () => (0, import_promises4.rm)(root, { recursive: true, force: true })
2340
2507
  };
2341
2508
  } catch (error) {
2342
- await (0, import_promises3.rm)(root, { recursive: true, force: true });
2509
+ await (0, import_promises4.rm)(root, { recursive: true, force: true });
2510
+ throw error;
2511
+ }
2512
+ }
2513
+ async function stageWorkspacePair(baselineSource, workspaceSource, options = {}) {
2514
+ const baselineDirSource = await (0, import_promises4.realpath)((0, import_path4.resolve)(baselineSource));
2515
+ const workspaceDirSource = await (0, import_promises4.realpath)((0, import_path4.resolve)(workspaceSource));
2516
+ const maxFiles = options.maxFiles ?? 2e4;
2517
+ const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
2518
+ const [baselineFiles, workspaceFiles] = await Promise.all([
2519
+ sourceFiles(baselineDirSource, maxFiles, maxBytes),
2520
+ sourceFiles(workspaceDirSource, maxFiles, maxBytes)
2521
+ ]);
2522
+ const root = await (0, import_promises4.mkdtemp)((0, import_path4.join)(options.tempRoot ?? (0, import_os2.tmpdir)(), "odla-harness-"));
2523
+ const baselineDir = (0, import_path4.join)(root, "baseline");
2524
+ const workspaceDir = (0, import_path4.join)(root, "workspace");
2525
+ await Promise.all([(0, import_promises4.mkdir)(baselineDir), (0, import_promises4.mkdir)(workspaceDir)]);
2526
+ try {
2527
+ await Promise.all([copyTree(baselineFiles, baselineDir), copyTree(workspaceFiles, workspaceDir)]);
2528
+ return {
2529
+ root,
2530
+ baselineDir,
2531
+ workspaceDir,
2532
+ fileCount: workspaceFiles.length,
2533
+ byteCount: workspaceFiles.reduce((sum, file) => sum + file.bytes, 0),
2534
+ patch: (maxPatchBytes) => captureGitDiff(root, maxPatchBytes),
2535
+ cleanup: () => (0, import_promises4.rm)(root, { recursive: true, force: true })
2536
+ };
2537
+ } catch (error) {
2538
+ await (0, import_promises4.rm)(root, { recursive: true, force: true });
2343
2539
  throw error;
2344
2540
  }
2345
2541
  }
2346
2542
 
2543
+ // ../harness/dist/chunk-4EM6NMET.js
2544
+ var import_crypto = require("crypto");
2545
+ var import_promises5 = require("fs/promises");
2546
+ var import_path5 = require("path");
2547
+
2347
2548
  // ../camel/dist/chunk-7FHPOQVP.js
2348
2549
  var CamelError = class extends Error {
2349
2550
  code;
@@ -2362,8 +2563,8 @@ function canonicalJson(value) {
2362
2563
  }
2363
2564
  async function sha256Hex(value) {
2364
2565
  const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
2365
- const digest2 = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
2366
- return [...new Uint8Array(digest2)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2566
+ const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
2567
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2367
2568
  }
2368
2569
  function utf8Length(value) {
2369
2570
  if (typeof value === "string") return new TextEncoder().encode(value).byteLength;
@@ -2640,8 +2841,8 @@ function freeze(value) {
2640
2841
  })
2641
2842
  });
2642
2843
  }
2643
- function invalid2(message3) {
2644
- return new CamelError("state_conflict", message3);
2844
+ function invalid2(message2) {
2845
+ return new CamelError("state_conflict", message2);
2645
2846
  }
2646
2847
  var SHA4 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
2647
2848
  var REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
@@ -2676,25 +2877,22 @@ function validateSnapshot(snapshot, limits) {
2676
2877
  }
2677
2878
  }
2678
2879
 
2679
- // ../harness/dist/chunk-RVUUURK2.js
2680
- var import_child_process3 = require("child_process");
2681
- var import_promises4 = require("fs/promises");
2682
- var import_path3 = require("path");
2880
+ // ../harness/dist/chunk-4EM6NMET.js
2683
2881
  var import_child_process4 = require("child_process");
2882
+ var import_promises6 = require("fs/promises");
2883
+ var import_path6 = require("path");
2884
+ var import_child_process5 = require("child_process");
2684
2885
  var import_process2 = require("process");
2685
- var import_crypto = require("crypto");
2686
2886
  var import_crypto2 = require("crypto");
2687
- var import_fs2 = require("fs");
2688
- var import_promises5 = require("fs/promises");
2689
- var import_path4 = require("path");
2690
2887
  var import_crypto3 = require("crypto");
2691
- var import_promises6 = require("fs/promises");
2692
- var import_path5 = require("path");
2888
+ var import_fs2 = require("fs");
2693
2889
  var import_promises7 = require("fs/promises");
2694
- var import_os2 = require("os");
2695
- var import_path6 = require("path");
2696
- var import_promises8 = require("fs/promises");
2697
2890
  var import_path7 = require("path");
2891
+ var import_promises8 = require("fs/promises");
2892
+ var import_os3 = require("os");
2893
+ var import_path8 = require("path");
2894
+ var import_promises9 = require("fs/promises");
2895
+ var import_path9 = require("path");
2698
2896
 
2699
2897
  // ../camel/dist/chunk-LAXU2AVK.js
2700
2898
  function conversionPolicyDigest(policy) {
@@ -2972,8 +3170,34 @@ function looksLikeDestination(value) {
2972
3170
  return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
2973
3171
  }
2974
3172
 
2975
- // ../harness/dist/chunk-RVUUURK2.js
3173
+ // ../harness/dist/chunk-4EM6NMET.js
2976
3174
  var import_crypto4 = require("crypto");
3175
+ async function digestStagedWorkspace(root, limits) {
3176
+ const files = [];
3177
+ const walk = async (directory) => {
3178
+ const entries = await (0, import_promises5.readdir)(directory, { withFileTypes: true });
3179
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
3180
+ if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
3181
+ const target = (0, import_path5.resolve)(directory, entry.name);
3182
+ if (entry.isDirectory()) await walk(target);
3183
+ else if (entry.isFile()) {
3184
+ files.push({ path: (0, import_path5.relative)(root, target).split("\\").join("/"), target });
3185
+ if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
3186
+ }
3187
+ }
3188
+ };
3189
+ await walk((0, import_path5.resolve)(root));
3190
+ const hash = (0, import_crypto.createHash)("sha256");
3191
+ let bytes = 0;
3192
+ for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
3193
+ const content = await (0, import_promises5.readFile)(file.target);
3194
+ bytes += Buffer.byteLength(file.path) + content.byteLength;
3195
+ if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
3196
+ hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
3197
+ hash.update(content);
3198
+ }
3199
+ return `sha256:${hash.digest("hex")}`;
3200
+ }
2977
3201
  var CODE_RUNTIME_PROTOCOL_VERSION = 1;
2978
3202
  async function runCodeRuntimeHeartbeatLoop(options) {
2979
3203
  const heartbeatMs = options.heartbeatMs ?? 15e3;
@@ -3045,8 +3269,8 @@ function wait(ms, signal) {
3045
3269
  });
3046
3270
  }
3047
3271
  var CodeRuntimeControlError = class extends Error {
3048
- constructor(message3, status, code = "control_error") {
3049
- super(message3);
3272
+ constructor(message2, status, code = "control_error") {
3273
+ super(message2);
3050
3274
  this.status = status;
3051
3275
  this.code = code;
3052
3276
  }
@@ -3125,9 +3349,9 @@ function createCodeRuntimeControlClient(options) {
3125
3349
  }
3126
3350
  await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
3127
3351
  },
3128
- reportSessionFailure: async (sessionId, message3) => {
3129
- if (!message3.trim() || message3.length > 2e3) throw new TypeError("invalid Code session failure");
3130
- await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message3 });
3352
+ reportSessionFailure: async (sessionId, message2) => {
3353
+ if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
3354
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
3131
3355
  }
3132
3356
  };
3133
3357
  }
@@ -3194,10 +3418,28 @@ async function parseSource(value) {
3194
3418
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
3195
3419
  return { path: file.path, content: file.content };
3196
3420
  });
3421
+ const referencesValue = snapshot.references === void 0 ? [] : snapshot.references;
3422
+ if (!Array.isArray(referencesValue) || referencesValue.length > 5) throw invalid("reference sources");
3423
+ const aliases = /* @__PURE__ */ new Set();
3424
+ const references = [];
3425
+ for (const item of referencesValue) {
3426
+ const reference = record3(item);
3427
+ if (!reference || typeof reference.alias !== "string" || !/^[a-z][a-z0-9-]{0,39}$/.test(reference.alias) || aliases.has(reference.alias) || reference.alias === "primary" || typeof reference.repository !== "string" || typeof reference.commitSha !== "string" || typeof reference.treeDigest !== "string" || !Array.isArray(reference.files)) throw invalid("reference source");
3428
+ aliases.add(reference.alias);
3429
+ const referenceFiles = reference.files.map((entry) => {
3430
+ const file = record3(entry);
3431
+ if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
3432
+ return { path: file.path, content: file.content };
3433
+ });
3434
+ const source2 = { repository: reference.repository, commitSha: reference.commitSha, files: referenceFiles };
3435
+ const referenceDigest = await digestCodeRepositorySnapshot(source2, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3436
+ if (referenceDigest !== reference.treeDigest) throw invalid("reference source digest");
3437
+ references.push({ alias: reference.alias, ...source2, treeDigest: referenceDigest });
3438
+ }
3197
3439
  const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
3198
- const digest2 = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3199
- if (digest2 !== snapshot.treeDigest) throw invalid("source digest");
3200
- return { ...source, treeDigest: digest2 };
3440
+ const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3441
+ if (digest !== snapshot.treeDigest) throw invalid("source digest");
3442
+ return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
3201
3443
  }
3202
3444
  function parseReview(value) {
3203
3445
  const review = record3(record3(value)?.review);
@@ -3256,9 +3498,9 @@ function validateRelativePath(path) {
3256
3498
  }
3257
3499
  function resolveCodePath(workspaceDir, path) {
3258
3500
  validateRelativePath(path);
3259
- const root = (0, import_path3.resolve)(workspaceDir);
3260
- const target = (0, import_path3.resolve)(root, path);
3261
- if (target !== root && !target.startsWith(`${root}${import_path3.sep}`)) throw new TypeError("path escapes the staged workspace");
3501
+ const root = (0, import_path6.resolve)(workspaceDir);
3502
+ const target = (0, import_path6.resolve)(root, path);
3503
+ if (target !== root && !target.startsWith(`${root}${import_path6.sep}`)) throw new TypeError("path escapes the staged workspace");
3262
3504
  return target;
3263
3505
  }
3264
3506
  async function applyCodePatch(workspaceDir, patch2, paths) {
@@ -3266,7 +3508,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
3266
3508
  await gitApply(workspaceDir, patch2, false);
3267
3509
  for (const path of paths) {
3268
3510
  try {
3269
- const info = await (0, import_promises4.lstat)(resolveCodePath(workspaceDir, path));
3511
+ const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
3270
3512
  if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
3271
3513
  throw new TypeError("patch created a non-regular workspace entry");
3272
3514
  }
@@ -3278,7 +3520,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
3278
3520
  function gitApply(cwd, patch2, check) {
3279
3521
  return new Promise((accept, reject) => {
3280
3522
  const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
3281
- const child = (0, import_child_process3.spawn)("git", args, {
3523
+ const child = (0, import_child_process4.spawn)("git", args, {
3282
3524
  cwd,
3283
3525
  shell: false,
3284
3526
  stdio: ["pipe", "ignore", "pipe"],
@@ -3322,7 +3564,7 @@ async function restoreCodeWorkspaceCheckpoint(input) {
3322
3564
  }
3323
3565
  var ARTIFACT_PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
3324
3566
  var PRIVATE_ARTIFACT_PART = /^(?:\.git|\.odla|\.wrangler|\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json)$/i;
3325
- function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0, import_crypto.randomUUID)().slice(0, 12)}`) {
3567
+ function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0, import_crypto2.randomUUID)().slice(0, 12)}`) {
3326
3568
  assertCodeBuildRecipe(recipe2);
3327
3569
  if (/[,\r\n]/.test(workspaceDir)) throw new TypeError("workspace path contains unsupported mount characters");
3328
3570
  const uid = typeof import_process2.getuid === "function" ? (0, import_process2.getuid)() : 1e3;
@@ -3377,7 +3619,7 @@ function createContainerRecipeExecutor(engine) {
3377
3619
  return {
3378
3620
  async run(input) {
3379
3621
  await verifyContainerEngineBoundary(engine);
3380
- const name = `odla-recipe-${(0, import_crypto.randomUUID)().slice(0, 12)}`;
3622
+ const name = `odla-recipe-${(0, import_crypto2.randomUUID)().slice(0, 12)}`;
3381
3623
  const args = buildRecipeContainerArgs(engine, input.workspaceDir, input.recipe, name);
3382
3624
  return execute(engine, args, name, input.recipe, input.signal);
3383
3625
  }
@@ -3401,7 +3643,7 @@ function parseMemory(value) {
3401
3643
  function execute(engine, args, name, recipe2, signal) {
3402
3644
  return new Promise((accept, reject) => {
3403
3645
  const started = Date.now();
3404
- const child = (0, import_child_process4.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
3646
+ const child = (0, import_child_process5.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
3405
3647
  const stdout = [];
3406
3648
  const stderr = [];
3407
3649
  let bytes = 0;
@@ -3414,7 +3656,7 @@ function execute(engine, args, name, recipe2, signal) {
3414
3656
  timedOut = reason === "timeout";
3415
3657
  outputLimitExceeded = reason === "output";
3416
3658
  const remove = engine === "container" ? ["delete", "--force", name] : ["rm", "-f", name];
3417
- const killer = (0, import_child_process4.spawn)(engine, remove, { shell: false, stdio: "ignore" });
3659
+ const killer = (0, import_child_process5.spawn)(engine, remove, { shell: false, stdio: "ignore" });
3418
3660
  killer.unref();
3419
3661
  child.kill("SIGTERM");
3420
3662
  };
@@ -3448,32 +3690,6 @@ function execute(engine, args, name, recipe2, signal) {
3448
3690
  });
3449
3691
  });
3450
3692
  }
3451
- async function digestStagedWorkspace(root, limits) {
3452
- const files = [];
3453
- const walk = async (directory) => {
3454
- const entries = await (0, import_promises6.readdir)(directory, { withFileTypes: true });
3455
- for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
3456
- if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
3457
- const target = (0, import_path5.resolve)(directory, entry.name);
3458
- if (entry.isDirectory()) await walk(target);
3459
- else if (entry.isFile()) {
3460
- files.push({ path: (0, import_path5.relative)(root, target).split("\\").join("/"), target });
3461
- if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
3462
- }
3463
- }
3464
- };
3465
- await walk((0, import_path5.resolve)(root));
3466
- const hash = (0, import_crypto3.createHash)("sha256");
3467
- let bytes = 0;
3468
- for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
3469
- const content = await (0, import_promises6.readFile)(file.target);
3470
- bytes += Buffer.byteLength(file.path) + content.byteLength;
3471
- if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
3472
- hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
3473
- hash.update(content);
3474
- }
3475
- return `sha256:${hash.digest("hex")}`;
3476
- }
3477
3693
  var SHA3 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
3478
3694
  var DIGEST3 = /^sha256:[0-9a-f]{64}$/;
3479
3695
  var ID3 = /^[A-Za-z0-9._:-]{1,160}$/;
@@ -3521,7 +3737,7 @@ async function verifyCodeCandidate(input) {
3521
3737
  }
3522
3738
  const fields = {
3523
3739
  schemaVersion: 1,
3524
- verificationId: input.verificationId ?? `verify-${(0, import_crypto2.randomUUID)()}`,
3740
+ verificationId: input.verificationId ?? `verify-${(0, import_crypto3.randomUUID)()}`,
3525
3741
  trustedBaseCommitSha: input.trustedBaseCommitSha,
3526
3742
  trustedBaseDigest: input.trustedBaseDigest,
3527
3743
  patchDigest,
@@ -3582,8 +3798,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
3582
3798
  const receipts = [];
3583
3799
  for (const artifact of recipe2.expectedArtifacts ?? []) {
3584
3800
  try {
3585
- const path = (0, import_path4.join)(workspaceDir, artifact.path);
3586
- const info = await (0, import_promises5.lstat)(path);
3801
+ const path = (0, import_path7.join)(workspaceDir, artifact.path);
3802
+ const info = await (0, import_promises7.lstat)(path);
3587
3803
  if (!info.isFile() || info.isSymbolicLink()) {
3588
3804
  receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
3589
3805
  } else if (info.size > artifact.maximumBytes) {
@@ -3600,7 +3816,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
3600
3816
  }
3601
3817
  function hashFile(path) {
3602
3818
  return new Promise((accept, reject) => {
3603
- const hash = (0, import_crypto2.createHash)("sha256");
3819
+ const hash = (0, import_crypto3.createHash)("sha256");
3604
3820
  const stream = (0, import_fs2.createReadStream)(path);
3605
3821
  stream.on("data", (chunk) => {
3606
3822
  hash.update(chunk);
@@ -3658,7 +3874,7 @@ function digestJson(value) {
3658
3874
  return digestBytes(JSON.stringify(value));
3659
3875
  }
3660
3876
  function digestBytes(value) {
3661
- return `sha256:${(0, import_crypto2.createHash)("sha256").update(value).digest("hex")}`;
3877
+ return `sha256:${(0, import_crypto3.createHash)("sha256").update(value).digest("hex")}`;
3662
3878
  }
3663
3879
  function integer(value, minimum, maximum) {
3664
3880
  return Number.isSafeInteger(value) && value >= minimum && value <= maximum;
@@ -3679,8 +3895,8 @@ async function prepareRuntimeCheckpoint(input) {
3679
3895
  policy: {
3680
3896
  policyId: "code.runtime",
3681
3897
  recipes: input.recipes,
3682
- maximumFiles: 1e4,
3683
- maximumBytes: 16 * 1024 * 1024
3898
+ maximumFiles: 2e4,
3899
+ maximumBytes: 512 * 1024 * 1024
3684
3900
  },
3685
3901
  recipeExecutor: input.recipeExecutor
3686
3902
  });
@@ -3754,7 +3970,7 @@ var CodeRuntimeCheckpointManager = class {
3754
3970
  await this.options.event(command, {
3755
3971
  type: "message",
3756
3972
  actor: "system",
3757
- body: `Candidate ${candidate.candidateId} is ready for exact owner publication approval`
3973
+ body: command.payload.sourceSet ? `Candidate ${candidate.candidateId} was verified and delivered to the session PR branch` : `Candidate ${candidate.candidateId} is ready for legacy owner publication approval`
3758
3974
  }, pending.refs);
3759
3975
  this.#pending.delete(command.commandId);
3760
3976
  return true;
@@ -3762,11 +3978,11 @@ var CodeRuntimeCheckpointManager = class {
3762
3978
  };
3763
3979
  var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
3764
3980
  var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
3765
- async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os2.tmpdir)()) {
3981
+ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.tmpdir)()) {
3766
3982
  if (!snapshot.files.length || snapshot.files.length > 1e4) throw new TypeError("Code source file count is invalid");
3767
- const root = await (0, import_promises7.mkdtemp)((0, import_path6.join)(tempRoot, "odla-code-source-"));
3768
- const sourceDir = (0, import_path6.join)(root, "source");
3769
- await (0, import_promises7.mkdir)(sourceDir);
3983
+ const root = await (0, import_promises8.mkdtemp)((0, import_path8.join)(tempRoot, "odla-code-source-"));
3984
+ const sourceDir = (0, import_path8.join)(root, "source");
3985
+ await (0, import_promises8.mkdir)(sourceDir);
3770
3986
  const seen = /* @__PURE__ */ new Set();
3771
3987
  let bytes = 0;
3772
3988
  try {
@@ -3776,17 +3992,56 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os2.
3776
3992
  seen.add(file.path);
3777
3993
  bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
3778
3994
  if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
3779
- const target = (0, import_path6.resolve)(sourceDir, file.path);
3780
- if (!target.startsWith(`${(0, import_path6.resolve)(sourceDir)}${import_path6.sep}`)) throw new TypeError("Code source path escapes its root");
3781
- await (0, import_promises7.mkdir)((0, import_path6.dirname)(target), { recursive: true });
3782
- await (0, import_promises7.writeFile)(target, file.content, { flag: "wx", mode: 420 });
3995
+ const target = (0, import_path8.resolve)(sourceDir, file.path);
3996
+ if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code source path escapes its root");
3997
+ await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
3998
+ await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 420 });
3999
+ }
4000
+ for (const reference of snapshot.references ?? []) {
4001
+ validateAlias(reference.alias);
4002
+ if (!reference.files.length || reference.files.length > 1e4) throw new TypeError("Code reference file count is invalid");
4003
+ for (const file of reference.files) {
4004
+ validatePath(file.path);
4005
+ const path = `.odla-references/${reference.alias}/${file.path}`;
4006
+ if (seen.has(path)) throw new TypeError("Code reference repeats a path");
4007
+ seen.add(path);
4008
+ bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
4009
+ if (bytes > 80 * 1024 * 1024) throw new TypeError("Code source set exceeds its byte bound");
4010
+ const target = (0, import_path8.resolve)(sourceDir, path);
4011
+ if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
4012
+ await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
4013
+ await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
4014
+ }
3783
4015
  }
3784
- return { sourceDir, cleanup: () => (0, import_promises7.rm)(root, { recursive: true, force: true }) };
4016
+ return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
3785
4017
  } catch (cause) {
3786
- await (0, import_promises7.rm)(root, { recursive: true, force: true });
4018
+ await (0, import_promises8.rm)(root, { recursive: true, force: true });
3787
4019
  throw cause;
3788
4020
  }
3789
4021
  }
4022
+ function validateAlias(alias) {
4023
+ if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias) || alias === "primary") {
4024
+ throw new TypeError("Code reference alias is invalid");
4025
+ }
4026
+ }
4027
+ async function attachCodeRuntimeReferences(workspace, references) {
4028
+ let bytes = 0;
4029
+ for (const reference of references) {
4030
+ validateAlias(reference.alias);
4031
+ for (const file of reference.files) {
4032
+ validatePath(file.path);
4033
+ const path = `.odla-references/${reference.alias}/${file.path}`;
4034
+ bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
4035
+ if (bytes > 64 * 1024 * 1024) throw new TypeError("Code reference set exceeds its byte bound");
4036
+ for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
4037
+ const target = (0, import_path8.resolve)(root, path);
4038
+ if (!target.startsWith(`${(0, import_path8.resolve)(root)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
4039
+ await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
4040
+ await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
4041
+ }
4042
+ }
4043
+ }
4044
+ }
3790
4045
  function validatePath(path) {
3791
4046
  const parts = path.split("/");
3792
4047
  if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." || RESERVED2.has(part) || SECRET2.test(part))) {
@@ -3889,15 +4144,15 @@ async function environment(input, options, tool) {
3889
4144
  { id: "authority", value: `lease:${input.lease.leaseId}`, readers: input.readers },
3890
4145
  { id: "reader", value: options.readerId, readers: input.readers }
3891
4146
  ]);
3892
- const digest2 = await destinationRegistryDigest([input.workspaceId]);
4147
+ const digest = await destinationRegistryDigest([input.workspaceId]);
3893
4148
  const approvals = ["irreversible_mutation", "external_send", "financial", "secret_read"];
3894
4149
  if (options.recipeAuthorization === "exact_approval") approvals.push("code_execution");
3895
4150
  const policy = createEffectPolicy({
3896
- destinationRegistries: { [DESTINATIONS]: { digest: digest2, values: [input.workspaceId] } },
4151
+ destinationRegistries: { [DESTINATIONS]: { digest, values: [input.workspaceId] } },
3897
4152
  approvalEffects: approvals
3898
4153
  });
3899
4154
  const fixedArgs = {
3900
- workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest2 },
4155
+ workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest },
3901
4156
  authority: { role: "authority", value: ingress.control("authority") }
3902
4157
  };
3903
4158
  return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
@@ -3968,11 +4223,11 @@ async function read(context, request, options, policy) {
3968
4223
  const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
3969
4224
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
3970
4225
  const target = resolveCodePath(context.workspaceDir, path);
3971
- const info = await (0, import_promises8.stat)(target);
4226
+ const info = await (0, import_promises9.stat)(target);
3972
4227
  if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
3973
4228
  throw new TypeError("file is not a bounded regular source file");
3974
4229
  }
3975
- const source = await (0, import_promises8.readFile)(target);
4230
+ const source = await (0, import_promises9.readFile)(target);
3976
4231
  if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
3977
4232
  const lines = source.toString("utf8").split("\n");
3978
4233
  const content = lines.slice(startLine - 1, endLine).join("\n");
@@ -3985,6 +4240,9 @@ async function patch(context, request, options, policy) {
3985
4240
  exactKeys(request.input, ["patch"]);
3986
4241
  const value = stringField(request.input, "patch");
3987
4242
  const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
4243
+ if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
4244
+ throw new TypeError("patch targets a read-only reference source");
4245
+ }
3988
4246
  const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
3989
4247
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
3990
4248
  await applyCodePatch(context.workspaceDir, value, paths);
@@ -4046,12 +4304,12 @@ function policyContext(context, request, options, extra) {
4046
4304
  async function registeredFiles(root, limit) {
4047
4305
  const paths = [];
4048
4306
  const walk = async (directory) => {
4049
- for (const entry of await (0, import_promises8.readdir)(directory, { withFileTypes: true })) {
4307
+ for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
4050
4308
  if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
4051
- const target = (0, import_path7.resolve)(directory, entry.name);
4309
+ const target = (0, import_path9.resolve)(directory, entry.name);
4052
4310
  if (entry.isDirectory()) await walk(target);
4053
4311
  else if (entry.isFile()) {
4054
- const path = (0, import_path7.relative)(root, target).split("\\").join("/");
4312
+ const path = (0, import_path9.relative)(root, target).split("\\").join("/");
4055
4313
  try {
4056
4314
  validateRelativePath(path);
4057
4315
  } catch {
@@ -4062,7 +4320,7 @@ async function registeredFiles(root, limit) {
4062
4320
  }
4063
4321
  }
4064
4322
  };
4065
- await walk((0, import_path7.resolve)(root));
4323
+ await walk((0, import_path9.resolve)(root));
4066
4324
  return paths.sort();
4067
4325
  }
4068
4326
  function validateOptions(options) {
@@ -4070,6 +4328,9 @@ function validateOptions(options) {
4070
4328
  throw new TypeError("Code tool broker requires a reader and unique registered recipes");
4071
4329
  }
4072
4330
  for (const recipe2 of options.recipes) assertCodeBuildRecipe(recipe2);
4331
+ if (options.readOnlyPrefixes?.some((prefix) => !/^[A-Za-z0-9_.-]+$/.test(prefix) || prefix === "." || prefix === "..")) {
4332
+ throw new TypeError("Code tool broker read-only prefix is invalid");
4333
+ }
4073
4334
  }
4074
4335
  function exactKeys(input, allowed) {
4075
4336
  if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
@@ -4097,14 +4358,31 @@ function codeCommandMetadata(payload, resume) {
4097
4358
  }
4098
4359
  const planning = trusted?.planningInputDigest;
4099
4360
  const attestation = trusted?.attestationDigest;
4361
+ const repository = trusted?.repository;
4362
+ const baseCommitSha = trusted?.commitSha;
4363
+ const sourceTreeDigest = trusted?.treeDigest;
4364
+ 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)) {
4365
+ throw new TypeError(`invalid Code ${resume ? "resume" : "start"} trusted base`);
4366
+ }
4100
4367
  return {
4101
4368
  role,
4102
4369
  title,
4103
4370
  prompt,
4104
4371
  planningInputDigest: typeof planning === "string" && /^sha256:[0-9a-f]{64}$/.test(planning) ? planning : null,
4105
- attestationDigest: typeof attestation === "string" ? attestation : "resume"
4372
+ attestationDigest: typeof attestation === "string" ? attestation : "resume",
4373
+ repository,
4374
+ baseCommitSha,
4375
+ sourceTreeDigest
4106
4376
  };
4107
4377
  }
4378
+ function codeLocalSource(payload) {
4379
+ const source = record22(payload.source);
4380
+ if (!source) return null;
4381
+ 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) {
4382
+ throw new TypeError("invalid local checkout source descriptor");
4383
+ }
4384
+ return source;
4385
+ }
4108
4386
  function codeCheckpointPayload(payload) {
4109
4387
  const value = payload.checkpoint;
4110
4388
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
@@ -4133,11 +4411,63 @@ function fakeCodeLease(command, metadata2) {
4133
4411
  };
4134
4412
  }
4135
4413
  var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4414
+ var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
4415
+ async function prepareRuntimeLocalSource(input) {
4416
+ const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
4417
+ if (!available || JSON.stringify(available.descriptor) !== JSON.stringify(descriptor2) || descriptor2.repository.toLowerCase() !== repository.toLowerCase() || descriptor2.headCommitSha !== baseCommitSha) {
4418
+ throw new TypeError("the session's local checkout snapshot is not available on this terminal");
4419
+ }
4420
+ const workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4421
+ trustedBaseDir: available.trustedBaseDir,
4422
+ trustedBaseCommitSha: baseCommitSha,
4423
+ checkpoint: codeCheckpointPayload(command.payload)
4424
+ })).workspace : await stageWorkspacePair(available.trustedBaseDir, available.sourceDir, SOURCE_LIMITS);
4425
+ const trustedBaseDigest = await digestStagedWorkspace(workspace.baselineDir, SOURCE_LIMITS);
4426
+ if (trustedBaseDigest !== descriptor2.trustedBaseDigest) {
4427
+ await workspace.cleanup();
4428
+ throw new TypeError("trusted Git base digest changed after connection");
4429
+ }
4430
+ if (!resume && await digestStagedWorkspace(workspace.workspaceDir, SOURCE_LIMITS) !== descriptor2.snapshotDigest) {
4431
+ await workspace.cleanup();
4432
+ throw new TypeError("local checkout snapshot digest changed after connection");
4433
+ }
4434
+ return { workspace, sourceDigest: descriptor2.snapshotDigest, trustedBaseDigest };
4435
+ }
4436
+ function createCodeRuntimeToolBroker(input, lease, role) {
4437
+ const broker = createCodeToolBroker({
4438
+ recipes: input.recipes,
4439
+ recipeExecutor: createContainerRecipeExecutor(input.engine),
4440
+ recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
4441
+ readerId: `code-session:${lease.task.taskId}`,
4442
+ readOnlyPrefixes: [".odla-references"]
4443
+ });
4444
+ 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" }) };
4445
+ }
4446
+ async function appendCodeRuntimeEvent(control, command, event, refs) {
4447
+ const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
4448
+ refs.push(eventId);
4449
+ const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
4450
+ await control.appendSessionEvent(command.sessionId, eventId, bounded);
4451
+ }
4452
+ var digestRuntimeValue = (value) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value).digest("hex")}`;
4453
+ var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
4454
+ var runtimeRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4455
+ var safeRuntimeJson = (value) => {
4456
+ try {
4457
+ return JSON.stringify(value).slice(0, 1e4);
4458
+ } catch {
4459
+ return "[event]";
4460
+ }
4461
+ };
4462
+ function runtimeResultText(value) {
4463
+ const record32 = runtimeRecord(value);
4464
+ return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4465
+ }
4136
4466
  var CodePiRuntimeEngine = class {
4137
4467
  constructor(options) {
4138
4468
  this.options = options;
4139
4469
  this.#run = options.runAttempt ?? runContainerAttempt;
4140
- this.#buildPolicyDigest = digest(JSON.stringify(options.recipes));
4470
+ this.#buildPolicyDigest = digestRuntimeValue(JSON.stringify(options.recipes));
4141
4471
  this.#checkpoints = new CodeRuntimeCheckpointManager({
4142
4472
  control: options.control,
4143
4473
  recipes: options.recipes,
@@ -4172,19 +4502,43 @@ var CodePiRuntimeEngine = class {
4172
4502
  }
4173
4503
  async #start(command, resume) {
4174
4504
  if (this.#active.has(command.sessionId)) throw new TypeError("Code session is already active on this runtime");
4175
- const source = await this.options.control.source(command.sessionId);
4176
- const materialized = await materializeCodeRuntimeSource(source);
4505
+ const metadata2 = codeCommandMetadata(command.payload, resume);
4506
+ const requestedLocal = codeLocalSource(command.payload);
4177
4507
  let workspace;
4178
- try {
4179
- workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4180
- trustedBaseDir: materialized.sourceDir,
4181
- trustedBaseCommitSha: source.commitSha,
4182
- checkpoint: codeCheckpointPayload(command.payload)
4183
- })).workspace : await stageWorkspace(materialized.sourceDir);
4184
- } finally {
4185
- await materialized.cleanup();
4508
+ let sourceDigest;
4509
+ let localTrustedBaseDigest;
4510
+ if (requestedLocal) {
4511
+ const prepared = await prepareRuntimeLocalSource({
4512
+ command,
4513
+ descriptor: requestedLocal,
4514
+ available: this.options.localSource,
4515
+ repository: metadata2.repository,
4516
+ baseCommitSha: metadata2.baseCommitSha,
4517
+ resume
4518
+ });
4519
+ ({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
4520
+ if (command.payload.sourceSet) {
4521
+ const selected = await this.options.control.source(command.sessionId);
4522
+ if (selected.repository !== metadata2.repository || selected.commitSha !== metadata2.baseCommitSha || selected.treeDigest !== metadata2.sourceTreeDigest) {
4523
+ await workspace.cleanup();
4524
+ throw new TypeError("Code local source does not match the selected GitHub primary source");
4525
+ }
4526
+ await attachCodeRuntimeReferences(workspace, selected.references ?? []);
4527
+ }
4528
+ } else {
4529
+ const source = await this.options.control.source(command.sessionId);
4530
+ const materialized = await materializeCodeRuntimeSource(source);
4531
+ try {
4532
+ workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4533
+ trustedBaseDir: materialized.sourceDir,
4534
+ trustedBaseCommitSha: source.commitSha,
4535
+ checkpoint: codeCheckpointPayload(command.payload)
4536
+ })).workspace : await stageWorkspace(materialized.sourceDir);
4537
+ } finally {
4538
+ await materialized.cleanup();
4539
+ }
4540
+ sourceDigest = source.treeDigest;
4186
4541
  }
4187
- const metadata2 = codeCommandMetadata(command.payload, resume);
4188
4542
  const abort = new AbortController();
4189
4543
  const conversationRefs = [];
4190
4544
  const active = {
@@ -4194,21 +4548,30 @@ var CodePiRuntimeEngine = class {
4194
4548
  acknowledged: false,
4195
4549
  role: metadata2.role,
4196
4550
  title: metadata2.title,
4197
- baseCommitSha: source.commitSha,
4198
- trustedBaseDigest: await digestStagedWorkspace(workspace.baselineDir, {
4199
- maxFiles: 1e4,
4200
- maxBytes: 16 * 1024 * 1024
4551
+ baseCommitSha: metadata2.baseCommitSha,
4552
+ repository: metadata2.repository,
4553
+ sourceTreeDigest: metadata2.sourceTreeDigest,
4554
+ trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
4555
+ maxFiles: 2e4,
4556
+ maxBytes: 512 * 1024 * 1024
4201
4557
  }),
4202
- planningInputDigest: metadata2.planningInputDigest ?? digest(
4203
- JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: source.treeDigest })
4558
+ planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
4559
+ JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })
4204
4560
  ),
4205
4561
  done: Promise.resolve(null)
4206
4562
  };
4207
4563
  this.#active.set(command.sessionId, active);
4564
+ if (requestedLocal) {
4565
+ await this.#event(command, {
4566
+ type: "message",
4567
+ actor: "system",
4568
+ body: `Source snapshot: local checkout ${requestedLocal.snapshotDigest} \xB7 ${requestedLocal.modified ? "modified" : "clean"} \xB7 Git ${requestedLocal.headCommitSha}`
4569
+ }, conversationRefs);
4570
+ }
4208
4571
  active.done = this.#runAttempt(command, metadata2, active).catch(async (cause) => {
4209
- await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` }, conversationRefs).catch(() => void 0);
4572
+ await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` }, conversationRefs).catch(() => void 0);
4210
4573
  await this.#event(command, { type: "status", status: "failed" }, conversationRefs).catch(() => void 0);
4211
- await this.#failure(command, active, message2(cause));
4574
+ await this.#failure(command, active, runtimeErrorMessage(cause));
4212
4575
  return null;
4213
4576
  });
4214
4577
  return { status: "running", message: resume ? "Pi resumed from a portable checkpoint" : "Pi started" };
@@ -4228,22 +4591,29 @@ var CodePiRuntimeEngine = class {
4228
4591
  title: active.title,
4229
4592
  prompt,
4230
4593
  planningInputDigest: active.planningInputDigest,
4231
- attestationDigest: "follow-up"
4594
+ attestationDigest: "follow-up",
4595
+ repository: active.repository,
4596
+ baseCommitSha: active.baseCommitSha,
4597
+ sourceTreeDigest: active.sourceTreeDigest
4232
4598
  }, active).catch(async (cause) => {
4233
4599
  await this.#event(
4234
4600
  command,
4235
- { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` },
4601
+ { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` },
4236
4602
  active.conversationRefs
4237
4603
  ).catch(() => void 0);
4238
4604
  await this.#event(command, { type: "status", status: "failed" }, active.conversationRefs).catch(() => void 0);
4239
- await this.#failure(command, active, message2(cause));
4605
+ await this.#failure(command, active, runtimeErrorMessage(cause));
4240
4606
  return null;
4241
4607
  });
4242
4608
  return { status: "running", message: "Pi accepted the owner prompt" };
4243
4609
  }
4244
4610
  async #runAttempt(command, metadata2, active) {
4245
4611
  const lease = fakeCodeLease(command, metadata2);
4246
- const broker = this.#toolBroker(lease, metadata2.role);
4612
+ const broker = createCodeRuntimeToolBroker({
4613
+ recipes: this.options.recipes,
4614
+ engine: this.options.engine,
4615
+ recipeAuthorization: this.options.recipeAuthorization
4616
+ }, lease, metadata2.role);
4247
4617
  const startedAt = Date.now();
4248
4618
  let completionSeen = false;
4249
4619
  const result = await this.#run({
@@ -4303,7 +4673,7 @@ var CodePiRuntimeEngine = class {
4303
4673
  return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
4304
4674
  }
4305
4675
  if (output.type === "event") {
4306
- const payload = object2(output.payload);
4676
+ const payload = runtimeRecord(output.payload);
4307
4677
  if (output.kind === "pi.started") {
4308
4678
  await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
4309
4679
  } else if (output.kind === "pi.thinking" && payload?.available === true && Number.isSafeInteger(payload.durationMs) && Number(payload.durationMs) >= 0) {
@@ -4316,12 +4686,12 @@ var CodePiRuntimeEngine = class {
4316
4686
  await this.#event(command, {
4317
4687
  type: "message",
4318
4688
  actor: "system",
4319
- body: `${output.kind}${output.payload === void 0 ? "" : ` ${json(output.payload)}`}`
4689
+ body: `${output.kind}${output.payload === void 0 ? "" : ` ${safeRuntimeJson(output.payload)}`}`
4320
4690
  }, active.conversationRefs);
4321
4691
  }
4322
4692
  } else if (output.type === "attempt.complete") {
4323
4693
  completionSeen = true;
4324
- const body = resultText(output.result) ?? `Pi ${output.status}.`;
4694
+ const body = runtimeResultText(output.result) ?? `Pi ${output.status}.`;
4325
4695
  await this.#event(command, {
4326
4696
  type: "message",
4327
4697
  actor: output.status === "completed" ? "agent" : "system",
@@ -4348,15 +4718,6 @@ var CodePiRuntimeEngine = class {
4348
4718
  }
4349
4719
  return result;
4350
4720
  }
4351
- #toolBroker(lease, role) {
4352
- const broker = createCodeToolBroker({
4353
- recipes: this.options.recipes,
4354
- recipeExecutor: createContainerRecipeExecutor(this.options.engine),
4355
- recipeAuthorization: this.options.recipeAuthorization ?? "registered_recipe",
4356
- readerId: `code-session:${lease.task.taskId}`
4357
- });
4358
- 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" }) };
4359
- }
4360
4721
  async #checkpoint(command) {
4361
4722
  const active = this.#active.get(command.sessionId);
4362
4723
  if (!active) throw new TypeError("Code session workspace is not active on this runtime");
@@ -4371,26 +4732,9 @@ var CodePiRuntimeEngine = class {
4371
4732
  }
4372
4733
  }
4373
4734
  async #event(command, event, refs) {
4374
- const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
4375
- refs.push(eventId);
4376
- const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
4377
- await this.options.control.appendSessionEvent(command.sessionId, eventId, bounded);
4378
- }
4379
- };
4380
- var object2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4381
- var digest = (value) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value).digest("hex")}`;
4382
- var message2 = (value) => value instanceof Error ? value.message : String(value);
4383
- var json = (value) => {
4384
- try {
4385
- return JSON.stringify(value).slice(0, 1e4);
4386
- } catch {
4387
- return "[event]";
4735
+ await appendCodeRuntimeEvent(this.options.control, command, event, refs);
4388
4736
  }
4389
4737
  };
4390
- function resultText(value) {
4391
- const record32 = object2(value);
4392
- return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4393
- }
4394
4738
 
4395
4739
  // src/help.ts
4396
4740
  var import_node_fs6 = require("fs");
@@ -4532,8 +4876,8 @@ async function requestHostedSecurityJson(options, path, init, action) {
4532
4876
  const body = await response2.json().catch(() => ({}));
4533
4877
  if (!response2.ok) {
4534
4878
  const code = optionalHostedText(body.error?.code, "error code", 128);
4535
- const message3 = optionalHostedText(body.error?.message, "error message", 300);
4536
- throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message3 ? `: ${message3}` : ""}`);
4879
+ const message2 = optionalHostedText(body.error?.message, "error message", 300);
4880
+ throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
4537
4881
  }
4538
4882
  return body;
4539
4883
  }
@@ -4722,24 +5066,111 @@ async function defaultReadOrigin(cwd) {
4722
5066
  return result.stdout;
4723
5067
  }
4724
5068
 
4725
- // src/code-connect.ts
4726
- var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:713db58428bc05cb486167c5747de8fd826669c9b283ff78e0139ac194c3d058";
5069
+ // src/code-local-source.ts
5070
+ var import_node_child_process3 = require("child_process");
5071
+ var import_node_crypto = require("crypto");
5072
+ var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
5073
+ async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
5074
+ const headCommitSha = await readHead(cwd);
5075
+ const staged = await stageWorkspace(cwd, { ...SOURCE_LIMITS2, gitTrackedAndUnignored: true });
5076
+ let trusted;
5077
+ try {
5078
+ trusted = await materializeGitTree(cwd, headCommitSha, SOURCE_LIMITS2);
5079
+ const comparison = await stageWorkspacePair(trusted.sourceDir, staged.baselineDir, SOURCE_LIMITS2);
5080
+ let developerPatch;
5081
+ try {
5082
+ developerPatch = await comparison.patch(4 * 1024 * 1024);
5083
+ } finally {
5084
+ await comparison.cleanup();
5085
+ }
5086
+ const descriptor2 = {
5087
+ kind: "local_checkout",
5088
+ repository,
5089
+ headCommitSha,
5090
+ trustedBaseDigest: await digestStagedWorkspace(trusted.sourceDir, SOURCE_LIMITS2),
5091
+ developerPatchDigest: digestText(developerPatch),
5092
+ snapshotDigest: await digestStagedWorkspace(staged.baselineDir, SOURCE_LIMITS2),
5093
+ modified: developerPatch.length > 0,
5094
+ fileCount: staged.fileCount,
5095
+ byteCount: staged.byteCount,
5096
+ capturedAt: Date.now()
5097
+ };
5098
+ return {
5099
+ descriptor: descriptor2,
5100
+ trustedBaseDir: trusted.sourceDir,
5101
+ sourceDir: staged.baselineDir,
5102
+ cleanup: async () => {
5103
+ await Promise.all([staged.cleanup(), trusted.cleanup()]);
5104
+ }
5105
+ };
5106
+ } catch (error) {
5107
+ await Promise.all([staged.cleanup(), trusted?.cleanup()]);
5108
+ throw error;
5109
+ }
5110
+ }
5111
+ async function readGitHead(cwd) {
5112
+ const value = await new Promise((accept, reject) => {
5113
+ (0, import_node_child_process3.execFile)("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
5114
+ if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
5115
+ else accept(stdout.trim());
5116
+ });
5117
+ });
5118
+ if (!/^[0-9a-f]{40}$/.test(value)) throw new Error("code connect could not resolve the checkout HEAD commit");
5119
+ return value;
5120
+ }
5121
+ function digestText(value) {
5122
+ return `sha256:${(0, import_node_crypto.createHash)("sha256").update(value).digest("hex")}`;
5123
+ }
5124
+
5125
+ // src/code-images.ts
5126
+ var import_node_child_process4 = require("child_process");
5127
+ async function prepareCodeImages(engine, images) {
5128
+ for (const image of images) {
5129
+ await new Promise((accept, reject) => {
5130
+ const args = engine === "container" ? ["image", "pull", image] : ["pull", image];
5131
+ (0, import_node_child_process4.execFile)(engine, args, { encoding: "utf8", maxBuffer: 2 * 1024 * 1024, timeout: 10 * 6e4 }, (error) => {
5132
+ if (error) reject(new Error(`could not prepare pinned Code image ${image}`));
5133
+ else accept();
5134
+ });
5135
+ });
5136
+ }
5137
+ }
5138
+
5139
+ // src/code-runtime-config.ts
5140
+ var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:e7858b4f0291543171b2659b47c5d00f97acc4767cafd9b74ae5228899e0cde4";
4727
5141
  var CODE_BUILD_RECIPES = Object.freeze([{
4728
- id: "node-test",
5142
+ id: "odla-code-contracts",
4729
5143
  image: "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd",
4730
- command: ["node", "--test"],
5144
+ command: [
5145
+ "node",
5146
+ "--test",
5147
+ "scripts/lib/directories.test.mjs",
5148
+ "scripts/lib/internal-deps.test.mjs",
5149
+ "scripts/lib/npm-audit.test.mjs",
5150
+ "scripts/lib/npm-release-contract.test.mjs",
5151
+ "scripts/lib/release-surfaces.test.mjs"
5152
+ ],
4731
5153
  timeoutMs: 12e4,
4732
5154
  maxOutputBytes: 1024 * 1024,
4733
5155
  cpus: 1,
4734
5156
  memory: "512m",
4735
5157
  pids: 128
4736
5158
  }]);
5159
+
5160
+ // src/code-connect.ts
4737
5161
  async function codeConnect(options) {
4738
5162
  const cwd = options.cwd ?? process.cwd();
4739
5163
  const configPath = (0, import_node_path4.resolve)(cwd, options.configPath);
4740
5164
  const cfg = (0, import_node_fs7.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
4741
- const repository = cfg ? null : await inferGitHubRepository(cwd, options.readGitOrigin);
4742
- const platform = cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai";
5165
+ const requestedAppId = options.appId?.trim();
5166
+ if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
5167
+ throw new Error("--app-id must be a valid odla app id");
5168
+ }
5169
+ if (requestedAppId && cfg && requestedAppId !== cfg.app.id) {
5170
+ throw new Error(`--app-id ${requestedAppId} does not match ${options.configPath} (${cfg.app.id})`);
5171
+ }
5172
+ const appId = requestedAppId ?? cfg?.app.id;
5173
+ const platform = (options.platform ?? cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai").replace(/\/+$/, "");
4743
5174
  const appEnv = options.env ?? (cfg ? cfg.envs.includes("dev") ? "dev" : cfg.envs[0] : "dev");
4744
5175
  if (appEnv !== "dev" && appEnv !== "prod" || cfg && !cfg.envs.includes(appEnv)) {
4745
5176
  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`);
@@ -4758,53 +5189,77 @@ async function codeConnect(options) {
4758
5189
  const out = options.stdout ?? console;
4759
5190
  const doFetch = options.fetch ?? fetch;
4760
5191
  const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
5192
+ await (options.prepareImages ?? prepareCodeImages)(engine, [
5193
+ CODE_PI_IMAGE,
5194
+ ...new Set(CODE_BUILD_RECIPES.map((recipe2) => recipe2.image))
5195
+ ]);
4761
5196
  const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
4762
5197
  const hostName = (options.name ?? (0, import_node_os.hostname)()).trim();
4763
5198
  if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
4764
- const approval = await (options.getToken ?? getScopedPlatformToken)({
4765
- platform,
4766
- scope: "platform:code:host:connect",
4767
- email: options.email,
4768
- open: options.open,
4769
- fetch: doFetch,
4770
- stdout: out,
4771
- openApprovalUrl: options.openApprovalUrl,
4772
- cache: false,
4773
- label: `odla CLI Code host for ${cfg?.app.id ?? repository}/${appEnv}`
4774
- });
4775
- const target = cfg ? { appId: cfg.app.id } : { repository };
4776
- const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
4777
- method: "POST",
4778
- headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
4779
- body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
4780
- redirect: "error",
4781
- signal: options.signal
4782
- });
4783
- const raw = await response2.json().catch(() => null);
4784
- if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
4785
- const connection = parseConnection(raw, cfg?.app.id, appEnv);
4786
- const capabilities = {
4787
- protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
4788
- platform: hostPlatform,
4789
- arch: process.arch,
4790
- engines: [engine],
4791
- cpuCount: (0, import_node_os.cpus)().length,
4792
- memoryBytes: (0, import_node_os.totalmem)()
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("credentials: stored in odla-ai/db; host plaintext is memory-only");
4797
- await (options.runRuntime ?? runCodeRuntime)({
4798
- endpoint: platform,
4799
- token: connection.token,
4800
- engine,
4801
- capabilities,
4802
- heartbeatMs,
4803
- once: options.once === true,
4804
- signal: options.signal,
4805
- stdout: out,
4806
- fetch: doFetch
4807
- });
5199
+ const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
5200
+ const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
5201
+ cwd,
5202
+ repository,
5203
+ options.readGitHead
5204
+ );
5205
+ try {
5206
+ const descriptor2 = localSource.descriptor;
5207
+ const approval = await (options.getToken ?? getScopedPlatformToken)({
5208
+ platform,
5209
+ scope: "app:code:host:connect",
5210
+ email: options.email,
5211
+ open: options.open,
5212
+ fetch: doFetch,
5213
+ stdout: out,
5214
+ openApprovalUrl: options.openApprovalUrl,
5215
+ cache: false,
5216
+ label: `Terminal connection for ${appId ?? repository}/${appEnv}`
5217
+ });
5218
+ const target = appId ? { appId } : { repository };
5219
+ const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
5220
+ method: "POST",
5221
+ headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
5222
+ body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
5223
+ redirect: "error",
5224
+ signal: options.signal
5225
+ });
5226
+ const raw = await response2.json().catch(() => null);
5227
+ if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
5228
+ const connection = parseConnection(raw, appId, appEnv);
5229
+ const capabilities = {
5230
+ protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
5231
+ platform: hostPlatform,
5232
+ arch: process.arch,
5233
+ engines: [engine],
5234
+ cpuCount: (0, import_node_os.cpus)().length,
5235
+ memoryBytes: (0, import_node_os.totalmem)(),
5236
+ source: descriptor2,
5237
+ images: {
5238
+ ready: true,
5239
+ pi: CODE_PI_IMAGE,
5240
+ recipes: CODE_BUILD_RECIPES.map((recipe2) => ({ id: recipe2.id, image: recipe2.image }))
5241
+ }
5242
+ };
5243
+ out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
5244
+ out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
5245
+ out.log(`source: local checkout \xB7 ${descriptor2.headCommitSha.slice(0, 10)} \xB7 ${descriptor2.fileCount} files \xB7 staged copy`);
5246
+ out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
5247
+ out.log(`Studio: ${platform}/studio/apps/${encodeURIComponent(connection.binding.appId)}/${connection.binding.env}/code/sessions`);
5248
+ await (options.runRuntime ?? runCodeRuntime)({
5249
+ endpoint: platform,
5250
+ token: connection.token,
5251
+ engine,
5252
+ capabilities,
5253
+ localSource,
5254
+ heartbeatMs,
5255
+ once: options.once === true,
5256
+ signal: options.signal,
5257
+ stdout: out,
5258
+ fetch: doFetch
5259
+ });
5260
+ } finally {
5261
+ await localSource.cleanup();
5262
+ }
4808
5263
  }
4809
5264
  async function runCodeRuntime(input) {
4810
5265
  const controller = new AbortController();
@@ -4827,7 +5282,8 @@ async function runCodeRuntime(input) {
4827
5282
  engine: input.engine,
4828
5283
  image: CODE_PI_IMAGE,
4829
5284
  recipes: CODE_BUILD_RECIPES,
4830
- recipeAuthorization: "registered_recipe"
5285
+ recipeAuthorization: "registered_recipe",
5286
+ localSource: input.localSource
4831
5287
  });
4832
5288
  const reconciler = new CodeRuntimeReconciler(control, commandEngine);
4833
5289
  try {
@@ -4868,8 +5324,8 @@ function parseConnection(value, appId, appEnv) {
4868
5324
  return root;
4869
5325
  }
4870
5326
  function apiFailure(action, status, value) {
4871
- const message3 = record4(record4(value)?.error)?.message;
4872
- return `${action} failed (${status})${typeof message3 === "string" ? `: ${message3}` : ""}`;
5327
+ const message2 = record4(record4(value)?.error)?.message;
5328
+ return `${action} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
4873
5329
  }
4874
5330
  function record4(value) {
4875
5331
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -4884,6 +5340,8 @@ async function codeCommand(parsed, dependencies) {
4884
5340
  assertArgs(parsed, [
4885
5341
  "config",
4886
5342
  "env",
5343
+ "platform",
5344
+ "app-id",
4887
5345
  "email",
4888
5346
  "open",
4889
5347
  "name",
@@ -4899,6 +5357,8 @@ async function codeCommand(parsed, dependencies) {
4899
5357
  await (dependencies.codeConnect ?? codeConnect)({
4900
5358
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
4901
5359
  env: stringOpt(parsed.options.env),
5360
+ platform: stringOpt(parsed.options.platform),
5361
+ appId: stringOpt(parsed.options["app-id"]),
4902
5362
  email: stringOpt(parsed.options.email),
4903
5363
  open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
4904
5364
  name: stringOpt(parsed.options.name),
@@ -4914,16 +5374,16 @@ async function codeCommand(parsed, dependencies) {
4914
5374
  }
4915
5375
 
4916
5376
  // src/doctor-checks.ts
4917
- var import_node_child_process4 = require("child_process");
5377
+ var import_node_child_process6 = require("child_process");
4918
5378
  var import_node_fs9 = require("fs");
4919
5379
  var import_node_path6 = require("path");
4920
5380
 
4921
5381
  // src/wrangler.ts
4922
- var import_node_child_process3 = require("child_process");
5382
+ var import_node_child_process5 = require("child_process");
4923
5383
  var import_node_fs8 = require("fs");
4924
5384
  var import_node_path5 = require("path");
4925
5385
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
4926
- const child = (0, import_node_child_process3.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
5386
+ const child = (0, import_node_child_process5.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4927
5387
  let stdout = "";
4928
5388
  let stderr = "";
4929
5389
  child.stdout.on("data", (chunk) => stdout += chunk.toString());
@@ -5014,7 +5474,7 @@ function lintRules(rules, entities, publicRead) {
5014
5474
  }
5015
5475
  return warnings;
5016
5476
  }
5017
- var defaultExec = (cmd, args, cwd) => (0, import_node_child_process4.execFileSync)(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
5477
+ var defaultExec = (cmd, args, cwd) => (0, import_node_child_process6.execFileSync)(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
5018
5478
  function trackedSecretFiles(rootDir, exec = defaultExec, localPaths = []) {
5019
5479
  let output;
5020
5480
  try {
@@ -5622,8 +6082,8 @@ async function readRegistryApp(cfg, token, doFetch) {
5622
6082
  });
5623
6083
  if (res.status === 404) return null;
5624
6084
  if (!res.ok) return null;
5625
- const json2 = await res.json();
5626
- return json2.app ?? null;
6085
+ const json = await res.json();
6086
+ return json.app ?? null;
5627
6087
  }
5628
6088
  async function postJson2(doFetch, url, bearer, body) {
5629
6089
  const res = await doFetch(url, {
@@ -5862,10 +6322,10 @@ async function provision(options) {
5862
6322
  stdout: out
5863
6323
  });
5864
6324
  } catch (error) {
5865
- const message3 = error instanceof Error ? error.message : String(error);
6325
+ const message2 = error instanceof Error ? error.message : String(error);
5866
6326
  const consent = env === "prod" || env === "production" ? " --yes" : "";
5867
6327
  throw new Error(
5868
- `${message3}
6328
+ `${message2}
5869
6329
  ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}${consent}" without issuing or rotating again`,
5870
6330
  { cause: error }
5871
6331
  );
@@ -5981,7 +6441,7 @@ async function resolveVaultWrite(options) {
5981
6441
  }
5982
6442
 
5983
6443
  // src/security-command-context.ts
5984
- var import_promises9 = require("readline/promises");
6444
+ var import_promises10 = require("readline/promises");
5985
6445
  async function hostedSecurityContext(parsed, dependencies) {
5986
6446
  const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
5987
6447
  const cfg = await loadProjectConfig(configPath);
@@ -6004,12 +6464,12 @@ async function hostedSecurityContext(parsed, dependencies) {
6004
6464
  );
6005
6465
  return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
6006
6466
  }
6007
- async function interactiveConfirmation(message3, dependencies) {
6008
- if (dependencies.confirm) return dependencies.confirm(message3);
6467
+ async function interactiveConfirmation(message2, dependencies) {
6468
+ if (dependencies.confirm) return dependencies.confirm(message2);
6009
6469
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
6010
- const prompt = (0, import_promises9.createInterface)({ input: process.stdin, output: process.stdout });
6470
+ const prompt = (0, import_promises10.createInterface)({ input: process.stdin, output: process.stdout });
6011
6471
  try {
6012
- const answer = await prompt.question(`${message3} [y/N] `);
6472
+ const answer = await prompt.question(`${message2} [y/N] `);
6013
6473
  return /^y(?:es)?$/i.test(answer.trim());
6014
6474
  } finally {
6015
6475
  prompt.close();
@@ -6136,7 +6596,7 @@ var import_security2 = require("@odla-ai/security");
6136
6596
  // src/security.ts
6137
6597
  var import_node_path8 = require("path");
6138
6598
  var import_security = require("@odla-ai/security");
6139
- var import_node2 = require("@odla-ai/security/node");
6599
+ var import_node3 = require("@odla-ai/security/node");
6140
6600
  async function runHostedSecurity(options) {
6141
6601
  if (options.sourceDisclosureAck !== "redacted") {
6142
6602
  throw new Error("Hosted security requires sourceDisclosureAck=redacted before repository source may leave this process");
@@ -6159,7 +6619,7 @@ async function runHostedSecurity(options) {
6159
6619
  scope: selfAudit ? "platform:security:self" : null
6160
6620
  };
6161
6621
  const token = await injectedToken(options, tokenRequest);
6162
- const snapshot = await (0, import_node2.snapshotDirectory)(target, {
6622
+ const snapshot = await (0, import_node3.snapshotDirectory)(target, {
6163
6623
  exclude: !outputRelative.startsWith("../") && !(0, import_node_path8.isAbsolute)(outputRelative) ? [outputRelative] : []
6164
6624
  });
6165
6625
  const hosted = await (0, import_security.createPlatformSecurityReasoners)({
@@ -6178,7 +6638,7 @@ async function runHostedSecurity(options) {
6178
6638
  });
6179
6639
  const harness = (0, import_security.createSecurityHarness)({
6180
6640
  profile,
6181
- store: new import_node2.FileRunStore((0, import_node_path8.resolve)(output, "state")),
6641
+ store: new import_node3.FileRunStore((0, import_node_path8.resolve)(output, "state")),
6182
6642
  discoveryReasoner: hosted.discoveryReasoner,
6183
6643
  validationReasoner: hosted.validationReasoner,
6184
6644
  policy: {
@@ -6188,7 +6648,7 @@ async function runHostedSecurity(options) {
6188
6648
  }
6189
6649
  });
6190
6650
  const report2 = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
6191
- await (0, import_node2.writeSecurityArtifacts)(output, report2);
6651
+ await (0, import_node3.writeSecurityArtifacts)(output, report2);
6192
6652
  const reportDigest = await (0, import_security.securityFingerprint)(report2);
6193
6653
  await hosted.complete({
6194
6654
  reportDigest,
@@ -6321,8 +6781,8 @@ async function startHostedSecurityJob(options) {
6321
6781
  "start hosted security job"
6322
6782
  );
6323
6783
  } catch (error) {
6324
- const message3 = error instanceof Error ? error.message : String(error);
6325
- if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message3)) {
6784
+ const message2 = error instanceof Error ? error.message : String(error);
6785
+ if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message2)) {
6326
6786
  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 });
6327
6787
  }
6328
6788
  throw error;