@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/bin.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  exitCodeFor,
4
4
  runCli
5
- } from "./chunk-E3CCYDIT.js";
5
+ } from "./chunk-FRDIQURT.js";
6
6
 
7
7
  // src/bin.ts
8
8
  runCli().catch((err) => {
@@ -2398,7 +2398,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
2398
2398
  }
2399
2399
  }
2400
2400
 
2401
- // ../harness/dist/chunk-QEW6NNAO.js
2401
+ // ../harness/dist/chunk-5E2R3LHE.js
2402
2402
  import { createHash } from "crypto";
2403
2403
  import { readFile, readdir as readdir2 } from "fs/promises";
2404
2404
  import { relative as relative3, resolve as resolve4 } from "path";
@@ -2735,7 +2735,7 @@ function validateSnapshot(snapshot, limits) {
2735
2735
  }
2736
2736
  }
2737
2737
 
2738
- // ../harness/dist/chunk-QEW6NNAO.js
2738
+ // ../harness/dist/chunk-5E2R3LHE.js
2739
2739
  import { spawn as spawn4 } from "child_process";
2740
2740
  import { lstat as lstat2 } from "fs/promises";
2741
2741
  import { resolve as resolve23, sep as sep3 } from "path";
@@ -3028,7 +3028,7 @@ function looksLikeDestination(value) {
3028
3028
  return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
3029
3029
  }
3030
3030
 
3031
- // ../harness/dist/chunk-QEW6NNAO.js
3031
+ // ../harness/dist/chunk-5E2R3LHE.js
3032
3032
  import { createHash as createHash3 } from "crypto";
