@odla-ai/cli 0.14.1 → 0.15.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 +22 -15
- package/dist/bin.cjs +665 -277
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-AN6KZMR5.js → chunk-E3CCYDIT.js} +670 -282
- package/dist/chunk-E3CCYDIT.js.map +1 -0
- package/dist/index.cjs +665 -277
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +53 -8
- package/dist/index.d.ts +53 -8
- 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-AN6KZMR5.js.map +0 -1
package/dist/bin.cjs
CHANGED
|
@@ -456,12 +456,13 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
456
456
|
const { token, expiresAt } = await (0, import_db2.requestToken)({
|
|
457
457
|
endpoint: audience,
|
|
458
458
|
email,
|
|
459
|
-
label: options.label ?? `odla CLI
|
|
459
|
+
label: options.label ?? `odla CLI (${scope})`,
|
|
460
460
|
scopes: [scope],
|
|
461
461
|
fetch: doFetch,
|
|
462
462
|
onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
|
|
463
463
|
const approvalUrl = verificationUriComplete ?? handshakeUrl(audience, userCode);
|
|
464
|
-
|
|
464
|
+
const requestName = scope === "app:code:host:connect" ? "terminal connection" : "scoped request";
|
|
465
|
+
out.log(`Review, then approve ${requestName} ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
|
|
465
466
|
if (approvalBrowser({ open: options.open }).open) {
|
|
466
467
|
await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
|
|
467
468
|
}
|
|
@@ -523,8 +524,8 @@ async function responseBody(response2) {
|
|
|
523
524
|
}
|
|
524
525
|
function apiError(status, body) {
|
|
525
526
|
const error = isRecord(body) && isRecord(body.error) ? body.error : void 0;
|
|
526
|
-
const
|
|
527
|
-
return `read System AI admin changes failed (${status}): ${
|
|
527
|
+
const message2 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
528
|
+
return `read System AI admin changes failed (${status}): ${message2}`;
|
|
528
529
|
}
|
|
529
530
|
function timestamp(value) {
|
|
530
531
|
if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
|
|
@@ -625,8 +626,8 @@ async function responseBody2(res) {
|
|
|
625
626
|
}
|
|
626
627
|
function apiError2(action, status, body) {
|
|
627
628
|
const error = isRecord2(body) && isRecord2(body.error) ? body.error : void 0;
|
|
628
|
-
const
|
|
629
|
-
return `${action} failed (${status}): ${
|
|
629
|
+
const message2 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
630
|
+
return `${action} failed (${status}): ${message2}`;
|
|
630
631
|
}
|
|
631
632
|
function isRecord2(value) {
|
|
632
633
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -806,8 +807,8 @@ async function responseBody3(res) {
|
|
|
806
807
|
}
|
|
807
808
|
function apiError3(action, status, body) {
|
|
808
809
|
const error = isRecord3(body) && isRecord3(body.error) ? body.error : void 0;
|
|
809
|
-
const
|
|
810
|
-
return `${action} failed (${status}): ${
|
|
810
|
+
const message2 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
811
|
+
return `${action} failed (${status}): ${message2}`;
|
|
811
812
|
}
|
|
812
813
|
function isRecord3(value) {
|
|
813
814
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -1487,8 +1488,8 @@ var CAPABILITIES = {
|
|
|
1487
1488
|
"connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"
|
|
1488
1489
|
]
|
|
1489
1490
|
};
|
|
1490
|
-
function printCapabilities(
|
|
1491
|
-
if (
|
|
1491
|
+
function printCapabilities(json = false, out = console) {
|
|
1492
|
+
if (json) {
|
|
1492
1493
|
out.log(JSON.stringify(CAPABILITIES, null, 2));
|
|
1493
1494
|
return;
|
|
1494
1495
|
}
|
|
@@ -1518,8 +1519,8 @@ var CalendarRequestError = class extends Error {
|
|
|
1518
1519
|
status;
|
|
1519
1520
|
/** Machine-readable `error.code` from the response body, when present. */
|
|
1520
1521
|
code;
|
|
1521
|
-
constructor(
|
|
1522
|
-
super(
|
|
1522
|
+
constructor(message2, status, code) {
|
|
1523
|
+
super(message2);
|
|
1523
1524
|
this.name = "CalendarRequestError";
|
|
1524
1525
|
this.status = status;
|
|
1525
1526
|
if (code !== void 0) this.code = code;
|
|
@@ -1686,8 +1687,8 @@ async function calendarJson(ctx, suffix, init) {
|
|
|
1686
1687
|
const body = await response2.json().catch(() => ({}));
|
|
1687
1688
|
if (!response2.ok) {
|
|
1688
1689
|
const code = textField(body.error?.code, 128);
|
|
1689
|
-
const
|
|
1690
|
-
const detail = `${code ? ` ${code}` : ""}${
|
|
1690
|
+
const message2 = textField(body.error?.message, 500);
|
|
1691
|
+
const detail = `${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`;
|
|
1691
1692
|
throw new CalendarRequestError(redactSecrets(`calendar request failed (${response2.status})${detail}`), response2.status, code);
|
|
1692
1693
|
}
|
|
1693
1694
|
return body;
|
|
@@ -1923,8 +1924,8 @@ function pollTimeout(value = 10 * 6e4) {
|
|
|
1923
1924
|
function productionConsent(env, yes, action) {
|
|
1924
1925
|
if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action} for "${env}" without --yes`);
|
|
1925
1926
|
}
|
|
1926
|
-
function printStatus(status,
|
|
1927
|
-
if (
|
|
1927
|
+
function printStatus(status, json, out) {
|
|
1928
|
+
if (json) {
|
|
1928
1929
|
out.log(JSON.stringify(status, null, 2));
|
|
1929
1930
|
return;
|
|
1930
1931
|
}
|
|
@@ -1966,63 +1967,63 @@ function parseAgentOutput(line) {
|
|
|
1966
1967
|
} catch {
|
|
1967
1968
|
throw new HarnessProtocolError("agent emitted invalid JSON");
|
|
1968
1969
|
}
|
|
1969
|
-
const
|
|
1970
|
-
if (!
|
|
1970
|
+
const message2 = record2(value);
|
|
1971
|
+
if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
|
|
1971
1972
|
throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
|
|
1972
1973
|
}
|
|
1973
|
-
if (
|
|
1974
|
+
if (message2.type === "event") {
|
|
1974
1975
|
return {
|
|
1975
1976
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
1976
1977
|
type: "event",
|
|
1977
|
-
kind: boundedText(
|
|
1978
|
-
...
|
|
1978
|
+
kind: boundedText(message2.kind, "event.kind", 120),
|
|
1979
|
+
...message2.payload === void 0 ? {} : { payload: message2.payload }
|
|
1979
1980
|
};
|
|
1980
1981
|
}
|
|
1981
|
-
if (
|
|
1982
|
-
const call = record2(
|
|
1982
|
+
if (message2.type === "inference.request") {
|
|
1983
|
+
const call = record2(message2.call);
|
|
1983
1984
|
if (!call || !Array.isArray(call.messages) || !Number.isSafeInteger(call.maxTokens)) {
|
|
1984
1985
|
throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
|
|
1985
1986
|
}
|
|
1986
1987
|
return {
|
|
1987
1988
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
1988
1989
|
type: "inference.request",
|
|
1989
|
-
requestId: boundedText(
|
|
1990
|
+
requestId: boundedText(message2.requestId, "requestId", 180),
|
|
1990
1991
|
call
|
|
1991
1992
|
};
|
|
1992
1993
|
}
|
|
1993
|
-
if (
|
|
1994
|
-
const input = record2(
|
|
1995
|
-
const tool = String(
|
|
1994
|
+
if (message2.type === "tool.request") {
|
|
1995
|
+
const input = record2(message2.input);
|
|
1996
|
+
const tool = String(message2.tool);
|
|
1996
1997
|
if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
|
|
1997
1998
|
throw new HarnessProtocolError("tool.request requires a registered tool and object input");
|
|
1998
1999
|
}
|
|
1999
2000
|
return {
|
|
2000
2001
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
2001
2002
|
type: "tool.request",
|
|
2002
|
-
requestId: boundedText(
|
|
2003
|
+
requestId: boundedText(message2.requestId, "requestId", 180),
|
|
2003
2004
|
tool,
|
|
2004
2005
|
input
|
|
2005
2006
|
};
|
|
2006
2007
|
}
|
|
2007
|
-
if (
|
|
2008
|
-
if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(
|
|
2008
|
+
if (message2.type === "attempt.complete") {
|
|
2009
|
+
if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(message2.status))) {
|
|
2009
2010
|
throw new HarnessProtocolError("attempt.complete.status is invalid");
|
|
2010
2011
|
}
|
|
2011
2012
|
return {
|
|
2012
2013
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
2013
2014
|
type: "attempt.complete",
|
|
2014
|
-
status:
|
|
2015
|
-
...
|
|
2015
|
+
status: message2.status,
|
|
2016
|
+
...message2.result === void 0 ? {} : { result: message2.result }
|
|
2016
2017
|
};
|
|
2017
2018
|
}
|
|
2018
2019
|
throw new HarnessProtocolError("agent message type is unsupported");
|
|
2019
2020
|
}
|
|
2020
|
-
function encodeAgentInput(
|
|
2021
|
-
return `${JSON.stringify(
|
|
2021
|
+
function encodeAgentInput(message2) {
|
|
2022
|
+
return `${JSON.stringify(message2)}
|
|
2022
2023
|
`;
|
|
2023
2024
|
}
|
|
2024
2025
|
|
|
2025
|
-
// ../harness/dist/chunk-
|
|
2026
|
+
// ../harness/dist/chunk-TA2FKK27.js
|
|
2026
2027
|
var import_child_process = require("child_process");
|
|
2027
2028
|
var import_fs = require("fs");
|
|
2028
2029
|
var import_promises2 = require("fs/promises");
|
|
@@ -2032,6 +2033,11 @@ var import_promises3 = require("fs/promises");
|
|
|
2032
2033
|
var import_os = require("os");
|
|
2033
2034
|
var import_path2 = require("path");
|
|
2034
2035
|
var import_child_process2 = require("child_process");
|
|
2036
|
+
var import_path3 = require("path");
|
|
2037
|
+
var import_promises4 = require("fs/promises");
|
|
2038
|
+
var import_os2 = require("os");
|
|
2039
|
+
var import_path4 = require("path");
|
|
2040
|
+
var import_child_process3 = require("child_process");
|
|
2035
2041
|
var DIGEST_IMAGE = /^[a-z0-9][a-z0-9._/-]*(?::[a-zA-Z0-9._-]+)?@sha256:[0-9a-f]{64}$/;
|
|
2036
2042
|
function assertPinnedImage(image) {
|
|
2037
2043
|
if (!DIGEST_IMAGE.test(image)) throw new TypeError("container image must be pinned by sha256 digest");
|
|
@@ -2075,7 +2081,7 @@ async function selectContainerEngine(requested = "auto", options = {}) {
|
|
|
2075
2081
|
throw new TypeError("no supported container engine found");
|
|
2076
2082
|
}
|
|
2077
2083
|
function inspectRootlessPodman() {
|
|
2078
|
-
return new Promise((
|
|
2084
|
+
return new Promise((resolve33, reject) => {
|
|
2079
2085
|
(0, import_child_process.execFile)(
|
|
2080
2086
|
"podman",
|
|
2081
2087
|
["info", "--format", "{{.Host.Security.Rootless}}"],
|
|
@@ -2085,7 +2091,7 @@ function inspectRootlessPodman() {
|
|
|
2085
2091
|
reject(new TypeError("could not verify that the active Podman service is rootless"));
|
|
2086
2092
|
return;
|
|
2087
2093
|
}
|
|
2088
|
-
|
|
2094
|
+
resolve33(stdout.trim() === "true");
|
|
2089
2095
|
}
|
|
2090
2096
|
);
|
|
2091
2097
|
});
|
|
@@ -2196,9 +2202,9 @@ async function runContainerAttempt(options) {
|
|
|
2196
2202
|
if (bytes.byteLength > 1e6) throw new Error("agent message exceeds 1 MB");
|
|
2197
2203
|
const line = bytes.toString("utf8");
|
|
2198
2204
|
if (!line.trim()) return;
|
|
2199
|
-
const
|
|
2200
|
-
if (
|
|
2201
|
-
const response2 = await options.onMessage(
|
|
2205
|
+
const message2 = parseAgentOutput(line);
|
|
2206
|
+
if (message2.type === "attempt.complete") complete = message2;
|
|
2207
|
+
const response2 = await options.onMessage(message2);
|
|
2202
2208
|
if (response2 && !child.stdin.destroyed) child.stdin.write(encodeAgentInput(response2));
|
|
2203
2209
|
};
|
|
2204
2210
|
try {
|
|
@@ -2246,29 +2252,142 @@ async function runContainerAttempt(options) {
|
|
|
2246
2252
|
options.signal?.removeEventListener("abort", abort);
|
|
2247
2253
|
}
|
|
2248
2254
|
}
|
|
2249
|
-
var
|
|
2250
|
-
|
|
2255
|
+
var SKIP_WORKSPACE_DIRS = /* @__PURE__ */ new Set([
|
|
2256
|
+
".git",
|
|
2257
|
+
".odla",
|
|
2258
|
+
".wrangler",
|
|
2259
|
+
"node_modules",
|
|
2260
|
+
"dist",
|
|
2261
|
+
"coverage"
|
|
2262
|
+
]);
|
|
2263
|
+
var SECRET_WORKSPACE_FILE = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
2264
|
+
function allowedWorkspacePath(relativePath) {
|
|
2265
|
+
const parts = relativePath.split("/");
|
|
2266
|
+
return !(0, import_path3.isAbsolute)(relativePath) && !relativePath.includes("\\") && !relativePath.includes("\0") && !parts.some((part) => !part || part === "." || part === ".." || SKIP_WORKSPACE_DIRS.has(part)) && !SECRET_WORKSPACE_FILE.test(parts.at(-1) ?? "");
|
|
2267
|
+
}
|
|
2268
|
+
async function gitOutput(cwd, args, maxBytes) {
|
|
2269
|
+
const child = (0, import_child_process2.spawn)("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: false });
|
|
2270
|
+
const stdout = [];
|
|
2271
|
+
const stderr = [];
|
|
2272
|
+
let bytes = 0;
|
|
2273
|
+
child.stdout.on("data", (chunk) => {
|
|
2274
|
+
bytes += chunk.byteLength;
|
|
2275
|
+
if (bytes > maxBytes) child.kill("SIGKILL");
|
|
2276
|
+
else stdout.push(chunk);
|
|
2277
|
+
});
|
|
2278
|
+
child.stderr.on("data", (chunk) => {
|
|
2279
|
+
if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
|
|
2280
|
+
});
|
|
2281
|
+
const code = await new Promise((accept, reject) => {
|
|
2282
|
+
child.once("error", reject);
|
|
2283
|
+
child.once("exit", accept);
|
|
2284
|
+
});
|
|
2285
|
+
if (bytes > maxBytes) throw new Error(`git output exceeds ${maxBytes} bytes`);
|
|
2286
|
+
if (code !== 0) throw new Error(`git command failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
|
|
2287
|
+
return Buffer.concat(stdout);
|
|
2288
|
+
}
|
|
2289
|
+
async function gitBlobs(cwd, entries, maxBytes) {
|
|
2290
|
+
const child = (0, import_child_process2.spawn)("git", ["cat-file", "--batch"], { cwd, stdio: ["pipe", "pipe", "pipe"], shell: false });
|
|
2291
|
+
const stdout = [];
|
|
2292
|
+
const stderr = [];
|
|
2293
|
+
let bytes = 0;
|
|
2294
|
+
child.stdout.on("data", (chunk) => {
|
|
2295
|
+
bytes += chunk.byteLength;
|
|
2296
|
+
if (bytes > maxBytes + entries.length * 100) child.kill("SIGKILL");
|
|
2297
|
+
else stdout.push(chunk);
|
|
2298
|
+
});
|
|
2299
|
+
child.stderr.on("data", (chunk) => {
|
|
2300
|
+
if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
|
|
2301
|
+
});
|
|
2302
|
+
child.stdin.end(`${entries.map((entry) => entry.hash).join("\n")}
|
|
2303
|
+
`);
|
|
2304
|
+
const code = await new Promise((accept, reject) => {
|
|
2305
|
+
child.once("error", reject);
|
|
2306
|
+
child.once("exit", accept);
|
|
2307
|
+
});
|
|
2308
|
+
if (bytes > maxBytes + entries.length * 100) throw new Error(`Git tree exceeds ${maxBytes} bytes`);
|
|
2309
|
+
if (code !== 0) throw new Error(`git object read failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
|
|
2310
|
+
const output = Buffer.concat(stdout);
|
|
2311
|
+
const blobs = [];
|
|
2312
|
+
let offset = 0;
|
|
2313
|
+
let total = 0;
|
|
2314
|
+
for (const entry of entries) {
|
|
2315
|
+
const newline = output.indexOf(10, offset);
|
|
2316
|
+
if (newline < 0) throw new Error("git object output is truncated");
|
|
2317
|
+
const match = /^([0-9a-f]{40,64}) blob ([0-9]+)$/.exec(output.subarray(offset, newline).toString("utf8"));
|
|
2318
|
+
if (!match || match[1] !== entry.hash) throw new Error("git object output does not match its inventory");
|
|
2319
|
+
const length = Number(match[2]);
|
|
2320
|
+
if (!Number.isSafeInteger(length) || length < 0 || newline + 1 + length >= output.length) {
|
|
2321
|
+
throw new Error("git object output has an invalid length");
|
|
2322
|
+
}
|
|
2323
|
+
const content = output.subarray(newline + 1, newline + 1 + length);
|
|
2324
|
+
if (output[newline + 1 + length] !== 10) throw new Error("git object output is malformed");
|
|
2325
|
+
total += content.byteLength;
|
|
2326
|
+
if (total > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
|
|
2327
|
+
blobs.push(content);
|
|
2328
|
+
offset = newline + 1 + length + 1;
|
|
2329
|
+
}
|
|
2330
|
+
return blobs;
|
|
2331
|
+
}
|
|
2332
|
+
async function materializeGitTree(source, commitSha, options = {}) {
|
|
2333
|
+
if (!/^[0-9a-f]{40}$/.test(commitSha)) throw new TypeError("Git tree requires an exact commit SHA");
|
|
2334
|
+
const sourceDir = await (0, import_promises3.realpath)((0, import_path2.resolve)(source));
|
|
2335
|
+
const maxFiles = options.maxFiles ?? 2e4;
|
|
2336
|
+
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
2337
|
+
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
2338
|
+
const entries = inventory.flatMap((record5) => {
|
|
2339
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record5);
|
|
2340
|
+
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
2341
|
+
});
|
|
2342
|
+
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
2343
|
+
const root = await (0, import_promises3.mkdtemp)((0, import_path2.join)(options.tempRoot ?? (0, import_os.tmpdir)(), "odla-git-tree-"));
|
|
2344
|
+
const targetRoot = (0, import_path2.join)(root, "source");
|
|
2345
|
+
await (0, import_promises3.mkdir)(targetRoot);
|
|
2346
|
+
let byteCount = 0;
|
|
2347
|
+
try {
|
|
2348
|
+
const blobs = await gitBlobs(sourceDir, entries, maxBytes);
|
|
2349
|
+
for (const [index, entry] of entries.entries()) {
|
|
2350
|
+
const content = blobs[index];
|
|
2351
|
+
byteCount += content.byteLength;
|
|
2352
|
+
if (byteCount > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
|
|
2353
|
+
const target = (0, import_path2.resolve)(targetRoot, entry.path);
|
|
2354
|
+
if (!target.startsWith(`${(0, import_path2.resolve)(targetRoot)}${import_path2.sep}`)) throw new TypeError("Git tree path escapes workspace");
|
|
2355
|
+
await (0, import_promises3.mkdir)((0, import_path2.resolve)(target, ".."), { recursive: true });
|
|
2356
|
+
await (0, import_promises3.writeFile)(target, content, { flag: "wx", mode: entry.mode === "100755" ? 493 : 420 });
|
|
2357
|
+
}
|
|
2358
|
+
return {
|
|
2359
|
+
root,
|
|
2360
|
+
sourceDir: targetRoot,
|
|
2361
|
+
fileCount: entries.length,
|
|
2362
|
+
byteCount,
|
|
2363
|
+
cleanup: () => (0, import_promises3.rm)(root, { recursive: true, force: true })
|
|
2364
|
+
};
|
|
2365
|
+
} catch (error) {
|
|
2366
|
+
await (0, import_promises3.rm)(root, { recursive: true, force: true });
|
|
2367
|
+
throw error;
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2251
2370
|
async function sourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
2252
2371
|
const files = [];
|
|
2253
2372
|
let bytes = 0;
|
|
2254
2373
|
const walk = async (dir) => {
|
|
2255
|
-
for (const entry of await (0,
|
|
2256
|
-
if (entry.isDirectory() &&
|
|
2257
|
-
if (!entry.isDirectory() &&
|
|
2258
|
-
const path = (0,
|
|
2374
|
+
for (const entry of await (0, import_promises4.readdir)(dir, { withFileTypes: true })) {
|
|
2375
|
+
if (entry.isDirectory() && SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
2376
|
+
if (!entry.isDirectory() && SECRET_WORKSPACE_FILE.test(entry.name)) continue;
|
|
2377
|
+
const path = (0, import_path4.join)(dir, entry.name);
|
|
2259
2378
|
if (entry.isSymbolicLink()) continue;
|
|
2260
2379
|
if (entry.isDirectory()) {
|
|
2261
2380
|
await walk(path);
|
|
2262
2381
|
continue;
|
|
2263
2382
|
}
|
|
2264
2383
|
if (!entry.isFile()) continue;
|
|
2265
|
-
const metadata2 = await (0,
|
|
2384
|
+
const metadata2 = await (0, import_promises4.stat)(path);
|
|
2266
2385
|
bytes += metadata2.size;
|
|
2267
2386
|
if (files.length + 1 > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
2268
2387
|
if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
|
|
2269
2388
|
files.push({
|
|
2270
2389
|
source: path,
|
|
2271
|
-
relativePath: (0,
|
|
2390
|
+
relativePath: (0, import_path4.relative)(sourceDir, path),
|
|
2272
2391
|
mode: metadata2.mode & 511,
|
|
2273
2392
|
bytes: metadata2.size
|
|
2274
2393
|
});
|
|
@@ -2277,16 +2396,62 @@ async function sourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
2277
2396
|
await walk(sourceDir);
|
|
2278
2397
|
return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
2279
2398
|
}
|
|
2399
|
+
async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
2400
|
+
const child = (0, import_child_process3.spawn)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
2401
|
+
cwd: sourceDir,
|
|
2402
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2403
|
+
shell: false
|
|
2404
|
+
});
|
|
2405
|
+
const stdout = [];
|
|
2406
|
+
const stderr = [];
|
|
2407
|
+
let outputBytes = 0;
|
|
2408
|
+
child.stdout.on("data", (chunk) => {
|
|
2409
|
+
outputBytes += chunk.byteLength;
|
|
2410
|
+
if (outputBytes > 8 * 1024 * 1024) child.kill("SIGKILL");
|
|
2411
|
+
else stdout.push(chunk);
|
|
2412
|
+
});
|
|
2413
|
+
child.stderr.on("data", (chunk) => {
|
|
2414
|
+
if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
|
|
2415
|
+
});
|
|
2416
|
+
const code = await new Promise((accept, reject) => {
|
|
2417
|
+
child.once("error", reject);
|
|
2418
|
+
child.once("exit", accept);
|
|
2419
|
+
});
|
|
2420
|
+
if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
|
|
2421
|
+
if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
|
|
2422
|
+
const paths = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
|
|
2423
|
+
if (paths.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
2424
|
+
const root = (0, import_path4.resolve)(sourceDir);
|
|
2425
|
+
const files = [];
|
|
2426
|
+
let bytes = 0;
|
|
2427
|
+
for (const relativePath of paths) {
|
|
2428
|
+
if (!allowedWorkspacePath(relativePath)) continue;
|
|
2429
|
+
const source = (0, import_path4.resolve)(root, relativePath);
|
|
2430
|
+
if (!source.startsWith(`${root}${import_path4.sep}`)) throw new TypeError("git file path escapes workspace");
|
|
2431
|
+
let metadata2;
|
|
2432
|
+
try {
|
|
2433
|
+
metadata2 = await (0, import_promises4.lstat)(source);
|
|
2434
|
+
} catch (error) {
|
|
2435
|
+
if (error.code === "ENOENT") continue;
|
|
2436
|
+
throw error;
|
|
2437
|
+
}
|
|
2438
|
+
if (metadata2.isSymbolicLink() || !metadata2.isFile()) continue;
|
|
2439
|
+
bytes += metadata2.size;
|
|
2440
|
+
if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
|
|
2441
|
+
files.push({ source, relativePath, mode: metadata2.mode & 511, bytes: metadata2.size });
|
|
2442
|
+
}
|
|
2443
|
+
return files;
|
|
2444
|
+
}
|
|
2280
2445
|
async function copyTree(files, destination) {
|
|
2281
2446
|
for (const file of files) {
|
|
2282
|
-
const target = (0,
|
|
2283
|
-
await (0,
|
|
2284
|
-
await (0,
|
|
2285
|
-
await (0,
|
|
2447
|
+
const target = (0, import_path4.join)(destination, file.relativePath);
|
|
2448
|
+
await (0, import_promises4.mkdir)((0, import_path4.resolve)(target, ".."), { recursive: true });
|
|
2449
|
+
await (0, import_promises4.copyFile)(file.source, target);
|
|
2450
|
+
await (0, import_promises4.chmod)(target, file.mode);
|
|
2286
2451
|
}
|
|
2287
2452
|
}
|
|
2288
2453
|
async function captureGitDiff(root, maxBytes) {
|
|
2289
|
-
const child = (0,
|
|
2454
|
+
const child = (0, import_child_process3.spawn)("git", [
|
|
2290
2455
|
"diff",
|
|
2291
2456
|
"--no-index",
|
|
2292
2457
|
"--binary",
|
|
@@ -2319,15 +2484,17 @@ async function captureGitDiff(root, maxBytes) {
|
|
|
2319
2484
|
return Buffer.concat(stdout).toString("utf8").replaceAll("a/baseline/", "a/").replaceAll("b/workspace/", "b/").replaceAll("--- a/baseline", "--- a").replaceAll("+++ b/workspace", "+++ b");
|
|
2320
2485
|
}
|
|
2321
2486
|
async function stageWorkspace(source, options = {}) {
|
|
2322
|
-
const sourceDir = await (0,
|
|
2323
|
-
const sourceStat = await (0,
|
|
2487
|
+
const sourceDir = await (0, import_promises4.realpath)((0, import_path4.resolve)(source));
|
|
2488
|
+
const sourceStat = await (0, import_promises4.stat)(sourceDir);
|
|
2324
2489
|
if (!sourceStat.isDirectory()) throw new TypeError("workspace source must be a directory");
|
|
2325
|
-
const root = await (0,
|
|
2326
|
-
const baselineDir = (0,
|
|
2327
|
-
const workspaceDir = (0,
|
|
2328
|
-
await Promise.all([(0,
|
|
2490
|
+
const root = await (0, import_promises4.mkdtemp)((0, import_path4.join)(options.tempRoot ?? (0, import_os2.tmpdir)(), "odla-harness-"));
|
|
2491
|
+
const baselineDir = (0, import_path4.join)(root, "baseline");
|
|
2492
|
+
const workspaceDir = (0, import_path4.join)(root, "workspace");
|
|
2493
|
+
await Promise.all([(0, import_promises4.mkdir)(baselineDir), (0, import_promises4.mkdir)(workspaceDir)]);
|
|
2329
2494
|
try {
|
|
2330
|
-
const
|
|
2495
|
+
const maxFiles = options.maxFiles ?? 2e4;
|
|
2496
|
+
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
2497
|
+
const files = options.gitTrackedAndUnignored ? await gitSourceFiles(sourceDir, maxFiles, maxBytes) : await sourceFiles(sourceDir, maxFiles, maxBytes);
|
|
2331
2498
|
await Promise.all([copyTree(files, baselineDir), copyTree(files, workspaceDir)]);
|
|
2332
2499
|
return {
|
|
2333
2500
|
root,
|
|
@@ -2335,14 +2502,48 @@ async function stageWorkspace(source, options = {}) {
|
|
|
2335
2502
|
workspaceDir,
|
|
2336
2503
|
fileCount: files.length,
|
|
2337
2504
|
byteCount: files.reduce((sum, file) => sum + file.bytes, 0),
|
|
2338
|
-
patch: (
|
|
2339
|
-
cleanup: () => (0,
|
|
2505
|
+
patch: (maxBytes2) => captureGitDiff(root, maxBytes2),
|
|
2506
|
+
cleanup: () => (0, import_promises4.rm)(root, { recursive: true, force: true })
|
|
2340
2507
|
};
|
|
2341
2508
|
} catch (error) {
|
|
2342
|
-
await (0,
|
|
2509
|
+
await (0, import_promises4.rm)(root, { recursive: true, force: true });
|
|
2343
2510
|
throw error;
|
|
2344
2511
|
}
|
|
2345
2512
|
}
|
|
2513
|
+
async function stageWorkspacePair(baselineSource, workspaceSource, options = {}) {
|
|
2514
|
+
const baselineDirSource = await (0, import_promises4.realpath)((0, import_path4.resolve)(baselineSource));
|
|
2515
|
+
const workspaceDirSource = await (0, import_promises4.realpath)((0, import_path4.resolve)(workspaceSource));
|
|
2516
|
+
const maxFiles = options.maxFiles ?? 2e4;
|
|
2517
|
+
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
2518
|
+
const [baselineFiles, workspaceFiles] = await Promise.all([
|
|
2519
|
+
sourceFiles(baselineDirSource, maxFiles, maxBytes),
|
|
2520
|
+
sourceFiles(workspaceDirSource, maxFiles, maxBytes)
|
|
2521
|
+
]);
|
|
2522
|
+
const root = await (0, import_promises4.mkdtemp)((0, import_path4.join)(options.tempRoot ?? (0, import_os2.tmpdir)(), "odla-harness-"));
|
|
2523
|
+
const baselineDir = (0, import_path4.join)(root, "baseline");
|
|
2524
|
+
const workspaceDir = (0, import_path4.join)(root, "workspace");
|
|
2525
|
+
await Promise.all([(0, import_promises4.mkdir)(baselineDir), (0, import_promises4.mkdir)(workspaceDir)]);
|
|
2526
|
+
try {
|
|
2527
|
+
await Promise.all([copyTree(baselineFiles, baselineDir), copyTree(workspaceFiles, workspaceDir)]);
|
|
2528
|
+
return {
|
|
2529
|
+
root,
|
|
2530
|
+
baselineDir,
|
|
2531
|
+
workspaceDir,
|
|
2532
|
+
fileCount: workspaceFiles.length,
|
|
2533
|
+
byteCount: workspaceFiles.reduce((sum, file) => sum + file.bytes, 0),
|
|
2534
|
+
patch: (maxPatchBytes) => captureGitDiff(root, maxPatchBytes),
|
|
2535
|
+
cleanup: () => (0, import_promises4.rm)(root, { recursive: true, force: true })
|
|
2536
|
+
};
|
|
2537
|
+
} catch (error) {
|
|
2538
|
+
await (0, import_promises4.rm)(root, { recursive: true, force: true });
|
|
2539
|
+
throw error;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
// ../harness/dist/chunk-QEW6NNAO.js
|
|
2544
|
+
var import_crypto = require("crypto");
|
|
2545
|
+
var import_promises5 = require("fs/promises");
|
|
2546
|
+
var import_path5 = require("path");
|
|
2346
2547
|
|
|
2347
2548
|
// ../camel/dist/chunk-7FHPOQVP.js
|
|
2348
2549
|
var CamelError = class extends Error {
|
|
@@ -2362,8 +2563,8 @@ function canonicalJson(value) {
|
|
|
2362
2563
|
}
|
|
2363
2564
|
async function sha256Hex(value) {
|
|
2364
2565
|
const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
|
|
2365
|
-
const
|
|
2366
|
-
return [...new Uint8Array(
|
|
2566
|
+
const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
|
|
2567
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2367
2568
|
}
|
|
2368
2569
|
function utf8Length(value) {
|
|
2369
2570
|
if (typeof value === "string") return new TextEncoder().encode(value).byteLength;
|
|
@@ -2640,8 +2841,8 @@ function freeze(value) {
|
|
|
2640
2841
|
})
|
|
2641
2842
|
});
|
|
2642
2843
|
}
|
|
2643
|
-
function invalid2(
|
|
2644
|
-
return new CamelError("state_conflict",
|
|
2844
|
+
function invalid2(message2) {
|
|
2845
|
+
return new CamelError("state_conflict", message2);
|
|
2645
2846
|
}
|
|
2646
2847
|
var SHA4 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
2647
2848
|
var REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
@@ -2676,25 +2877,22 @@ function validateSnapshot(snapshot, limits) {
|
|
|
2676
2877
|
}
|
|
2677
2878
|
}
|
|
2678
2879
|
|
|
2679
|
-
// ../harness/dist/chunk-
|
|
2680
|
-
var import_child_process3 = require("child_process");
|
|
2681
|
-
var import_promises4 = require("fs/promises");
|
|
2682
|
-
var import_path3 = require("path");
|
|
2880
|
+
// ../harness/dist/chunk-QEW6NNAO.js
|
|
2683
2881
|
var import_child_process4 = require("child_process");
|
|
2882
|
+
var import_promises6 = require("fs/promises");
|
|
2883
|
+
var import_path6 = require("path");
|
|
2884
|
+
var import_child_process5 = require("child_process");
|
|
2684
2885
|
var import_process2 = require("process");
|
|
2685
|
-
var import_crypto = require("crypto");
|
|
2686
2886
|
var import_crypto2 = require("crypto");
|
|
2687
|
-
var import_fs2 = require("fs");
|
|
2688
|
-
var import_promises5 = require("fs/promises");
|
|
2689
|
-
var import_path4 = require("path");
|
|
2690
2887
|
var import_crypto3 = require("crypto");
|
|
2691
|
-
var
|
|
2692
|
-
var import_path5 = require("path");
|
|
2888
|
+
var import_fs2 = require("fs");
|
|
2693
2889
|
var import_promises7 = require("fs/promises");
|
|
2694
|
-
var import_os2 = require("os");
|
|
2695
|
-
var import_path6 = require("path");
|
|
2696
|
-
var import_promises8 = require("fs/promises");
|
|
2697
2890
|
var import_path7 = require("path");
|
|
2891
|
+
var import_promises8 = require("fs/promises");
|
|
2892
|
+
var import_os3 = require("os");
|
|
2893
|
+
var import_path8 = require("path");
|
|
2894
|
+
var import_promises9 = require("fs/promises");
|
|
2895
|
+
var import_path9 = require("path");
|
|
2698
2896
|
|
|
2699
2897
|
// ../camel/dist/chunk-LAXU2AVK.js
|
|
2700
2898
|
function conversionPolicyDigest(policy) {
|
|
@@ -2972,8 +3170,34 @@ function looksLikeDestination(value) {
|
|
|
2972
3170
|
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
|
|
2973
3171
|
}
|
|
2974
3172
|
|
|
2975
|
-
// ../harness/dist/chunk-
|
|
3173
|
+
// ../harness/dist/chunk-QEW6NNAO.js
|
|
2976
3174
|
var import_crypto4 = require("crypto");
|
|
3175
|
+
async function digestStagedWorkspace(root, limits) {
|
|
3176
|
+
const files = [];
|
|
3177
|
+
const walk = async (directory) => {
|
|
3178
|
+
const entries = await (0, import_promises5.readdir)(directory, { withFileTypes: true });
|
|
3179
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
3180
|
+
if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
|
|
3181
|
+
const target = (0, import_path5.resolve)(directory, entry.name);
|
|
3182
|
+
if (entry.isDirectory()) await walk(target);
|
|
3183
|
+
else if (entry.isFile()) {
|
|
3184
|
+
files.push({ path: (0, import_path5.relative)(root, target).split("\\").join("/"), target });
|
|
3185
|
+
if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
};
|
|
3189
|
+
await walk((0, import_path5.resolve)(root));
|
|
3190
|
+
const hash = (0, import_crypto.createHash)("sha256");
|
|
3191
|
+
let bytes = 0;
|
|
3192
|
+
for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
|
|
3193
|
+
const content = await (0, import_promises5.readFile)(file.target);
|
|
3194
|
+
bytes += Buffer.byteLength(file.path) + content.byteLength;
|
|
3195
|
+
if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
|
|
3196
|
+
hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
|
|
3197
|
+
hash.update(content);
|
|
3198
|
+
}
|
|
3199
|
+
return `sha256:${hash.digest("hex")}`;
|
|
3200
|
+
}
|
|
2977
3201
|
var CODE_RUNTIME_PROTOCOL_VERSION = 1;
|
|
2978
3202
|
async function runCodeRuntimeHeartbeatLoop(options) {
|
|
2979
3203
|
const heartbeatMs = options.heartbeatMs ?? 15e3;
|
|
@@ -3045,8 +3269,8 @@ function wait(ms, signal) {
|
|
|
3045
3269
|
});
|
|
3046
3270
|
}
|
|
3047
3271
|
var CodeRuntimeControlError = class extends Error {
|
|
3048
|
-
constructor(
|
|
3049
|
-
super(
|
|
3272
|
+
constructor(message2, status, code = "control_error") {
|
|
3273
|
+
super(message2);
|
|
3050
3274
|
this.status = status;
|
|
3051
3275
|
this.code = code;
|
|
3052
3276
|
}
|
|
@@ -3125,9 +3349,9 @@ function createCodeRuntimeControlClient(options) {
|
|
|
3125
3349
|
}
|
|
3126
3350
|
await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
|
|
3127
3351
|
},
|
|
3128
|
-
reportSessionFailure: async (sessionId,
|
|
3129
|
-
if (!
|
|
3130
|
-
await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message:
|
|
3352
|
+
reportSessionFailure: async (sessionId, message2) => {
|
|
3353
|
+
if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
|
|
3354
|
+
await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
|
|
3131
3355
|
}
|
|
3132
3356
|
};
|
|
3133
3357
|
}
|
|
@@ -3195,9 +3419,9 @@ async function parseSource(value) {
|
|
|
3195
3419
|
return { path: file.path, content: file.content };
|
|
3196
3420
|
});
|
|
3197
3421
|
const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
|
|
3198
|
-
const
|
|
3199
|
-
if (
|
|
3200
|
-
return { ...source, treeDigest:
|
|
3422
|
+
const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
|
|
3423
|
+
if (digest !== snapshot.treeDigest) throw invalid("source digest");
|
|
3424
|
+
return { ...source, treeDigest: digest };
|
|
3201
3425
|
}
|
|
3202
3426
|
function parseReview(value) {
|
|
3203
3427
|
const review = record3(record3(value)?.review);
|
|
@@ -3256,9 +3480,9 @@ function validateRelativePath(path) {
|
|
|
3256
3480
|
}
|
|
3257
3481
|
function resolveCodePath(workspaceDir, path) {
|
|
3258
3482
|
validateRelativePath(path);
|
|
3259
|
-
const root = (0,
|
|
3260
|
-
const target = (0,
|
|
3261
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
3483
|
+
const root = (0, import_path6.resolve)(workspaceDir);
|
|
3484
|
+
const target = (0, import_path6.resolve)(root, path);
|
|
3485
|
+
if (target !== root && !target.startsWith(`${root}${import_path6.sep}`)) throw new TypeError("path escapes the staged workspace");
|
|
3262
3486
|
return target;
|
|
3263
3487
|
}
|
|
3264
3488
|
async function applyCodePatch(workspaceDir, patch2, paths) {
|
|
@@ -3266,7 +3490,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
|
|
|
3266
3490
|
await gitApply(workspaceDir, patch2, false);
|
|
3267
3491
|
for (const path of paths) {
|
|
3268
3492
|
try {
|
|
3269
|
-
const info = await (0,
|
|
3493
|
+
const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
|
|
3270
3494
|
if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
|
|
3271
3495
|
throw new TypeError("patch created a non-regular workspace entry");
|
|
3272
3496
|
}
|
|
@@ -3278,7 +3502,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
|
|
|
3278
3502
|
function gitApply(cwd, patch2, check) {
|
|
3279
3503
|
return new Promise((accept, reject) => {
|
|
3280
3504
|
const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
|
|
3281
|
-
const child = (0,
|
|
3505
|
+
const child = (0, import_child_process4.spawn)("git", args, {
|
|
3282
3506
|
cwd,
|
|
3283
3507
|
shell: false,
|
|
3284
3508
|
stdio: ["pipe", "ignore", "pipe"],
|
|
@@ -3322,7 +3546,7 @@ async function restoreCodeWorkspaceCheckpoint(input) {
|
|
|
3322
3546
|
}
|
|
3323
3547
|
var ARTIFACT_PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
|
|
3324
3548
|
var PRIVATE_ARTIFACT_PART = /^(?:\.git|\.odla|\.wrangler|\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json)$/i;
|
|
3325
|
-
function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0,
|
|
3549
|
+
function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0, import_crypto2.randomUUID)().slice(0, 12)}`) {
|
|
3326
3550
|
assertCodeBuildRecipe(recipe2);
|
|
3327
3551
|
if (/[,\r\n]/.test(workspaceDir)) throw new TypeError("workspace path contains unsupported mount characters");
|
|
3328
3552
|
const uid = typeof import_process2.getuid === "function" ? (0, import_process2.getuid)() : 1e3;
|
|
@@ -3377,7 +3601,7 @@ function createContainerRecipeExecutor(engine) {
|
|
|
3377
3601
|
return {
|
|
3378
3602
|
async run(input) {
|
|
3379
3603
|
await verifyContainerEngineBoundary(engine);
|
|
3380
|
-
const name = `odla-recipe-${(0,
|
|
3604
|
+
const name = `odla-recipe-${(0, import_crypto2.randomUUID)().slice(0, 12)}`;
|
|
3381
3605
|
const args = buildRecipeContainerArgs(engine, input.workspaceDir, input.recipe, name);
|
|
3382
3606
|
return execute(engine, args, name, input.recipe, input.signal);
|
|
3383
3607
|
}
|
|
@@ -3401,7 +3625,7 @@ function parseMemory(value) {
|
|
|
3401
3625
|
function execute(engine, args, name, recipe2, signal) {
|
|
3402
3626
|
return new Promise((accept, reject) => {
|
|
3403
3627
|
const started = Date.now();
|
|
3404
|
-
const child = (0,
|
|
3628
|
+
const child = (0, import_child_process5.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
3405
3629
|
const stdout = [];
|
|
3406
3630
|
const stderr = [];
|
|
3407
3631
|
let bytes = 0;
|
|
@@ -3414,7 +3638,7 @@ function execute(engine, args, name, recipe2, signal) {
|
|
|
3414
3638
|
timedOut = reason === "timeout";
|
|
3415
3639
|
outputLimitExceeded = reason === "output";
|
|
3416
3640
|
const remove = engine === "container" ? ["delete", "--force", name] : ["rm", "-f", name];
|
|
3417
|
-
const killer = (0,
|
|
3641
|
+
const killer = (0, import_child_process5.spawn)(engine, remove, { shell: false, stdio: "ignore" });
|
|
3418
3642
|
killer.unref();
|
|
3419
3643
|
child.kill("SIGTERM");
|
|
3420
3644
|
};
|
|
@@ -3448,32 +3672,6 @@ function execute(engine, args, name, recipe2, signal) {
|
|
|
3448
3672
|
});
|
|
3449
3673
|
});
|
|
3450
3674
|
}
|
|
3451
|
-
async function digestStagedWorkspace(root, limits) {
|
|
3452
|
-
const files = [];
|
|
3453
|
-
const walk = async (directory) => {
|
|
3454
|
-
const entries = await (0, import_promises6.readdir)(directory, { withFileTypes: true });
|
|
3455
|
-
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
3456
|
-
if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
|
|
3457
|
-
const target = (0, import_path5.resolve)(directory, entry.name);
|
|
3458
|
-
if (entry.isDirectory()) await walk(target);
|
|
3459
|
-
else if (entry.isFile()) {
|
|
3460
|
-
files.push({ path: (0, import_path5.relative)(root, target).split("\\").join("/"), target });
|
|
3461
|
-
if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
|
|
3462
|
-
}
|
|
3463
|
-
}
|
|
3464
|
-
};
|
|
3465
|
-
await walk((0, import_path5.resolve)(root));
|
|
3466
|
-
const hash = (0, import_crypto3.createHash)("sha256");
|
|
3467
|
-
let bytes = 0;
|
|
3468
|
-
for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
|
|
3469
|
-
const content = await (0, import_promises6.readFile)(file.target);
|
|
3470
|
-
bytes += Buffer.byteLength(file.path) + content.byteLength;
|
|
3471
|
-
if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
|
|
3472
|
-
hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
|
|
3473
|
-
hash.update(content);
|
|
3474
|
-
}
|
|
3475
|
-
return `sha256:${hash.digest("hex")}`;
|
|
3476
|
-
}
|
|
3477
3675
|
var SHA3 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
3478
3676
|
var DIGEST3 = /^sha256:[0-9a-f]{64}$/;
|
|
3479
3677
|
var ID3 = /^[A-Za-z0-9._:-]{1,160}$/;
|
|
@@ -3521,7 +3719,7 @@ async function verifyCodeCandidate(input) {
|
|
|
3521
3719
|
}
|
|
3522
3720
|
const fields = {
|
|
3523
3721
|
schemaVersion: 1,
|
|
3524
|
-
verificationId: input.verificationId ?? `verify-${(0,
|
|
3722
|
+
verificationId: input.verificationId ?? `verify-${(0, import_crypto3.randomUUID)()}`,
|
|
3525
3723
|
trustedBaseCommitSha: input.trustedBaseCommitSha,
|
|
3526
3724
|
trustedBaseDigest: input.trustedBaseDigest,
|
|
3527
3725
|
patchDigest,
|
|
@@ -3582,8 +3780,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
|
|
|
3582
3780
|
const receipts = [];
|
|
3583
3781
|
for (const artifact of recipe2.expectedArtifacts ?? []) {
|
|
3584
3782
|
try {
|
|
3585
|
-
const path = (0,
|
|
3586
|
-
const info = await (0,
|
|
3783
|
+
const path = (0, import_path7.join)(workspaceDir, artifact.path);
|
|
3784
|
+
const info = await (0, import_promises7.lstat)(path);
|
|
3587
3785
|
if (!info.isFile() || info.isSymbolicLink()) {
|
|
3588
3786
|
receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
|
|
3589
3787
|
} else if (info.size > artifact.maximumBytes) {
|
|
@@ -3600,7 +3798,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
|
|
|
3600
3798
|
}
|
|
3601
3799
|
function hashFile(path) {
|
|
3602
3800
|
return new Promise((accept, reject) => {
|
|
3603
|
-
const hash = (0,
|
|
3801
|
+
const hash = (0, import_crypto3.createHash)("sha256");
|
|
3604
3802
|
const stream = (0, import_fs2.createReadStream)(path);
|
|
3605
3803
|
stream.on("data", (chunk) => {
|
|
3606
3804
|
hash.update(chunk);
|
|
@@ -3658,7 +3856,7 @@ function digestJson(value) {
|
|
|
3658
3856
|
return digestBytes(JSON.stringify(value));
|
|
3659
3857
|
}
|
|
3660
3858
|
function digestBytes(value) {
|
|
3661
|
-
return `sha256:${(0,
|
|
3859
|
+
return `sha256:${(0, import_crypto3.createHash)("sha256").update(value).digest("hex")}`;
|
|
3662
3860
|
}
|
|
3663
3861
|
function integer(value, minimum, maximum) {
|
|
3664
3862
|
return Number.isSafeInteger(value) && value >= minimum && value <= maximum;
|
|
@@ -3679,8 +3877,8 @@ async function prepareRuntimeCheckpoint(input) {
|
|
|
3679
3877
|
policy: {
|
|
3680
3878
|
policyId: "code.runtime",
|
|
3681
3879
|
recipes: input.recipes,
|
|
3682
|
-
maximumFiles:
|
|
3683
|
-
maximumBytes:
|
|
3880
|
+
maximumFiles: 2e4,
|
|
3881
|
+
maximumBytes: 512 * 1024 * 1024
|
|
3684
3882
|
},
|
|
3685
3883
|
recipeExecutor: input.recipeExecutor
|
|
3686
3884
|
});
|
|
@@ -3762,11 +3960,11 @@ var CodeRuntimeCheckpointManager = class {
|
|
|
3762
3960
|
};
|
|
3763
3961
|
var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
3764
3962
|
var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
3765
|
-
async function materializeCodeRuntimeSource(snapshot, tempRoot = (0,
|
|
3963
|
+
async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.tmpdir)()) {
|
|
3766
3964
|
if (!snapshot.files.length || snapshot.files.length > 1e4) throw new TypeError("Code source file count is invalid");
|
|
3767
|
-
const root = await (0,
|
|
3768
|
-
const sourceDir = (0,
|
|
3769
|
-
await (0,
|
|
3965
|
+
const root = await (0, import_promises8.mkdtemp)((0, import_path8.join)(tempRoot, "odla-code-source-"));
|
|
3966
|
+
const sourceDir = (0, import_path8.join)(root, "source");
|
|
3967
|
+
await (0, import_promises8.mkdir)(sourceDir);
|
|
3770
3968
|
const seen = /* @__PURE__ */ new Set();
|
|
3771
3969
|
let bytes = 0;
|
|
3772
3970
|
try {
|
|
@@ -3776,14 +3974,14 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os2.
|
|
|
3776
3974
|
seen.add(file.path);
|
|
3777
3975
|
bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
|
|
3778
3976
|
if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
|
|
3779
|
-
const target = (0,
|
|
3780
|
-
if (!target.startsWith(`${(0,
|
|
3781
|
-
await (0,
|
|
3782
|
-
await (0,
|
|
3977
|
+
const target = (0, import_path8.resolve)(sourceDir, file.path);
|
|
3978
|
+
if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code source path escapes its root");
|
|
3979
|
+
await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
|
|
3980
|
+
await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 420 });
|
|
3783
3981
|
}
|
|
3784
|
-
return { sourceDir, cleanup: () => (0,
|
|
3982
|
+
return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
|
|
3785
3983
|
} catch (cause) {
|
|
3786
|
-
await (0,
|
|
3984
|
+
await (0, import_promises8.rm)(root, { recursive: true, force: true });
|
|
3787
3985
|
throw cause;
|
|
3788
3986
|
}
|
|
3789
3987
|
}
|
|
@@ -3889,15 +4087,15 @@ async function environment(input, options, tool) {
|
|
|
3889
4087
|
{ id: "authority", value: `lease:${input.lease.leaseId}`, readers: input.readers },
|
|
3890
4088
|
{ id: "reader", value: options.readerId, readers: input.readers }
|
|
3891
4089
|
]);
|
|
3892
|
-
const
|
|
4090
|
+
const digest = await destinationRegistryDigest([input.workspaceId]);
|
|
3893
4091
|
const approvals = ["irreversible_mutation", "external_send", "financial", "secret_read"];
|
|
3894
4092
|
if (options.recipeAuthorization === "exact_approval") approvals.push("code_execution");
|
|
3895
4093
|
const policy = createEffectPolicy({
|
|
3896
|
-
destinationRegistries: { [DESTINATIONS]: { digest
|
|
4094
|
+
destinationRegistries: { [DESTINATIONS]: { digest, values: [input.workspaceId] } },
|
|
3897
4095
|
approvalEffects: approvals
|
|
3898
4096
|
});
|
|
3899
4097
|
const fixedArgs = {
|
|
3900
|
-
workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest:
|
|
4098
|
+
workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest },
|
|
3901
4099
|
authority: { role: "authority", value: ingress.control("authority") }
|
|
3902
4100
|
};
|
|
3903
4101
|
return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
|
|
@@ -3968,11 +4166,11 @@ async function read(context, request, options, policy) {
|
|
|
3968
4166
|
const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
|
|
3969
4167
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
3970
4168
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
3971
|
-
const info = await (0,
|
|
4169
|
+
const info = await (0, import_promises9.stat)(target);
|
|
3972
4170
|
if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
|
|
3973
4171
|
throw new TypeError("file is not a bounded regular source file");
|
|
3974
4172
|
}
|
|
3975
|
-
const source = await (0,
|
|
4173
|
+
const source = await (0, import_promises9.readFile)(target);
|
|
3976
4174
|
if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
|
|
3977
4175
|
const lines = source.toString("utf8").split("\n");
|
|
3978
4176
|
const content = lines.slice(startLine - 1, endLine).join("\n");
|
|
@@ -4046,12 +4244,12 @@ function policyContext(context, request, options, extra) {
|
|
|
4046
4244
|
async function registeredFiles(root, limit) {
|
|
4047
4245
|
const paths = [];
|
|
4048
4246
|
const walk = async (directory) => {
|
|
4049
|
-
for (const entry of await (0,
|
|
4247
|
+
for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
|
|
4050
4248
|
if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
|
|
4051
|
-
const target = (0,
|
|
4249
|
+
const target = (0, import_path9.resolve)(directory, entry.name);
|
|
4052
4250
|
if (entry.isDirectory()) await walk(target);
|
|
4053
4251
|
else if (entry.isFile()) {
|
|
4054
|
-
const path = (0,
|
|
4252
|
+
const path = (0, import_path9.relative)(root, target).split("\\").join("/");
|
|
4055
4253
|
try {
|
|
4056
4254
|
validateRelativePath(path);
|
|
4057
4255
|
} catch {
|
|
@@ -4062,7 +4260,7 @@ async function registeredFiles(root, limit) {
|
|
|
4062
4260
|
}
|
|
4063
4261
|
}
|
|
4064
4262
|
};
|
|
4065
|
-
await walk((0,
|
|
4263
|
+
await walk((0, import_path9.resolve)(root));
|
|
4066
4264
|
return paths.sort();
|
|
4067
4265
|
}
|
|
4068
4266
|
function validateOptions(options) {
|
|
@@ -4097,14 +4295,31 @@ function codeCommandMetadata(payload, resume) {
|
|
|
4097
4295
|
}
|
|
4098
4296
|
const planning = trusted?.planningInputDigest;
|
|
4099
4297
|
const attestation = trusted?.attestationDigest;
|
|
4298
|
+
const repository = trusted?.repository;
|
|
4299
|
+
const baseCommitSha = trusted?.commitSha;
|
|
4300
|
+
const sourceTreeDigest = trusted?.treeDigest;
|
|
4301
|
+
if (typeof repository !== "string" || !repository.includes("/") || typeof baseCommitSha !== "string" || !/^[0-9a-f]{40}$/.test(baseCommitSha) || typeof sourceTreeDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(sourceTreeDigest)) {
|
|
4302
|
+
throw new TypeError(`invalid Code ${resume ? "resume" : "start"} trusted base`);
|
|
4303
|
+
}
|
|
4100
4304
|
return {
|
|
4101
4305
|
role,
|
|
4102
4306
|
title,
|
|
4103
4307
|
prompt,
|
|
4104
4308
|
planningInputDigest: typeof planning === "string" && /^sha256:[0-9a-f]{64}$/.test(planning) ? planning : null,
|
|
4105
|
-
attestationDigest: typeof attestation === "string" ? attestation : "resume"
|
|
4309
|
+
attestationDigest: typeof attestation === "string" ? attestation : "resume",
|
|
4310
|
+
repository,
|
|
4311
|
+
baseCommitSha,
|
|
4312
|
+
sourceTreeDigest
|
|
4106
4313
|
};
|
|
4107
4314
|
}
|
|
4315
|
+
function codeLocalSource(payload) {
|
|
4316
|
+
const source = record22(payload.source);
|
|
4317
|
+
if (!source) return null;
|
|
4318
|
+
if (source.kind !== "local_checkout" || typeof source.repository !== "string" || typeof source.headCommitSha !== "string" || !/^[0-9a-f]{40}$/.test(source.headCommitSha) || typeof source.trustedBaseDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(source.trustedBaseDigest) || typeof source.developerPatchDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(source.developerPatchDigest) || typeof source.snapshotDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(source.snapshotDigest) || typeof source.modified !== "boolean" || !Number.isSafeInteger(source.fileCount) || Number(source.fileCount) < 1 || Number(source.fileCount) > 2e4 || !Number.isSafeInteger(source.byteCount) || Number(source.byteCount) < 1 || Number(source.byteCount) > 512 * 1024 * 1024 || !Number.isSafeInteger(source.capturedAt) || Number(source.capturedAt) < 1) {
|
|
4319
|
+
throw new TypeError("invalid local checkout source descriptor");
|
|
4320
|
+
}
|
|
4321
|
+
return source;
|
|
4322
|
+
}
|
|
4108
4323
|
function codeCheckpointPayload(payload) {
|
|
4109
4324
|
const value = payload.checkpoint;
|
|
4110
4325
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
|
|
@@ -4133,11 +4348,62 @@ function fakeCodeLease(command, metadata2) {
|
|
|
4133
4348
|
};
|
|
4134
4349
|
}
|
|
4135
4350
|
var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4351
|
+
var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
|
|
4352
|
+
async function prepareRuntimeLocalSource(input) {
|
|
4353
|
+
const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
|
|
4354
|
+
if (!available || JSON.stringify(available.descriptor) !== JSON.stringify(descriptor2) || descriptor2.repository.toLowerCase() !== repository.toLowerCase() || descriptor2.headCommitSha !== baseCommitSha) {
|
|
4355
|
+
throw new TypeError("the session's local checkout snapshot is not available on this terminal");
|
|
4356
|
+
}
|
|
4357
|
+
const workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
|
|
4358
|
+
trustedBaseDir: available.trustedBaseDir,
|
|
4359
|
+
trustedBaseCommitSha: baseCommitSha,
|
|
4360
|
+
checkpoint: codeCheckpointPayload(command.payload)
|
|
4361
|
+
})).workspace : await stageWorkspacePair(available.trustedBaseDir, available.sourceDir, SOURCE_LIMITS);
|
|
4362
|
+
const trustedBaseDigest = await digestStagedWorkspace(workspace.baselineDir, SOURCE_LIMITS);
|
|
4363
|
+
if (trustedBaseDigest !== descriptor2.trustedBaseDigest) {
|
|
4364
|
+
await workspace.cleanup();
|
|
4365
|
+
throw new TypeError("trusted Git base digest changed after connection");
|
|
4366
|
+
}
|
|
4367
|
+
if (!resume && await digestStagedWorkspace(workspace.workspaceDir, SOURCE_LIMITS) !== descriptor2.snapshotDigest) {
|
|
4368
|
+
await workspace.cleanup();
|
|
4369
|
+
throw new TypeError("local checkout snapshot digest changed after connection");
|
|
4370
|
+
}
|
|
4371
|
+
return { workspace, sourceDigest: descriptor2.snapshotDigest, trustedBaseDigest };
|
|
4372
|
+
}
|
|
4373
|
+
function createCodeRuntimeToolBroker(input, lease, role) {
|
|
4374
|
+
const broker = createCodeToolBroker({
|
|
4375
|
+
recipes: input.recipes,
|
|
4376
|
+
recipeExecutor: createContainerRecipeExecutor(input.engine),
|
|
4377
|
+
recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
|
|
4378
|
+
readerId: `code-session:${lease.task.taskId}`
|
|
4379
|
+
});
|
|
4380
|
+
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
|
+
}
|
|
4382
|
+
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
4383
|
+
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
4384
|
+
refs.push(eventId);
|
|
4385
|
+
const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
|
|
4386
|
+
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
4387
|
+
}
|
|
4388
|
+
var digestRuntimeValue = (value) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value).digest("hex")}`;
|
|
4389
|
+
var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
|
|
4390
|
+
var runtimeRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4391
|
+
var safeRuntimeJson = (value) => {
|
|
4392
|
+
try {
|
|
4393
|
+
return JSON.stringify(value).slice(0, 1e4);
|
|
4394
|
+
} catch {
|
|
4395
|
+
return "[event]";
|
|
4396
|
+
}
|
|
4397
|
+
};
|
|
4398
|
+
function runtimeResultText(value) {
|
|
4399
|
+
const record32 = runtimeRecord(value);
|
|
4400
|
+
return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
|
|
4401
|
+
}
|
|
4136
4402
|
var CodePiRuntimeEngine = class {
|
|
4137
4403
|
constructor(options) {
|
|
4138
4404
|
this.options = options;
|
|
4139
4405
|
this.#run = options.runAttempt ?? runContainerAttempt;
|
|
4140
|
-
this.#buildPolicyDigest =
|
|
4406
|
+
this.#buildPolicyDigest = digestRuntimeValue(JSON.stringify(options.recipes));
|
|
4141
4407
|
this.#checkpoints = new CodeRuntimeCheckpointManager({
|
|
4142
4408
|
control: options.control,
|
|
4143
4409
|
recipes: options.recipes,
|
|
@@ -4172,19 +4438,35 @@ var CodePiRuntimeEngine = class {
|
|
|
4172
4438
|
}
|
|
4173
4439
|
async #start(command, resume) {
|
|
4174
4440
|
if (this.#active.has(command.sessionId)) throw new TypeError("Code session is already active on this runtime");
|
|
4175
|
-
const
|
|
4176
|
-
const
|
|
4441
|
+
const metadata2 = codeCommandMetadata(command.payload, resume);
|
|
4442
|
+
const requestedLocal = codeLocalSource(command.payload);
|
|
4177
4443
|
let workspace;
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4444
|
+
let sourceDigest;
|
|
4445
|
+
let localTrustedBaseDigest;
|
|
4446
|
+
if (requestedLocal) {
|
|
4447
|
+
const prepared = await prepareRuntimeLocalSource({
|
|
4448
|
+
command,
|
|
4449
|
+
descriptor: requestedLocal,
|
|
4450
|
+
available: this.options.localSource,
|
|
4451
|
+
repository: metadata2.repository,
|
|
4452
|
+
baseCommitSha: metadata2.baseCommitSha,
|
|
4453
|
+
resume
|
|
4454
|
+
});
|
|
4455
|
+
({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
|
|
4456
|
+
} else {
|
|
4457
|
+
const source = await this.options.control.source(command.sessionId);
|
|
4458
|
+
const materialized = await materializeCodeRuntimeSource(source);
|
|
4459
|
+
try {
|
|
4460
|
+
workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
|
|
4461
|
+
trustedBaseDir: materialized.sourceDir,
|
|
4462
|
+
trustedBaseCommitSha: source.commitSha,
|
|
4463
|
+
checkpoint: codeCheckpointPayload(command.payload)
|
|
4464
|
+
})).workspace : await stageWorkspace(materialized.sourceDir);
|
|
4465
|
+
} finally {
|
|
4466
|
+
await materialized.cleanup();
|
|
4467
|
+
}
|
|
4468
|
+
sourceDigest = source.treeDigest;
|
|
4186
4469
|
}
|
|
4187
|
-
const metadata2 = codeCommandMetadata(command.payload, resume);
|
|
4188
4470
|
const abort = new AbortController();
|
|
4189
4471
|
const conversationRefs = [];
|
|
4190
4472
|
const active = {
|
|
@@ -4194,21 +4476,30 @@ var CodePiRuntimeEngine = class {
|
|
|
4194
4476
|
acknowledged: false,
|
|
4195
4477
|
role: metadata2.role,
|
|
4196
4478
|
title: metadata2.title,
|
|
4197
|
-
baseCommitSha:
|
|
4198
|
-
|
|
4479
|
+
baseCommitSha: metadata2.baseCommitSha,
|
|
4480
|
+
repository: metadata2.repository,
|
|
4481
|
+
sourceTreeDigest: metadata2.sourceTreeDigest,
|
|
4482
|
+
trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
|
|
4199
4483
|
maxFiles: 1e4,
|
|
4200
4484
|
maxBytes: 16 * 1024 * 1024
|
|
4201
4485
|
}),
|
|
4202
|
-
planningInputDigest: metadata2.planningInputDigest ??
|
|
4203
|
-
JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree:
|
|
4486
|
+
planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
|
|
4487
|
+
JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })
|
|
4204
4488
|
),
|
|
4205
4489
|
done: Promise.resolve(null)
|
|
4206
4490
|
};
|
|
4207
4491
|
this.#active.set(command.sessionId, active);
|
|
4492
|
+
if (requestedLocal) {
|
|
4493
|
+
await this.#event(command, {
|
|
4494
|
+
type: "message",
|
|
4495
|
+
actor: "system",
|
|
4496
|
+
body: `Source snapshot: local checkout ${requestedLocal.snapshotDigest} \xB7 ${requestedLocal.modified ? "modified" : "clean"} \xB7 Git ${requestedLocal.headCommitSha}`
|
|
4497
|
+
}, conversationRefs);
|
|
4498
|
+
}
|
|
4208
4499
|
active.done = this.#runAttempt(command, metadata2, active).catch(async (cause) => {
|
|
4209
|
-
await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${
|
|
4500
|
+
await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` }, conversationRefs).catch(() => void 0);
|
|
4210
4501
|
await this.#event(command, { type: "status", status: "failed" }, conversationRefs).catch(() => void 0);
|
|
4211
|
-
await this.#failure(command, active,
|
|
4502
|
+
await this.#failure(command, active, runtimeErrorMessage(cause));
|
|
4212
4503
|
return null;
|
|
4213
4504
|
});
|
|
4214
4505
|
return { status: "running", message: resume ? "Pi resumed from a portable checkpoint" : "Pi started" };
|
|
@@ -4228,22 +4519,29 @@ var CodePiRuntimeEngine = class {
|
|
|
4228
4519
|
title: active.title,
|
|
4229
4520
|
prompt,
|
|
4230
4521
|
planningInputDigest: active.planningInputDigest,
|
|
4231
|
-
attestationDigest: "follow-up"
|
|
4522
|
+
attestationDigest: "follow-up",
|
|
4523
|
+
repository: active.repository,
|
|
4524
|
+
baseCommitSha: active.baseCommitSha,
|
|
4525
|
+
sourceTreeDigest: active.sourceTreeDigest
|
|
4232
4526
|
}, active).catch(async (cause) => {
|
|
4233
4527
|
await this.#event(
|
|
4234
4528
|
command,
|
|
4235
|
-
{ type: "message", actor: "system", body: `Pi failed: ${
|
|
4529
|
+
{ type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` },
|
|
4236
4530
|
active.conversationRefs
|
|
4237
4531
|
).catch(() => void 0);
|
|
4238
4532
|
await this.#event(command, { type: "status", status: "failed" }, active.conversationRefs).catch(() => void 0);
|
|
4239
|
-
await this.#failure(command, active,
|
|
4533
|
+
await this.#failure(command, active, runtimeErrorMessage(cause));
|
|
4240
4534
|
return null;
|
|
4241
4535
|
});
|
|
4242
4536
|
return { status: "running", message: "Pi accepted the owner prompt" };
|
|
4243
4537
|
}
|
|
4244
4538
|
async #runAttempt(command, metadata2, active) {
|
|
4245
4539
|
const lease = fakeCodeLease(command, metadata2);
|
|
4246
|
-
const broker =
|
|
4540
|
+
const broker = createCodeRuntimeToolBroker({
|
|
4541
|
+
recipes: this.options.recipes,
|
|
4542
|
+
engine: this.options.engine,
|
|
4543
|
+
recipeAuthorization: this.options.recipeAuthorization
|
|
4544
|
+
}, lease, metadata2.role);
|
|
4247
4545
|
const startedAt = Date.now();
|
|
4248
4546
|
let completionSeen = false;
|
|
4249
4547
|
const result = await this.#run({
|
|
@@ -4303,7 +4601,7 @@ var CodePiRuntimeEngine = class {
|
|
|
4303
4601
|
return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
|
|
4304
4602
|
}
|
|
4305
4603
|
if (output.type === "event") {
|
|
4306
|
-
const payload =
|
|
4604
|
+
const payload = runtimeRecord(output.payload);
|
|
4307
4605
|
if (output.kind === "pi.started") {
|
|
4308
4606
|
await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
|
|
4309
4607
|
} else if (output.kind === "pi.thinking" && payload?.available === true && Number.isSafeInteger(payload.durationMs) && Number(payload.durationMs) >= 0) {
|
|
@@ -4316,12 +4614,12 @@ var CodePiRuntimeEngine = class {
|
|
|
4316
4614
|
await this.#event(command, {
|
|
4317
4615
|
type: "message",
|
|
4318
4616
|
actor: "system",
|
|
4319
|
-
body: `${output.kind}${output.payload === void 0 ? "" : ` ${
|
|
4617
|
+
body: `${output.kind}${output.payload === void 0 ? "" : ` ${safeRuntimeJson(output.payload)}`}`
|
|
4320
4618
|
}, active.conversationRefs);
|
|
4321
4619
|
}
|
|
4322
4620
|
} else if (output.type === "attempt.complete") {
|
|
4323
4621
|
completionSeen = true;
|
|
4324
|
-
const body =
|
|
4622
|
+
const body = runtimeResultText(output.result) ?? `Pi ${output.status}.`;
|
|
4325
4623
|
await this.#event(command, {
|
|
4326
4624
|
type: "message",
|
|
4327
4625
|
actor: output.status === "completed" ? "agent" : "system",
|
|
@@ -4348,15 +4646,6 @@ var CodePiRuntimeEngine = class {
|
|
|
4348
4646
|
}
|
|
4349
4647
|
return result;
|
|
4350
4648
|
}
|
|
4351
|
-
#toolBroker(lease, role) {
|
|
4352
|
-
const broker = createCodeToolBroker({
|
|
4353
|
-
recipes: this.options.recipes,
|
|
4354
|
-
recipeExecutor: createContainerRecipeExecutor(this.options.engine),
|
|
4355
|
-
recipeAuthorization: this.options.recipeAuthorization ?? "registered_recipe",
|
|
4356
|
-
readerId: `code-session:${lease.task.taskId}`
|
|
4357
|
-
});
|
|
4358
|
-
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" }) };
|
|
4359
|
-
}
|
|
4360
4649
|
async #checkpoint(command) {
|
|
4361
4650
|
const active = this.#active.get(command.sessionId);
|
|
4362
4651
|
if (!active) throw new TypeError("Code session workspace is not active on this runtime");
|
|
@@ -4371,26 +4660,9 @@ var CodePiRuntimeEngine = class {
|
|
|
4371
4660
|
}
|
|
4372
4661
|
}
|
|
4373
4662
|
async #event(command, event, refs) {
|
|
4374
|
-
|
|
4375
|
-
refs.push(eventId);
|
|
4376
|
-
const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
|
|
4377
|
-
await this.options.control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
4378
|
-
}
|
|
4379
|
-
};
|
|
4380
|
-
var object2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4381
|
-
var digest = (value) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value).digest("hex")}`;
|
|
4382
|
-
var message2 = (value) => value instanceof Error ? value.message : String(value);
|
|
4383
|
-
var json = (value) => {
|
|
4384
|
-
try {
|
|
4385
|
-
return JSON.stringify(value).slice(0, 1e4);
|
|
4386
|
-
} catch {
|
|
4387
|
-
return "[event]";
|
|
4663
|
+
await appendCodeRuntimeEvent(this.options.control, command, event, refs);
|
|
4388
4664
|
}
|
|
4389
4665
|
};
|
|
4390
|
-
function resultText(value) {
|
|
4391
|
-
const record32 = object2(value);
|
|
4392
|
-
return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
|
|
4393
|
-
}
|
|
4394
4666
|
|
|
4395
4667
|
// src/help.ts
|
|
4396
4668
|
var import_node_fs6 = require("fs");
|
|
@@ -4532,8 +4804,8 @@ async function requestHostedSecurityJson(options, path, init, action) {
|
|
|
4532
4804
|
const body = await response2.json().catch(() => ({}));
|
|
4533
4805
|
if (!response2.ok) {
|
|
4534
4806
|
const code = optionalHostedText(body.error?.code, "error code", 128);
|
|
4535
|
-
const
|
|
4536
|
-
throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${
|
|
4807
|
+
const message2 = optionalHostedText(body.error?.message, "error message", 300);
|
|
4808
|
+
throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
|
|
4537
4809
|
}
|
|
4538
4810
|
return body;
|
|
4539
4811
|
}
|
|
@@ -4722,24 +4994,111 @@ async function defaultReadOrigin(cwd) {
|
|
|
4722
4994
|
return result.stdout;
|
|
4723
4995
|
}
|
|
4724
4996
|
|
|
4725
|
-
// src/code-
|
|
4726
|
-
var
|
|
4997
|
+
// src/code-local-source.ts
|
|
4998
|
+
var import_node_child_process3 = require("child_process");
|
|
4999
|
+
var import_node_crypto = require("crypto");
|
|
5000
|
+
var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
|
|
5001
|
+
async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
|
|
5002
|
+
const headCommitSha = await readHead(cwd);
|
|
5003
|
+
const staged = await stageWorkspace(cwd, { ...SOURCE_LIMITS2, gitTrackedAndUnignored: true });
|
|
5004
|
+
let trusted;
|
|
5005
|
+
try {
|
|
5006
|
+
trusted = await materializeGitTree(cwd, headCommitSha, SOURCE_LIMITS2);
|
|
5007
|
+
const comparison = await stageWorkspacePair(trusted.sourceDir, staged.baselineDir, SOURCE_LIMITS2);
|
|
5008
|
+
let developerPatch;
|
|
5009
|
+
try {
|
|
5010
|
+
developerPatch = await comparison.patch(4 * 1024 * 1024);
|
|
5011
|
+
} finally {
|
|
5012
|
+
await comparison.cleanup();
|
|
5013
|
+
}
|
|
5014
|
+
const descriptor2 = {
|
|
5015
|
+
kind: "local_checkout",
|
|
5016
|
+
repository,
|
|
5017
|
+
headCommitSha,
|
|
5018
|
+
trustedBaseDigest: await digestStagedWorkspace(trusted.sourceDir, SOURCE_LIMITS2),
|
|
5019
|
+
developerPatchDigest: digestText(developerPatch),
|
|
5020
|
+
snapshotDigest: await digestStagedWorkspace(staged.baselineDir, SOURCE_LIMITS2),
|
|
5021
|
+
modified: developerPatch.length > 0,
|
|
5022
|
+
fileCount: staged.fileCount,
|
|
5023
|
+
byteCount: staged.byteCount,
|
|
5024
|
+
capturedAt: Date.now()
|
|
5025
|
+
};
|
|
5026
|
+
return {
|
|
5027
|
+
descriptor: descriptor2,
|
|
5028
|
+
trustedBaseDir: trusted.sourceDir,
|
|
5029
|
+
sourceDir: staged.baselineDir,
|
|
5030
|
+
cleanup: async () => {
|
|
5031
|
+
await Promise.all([staged.cleanup(), trusted.cleanup()]);
|
|
5032
|
+
}
|
|
5033
|
+
};
|
|
5034
|
+
} catch (error) {
|
|
5035
|
+
await Promise.all([staged.cleanup(), trusted?.cleanup()]);
|
|
5036
|
+
throw error;
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5039
|
+
async function readGitHead(cwd) {
|
|
5040
|
+
const value = await new Promise((accept, reject) => {
|
|
5041
|
+
(0, import_node_child_process3.execFile)("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
|
|
5042
|
+
if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
|
|
5043
|
+
else accept(stdout.trim());
|
|
5044
|
+
});
|
|
5045
|
+
});
|
|
5046
|
+
if (!/^[0-9a-f]{40}$/.test(value)) throw new Error("code connect could not resolve the checkout HEAD commit");
|
|
5047
|
+
return value;
|
|
5048
|
+
}
|
|
5049
|
+
function digestText(value) {
|
|
5050
|
+
return `sha256:${(0, import_node_crypto.createHash)("sha256").update(value).digest("hex")}`;
|
|
5051
|
+
}
|
|
5052
|
+
|
|
5053
|
+
// src/code-images.ts
|
|
5054
|
+
var import_node_child_process4 = require("child_process");
|
|
5055
|
+
async function prepareCodeImages(engine, images) {
|
|
5056
|
+
for (const image of images) {
|
|
5057
|
+
await new Promise((accept, reject) => {
|
|
5058
|
+
const args = engine === "container" ? ["image", "pull", image] : ["pull", image];
|
|
5059
|
+
(0, import_node_child_process4.execFile)(engine, args, { encoding: "utf8", maxBuffer: 2 * 1024 * 1024, timeout: 10 * 6e4 }, (error) => {
|
|
5060
|
+
if (error) reject(new Error(`could not prepare pinned Code image ${image}`));
|
|
5061
|
+
else accept();
|
|
5062
|
+
});
|
|
5063
|
+
});
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
|
|
5067
|
+
// src/code-runtime-config.ts
|
|
5068
|
+
var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:e7858b4f0291543171b2659b47c5d00f97acc4767cafd9b74ae5228899e0cde4";
|
|
4727
5069
|
var CODE_BUILD_RECIPES = Object.freeze([{
|
|
4728
|
-
id: "
|
|
5070
|
+
id: "odla-code-contracts",
|
|
4729
5071
|
image: "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd",
|
|
4730
|
-
command: [
|
|
5072
|
+
command: [
|
|
5073
|
+
"node",
|
|
5074
|
+
"--test",
|
|
5075
|
+
"scripts/lib/directories.test.mjs",
|
|
5076
|
+
"scripts/lib/internal-deps.test.mjs",
|
|
5077
|
+
"scripts/lib/npm-audit.test.mjs",
|
|
5078
|
+
"scripts/lib/npm-release-contract.test.mjs",
|
|
5079
|
+
"scripts/lib/release-surfaces.test.mjs"
|
|
5080
|
+
],
|
|
4731
5081
|
timeoutMs: 12e4,
|
|
4732
5082
|
maxOutputBytes: 1024 * 1024,
|
|
4733
5083
|
cpus: 1,
|
|
4734
5084
|
memory: "512m",
|
|
4735
5085
|
pids: 128
|
|
4736
5086
|
}]);
|
|
5087
|
+
|
|
5088
|
+
// src/code-connect.ts
|
|
4737
5089
|
async function codeConnect(options) {
|
|
4738
5090
|
const cwd = options.cwd ?? process.cwd();
|
|
4739
5091
|
const configPath = (0, import_node_path4.resolve)(cwd, options.configPath);
|
|
4740
5092
|
const cfg = (0, import_node_fs7.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
|
|
4741
|
-
const
|
|
4742
|
-
|
|
5093
|
+
const requestedAppId = options.appId?.trim();
|
|
5094
|
+
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
5095
|
+
throw new Error("--app-id must be a valid odla app id");
|
|
5096
|
+
}
|
|
5097
|
+
if (requestedAppId && cfg && requestedAppId !== cfg.app.id) {
|
|
5098
|
+
throw new Error(`--app-id ${requestedAppId} does not match ${options.configPath} (${cfg.app.id})`);
|
|
5099
|
+
}
|
|
5100
|
+
const appId = requestedAppId ?? cfg?.app.id;
|
|
5101
|
+
const platform = (options.platform ?? cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai").replace(/\/+$/, "");
|
|
4743
5102
|
const appEnv = options.env ?? (cfg ? cfg.envs.includes("dev") ? "dev" : cfg.envs[0] : "dev");
|
|
4744
5103
|
if (appEnv !== "dev" && appEnv !== "prod" || cfg && !cfg.envs.includes(appEnv)) {
|
|
4745
5104
|
throw new Error(cfg ? `Code env "${appEnv ?? ""}" must be dev or prod and declared in ${options.configPath}` : `Code env "${appEnv ?? ""}" must be dev or prod`);
|
|
@@ -4758,53 +5117,77 @@ async function codeConnect(options) {
|
|
|
4758
5117
|
const out = options.stdout ?? console;
|
|
4759
5118
|
const doFetch = options.fetch ?? fetch;
|
|
4760
5119
|
const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
|
|
5120
|
+
await (options.prepareImages ?? prepareCodeImages)(engine, [
|
|
5121
|
+
CODE_PI_IMAGE,
|
|
5122
|
+
...new Set(CODE_BUILD_RECIPES.map((recipe2) => recipe2.image))
|
|
5123
|
+
]);
|
|
4761
5124
|
const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
|
|
4762
5125
|
const hostName = (options.name ?? (0, import_node_os.hostname)()).trim();
|
|
4763
5126
|
if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
|
|
4764
|
-
const
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
5127
|
+
const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
|
|
5128
|
+
const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
|
|
5129
|
+
cwd,
|
|
5130
|
+
repository,
|
|
5131
|
+
options.readGitHead
|
|
5132
|
+
);
|
|
5133
|
+
try {
|
|
5134
|
+
const descriptor2 = localSource.descriptor;
|
|
5135
|
+
const approval = await (options.getToken ?? getScopedPlatformToken)({
|
|
5136
|
+
platform,
|
|
5137
|
+
scope: "app:code:host:connect",
|
|
5138
|
+
email: options.email,
|
|
5139
|
+
open: options.open,
|
|
5140
|
+
fetch: doFetch,
|
|
5141
|
+
stdout: out,
|
|
5142
|
+
openApprovalUrl: options.openApprovalUrl,
|
|
5143
|
+
cache: false,
|
|
5144
|
+
label: `Terminal connection for ${appId ?? repository}/${appEnv}`
|
|
5145
|
+
});
|
|
5146
|
+
const target = appId ? { appId } : { repository };
|
|
5147
|
+
const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
|
|
5148
|
+
method: "POST",
|
|
5149
|
+
headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
|
|
5150
|
+
body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
|
|
5151
|
+
redirect: "error",
|
|
5152
|
+
signal: options.signal
|
|
5153
|
+
});
|
|
5154
|
+
const raw = await response2.json().catch(() => null);
|
|
5155
|
+
if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
|
|
5156
|
+
const connection = parseConnection(raw, appId, appEnv);
|
|
5157
|
+
const capabilities = {
|
|
5158
|
+
protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
|
|
5159
|
+
platform: hostPlatform,
|
|
5160
|
+
arch: process.arch,
|
|
5161
|
+
engines: [engine],
|
|
5162
|
+
cpuCount: (0, import_node_os.cpus)().length,
|
|
5163
|
+
memoryBytes: (0, import_node_os.totalmem)(),
|
|
5164
|
+
source: descriptor2,
|
|
5165
|
+
images: {
|
|
5166
|
+
ready: true,
|
|
5167
|
+
pi: CODE_PI_IMAGE,
|
|
5168
|
+
recipes: CODE_BUILD_RECIPES.map((recipe2) => ({ id: recipe2.id, image: recipe2.image }))
|
|
5169
|
+
}
|
|
5170
|
+
};
|
|
5171
|
+
out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
|
|
5172
|
+
out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
|
|
5173
|
+
out.log(`source: local checkout \xB7 ${descriptor2.headCommitSha.slice(0, 10)} \xB7 ${descriptor2.fileCount} files \xB7 staged copy`);
|
|
5174
|
+
out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
|
|
5175
|
+
out.log(`Studio: ${platform}/studio/apps/${encodeURIComponent(connection.binding.appId)}/${connection.binding.env}/code/sessions`);
|
|
5176
|
+
await (options.runRuntime ?? runCodeRuntime)({
|
|
5177
|
+
endpoint: platform,
|
|
5178
|
+
token: connection.token,
|
|
5179
|
+
engine,
|
|
5180
|
+
capabilities,
|
|
5181
|
+
localSource,
|
|
5182
|
+
heartbeatMs,
|
|
5183
|
+
once: options.once === true,
|
|
5184
|
+
signal: options.signal,
|
|
5185
|
+
stdout: out,
|
|
5186
|
+
fetch: doFetch
|
|
5187
|
+
});
|
|
5188
|
+
} finally {
|
|
5189
|
+
await localSource.cleanup();
|
|
5190
|
+
}
|
|
4808
5191
|
}
|
|
4809
5192
|
async function runCodeRuntime(input) {
|
|
4810
5193
|
const controller = new AbortController();
|
|
@@ -4827,7 +5210,8 @@ async function runCodeRuntime(input) {
|
|
|
4827
5210
|
engine: input.engine,
|
|
4828
5211
|
image: CODE_PI_IMAGE,
|
|
4829
5212
|
recipes: CODE_BUILD_RECIPES,
|
|
4830
|
-
recipeAuthorization: "registered_recipe"
|
|
5213
|
+
recipeAuthorization: "registered_recipe",
|
|
5214
|
+
localSource: input.localSource
|
|
4831
5215
|
});
|
|
4832
5216
|
const reconciler = new CodeRuntimeReconciler(control, commandEngine);
|
|
4833
5217
|
try {
|
|
@@ -4868,8 +5252,8 @@ function parseConnection(value, appId, appEnv) {
|
|
|
4868
5252
|
return root;
|
|
4869
5253
|
}
|
|
4870
5254
|
function apiFailure(action, status, value) {
|
|
4871
|
-
const
|
|
4872
|
-
return `${action} failed (${status})${typeof
|
|
5255
|
+
const message2 = record4(record4(value)?.error)?.message;
|
|
5256
|
+
return `${action} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
4873
5257
|
}
|
|
4874
5258
|
function record4(value) {
|
|
4875
5259
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -4884,6 +5268,8 @@ async function codeCommand(parsed, dependencies) {
|
|
|
4884
5268
|
assertArgs(parsed, [
|
|
4885
5269
|
"config",
|
|
4886
5270
|
"env",
|
|
5271
|
+
"platform",
|
|
5272
|
+
"app-id",
|
|
4887
5273
|
"email",
|
|
4888
5274
|
"open",
|
|
4889
5275
|
"name",
|
|
@@ -4899,6 +5285,8 @@ async function codeCommand(parsed, dependencies) {
|
|
|
4899
5285
|
await (dependencies.codeConnect ?? codeConnect)({
|
|
4900
5286
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
4901
5287
|
env: stringOpt(parsed.options.env),
|
|
5288
|
+
platform: stringOpt(parsed.options.platform),
|
|
5289
|
+
appId: stringOpt(parsed.options["app-id"]),
|
|
4902
5290
|
email: stringOpt(parsed.options.email),
|
|
4903
5291
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
4904
5292
|
name: stringOpt(parsed.options.name),
|
|
@@ -4914,16 +5302,16 @@ async function codeCommand(parsed, dependencies) {
|
|
|
4914
5302
|
}
|
|
4915
5303
|
|
|
4916
5304
|
// src/doctor-checks.ts
|
|
4917
|
-
var
|
|
5305
|
+
var import_node_child_process6 = require("child_process");
|
|
4918
5306
|
var import_node_fs9 = require("fs");
|
|
4919
5307
|
var import_node_path6 = require("path");
|
|
4920
5308
|
|
|
4921
5309
|
// src/wrangler.ts
|
|
4922
|
-
var
|
|
5310
|
+
var import_node_child_process5 = require("child_process");
|
|
4923
5311
|
var import_node_fs8 = require("fs");
|
|
4924
5312
|
var import_node_path5 = require("path");
|
|
4925
5313
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
4926
|
-
const child = (0,
|
|
5314
|
+
const child = (0, import_node_child_process5.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
4927
5315
|
let stdout = "";
|
|
4928
5316
|
let stderr = "";
|
|
4929
5317
|
child.stdout.on("data", (chunk) => stdout += chunk.toString());
|
|
@@ -5014,7 +5402,7 @@ function lintRules(rules, entities, publicRead) {
|
|
|
5014
5402
|
}
|
|
5015
5403
|
return warnings;
|
|
5016
5404
|
}
|
|
5017
|
-
var defaultExec = (cmd, args, cwd) => (0,
|
|
5405
|
+
var defaultExec = (cmd, args, cwd) => (0, import_node_child_process6.execFileSync)(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
5018
5406
|
function trackedSecretFiles(rootDir, exec = defaultExec, localPaths = []) {
|
|
5019
5407
|
let output;
|
|
5020
5408
|
try {
|
|
@@ -5622,8 +6010,8 @@ async function readRegistryApp(cfg, token, doFetch) {
|
|
|
5622
6010
|
});
|
|
5623
6011
|
if (res.status === 404) return null;
|
|
5624
6012
|
if (!res.ok) return null;
|
|
5625
|
-
const
|
|
5626
|
-
return
|
|
6013
|
+
const json = await res.json();
|
|
6014
|
+
return json.app ?? null;
|
|
5627
6015
|
}
|
|
5628
6016
|
async function postJson2(doFetch, url, bearer, body) {
|
|
5629
6017
|
const res = await doFetch(url, {
|
|
@@ -5862,10 +6250,10 @@ async function provision(options) {
|
|
|
5862
6250
|
stdout: out
|
|
5863
6251
|
});
|
|
5864
6252
|
} catch (error) {
|
|
5865
|
-
const
|
|
6253
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
5866
6254
|
const consent = env === "prod" || env === "production" ? " --yes" : "";
|
|
5867
6255
|
throw new Error(
|
|
5868
|
-
`${
|
|
6256
|
+
`${message2}
|
|
5869
6257
|
${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}${consent}" without issuing or rotating again`,
|
|
5870
6258
|
{ cause: error }
|
|
5871
6259
|
);
|
|
@@ -5981,7 +6369,7 @@ async function resolveVaultWrite(options) {
|
|
|
5981
6369
|
}
|
|
5982
6370
|
|
|
5983
6371
|
// src/security-command-context.ts
|
|
5984
|
-
var
|
|
6372
|
+
var import_promises10 = require("readline/promises");
|
|
5985
6373
|
async function hostedSecurityContext(parsed, dependencies) {
|
|
5986
6374
|
const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
5987
6375
|
const cfg = await loadProjectConfig(configPath);
|
|
@@ -6004,12 +6392,12 @@ async function hostedSecurityContext(parsed, dependencies) {
|
|
|
6004
6392
|
);
|
|
6005
6393
|
return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
|
|
6006
6394
|
}
|
|
6007
|
-
async function interactiveConfirmation(
|
|
6008
|
-
if (dependencies.confirm) return dependencies.confirm(
|
|
6395
|
+
async function interactiveConfirmation(message2, dependencies) {
|
|
6396
|
+
if (dependencies.confirm) return dependencies.confirm(message2);
|
|
6009
6397
|
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
6010
|
-
const prompt = (0,
|
|
6398
|
+
const prompt = (0, import_promises10.createInterface)({ input: process.stdin, output: process.stdout });
|
|
6011
6399
|
try {
|
|
6012
|
-
const answer = await prompt.question(`${
|
|
6400
|
+
const answer = await prompt.question(`${message2} [y/N] `);
|
|
6013
6401
|
return /^y(?:es)?$/i.test(answer.trim());
|
|
6014
6402
|
} finally {
|
|
6015
6403
|
prompt.close();
|
|
@@ -6136,7 +6524,7 @@ var import_security2 = require("@odla-ai/security");
|
|
|
6136
6524
|
// src/security.ts
|
|
6137
6525
|
var import_node_path8 = require("path");
|
|
6138
6526
|
var import_security = require("@odla-ai/security");
|
|
6139
|
-
var
|
|
6527
|
+
var import_node3 = require("@odla-ai/security/node");
|
|
6140
6528
|
async function runHostedSecurity(options) {
|
|
6141
6529
|
if (options.sourceDisclosureAck !== "redacted") {
|
|
6142
6530
|
throw new Error("Hosted security requires sourceDisclosureAck=redacted before repository source may leave this process");
|
|
@@ -6159,7 +6547,7 @@ async function runHostedSecurity(options) {
|
|
|
6159
6547
|
scope: selfAudit ? "platform:security:self" : null
|
|
6160
6548
|
};
|
|
6161
6549
|
const token = await injectedToken(options, tokenRequest);
|
|
6162
|
-
const snapshot = await (0,
|
|
6550
|
+
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
6163
6551
|
exclude: !outputRelative.startsWith("../") && !(0, import_node_path8.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
6164
6552
|
});
|
|
6165
6553
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
@@ -6178,7 +6566,7 @@ async function runHostedSecurity(options) {
|
|
|
6178
6566
|
});
|
|
6179
6567
|
const harness = (0, import_security.createSecurityHarness)({
|
|
6180
6568
|
profile,
|
|
6181
|
-
store: new
|
|
6569
|
+
store: new import_node3.FileRunStore((0, import_node_path8.resolve)(output, "state")),
|
|
6182
6570
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
6183
6571
|
validationReasoner: hosted.validationReasoner,
|
|
6184
6572
|
policy: {
|
|
@@ -6188,7 +6576,7 @@ async function runHostedSecurity(options) {
|
|
|
6188
6576
|
}
|
|
6189
6577
|
});
|
|
6190
6578
|
const report2 = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
|
|
6191
|
-
await (0,
|
|
6579
|
+
await (0, import_node3.writeSecurityArtifacts)(output, report2);
|
|
6192
6580
|
const reportDigest = await (0, import_security.securityFingerprint)(report2);
|
|
6193
6581
|
await hosted.complete({
|
|
6194
6582
|
reportDigest,
|
|
@@ -6321,8 +6709,8 @@ async function startHostedSecurityJob(options) {
|
|
|
6321
6709
|
"start hosted security job"
|
|
6322
6710
|
);
|
|
6323
6711
|
} catch (error) {
|
|
6324
|
-
const
|
|
6325
|
-
if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(
|
|
6712
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
6713
|
+
if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message2)) {
|
|
6326
6714
|
throw new Error("Hosted security plan or execution intent changed or became unavailable after review; fetch a fresh intent and explicitly acknowledge its digest before enqueueing again", { cause: error });
|
|
6327
6715
|
}
|
|
6328
6716
|
throw error;
|