@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/README.md +2 -2
- package/dist/bin.cjs +80 -8
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-E3CCYDIT.js → chunk-S3OYXHDG.js} +81 -9
- package/dist/chunk-S3OYXHDG.js.map +1 -0
- package/dist/index.cjs +80 -8
- 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.0 version
|
|
33
|
+
npm i -D --save-exact @odla-ai/cli@0.16.0
|
|
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-4EM6NMET.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-4EM6NMET.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-4EM6NMET.js
|
|
3174
3174
|
var import_crypto4 = require("crypto");
|
|
3175
3175
|
async function digestStagedWorkspace(root, limits) {
|
|
3176
3176
|
const files = [];
|
|
@@ -3418,10 +3418,28 @@ async function parseSource(value) {
|
|
|
3418
3418
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
3419
3419
|
return { path: file.path, content: file.content };
|
|
3420
3420
|
});
|
|
3421
|
+
const referencesValue = snapshot.references === void 0 ? [] : snapshot.references;
|
|
3422
|
+
if (!Array.isArray(referencesValue) || referencesValue.length > 5) throw invalid("reference sources");
|
|
3423
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
3424
|
+
const references = [];
|
|
3425
|
+
for (const item of referencesValue) {
|
|
3426
|
+
const reference = record3(item);
|
|
3427
|
+
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");
|
|
3428
|
+
aliases.add(reference.alias);
|
|
3429
|
+
const referenceFiles = reference.files.map((entry) => {
|
|
3430
|
+
const file = record3(entry);
|
|
3431
|
+
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
|
|
3432
|
+
return { path: file.path, content: file.content };
|
|
3433
|
+
});
|
|
3434
|
+
const source2 = { repository: reference.repository, commitSha: reference.commitSha, files: referenceFiles };
|
|
3435
|
+
const referenceDigest = await digestCodeRepositorySnapshot(source2, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
|
|
3436
|
+
if (referenceDigest !== reference.treeDigest) throw invalid("reference source digest");
|
|
3437
|
+
references.push({ alias: reference.alias, ...source2, treeDigest: referenceDigest });
|
|
3438
|
+
}
|
|
3421
3439
|
const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
|
|
3422
3440
|
const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
|
|
3423
3441
|
if (digest !== snapshot.treeDigest) throw invalid("source digest");
|
|
3424
|
-
return { ...source, treeDigest: digest };
|
|
3442
|
+
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
3425
3443
|
}
|
|
3426
3444
|
function parseReview(value) {
|
|
3427
3445
|
const review = record3(record3(value)?.review);
|
|
@@ -3952,7 +3970,7 @@ var CodeRuntimeCheckpointManager = class {
|
|
|
3952
3970
|
await this.options.event(command, {
|
|
3953
3971
|
type: "message",
|
|
3954
3972
|
actor: "system",
|
|
3955
|
-
body: `Candidate ${candidate.candidateId} is ready for
|
|
3973
|
+
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
3974
|
}, pending.refs);
|
|
3957
3975
|
this.#pending.delete(command.commandId);
|
|
3958
3976
|
return true;
|
|
@@ -3979,12 +3997,51 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.
|
|
|
3979
3997
|
await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
|
|
3980
3998
|
await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 420 });
|
|
3981
3999
|
}
|
|
4000
|
+
for (const reference of snapshot.references ?? []) {
|
|
4001
|
+
validateAlias(reference.alias);
|
|
4002
|
+
if (!reference.files.length || reference.files.length > 1e4) throw new TypeError("Code reference file count is invalid");
|
|
4003
|
+
for (const file of reference.files) {
|
|
4004
|
+
validatePath(file.path);
|
|
4005
|
+
const path = `.odla-references/${reference.alias}/${file.path}`;
|
|
4006
|
+
if (seen.has(path)) throw new TypeError("Code reference repeats a path");
|
|
4007
|
+
seen.add(path);
|
|
4008
|
+
bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
|
|
4009
|
+
if (bytes > 80 * 1024 * 1024) throw new TypeError("Code source set exceeds its byte bound");
|
|
4010
|
+
const target = (0, import_path8.resolve)(sourceDir, path);
|
|
4011
|
+
if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
|
|
4012
|
+
await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
|
|
4013
|
+
await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
3982
4016
|
return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
|
|
3983
4017
|
} catch (cause) {
|
|
3984
4018
|
await (0, import_promises8.rm)(root, { recursive: true, force: true });
|
|
3985
4019
|
throw cause;
|
|
3986
4020
|
}
|
|
3987
4021
|
}
|
|
4022
|
+
function validateAlias(alias) {
|
|
4023
|
+
if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias) || alias === "primary") {
|
|
4024
|
+
throw new TypeError("Code reference alias is invalid");
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
async function attachCodeRuntimeReferences(workspace, references) {
|
|
4028
|
+
let bytes = 0;
|
|
4029
|
+
for (const reference of references) {
|
|
4030
|
+
validateAlias(reference.alias);
|
|
4031
|
+
for (const file of reference.files) {
|
|
4032
|
+
validatePath(file.path);
|
|
4033
|
+
const path = `.odla-references/${reference.alias}/${file.path}`;
|
|
4034
|
+
bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
|
|
4035
|
+
if (bytes > 64 * 1024 * 1024) throw new TypeError("Code reference set exceeds its byte bound");
|
|
4036
|
+
for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
|
|
4037
|
+
const target = (0, import_path8.resolve)(root, path);
|
|
4038
|
+
if (!target.startsWith(`${(0, import_path8.resolve)(root)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
|
|
4039
|
+
await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
|
|
4040
|
+
await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
3988
4045
|
function validatePath(path) {
|
|
3989
4046
|
const parts = path.split("/");
|
|
3990
4047
|
if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." || RESERVED2.has(part) || SECRET2.test(part))) {
|
|
@@ -4183,6 +4240,9 @@ async function patch(context, request, options, policy) {
|
|
|
4183
4240
|
exactKeys(request.input, ["patch"]);
|
|
4184
4241
|
const value = stringField(request.input, "patch");
|
|
4185
4242
|
const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
|
|
4243
|
+
if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
4244
|
+
throw new TypeError("patch targets a read-only reference source");
|
|
4245
|
+
}
|
|
4186
4246
|
const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
|
|
4187
4247
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
4188
4248
|
await applyCodePatch(context.workspaceDir, value, paths);
|
|
@@ -4268,6 +4328,9 @@ function validateOptions(options) {
|
|
|
4268
4328
|
throw new TypeError("Code tool broker requires a reader and unique registered recipes");
|
|
4269
4329
|
}
|
|
4270
4330
|
for (const recipe2 of options.recipes) assertCodeBuildRecipe(recipe2);
|
|
4331
|
+
if (options.readOnlyPrefixes?.some((prefix) => !/^[A-Za-z0-9_.-]+$/.test(prefix) || prefix === "." || prefix === "..")) {
|
|
4332
|
+
throw new TypeError("Code tool broker read-only prefix is invalid");
|
|
4333
|
+
}
|
|
4271
4334
|
}
|
|
4272
4335
|
function exactKeys(input, allowed) {
|
|
4273
4336
|
if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
|
|
@@ -4375,7 +4438,8 @@ function createCodeRuntimeToolBroker(input, lease, role) {
|
|
|
4375
4438
|
recipes: input.recipes,
|
|
4376
4439
|
recipeExecutor: createContainerRecipeExecutor(input.engine),
|
|
4377
4440
|
recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
|
|
4378
|
-
readerId: `code-session:${lease.task.taskId}
|
|
4441
|
+
readerId: `code-session:${lease.task.taskId}`,
|
|
4442
|
+
readOnlyPrefixes: [".odla-references"]
|
|
4379
4443
|
});
|
|
4380
4444
|
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
4445
|
}
|
|
@@ -4453,6 +4517,14 @@ var CodePiRuntimeEngine = class {
|
|
|
4453
4517
|
resume
|
|
4454
4518
|
});
|
|
4455
4519
|
({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
|
|
4520
|
+
if (command.payload.sourceSet) {
|
|
4521
|
+
const selected = await this.options.control.source(command.sessionId);
|
|
4522
|
+
if (selected.repository !== metadata2.repository || selected.commitSha !== metadata2.baseCommitSha || selected.treeDigest !== metadata2.sourceTreeDigest) {
|
|
4523
|
+
await workspace.cleanup();
|
|
4524
|
+
throw new TypeError("Code local source does not match the selected GitHub primary source");
|
|
4525
|
+
}
|
|
4526
|
+
await attachCodeRuntimeReferences(workspace, selected.references ?? []);
|
|
4527
|
+
}
|
|
4456
4528
|
} else {
|
|
4457
4529
|
const source = await this.options.control.source(command.sessionId);
|
|
4458
4530
|
const materialized = await materializeCodeRuntimeSource(source);
|
|
@@ -4480,8 +4552,8 @@ var CodePiRuntimeEngine = class {
|
|
|
4480
4552
|
repository: metadata2.repository,
|
|
4481
4553
|
sourceTreeDigest: metadata2.sourceTreeDigest,
|
|
4482
4554
|
trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
|
|
4483
|
-
maxFiles:
|
|
4484
|
-
maxBytes:
|
|
4555
|
+
maxFiles: 2e4,
|
|
4556
|
+
maxBytes: 512 * 1024 * 1024
|
|
4485
4557
|
}),
|
|
4486
4558
|
planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
|
|
4487
4559
|
JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })
|