@odla-ai/cli 0.14.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,14 +2555,48 @@ 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 });
2396
2563
  throw error;
2397
2564
  }
2398
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 });
2592
+ throw error;
2593
+ }
2594
+ }
2595
+
2596
+ // ../harness/dist/chunk-QEW6NNAO.js
2597
+ var import_crypto = require("crypto");
2598
+ var import_promises5 = require("fs/promises");
2599
+ var import_path5 = require("path");
2399
2600
 
2400
2601
  // ../camel/dist/chunk-7FHPOQVP.js
2401
2602
  var CamelError = class extends Error {
@@ -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-QEW6NNAO.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-QEW6NNAO.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
  }
@@ -3248,9 +3472,9 @@ async function parseSource(value) {
3248
3472
  return { path: file.path, content: file.content };
3249
3473
  });
3250
3474
  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 };
3475
+ const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3476
+ if (digest !== snapshot.treeDigest) throw invalid("source digest");
3477
+ return { ...source, treeDigest: digest };
3254
3478
  }
3255
3479
  function parseReview(value) {
3256
3480
  const review = record3(record3(value)?.review);
@@ -3309,9 +3533,9 @@ function validateRelativePath(path) {
3309
3533
  }
3310
3534
  function resolveCodePath(workspaceDir, path) {
3311
3535
  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");
3536
+ const root = (0, import_path6.resolve)(workspaceDir);
3537
+ const target = (0, import_path6.resolve)(root, path);
3538
+ if (target !== root && !target.startsWith(`${root}${import_path6.sep}`)) throw new TypeError("path escapes the staged workspace");
3315
3539
  return target;
3316
3540
  }
3317
3541
  async function applyCodePatch(workspaceDir, patch2, paths) {
@@ -3319,7 +3543,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
3319
3543
  await gitApply(workspaceDir, patch2, false);
3320
3544
  for (const path of paths) {
3321
3545
  try {
3322
- const info = await (0, import_promises4.lstat)(resolveCodePath(workspaceDir, path));
3546
+ const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
3323
3547
  if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
3324
3548
  throw new TypeError("patch created a non-regular workspace entry");
3325
3549
  }
@@ -3331,7 +3555,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
3331
3555
  function gitApply(cwd, patch2, check) {
3332
3556
  return new Promise((accept, reject) => {
3333
3557
  const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
3334
- const child = (0, import_child_process3.spawn)("git", args, {
3558
+ const child = (0, import_child_process4.spawn)("git", args, {
3335
3559
  cwd,
3336
3560
  shell: false,
3337
3561
  stdio: ["pipe", "ignore", "pipe"],
@@ -3375,7 +3599,7 @@ async function restoreCodeWorkspaceCheckpoint(input) {
3375
3599
  }
3376
3600
  var ARTIFACT_PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
3377
3601
  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)}`) {
3602
+ function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0, import_crypto2.randomUUID)().slice(0, 12)}`) {
3379
3603
  assertCodeBuildRecipe(recipe2);
3380
3604
  if (/[,\r\n]/.test(workspaceDir)) throw new TypeError("workspace path contains unsupported mount characters");
3381
3605
  const uid = typeof import_process2.getuid === "function" ? (0, import_process2.getuid)() : 1e3;
@@ -3430,7 +3654,7 @@ function createContainerRecipeExecutor(engine) {
3430
3654
  return {
3431
3655
  async run(input) {
3432
3656
  await verifyContainerEngineBoundary(engine);
3433
- const name = `odla-recipe-${(0, import_crypto.randomUUID)().slice(0, 12)}`;
3657
+ const name = `odla-recipe-${(0, import_crypto2.randomUUID)().slice(0, 12)}`;
3434
3658
  const args = buildRecipeContainerArgs(engine, input.workspaceDir, input.recipe, name);
3435
3659
  return execute(engine, args, name, input.recipe, input.signal);
3436
3660
  }
@@ -3454,7 +3678,7 @@ function parseMemory(value) {
3454
3678
  function execute(engine, args, name, recipe2, signal) {
3455
3679
  return new Promise((accept, reject) => {
3456
3680
  const started = Date.now();
3457
- const child = (0, import_child_process4.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
3681
+ const child = (0, import_child_process5.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
3458
3682
  const stdout = [];
3459
3683
  const stderr = [];
3460
3684
  let bytes = 0;
@@ -3467,7 +3691,7 @@ function execute(engine, args, name, recipe2, signal) {
3467
3691
  timedOut = reason === "timeout";
3468
3692
  outputLimitExceeded = reason === "output";
3469
3693
  const remove = engine === "container" ? ["delete", "--force", name] : ["rm", "-f", name];
3470
- const killer = (0, import_child_process4.spawn)(engine, remove, { shell: false, stdio: "ignore" });
3694
+ const killer = (0, import_child_process5.spawn)(engine, remove, { shell: false, stdio: "ignore" });
3471
3695
  killer.unref();
3472
3696
  child.kill("SIGTERM");
3473
3697
  };
@@ -3501,32 +3725,6 @@ function execute(engine, args, name, recipe2, signal) {
3501
3725
  });
3502
3726
  });
3503
3727
  }
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
3728
  var SHA3 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
3531
3729
  var DIGEST3 = /^sha256:[0-9a-f]{64}$/;
3532
3730
  var ID3 = /^[A-Za-z0-9._:-]{1,160}$/;
@@ -3574,7 +3772,7 @@ async function verifyCodeCandidate(input) {
3574
3772
  }
3575
3773
  const fields = {
3576
3774
  schemaVersion: 1,
3577
- verificationId: input.verificationId ?? `verify-${(0, import_crypto2.randomUUID)()}`,
3775
+ verificationId: input.verificationId ?? `verify-${(0, import_crypto3.randomUUID)()}`,
3578
3776
  trustedBaseCommitSha: input.trustedBaseCommitSha,
3579
3777
  trustedBaseDigest: input.trustedBaseDigest,
3580
3778
  patchDigest,
@@ -3635,8 +3833,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
3635
3833
  const receipts = [];
3636
3834
  for (const artifact of recipe2.expectedArtifacts ?? []) {
3637
3835
  try {
3638
- const path = (0, import_path4.join)(workspaceDir, artifact.path);
3639
- const info = await (0, import_promises5.lstat)(path);
3836
+ const path = (0, import_path7.join)(workspaceDir, artifact.path);
3837
+ const info = await (0, import_promises7.lstat)(path);
3640
3838
  if (!info.isFile() || info.isSymbolicLink()) {
3641
3839
  receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
3642
3840
  } else if (info.size > artifact.maximumBytes) {
@@ -3653,7 +3851,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
3653
3851
  }
3654
3852
  function hashFile(path) {
3655
3853
  return new Promise((accept, reject) => {
3656
- const hash = (0, import_crypto2.createHash)("sha256");
3854
+ const hash = (0, import_crypto3.createHash)("sha256");
3657
3855
  const stream = (0, import_fs2.createReadStream)(path);
3658
3856
  stream.on("data", (chunk) => {
3659
3857
  hash.update(chunk);
@@ -3711,7 +3909,7 @@ function digestJson(value) {
3711
3909
  return digestBytes(JSON.stringify(value));
3712
3910
  }
3713
3911
  function digestBytes(value) {
3714
- return `sha256:${(0, import_crypto2.createHash)("sha256").update(value).digest("hex")}`;
3912
+ return `sha256:${(0, import_crypto3.createHash)("sha256").update(value).digest("hex")}`;
3715
3913
  }
3716
3914
  function integer(value, minimum, maximum) {
3717
3915
  return Number.isSafeInteger(value) && value >= minimum && value <= maximum;
@@ -3732,8 +3930,8 @@ async function prepareRuntimeCheckpoint(input) {
3732
3930
  policy: {
3733
3931
  policyId: "code.runtime",
3734
3932
  recipes: input.recipes,
3735
- maximumFiles: 1e4,
3736
- maximumBytes: 16 * 1024 * 1024
3933
+ maximumFiles: 2e4,
3934
+ maximumBytes: 512 * 1024 * 1024
3737
3935
  },
3738
3936
  recipeExecutor: input.recipeExecutor
3739
3937
  });
@@ -3815,11 +4013,11 @@ var CodeRuntimeCheckpointManager = class {
3815
4013
  };
3816
4014
  var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
3817
4015
  var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
3818
- async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os2.tmpdir)()) {
4016
+ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.tmpdir)()) {
3819
4017
  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);
4018
+ const root = await (0, import_promises8.mkdtemp)((0, import_path8.join)(tempRoot, "odla-code-source-"));
4019
+ const sourceDir = (0, import_path8.join)(root, "source");
4020
+ await (0, import_promises8.mkdir)(sourceDir);
3823
4021
  const seen = /* @__PURE__ */ new Set();
3824
4022
  let bytes = 0;
3825
4023
  try {
@@ -3829,14 +4027,14 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os2.
3829
4027
  seen.add(file.path);
3830
4028
  bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
3831
4029
  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 });
4030
+ const target = (0, import_path8.resolve)(sourceDir, file.path);
4031
+ if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code source path escapes its root");
4032
+ await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
4033
+ await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 420 });
3836
4034
  }
3837
- return { sourceDir, cleanup: () => (0, import_promises7.rm)(root, { recursive: true, force: true }) };
4035
+ return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
3838
4036
  } catch (cause) {
3839
- await (0, import_promises7.rm)(root, { recursive: true, force: true });
4037
+ await (0, import_promises8.rm)(root, { recursive: true, force: true });
3840
4038
  throw cause;
3841
4039
  }
3842
4040
  }
@@ -3942,15 +4140,15 @@ async function environment(input, options, tool) {
3942
4140
  { id: "authority", value: `lease:${input.lease.leaseId}`, readers: input.readers },
3943
4141
  { id: "reader", value: options.readerId, readers: input.readers }
3944
4142
  ]);
3945
- const digest2 = await destinationRegistryDigest([input.workspaceId]);
4143
+ const digest = await destinationRegistryDigest([input.workspaceId]);
3946
4144
  const approvals = ["irreversible_mutation", "external_send", "financial", "secret_read"];
3947
4145
  if (options.recipeAuthorization === "exact_approval") approvals.push("code_execution");
3948
4146
  const policy = createEffectPolicy({
3949
- destinationRegistries: { [DESTINATIONS]: { digest: digest2, values: [input.workspaceId] } },
4147
+ destinationRegistries: { [DESTINATIONS]: { digest, values: [input.workspaceId] } },
3950
4148
  approvalEffects: approvals
3951
4149
  });
3952
4150
  const fixedArgs = {
3953
- workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest2 },
4151
+ workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest },
3954
4152
  authority: { role: "authority", value: ingress.control("authority") }
3955
4153
  };
3956
4154
  return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
@@ -4021,11 +4219,11 @@ async function read(context, request, options, policy) {
4021
4219
  const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
4022
4220
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
4023
4221
  const target = resolveCodePath(context.workspaceDir, path);
4024
- const info = await (0, import_promises8.stat)(target);
4222
+ const info = await (0, import_promises9.stat)(target);
4025
4223
  if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
4026
4224
  throw new TypeError("file is not a bounded regular source file");
4027
4225
  }
4028
- const source = await (0, import_promises8.readFile)(target);
4226
+ const source = await (0, import_promises9.readFile)(target);
4029
4227
  if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
4030
4228
  const lines = source.toString("utf8").split("\n");
4031
4229
  const content = lines.slice(startLine - 1, endLine).join("\n");
@@ -4099,12 +4297,12 @@ function policyContext(context, request, options, extra) {
4099
4297
  async function registeredFiles(root, limit) {
4100
4298
  const paths = [];
4101
4299
  const walk = async (directory) => {
4102
- for (const entry of await (0, import_promises8.readdir)(directory, { withFileTypes: true })) {
4300
+ for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
4103
4301
  if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
4104
- const target = (0, import_path7.resolve)(directory, entry.name);
4302
+ const target = (0, import_path9.resolve)(directory, entry.name);
4105
4303
  if (entry.isDirectory()) await walk(target);
4106
4304
  else if (entry.isFile()) {
4107
- const path = (0, import_path7.relative)(root, target).split("\\").join("/");
4305
+ const path = (0, import_path9.relative)(root, target).split("\\").join("/");
4108
4306
  try {
4109
4307
  validateRelativePath(path);
4110
4308
  } catch {
@@ -4115,7 +4313,7 @@ async function registeredFiles(root, limit) {
4115
4313
  }
4116
4314
  }
4117
4315
  };
4118
- await walk((0, import_path7.resolve)(root));
4316
+ await walk((0, import_path9.resolve)(root));
4119
4317
  return paths.sort();
4120
4318
  }
4121
4319
  function validateOptions(options) {
@@ -4150,14 +4348,31 @@ function codeCommandMetadata(payload, resume) {
4150
4348
  }
4151
4349
  const planning = trusted?.planningInputDigest;
4152
4350
  const attestation = trusted?.attestationDigest;
4351
+ const repository = trusted?.repository;
4352
+ const baseCommitSha = trusted?.commitSha;
4353
+ const sourceTreeDigest = trusted?.treeDigest;
4354
+ 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)) {
4355
+ throw new TypeError(`invalid Code ${resume ? "resume" : "start"} trusted base`);
4356
+ }
4153
4357
  return {
4154
4358
  role,
4155
4359
  title,
4156
4360
  prompt,
4157
4361
  planningInputDigest: typeof planning === "string" && /^sha256:[0-9a-f]{64}$/.test(planning) ? planning : null,
4158
- attestationDigest: typeof attestation === "string" ? attestation : "resume"
4362
+ attestationDigest: typeof attestation === "string" ? attestation : "resume",
4363
+ repository,
4364
+ baseCommitSha,
4365
+ sourceTreeDigest
4159
4366
  };
4160
4367
  }
4368
+ function codeLocalSource(payload) {
4369
+ const source = record22(payload.source);
4370
+ if (!source) return null;
4371
+ 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) {
4372
+ throw new TypeError("invalid local checkout source descriptor");
4373
+ }
4374
+ return source;
4375
+ }
4161
4376
  function codeCheckpointPayload(payload) {
4162
4377
  const value = payload.checkpoint;
4163
4378
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
@@ -4186,11 +4401,62 @@ function fakeCodeLease(command, metadata2) {
4186
4401
  };
4187
4402
  }
4188
4403
  var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4404
+ var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
4405
+ async function prepareRuntimeLocalSource(input) {
4406
+ const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
4407
+ if (!available || JSON.stringify(available.descriptor) !== JSON.stringify(descriptor2) || descriptor2.repository.toLowerCase() !== repository.toLowerCase() || descriptor2.headCommitSha !== baseCommitSha) {
4408
+ throw new TypeError("the session's local checkout snapshot is not available on this terminal");
4409
+ }
4410
+ const workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4411
+ trustedBaseDir: available.trustedBaseDir,
4412
+ trustedBaseCommitSha: baseCommitSha,
4413
+ checkpoint: codeCheckpointPayload(command.payload)
4414
+ })).workspace : await stageWorkspacePair(available.trustedBaseDir, available.sourceDir, SOURCE_LIMITS);
4415
+ const trustedBaseDigest = await digestStagedWorkspace(workspace.baselineDir, SOURCE_LIMITS);
4416
+ if (trustedBaseDigest !== descriptor2.trustedBaseDigest) {
4417
+ await workspace.cleanup();
4418
+ throw new TypeError("trusted Git base digest changed after connection");
4419
+ }
4420
+ if (!resume && await digestStagedWorkspace(workspace.workspaceDir, SOURCE_LIMITS) !== descriptor2.snapshotDigest) {
4421
+ await workspace.cleanup();
4422
+ throw new TypeError("local checkout snapshot digest changed after connection");
4423
+ }
4424
+ return { workspace, sourceDigest: descriptor2.snapshotDigest, trustedBaseDigest };
4425
+ }
4426
+ function createCodeRuntimeToolBroker(input, lease, role) {
4427
+ const broker = createCodeToolBroker({
4428
+ recipes: input.recipes,
4429
+ recipeExecutor: createContainerRecipeExecutor(input.engine),
4430
+ recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
4431
+ readerId: `code-session:${lease.task.taskId}`
4432
+ });
4433
+ 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" }) };
4434
+ }
4435
+ async function appendCodeRuntimeEvent(control, command, event, refs) {
4436
+ const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
4437
+ refs.push(eventId);
4438
+ const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
4439
+ await control.appendSessionEvent(command.sessionId, eventId, bounded);
4440
+ }
4441
+ var digestRuntimeValue = (value) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value).digest("hex")}`;
4442
+ var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
4443
+ var runtimeRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4444
+ var safeRuntimeJson = (value) => {
4445
+ try {
4446
+ return JSON.stringify(value).slice(0, 1e4);
4447
+ } catch {
4448
+ return "[event]";
4449
+ }
4450
+ };
4451
+ function runtimeResultText(value) {
4452
+ const record32 = runtimeRecord(value);
4453
+ return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4454
+ }
4189
4455
  var CodePiRuntimeEngine = class {
4190
4456
  constructor(options) {
4191
4457
  this.options = options;
4192
4458
  this.#run = options.runAttempt ?? runContainerAttempt;
4193
- this.#buildPolicyDigest = digest(JSON.stringify(options.recipes));
4459
+ this.#buildPolicyDigest = digestRuntimeValue(JSON.stringify(options.recipes));
4194
4460
  this.#checkpoints = new CodeRuntimeCheckpointManager({
4195
4461
  control: options.control,
4196
4462
  recipes: options.recipes,
@@ -4225,19 +4491,35 @@ var CodePiRuntimeEngine = class {
4225
4491
  }
4226
4492
  async #start(command, resume) {
4227
4493
  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);
4494
+ const metadata2 = codeCommandMetadata(command.payload, resume);
4495
+ const requestedLocal = codeLocalSource(command.payload);
4230
4496
  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();
4497
+ let sourceDigest;
4498
+ let localTrustedBaseDigest;
4499
+ if (requestedLocal) {
4500
+ const prepared = await prepareRuntimeLocalSource({
4501
+ command,
4502
+ descriptor: requestedLocal,
4503
+ available: this.options.localSource,
4504
+ repository: metadata2.repository,
4505
+ baseCommitSha: metadata2.baseCommitSha,
4506
+ resume
4507
+ });
4508
+ ({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
4509
+ } else {
4510
+ const source = await this.options.control.source(command.sessionId);
4511
+ const materialized = await materializeCodeRuntimeSource(source);
4512
+ try {
4513
+ workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4514
+ trustedBaseDir: materialized.sourceDir,
4515
+ trustedBaseCommitSha: source.commitSha,
4516
+ checkpoint: codeCheckpointPayload(command.payload)
4517
+ })).workspace : await stageWorkspace(materialized.sourceDir);
4518
+ } finally {
4519
+ await materialized.cleanup();
4520
+ }
4521
+ sourceDigest = source.treeDigest;
4239
4522
  }
4240
- const metadata2 = codeCommandMetadata(command.payload, resume);
4241
4523
  const abort = new AbortController();
4242
4524
  const conversationRefs = [];
4243
4525
  const active = {
@@ -4247,21 +4529,30 @@ var CodePiRuntimeEngine = class {
4247
4529
  acknowledged: false,
4248
4530
  role: metadata2.role,
4249
4531
  title: metadata2.title,
4250
- baseCommitSha: source.commitSha,
4251
- trustedBaseDigest: await digestStagedWorkspace(workspace.baselineDir, {
4532
+ baseCommitSha: metadata2.baseCommitSha,
4533
+ repository: metadata2.repository,
4534
+ sourceTreeDigest: metadata2.sourceTreeDigest,
4535
+ trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
4252
4536
  maxFiles: 1e4,
4253
4537
  maxBytes: 16 * 1024 * 1024
4254
4538
  }),
4255
- planningInputDigest: metadata2.planningInputDigest ?? digest(
4256
- JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: source.treeDigest })
4539
+ planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
4540
+ JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })
4257
4541
  ),
4258
4542
  done: Promise.resolve(null)
4259
4543
  };
4260
4544
  this.#active.set(command.sessionId, active);
4545
+ if (requestedLocal) {
4546
+ await this.#event(command, {
4547
+ type: "message",
4548
+ actor: "system",
4549
+ body: `Source snapshot: local checkout ${requestedLocal.snapshotDigest} \xB7 ${requestedLocal.modified ? "modified" : "clean"} \xB7 Git ${requestedLocal.headCommitSha}`
4550
+ }, conversationRefs);
4551
+ }
4261
4552
  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);
4553
+ await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` }, conversationRefs).catch(() => void 0);
4263
4554
  await this.#event(command, { type: "status", status: "failed" }, conversationRefs).catch(() => void 0);
4264
- await this.#failure(command, active, message2(cause));
4555
+ await this.#failure(command, active, runtimeErrorMessage(cause));
4265
4556
  return null;
4266
4557
  });
