@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/README.md
CHANGED
|
@@ -29,8 +29,8 @@ For a project you keep working in, install it as a dev dependency so the
|
|
|
29
29
|
shorter `npx odla-ai` form works and the version is pinned by your lockfile:
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
npm view @odla-ai/cli@0.
|
|
33
|
-
npm i -D --save-exact @odla-ai/cli@0.
|
|
32
|
+
npm view @odla-ai/cli@0.16.1 version
|
|
33
|
+
npm i -D --save-exact @odla-ai/cli@0.16.1
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
## Prerequisites
|
package/dist/bin.cjs
CHANGED
|
@@ -2540,7 +2540,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
|
|
|
2540
2540
|
}
|
|
2541
2541
|
}
|
|
2542
2542
|
|
|
2543
|
-
// ../harness/dist/chunk-
|
|
2543
|
+
// ../harness/dist/chunk-5E2R3LHE.js
|
|
2544
2544
|
var import_crypto = require("crypto");
|
|
2545
2545
|
var import_promises5 = require("fs/promises");
|
|
2546
2546
|
var import_path5 = require("path");
|
|
@@ -2877,7 +2877,7 @@ function validateSnapshot(snapshot, limits) {
|
|
|
2877
2877
|
}
|
|
2878
2878
|
}
|
|
2879
2879
|
|
|
2880
|
-
// ../harness/dist/chunk-
|
|
2880
|
+
// ../harness/dist/chunk-5E2R3LHE.js
|
|
2881
2881
|
var import_child_process4 = require("child_process");
|
|
2882
2882
|
var import_promises6 = require("fs/promises");
|
|
2883
2883
|
var import_path6 = require("path");
|
|
@@ -3170,7 +3170,7 @@ function looksLikeDestination(value) {
|
|
|
3170
3170
|
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
|
|
3171
3171
|
}
|
|
3172
3172
|
|
|
3173
|
-
// ../harness/dist/chunk-
|
|
3173
|
+
// ../harness/dist/chunk-5E2R3LHE.js
|
|
3174
3174
|
var import_crypto4 = require("crypto");
|
|
3175
3175
|
async function digestStagedWorkspace(root, limits) {
|
|
3176
3176
|
const files = [];
|
|
@@ -3285,9 +3285,13 @@ function createCodeRuntimeControlClient(options) {
|
|
|
3285
3285
|
if (!Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 1e3 || requestTimeoutMs > 12e4) {
|
|
3286
3286
|
throw new TypeError("requestTimeoutMs must be an integer from 1000 to 120000");
|
|
3287
3287
|
}
|
|
3288
|
+
const modelRequestTimeoutMs = options.modelRequestTimeoutMs ?? 15 * 6e4;
|
|
3289
|
+
if (!Number.isSafeInteger(modelRequestTimeoutMs) || modelRequestTimeoutMs < 3e4 || modelRequestTimeoutMs > 30 * 6e4) {
|
|
3290
|
+
throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
|
|
3291
|
+
}
|
|
3288
3292
|
const request = options.fetch ?? fetch;
|
|
3289
|
-
const call = async (path, body) => {
|
|
3290
|
-
const timeout = AbortSignal.timeout(
|
|
3293
|
+
const call = async (path, body, timeoutMs = requestTimeoutMs) => {
|
|
3294
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
3291
3295
|
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
3292
3296
|
let response2;
|
|
3293
3297
|
try {
|
|
@@ -3326,14 +3330,18 @@ function createCodeRuntimeControlClient(options) {
|
|
|
3326
3330
|
await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
3327
3331
|
),
|
|
3328
3332
|
infer: async (sessionId, inference) => {
|
|
3329
|
-
const value = record3(await call(
|
|
3333
|
+
const value = record3(await call(
|
|
3334
|
+
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
3335
|
+
inference,
|
|
3336
|
+
modelRequestTimeoutMs
|
|
3337
|
+
));
|
|
3330
3338
|
if (!value || value.requestId !== inference.requestId || !record3(value.response) || !record3(value.receipt)) {
|
|
3331
3339
|
throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
|
|
3332
3340
|
}
|
|
3333
3341
|
return value;
|
|
3334
3342
|
},
|
|
3335
3343
|
review: async (sessionId, review) => parseReview(
|
|
3336
|
-
await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review)
|
|
3344
|
+
await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
|
|
3337
3345
|
),
|
|
3338
3346
|
submitCandidate: async (sessionId, checkpointId, verification) => {
|
|
3339
3347
|
if (!/^cpoint_[0-9a-f]{32}$/.test(checkpointId)) throw new TypeError("invalid Code checkpoint id");
|
|
@@ -3418,10 +3426,28 @@ async function parseSource(value) {
|
|
|
3418
3426
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
3419
3427
|
return { path: file.path, content: file.content };
|
|
3420
3428
|
});
|
|
3429
|
+
const referencesValue = snapshot.references === void 0 ? [] : snapshot.references;
|
|
3430
|
+
if (!Array.isArray(referencesValue) || referencesValue.length > 5) throw invalid("reference sources");
|
|
3431
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
3432
|
+
const references = [];
|
|
3433
|
+
for (const item of referencesValue) {
|
|
3434
|
+
const reference = record3(item);
|
|
3435
|
+
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");
|
|
3436
|
+
aliases.add(reference.alias);
|
|
3437
|
+
const referenceFiles = reference.files.map((entry) => {
|
|
3438
|
+
const file = record3(entry);
|
|
3439
|
+
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
|
|
3440
|
+
return { path: file.path, content: file.content };
|
|
3441
|
+
});
|
|
3442
|
+
const source2 = { repository: reference.repository, commitSha: reference.commitSha, files: referenceFiles };
|
|
3443
|
+
const referenceDigest = await digestCodeRepositorySnapshot(source2, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
|
|
3444
|
+
if (referenceDigest !== reference.treeDigest) throw invalid("reference source digest");
|
|
3445
|
+
references.push({ alias: reference.alias, ...source2, treeDigest: referenceDigest });
|
|
3446
|
+
}
|
|
3421
3447
|
const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
|
|
3422
3448
|
const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
|
|
3423
3449
|
if (digest !== snapshot.treeDigest) throw invalid("source digest");
|
|
3424
|
-
return { ...source, treeDigest: digest };
|
|
3450
|
+
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
3425
3451
|
}
|
|
3426
3452
|
function parseReview(value) {
|
|
3427
3453
|
const review = record3(record3(value)?.review);
|
|
@@ -3952,7 +3978,7 @@ var CodeRuntimeCheckpointManager = class {
|
|
|
3952
3978
|
await this.options.event(command, {
|
|
3953
3979
|
type: "message",
|
|
3954
3980
|
actor: "system",
|
|
3955
|
-
body: `Candidate ${candidate.candidateId} is ready for
|
|
3981
|
+
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`
|
|
3956
3982
|
}, pending.refs);
|
|
3957
3983
|
this.#pending.delete(command.commandId);
|
|
3958
3984
|
return true;
|
|
@@ -3979,12 +4005,51 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.
|
|
|
3979
4005
|
await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
|
|
3980
4006
|
await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 420 });
|
|
3981
4007
|
}
|
|
4008
|
+
for (const reference of snapshot.references ?? []) {
|
|
4009
|
+
validateAlias(reference.alias);
|
|
4010
|
+
if (!reference.files.length || reference.files.length > 1e4) throw new TypeError("Code reference file count is invalid");
|
|
4011
|
+
for (const file of reference.files) {
|
|
4012
|
+
validatePath(file.path);
|
|
4013
|
+
const path = `.odla-references/${reference.alias}/${file.path}`;
|
|
4014
|
+
if (seen.has(path)) throw new TypeError("Code reference repeats a path");
|
|
4015
|
+
seen.add(path);
|
|
4016
|
+
bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
|
|
4017
|
+
if (bytes > 80 * 1024 * 1024) throw new TypeError("Code source set exceeds its byte bound");
|
|
4018
|
+
const target = (0, import_path8.resolve)(sourceDir, path);
|
|
4019
|
+
if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
|
|
4020
|
+
await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
|
|
4021
|
+
await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
3982
4024
|
return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
|
|
3983
4025
|
} catch (cause) {
|
|
3984
4026
|
await (0, import_promises8.rm)(root, { recursive: true, force: true });
|
|
3985
4027
|
throw cause;
|
|
3986
4028
|
}
|
|
3987
4029
|
}
|
|
4030
|
+
function validateAlias(alias) {
|
|
4031
|
+
if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias) || alias === "primary") {
|
|
4032
|
+
throw new TypeError("Code reference alias is invalid");
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
async function attachCodeRuntimeReferences(workspace, references) {
|
|
4036
|
+
let bytes = 0;
|
|
4037
|
+
for (const reference of references) {
|
|
4038
|
+
validateAlias(reference.alias);
|
|
4039
|
+
for (const file of reference.files) {
|
|
4040
|
+
validatePath(file.path);
|
|
4041
|
+
const path = `.odla-references/${reference.alias}/${file.path}`;
|
|
4042
|
+
bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
|
|
4043
|
+
if (bytes > 64 * 1024 * 1024) throw new TypeError("Code reference set exceeds its byte bound");
|
|
4044
|
+
for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
|
|
4045
|
+
const target = (0, import_path8.resolve)(root, path);
|
|
4046
|
+
if (!target.startsWith(`${(0, import_path8.resolve)(root)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
|
|
4047
|
+
await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
|
|
4048
|
+
await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
3988
4053
|
function validatePath(path) {
|
|
3989
4054
|
const parts = path.split("/");
|
|
3990
4055
|
if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." || RESERVED2.has(part) || SECRET2.test(part))) {
|
|
@@ -4183,6 +4248,9 @@ async function patch(context, request, options, policy) {
|
|
|
4183
4248
|
exactKeys(request.input, ["patch"]);
|
|
4184
4249
|
const value = stringField(request.input, "patch");
|
|
4185
4250
|
const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
|
|
4251
|
+
if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
4252
|
+
throw new TypeError("patch targets a read-only reference source");
|
|
4253
|
+
}
|
|
4186
4254
|
const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
|
|
4187
4255
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
4188
4256
|
await applyCodePatch(context.workspaceDir, value, paths);
|
|
@@ -4268,6 +4336,9 @@ function validateOptions(options) {
|
|
|
4268
4336
|
throw new TypeError("Code tool broker requires a reader and unique registered recipes");
|
|
4269
4337
|
}
|
|
4270
4338
|
for (const recipe2 of options.recipes) assertCodeBuildRecipe(recipe2);
|
|
4339
|
+
if (options.readOnlyPrefixes?.some((prefix) => !/^[A-Za-z0-9_.-]+$/.test(prefix) || prefix === "." || prefix === "..")) {
|
|
4340
|
+
throw new TypeError("Code tool broker read-only prefix is invalid");
|
|
4341
|
+
}
|
|
4271
4342
|
}
|
|
4272
4343
|
function exactKeys(input, allowed) {
|
|
4273
4344
|
if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
|
|
@@ -4375,7 +4446,8 @@ function createCodeRuntimeToolBroker(input, lease, role) {
|
|
|
4375
4446
|
recipes: input.recipes,
|
|
4376
4447
|
recipeExecutor: createContainerRecipeExecutor(input.engine),
|
|
4377
4448
|
recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
|
|
4378
|
-
readerId: `code-session:${lease.task.taskId}
|
|
4449
|
+
readerId: `code-session:${lease.task.taskId}`,
|
|
4450
|
+
readOnlyPrefixes: [".odla-references"]
|
|
4379
4451
|
});
|
|
4380
4452
|
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
4453
|
}
|
|
@@ -4453,6 +4525,14 @@ var CodePiRuntimeEngine = class {
|
|
|
4453
4525
|
resume
|
|
4454
4526
|
});
|
|
4455
4527
|
({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
|
|
4528
|
+
if (command.payload.sourceSet) {
|
|
4529
|
+
const selected = await this.options.control.source(command.sessionId);
|
|
4530
|
+
if (selected.repository !== metadata2.repository || selected.commitSha !== metadata2.baseCommitSha || selected.treeDigest !== metadata2.sourceTreeDigest) {
|
|
4531
|
+
await workspace.cleanup();
|
|
4532
|
+
throw new TypeError("Code local source does not match the selected GitHub primary source");
|
|
4533
|
+
}
|
|
4534
|
+
await attachCodeRuntimeReferences(workspace, selected.references ?? []);
|
|
4535
|
+
}
|
|
4456
4536
|
} else {
|
|
4457
4537
|
const source = await this.options.control.source(command.sessionId);
|
|
4458
4538
|
const materialized = await materializeCodeRuntimeSource(source);
|
|
@@ -4480,8 +4560,8 @@ var CodePiRuntimeEngine = class {
|
|
|
4480
4560
|
repository: metadata2.repository,
|
|
4481
4561
|
sourceTreeDigest: metadata2.sourceTreeDigest,
|
|
4482
4562
|
trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
|
|
4483
|
-
maxFiles:
|
|
4484
|
-
maxBytes:
|
|
4563
|
+
maxFiles: 2e4,
|
|
4564
|
+
maxBytes: 512 * 1024 * 1024
|
|
4485
4565
|
}),
|
|
4486
4566
|
planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
|
|
4487
4567
|
JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })
|