@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/README.md +2 -2
- package/dist/bin.cjs +92 -12
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-E3CCYDIT.js → chunk-FRDIQURT.js} +93 -13
- package/dist/chunk-FRDIQURT.js.map +1 -0
- package/dist/index.cjs +92 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/odla-migrate/references/phase-2-db.md +2 -2
- package/dist/chunk-E3CCYDIT.js.map +0 -1
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-
|
|
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-
|
|
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-
|
|
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(
|
|
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(
|
|
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
|
|
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:
|
|
4537
|
-
maxBytes:
|
|
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 })
|