@odla-ai/cli 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -456,12 +456,13 @@ async function scopedToken(platform, scope, options, doFetch, out) {
456
456
  const { token, expiresAt } = await (0, import_db2.requestToken)({
457
457
  endpoint: audience,
458
458
  email,
459
- label: options.label ?? `odla CLI admin AI (${scope})`,
459
+ label: options.label ?? `odla CLI (${scope})`,
460
460
  scopes: [scope],
461
461
  fetch: doFetch,
462
462
  onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
463
463
  const approvalUrl = verificationUriComplete ?? handshakeUrl(audience, userCode);
464
- out.log(`Review, then approve scoped request ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
464
+ const requestName = scope === "app:code:host:connect" ? "terminal connection" : "scoped request";
465
+ out.log(`Review, then approve ${requestName} ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
465
466
  if (approvalBrowser({ open: options.open }).open) {
466
467
  await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
467
468
  }
@@ -523,8 +524,8 @@ async function responseBody(response2) {
523
524
  }
524
525
  function apiError(status, body) {
525
526
  const error = isRecord(body) && isRecord(body.error) ? body.error : void 0;
526
- const message3 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
527
- return `read System AI admin changes failed (${status}): ${message3}`;
527
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
528
+ return `read System AI admin changes failed (${status}): ${message2}`;
528
529
  }
529
530
  function timestamp(value) {
530
531
  if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
@@ -625,8 +626,8 @@ async function responseBody2(res) {
625
626
  }
626
627
  function apiError2(action, status, body) {
627
628
  const error = isRecord2(body) && isRecord2(body.error) ? body.error : void 0;
628
- const message3 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
629
- return `${action} failed (${status}): ${message3}`;
629
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
630
+ return `${action} failed (${status}): ${message2}`;
630
631
  }
631
632
  function isRecord2(value) {
632
633
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -806,8 +807,8 @@ async function responseBody3(res) {
806
807
  }
807
808
  function apiError3(action, status, body) {
808
809
  const error = isRecord3(body) && isRecord3(body.error) ? body.error : void 0;
809
- const message3 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
810
- return `${action} failed (${status}): ${message3}`;
810
+ const message2 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
811
+ return `${action} failed (${status}): ${message2}`;
811
812
  }
812
813
  function isRecord3(value) {
813
814
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -1487,8 +1488,8 @@ var CAPABILITIES = {
1487
1488
  "connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"
1488
1489
  ]
1489
1490
  };
1490
- function printCapabilities(json2 = false, out = console) {
1491
- if (json2) {
1491
+ function printCapabilities(json = false, out = console) {
1492
+ if (json) {
1492
1493
  out.log(JSON.stringify(CAPABILITIES, null, 2));
1493
1494
  return;
1494
1495
  }
@@ -1518,8 +1519,8 @@ var CalendarRequestError = class extends Error {
1518
1519
  status;
1519
1520
  /** Machine-readable `error.code` from the response body, when present. */
1520
1521
  code;
1521
- constructor(message3, status, code) {
1522
- super(message3);
1522
+ constructor(message2, status, code) {
1523
+ super(message2);
1523
1524
  this.name = "CalendarRequestError";
1524
1525
  this.status = status;
1525
1526
  if (code !== void 0) this.code = code;
@@ -1686,8 +1687,8 @@ async function calendarJson(ctx, suffix, init) {
1686
1687
  const body = await response2.json().catch(() => ({}));
1687
1688
  if (!response2.ok) {
1688
1689
  const code = textField(body.error?.code, 128);
1689
- const message3 = textField(body.error?.message, 500);
1690
- const detail = `${code ? ` ${code}` : ""}${message3 ? `: ${message3}` : ""}`;
1690
+ const message2 = textField(body.error?.message, 500);
1691
+ const detail = `${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`;
1691
1692
  throw new CalendarRequestError(redactSecrets(`calendar request failed (${response2.status})${detail}`), response2.status, code);
1692
1693
  }
1693
1694
  return body;
@@ -1923,8 +1924,8 @@ function pollTimeout(value = 10 * 6e4) {
1923
1924
  function productionConsent(env, yes, action) {
1924
1925
  if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action} for "${env}" without --yes`);
1925
1926
  }
1926
- function printStatus(status, json2, out) {
1927
- if (json2) {
1927
+ function printStatus(status, json, out) {
1928
+ if (json) {
1928
1929
  out.log(JSON.stringify(status, null, 2));
1929
1930
  return;
1930
1931
  }
@@ -1966,63 +1967,63 @@ function parseAgentOutput(line) {
1966
1967
  } catch {
1967
1968
  throw new HarnessProtocolError("agent emitted invalid JSON");
1968
1969
  }
1969
- const message3 = record2(value);
1970
- if (!message3 || message3.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
1970
+ const message2 = record2(value);
1971
+ if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
1971
1972
  throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
1972
1973
  }
1973
- if (message3.type === "event") {
1974
+ if (message2.type === "event") {
1974
1975
  return {
1975
1976
  protocolVersion: HARNESS_PROTOCOL_VERSION,
1976
1977
  type: "event",
1977
- kind: boundedText(message3.kind, "event.kind", 120),
1978
- ...message3.payload === void 0 ? {} : { payload: message3.payload }
1978
+ kind: boundedText(message2.kind, "event.kind", 120),
1979
+ ...message2.payload === void 0 ? {} : { payload: message2.payload }
1979
1980
  };
1980
1981
  }
1981
- if (message3.type === "inference.request") {
1982
- const call = record2(message3.call);
1982
+ if (message2.type === "inference.request") {
1983
+ const call = record2(message2.call);
1983
1984
  if (!call || !Array.isArray(call.messages) || !Number.isSafeInteger(call.maxTokens)) {
1984
1985
  throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
1985
1986
  }
1986
1987
  return {
1987
1988
  protocolVersion: HARNESS_PROTOCOL_VERSION,
1988
1989
  type: "inference.request",
1989
- requestId: boundedText(message3.requestId, "requestId", 180),
1990
+ requestId: boundedText(message2.requestId, "requestId", 180),
1990
1991
  call
1991
1992
  };
1992
1993
  }
1993
- if (message3.type === "tool.request") {
1994
- const input = record2(message3.input);
1995
- const tool = String(message3.tool);
1994
+ if (message2.type === "tool.request") {
1995
+ const input = record2(message2.input);
1996
+ const tool = String(message2.tool);
1996
1997
  if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
1997
1998
  throw new HarnessProtocolError("tool.request requires a registered tool and object input");
1998
1999
  }
1999
2000
  return {
2000
2001
  protocolVersion: HARNESS_PROTOCOL_VERSION,
2001
2002
  type: "tool.request",
2002
- requestId: boundedText(message3.requestId, "requestId", 180),
2003
+ requestId: boundedText(message2.requestId, "requestId", 180),
2003
2004
  tool,
2004
2005
  input
2005
2006
  };
2006
2007
  }
2007
- if (message3.type === "attempt.complete") {
2008
- if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(message3.status))) {
2008
+ if (message2.type === "attempt.complete") {
2009
+ if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(message2.status))) {
2009
2010
  throw new HarnessProtocolError("attempt.complete.status is invalid");
2010
2011
  }
2011
2012
  return {
2012
2013
  protocolVersion: HARNESS_PROTOCOL_VERSION,
2013
2014
  type: "attempt.complete",
2014
- status: message3.status,
2015
- ...message3.result === void 0 ? {} : { result: message3.result }
2015
+ status: message2.status,
2016
+ ...message2.result === void 0 ? {} : { result: message2.result }
2016
2017
  };
2017
2018
  }
2018
2019
  throw new HarnessProtocolError("agent message type is unsupported");
2019
2020
  }
2020
- function encodeAgentInput(message3) {
2021
- return `${JSON.stringify(message3)}
2021
+ function encodeAgentInput(message2) {
2022
+ return `${JSON.stringify(message2)}
2022
2023
  `;
2023
2024
  }
2024
2025
 
2025
- // ../harness/dist/chunk-FNYUIJUN.js
2026
+ // ../harness/dist/chunk-TA2FKK27.js
2026
2027
  var import_child_process = require("child_process");
2027
2028
  var import_fs = require("fs");
2028
2029
  var import_promises2 = require("fs/promises");
@@ -2032,6 +2033,11 @@ var import_promises3 = require("fs/promises");
2032
2033
  var import_os = require("os");
2033
2034
  var import_path2 = require("path");
2034
2035
  var import_child_process2 = require("child_process");
2036
+ var import_path3 = require("path");
2037
+ var import_promises4 = require("fs/promises");
2038
+ var import_os2 = require("os");
2039
+ var import_path4 = require("path");
2040
+ var import_child_process3 = require("child_process");
2035
2041
  var DIGEST_IMAGE = /^[a-z0-9][a-z0-9._/-]*(?::[a-zA-Z0-9._-]+)?@sha256:[0-9a-f]{64}$/;
2036
2042
  function assertPinnedImage(image) {
2037
2043
  if (!DIGEST_IMAGE.test(image)) throw new TypeError("container image must be pinned by sha256 digest");
@@ -2075,7 +2081,7 @@ async function selectContainerEngine(requested = "auto", options = {}) {
2075
2081
  throw new TypeError("no supported container engine found");
2076
2082
  }
2077
2083
  function inspectRootlessPodman() {
2078
- return new Promise((resolve23, reject) => {
2084
+ return new Promise((resolve33, reject) => {
2079
2085
  (0, import_child_process.execFile)(
2080
2086
  "podman",
2081
2087
  ["info", "--format", "{{.Host.Security.Rootless}}"],
@@ -2085,7 +2091,7 @@ function inspectRootlessPodman() {
2085
2091
  reject(new TypeError("could not verify that the active Podman service is rootless"));
2086
2092
  return;
2087
2093
  }
2088
- resolve23(stdout.trim() === "true");
2094
+ resolve33(stdout.trim() === "true");
2089
2095
  }
2090
2096
  );
2091
2097
  });
@@ -2196,9 +2202,9 @@ async function runContainerAttempt(options) {
2196
2202
  if (bytes.byteLength > 1e6) throw new Error("agent message exceeds 1 MB");
2197
2203
  const line = bytes.toString("utf8");
2198
2204
  if (!line.trim()) return;
2199
- const message3 = parseAgentOutput(line);
2200
- if (message3.type === "attempt.complete") complete = message3;
2201
- const response2 = await options.onMessage(message3);
2205
+ const message2 = parseAgentOutput(line);
2206
+ if (message2.type === "attempt.complete") complete = message2;
2207
+ const response2 = await options.onMessage(message2);
2202
2208
  if (response2 && !child.stdin.destroyed) child.stdin.write(encodeAgentInput(response2));
2203
2209
  };
2204
2210
  try {
@@ -2246,29 +2252,142 @@ async function runContainerAttempt(options) {
2246
2252
  options.signal?.removeEventListener("abort", abort);
2247
2253
  }
2248
2254
  }
2249
- var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
2250
- var SECRET_FILE = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
2255
+ var SKIP_WORKSPACE_DIRS = /* @__PURE__ */ new Set([
2256
+ ".git",
2257
+ ".odla",
2258
+ ".wrangler",
2259
+ "node_modules",
2260
+ "dist",
2261
+ "coverage"
2262
+ ]);
2263
+ var SECRET_WORKSPACE_FILE = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
2264
+ function allowedWorkspacePath(relativePath) {
2265
+ const parts = relativePath.split("/");
2266
+ return !(0, import_path3.isAbsolute)(relativePath) && !relativePath.includes("\\") && !relativePath.includes("\0") && !parts.some((part) => !part || part === "." || part === ".." || SKIP_WORKSPACE_DIRS.has(part)) && !SECRET_WORKSPACE_FILE.test(parts.at(-1) ?? "");
2267
+ }
2268
+ async function gitOutput(cwd, args, maxBytes) {
2269
+ const child = (0, import_child_process2.spawn)("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: false });
2270
+ const stdout = [];
2271
+ const stderr = [];
2272
+ let bytes = 0;
2273
+ child.stdout.on("data", (chunk) => {
2274
+ bytes += chunk.byteLength;
2275
+ if (bytes > maxBytes) child.kill("SIGKILL");
2276
+ else stdout.push(chunk);
2277
+ });
2278
+ child.stderr.on("data", (chunk) => {
2279
+ if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
2280
+ });
2281
+ const code = await new Promise((accept, reject) => {
2282
+ child.once("error", reject);
2283
+ child.once("exit", accept);
2284
+ });
2285
+ if (bytes > maxBytes) throw new Error(`git output exceeds ${maxBytes} bytes`);
2286
+ if (code !== 0) throw new Error(`git command failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
2287
+ return Buffer.concat(stdout);
2288
+ }
2289
+ async function gitBlobs(cwd, entries, maxBytes) {
2290
+ const child = (0, import_child_process2.spawn)("git", ["cat-file", "--batch"], { cwd, stdio: ["pipe", "pipe", "pipe"], shell: false });
2291
+ const stdout = [];
2292
+ const stderr = [];
2293
+ let bytes = 0;
2294
+ child.stdout.on("data", (chunk) => {
2295
+ bytes += chunk.byteLength;
2296
+ if (bytes > maxBytes + entries.length * 100) child.kill("SIGKILL");
2297
+ else stdout.push(chunk);
2298
+ });
2299
+ child.stderr.on("data", (chunk) => {
2300
+ if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
2301
+ });
2302
+ child.stdin.end(`${entries.map((entry) => entry.hash).join("\n")}
2303
+ `);
2304
+ const code = await new Promise((accept, reject) => {
2305
+ child.once("error", reject);
2306
+ child.once("exit", accept);
2307
+ });
2308
+ if (bytes > maxBytes + entries.length * 100) throw new Error(`Git tree exceeds ${maxBytes} bytes`);
2309
+ if (code !== 0) throw new Error(`git object read failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
2310
+ const output = Buffer.concat(stdout);
2311
+ const blobs = [];
2312
+ let offset = 0;
2313
+ let total = 0;
2314
+ for (const entry of entries) {
2315
+ const newline = output.indexOf(10, offset);
2316
+ if (newline < 0) throw new Error("git object output is truncated");
2317
+ const match = /^([0-9a-f]{40,64}) blob ([0-9]+)$/.exec(output.subarray(offset, newline).toString("utf8"));
2318
+ if (!match || match[1] !== entry.hash) throw new Error("git object output does not match its inventory");
2319
+ const length = Number(match[2]);
2320
+ if (!Number.isSafeInteger(length) || length < 0 || newline + 1 + length >= output.length) {
2321
+ throw new Error("git object output has an invalid length");
2322
+ }
2323
+ const content = output.subarray(newline + 1, newline + 1 + length);
2324
+ if (output[newline + 1 + length] !== 10) throw new Error("git object output is malformed");
2325
+ total += content.byteLength;
2326
+ if (total > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2327
+ blobs.push(content);
2328
+ offset = newline + 1 + length + 1;
2329
+ }
2330
+ return blobs;
2331
+ }
2332
+ async function materializeGitTree(source, commitSha, options = {}) {
2333
+ if (!/^[0-9a-f]{40}$/.test(commitSha)) throw new TypeError("Git tree requires an exact commit SHA");
2334
+ const sourceDir = await (0, import_promises3.realpath)((0, import_path2.resolve)(source));
2335
+ const maxFiles = options.maxFiles ?? 2e4;
2336
+ const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
2337
+ const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
2338
+ const entries = inventory.flatMap((record5) => {
2339
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record5);
2340
+ return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
2341
+ });
2342
+ if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
2343
+ const root = await (0, import_promises3.mkdtemp)((0, import_path2.join)(options.tempRoot ?? (0, import_os.tmpdir)(), "odla-git-tree-"));
2344
+ const targetRoot = (0, import_path2.join)(root, "source");
2345
+ await (0, import_promises3.mkdir)(targetRoot);
2346
+ let byteCount = 0;
2347
+ try {
2348
+ const blobs = await gitBlobs(sourceDir, entries, maxBytes);
2349
+ for (const [index, entry] of entries.entries()) {
2350
+ const content = blobs[index];
2351
+ byteCount += content.byteLength;
2352
+ if (byteCount > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2353
+ const target = (0, import_path2.resolve)(targetRoot, entry.path);
2354
+ if (!target.startsWith(`${(0, import_path2.resolve)(targetRoot)}${import_path2.sep}`)) throw new TypeError("Git tree path escapes workspace");
2355
+ await (0, import_promises3.mkdir)((0, import_path2.resolve)(target, ".."), { recursive: true });
2356
+ await (0, import_promises3.writeFile)(target, content, { flag: "wx", mode: entry.mode === "100755" ? 493 : 420 });
2357
+ }
2358
+ return {
2359
+ root,
2360
+ sourceDir: targetRoot,
2361
+ fileCount: entries.length,
2362
+ byteCount,
2363
+ cleanup: () => (0, import_promises3.rm)(root, { recursive: true, force: true })
2364
+ };
2365
+ } catch (error) {
2366
+ await (0, import_promises3.rm)(root, { recursive: true, force: true });
2367
+ throw error;
2368
+ }
2369
+ }
2251
2370
  async function sourceFiles(sourceDir, maxFiles, maxBytes) {
2252
2371
  const files = [];
2253
2372
  let bytes = 0;
2254
2373
  const walk = async (dir) => {
2255
- for (const entry of await (0, import_promises3.readdir)(dir, { withFileTypes: true })) {
2256
- if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue;
2257
- if (!entry.isDirectory() && SECRET_FILE.test(entry.name)) continue;
2258
- const path = (0, import_path2.join)(dir, entry.name);
2374
+ for (const entry of await (0, import_promises4.readdir)(dir, { withFileTypes: true })) {
2375
+ if (entry.isDirectory() && SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
2376
+ if (!entry.isDirectory() && SECRET_WORKSPACE_FILE.test(entry.name)) continue;
2377
+ const path = (0, import_path4.join)(dir, entry.name);
2259
2378
  if (entry.isSymbolicLink()) continue;
2260
2379
  if (entry.isDirectory()) {
2261
2380
  await walk(path);
2262
2381
  continue;
2263
2382
  }
2264
2383
  if (!entry.isFile()) continue;
2265
- const metadata2 = await (0, import_promises3.stat)(path);
2384
+ const metadata2 = await (0, import_promises4.stat)(path);
2266
2385
  bytes += metadata2.size;
2267
2386
  if (files.length + 1 > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
2268
2387
  if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2269
2388
  files.push({
2270
2389
  source: path,
2271
- relativePath: (0, import_path2.relative)(sourceDir, path),
2390
+ relativePath: (0, import_path4.relative)(sourceDir, path),
2272
2391
  mode: metadata2.mode & 511,
2273
2392
  bytes: metadata2.size
2274
2393
  });
@@ -2277,16 +2396,62 @@ async function sourceFiles(sourceDir, maxFiles, maxBytes) {
2277
2396
  await walk(sourceDir);
2278
2397
  return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
2279
2398
  }
2399
+ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
2400
+ const child = (0, import_child_process3.spawn)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
2401
+ cwd: sourceDir,
2402
+ stdio: ["ignore", "pipe", "pipe"],
2403
+ shell: false
2404
+ });
2405
+ const stdout = [];
2406
+ const stderr = [];
2407
+ let outputBytes = 0;
2408
+ child.stdout.on("data", (chunk) => {
2409
+ outputBytes += chunk.byteLength;
2410
+ if (outputBytes > 8 * 1024 * 1024) child.kill("SIGKILL");
2411
+ else stdout.push(chunk);
2412
+ });
2413
+ child.stderr.on("data", (chunk) => {
2414
+ if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
2415
+ });
2416
+ const code = await new Promise((accept, reject) => {
2417
+ child.once("error", reject);
2418
+ child.once("exit", accept);
2419
+ });
2420
+ if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
2421
+ if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
2422
+ const paths = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
2423
+ if (paths.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
2424
+ const root = (0, import_path4.resolve)(sourceDir);
2425
+ const files = [];
2426
+ let bytes = 0;
2427
+ for (const relativePath of paths) {
2428
+ if (!allowedWorkspacePath(relativePath)) continue;
2429
+ const source = (0, import_path4.resolve)(root, relativePath);
2430
+ if (!source.startsWith(`${root}${import_path4.sep}`)) throw new TypeError("git file path escapes workspace");
2431
+ let metadata2;
2432
+ try {
2433
+ metadata2 = await (0, import_promises4.lstat)(source);
2434
+ } catch (error) {
2435
+ if (error.code === "ENOENT") continue;
2436
+ throw error;
2437
+ }
2438
+ if (metadata2.isSymbolicLink() || !metadata2.isFile()) continue;
2439
+ bytes += metadata2.size;
2440
+ if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2441
+ files.push({ source, relativePath, mode: metadata2.mode & 511, bytes: metadata2.size });
2442
+ }
2443
+ return files;
2444
+ }
2280
2445
  async function copyTree(files, destination) {
2281
2446
  for (const file of files) {
2282
- const target = (0, import_path2.join)(destination, file.relativePath);
2283
- await (0, import_promises3.mkdir)((0, import_path2.resolve)(target, ".."), { recursive: true });
2284
- await (0, import_promises3.copyFile)(file.source, target);
2285
- await (0, import_promises3.chmod)(target, file.mode);
2447
+ const target = (0, import_path4.join)(destination, file.relativePath);
2448
+ await (0, import_promises4.mkdir)((0, import_path4.resolve)(target, ".."), { recursive: true });
2449
+ await (0, import_promises4.copyFile)(file.source, target);
2450
+ await (0, import_promises4.chmod)(target, file.mode);
2286
2451
  }
2287
2452
  }
2288
2453
  async function captureGitDiff(root, maxBytes) {
2289
- const child = (0, import_child_process2.spawn)("git", [
2454
+ const child = (0, import_child_process3.spawn)("git", [
2290
2455
  "diff",
2291
2456
  "--no-index",
2292
2457
  "--binary",
@@ -2319,15 +2484,17 @@ async function captureGitDiff(root, maxBytes) {
2319
2484
  return Buffer.concat(stdout).toString("utf8").replaceAll("a/baseline/", "a/").replaceAll("b/workspace/", "b/").replaceAll("--- a/baseline", "--- a").replaceAll("+++ b/workspace", "+++ b");
2320
2485
  }
2321
2486
  async function stageWorkspace(source, options = {}) {
2322
- const sourceDir = await (0, import_promises3.realpath)((0, import_path2.resolve)(source));
2323
- const sourceStat = await (0, import_promises3.stat)(sourceDir);
2487
+ const sourceDir = await (0, import_promises4.realpath)((0, import_path4.resolve)(source));
2488
+ const sourceStat = await (0, import_promises4.stat)(sourceDir);
2324
2489
  if (!sourceStat.isDirectory()) throw new TypeError("workspace source must be a directory");
2325
- const root = await (0, import_promises3.mkdtemp)((0, import_path2.join)(options.tempRoot ?? (0, import_os.tmpdir)(), "odla-harness-"));
2326
- const baselineDir = (0, import_path2.join)(root, "baseline");
2327
- const workspaceDir = (0, import_path2.join)(root, "workspace");
2328
- await Promise.all([(0, import_promises3.mkdir)(baselineDir), (0, import_promises3.mkdir)(workspaceDir)]);
2490
+ const root = await (0, import_promises4.mkdtemp)((0, import_path4.join)(options.tempRoot ?? (0, import_os2.tmpdir)(), "odla-harness-"));
2491
+ const baselineDir = (0, import_path4.join)(root, "baseline");
2492
+ const workspaceDir = (0, import_path4.join)(root, "workspace");
2493
+ await Promise.all([(0, import_promises4.mkdir)(baselineDir), (0, import_promises4.mkdir)(workspaceDir)]);
2329
2494
  try {
2330
- const files = await sourceFiles(sourceDir, options.maxFiles ?? 2e4, options.maxBytes ?? 512 * 1024 * 1024);
2495
+ const maxFiles = options.maxFiles ?? 2e4;
2496
+ const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
2497
+ const files = options.gitTrackedAndUnignored ? await gitSourceFiles(sourceDir, maxFiles, maxBytes) : await sourceFiles(sourceDir, maxFiles, maxBytes);
2331
2498
  await Promise.all([copyTree(files, baselineDir), copyTree(files, workspaceDir)]);
2332
2499
  return {
2333
2500
  root,
@@ -2335,15 +2502,49 @@ async function stageWorkspace(source, options = {}) {
2335
2502
  workspaceDir,
2336
2503
  fileCount: files.length,
2337
2504
  byteCount: files.reduce((sum, file) => sum + file.bytes, 0),
2338
- patch: (maxBytes) => captureGitDiff(root, maxBytes),
2339
- cleanup: () => (0, import_promises3.rm)(root, { recursive: true, force: true })
2505
+ patch: (maxBytes2) => captureGitDiff(root, maxBytes2),
2506
+ cleanup: () => (0, import_promises4.rm)(root, { recursive: true, force: true })
2340
2507
  };
2341
2508
  } catch (error) {
2342
- await (0, import_promises3.rm)(root, { recursive: true, force: true });
2509
+ await (0, import_promises4.rm)(root, { recursive: true, force: true });
2510
+ throw error;
2511
+ }
2512
+ }
2513
+ async function stageWorkspacePair(baselineSource, workspaceSource, options = {}) {
2514
+ const baselineDirSource = await (0, import_promises4.realpath)((0, import_path4.resolve)(baselineSource));
2515
+ const workspaceDirSource = await (0, import_promises4.realpath)((0, import_path4.resolve)(workspaceSource));
2516
+ const maxFiles = options.maxFiles ?? 2e4;
2517
+ const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
2518
+ const [baselineFiles, workspaceFiles] = await Promise.all([
2519
+ sourceFiles(baselineDirSource, maxFiles, maxBytes),
2520
+ sourceFiles(workspaceDirSource, maxFiles, maxBytes)
2521
+ ]);
2522
+ const root = await (0, import_promises4.mkdtemp)((0, import_path4.join)(options.tempRoot ?? (0, import_os2.tmpdir)(), "odla-harness-"));
2523
+ const baselineDir = (0, import_path4.join)(root, "baseline");
2524
+ const workspaceDir = (0, import_path4.join)(root, "workspace");
2525
+ await Promise.all([(0, import_promises4.mkdir)(baselineDir), (0, import_promises4.mkdir)(workspaceDir)]);
2526
+ try {
2527
+ await Promise.all([copyTree(baselineFiles, baselineDir), copyTree(workspaceFiles, workspaceDir)]);
2528
+ return {
2529
+ root,
2530
+ baselineDir,
2531
+ workspaceDir,
2532
+ fileCount: workspaceFiles.length,
2533
+ byteCount: workspaceFiles.reduce((sum, file) => sum + file.bytes, 0),
2534
+ patch: (maxPatchBytes) => captureGitDiff(root, maxPatchBytes),
2535
+ cleanup: () => (0, import_promises4.rm)(root, { recursive: true, force: true })
2536
+ };
2537
+ } catch (error) {
2538
+ await (0, import_promises4.rm)(root, { recursive: true, force: true });
2343
2539
  throw error;
2344
2540
  }
2345
2541
  }
2346
2542
 
2543
+ // ../harness/dist/chunk-QEW6NNAO.js
2544
+ var import_crypto = require("crypto");
2545
+ var import_promises5 = require("fs/promises");
2546
+ var import_path5 = require("path");
2547
+
2347
2548
  // ../camel/dist/chunk-7FHPOQVP.js
2348
2549
  var CamelError = class extends Error {
2349
2550
  code;
@@ -2362,8 +2563,8 @@ function canonicalJson(value) {
2362
2563
  }
2363
2564
  async function sha256Hex(value) {
2364
2565
  const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
2365
- const digest2 = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
2366
- return [...new Uint8Array(digest2)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2566
+ const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
2567
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2367
2568
  }
2368
2569
  function utf8Length(value) {
2369
2570
  if (typeof value === "string") return new TextEncoder().encode(value).byteLength;
@@ -2640,8 +2841,8 @@ function freeze(value) {
2640
2841
  })
2641
2842
  });
2642
2843
  }
2643
- function invalid2(message3) {
2644
- return new CamelError("state_conflict", message3);
2844
+ function invalid2(message2) {
2845
+ return new CamelError("state_conflict", message2);
2645
2846
  }
2646
2847
  var SHA4 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
2647
2848
  var REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
@@ -2676,25 +2877,22 @@ function validateSnapshot(snapshot, limits) {
2676
2877
  }
2677
2878
  }
2678
2879
 
2679
- // ../harness/dist/chunk-RVUUURK2.js
2680
- var import_child_process3 = require("child_process");
2681
- var import_promises4 = require("fs/promises");
2682
- var import_path3 = require("path");
2880
+ // ../harness/dist/chunk-QEW6NNAO.js
2683
2881
  var import_child_process4 = require("child_process");
2882
+ var import_promises6 = require("fs/promises");
2883
+ var import_path6 = require("path");
2884
+ var import_child_process5 = require("child_process");
2684
2885
  var import_process2 = require("process");
2685
- var import_crypto = require("crypto");
2686
2886
  var import_crypto2 = require("crypto");
2687
- var import_fs2 = require("fs");
2688
- var import_promises5 = require("fs/promises");
2689
- var import_path4 = require("path");
2690
2887
  var import_crypto3 = require("crypto");
2691
- var import_promises6 = require("fs/promises");
2692
- var import_path5 = require("path");
2888
+ var import_fs2 = require("fs");
2693
2889
  var import_promises7 = require("fs/promises");
2694
- var import_os2 = require("os");
2695
- var import_path6 = require("path");
2696
- var import_promises8 = require("fs/promises");
2697
2890
  var import_path7 = require("path");
2891
+ var import_promises8 = require("fs/promises");
2892
+ var import_os3 = require("os");
2893
+ var import_path8 = require("path");
2894
+ var import_promises9 = require("fs/promises");
2895
+ var import_path9 = require("path");
2698
2896
 
2699
2897
  // ../camel/dist/chunk-LAXU2AVK.js
2700
2898
  function conversionPolicyDigest(policy) {
@@ -2972,8 +3170,34 @@ function looksLikeDestination(value) {
2972
3170
  return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
2973
3171
  }
2974
3172
 
2975
- // ../harness/dist/chunk-RVUUURK2.js
3173
+ // ../harness/dist/chunk-QEW6NNAO.js
2976
3174
  var import_crypto4 = require("crypto");
3175
+ async function digestStagedWorkspace(root, limits) {
3176
+ const files = [];
3177
+ const walk = async (directory) => {
3178
+ const entries = await (0, import_promises5.readdir)(directory, { withFileTypes: true });
3179
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
3180
+ if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
3181
+ const target = (0, import_path5.resolve)(directory, entry.name);
3182
+ if (entry.isDirectory()) await walk(target);
3183
+ else if (entry.isFile()) {
3184
+ files.push({ path: (0, import_path5.relative)(root, target).split("\\").join("/"), target });
3185
+ if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
3186
+ }
3187
+ }
3188
+ };
3189
+ await walk((0, import_path5.resolve)(root));
3190
+ const hash = (0, import_crypto.createHash)("sha256");
3191
+ let bytes = 0;
3192
+ for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
3193
+ const content = await (0, import_promises5.readFile)(file.target);
3194
+ bytes += Buffer.byteLength(file.path) + content.byteLength;
3195
+ if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
3196
+ hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
3197
+ hash.update(content);
3198
+ }
3199
+ return `sha256:${hash.digest("hex")}`;
3200
+ }
2977
3201
  var CODE_RUNTIME_PROTOCOL_VERSION = 1;
2978
3202
  async function runCodeRuntimeHeartbeatLoop(options) {
2979
3203
  const heartbeatMs = options.heartbeatMs ?? 15e3;
@@ -3045,8 +3269,8 @@ function wait(ms, signal) {
3045
3269
  });
3046
3270
  }
3047
3271
  var CodeRuntimeControlError = class extends Error {
3048
- constructor(message3, status, code = "control_error") {
3049
- super(message3);
3272
+ constructor(message2, status, code = "control_error") {
3273
+ super(message2);
3050
3274
  this.status = status;
3051
3275
  this.code = code;
3052
3276
  }
@@ -3125,9 +3349,9 @@ function createCodeRuntimeControlClient(options) {
3125
3349
  }
3126
3350
  await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
3127
3351
  },
3128
- reportSessionFailure: async (sessionId, message3) => {
3129
- if (!message3.trim() || message3.length > 2e3) throw new TypeError("invalid Code session failure");
3130
- await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message3 });
3352
+ reportSessionFailure: async (sessionId, message2) => {
3353
+ if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
3354
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
3131
3355
  }
3132
3356
  };
3133
3357
  }
@@ -3195,9 +3419,9 @@ async function parseSource(value) {
3195
3419
  return { path: file.path, content: file.content };
3196
3420
  });
3197
3421
  const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
3198
- const digest2 = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3199
- if (digest2 !== snapshot.treeDigest) throw invalid("source digest");
3200
- return { ...source, treeDigest: digest2 };
3422
+ const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3423
+ if (digest !== snapshot.treeDigest) throw invalid("source digest");
3424
+ return { ...source, treeDigest: digest };
3201
3425
  }
3202
3426
  function parseReview(value) {
3203
3427
  const review = record3(record3(value)?.review);
@@ -3256,9 +3480,9 @@ function validateRelativePath(path) {
3256
3480
  }
3257
3481
  function resolveCodePath(workspaceDir, path) {
3258
3482
  validateRelativePath(path);
3259
- const root = (0, import_path3.resolve)(workspaceDir);
3260
- const target = (0, import_path3.resolve)(root, path);
3261
- if (target !== root && !target.startsWith(`${root}${import_path3.sep}`)) throw new TypeError("path escapes the staged workspace");
3483
+ const root = (0, import_path6.resolve)(workspaceDir);
3484
+ const target = (0, import_path6.resolve)(root, path);
3485
+ if (target !== root && !target.startsWith(`${root}${import_path6.sep}`)) throw new TypeError("path escapes the staged workspace");
3262
3486
  return target;
3263
3487
  }
3264
3488
  async function applyCodePatch(workspaceDir, patch2, paths) {
@@ -3266,7 +3490,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
3266
3490
  await gitApply(workspaceDir, patch2, false);
3267
3491
  for (const path of paths) {
3268
3492
  try {
3269
- const info = await (0, import_promises4.lstat)(resolveCodePath(workspaceDir, path));
3493
+ const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
3270
3494
  if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
3271
3495
  throw new TypeError("patch created a non-regular workspace entry");
3272
3496
  }
@@ -3278,7 +3502,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
3278
3502
  function gitApply(cwd, patch2, check) {
3279
3503
  return new Promise((accept, reject) => {
3280
3504
  const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
3281
- const child = (0, import_child_process3.spawn)("git", args, {
3505
+ const child = (0, import_child_process4.spawn)("git", args, {
3282
3506
  cwd,
3283
3507
  shell: false,
3284
3508
  stdio: ["pipe", "ignore", "pipe"],
@@ -3322,7 +3546,7 @@ async function restoreCodeWorkspaceCheckpoint(input) {
3322
3546
  }
3323
3547
  var ARTIFACT_PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
3324
3548
  var PRIVATE_ARTIFACT_PART = /^(?:\.git|\.odla|\.wrangler|\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json)$/i;
3325
- function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0, import_crypto.randomUUID)().slice(0, 12)}`) {
3549
+ function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0, import_crypto2.randomUUID)().slice(0, 12)}`) {
3326
3550
  assertCodeBuildRecipe(recipe2);
3327
3551
  if (/[,\r\n]/.test(workspaceDir)) throw new TypeError("workspace path contains unsupported mount characters");
3328
3552
  const uid = typeof import_process2.getuid === "function" ? (0, import_process2.getuid)() : 1e3;
@@ -3377,7 +3601,7 @@ function createContainerRecipeExecutor(engine) {
3377
3601
  return {
3378
3602
  async run(input) {
3379
3603
  await verifyContainerEngineBoundary(engine);
3380
- const name = `odla-recipe-${(0, import_crypto.randomUUID)().slice(0, 12)}`;
3604
+ const name = `odla-recipe-${(0, import_crypto2.randomUUID)().slice(0, 12)}`;
3381
3605
  const args = buildRecipeContainerArgs(engine, input.workspaceDir, input.recipe, name);
3382
3606
  return execute(engine, args, name, input.recipe, input.signal);
3383
3607
  }
@@ -3401,7 +3625,7 @@ function parseMemory(value) {
3401
3625
  function execute(engine, args, name, recipe2, signal) {
3402
3626
  return new Promise((accept, reject) => {
3403
3627
  const started = Date.now();
3404
- const child = (0, import_child_process4.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
3628
+ const child = (0, import_child_process5.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
3405
3629
  const stdout = [];
3406
3630
  const stderr = [];
3407
3631
  let bytes = 0;
@@ -3414,7 +3638,7 @@ function execute(engine, args, name, recipe2, signal) {
3414
3638
  timedOut = reason === "timeout";
3415
3639
  outputLimitExceeded = reason === "output";
3416
3640
  const remove = engine === "container" ? ["delete", "--force", name] : ["rm", "-f", name];
3417
- const killer = (0, import_child_process4.spawn)(engine, remove, { shell: false, stdio: "ignore" });
3641
+ const killer = (0, import_child_process5.spawn)(engine, remove, { shell: false, stdio: "ignore" });
3418
3642
  killer.unref();
3419
3643
  child.kill("SIGTERM");
3420
3644
  };
@@ -3448,32 +3672,6 @@ function execute(engine, args, name, recipe2, signal) {
3448
3672
  });
3449
3673
  });
3450
3674
  }
3451
- async function digestStagedWorkspace(root, limits) {
3452
- const files = [];
3453
- const walk = async (directory) => {
3454
- const entries = await (0, import_promises6.readdir)(directory, { withFileTypes: true });
3455
- for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
3456
- if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
3457
- const target = (0, import_path5.resolve)(directory, entry.name);
3458
- if (entry.isDirectory()) await walk(target);
3459
- else if (entry.isFile()) {
3460
- files.push({ path: (0, import_path5.relative)(root, target).split("\\").join("/"), target });
3461
- if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
3462
- }
3463
- }
3464
- };
3465
- await walk((0, import_path5.resolve)(root));
3466
- const hash = (0, import_crypto3.createHash)("sha256");
3467
- let bytes = 0;
3468
- for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
3469
- const content = await (0, import_promises6.readFile)(file.target);
3470
- bytes += Buffer.byteLength(file.path) + content.byteLength;
3471
- if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
3472
- hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
3473
- hash.update(content);
3474
- }
3475
- return `sha256:${hash.digest("hex")}`;
3476
- }
3477
3675
  var SHA3 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
3478
3676
  var DIGEST3 = /^sha256:[0-9a-f]{64}$/;
3479
3677
  var ID3 = /^[A-Za-z0-9._:-]{1,160}$/;
@@ -3521,7 +3719,7 @@ async function verifyCodeCandidate(input) {
3521
3719
  }
3522
3720
  const fields = {
3523
3721
  schemaVersion: 1,
3524
- verificationId: input.verificationId ?? `verify-${(0, import_crypto2.randomUUID)()}`,
3722
+ verificationId: input.verificationId ?? `verify-${(0, import_crypto3.randomUUID)()}`,
3525
3723
  trustedBaseCommitSha: input.trustedBaseCommitSha,
3526
3724
  trustedBaseDigest: input.trustedBaseDigest,
3527
3725
  patchDigest,
@@ -3582,8 +3780,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
3582
3780
  const receipts = [];
3583
3781
  for (const artifact of recipe2.expectedArtifacts ?? []) {
3584
3782
  try {
3585
- const path = (0, import_path4.join)(workspaceDir, artifact.path);
3586
- const info = await (0, import_promises5.lstat)(path);
3783
+ const path = (0, import_path7.join)(workspaceDir, artifact.path);
3784
+ const info = await (0, import_promises7.lstat)(path);
3587
3785
  if (!info.isFile() || info.isSymbolicLink()) {
3588
3786
  receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
3589
3787
  } else if (info.size > artifact.maximumBytes) {
@@ -3600,7 +3798,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
3600
3798
  }
3601
3799
  function hashFile(path) {
3602
3800
  return new Promise((accept, reject) => {
3603
- const hash = (0, import_crypto2.createHash)("sha256");
3801
+ const hash = (0, import_crypto3.createHash)("sha256");
3604
3802
  const stream = (0, import_fs2.createReadStream)(path);
3605
3803
  stream.on("data", (chunk) => {
3606
3804
  hash.update(chunk);
@@ -3658,7 +3856,7 @@ function digestJson(value) {
3658
3856
  return digestBytes(JSON.stringify(value));
3659
3857
  }
3660
3858
  function digestBytes(value) {
3661
- return `sha256:${(0, import_crypto2.createHash)("sha256").update(value).digest("hex")}`;
3859
+ return `sha256:${(0, import_crypto3.createHash)("sha256").update(value).digest("hex")}`;
3662
3860
  }
3663
3861
  function integer(value, minimum, maximum) {
3664
3862
  return Number.isSafeInteger(value) && value >= minimum && value <= maximum;
@@ -3679,8 +3877,8 @@ async function prepareRuntimeCheckpoint(input) {
3679
3877
  policy: {
3680
3878
  policyId: "code.runtime",
3681
3879
  recipes: input.recipes,
3682
- maximumFiles: 1e4,
3683
- maximumBytes: 16 * 1024 * 1024
3880
+ maximumFiles: 2e4,
3881
+ maximumBytes: 512 * 1024 * 1024
3684
3882
  },
3685
3883
  recipeExecutor: input.recipeExecutor
3686
3884
  });
@@ -3762,11 +3960,11 @@ var CodeRuntimeCheckpointManager = class {
3762
3960
  };
3763
3961
  var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
3764
3962
  var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
3765
- async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os2.tmpdir)()) {
3963
+ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.tmpdir)()) {
3766
3964
  if (!snapshot.files.length || snapshot.files.length > 1e4) throw new TypeError("Code source file count is invalid");
3767
- const root = await (0, import_promises7.mkdtemp)((0, import_path6.join)(tempRoot, "odla-code-source-"));
3768
- const sourceDir = (0, import_path6.join)(root, "source");
3769
- await (0, import_promises7.mkdir)(sourceDir);
3965
+ const root = await (0, import_promises8.mkdtemp)((0, import_path8.join)(tempRoot, "odla-code-source-"));
3966
+ const sourceDir = (0, import_path8.join)(root, "source");
3967
+ await (0, import_promises8.mkdir)(sourceDir);
3770
3968
  const seen = /* @__PURE__ */ new Set();
3771
3969
  let bytes = 0;
3772
3970
  try {
@@ -3776,14 +3974,14 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os2.
3776
3974
  seen.add(file.path);
3777
3975
  bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
3778
3976
  if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
3779
- const target = (0, import_path6.resolve)(sourceDir, file.path);
3780
- if (!target.startsWith(`${(0, import_path6.resolve)(sourceDir)}${import_path6.sep}`)) throw new TypeError("Code source path escapes its root");
3781
- await (0, import_promises7.mkdir)((0, import_path6.dirname)(target), { recursive: true });
3782
- await (0, import_promises7.writeFile)(target, file.content, { flag: "wx", mode: 420 });
3977
+ const target = (0, import_path8.resolve)(sourceDir, file.path);
3978
+ if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code source path escapes its root");
3979
+ await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
3980
+ await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 420 });
3783
3981
  }
3784
- return { sourceDir, cleanup: () => (0, import_promises7.rm)(root, { recursive: true, force: true }) };
3982
+ return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
3785
3983
  } catch (cause) {
3786
- await (0, import_promises7.rm)(root, { recursive: true, force: true });
3984
+ await (0, import_promises8.rm)(root, { recursive: true, force: true });
3787
3985
  throw cause;
3788
3986
  }
3789
3987
  }
@@ -3889,15 +4087,15 @@ async function environment(input, options, tool) {
3889
4087
  { id: "authority", value: `lease:${input.lease.leaseId}`, readers: input.readers },
3890
4088
  { id: "reader", value: options.readerId, readers: input.readers }
3891
4089
  ]);
3892
- const digest2 = await destinationRegistryDigest([input.workspaceId]);
4090
+ const digest = await destinationRegistryDigest([input.workspaceId]);
3893
4091
  const approvals = ["irreversible_mutation", "external_send", "financial", "secret_read"];
3894
4092
  if (options.recipeAuthorization === "exact_approval") approvals.push("code_execution");
3895
4093
  const policy = createEffectPolicy({
3896
- destinationRegistries: { [DESTINATIONS]: { digest: digest2, values: [input.workspaceId] } },
4094
+ destinationRegistries: { [DESTINATIONS]: { digest, values: [input.workspaceId] } },
3897
4095
  approvalEffects: approvals
3898
4096
  });
3899
4097
  const fixedArgs = {
3900
- workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest2 },
4098
+ workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest },
3901
4099
  authority: { role: "authority", value: ingress.control("authority") }
3902
4100
  };
3903
4101
  return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
@@ -3968,11 +4166,11 @@ async function read(context, request, options, policy) {
3968
4166
  const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
3969
4167
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
3970
4168
  const target = resolveCodePath(context.workspaceDir, path);
3971
- const info = await (0, import_promises8.stat)(target);
4169
+ const info = await (0, import_promises9.stat)(target);
3972
4170
  if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
3973
4171
  throw new TypeError("file is not a bounded regular source file");
3974
4172
  }
3975
- const source = await (0, import_promises8.readFile)(target);
4173
+ const source = await (0, import_promises9.readFile)(target);
3976
4174
  if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
3977
4175
  const lines = source.toString("utf8").split("\n");
3978
4176
  const content = lines.slice(startLine - 1, endLine).join("\n");
@@ -4046,12 +4244,12 @@ function policyContext(context, request, options, extra) {
4046
4244
  async function registeredFiles(root, limit) {
4047
4245
  const paths = [];
4048
4246
  const walk = async (directory) => {
4049
- for (const entry of await (0, import_promises8.readdir)(directory, { withFileTypes: true })) {
4247
+ for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
4050
4248
  if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
4051
- const target = (0, import_path7.resolve)(directory, entry.name);
4249
+ const target = (0, import_path9.resolve)(directory, entry.name);
4052
4250
  if (entry.isDirectory()) await walk(target);
4053
4251
  else if (entry.isFile()) {
4054
- const path = (0, import_path7.relative)(root, target).split("\\").join("/");
4252
+ const path = (0, import_path9.relative)(root, target).split("\\").join("/");
4055
4253
  try {
4056
4254
  validateRelativePath(path);
4057
4255
  } catch {
@@ -4062,7 +4260,7 @@ async function registeredFiles(root, limit) {
4062
4260
  }
4063
4261
  }
4064
4262
  };
4065
- await walk((0, import_path7.resolve)(root));
4263
+ await walk((0, import_path9.resolve)(root));
4066
4264
  return paths.sort();
4067
4265
  }
4068
4266
  function validateOptions(options) {
@@ -4097,14 +4295,31 @@ function codeCommandMetadata(payload, resume) {
4097
4295
  }
4098
4296
  const planning = trusted?.planningInputDigest;
4099
4297
  const attestation = trusted?.attestationDigest;
4298
+ const repository = trusted?.repository;
4299
+ const baseCommitSha = trusted?.commitSha;
4300
+ const sourceTreeDigest = trusted?.treeDigest;
4301
+ 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)) {
4302
+ throw new TypeError(`invalid Code ${resume ? "resume" : "start"} trusted base`);
4303
+ }
4100
4304
  return {
4101
4305
  role,
4102
4306
  title,
4103
4307
  prompt,
4104
4308
  planningInputDigest: typeof planning === "string" && /^sha256:[0-9a-f]{64}$/.test(planning) ? planning : null,
4105
- attestationDigest: typeof attestation === "string" ? attestation : "resume"
4309
+ attestationDigest: typeof attestation === "string" ? attestation : "resume",
4310
+ repository,
4311
+ baseCommitSha,
4312
+ sourceTreeDigest
4106
4313
  };
4107
4314
  }
4315
+ function codeLocalSource(payload) {
4316
+ const source = record22(payload.source);
4317
+ if (!source) return null;
4318
+ 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) {
4319
+ throw new TypeError("invalid local checkout source descriptor");
4320
+ }
4321
+ return source;
4322
+ }
4108
4323
  function codeCheckpointPayload(payload) {
4109
4324
  const value = payload.checkpoint;
4110
4325
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
@@ -4133,11 +4348,62 @@ function fakeCodeLease(command, metadata2) {
4133
4348
  };
4134
4349
  }
4135
4350
  var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4351
+ var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
4352
+ async function prepareRuntimeLocalSource(input) {
4353
+ const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
4354
+ if (!available || JSON.stringify(available.descriptor) !== JSON.stringify(descriptor2) || descriptor2.repository.toLowerCase() !== repository.toLowerCase() || descriptor2.headCommitSha !== baseCommitSha) {
4355
+ throw new TypeError("the session's local checkout snapshot is not available on this terminal");
4356
+ }
4357
+ const workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4358
+ trustedBaseDir: available.trustedBaseDir,
4359
+ trustedBaseCommitSha: baseCommitSha,
4360
+ checkpoint: codeCheckpointPayload(command.payload)
4361
+ })).workspace : await stageWorkspacePair(available.trustedBaseDir, available.sourceDir, SOURCE_LIMITS);
4362
+ const trustedBaseDigest = await digestStagedWorkspace(workspace.baselineDir, SOURCE_LIMITS);
4363
+ if (trustedBaseDigest !== descriptor2.trustedBaseDigest) {
4364
+ await workspace.cleanup();
4365
+ throw new TypeError("trusted Git base digest changed after connection");
4366
+ }
4367
+ if (!resume && await digestStagedWorkspace(workspace.workspaceDir, SOURCE_LIMITS) !== descriptor2.snapshotDigest) {
4368
+ await workspace.cleanup();
4369
+ throw new TypeError("local checkout snapshot digest changed after connection");
4370
+ }
4371
+ return { workspace, sourceDigest: descriptor2.snapshotDigest, trustedBaseDigest };
4372
+ }
4373
+ function createCodeRuntimeToolBroker(input, lease, role) {
4374
+ const broker = createCodeToolBroker({
4375
+ recipes: input.recipes,
4376
+ recipeExecutor: createContainerRecipeExecutor(input.engine),
4377
+ recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
4378
+ readerId: `code-session:${lease.task.taskId}`
4379
+ });
4380
+ 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" }) };
4381
+ }
4382
+ async function appendCodeRuntimeEvent(control, command, event, refs) {
4383
+ const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
4384
+ refs.push(eventId);
4385
+ const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
4386
+ await control.appendSessionEvent(command.sessionId, eventId, bounded);
4387
+ }
4388
+ var digestRuntimeValue = (value) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value).digest("hex")}`;
4389
+ var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
4390
+ var runtimeRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4391
+ var safeRuntimeJson = (value) => {
4392
+ try {
4393
+ return JSON.stringify(value).slice(0, 1e4);
4394
+ } catch {
4395
+ return "[event]";
4396
+ }
4397
+ };
4398
+ function runtimeResultText(value) {
4399
+ const record32 = runtimeRecord(value);
4400
+ return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4401
+ }
4136
4402
  var CodePiRuntimeEngine = class {
4137
4403
  constructor(options) {
4138
4404
  this.options = options;
4139
4405
  this.#run = options.runAttempt ?? runContainerAttempt;
4140
- this.#buildPolicyDigest = digest(JSON.stringify(options.recipes));
4406
+ this.#buildPolicyDigest = digestRuntimeValue(JSON.stringify(options.recipes));
4141
4407
  this.#checkpoints = new CodeRuntimeCheckpointManager({
4142
4408
  control: options.control,
4143
4409
  recipes: options.recipes,
@@ -4172,19 +4438,35 @@ var CodePiRuntimeEngine = class {
4172
4438
  }
4173
4439
  async #start(command, resume) {
4174
4440
  if (this.#active.has(command.sessionId)) throw new TypeError("Code session is already active on this runtime");
4175
- const source = await this.options.control.source(command.sessionId);
4176
- const materialized = await materializeCodeRuntimeSource(source);
4441
+ const metadata2 = codeCommandMetadata(command.payload, resume);
4442
+ const requestedLocal = codeLocalSource(command.payload);
4177
4443
  let workspace;
4178
- try {
4179
- workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4180
- trustedBaseDir: materialized.sourceDir,
4181
- trustedBaseCommitSha: source.commitSha,
4182
- checkpoint: codeCheckpointPayload(command.payload)
4183
- })).workspace : await stageWorkspace(materialized.sourceDir);
4184
- } finally {
4185
- await materialized.cleanup();
4444
+ let sourceDigest;
4445
+ let localTrustedBaseDigest;
4446
+ if (requestedLocal) {
4447
+ const prepared = await prepareRuntimeLocalSource({
4448
+ command,
4449
+ descriptor: requestedLocal,
4450
+ available: this.options.localSource,
4451
+ repository: metadata2.repository,
4452
+ baseCommitSha: metadata2.baseCommitSha,
4453
+ resume
4454
+ });
4455
+ ({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
4456
+ } else {
4457
+ const source = await this.options.control.source(command.sessionId);
4458
+ const materialized = await materializeCodeRuntimeSource(source);
4459
+ try {
4460
+ workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4461
+ trustedBaseDir: materialized.sourceDir,
4462
+ trustedBaseCommitSha: source.commitSha,
4463
+ checkpoint: codeCheckpointPayload(command.payload)
4464
+ })).workspace : await stageWorkspace(materialized.sourceDir);
4465
+ } finally {
4466
+ await materialized.cleanup();
4467
+ }
4468
+ sourceDigest = source.treeDigest;
4186
4469
  }
4187
- const metadata2 = codeCommandMetadata(command.payload, resume);
4188
4470
  const abort = new AbortController();
4189
4471
  const conversationRefs = [];
4190
4472
  const active = {
@@ -4194,21 +4476,30 @@ var CodePiRuntimeEngine = class {
4194
4476
  acknowledged: false,
4195
4477
  role: metadata2.role,
4196
4478
  title: metadata2.title,
4197
- baseCommitSha: source.commitSha,
4198
- trustedBaseDigest: await digestStagedWorkspace(workspace.baselineDir, {
4479
+ baseCommitSha: metadata2.baseCommitSha,
4480
+ repository: metadata2.repository,
4481
+ sourceTreeDigest: metadata2.sourceTreeDigest,
4482
+ trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
4199
4483
  maxFiles: 1e4,
4200
4484
  maxBytes: 16 * 1024 * 1024
4201
4485
  }),
4202
- planningInputDigest: metadata2.planningInputDigest ?? digest(
4203
- JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: source.treeDigest })
4486
+ planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
4487
+ JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })
4204
4488
  ),
4205
4489
  done: Promise.resolve(null)
4206
4490
  };
4207
4491
  this.#active.set(command.sessionId, active);
4492
+ if (requestedLocal) {
4493
+ await this.#event(command, {
4494
+ type: "message",
4495
+ actor: "system",
4496
+ body: `Source snapshot: local checkout ${requestedLocal.snapshotDigest} \xB7 ${requestedLocal.modified ? "modified" : "clean"} \xB7 Git ${requestedLocal.headCommitSha}`
4497
+ }, conversationRefs);
4498
+ }
4208
4499
  active.done = this.#runAttempt(command, metadata2, active).catch(async (cause) => {
4209
- await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` }, conversationRefs).catch(() => void 0);
4500
+ await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` }, conversationRefs).catch(() => void 0);
4210
4501
  await this.#event(command, { type: "status", status: "failed" }, conversationRefs).catch(() => void 0);
4211
- await this.#failure(command, active, message2(cause));
4502
+ await this.#failure(command, active, runtimeErrorMessage(cause));
4212
4503
  return null;
4213
4504
  });
4214
4505
  return { status: "running", message: resume ? "Pi resumed from a portable checkpoint" : "Pi started" };
@@ -4228,22 +4519,29 @@ var CodePiRuntimeEngine = class {
4228
4519
  title: active.title,
4229
4520
  prompt,
4230
4521
  planningInputDigest: active.planningInputDigest,
4231
- attestationDigest: "follow-up"
4522
+ attestationDigest: "follow-up",
4523
+ repository: active.repository,
4524
+ baseCommitSha: active.baseCommitSha,
4525
+ sourceTreeDigest: active.sourceTreeDigest
4232
4526
  }, active).catch(async (cause) => {
4233
4527
  await this.#event(
4234
4528
  command,
4235
- { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` },
4529
+ { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` },
4236
4530
  active.conversationRefs
4237
4531
  ).catch(() => void 0);
4238
4532
  await this.#event(command, { type: "status", status: "failed" }, active.conversationRefs).catch(() => void 0);
4239
- await this.#failure(command, active, message2(cause));
4533
+ await this.#failure(command, active, runtimeErrorMessage(cause));
4240
4534
  return null;
4241
4535
  });
4242
4536
  return { status: "running", message: "Pi accepted the owner prompt" };
4243
4537
  }
4244
4538
  async #runAttempt(command, metadata2, active) {
4245
4539
  const lease = fakeCodeLease(command, metadata2);
4246
- const broker = this.#toolBroker(lease, metadata2.role);
4540
+ const broker = createCodeRuntimeToolBroker({
4541
+ recipes: this.options.recipes,
4542
+ engine: this.options.engine,
4543
+ recipeAuthorization: this.options.recipeAuthorization
4544
+ }, lease, metadata2.role);
4247
4545
  const startedAt = Date.now();
4248
4546
  let completionSeen = false;
4249
4547
  const result = await this.#run({
@@ -4303,7 +4601,7 @@ var CodePiRuntimeEngine = class {
4303
4601
  return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
4304
4602
  }
4305
4603
  if (output.type === "event") {
4306
- const payload = object2(output.payload);
4604
+ const payload = runtimeRecord(output.payload);
4307
4605
  if (output.kind === "pi.started") {
4308
4606
  await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
4309
4607
  } else if (output.kind === "pi.thinking" && payload?.available === true && Number.isSafeInteger(payload.durationMs) && Number(payload.durationMs) >= 0) {
@@ -4316,12 +4614,12 @@ var CodePiRuntimeEngine = class {
4316
4614
  await this.#event(command, {
4317
4615
  type: "message",
4318
4616
  actor: "system",
4319
- body: `${output.kind}${output.payload === void 0 ? "" : ` ${json(output.payload)}`}`
4617
+ body: `${output.kind}${output.payload === void 0 ? "" : ` ${safeRuntimeJson(output.payload)}`}`
4320
4618
  }, active.conversationRefs);
4321
4619
  }
4322
4620
  } else if (output.type === "attempt.complete") {
4323
4621
  completionSeen = true;
4324
- const body = resultText(output.result) ?? `Pi ${output.status}.`;
4622
+ const body = runtimeResultText(output.result) ?? `Pi ${output.status}.`;
4325
4623
  await this.#event(command, {
4326
4624
  type: "message",
4327
4625
  actor: output.status === "completed" ? "agent" : "system",
@@ -4348,15 +4646,6 @@ var CodePiRuntimeEngine = class {
4348
4646
  }
4349
4647
  return result;
4350
4648
  }
4351
- #toolBroker(lease, role) {
4352
- const broker = createCodeToolBroker({
4353
- recipes: this.options.recipes,
4354
- recipeExecutor: createContainerRecipeExecutor(this.options.engine),
4355
- recipeAuthorization: this.options.recipeAuthorization ?? "registered_recipe",
4356
- readerId: `code-session:${lease.task.taskId}`
4357
- });
4358
- return role === "coding" ? broker : { execute: (context, request) => request.tool === "sandbox.read" ? broker.execute(context, request) : Promise.resolve({ requestId: request.requestId, ok: false, content: "review sessions are read-only" }) };
4359
- }
4360
4649
  async #checkpoint(command) {
4361
4650
  const active = this.#active.get(command.sessionId);
4362
4651
  if (!active) throw new TypeError("Code session workspace is not active on this runtime");
@@ -4371,26 +4660,9 @@ var CodePiRuntimeEngine = class {
4371
4660
  }
4372
4661
  }
4373
4662
  async #event(command, event, refs) {
4374
- const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
4375
- refs.push(eventId);
4376
- const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
4377
- await this.options.control.appendSessionEvent(command.sessionId, eventId, bounded);
4663
+ await appendCodeRuntimeEvent(this.options.control, command, event, refs);
4378
4664
  }
4379
4665
  };
4380
- var object2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4381
- var digest = (value) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value).digest("hex")}`;
4382
- var message2 = (value) => value instanceof Error ? value.message : String(value);
4383
- var json = (value) => {
4384
- try {
4385
- return JSON.stringify(value).slice(0, 1e4);
4386
- } catch {
4387
- return "[event]";
4388
- }
4389
- };
4390
- function resultText(value) {
4391
- const record32 = object2(value);
4392
- return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4393
- }
4394
4666
 
4395
4667
  // src/help.ts
4396
4668
  var import_node_fs6 = require("fs");
@@ -4532,8 +4804,8 @@ async function requestHostedSecurityJson(options, path, init, action) {
4532
4804
  const body = await response2.json().catch(() => ({}));
4533
4805
  if (!response2.ok) {
4534
4806
  const code = optionalHostedText(body.error?.code, "error code", 128);
4535
- const message3 = optionalHostedText(body.error?.message, "error message", 300);
4536
- throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message3 ? `: ${message3}` : ""}`);
4807
+ const message2 = optionalHostedText(body.error?.message, "error message", 300);
4808
+ throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
4537
4809
  }
4538
4810
  return body;
4539
4811
  }
@@ -4618,7 +4890,7 @@ function hostedSecurityCredential(value) {
4618
4890
  // src/security-hosted-github.ts
4619
4891
  async function connectGitHubSecuritySource(options) {
4620
4892
  const out = options.stdout ?? console;
4621
- const repository = githubRepositoryName(options.repository);
4893
+ const repository = options.repository === void 0 ? void 0 : githubRepositoryName(options.repository);
4622
4894
  const attempt = await requestHostedSecurityJson(
4623
4895
  options,
4624
4896
  "/registry/github/connect",
@@ -4627,7 +4899,7 @@ async function connectGitHubSecuritySource(options) {
4627
4899
  body: JSON.stringify({
4628
4900
  appId: hostedIdentifier(options.appId, "appId"),
4629
4901
  env: hostedIdentifier(options.env, "env"),
4630
- repository
4902
+ ...repository === void 0 ? {} : { repository }
4631
4903
  })
4632
4904
  },
4633
4905
  "start GitHub connection"
@@ -4722,24 +4994,111 @@ async function defaultReadOrigin(cwd) {
4722
4994
  return result.stdout;
4723
4995
  }
4724
4996
 
4725
- // src/code-connect.ts
4726
- var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:713db58428bc05cb486167c5747de8fd826669c9b283ff78e0139ac194c3d058";
4997
+ // src/code-local-source.ts
4998
+ var import_node_child_process3 = require("child_process");
4999
+ var import_node_crypto = require("crypto");
5000
+ var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
5001
+ async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
5002
+ const headCommitSha = await readHead(cwd);
5003
+ const staged = await stageWorkspace(cwd, { ...SOURCE_LIMITS2, gitTrackedAndUnignored: true });
5004
+ let trusted;
5005
+ try {
5006
+ trusted = await materializeGitTree(cwd, headCommitSha, SOURCE_LIMITS2);
5007
+ const comparison = await stageWorkspacePair(trusted.sourceDir, staged.baselineDir, SOURCE_LIMITS2);
5008
+ let developerPatch;
5009
+ try {
5010
+ developerPatch = await comparison.patch(4 * 1024 * 1024);
5011
+ } finally {
5012
+ await comparison.cleanup();
5013
+ }
5014
+ const descriptor2 = {
5015
+ kind: "local_checkout",
5016
+ repository,
5017
+ headCommitSha,
5018
+ trustedBaseDigest: await digestStagedWorkspace(trusted.sourceDir, SOURCE_LIMITS2),
5019
+ developerPatchDigest: digestText(developerPatch),
5020
+ snapshotDigest: await digestStagedWorkspace(staged.baselineDir, SOURCE_LIMITS2),
5021
+ modified: developerPatch.length > 0,
5022
+ fileCount: staged.fileCount,
5023
+ byteCount: staged.byteCount,
5024
+ capturedAt: Date.now()
5025
+ };
5026
+ return {
5027
+ descriptor: descriptor2,
5028
+ trustedBaseDir: trusted.sourceDir,
5029
+ sourceDir: staged.baselineDir,
5030
+ cleanup: async () => {
5031
+ await Promise.all([staged.cleanup(), trusted.cleanup()]);
5032
+ }
5033
+ };
5034
+ } catch (error) {
5035
+ await Promise.all([staged.cleanup(), trusted?.cleanup()]);
5036
+ throw error;
5037
+ }
5038
+ }
5039
+ async function readGitHead(cwd) {
5040
+ const value = await new Promise((accept, reject) => {
5041
+ (0, import_node_child_process3.execFile)("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
5042
+ if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
5043
+ else accept(stdout.trim());
5044
+ });
5045
+ });
5046
+ if (!/^[0-9a-f]{40}$/.test(value)) throw new Error("code connect could not resolve the checkout HEAD commit");
5047
+ return value;
5048
+ }
5049
+ function digestText(value) {
5050
+ return `sha256:${(0, import_node_crypto.createHash)("sha256").update(value).digest("hex")}`;
5051
+ }
5052
+
5053
+ // src/code-images.ts
5054
+ var import_node_child_process4 = require("child_process");
5055
+ async function prepareCodeImages(engine, images) {
5056
+ for (const image of images) {
5057
+ await new Promise((accept, reject) => {
5058
+ const args = engine === "container" ? ["image", "pull", image] : ["pull", image];
5059
+ (0, import_node_child_process4.execFile)(engine, args, { encoding: "utf8", maxBuffer: 2 * 1024 * 1024, timeout: 10 * 6e4 }, (error) => {
5060
+ if (error) reject(new Error(`could not prepare pinned Code image ${image}`));
5061
+ else accept();
5062
+ });
5063
+ });
5064
+ }
5065
+ }
5066
+
5067
+ // src/code-runtime-config.ts
5068
+ var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:e7858b4f0291543171b2659b47c5d00f97acc4767cafd9b74ae5228899e0cde4";
4727
5069
  var CODE_BUILD_RECIPES = Object.freeze([{
4728
- id: "node-test",
5070
+ id: "odla-code-contracts",
4729
5071
  image: "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd",
4730
- command: ["node", "--test"],
5072
+ command: [
5073
+ "node",
5074
+ "--test",
5075
+ "scripts/lib/directories.test.mjs",
5076
+ "scripts/lib/internal-deps.test.mjs",
5077
+ "scripts/lib/npm-audit.test.mjs",
5078
+ "scripts/lib/npm-release-contract.test.mjs",
5079
+ "scripts/lib/release-surfaces.test.mjs"
5080
+ ],
4731
5081
  timeoutMs: 12e4,
4732
5082
  maxOutputBytes: 1024 * 1024,
4733
5083
  cpus: 1,
4734
5084
  memory: "512m",
4735
5085
  pids: 128
4736
5086
  }]);
5087
+
5088
+ // src/code-connect.ts
4737
5089
  async function codeConnect(options) {
4738
5090
  const cwd = options.cwd ?? process.cwd();
4739
5091
  const configPath = (0, import_node_path4.resolve)(cwd, options.configPath);
4740
5092
  const cfg = (0, import_node_fs7.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
4741
- const repository = cfg ? null : await inferGitHubRepository(cwd, options.readGitOrigin);
4742
- const platform = cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai";
5093
+ const requestedAppId = options.appId?.trim();
5094
+ if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
5095
+ throw new Error("--app-id must be a valid odla app id");
5096
+ }
5097
+ if (requestedAppId && cfg && requestedAppId !== cfg.app.id) {
5098
+ throw new Error(`--app-id ${requestedAppId} does not match ${options.configPath} (${cfg.app.id})`);
5099
+ }
5100
+ const appId = requestedAppId ?? cfg?.app.id;
5101
+ const platform = (options.platform ?? cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai").replace(/\/+$/, "");
4743
5102
  const appEnv = options.env ?? (cfg ? cfg.envs.includes("dev") ? "dev" : cfg.envs[0] : "dev");
4744
5103
  if (appEnv !== "dev" && appEnv !== "prod" || cfg && !cfg.envs.includes(appEnv)) {
4745
5104
  throw new Error(cfg ? `Code env "${appEnv ?? ""}" must be dev or prod and declared in ${options.configPath}` : `Code env "${appEnv ?? ""}" must be dev or prod`);
@@ -4758,53 +5117,77 @@ async function codeConnect(options) {
4758
5117
  const out = options.stdout ?? console;
4759
5118
  const doFetch = options.fetch ?? fetch;
4760
5119
  const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
5120
+ await (options.prepareImages ?? prepareCodeImages)(engine, [
5121
+ CODE_PI_IMAGE,
5122
+ ...new Set(CODE_BUILD_RECIPES.map((recipe2) => recipe2.image))
5123
+ ]);
4761
5124
  const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
4762
5125
  const hostName = (options.name ?? (0, import_node_os.hostname)()).trim();
4763
5126
  if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
4764
- const approval = await (options.getToken ?? getScopedPlatformToken)({
4765
- platform,
4766
- scope: "platform:code:host:connect",
4767
- email: options.email,
4768
- open: options.open,
4769
- fetch: doFetch,
4770
- stdout: out,
4771
- openApprovalUrl: options.openApprovalUrl,
4772
- cache: false,
4773
- label: `odla CLI Code host for ${cfg?.app.id ?? repository}/${appEnv}`
4774
- });
4775
- const target = cfg ? { appId: cfg.app.id } : { repository };
4776
- const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
4777
- method: "POST",
4778
- headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
4779
- body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
4780
- redirect: "error",
4781
- signal: options.signal
4782
- });
4783
- const raw = await response2.json().catch(() => null);
4784
- if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
4785
- const connection = parseConnection(raw, cfg?.app.id, appEnv);
4786
- const capabilities = {
4787
- protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
4788
- platform: hostPlatform,
4789
- arch: process.arch,
4790
- engines: [engine],
4791
- cpuCount: (0, import_node_os.cpus)().length,
4792
- memoryBytes: (0, import_node_os.totalmem)()
4793
- };
4794
- out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
4795
- out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
4796
- out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
4797
- await (options.runRuntime ?? runCodeRuntime)({
4798
- endpoint: platform,
4799
- token: connection.token,
4800
- engine,
4801
- capabilities,
4802
- heartbeatMs,
4803
- once: options.once === true,
4804
- signal: options.signal,
4805
- stdout: out,
4806
- fetch: doFetch
4807
- });
5127
+ const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
5128
+ const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
5129
+ cwd,
5130
+ repository,
5131
+ options.readGitHead
5132
+ );
5133
+ try {
5134
+ const descriptor2 = localSource.descriptor;
5135
+ const approval = await (options.getToken ?? getScopedPlatformToken)({
5136
+ platform,
5137
+ scope: "app:code:host:connect",
5138
+ email: options.email,
5139
+ open: options.open,
5140
+ fetch: doFetch,
5141
+ stdout: out,
5142
+ openApprovalUrl: options.openApprovalUrl,
5143
+ cache: false,
5144
+ label: `Terminal connection for ${appId ?? repository}/${appEnv}`
5145
+ });
5146
+ const target = appId ? { appId } : { repository };
5147
+ const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
5148
+ method: "POST",
5149
+ headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
5150
+ body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
5151
+ redirect: "error",
5152
+ signal: options.signal
5153
+ });
5154
+ const raw = await response2.json().catch(() => null);
5155
+ if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
5156
+ const connection = parseConnection(raw, appId, appEnv);
5157
+ const capabilities = {
5158
+ protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
5159
+ platform: hostPlatform,
5160
+ arch: process.arch,
5161
+ engines: [engine],
5162
+ cpuCount: (0, import_node_os.cpus)().length,
5163
+ memoryBytes: (0, import_node_os.totalmem)(),
5164
+ source: descriptor2,
5165
+ images: {
5166
+ ready: true,
5167
+ pi: CODE_PI_IMAGE,
5168
+ recipes: CODE_BUILD_RECIPES.map((recipe2) => ({ id: recipe2.id, image: recipe2.image }))
5169
+ }
5170
+ };
5171
+ out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
5172
+ out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
5173
+ out.log(`source: local checkout \xB7 ${descriptor2.headCommitSha.slice(0, 10)} \xB7 ${descriptor2.fileCount} files \xB7 staged copy`);
5174
+ out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
5175
+ out.log(`Studio: ${platform}/studio/apps/${encodeURIComponent(connection.binding.appId)}/${connection.binding.env}/code/sessions`);
5176
+ await (options.runRuntime ?? runCodeRuntime)({
5177
+ endpoint: platform,
5178
+ token: connection.token,
5179
+ engine,
5180
+ capabilities,
5181
+ localSource,
5182
+ heartbeatMs,
5183
+ once: options.once === true,
5184
+ signal: options.signal,
5185
+ stdout: out,
5186
+ fetch: doFetch
5187
+ });
5188
+ } finally {
5189
+ await localSource.cleanup();
5190
+ }
4808
5191
  }
4809
5192
  async function runCodeRuntime(input) {
4810
5193
  const controller = new AbortController();
@@ -4827,7 +5210,8 @@ async function runCodeRuntime(input) {
4827
5210
  engine: input.engine,
4828
5211
  image: CODE_PI_IMAGE,
4829
5212
  recipes: CODE_BUILD_RECIPES,
4830
- recipeAuthorization: "registered_recipe"
5213
+ recipeAuthorization: "registered_recipe",
5214
+ localSource: input.localSource
4831
5215
  });
4832
5216
  const reconciler = new CodeRuntimeReconciler(control, commandEngine);
4833
5217
  try {
@@ -4868,8 +5252,8 @@ function parseConnection(value, appId, appEnv) {
4868
5252
  return root;
4869
5253
  }
4870
5254
  function apiFailure(action, status, value) {
4871
- const message3 = record4(record4(value)?.error)?.message;
4872
- return `${action} failed (${status})${typeof message3 === "string" ? `: ${message3}` : ""}`;
5255
+ const message2 = record4(record4(value)?.error)?.message;
5256
+ return `${action} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
4873
5257
  }
4874
5258
  function record4(value) {
4875
5259
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -4884,6 +5268,8 @@ async function codeCommand(parsed, dependencies) {
4884
5268
  assertArgs(parsed, [
4885
5269
  "config",
4886
5270
  "env",
5271
+ "platform",
5272
+ "app-id",
4887
5273
  "email",
4888
5274
  "open",
4889
5275
  "name",
@@ -4899,6 +5285,8 @@ async function codeCommand(parsed, dependencies) {
4899
5285
  await (dependencies.codeConnect ?? codeConnect)({
4900
5286
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
4901
5287
  env: stringOpt(parsed.options.env),
5288
+ platform: stringOpt(parsed.options.platform),
5289
+ appId: stringOpt(parsed.options["app-id"]),
4902
5290
  email: stringOpt(parsed.options.email),
4903
5291
  open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
4904
5292
  name: stringOpt(parsed.options.name),
@@ -4914,16 +5302,16 @@ async function codeCommand(parsed, dependencies) {
4914
5302
  }
4915
5303
 
4916
5304
  // src/doctor-checks.ts
4917
- var import_node_child_process4 = require("child_process");
5305
+ var import_node_child_process6 = require("child_process");
4918
5306
  var import_node_fs9 = require("fs");
4919
5307
  var import_node_path6 = require("path");
4920
5308
 
4921
5309
  // src/wrangler.ts
4922
- var import_node_child_process3 = require("child_process");
5310
+ var import_node_child_process5 = require("child_process");
4923
5311
  var import_node_fs8 = require("fs");
4924
5312
  var import_node_path5 = require("path");
4925
5313
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
4926
- const child = (0, import_node_child_process3.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
5314
+ const child = (0, import_node_child_process5.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4927
5315
  let stdout = "";
4928
5316
  let stderr = "";
4929
5317
  child.stdout.on("data", (chunk) => stdout += chunk.toString());
@@ -5014,7 +5402,7 @@ function lintRules(rules, entities, publicRead) {
5014
5402
  }
5015
5403
  return warnings;
5016
5404
  }
5017
- var defaultExec = (cmd, args, cwd) => (0, import_node_child_process4.execFileSync)(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
5405
+ var defaultExec = (cmd, args, cwd) => (0, import_node_child_process6.execFileSync)(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
5018
5406
  function trackedSecretFiles(rootDir, exec = defaultExec, localPaths = []) {
5019
5407
  let output;
5020
5408
  try {
@@ -5440,7 +5828,7 @@ function relativeDisplay(path, rootDir) {
5440
5828
  }
5441
5829
 
5442
5830
  // src/provision.ts
5443
- var import_apps2 = require("@odla-ai/apps");
5831
+ var import_apps3 = require("@odla-ai/apps");
5444
5832
  var import_ai2 = require("@odla-ai/ai");
5445
5833
  var import_node_process7 = __toESM(require("process"), 1);
5446
5834
 
@@ -5593,6 +5981,7 @@ async function safeText3(res) {
5593
5981
 
5594
5982
  // src/provision-helpers.ts
5595
5983
  var import_ai = require("@odla-ai/ai");
5984
+ var import_apps2 = require("@odla-ai/apps");
5596
5985
  function serviceRank(service) {
5597
5986
  if (service === "db") return 0;
5598
5987
  if (service === "calendar") return 2;
@@ -5602,6 +5991,54 @@ function defaultSecretName(provider) {
5602
5991
  const names = import_ai.DEFAULT_SECRET_NAMES;
5603
5992
  return names[provider] ?? `${provider}_api_key`;
5604
5993
  }
5994
+ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
5995
+ const tenantId = (0, import_apps2.tenantIdFor)(cfg.app.id, env);
5996
+ const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
5997
+ headers: { authorization: `Bearer ${token}` }
5998
+ });
5999
+ if (res.ok || res.status === 404) return;
6000
+ if (res.status === 403) {
6001
+ throw new Error(
6002
+ `${env}: you are not an owner of "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; ask an existing owner to run "odla-ai app owners add <your-email>", then re-run provision`
6003
+ );
6004
+ }
6005
+ throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText4(res)}`);
6006
+ }
6007
+ async function readRegistryApp(cfg, token, doFetch) {
6008
+ const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}`, {
6009
+ headers: { authorization: `Bearer ${token}` }
6010
+ });
6011
+ if (res.status === 404) return null;
6012
+ if (!res.ok) return null;
6013
+ const json = await res.json();
6014
+ return json.app ?? null;
6015
+ }
6016
+ async function postJson2(doFetch, url, bearer, body) {
6017
+ const res = await doFetch(url, {
6018
+ method: "POST",
6019
+ headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
6020
+ body: JSON.stringify(body)
6021
+ });
6022
+ if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText4(res)}`);
6023
+ }
6024
+ function normalizeClerkConfig(value) {
6025
+ if (!value) return null;
6026
+ if (typeof value === "string") {
6027
+ const publishableKey2 = envValue(value);
6028
+ return publishableKey2 ? { publishableKey: publishableKey2 } : null;
6029
+ }
6030
+ if (typeof value !== "object") return null;
6031
+ const cfg = value;
6032
+ const publishableKey = envValue(cfg.publishableKey);
6033
+ return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
6034
+ }
6035
+ async function safeText4(res) {
6036
+ try {
6037
+ return redactSecrets((await res.text()).slice(0, 500));
6038
+ } catch {
6039
+ return "";
6040
+ }
6041
+ }
5605
6042
 
5606
6043
  // src/secrets.ts
5607
6044
  var PROD_ENV_NAMES = /* @__PURE__ */ new Set(["prod", "production"]);
@@ -5746,7 +6183,7 @@ async function provision(options) {
5746
6183
  }
5747
6184
  const doFetch = options.fetch ?? fetch;
5748
6185
  const token = await getDeveloperToken(cfg, options, doFetch, out);
5749
- const apps = (0, import_apps2.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
6186
+ const apps = (0, import_apps3.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
5750
6187
  const existing = await readRegistryApp(cfg, token, doFetch);
5751
6188
  if (existing) {
5752
6189
  out.log(`app: ${cfg.app.id} already exists`);
@@ -5754,6 +6191,9 @@ async function provision(options) {
5754
6191
  await apps.createApp({ name: cfg.app.name, appId: cfg.app.id });
5755
6192
  out.log(`app: created ${cfg.app.id}`);
5756
6193
  }
6194
+ for (const env of cfg.envs) {
6195
+ await assertTenantAdminAccess(doFetch, cfg, env, token);
6196
+ }
5757
6197
  const serviceOrder = [...cfg.services].sort((a, b) => serviceRank(a) - serviceRank(b));
5758
6198
  for (const env of cfg.envs) {
5759
6199
  for (const service of serviceOrder) {
@@ -5787,7 +6227,7 @@ async function provision(options) {
5787
6227
  }
5788
6228
  }
5789
6229
  for (const env of cfg.envs) {
5790
- const tenantId = (0, import_apps2.tenantIdFor)(cfg.app.id, env);
6230
+ const tenantId = (0, import_apps3.tenantIdFor)(cfg.app.id, env);
5791
6231
  credentials = await provisionEnvCredentials({
5792
6232
  cfg,
5793
6233
  env,
@@ -5810,10 +6250,10 @@ async function provision(options) {
5810
6250
  stdout: out
5811
6251
  });
5812
6252
  } catch (error) {
5813
- const message3 = error instanceof Error ? error.message : String(error);
6253
+ const message2 = error instanceof Error ? error.message : String(error);
5814
6254
  const consent = env === "prod" || env === "production" ? " --yes" : "";
5815
6255
  throw new Error(
5816
- `${message3}
6256
+ `${message2}
5817
6257
  ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}${consent}" without issuing or rotating again`,
5818
6258
  { cause: error }
5819
6259
  );
@@ -5870,45 +6310,10 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
5870
6310
  }
5871
6311
  }
5872
6312
  }
5873
- async function readRegistryApp(cfg, token, doFetch) {
5874
- const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}`, {
5875
- headers: { authorization: `Bearer ${token}` }
5876
- });
5877
- if (res.status === 404) return null;
5878
- if (!res.ok) return null;
5879
- const json2 = await res.json();
5880
- return json2.app ?? null;
5881
- }
5882
- async function postJson2(doFetch, url, bearer, body) {
5883
- const res = await doFetch(url, {
5884
- method: "POST",
5885
- headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
5886
- body: JSON.stringify(body)
5887
- });
5888
- if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText4(res)}`);
5889
- }
5890
- function normalizeClerkConfig(value) {
5891
- if (!value) return null;
5892
- if (typeof value === "string") {
5893
- const publishableKey2 = envValue(value);
5894
- return publishableKey2 ? { publishableKey: publishableKey2 } : null;
5895
- }
5896
- if (typeof value !== "object") return null;
5897
- const cfg = value;
5898
- const publishableKey = envValue(cfg.publishableKey);
5899
- return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
5900
- }
5901
- async function safeText4(res) {
5902
- try {
5903
- return redactSecrets((await res.text()).slice(0, 500));
5904
- } catch {
5905
- return "";
5906
- }
5907
- }
5908
6313
 
5909
6314
  // src/secrets-set.ts
5910
6315
  var import_ai3 = require("@odla-ai/ai");
5911
- var import_apps3 = require("@odla-ai/apps");
6316
+ var import_apps4 = require("@odla-ai/apps");
5912
6317
  var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
5913
6318
  async function secretsSet(options) {
5914
6319
  const name = (options.name ?? "").trim();
@@ -5960,11 +6365,11 @@ async function resolveVaultWrite(options) {
5960
6365
  throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
5961
6366
  }
5962
6367
  const value = await secretInputValue(options, "secret");
5963
- return { cfg, tenantId: (0, import_apps3.tenantIdFor)(cfg.app.id, options.env), value, doFetch, out };
6368
+ return { cfg, tenantId: (0, import_apps4.tenantIdFor)(cfg.app.id, options.env), value, doFetch, out };
5964
6369
  }
5965
6370
 
5966
6371
  // src/security-command-context.ts
5967
- var import_promises9 = require("readline/promises");
6372
+ var import_promises10 = require("readline/promises");
5968
6373
  async function hostedSecurityContext(parsed, dependencies) {
5969
6374
  const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
5970
6375
  const cfg = await loadProjectConfig(configPath);
@@ -5987,12 +6392,12 @@ async function hostedSecurityContext(parsed, dependencies) {
5987
6392
  );
5988
6393
  return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
5989
6394
  }
5990
- async function interactiveConfirmation(message3, dependencies) {
5991
- if (dependencies.confirm) return dependencies.confirm(message3);
6395
+ async function interactiveConfirmation(message2, dependencies) {
6396
+ if (dependencies.confirm) return dependencies.confirm(message2);
5992
6397
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
5993
- const prompt = (0, import_promises9.createInterface)({ input: process.stdin, output: process.stdout });
6398
+ const prompt = (0, import_promises10.createInterface)({ input: process.stdin, output: process.stdout });
5994
6399
  try {
5995
- const answer = await prompt.question(`${message3} [y/N] `);
6400
+ const answer = await prompt.question(`${message2} [y/N] `);
5996
6401
  return /^y(?:es)?$/i.test(answer.trim());
5997
6402
  } finally {
5998
6403
  prompt.close();
@@ -6119,7 +6524,7 @@ var import_security2 = require("@odla-ai/security");
6119
6524
  // src/security.ts
6120
6525
  var import_node_path8 = require("path");
6121
6526
  var import_security = require("@odla-ai/security");
6122
- var import_node2 = require("@odla-ai/security/node");
6527
+ var import_node3 = require("@odla-ai/security/node");
6123
6528
  async function runHostedSecurity(options) {
6124
6529
  if (options.sourceDisclosureAck !== "redacted") {
6125
6530
  throw new Error("Hosted security requires sourceDisclosureAck=redacted before repository source may leave this process");
@@ -6142,7 +6547,7 @@ async function runHostedSecurity(options) {
6142
6547
  scope: selfAudit ? "platform:security:self" : null
6143
6548
  };
6144
6549
  const token = await injectedToken(options, tokenRequest);
6145
- const snapshot = await (0, import_node2.snapshotDirectory)(target, {
6550
+ const snapshot = await (0, import_node3.snapshotDirectory)(target, {
6146
6551
  exclude: !outputRelative.startsWith("../") && !(0, import_node_path8.isAbsolute)(outputRelative) ? [outputRelative] : []
6147
6552
  });
6148
6553
  const hosted = await (0, import_security.createPlatformSecurityReasoners)({
@@ -6161,7 +6566,7 @@ async function runHostedSecurity(options) {
6161
6566
  });
6162
6567
  const harness = (0, import_security.createSecurityHarness)({
6163
6568
  profile,
6164
- store: new import_node2.FileRunStore((0, import_node_path8.resolve)(output, "state")),
6569
+ store: new import_node3.FileRunStore((0, import_node_path8.resolve)(output, "state")),
6165
6570
  discoveryReasoner: hosted.discoveryReasoner,
6166
6571
  validationReasoner: hosted.validationReasoner,
6167
6572
  policy: {
@@ -6171,7 +6576,7 @@ async function runHostedSecurity(options) {
6171
6576
  }
6172
6577
  });
6173
6578
  const report2 = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
6174
- await (0, import_node2.writeSecurityArtifacts)(output, report2);
6579
+ await (0, import_node3.writeSecurityArtifacts)(output, report2);
6175
6580
  const reportDigest = await (0, import_security.securityFingerprint)(report2);
6176
6581
  await hosted.complete({
6177
6582
  reportDigest,
@@ -6304,8 +6709,8 @@ async function startHostedSecurityJob(options) {
6304
6709
  "start hosted security job"
6305
6710
  );
6306
6711
  } catch (error) {
6307
- const message3 = error instanceof Error ? error.message : String(error);
6308
- if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message3)) {
6712
+ const message2 = error instanceof Error ? error.message : String(error);
6713
+ if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message2)) {
6309
6714
  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 });
6310
6715
  }
6311
6716
  throw error;
@@ -6601,16 +7006,16 @@ async function githubSecurityCommand(parsed, dependencies) {
6601
7006
  }
6602
7007
  assertArgs(parsed, ["config", "env", "platform", "repo", "email", "open"], 3);
6603
7008
  const context = await hostedSecurityContext(parsed, dependencies);
6604
- const repository = stringOpt(parsed.options.repo) ?? await inferGitHubRepository(process.cwd(), dependencies.readGitOrigin);
7009
+ const repository = stringOpt(parsed.options.repo) ?? await inferGitHubRepository(process.cwd(), dependencies.readGitOrigin).catch(() => void 0);
6605
7010
  const connection = await connectGitHubSecuritySource({
6606
7011
  ...context,
6607
- repository,
7012
+ ...repository === void 0 ? {} : { repository },
6608
7013
  open: parsed.options.open !== false,
6609
7014
  openInstallUrl: dependencies.openUrl ?? openUrl,
6610
7015
  wait: dependencies.pollWait,
6611
7016
  stdout: context.stdout
6612
7017
  });
6613
- context.stdout.log(`github: connected ${connection.repository ?? repository} (${connection.sourceId ?? "source pending"})`);
7018
+ context.stdout.log(`github: connected ${connection.repository ?? repository ?? "app repository"} (${connection.sourceId ?? "source pending"})`);
6614
7019
  context.stdout.log("github: odla.ai stores the installation; no PAT or GitHub token is written locally");
6615
7020
  }
6616
7021
  async function listSecuritySources(parsed, dependencies) {