4267
4558
  return { status: "running", message: resume ? "Pi resumed from a portable checkpoint" : "Pi started" };
@@ -4281,22 +4572,29 @@ var CodePiRuntimeEngine = class {
4281
4572
  title: active.title,
4282
4573
  prompt,
4283
4574
  planningInputDigest: active.planningInputDigest,
4284
- attestationDigest: "follow-up"
4575
+ attestationDigest: "follow-up",
4576
+ repository: active.repository,
4577
+ baseCommitSha: active.baseCommitSha,
4578
+ sourceTreeDigest: active.sourceTreeDigest
4285
4579
  }, active).catch(async (cause) => {
4286
4580
  await this.#event(
4287
4581
  command,
4288
- { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` },
4582
+ { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` },
4289
4583
  active.conversationRefs
4290
4584
  ).catch(() => void 0);
4291
4585
  await this.#event(command, { type: "status", status: "failed" }, active.conversationRefs).catch(() => void 0);
4292
- await this.#failure(command, active, message2(cause));
4586
+ await this.#failure(command, active, runtimeErrorMessage(cause));
4293
4587
  return null;
4294
4588
  });
4295
4589
  return { status: "running", message: "Pi accepted the owner prompt" };
4296
4590
  }
4297
4591
  async #runAttempt(command, metadata2, active) {