3033
3033
  async function digestStagedWorkspace(root, limits) {
3034
3034
  const files = [];
@@ -3143,9 +3143,13 @@ function createCodeRuntimeControlClient(options) {
3143
3143
  if (!Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 1e3 || requestTimeoutMs > 12e4) {
3144
3144
  throw new TypeError("requestTimeoutMs must be an integer from 1000 to 120000");
3145
3145
  }
3146
+ const modelRequestTimeoutMs = options.modelRequestTimeoutMs ?? 15 * 6e4;
3147
+ if (!Number.isSafeInteger(modelRequestTimeoutMs) || modelRequestTimeoutMs < 3e4 || modelRequestTimeoutMs > 30 * 6e4) {
3148
+ throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
3149
+ }
3146
3150
  const request = options.fetch ?? fetch;
3147
- const call = async (path, body) => {
3148
- const timeout = AbortSignal.timeout(requestTimeoutMs);
3151
+ const call = async (path, body, timeoutMs = requestTimeoutMs) => {
3152
+ const timeout = AbortSignal.timeout(timeoutMs);
3149
3153
  const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
3150
3154
  let response2;
3151
3155
  try {
@@ -3184,14 +3188,18 @@ function createCodeRuntimeControlClient(options) {
3184
3188
  await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
3185
3189
  ),
3186
3190
  infer: async (sessionId, inference) => {
3187
- const value = record3(await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`, inference));
3191
+ const value = record3(await call(
3192
+ `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
3193
+ inference,
3194
+ modelRequestTimeoutMs
3195
+ ));
3188
3196
  if (!value || value.requestId !== inference.requestId || !record3(value.response) || !record3(value.receipt)) {
3189
3197
  throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
3190
3198
  }
3191
3199
  return value;
3192
3200
  },
3193
3201
  review: async (sessionId, review) => parseReview(
3194
- await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review)
3202
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
3195
3203
  ),
3196
3204
  submitCandidate: async (sessionId, checkpointId, verification) => {
3197
3205
  if (!/^cpoint_[0-9a-f]{32}$/.test(checkpointId)) throw new TypeError("invalid Code checkpoint id");
@@ -3276,10 +3284,28 @@ async function parseSource(value) {
3276
3284
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
3277
3285
  return { path: file.path, content: file.content };
3278
3286
  });
3287
+ const referencesValue = snapshot.references === void 0 ? [] : snapshot.references;
3288
+ if (!Array.isArray(referencesValue) || referencesValue.length > 5) throw invalid("reference sources");
3289
+ const aliases = /* @__PURE__ */ new Set();
3290
+ const references = [];
3291
+ for (const item of referencesValue) {
3292
+ const reference = record3(item);
3293
+ 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");
3294
+ aliases.add(reference.alias);
3295
+ const referenceFiles = reference.files.map((entry) => {
3296
+ const file = record3(entry);
3297
+ if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
3298
+ return { path: file.path, content: file.content };
3299
+ });
3300
+ const source2 = { repository: reference.repository, commitSha: reference.commitSha, files: referenceFiles };
3301
+ const referenceDigest = await digestCodeRepositorySnapshot(source2, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3302
+ if (referenceDigest !== reference.treeDigest) throw invalid("reference source digest");
3303
+ references.push({ alias: reference.alias, ...source2, treeDigest: referenceDigest });
3304
+ }
3279
3305
  const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
3280
3306
  const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3281
3307
  if (digest !== snapshot.treeDigest) throw invalid("source digest");
3282
- return { ...source, treeDigest: digest };
3308
+ return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
3283
3309
  }
3284
3310
  function parseReview(value) {
3285
3311
  const review = record3(record3(value)?.review);
@@ -3810,7 +3836,7 @@ var CodeRuntimeCheckpointManager = class {
3810
3836
  await this.options.event(command, {
3811
3837
  type: "message",
3812
3838
  actor: "system",
3813
- body: `Candidate ${candidate.candidateId} is ready for exact owner publication approval`
3839
+ 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`
3814
3840
  }, pending.refs);
3815
3841
  this.#pending.delete(command.commandId);
3816
3842
  return true;
@@ -3837,12 +3863,51 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir3()) {
3837
3863
  await mkdir3(dirname4(target), { recursive: true });
3838
3864
  await writeFile2(target, file.content, { flag: "wx", mode: 420 });
3839
3865
  }
3866
+ for (const reference of snapshot.references ?? []) {
3867
+ validateAlias(reference.alias);
3868
+ if (!reference.files.length || reference.files.length > 1e4) throw new TypeError("Code reference file count is invalid");
3869
+ for (const file of reference.files) {
3870
+ validatePath(file.path);
3871
+ const path = `.odla-references/${reference.alias}/${file.path}`;
3872
+ if (seen.has(path)) throw new TypeError("Code reference repeats a path");
3873
+ seen.add(path);
3874
+ bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
3875
+ if (bytes > 80 * 1024 * 1024) throw new TypeError("Code source set exceeds its byte bound");
3876
+ const target = resolve32(sourceDir, path);
3877
+ if (!target.startsWith(`${resolve32(sourceDir)}${sep22}`)) throw new TypeError("Code reference path escapes its root");
3878
+ await mkdir3(dirname4(target), { recursive: true });
3879
+ await writeFile2(target, file.content, { flag: "wx", mode: 292 });
3880
+ }
3881
+ }
3840
3882
  return { sourceDir, cleanup: () => rm3(root, { recursive: true, force: true }) };
3841
3883
  } catch (cause) {
3842
3884
  await rm3(root, { recursive: true, force: true });
3843
3885
  throw cause;
3844
3886
  }
3845
3887
  }
3888
+ function validateAlias(alias) {
3889
+ if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias) || alias === "primary") {
3890
+ throw new TypeError("Code reference alias is invalid");
3891
+ }
3892
+ }
3893
+ async function attachCodeRuntimeReferences(workspace, references) {
3894
+ let bytes = 0;
3895
+ for (const reference of references) {
3896
+ validateAlias(reference.alias);
3897
+ for (const file of reference.files) {
3898
+ validatePath(file.path);
3899
+ const path = `.odla-references/${reference.alias}/${file.path}`;
3900
+ bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
3901
+ if (bytes > 64 * 1024 * 1024) throw new TypeError("Code reference set exceeds its byte bound");
3902
+ for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
3903
+ const target = resolve32(root, path);
3904
+ if (!target.startsWith(`${resolve32(root)}${sep22}`)) throw new TypeError("Code reference path escapes its root");
3905
+ await mkdir3(dirname4(target), { recursive: true });
3906
+ await writeFile2(target, file.content, { flag: "wx", mode: 292 });
3907
+ }
3908
+ }
3909
+ }
3910
+ }
3846
3911
  function validatePath(path) {
3847
3912
  const parts = path.split("/");
3848
3913
  if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." || RESERVED2.has(part) || SECRET2.test(part))) {
@@ -4041,6 +4106,9 @@ async function patch(context, request, options, policy) {
4041
4106
  exactKeys(request.input, ["patch"]);
4042
4107
  const value = stringField(request.input, "patch");
4043
4108
  const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
4109
+ if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
4110
+ throw new TypeError("patch targets a read-only reference source");
4111
+ }
4044
4112
  const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
4045
4113
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
4046
4114
  await applyCodePatch(context.workspaceDir, value, paths);
@@ -4126,6 +4194,9 @@ function validateOptions(options) {
4126
4194
  throw new TypeError("Code tool broker requires a reader and unique registered recipes");
4127
4195
  }
4128
4196
  for (const recipe2 of options.recipes) assertCodeBuildRecipe(recipe2);
4197
+ if (options.readOnlyPrefixes?.some((prefix) => !/^[A-Za-z0-9_.-]+$/.test(prefix) || prefix === "." || prefix === "..")) {
4198
+ throw new TypeError("Code tool broker read-only prefix is invalid");
4199
+ }
4129
4200
  }
4130
4201
  function exactKeys(input, allowed) {
4131
4202
  if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
@@ -4233,7 +4304,8 @@ function createCodeRuntimeToolBroker(input, lease, role) {
4233
4304
  recipes: input.recipes,
4234
4305
  recipeExecutor: createContainerRecipeExecutor(input.engine),
4235
4306
  recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
4236
- readerId: `code-session:${lease.task.taskId}`
4307
+ readerId: `code-session:${lease.task.taskId}`,
4308
+ readOnlyPrefixes: [".odla-references"]
4237
4309
  });
4238
4310
  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" }) };
4239
4311
  }
@@ -4311,6 +4383,14 @@ var CodePiRuntimeEngine = class {
4311
4383
  resume
4312
4384
  });
4313
4385
  ({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
4386
+ if (command.payload.sourceSet) {
4387
+ const selected = await this.options.control.source(command.sessionId);
4388
+ if (selected.repository !== metadata2.repository || selected.commitSha !== metadata2.baseCommitSha || selected.treeDigest !== metadata2.sourceTreeDigest) {
4389
+ await workspace.cleanup();
4390
+ throw new TypeError("Code local source does not match the selected GitHub primary source");
4391
+ }
4392
+ await attachCodeRuntimeReferences(workspace, selected.references ?? []);
4393
+ }
4314
4394
  } else {
4315
4395
  const source = await this.options.control.source(command.sessionId);
4316
4396
  const materialized = await materializeCodeRuntimeSource(source);
@@ -4338,8 +4418,8 @@ var CodePiRuntimeEngine = class {
4338
4418
  repository: metadata2.repository,
4339
4419
  sourceTreeDigest: metadata2.sourceTreeDigest,
4340
4420
  trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
4341
- maxFiles: 1e4,
4342
- maxBytes: 16 * 1024 * 1024
4421
+ maxFiles: 2e4,
4422
+ maxBytes: 512 * 1024 * 1024
4343
4423
  }),
4344
4424
  planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
4345
4425
  JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })
@@ -7674,4 +7754,4 @@ export {
7674
7754
  exitCodeFor,
7675
7755
  runCli
7676
7756
  };
7677
- //# sourceMappingURL=chunk-E3CCYDIT.js.map
7757
+ //# sourceMappingURL=chunk-FRDIQURT.js.map