@odla-ai/cli 0.15.0 → 0.16.1

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-5E2R3LHE.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-5E2R3LHE.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-5E2R3LHE.js
3227
3227
  var import_crypto4 = require("crypto");
3228
3228
  async function digestStagedWorkspace(root, limits) {
3229
3229
  const files = [];
@@ -3338,9 +3338,13 @@ function createCodeRuntimeControlClient(options) {
3338
3338
  if (!Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 1e3 || requestTimeoutMs > 12e4) {
3339
3339
  throw new TypeError("requestTimeoutMs must be an integer from 1000 to 120000");
3340
3340
  }
3341
+ const modelRequestTimeoutMs = options.modelRequestTimeoutMs ?? 15 * 6e4;
3342
+ if (!Number.isSafeInteger(modelRequestTimeoutMs) || modelRequestTimeoutMs < 3e4 || modelRequestTimeoutMs > 30 * 6e4) {
3343
+ throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
3344
+ }
3341
3345
  const request = options.fetch ?? fetch;
3342
- const call = async (path, body) => {
3343
- const timeout = AbortSignal.timeout(requestTimeoutMs);
3346
+ const call = async (path, body, timeoutMs = requestTimeoutMs) => {
3347
+ const timeout = AbortSignal.timeout(timeoutMs);
3344
3348
  const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
3345
3349
  let response2;
3346
3350
  try {
@@ -3379,14 +3383,18 @@ function createCodeRuntimeControlClient(options) {
3379
3383
  await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
3380
3384
  ),
3381
3385
  infer: async (sessionId, inference) => {
3382
- const value = record3(await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`, inference));
3386
+ const value = record3(await call(
3387
+ `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
3388
+ inference,
3389
+ modelRequestTimeoutMs
3390
+ ));
3383
3391
  if (!value || value.requestId !== inference.requestId || !record3(value.response) || !record3(value.receipt)) {
3384
3392
  throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
3385
3393
  }
3386
3394
  return value;
3387
3395
  },
3388
3396
  review: async (sessionId, review) => parseReview(
3389
- await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review)
3397
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
3390
3398
  ),
3391
3399
  submitCandidate: async (sessionId, checkpointId, verification) => {
3392
3400
  if (!/^cpoint_[0-9a-f]{32}$/.test(checkpointId)) throw new TypeError("invalid Code checkpoint id");
@@ -3471,10 +3479,28 @@ async function parseSource(value) {
3471
3479
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
3472
3480
  return { path: file.path, content: file.content };
3473
3481
  });
3482
+ const referencesValue = snapshot.references === void 0 ? [] : snapshot.references;
3483
+ if (!Array.isArray(referencesValue) || referencesValue.length > 5) throw invalid("reference sources");
3484
+ const aliases = /* @__PURE__ */ new Set();
3485
+ const references = [];
3486
+ for (const item of referencesValue) {
3487
+ const reference = record3(item);
3488
+ 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");
3489
+ aliases.add(reference.alias);
3490
+ const referenceFiles = reference.files.map((entry) => {
3491
+ const file = record3(entry);
3492
+ if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
3493
+ return { path: file.path, content: file.content };
3494
+ });
3495
+ const source2 = { repository: reference.repository, commitSha: reference.commitSha, files: referenceFiles };
3496
+ const referenceDigest = await digestCodeRepositorySnapshot(source2, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3497
+ if (referenceDigest !== reference.treeDigest) throw invalid("reference source digest");
3498
+ references.push({ alias: reference.alias, ...source2, treeDigest: referenceDigest });
3499
+ }
3474
3500
  const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
3475
3501
  const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3476
3502
  if (digest !== snapshot.treeDigest) throw invalid("source digest");
3477
- return { ...source, treeDigest: digest };
3503
+ return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
3478
3504
  }
3479
3505
  function parseReview(value) {
3480
3506
  const review = record3(record3(value)?.review);
@@ -4005,7 +4031,7 @@ var CodeRuntimeCheckpointManager = class {
4005
4031
  await this.options.event(command, {
4006
4032
  type: "message",
4007
4033
  actor: "system",
4008
- body: `Candidate ${candidate.candidateId} is ready for exact owner publication approval`
4034
+ 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
4035
  }, pending.refs);
4010
4036
  this.#pending.delete(command.commandId);
4011
4037
  return true;
@@ -4032,12 +4058,51 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.
4032
4058
  await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
4033
4059
  await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 420 });
4034
4060
  }
4061
+ for (const reference of snapshot.references ?? []) {
4062
+ validateAlias(reference.alias);
4063
+ if (!reference.files.length || reference.files.length > 1e4) throw new TypeError("Code reference file count is invalid");
4064
+ for (const file of reference.files) {
4065
+ validatePath(file.path);
4066
+ const path = `.odla-references/${reference.alias}/${file.path}`;
4067
+ if (seen.has(path)) throw new TypeError("Code reference repeats a path");
4068
+ seen.add(path);
4069
+ bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
4070
+ if (bytes > 80 * 1024 * 1024) throw new TypeError("Code source set exceeds its byte bound");
4071
+ const target = (0, import_path8.resolve)(sourceDir, path);
4072
+ if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
4073
+ await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
4074
+ await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
4075
+ }
4076
+ }
4035
4077
  return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
4036
4078
  } catch (cause) {
4037
4079
  await (0, import_promises8.rm)(root, { recursive: true, force: true });
4038
4080
  throw cause;
4039
4081
  }
4040
4082
  }