4298
4592
  const lease = fakeCodeLease(command, metadata2);
4299
- const broker = this.#toolBroker(lease, metadata2.role);
4593
+ const broker = createCodeRuntimeToolBroker({
4594
+ recipes: this.options.recipes,
4595
+ engine: this.options.engine,
4596
+ recipeAuthorization: this.options.recipeAuthorization
4597
+ }, lease, metadata2.role);
4300
4598
  const startedAt = Date.now();
4301
4599
  let completionSeen = false;
4302
4600
  const result = await this.#run({
@@ -4356,7 +4654,7 @@ var CodePiRuntimeEngine = class {
4356
4654
  return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
4357
4655
  }
4358
4656
  if (output.type === "event") {
4359
- const payload = object2(output.payload);
4657
+ const payload = runtimeRecord(output.payload);
4360
4658
  if (output.kind === "pi.started") {
4361
4659
  await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
4362
4660
  } else if (output.kind === "pi.thinking" && payload?.available === true && Number.isSafeInteger(payload.durationMs) && Number(payload.durationMs) >= 0) {
@@ -4369,12 +4667,12 @@ var CodePiRuntimeEngine = class {
4369
4667
  await this.#event(command, {
4370
4668
  type: "message",
4371
4669
  actor: "system",
4372
- body: `${output.kind}${output.payload === void 0 ? "" : ` ${json(output.payload)}`}`
4670
+ body: `${output.kind}${output.payload === void 0 ? "" : ` ${safeRuntimeJson(output.payload)}`}`
4373
4671
  }, active.conversationRefs);
4374
4672
  }
4375
4673
  } else if (output.type === "attempt.complete") {
4376
4674
  completionSeen = true;
4377
- const body = resultText(output.result) ?? `Pi ${output.status}.`;
4675
+ const body = runtimeResultText(output.result) ?? `Pi ${output.status}.`;
4378
4676
  await this.#event(command, {
4379
4677
  type: "message",
4380
4678
  actor: output.status === "completed" ? "agent" : "system",
@@ -4401,15 +4699,6 @@ var CodePiRuntimeEngine = class {
4401
4699
  }
4402
4700
  return result;
4403
4701
  }
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
4702
  async #checkpoint(command) {
4414
4703
  const active = this.#active.get(command.sessionId);
4415
4704
  if (!active) throw new TypeError("Code session workspace is not active on this runtime");
@@ -4424,26 +4713,9 @@ var CodePiRuntimeEngine = class {
4424
4713
  }
4425
4714
  }
4426
4715
  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]";
4716
+ await appendCodeRuntimeEvent(this.options.control, command, event, refs);
4441
4717
  }
4442
4718
  };
4443
- function resultText(value) {
4444
- const record32 = object2(value);
4445
- return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4446
- }
4447
4719
 
4448
4720
  // src/help.ts
4449
4721
  var import_node_fs6 = require("fs");
@@ -4585,8 +4857,8 @@ async function requestHostedSecurityJson(options, path, init, action) {
4585
4857
  const body = await response2.json().catch(() => ({}));
4586
4858
  if (!response2.ok) {
4587
4859
  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}` : ""}`);
4860
+ const message2 = optionalHostedText(body.error?.message, "error message", 300);
4861
+ throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
4590
4862
  }
4591
4863
  return body;
4592
4864
  }
@@ -4775,24 +5047,111 @@ async function defaultReadOrigin(cwd) {
4775
5047
  return result.stdout;
4776
5048
  }
4777
5049
 
4778
- // src/code-connect.ts
4779
- var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:713db58428bc05cb486167c5747de8fd826669c9b283ff78e0139ac194c3d058";
5050
+ // src/code-local-source.ts
5051
+ var import_node_child_process3 = require("child_process");
5052
+ var import_node_crypto = require("crypto");
5053
+ var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
5054
+ async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
5055
+ const headCommitSha = await readHead(cwd);
5056
+ const staged = await stageWorkspace(cwd, { ...SOURCE_LIMITS2, gitTrackedAndUnignored: true });
5057
+ let trusted;
5058
+ try {
5059
+ trusted = await materializeGitTree(cwd, headCommitSha, SOURCE_LIMITS2);
5060
+ const comparison = await stageWorkspacePair(trusted.sourceDir, staged.baselineDir, SOURCE_LIMITS2);
5061
+ let developerPatch;
5062
+ try {
5063
+ developerPatch = await comparison.patch(4 * 1024 * 1024);
5064
+ } finally {
5065
+ await comparison.cleanup();
5066
+ }
5067
+ const descriptor2 = {
5068
+ kind: "local_checkout",
5069
+ repository,
5070
+ headCommitSha,
5071
+ trustedBaseDigest: await digestStagedWorkspace(trusted.sourceDir, SOURCE_LIMITS2),
5072
+ developerPatchDigest: digestText(developerPatch),
5073
+ snapshotDigest: await digestStagedWorkspace(staged.baselineDir, SOURCE_LIMITS2),
5074
+ modified: developerPatch.length > 0,
5075
+ fileCount: staged.fileCount,
5076
+ byteCount: staged.byteCount,
5077
+ capturedAt: Date.now()
5078
+ };
5079
+ return {
5080
+ descriptor: descriptor2,
5081
+ trustedBaseDir: trusted.sourceDir,
5082
+ sourceDir: staged.baselineDir,
5083
+ cleanup: async () => {
5084
+ await Promise.all([staged.cleanup(), trusted.cleanup()]);
5085
+ }
5086
+ };
5087
+ } catch (error) {
5088
+ await Promise.all([staged.cleanup(), trusted?.cleanup()]);
5089
+ throw error;
5090
+ }
5091
+ }
5092
+ async function readGitHead(cwd) {
5093
+ const value = await new Promise((accept, reject) => {
5094
+ (0, import_node_child_process3.execFile)("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
5095
+ if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
5096
+ else accept(stdout.trim());
5097
+ });
5098
+ });
5099
+ if (!/^[0-9a-f]{40}$/.test(value)) throw new Error("code connect could not resolve the checkout HEAD commit");
5100
+ return value;
5101
+ }
5102
+ function digestText(value) {
5103
+ return `sha256:${(0, import_node_crypto.createHash)("sha256").update(value).digest("hex")}`;
5104
+ }
5105
+
5106
+ // src/code-images.ts
5107
+ var import_node_child_process4 = require("child_process");
5108
+ async function prepareCodeImages(engine, images) {
5109
+ for (const image of images) {
5110
+ await new Promise((accept, reject) => {
5111
+ const args = engine === "container" ? ["image", "pull", image] : ["pull", image];
5112
+ (0, import_node_child_process4.execFile)(engine, args, { encoding: "utf8", maxBuffer: 2 * 1024 * 1024, timeout: 10 * 6e4 }, (error) => {
5113
+ if (error) reject(new Error(`could not prepare pinned Code image ${image}`));
5114
+ else accept();
5115
+ });
5116
+ });
5117
+ }
5118
+ }
5119
+
5120
+ // src/code-runtime-config.ts
5121
+ var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:e7858b4f0291543171b2659b47c5d00f97acc4767cafd9b74ae5228899e0cde4";
4780
5122
  var CODE_BUILD_RECIPES = Object.freeze([{
4781
- id: "node-test",
5123
+ id: "odla-code-contracts",
4782
5124
  image: "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd",
4783
- command: ["node", "--test"],
5125
+ command: [
5126
+ "node",
5127
+ "--test",
5128
+ "scripts/lib/directories.test.mjs",
5129
+ "scripts/lib/internal-deps.test.mjs",
5130
+ "scripts/lib/npm-audit.test.mjs",
5131
+ "scripts/lib/npm-release-contract.test.mjs",
5132
+ "scripts/lib/release-surfaces.test.mjs"
5133
+ ],
4784
5134
  timeoutMs: 12e4,
4785
5135
  maxOutputBytes: 1024 * 1024,
4786
5136
  cpus: 1,
4787
5137
  memory: "512m",
4788
5138
  pids: 128
4789
5139
  }]);
5140
+
5141
+ // src/code-connect.ts
4790
5142
  async function codeConnect(options) {
4791
5143
  const cwd = options.cwd ?? process.cwd();
4792
5144
  const configPath = (0, import_node_path4.resolve)(cwd, options.configPath);
4793
5145
  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";
5146
+ const requestedAppId = options.appId?.trim();
5147
+ if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
5148
+ throw new Error("--app-id must be a valid odla app id");
5149
+ }
5150
+ if (requestedAppId && cfg && requestedAppId !== cfg.app.id) {
5151
+ throw new Error(`--app-id ${requestedAppId} does not match ${options.configPath} (${cfg.app.id})`);
5152
+ }
5153
+ const appId = requestedAppId ?? cfg?.app.id;
5154
+ const platform = (options.platform ?? cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai").replace(/\/+$/, "");
4796
5155
  const appEnv = options.env ?? (cfg ? cfg.envs.includes("dev") ? "dev" : cfg.envs[0] : "dev");
4797
5156
  if (appEnv !== "dev" && appEnv !== "prod" || cfg && !cfg.envs.includes(appEnv)) {
4798
5157
  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 +5170,77 @@ async function codeConnect(options) {
4811
5170
  const out = options.stdout ?? console;
4812
5171
  const doFetch = options.fetch ?? fetch;
4813
5172
  const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
5173
+ await (options.prepareImages ?? prepareCodeImages)(engine, [
5174
+ CODE_PI_IMAGE,
5175
+ ...new Set(CODE_BUILD_RECIPES.map((recipe2) => recipe2.image))
5176
+ ]);
4814
5177
  const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
4815
5178
  const hostName = (options.name ?? (0, import_node_os.hostname)()).trim();
4816
5179
  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
- });
5180
+ const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
5181
+ const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
5182
+ cwd,
5183
+ repository,
5184
+ options.readGitHead
5185
+ );
5186
+ try {
5187
+ const descriptor2 = localSource.descriptor;
5188
+ const approval = await (options.getToken ?? getScopedPlatformToken)({
5189
+ platform,
5190
+ scope: "app:code:host:connect",
5191
+ email: options.email,
5192
+ open: options.open,
5193
+ fetch: doFetch,
5194
+ stdout: out,
5195
+ openApprovalUrl: options.openApprovalUrl,
5196
+ cache: false,
5197
+ label: `Terminal connection for ${appId ?? repository}/${appEnv}`
5198
+ });
5199
+ const target = appId ? { appId } : { repository };
5200
+ const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
5201
+ method: "POST",
5202
+ headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
5203
+ body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
5204
+ redirect: "error",
5205
+ signal: options.signal
5206
+ });
5207
+ const raw = await response2.json().catch(() => null);
5208
+ if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
5209
+ const connection = parseConnection(raw, appId, appEnv);
5210
+ const capabilities = {
5211
+ protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
5212
+ platform: hostPlatform,
5213
+ arch: process.arch,
5214
+ engines: [engine],
5215
+ cpuCount: (0, import_node_os.cpus)().length,
5216
+ memoryBytes: (0, import_node_os.totalmem)(),
5217
+ source: descriptor2,
5218
+ images: {
5219
+ ready: true,
5220
+ pi: CODE_PI_IMAGE,
5221
+ recipes: CODE_BUILD_RECIPES.map((recipe2) => ({ id: recipe2.id, image: recipe2.image }))
5222
+ }
5223
+ };
5224
+ out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
5225
+ out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
5226
+ out.log(`source: local checkout \xB7 ${descriptor2.headCommitSha.slice(0, 10)} \xB7 ${descriptor2.fileCount} files \xB7 staged copy`);
5227
+ out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
5228
+ out.log(`Studio: ${platform}/studio/apps/${encodeURIComponent(connection.binding.appId)}/${connection.binding.env}/code/sessions`);
5229
+ await (options.runRuntime ?? runCodeRuntime)({
5230
+ endpoint: platform,
5231
+ token: connection.token,
5232
+ engine,
5233
+ capabilities,
5234
+ localSource,
5235
+ heartbeatMs,
5236
+ once: options.once === true,
5237
+ signal: options.signal,
5238
+ stdout: out,
5239
+ fetch: doFetch
5240
+ });
5241
+ } finally {
5242
+ await localSource.cleanup();
5243
+ }
4861
5244
  }
