@odla-ai/cli 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2593,7 +2593,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
2593
2593
  }
2594
2594
  }
2595
2595
 
2596
- // ../harness/dist/chunk-QEW6NNAO.js
2596
+ // ../harness/dist/chunk-4EM6NMET.js
2597
2597
  var import_crypto = require("crypto");
2598
2598
  var import_promises5 = require("fs/promises");
2599
2599
  var import_path5 = require("path");
@@ -2930,7 +2930,7 @@ function validateSnapshot(snapshot, limits) {
2930
2930
  }
2931
2931
  }
2932
2932
 
2933
- // ../harness/dist/chunk-QEW6NNAO.js
2933
+ // ../harness/dist/chunk-4EM6NMET.js
2934
2934
  var import_child_process4 = require("child_process");
2935
2935
  var import_promises6 = require("fs/promises");
2936
2936
  var import_path6 = require("path");
@@ -3223,7 +3223,7 @@ function looksLikeDestination(value) {
3223
3223
  return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
3224
3224
  }
3225
3225
 
3226
- // ../harness/dist/chunk-QEW6NNAO.js
3226
+ // ../harness/dist/chunk-4EM6NMET.js
3227
3227
  var import_crypto4 = require("crypto");
3228
3228
  async function digestStagedWorkspace(root, limits) {
3229
3229
  const files = [];
@@ -3471,10 +3471,28 @@ async function parseSource(value) {
3471
3471
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
3472
3472
  return { path: file.path, content: file.content };
3473
3473
  });
3474
+ const referencesValue = snapshot.references === void 0 ? [] : snapshot.references;
3475
+ if (!Array.isArray(referencesValue) || referencesValue.length > 5) throw invalid("reference sources");
3476
+ const aliases = /* @__PURE__ */ new Set();
3477
+ const references = [];
3478
+ for (const item of referencesValue) {
3479
+ const reference = record3(item);
3480
+ if (!reference || typeof reference.alias !== "string" || !/^[a-z][a-z0-9-]{0,39}$/.test(reference.alias) || aliases.has(reference.alias) || reference.alias === "primary" || typeof reference.repository !== "string" || typeof reference.commitSha !== "string" || typeof reference.treeDigest !== "string" || !Array.isArray(reference.files)) throw invalid("reference source");
3481
+ aliases.add(reference.alias);
3482
+ const referenceFiles = reference.files.map((entry) => {
3483
+ const file = record3(entry);
3484
+ if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
3485
+ return { path: file.path, content: file.content };
3486
+ });
3487
+ const source2 = { repository: reference.repository, commitSha: reference.commitSha, files: referenceFiles };
3488
+ const referenceDigest = await digestCodeRepositorySnapshot(source2, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3489
+ if (referenceDigest !== reference.treeDigest) throw invalid("reference source digest");
3490
+ references.push({ alias: reference.alias, ...source2, treeDigest: referenceDigest });
3491
+ }
3474
3492
  const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
3475
3493
  const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3476
3494
  if (digest !== snapshot.treeDigest) throw invalid("source digest");
3477
- return { ...source, treeDigest: digest };
3495
+ return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
3478
3496
  }
3479
3497
  function parseReview(value) {
3480
3498
  const review = record3(record3(value)?.review);
@@ -4005,7 +4023,7 @@ var CodeRuntimeCheckpointManager = class {
4005
4023
  await this.options.event(command, {
4006
4024
  type: "message",
4007
4025
  actor: "system",
4008
- body: `Candidate ${candidate.candidateId} is ready for exact owner publication approval`
4026
+ body: command.payload.sourceSet ? `Candidate ${candidate.candidateId} was verified and delivered to the session PR branch` : `Candidate ${candidate.candidateId} is ready for legacy owner publication approval`
4009
4027
  }, pending.refs);
4010
4028
  this.#pending.delete(command.commandId);
4011
4029
  return true;
@@ -4032,12 +4050,51 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.
4032
4050
  await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
4033
4051
  await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 420 });
4034
4052
  }
4053
+ for (const reference of snapshot.references ?? []) {
4054
+ validateAlias(reference.alias);
4055
+ if (!reference.files.length || reference.files.length > 1e4) throw new TypeError("Code reference file count is invalid");
4056
+ for (const file of reference.files) {
4057
+ validatePath(file.path);
4058
+ const path = `.odla-references/${reference.alias}/${file.path}`;
4059
+ if (seen.has(path)) throw new TypeError("Code reference repeats a path");
4060
+ seen.add(path);
4061
+ bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
4062
+ if (bytes > 80 * 1024 * 1024) throw new TypeError("Code source set exceeds its byte bound");
4063
+ const target = (0, import_path8.resolve)(sourceDir, path);
4064
+ if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
4065
+ await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
4066
+ await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
4067
+ }
4068
+ }
4035
4069
  return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
4036
4070
  } catch (cause) {
4037
4071
  await (0, import_promises8.rm)(root, { recursive: true, force: true });
4038
4072
  throw cause;
4039
4073
  }
4040
4074
  }
4075
+ function validateAlias(alias) {
4076
+ if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias) || alias === "primary") {
4077
+ throw new TypeError("Code reference alias is invalid");
4078
+ }
4079
+ }
4080
+ async function attachCodeRuntimeReferences(workspace, references) {
4081
+ let bytes = 0;
4082
+ for (const reference of references) {
4083
+ validateAlias(reference.alias);
4084
+ for (const file of reference.files) {
4085
+ validatePath(file.path);
4086
+ const path = `.odla-references/${reference.alias}/${file.path}`;
4087
+ bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
4088
+ if (bytes > 64 * 1024 * 1024) throw new TypeError("Code reference set exceeds its byte bound");
4089
+ for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
4090
+ const target = (0, import_path8.resolve)(root, path);
4091
+ if (!target.startsWith(`${(0, import_path8.resolve)(root)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
4092
+ await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
4093
+ await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
4094
+ }
4095
+ }
4096
+ }
4097
+ }
4041
4098
  function validatePath(path) {
4042
4099
  const parts = path.split("/");
4043
4100
  if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." || RESERVED2.has(part) || SECRET2.test(part))) {
@@ -4236,6 +4293,9 @@ async function patch(context, request, options, policy) {
4236
4293
  exactKeys(request.input, ["patch"]);
4237
4294
  const value = stringField(request.input, "patch");
4238
4295
  const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
4296
+ if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
4297
+ throw new TypeError("patch targets a read-only reference source");
4298
+ }
4239
4299
  const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
4240
4300
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
4241
4301
  await applyCodePatch(context.workspaceDir, value, paths);
@@ -4321,6 +4381,9 @@ function validateOptions(options) {
4321
4381
  throw new TypeError("Code tool broker requires a reader and unique registered recipes");
4322
4382
  }
4323
4383
  for (const recipe2 of options.recipes) assertCodeBuildRecipe(recipe2);
4384
+ if (options.readOnlyPrefixes?.some((prefix) => !/^[A-Za-z0-9_.-]+$/.test(prefix) || prefix === "." || prefix === "..")) {
4385
+ throw new TypeError("Code tool broker read-only prefix is invalid");
4386
+ }
4324
4387
  }
4325
4388
  function exactKeys(input, allowed) {
4326
4389
  if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
@@ -4428,7 +4491,8 @@ function createCodeRuntimeToolBroker(input, lease, role) {
4428
4491
  recipes: input.recipes,
4429
4492
  recipeExecutor: createContainerRecipeExecutor(input.engine),
4430
4493
  recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
4431
- readerId: `code-session:${lease.task.taskId}`
4494
+ readerId: `code-session:${lease.task.taskId}`,
4495
+ readOnlyPrefixes: [".odla-references"]
4432
4496
  });
4433
4497
  return role === "coding" ? broker : { execute: (context, request) => request.tool === "sandbox.read" ? broker.execute(context, request) : Promise.resolve({ requestId: request.requestId, ok: false, content: "review sessions are read-only" }) };
4434
4498
  }
@@ -4506,6 +4570,14 @@ var CodePiRuntimeEngine = class {
4506
4570
  resume
4507
4571
  });
4508
4572
  ({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
4573
+ if (command.payload.sourceSet) {
4574
+ const selected = await this.options.control.source(command.sessionId);
4575
+ if (selected.repository !== metadata2.repository || selected.commitSha !== metadata2.baseCommitSha || selected.treeDigest !== metadata2.sourceTreeDigest) {
4576
+ await workspace.cleanup();
4577
+ throw new TypeError("Code local source does not match the selected GitHub primary source");
4578
+ }
4579
+ await attachCodeRuntimeReferences(workspace, selected.references ?? []);
4580
+ }
4509
4581
  } else {
4510
4582
  const source = await this.options.control.source(command.sessionId);
4511
4583
  const materialized = await materializeCodeRuntimeSource(source);
@@ -4533,8 +4605,8 @@ var CodePiRuntimeEngine = class {
4533
4605
  repository: metadata2.repository,
4534
4606
  sourceTreeDigest: metadata2.sourceTreeDigest,
4535
4607
  trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
4536
- maxFiles: 1e4,
4537
- maxBytes: 16 * 1024 * 1024
4608
+ maxFiles: 2e4,
4609
+ maxBytes: 512 * 1024 * 1024
4538
4610
  }),
4539
4611
  planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
4540
4612
  JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })