@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/index.cjs CHANGED
@@ -509,12 +509,13 @@ async function scopedToken(platform, scope, options, doFetch, out) {
509
509
  const { token, expiresAt } = await (0, import_db2.requestToken)({
510
510
  endpoint: audience,
511
511
  email,
512
- label: options.label ?? `odla CLI admin AI (${scope})`,
512
+ label: options.label ?? `odla CLI (${scope})`,
513
513
  scopes: [scope],
514
514
  fetch: doFetch,
515
515
  onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
516
516
  const approvalUrl = verificationUriComplete ?? handshakeUrl(audience, userCode);
517
- out.log(`Review, then approve scoped request ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
517
+ const requestName = scope === "app:code:host:connect" ? "terminal connection" : "scoped request";
518
+ out.log(`Review, then approve ${requestName} ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
518
519
  if (approvalBrowser({ open: options.open }).open) {
519
520
  await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
520
521
  }
@@ -576,8 +577,8 @@ async function responseBody(response2) {
576
577
  }
577
578
  function apiError(status, body) {
578
579
  const error = isRecord(body) && isRecord(body.error) ? body.error : void 0;
579
- const message3 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
580
- return `read System AI admin changes failed (${status}): ${message3}`;
580
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
581
+ return `read System AI admin changes failed (${status}): ${message2}`;
581
582
  }
582
583
  function timestamp(value) {
583
584
  if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
@@ -678,8 +679,8 @@ async function responseBody2(res) {
678
679
  }
679
680
  function apiError2(action, status, body) {
680
681
  const error = isRecord2(body) && isRecord2(body.error) ? body.error : void 0;
681
- const message3 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
682
- return `${action} failed (${status}): ${message3}`;
682
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
683
+ return `${action} failed (${status}): ${message2}`;
683
684
  }
684
685
  function isRecord2(value) {
685
686
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -859,8 +860,8 @@ async function responseBody3(res) {
859
860
  }
860
861
  function apiError3(action, status, body) {
861
862
  const error = isRecord3(body) && isRecord3(body.error) ? body.error : void 0;
862
- const message3 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
863
- return `${action} failed (${status}): ${message3}`;
863
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
864
+ return `${action} failed (${status}): ${message2}`;
864
865
  }
865
866
  function isRecord3(value) {
866
867
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -1540,8 +1541,8 @@ var CAPABILITIES = {
1540
1541
  "connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"
1541
1542
  ]
1542
1543
  };
1543
- function printCapabilities(json2 = false, out = console) {
1544
- if (json2) {
1544
+ function printCapabilities(json = false, out = console) {
1545
+ if (json) {
1545
1546
  out.log(JSON.stringify(CAPABILITIES, null, 2));
1546
1547
  return;
1547
1548
  }
@@ -1571,8 +1572,8 @@ var CalendarRequestError = class extends Error {
1571
1572
  status;
1572
1573
  /** Machine-readable `error.code` from the response body, when present. */
1573
1574
  code;
1574
- constructor(message3, status, code) {
1575
- super(message3);
1575
+ constructor(message2, status, code) {
1576
+ super(message2);
1576
1577
  this.name = "CalendarRequestError";
1577
1578
  this.status = status;
1578
1579
  if (code !== void 0) this.code = code;
@@ -1739,8 +1740,8 @@ async function calendarJson(ctx, suffix, init) {
1739
1740
  const body = await response2.json().catch(() => ({}));
1740
1741
  if (!response2.ok) {
1741
1742
  const code = textField(body.error?.code, 128);
1742
- const message3 = textField(body.error?.message, 500);
1743
- const detail = `${code ? ` ${code}` : ""}${message3 ? `: ${message3}` : ""}`;
1743
+ const message2 = textField(body.error?.message, 500);
1744
+ const detail = `${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`;
1744
1745
  throw new CalendarRequestError(redactSecrets(`calendar request failed (${response2.status})${detail}`), response2.status, code);
1745
1746
  }
1746
1747
  return body;
@@ -1976,8 +1977,8 @@ function pollTimeout(value = 10 * 6e4) {
1976
1977
  function productionConsent(env, yes, action) {
1977
1978
  if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action} for "${env}" without --yes`);
1978
1979
  }
1979
- function printStatus(status, json2, out) {
1980
- if (json2) {
1980
+ function printStatus(status, json, out) {
1981
+ if (json) {
1981
1982
  out.log(JSON.stringify(status, null, 2));
1982
1983
  return;
1983
1984
  }
@@ -2019,63 +2020,63 @@ function parseAgentOutput(line) {
2019
2020
  } catch {
2020
2021
  throw new HarnessProtocolError("agent emitted invalid JSON");
2021
2022
  }
2022
- const message3 = record2(value);
2023
- if (!message3 || message3.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
2023
+ const message2 = record2(value);
2024
+ if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
2024
2025
  throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
2025
2026
  }
2026
- if (message3.type === "event") {
2027
+ if (message2.type === "event") {
2027
2028
  return {
2028
2029
  protocolVersion: HARNESS_PROTOCOL_VERSION,
2029
2030
  type: "event",
2030
- kind: boundedText(message3.kind, "event.kind", 120),
2031
- ...message3.payload === void 0 ? {} : { payload: message3.payload }
2031
+ kind: boundedText(message2.kind, "event.kind", 120),
2032
+ ...message2.payload === void 0 ? {} : { payload: message2.payload }
2032
2033
  };
2033
2034
  }
2034
- if (message3.type === "inference.request") {
2035
- const call = record2(message3.call);
2035
+ if (message2.type === "inference.request") {
2036
+ const call = record2(message2.call);
2036
2037
  if (!call || !Array.isArray(call.messages) || !Number.isSafeInteger(call.maxTokens)) {
2037
2038
  throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
2038
2039
  }
2039
2040
  return {
2040
2041
  protocolVersion: HARNESS_PROTOCOL_VERSION,
2041
2042
  type: "inference.request",
2042
- requestId: boundedText(message3.requestId, "requestId", 180),
2043
+ requestId: boundedText(message2.requestId, "requestId", 180),
2043
2044
  call
2044
2045
  };
2045
2046
  }
2046
- if (message3.type === "tool.request") {
2047
- const input = record2(message3.input);
2048
- const tool = String(message3.tool);
2047
+ if (message2.type === "tool.request") {
2048
+ const input = record2(message2.input);
2049
+ const tool = String(message2.tool);
2049
2050
  if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
2050
2051
  throw new HarnessProtocolError("tool.request requires a registered tool and object input");
2051
2052
  }
2052
2053
  return {
2053
2054
  protocolVersion: HARNESS_PROTOCOL_VERSION,
2054
2055
  type: "tool.request",
2055
- requestId: boundedText(message3.requestId, "requestId", 180),
2056
+ requestId: boundedText(message2.requestId, "requestId", 180),
2056
2057
  tool,
2057
2058
  input
2058
2059
  };
2059
2060
  }
2060
- if (message3.type === "attempt.complete") {
2061
- if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(message3.status))) {
2061
+ if (message2.type === "attempt.complete") {
2062
+ if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(message2.status))) {
2062
2063
  throw new HarnessProtocolError("attempt.complete.status is invalid");
2063
2064
  }
2064
2065
  return {
2065
2066
  protocolVersion: HARNESS_PROTOCOL_VERSION,
2066
2067
  type: "attempt.complete",
2067
- status: message3.status,
2068
- ...message3.result === void 0 ? {} : { result: message3.result }
2068
+ status: message2.status,
2069
+ ...message2.result === void 0 ? {} : { result: message2.result }
2069
2070
  };
2070
2071
  }
2071
2072
  throw new HarnessProtocolError("agent message type is unsupported");
2072
2073
  }
2073
- function encodeAgentInput(message3) {
2074
- return `${JSON.stringify(message3)}
2074
+ function encodeAgentInput(message2) {
2075
+ return `${JSON.stringify(message2)}
2075
2076
  `;
2076
2077
  }
2077
2078
 
2078
- // ../harness/dist/chunk-FNYUIJUN.js
2079
+ // ../harness/dist/chunk-TA2FKK27.js
2079
2080
  var import_child_process = require("child_process");
2080
2081
  var import_fs = require("fs");
2081
2082
  var import_promises2 = require("fs/promises");
@@ -2085,6 +2086,11 @@ var import_promises3 = require("fs/promises");
2085
2086
  var import_os = require("os");
2086
2087
  var import_path2 = require("path");
2087
2088
  var import_child_process2 = require("child_process");
2089
+ var import_path3 = require("path");
2090
+ var import_promises4 = require("fs/promises");
2091
+ var import_os2 = require("os");
2092
+ var import_path4 = require("path");
2093
+ var import_child_process3 = require("child_process");
2088
2094
  var DIGEST_IMAGE = /^[a-z0-9][a-z0-9._/-]*(?::[a-zA-Z0-9._-]+)?@sha256:[0-9a-f]{64}$/;
2089
2095
  function assertPinnedImage(image) {
2090
2096
  if (!DIGEST_IMAGE.test(image)) throw new TypeError("container image must be pinned by sha256 digest");
@@ -2128,7 +2134,7 @@ async function selectContainerEngine(requested = "auto", options = {}) {
2128
2134
  throw new TypeError("no supported container engine found");
2129
2135
  }
2130
2136
  function inspectRootlessPodman() {
2131
- return new Promise((resolve23, reject) => {
2137
+ return new Promise((resolve33, reject) => {
2132
2138
  (0, import_child_process.execFile)(
2133
2139
  "podman",
2134
2140
  ["info", "--format", "{{.Host.Security.Rootless}}"],
@@ -2138,7 +2144,7 @@ function inspectRootlessPodman() {
2138
2144
  reject(new TypeError("could not verify that the active Podman service is rootless"));
2139
2145
  return;
2140
2146
  }
2141
- resolve23(stdout.trim() === "true");
2147
+ resolve33(stdout.trim() === "true");
2142
2148
  }
2143
2149
  );
2144
2150
  });
@@ -2249,9 +2255,9 @@ async function runContainerAttempt(options) {
2249
2255
  if (bytes.byteLength > 1e6) throw new Error("agent message exceeds 1 MB");
2250
2256
  const line = bytes.toString("utf8");
2251
2257
  if (!line.trim()) return;
2252
- const message3 = parseAgentOutput(line);
2253
- if (message3.type === "attempt.complete") complete = message3;
2254
- const response2 = await options.onMessage(message3);
2258
+ const message2 = parseAgentOutput(line);
2259
+ if (message2.type === "attempt.complete") complete = message2;
2260
+ const response2 = await options.onMessage(message2);
2255
2261
  if (response2 && !child.stdin.destroyed) child.stdin.write(encodeAgentInput(response2));
2256
2262
  };
2257
2263
  try {
@@ -2299,29 +2305,142 @@ async function runContainerAttempt(options) {
2299
2305
  options.signal?.removeEventListener("abort", abort);
2300
2306
  }
2301
2307
  }
2302
- var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
2303
- var SECRET_FILE = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
2308
+ var SKIP_WORKSPACE_DIRS = /* @__PURE__ */ new Set([
2309
+ ".git",
2310
+ ".odla",
2311
+ ".wrangler",
2312
+ "node_modules",
2313
+ "dist",
2314
+ "coverage"
2315
+ ]);
2316
+ var SECRET_WORKSPACE_FILE = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
2317
+ function allowedWorkspacePath(relativePath) {
2318
+ const parts = relativePath.split("/");
2319
+ 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) ?? "");
2320
+ }
2321
+ async function gitOutput(cwd, args, maxBytes) {
2322
+ const child = (0, import_child_process2.spawn)("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: false });
2323
+ const stdout = [];
2324
+ const stderr = [];
2325
+ let bytes = 0;
2326
+ child.stdout.on("data", (chunk) => {
2327
+ bytes += chunk.byteLength;
2328
+ if (bytes > maxBytes) child.kill("SIGKILL");
2329
+ else stdout.push(chunk);
2330
+ });
2331
+ child.stderr.on("data", (chunk) => {
2332
+ if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
2333
+ });
2334
+ const code = await new Promise((accept, reject) => {
2335
+ child.once("error", reject);
2336
+ child.once("exit", accept);
2337
+ });
2338
+ if (bytes > maxBytes) throw new Error(`git output exceeds ${maxBytes} bytes`);
2339
+ if (code !== 0) throw new Error(`git command failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
2340
+ return Buffer.concat(stdout);
2341
+ }
2342
+ async function gitBlobs(cwd, entries, maxBytes) {
2343
+ const child = (0, import_child_process2.spawn)("git", ["cat-file", "--batch"], { cwd, stdio: ["pipe", "pipe", "pipe"], shell: false });
2344
+ const stdout = [];
2345
+ const stderr = [];
2346
+ let bytes = 0;
2347
+ child.stdout.on("data", (chunk) => {
2348
+ bytes += chunk.byteLength;
2349
+ if (bytes > maxBytes + entries.length * 100) child.kill("SIGKILL");
2350
+ else stdout.push(chunk);
2351
+ });
2352
+ child.stderr.on("data", (chunk) => {
2353
+ if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
2354
+ });
2355
+ child.stdin.end(`${entries.map((entry) => entry.hash).join("\n")}
2356
+ `);
2357
+ const code = await new Promise((accept, reject) => {
2358
+ child.once("error", reject);
2359
+ child.once("exit", accept);
2360
+ });
2361
+ if (bytes > maxBytes + entries.length * 100) throw new Error(`Git tree exceeds ${maxBytes} bytes`);
2362
+ if (code !== 0) throw new Error(`git object read failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
2363
+ const output = Buffer.concat(stdout);
2364
+ const blobs = [];
2365
+ let offset = 0;
2366
+ let total = 0;
2367
+ for (const entry of entries) {
2368
+ const newline = output.indexOf(10, offset);
2369
+ if (newline < 0) throw new Error("git object output is truncated");
2370
+ const match = /^([0-9a-f]{40,64}) blob ([0-9]+)$/.exec(output.subarray(offset, newline).toString("utf8"));
2371
+ if (!match || match[1] !== entry.hash) throw new Error("git object output does not match its inventory");
2372
+ const length = Number(match[2]);
2373
+ if (!Number.isSafeInteger(length) || length < 0 || newline + 1 + length >= output.length) {
2374
+ throw new Error("git object output has an invalid length");
2375
+ }
2376
+ const content = output.subarray(newline + 1, newline + 1 + length);
2377
+ if (output[newline + 1 + length] !== 10) throw new Error("git object output is malformed");
2378
+ total += content.byteLength;
2379
+ if (total > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2380
+ blobs.push(content);
2381
+ offset = newline + 1 + length + 1;
2382
+ }
2383
+ return blobs;
2384
+ }
2385
+ async function materializeGitTree(source, commitSha, options = {}) {
2386
+ if (!/^[0-9a-f]{40}$/.test(commitSha)) throw new TypeError("Git tree requires an exact commit SHA");
2387
+ const sourceDir = await (0, import_promises3.realpath)((0, import_path2.resolve)(source));
2388
+ const maxFiles = options.maxFiles ?? 2e4;
2389
+ const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
2390
+ const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
2391
+ const entries = inventory.flatMap((record5) => {
2392
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record5);
2393
+ return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
2394
+ });
2395
+ if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
2396
+ const root = await (0, import_promises3.mkdtemp)((0, import_path2.join)(options.tempRoot ?? (0, import_os.tmpdir)(), "odla-git-tree-"));
2397
+ const targetRoot = (0, import_path2.join)(root, "source");
2398
+ await (0, import_promises3.mkdir)(targetRoot);
2399
+ let byteCount = 0;
2400
+ try {
2401
+ const blobs = await gitBlobs(sourceDir, entries, maxBytes);
2402
+ for (const [index, entry] of entries.entries()) {
2403
+ const content = blobs[index];
2404
+ byteCount += content.byteLength;
2405
+ if (byteCount > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2406
+ const target = (0, import_path2.resolve)(targetRoot, entry.path);
2407
+ if (!target.startsWith(`${(0, import_path2.resolve)(targetRoot)}${import_path2.sep}`)) throw new TypeError("Git tree path escapes workspace");
2408
+ await (0, import_promises3.mkdir)((0, import_path2.resolve)(target, ".."), { recursive: true });
2409
+ await (0, import_promises3.writeFile)(target, content, { flag: "wx", mode: entry.mode === "100755" ? 493 : 420 });
2410
+ }
2411
+ return {
2412
+ root,
2413
+ sourceDir: targetRoot,
2414
+ fileCount: entries.length,
2415
+ byteCount,
2416
+ cleanup: () => (0, import_promises3.rm)(root, { recursive: true, force: true })
2417
+ };
2418
+ } catch (error) {
2419
+ await (0, import_promises3.rm)(root, { recursive: true, force: true });
2420
+ throw error;
2421
+ }
2422
+ }
2304
2423
  async function sourceFiles(sourceDir, maxFiles, maxBytes) {
2305
2424
  const files = [];
2306
2425
  let bytes = 0;
2307
2426
  const walk = async (dir) => {
2308
- for (const entry of await (0, import_promises3.readdir)(dir, { withFileTypes: true })) {
2309
- if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue;
2310
- if (!entry.isDirectory() && SECRET_FILE.test(entry.name)) continue;
2311
- const path = (0, import_path2.join)(dir, entry.name);
2427
+ for (const entry of await (0, import_promises4.readdir)(dir, { withFileTypes: true })) {
2428
+ if (entry.isDirectory() && SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
2429
+ if (!entry.isDirectory() && SECRET_WORKSPACE_FILE.test(entry.name)) continue;
2430
+ const path = (0, import_path4.join)(dir, entry.name);
2312
2431
  if (entry.isSymbolicLink()) continue;
2313
2432
  if (entry.isDirectory()) {
2314
2433
  await walk(path);
2315
2434
  continue;
2316
2435
  }
2317
2436
  if (!entry.isFile()) continue;
2318
- const metadata2 = await (0, import_promises3.stat)(path);
2437
+ const metadata2 = await (0, import_promises4.stat)(path);
2319
2438
  bytes += metadata2.size;
2320
2439
  if (files.length + 1 > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
2321
2440
  if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2322
2441
  files.push({
2323
2442
  source: path,
2324
- relativePath: (0, import_path2.relative)(sourceDir, path),
2443
+ relativePath: (0, import_path4.relative)(sourceDir, path),
2325
2444
  mode: metadata2.mode & 511,
2326
2445
  bytes: metadata2.size
2327
2446
  });
@@ -2330,16 +2449,62 @@ async function sourceFiles(sourceDir, maxFiles, maxBytes) {
2330
2449
  await walk(sourceDir);
2331
2450
  return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
2332
2451
  }
2452
+ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
2453
+ const child = (0, import_child_process3.spawn)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
2454
+ cwd: sourceDir,
2455
+ stdio: ["ignore", "pipe", "pipe"],
2456
+ shell: false
2457
+ });
2458
+ const stdout = [];
2459
+ const stderr = [];
2460
+ let outputBytes = 0;
2461
+ child.stdout.on("data", (chunk) => {
2462
+ outputBytes += chunk.byteLength;
2463
+ if (outputBytes > 8 * 1024 * 1024) child.kill("SIGKILL");
2464
+ else stdout.push(chunk);
2465
+ });
2466
+ child.stderr.on("data", (chunk) => {
2467
+ if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
2468
+ });
2469
+ const code = await new Promise((accept, reject) => {
2470
+ child.once("error", reject);
2471
+ child.once("exit", accept);
2472
+ });
2473
+ if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
2474
+ if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
2475
+ const paths = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
2476
+ if (paths.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
2477
+ const root = (0, import_path4.resolve)(sourceDir);
2478
+ const files = [];
2479
+ let bytes = 0;
2480
+ for (const relativePath of paths) {
2481
+ if (!allowedWorkspacePath(relativePath)) continue;
2482
+ const source = (0, import_path4.resolve)(root, relativePath);
2483
+ if (!source.startsWith(`${root}${import_path4.sep}`)) throw new TypeError("git file path escapes workspace");
2484
+ let metadata2;
2485
+ try {
2486
+ metadata2 = await (0, import_promises4.lstat)(source);
2487
+ } catch (error) {
2488
+ if (error.code === "ENOENT") continue;
2489
+ throw error;
2490
+ }
2491
+ if (metadata2.isSymbolicLink() || !metadata2.isFile()) continue;
2492
+ bytes += metadata2.size;
2493
+ if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2494
+ files.push({ source, relativePath, mode: metadata2.mode & 511, bytes: metadata2.size });
2495
+ }
2496
+ return files;
2497
+ }
2333
2498
  async function copyTree(files, destination) {
2334
2499
  for (const file of files) {
2335
- const target = (0, import_path2.join)(destination, file.relativePath);
2336
- await (0, import_promises3.mkdir)((0, import_path2.resolve)(target, ".."), { recursive: true });
2337
- await (0, import_promises3.copyFile)(file.source, target);
2338
- await (0, import_promises3.chmod)(target, file.mode);
2500
+ const target = (0, import_path4.join)(destination, file.relativePath);
2501
+ await (0, import_promises4.mkdir)((0, import_path4.resolve)(target, ".."), { recursive: true });
2502
+ await (0, import_promises4.copyFile)(file.source, target);
2503
+ await (0, import_promises4.chmod)(target, file.mode);
2339
2504
  }
2340
2505
  }
2341
2506
  async function captureGitDiff(root, maxBytes) {
2342
- const child = (0, import_child_process2.spawn)("git", [
2507
+ const child = (0, import_child_process3.spawn)("git", [
2343
2508
  "diff",
2344
2509
  "--no-index",
2345
2510
  "--binary",
@@ -2372,15 +2537,17 @@ async function captureGitDiff(root, maxBytes) {
2372
2537
  return Buffer.concat(stdout).toString("utf8").replaceAll("a/baseline/", "a/").replaceAll("b/workspace/", "b/").replaceAll("--- a/baseline", "--- a").replaceAll("+++ b/workspace", "+++ b");
2373
2538
  }
2374
2539
  async function stageWorkspace(source, options = {}) {
2375
- const sourceDir = await (0, import_promises3.realpath)((0, import_path2.resolve)(source));
2376
- const sourceStat = await (0, import_promises3.stat)(sourceDir);
2540
+ const sourceDir = await (0, import_promises4.realpath)((0, import_path4.resolve)(source));
2541
+ const sourceStat = await (0, import_promises4.stat)(sourceDir);
2377
2542
  if (!sourceStat.isDirectory()) throw new TypeError("workspace source must be a directory");
2378
- const root = await (0, import_promises3.mkdtemp)((0, import_path2.join)(options.tempRoot ?? (0, import_os.tmpdir)(), "odla-harness-"));
2379
- const baselineDir = (0, import_path2.join)(root, "baseline");
2380
- const workspaceDir = (0, import_path2.join)(root, "workspace");
2381
- await Promise.all([(0, import_promises3.mkdir)(baselineDir), (0, import_promises3.mkdir)(workspaceDir)]);
2543
+ const root = await (0, import_promises4.mkdtemp)((0, import_path4.join)(options.tempRoot ?? (0, import_os2.tmpdir)(), "odla-harness-"));
2544
+ const baselineDir = (0, import_path4.join)(root, "baseline");
2545
+ const workspaceDir = (0, import_path4.join)(root, "workspace");
2546
+ await Promise.all([(0, import_promises4.mkdir)(baselineDir), (0, import_promises4.mkdir)(workspaceDir)]);
2382
2547
  try {
2383
- const files = await sourceFiles(sourceDir, options.maxFiles ?? 2e4, options.maxBytes ?? 512 * 1024 * 1024);
2548
+ const maxFiles = options.maxFiles ?? 2e4;
2549
+ const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
2550
+ const files = options.gitTrackedAndUnignored ? await gitSourceFiles(sourceDir, maxFiles, maxBytes) : await sourceFiles(sourceDir, maxFiles, maxBytes);
2384
2551
  await Promise.all([copyTree(files, baselineDir), copyTree(files, workspaceDir)]);
2385
2552
  return {
2386
2553
  root,
@@ -2388,15 +2555,49 @@ async function stageWorkspace(source, options = {}) {
2388
2555
  workspaceDir,
2389
2556
  fileCount: files.length,
2390
2557
  byteCount: files.reduce((sum, file) => sum + file.bytes, 0),
2391
- patch: (maxBytes) => captureGitDiff(root, maxBytes),
2392
- cleanup: () => (0, import_promises3.rm)(root, { recursive: true, force: true })
2558
+ patch: (maxBytes2) => captureGitDiff(root, maxBytes2),
2559
+ cleanup: () => (0, import_promises4.rm)(root, { recursive: true, force: true })
2393
2560
  };
2394
2561
  } catch (error) {
2395
- await (0, import_promises3.rm)(root, { recursive: true, force: true });
2562
+ await (0, import_promises4.rm)(root, { recursive: true, force: true });
2563
+ throw error;
2564
+ }
2565
+ }
2566
+ async function stageWorkspacePair(baselineSource, workspaceSource, options = {}) {
2567
+ const baselineDirSource = await (0, import_promises4.realpath)((0, import_path4.resolve)(baselineSource));
2568
+ const workspaceDirSource = await (0, import_promises4.realpath)((0, import_path4.resolve)(workspaceSource));
2569
+ const maxFiles = options.maxFiles ?? 2e4;
2570
+ const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
2571
+ const [baselineFiles, workspaceFiles] = await Promise.all([
2572
+ sourceFiles(baselineDirSource, maxFiles, maxBytes),
2573
+ sourceFiles(workspaceDirSource, maxFiles, maxBytes)
2574
+ ]);
2575
+ const root = await (0, import_promises4.mkdtemp)((0, import_path4.join)(options.tempRoot ?? (0, import_os2.tmpdir)(), "odla-harness-"));
2576
+ const baselineDir = (0, import_path4.join)(root, "baseline");
2577
+ const workspaceDir = (0, import_path4.join)(root, "workspace");
2578
+ await Promise.all([(0, import_promises4.mkdir)(baselineDir), (0, import_promises4.mkdir)(workspaceDir)]);
2579
+ try {
2580
+ await Promise.all([copyTree(baselineFiles, baselineDir), copyTree(workspaceFiles, workspaceDir)]);
2581
+ return {
2582
+ root,
2583
+ baselineDir,
2584
+ workspaceDir,
2585
+ fileCount: workspaceFiles.length,
2586
+ byteCount: workspaceFiles.reduce((sum, file) => sum + file.bytes, 0),
2587
+ patch: (maxPatchBytes) => captureGitDiff(root, maxPatchBytes),
2588
+ cleanup: () => (0, import_promises4.rm)(root, { recursive: true, force: true })
2589
+ };
2590
+ } catch (error) {
2591
+ await (0, import_promises4.rm)(root, { recursive: true, force: true });
2396
2592
  throw error;
2397
2593
  }
2398
2594
  }
2399
2595
 
2596
+ // ../harness/dist/chunk-4EM6NMET.js
2597
+ var import_crypto = require("crypto");
2598
+ var import_promises5 = require("fs/promises");
2599
+ var import_path5 = require("path");
2600
+
2400
2601
  // ../camel/dist/chunk-7FHPOQVP.js
2401
2602
  var CamelError = class extends Error {
2402
2603
  code;
@@ -2415,8 +2616,8 @@ function canonicalJson(value) {
2415
2616
  }
2416
2617
  async function sha256Hex(value) {
2417
2618
  const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
2418
- const digest2 = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
2419
- return [...new Uint8Array(digest2)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2619
+ const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
2620
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2420
2621
  }
2421
2622
  function utf8Length(value) {
2422
2623
  if (typeof value === "string") return new TextEncoder().encode(value).byteLength;
@@ -2693,8 +2894,8 @@ function freeze(value) {
2693
2894
  })
2694
2895
  });
2695
2896
  }
2696
- function invalid2(message3) {
2697
- return new CamelError("state_conflict", message3);
2897
+ function invalid2(message2) {
2898
+ return new CamelError("state_conflict", message2);
2698
2899
  }
2699
2900
  var SHA4 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
2700
2901
  var REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
@@ -2729,25 +2930,22 @@ function validateSnapshot(snapshot, limits) {
2729
2930
  }
2730
2931
  }
2731
2932
 
2732
- // ../harness/dist/chunk-RVUUURK2.js
2733
- var import_child_process3 = require("child_process");
2734
- var import_promises4 = require("fs/promises");
2735
- var import_path3 = require("path");
2933
+ // ../harness/dist/chunk-4EM6NMET.js
2736
2934
  var import_child_process4 = require("child_process");
2935
+ var import_promises6 = require("fs/promises");
2936
+ var import_path6 = require("path");
2937
+ var import_child_process5 = require("child_process");
2737
2938
  var import_process2 = require("process");
2738
- var import_crypto = require("crypto");
2739
2939
  var import_crypto2 = require("crypto");
2740
- var import_fs2 = require("fs");
2741
- var import_promises5 = require("fs/promises");
2742
- var import_path4 = require("path");
2743
2940
  var import_crypto3 = require("crypto");
2744
- var import_promises6 = require("fs/promises");
2745
- var import_path5 = require("path");
2941
+ var import_fs2 = require("fs");
2746
2942
  var import_promises7 = require("fs/promises");
2747
- var import_os2 = require("os");
2748
- var import_path6 = require("path");
2749
- var import_promises8 = require("fs/promises");
2750
2943
  var import_path7 = require("path");
2944
+ var import_promises8 = require("fs/promises");
2945
+ var import_os3 = require("os");
2946
+ var import_path8 = require("path");
2947
+ var import_promises9 = require("fs/promises");
2948
+ var import_path9 = require("path");
2751
2949
 
2752
2950
  // ../camel/dist/chunk-LAXU2AVK.js
2753
2951
  function conversionPolicyDigest(policy) {
@@ -3025,8 +3223,34 @@ function looksLikeDestination(value) {
3025
3223
  return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
3026
3224
  }
3027
3225
 
3028
- // ../harness/dist/chunk-RVUUURK2.js
3226
+ // ../harness/dist/chunk-4EM6NMET.js
3029
3227
  var import_crypto4 = require("crypto");
3228
+ async function digestStagedWorkspace(root, limits) {
3229
+ const files = [];
3230
+ const walk = async (directory) => {
3231
+ const entries = await (0, import_promises5.readdir)(directory, { withFileTypes: true });
3232
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
3233
+ if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
3234
+ const target = (0, import_path5.resolve)(directory, entry.name);
3235
+ if (entry.isDirectory()) await walk(target);
3236
+ else if (entry.isFile()) {
3237
+ files.push({ path: (0, import_path5.relative)(root, target).split("\\").join("/"), target });
3238
+ if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
3239
+ }
3240
+ }
3241
+ };
3242
+ await walk((0, import_path5.resolve)(root));
3243
+ const hash = (0, import_crypto.createHash)("sha256");
3244
+ let bytes = 0;
3245
+ for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
3246
+ const content = await (0, import_promises5.readFile)(file.target);
3247
+ bytes += Buffer.byteLength(file.path) + content.byteLength;
3248
+ if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
3249
+ hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
3250
+ hash.update(content);
3251
+ }
3252
+ return `sha256:${hash.digest("hex")}`;
3253
+ }
3030
3254
  var CODE_RUNTIME_PROTOCOL_VERSION = 1;
3031
3255
  async function runCodeRuntimeHeartbeatLoop(options) {
3032
3256
  const heartbeatMs = options.heartbeatMs ?? 15e3;
@@ -3098,8 +3322,8 @@ function wait(ms, signal) {
3098
3322
  });
3099
3323
  }
3100
3324
  var CodeRuntimeControlError = class extends Error {
3101
- constructor(message3, status, code = "control_error") {
3102
- super(message3);
3325
+ constructor(message2, status, code = "control_error") {
3326
+ super(message2);
3103
3327
  this.status = status;
3104
3328
  this.code = code;
3105
3329
  }
@@ -3178,9 +3402,9 @@ function createCodeRuntimeControlClient(options) {
3178
3402
  }
3179
3403
  await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
3180
3404
  },
3181
- reportSessionFailure: async (sessionId, message3) => {
3182
- if (!message3.trim() || message3.length > 2e3) throw new TypeError("invalid Code session failure");
3183
- await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message3 });
3405
+ reportSessionFailure: async (sessionId, message2) => {
3406
+ if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
3407
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
3184
3408
  }
3185
3409
  };
3186
3410
  }
@@ -3247,10 +3471,28 @@ async function parseSource(value) {
3247
3471
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
3248
3472
  return { path: file.path, content: file.content };
3249
3473
  });
3474
+ const referencesValue = snapshot.references === void 0 ? [] : snapshot.references;
3475
+ if (!Array.isArray(referencesValue) || referencesValue.length > 5) throw invalid("reference sources");
3476
+ const aliases = /* @__PURE__ */ new Set();
3477
+ const references = [];
3478
+ for (const item of referencesValue) {
3479
+ const reference = record3(item);
3480
+ 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");
3481
+ aliases.add(reference.alias);
3482
+ const referenceFiles = reference.files.map((entry) => {
3483
+ const file = record3(entry);
3484
+ if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
3485
+ return { path: file.path, content: file.content };
3486
+ });
3487
+ const source2 = { repository: reference.repository, commitSha: reference.commitSha, files: referenceFiles };
3488
+ const referenceDigest = await digestCodeRepositorySnapshot(source2, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3489
+ if (referenceDigest !== reference.treeDigest) throw invalid("reference source digest");
3490
+ references.push({ alias: reference.alias, ...source2, treeDigest: referenceDigest });
3491
+ }
3250
3492
  const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
3251
- const digest2 = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3252
- if (digest2 !== snapshot.treeDigest) throw invalid("source digest");
3253
- return { ...source, treeDigest: digest2 };
3493
+ const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3494
+ if (digest !== snapshot.treeDigest) throw invalid("source digest");
3495
+ return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
3254
3496
  }
3255
3497
  function parseReview(value) {
3256
3498
  const review = record3(record3(value)?.review);
@@ -3309,9 +3551,9 @@ function validateRelativePath(path) {
3309
3551
  }
3310
3552
  function resolveCodePath(workspaceDir, path) {
3311
3553
  validateRelativePath(path);
3312
- const root = (0, import_path3.resolve)(workspaceDir);
3313
- const target = (0, import_path3.resolve)(root, path);
3314
- if (target !== root && !target.startsWith(`${root}${import_path3.sep}`)) throw new TypeError("path escapes the staged workspace");
3554
+ const root = (0, import_path6.resolve)(workspaceDir);
3555
+ const target = (0, import_path6.resolve)(root, path);
3556
+ if (target !== root && !target.startsWith(`${root}${import_path6.sep}`)) throw new TypeError("path escapes the staged workspace");
3315
3557
  return target;
3316
3558
  }
3317
3559
  async function applyCodePatch(workspaceDir, patch2, paths) {
@@ -3319,7 +3561,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
3319
3561
  await gitApply(workspaceDir, patch2, false);
3320
3562
  for (const path of paths) {
3321
3563
  try {
3322
- const info = await (0, import_promises4.lstat)(resolveCodePath(workspaceDir, path));
3564
+ const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
3323
3565
  if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
3324
3566
  throw new TypeError("patch created a non-regular workspace entry");
3325
3567
  }
@@ -3331,7 +3573,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
3331
3573
  function gitApply(cwd, patch2, check) {
3332
3574
  return new Promise((accept, reject) => {
3333
3575
  const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
3334
- const child = (0, import_child_process3.spawn)("git", args, {
3576
+ const child = (0, import_child_process4.spawn)("git", args, {
3335
3577
  cwd,
3336
3578
  shell: false,
3337
3579
  stdio: ["pipe", "ignore", "pipe"],
@@ -3375,7 +3617,7 @@ async function restoreCodeWorkspaceCheckpoint(input) {
3375
3617
  }
3376
3618
  var ARTIFACT_PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
3377
3619
  var PRIVATE_ARTIFACT_PART = /^(?:\.git|\.odla|\.wrangler|\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json)$/i;
3378
- function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0, import_crypto.randomUUID)().slice(0, 12)}`) {
3620
+ function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0, import_crypto2.randomUUID)().slice(0, 12)}`) {
3379
3621
  assertCodeBuildRecipe(recipe2);
3380
3622
  if (/[,\r\n]/.test(workspaceDir)) throw new TypeError("workspace path contains unsupported mount characters");
3381
3623
  const uid = typeof import_process2.getuid === "function" ? (0, import_process2.getuid)() : 1e3;
@@ -3430,7 +3672,7 @@ function createContainerRecipeExecutor(engine) {
3430
3672
  return {
3431
3673
  async run(input) {
3432
3674
  await verifyContainerEngineBoundary(engine);
3433
- const name = `odla-recipe-${(0, import_crypto.randomUUID)().slice(0, 12)}`;
3675
+ const name = `odla-recipe-${(0, import_crypto2.randomUUID)().slice(0, 12)}`;
3434
3676
  const args = buildRecipeContainerArgs(engine, input.workspaceDir, input.recipe, name);
3435
3677
  return execute(engine, args, name, input.recipe, input.signal);
3436
3678
  }
@@ -3454,7 +3696,7 @@ function parseMemory(value) {
3454
3696
  function execute(engine, args, name, recipe2, signal) {
3455
3697
  return new Promise((accept, reject) => {
3456
3698
  const started = Date.now();
3457
- const child = (0, import_child_process4.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
3699
+ const child = (0, import_child_process5.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
3458
3700
  const stdout = [];
3459
3701
  const stderr = [];
3460
3702
  let bytes = 0;
@@ -3467,7 +3709,7 @@ function execute(engine, args, name, recipe2, signal) {
3467
3709
  timedOut = reason === "timeout";
3468
3710
  outputLimitExceeded = reason === "output";
3469
3711
  const remove = engine === "container" ? ["delete", "--force", name] : ["rm", "-f", name];
3470
- const killer = (0, import_child_process4.spawn)(engine, remove, { shell: false, stdio: "ignore" });
3712
+ const killer = (0, import_child_process5.spawn)(engine, remove, { shell: false, stdio: "ignore" });
3471
3713
  killer.unref();
3472
3714
  child.kill("SIGTERM");
3473
3715
  };
@@ -3501,32 +3743,6 @@ function execute(engine, args, name, recipe2, signal) {
3501
3743
  });
3502
3744
  });
3503
3745
  }
3504
- async function digestStagedWorkspace(root, limits) {
3505
- const files = [];
3506
- const walk = async (directory) => {
3507
- const entries = await (0, import_promises6.readdir)(directory, { withFileTypes: true });
3508
- for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
3509
- if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
3510
- const target = (0, import_path5.resolve)(directory, entry.name);
3511
- if (entry.isDirectory()) await walk(target);
3512
- else if (entry.isFile()) {
3513
- files.push({ path: (0, import_path5.relative)(root, target).split("\\").join("/"), target });
3514
- if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
3515
- }
3516
- }
3517
- };
3518
- await walk((0, import_path5.resolve)(root));
3519
- const hash = (0, import_crypto3.createHash)("sha256");
3520
- let bytes = 0;
3521
- for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
3522
- const content = await (0, import_promises6.readFile)(file.target);
3523
- bytes += Buffer.byteLength(file.path) + content.byteLength;
3524
- if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
3525
- hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
3526
- hash.update(content);
3527
- }
3528
- return `sha256:${hash.digest("hex")}`;
3529
- }
3530
3746
  var SHA3 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
3531
3747
  var DIGEST3 = /^sha256:[0-9a-f]{64}$/;
3532
3748
  var ID3 = /^[A-Za-z0-9._:-]{1,160}$/;
@@ -3574,7 +3790,7 @@ async function verifyCodeCandidate(input) {
3574
3790
  }
3575
3791
  const fields = {
3576
3792
  schemaVersion: 1,
3577
- verificationId: input.verificationId ?? `verify-${(0, import_crypto2.randomUUID)()}`,
3793
+ verificationId: input.verificationId ?? `verify-${(0, import_crypto3.randomUUID)()}`,
3578
3794
  trustedBaseCommitSha: input.trustedBaseCommitSha,
3579
3795
  trustedBaseDigest: input.trustedBaseDigest,
3580
3796
  patchDigest,
@@ -3635,8 +3851,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
3635
3851
  const receipts = [];
3636
3852
  for (const artifact of recipe2.expectedArtifacts ?? []) {
3637
3853
  try {
3638
- const path = (0, import_path4.join)(workspaceDir, artifact.path);
3639
- const info = await (0, import_promises5.lstat)(path);
3854
+ const path = (0, import_path7.join)(workspaceDir, artifact.path);
3855
+ const info = await (0, import_promises7.lstat)(path);
3640
3856
  if (!info.isFile() || info.isSymbolicLink()) {
3641
3857
  receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
3642
3858
  } else if (info.size > artifact.maximumBytes) {
@@ -3653,7 +3869,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
3653
3869
  }
3654
3870
  function hashFile(path) {
3655
3871
  return new Promise((accept, reject) => {
3656
- const hash = (0, import_crypto2.createHash)("sha256");
3872
+ const hash = (0, import_crypto3.createHash)("sha256");
3657
3873
  const stream = (0, import_fs2.createReadStream)(path);
3658
3874
  stream.on("data", (chunk) => {
3659
3875
  hash.update(chunk);
@@ -3711,7 +3927,7 @@ function digestJson(value) {
3711
3927
  return digestBytes(JSON.stringify(value));
3712
3928
  }
3713
3929
  function digestBytes(value) {
3714
- return `sha256:${(0, import_crypto2.createHash)("sha256").update(value).digest("hex")}`;
3930
+ return `sha256:${(0, import_crypto3.createHash)("sha256").update(value).digest("hex")}`;
3715
3931
  }
3716
3932
  function integer(value, minimum, maximum) {
3717
3933
  return Number.isSafeInteger(value) && value >= minimum && value <= maximum;
@@ -3732,8 +3948,8 @@ async function prepareRuntimeCheckpoint(input) {
3732
3948
  policy: {
3733
3949
  policyId: "code.runtime",
3734
3950
  recipes: input.recipes,
3735
- maximumFiles: 1e4,
3736
- maximumBytes: 16 * 1024 * 1024
3951
+ maximumFiles: 2e4,
3952
+ maximumBytes: 512 * 1024 * 1024
3737
3953
  },
3738
3954
  recipeExecutor: input.recipeExecutor
3739
3955
  });
@@ -3807,7 +4023,7 @@ var CodeRuntimeCheckpointManager = class {
3807
4023
  await this.options.event(command, {
3808
4024
  type: "message",
3809
4025
  actor: "system",
3810
- body: `Candidate ${candidate.candidateId} is ready for exact owner publication approval`
4026
+ 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`
3811
4027
  }, pending.refs);
3812
4028
  this.#pending.delete(command.commandId);
3813
4029
  return true;
@@ -3815,11 +4031,11 @@ var CodeRuntimeCheckpointManager = class {
3815
4031
  };
3816
4032
  var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
3817
4033
  var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
3818
- async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os2.tmpdir)()) {
4034
+ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.tmpdir)()) {
3819
4035
  if (!snapshot.files.length || snapshot.files.length > 1e4) throw new TypeError("Code source file count is invalid");
3820
- const root = await (0, import_promises7.mkdtemp)((0, import_path6.join)(tempRoot, "odla-code-source-"));
3821
- const sourceDir = (0, import_path6.join)(root, "source");
3822
- await (0, import_promises7.mkdir)(sourceDir);
4036
+ const root = await (0, import_promises8.mkdtemp)((0, import_path8.join)(tempRoot, "odla-code-source-"));
4037
+ const sourceDir = (0, import_path8.join)(root, "source");
4038
+ await (0, import_promises8.mkdir)(sourceDir);
3823
4039
  const seen = /* @__PURE__ */ new Set();
3824
4040
  let bytes = 0;
3825
4041
  try {
@@ -3829,17 +4045,56 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os2.
3829
4045
  seen.add(file.path);
3830
4046
  bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
3831
4047
  if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
3832
- const target = (0, import_path6.resolve)(sourceDir, file.path);
3833
- if (!target.startsWith(`${(0, import_path6.resolve)(sourceDir)}${import_path6.sep}`)) throw new TypeError("Code source path escapes its root");
3834
- await (0, import_promises7.mkdir)((0, import_path6.dirname)(target), { recursive: true });
3835
- await (0, import_promises7.writeFile)(target, file.content, { flag: "wx", mode: 420 });
4048
+ const target = (0, import_path8.resolve)(sourceDir, file.path);
4049
+ if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code source path escapes its root");
4050
+ await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
4051
+ await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 420 });
4052
+ }
4053
+ for (const reference of snapshot.references ?? []) {
4054
+ validateAlias(reference.alias);
4055
+ if (!reference.files.length || reference.files.length > 1e4) throw new TypeError("Code reference file count is invalid");
4056
+ for (const file of reference.files) {
4057
+ validatePath(file.path);
4058
+ const path = `.odla-references/${reference.alias}/${file.path}`;
4059
+ if (seen.has(path)) throw new TypeError("Code reference repeats a path");
4060
+ seen.add(path);
4061
+ bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
4062
+ if (bytes > 80 * 1024 * 1024) throw new TypeError("Code source set exceeds its byte bound");
4063
+ const target = (0, import_path8.resolve)(sourceDir, path);
4064
+ if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
4065
+ await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
4066
+ await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
4067
+ }
3836
4068
  }
3837
- return { sourceDir, cleanup: () => (0, import_promises7.rm)(root, { recursive: true, force: true }) };
4069
+ return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
3838
4070
  } catch (cause) {
3839
- await (0, import_promises7.rm)(root, { recursive: true, force: true });
4071
+ await (0, import_promises8.rm)(root, { recursive: true, force: true });
3840
4072
  throw cause;
3841
4073
  }
3842
4074
  }
4075
+ function validateAlias(alias) {
4076
+ if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias) || alias === "primary") {
4077
+ throw new TypeError("Code reference alias is invalid");
4078
+ }
4079
+ }
4080
+ async function attachCodeRuntimeReferences(workspace, references) {
4081
+ let bytes = 0;
4082
+ for (const reference of references) {
4083
+ validateAlias(reference.alias);
4084
+ for (const file of reference.files) {
4085
+ validatePath(file.path);
4086
+ const path = `.odla-references/${reference.alias}/${file.path}`;
4087
+ bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
4088
+ if (bytes > 64 * 1024 * 1024) throw new TypeError("Code reference set exceeds its byte bound");
4089
+ for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
4090
+ const target = (0, import_path8.resolve)(root, path);
4091
+ if (!target.startsWith(`${(0, import_path8.resolve)(root)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
4092
+ await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
4093
+ await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
4094
+ }
4095
+ }
4096
+ }
4097
+ }
3843
4098
  function validatePath(path) {
3844
4099
  const parts = path.split("/");
3845
4100
  if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." || RESERVED2.has(part) || SECRET2.test(part))) {
@@ -3942,15 +4197,15 @@ async function environment(input, options, tool) {
3942
4197
  { id: "authority", value: `lease:${input.lease.leaseId}`, readers: input.readers },
3943
4198
  { id: "reader", value: options.readerId, readers: input.readers }
3944
4199
  ]);
3945
- const digest2 = await destinationRegistryDigest([input.workspaceId]);
4200
+ const digest = await destinationRegistryDigest([input.workspaceId]);
3946
4201
  const approvals = ["irreversible_mutation", "external_send", "financial", "secret_read"];
3947
4202
  if (options.recipeAuthorization === "exact_approval") approvals.push("code_execution");
3948
4203
  const policy = createEffectPolicy({
3949
- destinationRegistries: { [DESTINATIONS]: { digest: digest2, values: [input.workspaceId] } },
4204
+ destinationRegistries: { [DESTINATIONS]: { digest, values: [input.workspaceId] } },
3950
4205
  approvalEffects: approvals
3951
4206
  });
3952
4207
  const fixedArgs = {
3953
- workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest2 },
4208
+ workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest },
3954
4209
  authority: { role: "authority", value: ingress.control("authority") }
3955
4210
  };
3956
4211
  return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
@@ -4021,11 +4276,11 @@ async function read(context, request, options, policy) {
4021
4276
  const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
4022
4277
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
4023
4278
  const target = resolveCodePath(context.workspaceDir, path);
4024
- const info = await (0, import_promises8.stat)(target);
4279
+ const info = await (0, import_promises9.stat)(target);
4025
4280
  if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
4026
4281
  throw new TypeError("file is not a bounded regular source file");
4027
4282
  }
4028
- const source = await (0, import_promises8.readFile)(target);
4283
+ const source = await (0, import_promises9.readFile)(target);
4029
4284
  if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
4030
4285
  const lines = source.toString("utf8").split("\n");
4031
4286
  const content = lines.slice(startLine - 1, endLine).join("\n");
@@ -4038,6 +4293,9 @@ async function patch(context, request, options, policy) {
4038
4293
  exactKeys(request.input, ["patch"]);
4039
4294
  const value = stringField(request.input, "patch");
4040
4295
  const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
4296
+ if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
4297
+ throw new TypeError("patch targets a read-only reference source");
4298
+ }
4041
4299
  const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
4042
4300
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
4043
4301
  await applyCodePatch(context.workspaceDir, value, paths);
@@ -4099,12 +4357,12 @@ function policyContext(context, request, options, extra) {
4099
4357
  async function registeredFiles(root, limit) {
4100
4358
  const paths = [];
4101
4359
  const walk = async (directory) => {
4102
- for (const entry of await (0, import_promises8.readdir)(directory, { withFileTypes: true })) {
4360
+ for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
4103
4361
  if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
4104
- const target = (0, import_path7.resolve)(directory, entry.name);
4362
+ const target = (0, import_path9.resolve)(directory, entry.name);
4105
4363
  if (entry.isDirectory()) await walk(target);
4106
4364
  else if (entry.isFile()) {
4107
- const path = (0, import_path7.relative)(root, target).split("\\").join("/");
4365
+ const path = (0, import_path9.relative)(root, target).split("\\").join("/");
4108
4366
  try {
4109
4367
  validateRelativePath(path);
4110
4368
  } catch {
@@ -4115,7 +4373,7 @@ async function registeredFiles(root, limit) {
4115
4373
  }
4116
4374
  }
4117
4375
  };
4118
- await walk((0, import_path7.resolve)(root));
4376
+ await walk((0, import_path9.resolve)(root));
4119
4377
  return paths.sort();
4120
4378
  }
4121
4379
  function validateOptions(options) {
@@ -4123,6 +4381,9 @@ function validateOptions(options) {
4123
4381
  throw new TypeError("Code tool broker requires a reader and unique registered recipes");
4124
4382
  }
4125
4383
  for (const recipe2 of options.recipes) assertCodeBuildRecipe(recipe2);
4384
+ if (options.readOnlyPrefixes?.some((prefix) => !/^[A-Za-z0-9_.-]+$/.test(prefix) || prefix === "." || prefix === "..")) {
4385
+ throw new TypeError("Code tool broker read-only prefix is invalid");
4386
+ }
4126
4387
  }
4127
4388
  function exactKeys(input, allowed) {
4128
4389
  if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
@@ -4150,14 +4411,31 @@ function codeCommandMetadata(payload, resume) {
4150
4411
  }
4151
4412
  const planning = trusted?.planningInputDigest;
4152
4413
  const attestation = trusted?.attestationDigest;
4414
+ const repository = trusted?.repository;
4415
+ const baseCommitSha = trusted?.commitSha;
4416
+ const sourceTreeDigest = trusted?.treeDigest;
4417
+ 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)) {
4418
+ throw new TypeError(`invalid Code ${resume ? "resume" : "start"} trusted base`);
4419
+ }
4153
4420
  return {
4154
4421
  role,
4155
4422
  title,
4156
4423
  prompt,
4157
4424
  planningInputDigest: typeof planning === "string" && /^sha256:[0-9a-f]{64}$/.test(planning) ? planning : null,
4158
- attestationDigest: typeof attestation === "string" ? attestation : "resume"
4425
+ attestationDigest: typeof attestation === "string" ? attestation : "resume",
4426
+ repository,
4427
+ baseCommitSha,
4428
+ sourceTreeDigest
4159
4429
  };
4160
4430
  }
4431
+ function codeLocalSource(payload) {
4432
+ const source = record22(payload.source);
4433
+ if (!source) return null;
4434
+ 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) {
4435
+ throw new TypeError("invalid local checkout source descriptor");
4436
+ }
4437
+ return source;
4438
+ }
4161
4439
  function codeCheckpointPayload(payload) {
4162
4440
  const value = payload.checkpoint;
4163
4441
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
@@ -4186,11 +4464,63 @@ function fakeCodeLease(command, metadata2) {
4186
4464
  };
4187
4465
  }
4188
4466
  var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4467
+ var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
4468
+ async function prepareRuntimeLocalSource(input) {
4469
+ const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
4470
+ if (!available || JSON.stringify(available.descriptor) !== JSON.stringify(descriptor2) || descriptor2.repository.toLowerCase() !== repository.toLowerCase() || descriptor2.headCommitSha !== baseCommitSha) {
4471
+ throw new TypeError("the session's local checkout snapshot is not available on this terminal");
4472
+ }
4473
+ const workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4474
+ trustedBaseDir: available.trustedBaseDir,
4475
+ trustedBaseCommitSha: baseCommitSha,
4476
+ checkpoint: codeCheckpointPayload(command.payload)
4477
+ })).workspace : await stageWorkspacePair(available.trustedBaseDir, available.sourceDir, SOURCE_LIMITS);
4478
+ const trustedBaseDigest = await digestStagedWorkspace(workspace.baselineDir, SOURCE_LIMITS);
4479
+ if (trustedBaseDigest !== descriptor2.trustedBaseDigest) {
4480
+ await workspace.cleanup();
4481
+ throw new TypeError("trusted Git base digest changed after connection");
4482
+ }
4483
+ if (!resume && await digestStagedWorkspace(workspace.workspaceDir, SOURCE_LIMITS) !== descriptor2.snapshotDigest) {
4484
+ await workspace.cleanup();
4485
+ throw new TypeError("local checkout snapshot digest changed after connection");
4486
+ }
4487
+ return { workspace, sourceDigest: descriptor2.snapshotDigest, trustedBaseDigest };
4488
+ }
4489
+ function createCodeRuntimeToolBroker(input, lease, role) {
4490
+ const broker = createCodeToolBroker({
4491
+ recipes: input.recipes,
4492
+ recipeExecutor: createContainerRecipeExecutor(input.engine),
4493
+ recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
4494
+ readerId: `code-session:${lease.task.taskId}`,
4495
+ readOnlyPrefixes: [".odla-references"]
4496
+ });
4497
+ 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" }) };
4498
+ }
4499
+ async function appendCodeRuntimeEvent(control, command, event, refs) {
4500
+ const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
4501
+ refs.push(eventId);
4502
+ const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
4503
+ await control.appendSessionEvent(command.sessionId, eventId, bounded);
4504
+ }
4505
+ var digestRuntimeValue = (value) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value).digest("hex")}`;
4506
+ var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
4507
+ var runtimeRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4508
+ var safeRuntimeJson = (value) => {
4509
+ try {
4510
+ return JSON.stringify(value).slice(0, 1e4);
4511
+ } catch {
4512
+ return "[event]";
4513
+ }
4514
+ };
4515
+ function runtimeResultText(value) {
4516
+ const record32 = runtimeRecord(value);
4517
+ return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4518
+ }
4189
4519
  var CodePiRuntimeEngine = class {
4190
4520
  constructor(options) {
4191
4521
  this.options = options;
4192
4522
  this.#run = options.runAttempt ?? runContainerAttempt;
4193
- this.#buildPolicyDigest = digest(JSON.stringify(options.recipes));
4523
+ this.#buildPolicyDigest = digestRuntimeValue(JSON.stringify(options.recipes));
4194
4524
  this.#checkpoints = new CodeRuntimeCheckpointManager({
4195
4525
  control: options.control,
4196
4526
  recipes: options.recipes,
@@ -4225,19 +4555,43 @@ var CodePiRuntimeEngine = class {
4225
4555
  }
4226
4556
  async #start(command, resume) {
4227
4557
  if (this.#active.has(command.sessionId)) throw new TypeError("Code session is already active on this runtime");
4228
- const source = await this.options.control.source(command.sessionId);
4229
- const materialized = await materializeCodeRuntimeSource(source);
4558
+ const metadata2 = codeCommandMetadata(command.payload, resume);
4559
+ const requestedLocal = codeLocalSource(command.payload);
4230
4560
  let workspace;
4231
- try {
4232
- workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4233
- trustedBaseDir: materialized.sourceDir,
4234
- trustedBaseCommitSha: source.commitSha,
4235
- checkpoint: codeCheckpointPayload(command.payload)
4236
- })).workspace : await stageWorkspace(materialized.sourceDir);
4237
- } finally {
4238
- await materialized.cleanup();
4561
+ let sourceDigest;
4562
+ let localTrustedBaseDigest;
4563
+ if (requestedLocal) {
4564
+ const prepared = await prepareRuntimeLocalSource({
4565
+ command,
4566
+ descriptor: requestedLocal,
4567
+ available: this.options.localSource,
4568
+ repository: metadata2.repository,
4569
+ baseCommitSha: metadata2.baseCommitSha,
4570
+ resume
4571
+ });
4572
+ ({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
4573
+ if (command.payload.sourceSet) {
4574
+ const selected = await this.options.control.source(command.sessionId);
4575
+ if (selected.repository !== metadata2.repository || selected.commitSha !== metadata2.baseCommitSha || selected.treeDigest !== metadata2.sourceTreeDigest) {
4576
+ await workspace.cleanup();
4577
+ throw new TypeError("Code local source does not match the selected GitHub primary source");
4578
+ }
4579
+ await attachCodeRuntimeReferences(workspace, selected.references ?? []);
4580
+ }
4581
+ } else {
4582
+ const source = await this.options.control.source(command.sessionId);
4583
+ const materialized = await materializeCodeRuntimeSource(source);
4584
+ try {
4585
+ workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4586
+ trustedBaseDir: materialized.sourceDir,
4587
+ trustedBaseCommitSha: source.commitSha,
4588
+ checkpoint: codeCheckpointPayload(command.payload)
4589
+ })).workspace : await stageWorkspace(materialized.sourceDir);
4590
+ } finally {
4591
+ await materialized.cleanup();
4592
+ }
4593
+ sourceDigest = source.treeDigest;
4239
4594
  }
4240
- const metadata2 = codeCommandMetadata(command.payload, resume);
4241
4595
  const abort = new AbortController();
4242
4596
  const conversationRefs = [];
4243
4597
  const active = {
@@ -4247,21 +4601,30 @@ var CodePiRuntimeEngine = class {
4247
4601
  acknowledged: false,
4248
4602
  role: metadata2.role,
4249
4603
  title: metadata2.title,
4250
- baseCommitSha: source.commitSha,
4251
- trustedBaseDigest: await digestStagedWorkspace(workspace.baselineDir, {
4252
- maxFiles: 1e4,
4253
- maxBytes: 16 * 1024 * 1024
4604
+ baseCommitSha: metadata2.baseCommitSha,
4605
+ repository: metadata2.repository,
4606
+ sourceTreeDigest: metadata2.sourceTreeDigest,
4607
+ trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
4608
+ maxFiles: 2e4,
4609
+ maxBytes: 512 * 1024 * 1024
4254
4610
  }),
4255
- planningInputDigest: metadata2.planningInputDigest ?? digest(
4256
- JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: source.treeDigest })
4611
+ planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
4612
+ JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })
4257
4613
  ),
4258
4614
  done: Promise.resolve(null)
4259
4615
  };
4260
4616
  this.#active.set(command.sessionId, active);
4617
+ if (requestedLocal) {
4618
+ await this.#event(command, {
4619
+ type: "message",
4620
+ actor: "system",
4621
+ body: `Source snapshot: local checkout ${requestedLocal.snapshotDigest} \xB7 ${requestedLocal.modified ? "modified" : "clean"} \xB7 Git ${requestedLocal.headCommitSha}`
4622
+ }, conversationRefs);
4623
+ }
4261
4624
  active.done = this.#runAttempt(command, metadata2, active).catch(async (cause) => {
4262
- await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` }, conversationRefs).catch(() => void 0);
4625
+ await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` }, conversationRefs).catch(() => void 0);
4263
4626
  await this.#event(command, { type: "status", status: "failed" }, conversationRefs).catch(() => void 0);
4264
- await this.#failure(command, active, message2(cause));
4627
+ await this.#failure(command, active, runtimeErrorMessage(cause));
4265
4628
  return null;
4266
4629
  });
4267
4630
  return { status: "running", message: resume ? "Pi resumed from a portable checkpoint" : "Pi started" };
@@ -4281,22 +4644,29 @@ var CodePiRuntimeEngine = class {
4281
4644
  title: active.title,
4282
4645
  prompt,
4283
4646
  planningInputDigest: active.planningInputDigest,
4284
- attestationDigest: "follow-up"
4647
+ attestationDigest: "follow-up",
4648
+ repository: active.repository,
4649
+ baseCommitSha: active.baseCommitSha,
4650
+ sourceTreeDigest: active.sourceTreeDigest
4285
4651
  }, active).catch(async (cause) => {
4286
4652
  await this.#event(
4287
4653
  command,
4288
- { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` },
4654
+ { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` },
4289
4655
  active.conversationRefs
4290
4656
  ).catch(() => void 0);
4291
4657
  await this.#event(command, { type: "status", status: "failed" }, active.conversationRefs).catch(() => void 0);
4292
- await this.#failure(command, active, message2(cause));
4658
+ await this.#failure(command, active, runtimeErrorMessage(cause));
4293
4659
  return null;
4294
4660
  });
4295
4661
  return { status: "running", message: "Pi accepted the owner prompt" };
4296
4662
  }
4297
4663
  async #runAttempt(command, metadata2, active) {
4298
4664
  const lease = fakeCodeLease(command, metadata2);
4299
- const broker = this.#toolBroker(lease, metadata2.role);
4665
+ const broker = createCodeRuntimeToolBroker({
4666
+ recipes: this.options.recipes,
4667
+ engine: this.options.engine,
4668
+ recipeAuthorization: this.options.recipeAuthorization
4669
+ }, lease, metadata2.role);
4300
4670
  const startedAt = Date.now();
4301
4671
  let completionSeen = false;
4302
4672
  const result = await this.#run({
@@ -4356,7 +4726,7 @@ var CodePiRuntimeEngine = class {
4356
4726
  return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
4357
4727
  }
4358
4728
  if (output.type === "event") {
4359
- const payload = object2(output.payload);
4729
+ const payload = runtimeRecord(output.payload);
4360
4730
  if (output.kind === "pi.started") {
4361
4731
  await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
4362
4732
  } else if (output.kind === "pi.thinking" && payload?.available === true && Number.isSafeInteger(payload.durationMs) && Number(payload.durationMs) >= 0) {
@@ -4369,12 +4739,12 @@ var CodePiRuntimeEngine = class {
4369
4739
  await this.#event(command, {
4370
4740
  type: "message",
4371
4741
  actor: "system",
4372
- body: `${output.kind}${output.payload === void 0 ? "" : ` ${json(output.payload)}`}`
4742
+ body: `${output.kind}${output.payload === void 0 ? "" : ` ${safeRuntimeJson(output.payload)}`}`
4373
4743
  }, active.conversationRefs);
4374
4744
  }
4375
4745
  } else if (output.type === "attempt.complete") {
4376
4746
  completionSeen = true;
4377
- const body = resultText(output.result) ?? `Pi ${output.status}.`;
4747
+ const body = runtimeResultText(output.result) ?? `Pi ${output.status}.`;
4378
4748
  await this.#event(command, {
4379
4749
  type: "message",
4380
4750
  actor: output.status === "completed" ? "agent" : "system",
@@ -4401,15 +4771,6 @@ var CodePiRuntimeEngine = class {
4401
4771
  }
4402
4772
  return result;
4403
4773
  }
4404
- #toolBroker(lease, role) {
4405
- const broker = createCodeToolBroker({
4406
- recipes: this.options.recipes,
4407
- recipeExecutor: createContainerRecipeExecutor(this.options.engine),
4408
- recipeAuthorization: this.options.recipeAuthorization ?? "registered_recipe",
4409
- readerId: `code-session:${lease.task.taskId}`
4410
- });
4411
- 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" }) };
4412
- }
4413
4774
  async #checkpoint(command) {
4414
4775
  const active = this.#active.get(command.sessionId);
4415
4776
  if (!active) throw new TypeError("Code session workspace is not active on this runtime");
@@ -4424,26 +4785,9 @@ var CodePiRuntimeEngine = class {
4424
4785
  }
4425
4786
  }
4426
4787
  async #event(command, event, refs) {
4427
- const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
4428
- refs.push(eventId);
4429
- const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
4430
- await this.options.control.appendSessionEvent(command.sessionId, eventId, bounded);
4431
- }
4432
- };
4433
- var object2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4434
- var digest = (value) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value).digest("hex")}`;
4435
- var message2 = (value) => value instanceof Error ? value.message : String(value);
4436
- var json = (value) => {
4437
- try {
4438
- return JSON.stringify(value).slice(0, 1e4);
4439
- } catch {
4440
- return "[event]";
4788
+ await appendCodeRuntimeEvent(this.options.control, command, event, refs);
4441
4789
  }
4442
4790
  };
4443
- function resultText(value) {
4444
- const record32 = object2(value);
4445
- return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4446
- }
4447
4791
 
4448
4792
  // src/help.ts
4449
4793
  var import_node_fs6 = require("fs");
@@ -4585,8 +4929,8 @@ async function requestHostedSecurityJson(options, path, init, action) {
4585
4929
  const body = await response2.json().catch(() => ({}));
4586
4930
  if (!response2.ok) {
4587
4931
  const code = optionalHostedText(body.error?.code, "error code", 128);
4588
- const message3 = optionalHostedText(body.error?.message, "error message", 300);
4589
- throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message3 ? `: ${message3}` : ""}`);
4932
+ const message2 = optionalHostedText(body.error?.message, "error message", 300);
4933
+ throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
4590
4934
  }
4591
4935
  return body;
4592
4936
  }
@@ -4775,24 +5119,111 @@ async function defaultReadOrigin(cwd) {
4775
5119
  return result.stdout;
4776
5120
  }
4777
5121
 
4778
- // src/code-connect.ts
4779
- var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:713db58428bc05cb486167c5747de8fd826669c9b283ff78e0139ac194c3d058";
5122
+ // src/code-local-source.ts
5123
+ var import_node_child_process3 = require("child_process");
5124
+ var import_node_crypto = require("crypto");
5125
+ var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
5126
+ async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
5127
+ const headCommitSha = await readHead(cwd);
5128
+ const staged = await stageWorkspace(cwd, { ...SOURCE_LIMITS2, gitTrackedAndUnignored: true });
5129
+ let trusted;
5130
+ try {
5131
+ trusted = await materializeGitTree(cwd, headCommitSha, SOURCE_LIMITS2);
5132
+ const comparison = await stageWorkspacePair(trusted.sourceDir, staged.baselineDir, SOURCE_LIMITS2);
5133
+ let developerPatch;
5134
+ try {
5135
+ developerPatch = await comparison.patch(4 * 1024 * 1024);
5136
+ } finally {
5137
+ await comparison.cleanup();
5138
+ }
5139
+ const descriptor2 = {
5140
+ kind: "local_checkout",
5141
+ repository,
5142
+ headCommitSha,
5143
+ trustedBaseDigest: await digestStagedWorkspace(trusted.sourceDir, SOURCE_LIMITS2),
5144
+ developerPatchDigest: digestText(developerPatch),
5145
+ snapshotDigest: await digestStagedWorkspace(staged.baselineDir, SOURCE_LIMITS2),
5146
+ modified: developerPatch.length > 0,
5147
+ fileCount: staged.fileCount,
5148
+ byteCount: staged.byteCount,
5149
+ capturedAt: Date.now()
5150
+ };
5151
+ return {
5152
+ descriptor: descriptor2,
5153
+ trustedBaseDir: trusted.sourceDir,
5154
+ sourceDir: staged.baselineDir,
5155
+ cleanup: async () => {
5156
+ await Promise.all([staged.cleanup(), trusted.cleanup()]);
5157
+ }
5158
+ };
5159
+ } catch (error) {
5160
+ await Promise.all([staged.cleanup(), trusted?.cleanup()]);
5161
+ throw error;
5162
+ }
5163
+ }
5164
+ async function readGitHead(cwd) {
5165
+ const value = await new Promise((accept, reject) => {
5166
+ (0, import_node_child_process3.execFile)("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
5167
+ if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
5168
+ else accept(stdout.trim());
5169
+ });
5170
+ });
5171
+ if (!/^[0-9a-f]{40}$/.test(value)) throw new Error("code connect could not resolve the checkout HEAD commit");
5172
+ return value;
5173
+ }
5174
+ function digestText(value) {
5175
+ return `sha256:${(0, import_node_crypto.createHash)("sha256").update(value).digest("hex")}`;
5176
+ }
5177
+
5178
+ // src/code-images.ts
5179
+ var import_node_child_process4 = require("child_process");
5180
+ async function prepareCodeImages(engine, images) {
5181
+ for (const image of images) {
5182
+ await new Promise((accept, reject) => {
5183
+ const args = engine === "container" ? ["image", "pull", image] : ["pull", image];
5184
+ (0, import_node_child_process4.execFile)(engine, args, { encoding: "utf8", maxBuffer: 2 * 1024 * 1024, timeout: 10 * 6e4 }, (error) => {
5185
+ if (error) reject(new Error(`could not prepare pinned Code image ${image}`));
5186
+ else accept();
5187
+ });
5188
+ });
5189
+ }
5190
+ }
5191
+
5192
+ // src/code-runtime-config.ts
5193
+ var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:e7858b4f0291543171b2659b47c5d00f97acc4767cafd9b74ae5228899e0cde4";
4780
5194
  var CODE_BUILD_RECIPES = Object.freeze([{
4781
- id: "node-test",
5195
+ id: "odla-code-contracts",
4782
5196
  image: "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd",
4783
- command: ["node", "--test"],
5197
+ command: [
5198
+ "node",
5199
+ "--test",
5200
+ "scripts/lib/directories.test.mjs",
5201
+ "scripts/lib/internal-deps.test.mjs",
5202
+ "scripts/lib/npm-audit.test.mjs",
5203
+ "scripts/lib/npm-release-contract.test.mjs",
5204
+ "scripts/lib/release-surfaces.test.mjs"
5205
+ ],
4784
5206
  timeoutMs: 12e4,
4785
5207
  maxOutputBytes: 1024 * 1024,
4786
5208
  cpus: 1,
4787
5209
  memory: "512m",
4788
5210
  pids: 128
4789
5211
  }]);
5212
+
5213
+ // src/code-connect.ts
4790
5214
  async function codeConnect(options) {
4791
5215
  const cwd = options.cwd ?? process.cwd();
4792
5216
  const configPath = (0, import_node_path4.resolve)(cwd, options.configPath);
4793
5217
  const cfg = (0, import_node_fs7.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
4794
- const repository = cfg ? null : await inferGitHubRepository(cwd, options.readGitOrigin);
4795
- const platform = cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai";
5218
+ const requestedAppId = options.appId?.trim();
5219
+ if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
5220
+ throw new Error("--app-id must be a valid odla app id");
5221
+ }
5222
+ if (requestedAppId && cfg && requestedAppId !== cfg.app.id) {
5223
+ throw new Error(`--app-id ${requestedAppId} does not match ${options.configPath} (${cfg.app.id})`);
5224
+ }
5225
+ const appId = requestedAppId ?? cfg?.app.id;
5226
+ const platform = (options.platform ?? cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai").replace(/\/+$/, "");
4796
5227
  const appEnv = options.env ?? (cfg ? cfg.envs.includes("dev") ? "dev" : cfg.envs[0] : "dev");
4797
5228
  if (appEnv !== "dev" && appEnv !== "prod" || cfg && !cfg.envs.includes(appEnv)) {
4798
5229
  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`);
@@ -4811,53 +5242,77 @@ async function codeConnect(options) {
4811
5242
  const out = options.stdout ?? console;
4812
5243
  const doFetch = options.fetch ?? fetch;
4813
5244
  const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
5245
+ await (options.prepareImages ?? prepareCodeImages)(engine, [
5246
+ CODE_PI_IMAGE,
5247
+ ...new Set(CODE_BUILD_RECIPES.map((recipe2) => recipe2.image))
5248
+ ]);
4814
5249
  const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
4815
5250
  const hostName = (options.name ?? (0, import_node_os.hostname)()).trim();
4816
5251
  if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
4817
- const approval = await (options.getToken ?? getScopedPlatformToken)({
4818
- platform,
4819
- scope: "platform:code:host:connect",
4820
- email: options.email,
4821
- open: options.open,
4822
- fetch: doFetch,
4823
- stdout: out,
4824
- openApprovalUrl: options.openApprovalUrl,
4825
- cache: false,
4826
- label: `odla CLI Code host for ${cfg?.app.id ?? repository}/${appEnv}`
4827
- });
4828
- const target = cfg ? { appId: cfg.app.id } : { repository };
4829
- const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
4830
- method: "POST",
4831
- headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
4832
- body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
4833
- redirect: "error",
4834
- signal: options.signal
4835
- });
4836
- const raw = await response2.json().catch(() => null);
4837
- if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
4838
- const connection = parseConnection(raw, cfg?.app.id, appEnv);
4839
- const capabilities = {
4840
- protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
4841
- platform: hostPlatform,
4842
- arch: process.arch,
4843
- engines: [engine],
4844
- cpuCount: (0, import_node_os.cpus)().length,
4845
- memoryBytes: (0, import_node_os.totalmem)()
4846
- };
4847
- out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
4848
- out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
4849
- out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
4850
- await (options.runRuntime ?? runCodeRuntime)({
4851
- endpoint: platform,
4852
- token: connection.token,
4853
- engine,
4854
- capabilities,
4855
- heartbeatMs,
4856
- once: options.once === true,
4857
- signal: options.signal,
4858
- stdout: out,
4859
- fetch: doFetch
4860
- });
5252
+ const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
5253
+ const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
5254
+ cwd,
5255
+ repository,
5256
+ options.readGitHead
5257
+ );
5258
+ try {
5259
+ const descriptor2 = localSource.descriptor;
5260
+ const approval = await (options.getToken ?? getScopedPlatformToken)({
5261
+ platform,
5262
+ scope: "app:code:host:connect",
5263
+ email: options.email,
5264
+ open: options.open,
5265
+ fetch: doFetch,
5266
+ stdout: out,
5267
+ openApprovalUrl: options.openApprovalUrl,
5268
+ cache: false,
5269
+ label: `Terminal connection for ${appId ?? repository}/${appEnv}`
5270
+ });
5271
+ const target = appId ? { appId } : { repository };
5272
+ const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
5273
+ method: "POST",
5274
+ headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
5275
+ body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
5276
+ redirect: "error",
5277
+ signal: options.signal
5278
+ });
5279
+ const raw = await response2.json().catch(() => null);
5280
+ if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
5281
+ const connection = parseConnection(raw, appId, appEnv);
5282
+ const capabilities = {
5283
+ protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
5284
+ platform: hostPlatform,
5285
+ arch: process.arch,
5286
+ engines: [engine],
5287
+ cpuCount: (0, import_node_os.cpus)().length,
5288
+ memoryBytes: (0, import_node_os.totalmem)(),
5289
+ source: descriptor2,
5290
+ images: {
5291
+ ready: true,
5292
+ pi: CODE_PI_IMAGE,
5293
+ recipes: CODE_BUILD_RECIPES.map((recipe2) => ({ id: recipe2.id, image: recipe2.image }))
5294
+ }
5295
+ };
5296
+ out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
5297
+ out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
5298
+ out.log(`source: local checkout \xB7 ${descriptor2.headCommitSha.slice(0, 10)} \xB7 ${descriptor2.fileCount} files \xB7 staged copy`);
5299
+ out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
5300
+ out.log(`Studio: ${platform}/studio/apps/${encodeURIComponent(connection.binding.appId)}/${connection.binding.env}/code/sessions`);
5301
+ await (options.runRuntime ?? runCodeRuntime)({
5302
+ endpoint: platform,
5303
+ token: connection.token,
5304
+ engine,
5305
+ capabilities,
5306
+ localSource,
5307
+ heartbeatMs,
5308
+ once: options.once === true,
5309
+ signal: options.signal,
5310
+ stdout: out,
5311
+ fetch: doFetch
5312
+ });
5313
+ } finally {
5314
+ await localSource.cleanup();
5315
+ }
4861
5316
  }
4862
5317
  async function runCodeRuntime(input) {
4863
5318
  const controller = new AbortController();
@@ -4880,7 +5335,8 @@ async function runCodeRuntime(input) {
4880
5335
  engine: input.engine,
4881
5336
  image: CODE_PI_IMAGE,
4882
5337
  recipes: CODE_BUILD_RECIPES,
4883
- recipeAuthorization: "registered_recipe"
5338
+ recipeAuthorization: "registered_recipe",
5339
+ localSource: input.localSource
4884
5340
  });
4885
5341
  const reconciler = new CodeRuntimeReconciler(control, commandEngine);
4886
5342
  try {
@@ -4921,8 +5377,8 @@ function parseConnection(value, appId, appEnv) {
4921
5377
  return root;
4922
5378
  }
4923
5379
  function apiFailure(action, status, value) {
4924
- const message3 = record4(record4(value)?.error)?.message;
4925
- return `${action} failed (${status})${typeof message3 === "string" ? `: ${message3}` : ""}`;
5380
+ const message2 = record4(record4(value)?.error)?.message;
5381
+ return `${action} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
4926
5382
  }
4927
5383
  function record4(value) {
4928
5384
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -4937,6 +5393,8 @@ async function codeCommand(parsed, dependencies) {
4937
5393
  assertArgs(parsed, [
4938
5394
  "config",
4939
5395
  "env",
5396
+ "platform",
5397
+ "app-id",
4940
5398
  "email",
4941
5399
  "open",
4942
5400
  "name",
@@ -4952,6 +5410,8 @@ async function codeCommand(parsed, dependencies) {
4952
5410
  await (dependencies.codeConnect ?? codeConnect)({
4953
5411
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
4954
5412
  env: stringOpt(parsed.options.env),
5413
+ platform: stringOpt(parsed.options.platform),
5414
+ appId: stringOpt(parsed.options["app-id"]),
4955
5415
  email: stringOpt(parsed.options.email),
4956
5416
  open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
4957
5417
  name: stringOpt(parsed.options.name),
@@ -4967,16 +5427,16 @@ async function codeCommand(parsed, dependencies) {
4967
5427
  }
4968
5428
 
4969
5429
  // src/doctor-checks.ts
4970
- var import_node_child_process4 = require("child_process");
5430
+ var import_node_child_process6 = require("child_process");
4971
5431
  var import_node_fs9 = require("fs");
4972
5432
  var import_node_path6 = require("path");
4973
5433
 
4974
5434
  // src/wrangler.ts
4975
- var import_node_child_process3 = require("child_process");
5435
+ var import_node_child_process5 = require("child_process");
4976
5436
  var import_node_fs8 = require("fs");
4977
5437
  var import_node_path5 = require("path");
4978
5438
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
4979
- const child = (0, import_node_child_process3.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
5439
+ const child = (0, import_node_child_process5.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4980
5440
  let stdout = "";
4981
5441
  let stderr = "";
4982
5442
  child.stdout.on("data", (chunk) => stdout += chunk.toString());
@@ -5067,7 +5527,7 @@ function lintRules(rules, entities, publicRead) {
5067
5527
  }
5068
5528
  return warnings;
5069
5529
  }
5070
- var defaultExec = (cmd, args, cwd) => (0, import_node_child_process4.execFileSync)(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
5530
+ var defaultExec = (cmd, args, cwd) => (0, import_node_child_process6.execFileSync)(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
5071
5531
  function trackedSecretFiles(rootDir, exec = defaultExec, localPaths = []) {
5072
5532
  let output;
5073
5533
  try {
@@ -5675,8 +6135,8 @@ async function readRegistryApp(cfg, token, doFetch) {
5675
6135
  });
5676
6136
  if (res.status === 404) return null;
5677
6137
  if (!res.ok) return null;
5678
- const json2 = await res.json();
5679
- return json2.app ?? null;
6138
+ const json = await res.json();
6139
+ return json.app ?? null;
5680
6140
  }
5681
6141
  async function postJson2(doFetch, url, bearer, body) {
5682
6142
  const res = await doFetch(url, {
@@ -5915,10 +6375,10 @@ async function provision(options) {
5915
6375
  stdout: out
5916
6376
  });
5917
6377
  } catch (error) {
5918
- const message3 = error instanceof Error ? error.message : String(error);
6378
+ const message2 = error instanceof Error ? error.message : String(error);
5919
6379
  const consent = env === "prod" || env === "production" ? " --yes" : "";
5920
6380
  throw new Error(
5921
- `${message3}
6381
+ `${message2}
5922
6382
  ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}${consent}" without issuing or rotating again`,
5923
6383
  { cause: error }
5924
6384
  );
@@ -6034,7 +6494,7 @@ async function resolveVaultWrite(options) {
6034
6494
  }
6035
6495
 
6036
6496
  // src/security-command-context.ts
6037
- var import_promises9 = require("readline/promises");
6497
+ var import_promises10 = require("readline/promises");
6038
6498
  async function hostedSecurityContext(parsed, dependencies) {
6039
6499
  const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
6040
6500
  const cfg = await loadProjectConfig(configPath);
@@ -6057,12 +6517,12 @@ async function hostedSecurityContext(parsed, dependencies) {
6057
6517
  );
6058
6518
  return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
6059
6519
  }
6060
- async function interactiveConfirmation(message3, dependencies) {
6061
- if (dependencies.confirm) return dependencies.confirm(message3);
6520
+ async function interactiveConfirmation(message2, dependencies) {
6521
+ if (dependencies.confirm) return dependencies.confirm(message2);
6062
6522
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
6063
- const prompt = (0, import_promises9.createInterface)({ input: process.stdin, output: process.stdout });
6523
+ const prompt = (0, import_promises10.createInterface)({ input: process.stdin, output: process.stdout });
6064
6524
  try {
6065
- const answer = await prompt.question(`${message3} [y/N] `);
6525
+ const answer = await prompt.question(`${message2} [y/N] `);
6066
6526
  return /^y(?:es)?$/i.test(answer.trim());
6067
6527
  } finally {
6068
6528
  prompt.close();
@@ -6189,7 +6649,7 @@ var import_security2 = require("@odla-ai/security");
6189
6649
  // src/security.ts
6190
6650
  var import_node_path8 = require("path");
6191
6651
  var import_security = require("@odla-ai/security");
6192
- var import_node2 = require("@odla-ai/security/node");
6652
+ var import_node3 = require("@odla-ai/security/node");
6193
6653
  async function runHostedSecurity(options) {
6194
6654
  if (options.sourceDisclosureAck !== "redacted") {
6195
6655
  throw new Error("Hosted security requires sourceDisclosureAck=redacted before repository source may leave this process");
@@ -6212,7 +6672,7 @@ async function runHostedSecurity(options) {
6212
6672
  scope: selfAudit ? "platform:security:self" : null
6213
6673
  };
6214
6674
  const token = await injectedToken(options, tokenRequest);
6215
- const snapshot = await (0, import_node2.snapshotDirectory)(target, {
6675
+ const snapshot = await (0, import_node3.snapshotDirectory)(target, {
6216
6676
  exclude: !outputRelative.startsWith("../") && !(0, import_node_path8.isAbsolute)(outputRelative) ? [outputRelative] : []
6217
6677
  });
6218
6678
  const hosted = await (0, import_security.createPlatformSecurityReasoners)({
@@ -6231,7 +6691,7 @@ async function runHostedSecurity(options) {
6231
6691
  });
6232
6692
  const harness = (0, import_security.createSecurityHarness)({
6233
6693
  profile,
6234
- store: new import_node2.FileRunStore((0, import_node_path8.resolve)(output, "state")),
6694
+ store: new import_node3.FileRunStore((0, import_node_path8.resolve)(output, "state")),
6235
6695
  discoveryReasoner: hosted.discoveryReasoner,
6236
6696
  validationReasoner: hosted.validationReasoner,
6237
6697
  policy: {
@@ -6241,7 +6701,7 @@ async function runHostedSecurity(options) {
6241
6701
  }
6242
6702
  });
6243
6703
  const report2 = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
6244
- await (0, import_node2.writeSecurityArtifacts)(output, report2);
6704
+ await (0, import_node3.writeSecurityArtifacts)(output, report2);
6245
6705
  const reportDigest = await (0, import_security.securityFingerprint)(report2);
6246
6706
  await hosted.complete({
6247
6707
  reportDigest,
@@ -6374,8 +6834,8 @@ async function startHostedSecurityJob(options) {
6374
6834
  "start hosted security job"
6375
6835
  );
6376
6836
  } catch (error) {
6377
- const message3 = error instanceof Error ? error.message : String(error);
6378
- if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message3)) {
6837
+ const message2 = error instanceof Error ? error.message : String(error);
6838
+ if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message2)) {
6379
6839
  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 });
6380
6840
  }
6381
6841
  throw error;