4862
5245
  async function runCodeRuntime(input) {
4863
5246
  const controller = new AbortController();
@@ -4880,7 +5263,8 @@ async function runCodeRuntime(input) {
4880
5263
  engine: input.engine,
4881
5264
  image: CODE_PI_IMAGE,
4882
5265
  recipes: CODE_BUILD_RECIPES,
4883
- recipeAuthorization: "registered_recipe"
5266
+ recipeAuthorization: "registered_recipe",
5267
+ localSource: input.localSource
4884
5268
  });
4885
5269
  const reconciler = new CodeRuntimeReconciler(control, commandEngine);
4886
5270
  try {
@@ -4921,8 +5305,8 @@ function parseConnection(value, appId, appEnv) {
4921
5305
  return root;
4922
5306
  }
4923
5307
  function apiFailure(action, status, value) {
4924
- const message3 = record4(record4(value)?.error)?.message;
4925
- return `${action} failed (${status})${typeof message3 === "string" ? `: ${message3}` : ""}`;
5308
+ const message2 = record4(record4(value)?.error)?.message;
5309
+ return `${action} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
4926
5310
  }
4927
5311
  function record4(value) {
4928
5312
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -4937,6 +5321,8 @@ async function codeCommand(parsed, dependencies) {
4937
5321
  assertArgs(parsed, [
4938
5322
  "config",
4939
5323
  "env",
5324
+ "platform",
5325
+ "app-id",
4940
5326
  "email",
4941
5327
  "open",
4942
5328
  "name",
@@ -4952,6 +5338,8 @@ async function codeCommand(parsed, dependencies) {
4952
5338
  await (dependencies.codeConnect ?? codeConnect)({
4953
5339
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
4954
5340
  env: stringOpt(parsed.options.env),
5341
+ platform: stringOpt(parsed.options.platform),
5342
+ appId: stringOpt(parsed.options["app-id"]),
4955
5343
  email: stringOpt(parsed.options.email),
4956
5344
  open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
4957
5345
  name: stringOpt(parsed.options.name),
@@ -4967,16 +5355,16 @@ async function codeCommand(parsed, dependencies) {
4967
5355
  }
4968
5356
 
4969
5357
  // src/doctor-checks.ts
4970
- var import_node_child_process4 = require("child_process");
5358
+ var import_node_child_process6 = require("child_process");
4971
5359
  var import_node_fs9 = require("fs");
4972
5360
  var import_node_path6 = require("path");
4973
5361
 
4974
5362
  // src/wrangler.ts
4975
- var import_node_child_process3 = require("child_process");
5363
+ var import_node_child_process5 = require("child_process");
4976
5364
  var import_node_fs8 = require("fs");
4977
5365
  var import_node_path5 = require("path");
4978
5366
  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"] });
5367
+ const child = (0, import_node_child_process5.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4980
5368
  let stdout = "";
4981
5369
  let stderr = "";
4982
5370
  child.stdout.on("data", (chunk) => stdout += chunk.toString());
@@ -5067,7 +5455,7 @@ function lintRules(rules, entities, publicRead) {
5067
5455
  }
5068
5456
  return warnings;
5069
5457
  }
5070
- var defaultExec = (cmd, args, cwd) => (0, import_node_child_process4.execFileSync)(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
5458
+ var defaultExec = (cmd, args, cwd) => (0, import_node_child_process6.execFileSync)(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
5071
5459
  function trackedSecretFiles(rootDir, exec = defaultExec, localPaths = []) {
5072
5460
  let output;
5073
5461
  try {
@@ -5675,8 +6063,8 @@ async function readRegistryApp(cfg, token, doFetch) {
5675
6063
  });
5676
6064
  if (res.status === 404) return null;
5677
6065
  if (!res.ok) return null;
5678
- const json2 = await res.json();
5679
- return json2.app ?? null;
6066
+ const json = await res.json();
6067
+ return json.app ?? null;
5680
6068
  }
5681
6069
  async function postJson2(doFetch, url, bearer, body) {
5682
6070
  const res = await doFetch(url, {
@@ -5915,10 +6303,10 @@ async function provision(options) {
5915
6303
  stdout: out
5916
6304
  });
5917
6305
  } catch (error) {
5918
- const message3 = error instanceof Error ? error.message : String(error);
6306
+ const message2 = error instanceof Error ? error.message : String(error);
5919
6307
  const consent = env === "prod" || env === "production" ? " --yes" : "";
5920
6308
  throw new Error(
5921
- `${message3}
6309
+ `${message2}
5922
6310
  ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}${consent}" without issuing or rotating again`,
5923
6311
  { cause: error }
5924
6312
  );
@@ -6034,7 +6422,7 @@ async function resolveVaultWrite(options) {
6034
6422
  }
6035
6423
 
6036
6424
  // src/security-command-context.ts
6037
- var import_promises9 = require("readline/promises");
6425
+ var import_promises10 = require("readline/promises");
6038
6426
  async function hostedSecurityContext(parsed, dependencies) {
6039
6427
  const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
6040
6428
  const cfg = await loadProjectConfig(configPath);
@@ -6057,12 +6445,12 @@ async function hostedSecurityContext(parsed, dependencies) {
6057
6445
  );
6058
6446
  return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
6059
6447
  }
6060
- async function interactiveConfirmation(message3, dependencies) {
6061
- if (dependencies.confirm) return dependencies.confirm(message3);
6448
+ async function interactiveConfirmation(message2, dependencies) {
6449
+ if (dependencies.confirm) return dependencies.confirm(message2);
6062
6450
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
6063
- const prompt = (0, import_promises9.createInterface)({ input: process.stdin, output: process.stdout });
6451
+ const prompt = (0, import_promises10.createInterface)({ input: process.stdin, output: process.stdout });
6064
6452
  try {
6065
- const answer = await prompt.question(`${message3} [y/N] `);
6453
+ const answer = await prompt.question(`${message2} [y/N] `);
6066
6454
  return /^y(?:es)?$/i.test(answer.trim());
6067
6455
  } finally {
6068
6456
  prompt.close();
@@ -6189,7 +6577,7 @@ var import_security2 = require("@odla-ai/security");
6189
6577
  // src/security.ts
6190
6578
  var import_node_path8 = require("path");
6191
6579
  var import_security = require("@odla-ai/security");
6192
- var import_node2 = require("@odla-ai/security/node");
6580
+ var import_node3 = require("@odla-ai/security/node");
6193
6581
  async function runHostedSecurity(options) {
6194
6582
  if (options.sourceDisclosureAck !== "redacted") {
6195
6583
  throw new Error("Hosted security requires sourceDisclosureAck=redacted before repository source may leave this process");
@@ -6212,7 +6600,7 @@ async function runHostedSecurity(options) {
6212
6600
  scope: selfAudit ? "platform:security:self" : null
6213
6601
  };
6214
6602
  const token = await injectedToken(options, tokenRequest);
6215
- const snapshot = await (0, import_node2.snapshotDirectory)(target, {
6603
+ const snapshot = await (0, import_node3.snapshotDirectory)(target, {
6216
6604
  exclude: !outputRelative.startsWith("../") && !(0, import_node_path8.isAbsolute)(outputRelative) ? [outputRelative] : []
6217
6605
  });
6218
6606
  const hosted = await (0, import_security.createPlatformSecurityReasoners)({
@@ -6231,7 +6619,7 @@ async function runHostedSecurity(options) {
6231
6619
  });
6232
6620
  const harness = (0, import_security.createSecurityHarness)({
6233
6621
  profile,
6234
- store: new import_node2.FileRunStore((0, import_node_path8.resolve)(output, "state")),
6622
+ store: new import_node3.FileRunStore((0, import_node_path8.resolve)(output, "state")),
6235
6623
  discoveryReasoner: hosted.discoveryReasoner,
6236
6624
  validationReasoner: hosted.validationReasoner,
6237
6625
  policy: {
@@ -6241,7 +6629,7 @@ async function runHostedSecurity(options) {
6241
6629
  }
6242
6630
  });
6243
6631
  const report2 = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
6244
- await (0, import_node2.writeSecurityArtifacts)(output, report2);
6632
+ await (0, import_node3.writeSecurityArtifacts)(output, report2);
6245
6633
  const reportDigest = await (0, import_security.securityFingerprint)(report2);
6246
6634
  await hosted.complete({
6247
6635
  reportDigest,
@@ -6374,8 +6762,8 @@ async function startHostedSecurityJob(options) {
6374
6762
  "start hosted security job"
6375
6763
  );
6376
6764
  } 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)) {
6765
+ const message2 = error instanceof Error ? error.message : String(error);
6766
+ if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message2)) {
6379
6767
  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
6768
  }
6381
6769
  throw error;