4083
+ function validateAlias(alias) {
4084
+ if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias) || alias === "primary") {
4085
+ throw new TypeError("Code reference alias is invalid");
4086
+ }
4087
+ }
4088
+ async function attachCodeRuntimeReferences(workspace, references) {
4089
+ let bytes = 0;
4090
+ for (const reference of references) {
4091
+ validateAlias(reference.alias);
4092
+ for (const file of reference.files) {
4093
+ validatePath(file.path);
4094
+ const path = `.odla-references/${reference.alias}/${file.path}`;
4095
+ bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
4096
+ if (bytes > 64 * 1024 * 1024) throw new TypeError("Code reference set exceeds its byte bound");
4097
+ for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
4098
+ const target = (0, import_path8.resolve)(root, path);
4099
+ if (!target.startsWith(`${(0, import_path8.resolve)(root)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
4100
+ await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
4101
+ await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
4102
+ }
4103
+ }
4104
+ }
4105
+ }
4041
4106
  function validatePath(path) {
4042
4107
  const parts = path.split("/");
4043
4108
  if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." || RESERVED2.has(part) || SECRET2.test(part))) {
@@ -4236,6 +4301,9 @@ async function patch(context, request, options, policy) {
4236
4301
  exactKeys(request.input, ["patch"]);
4237
4302
  const value = stringField(request.input, "patch");
4238
4303
  const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
4304
+ if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
4305
+ throw new TypeError("patch targets a read-only reference source");
4306
+ }
4239
4307
  const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
4240
4308
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
4241
4309
  await applyCodePatch(context.workspaceDir, value, paths);
@@ -4321,6 +4389,9 @@ function validateOptions(options) {
4321
4389
  throw new TypeError("Code tool broker requires a reader and unique registered recipes");
4322
4390
  }
4323
4391
  for (const recipe2 of options.recipes) assertCodeBuildRecipe(recipe2);
4392
+ if (options.readOnlyPrefixes?.some((prefix) => !/^[A-Za-z0-9_.-]+$/.test(prefix) || prefix === "." || prefix === "..")) {
4393
+ throw new TypeError("Code tool broker read-only prefix is invalid");
4394
+ }
4324
4395
  }
4325
4396
  function exactKeys(input, allowed) {
4326
4397
  if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
@@ -4428,7 +4499,8 @@ function createCodeRuntimeToolBroker(input, lease, role) {
4428
4499
  recipes: input.recipes,
4429
4500
  recipeExecutor: createContainerRecipeExecutor(input.engine),
4430
4501
  recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
4431
- readerId: `code-session:${lease.task.taskId}`
4502
+ readerId: `code-session:${lease.task.taskId}`,
4503
+ readOnlyPrefixes: [".odla-references"]
4432
4504
  });
4433
4505
  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
4506
  }
@@ -4506,6 +4578,14 @@ var CodePiRuntimeEngine = class {
4506
4578
  resume
4507
4579
  });
4508
4580
  ({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
4581
+ if (command.payload.sourceSet) {
4582
+ const selected = await this.options.control.source(command.sessionId);
4583
+ if (selected.repository !== metadata2.repository || selected.commitSha !== metadata2.baseCommitSha || selected.treeDigest !== metadata2.sourceTreeDigest) {
4584
+ await workspace.cleanup();
4585
+ throw new TypeError("Code local source does not match the selected GitHub primary source");
4586
+ }
4587
+ await attachCodeRuntimeReferences(workspace, selected.references ?? []);
4588
+ }
4509
4589
  } else {
4510
4590
  const source = await this.options.control.source(command.sessionId);
4511
4591
  const materialized = await materializeCodeRuntimeSource(source);
@@ -4533,8 +4613,8 @@ var CodePiRuntimeEngine = class {
4533
4613
  repository: metadata2.repository,
4534
4614
  sourceTreeDigest: metadata2.sourceTreeDigest,
4535
4615
  trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
4536
- maxFiles: 1e4,
4537
- maxBytes: 16 * 1024 * 1024
4616
+ maxFiles: 2e4,
4617
+ maxBytes: 512 * 1024 * 1024
4538
4618
  }),
4539
4619
  planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
4540
4620
  JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })