@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/index.cjs
CHANGED
|
@@ -509,12 +509,13 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
509
509
|
const { token, expiresAt } = await (0, import_db2.requestToken)({
|
|
510
510
|
endpoint: audience,
|
|
511
511
|
email,
|
|
512
|
-
label: options.label ?? `odla CLI
|
|
512
|
+
label: options.label ?? `odla CLI (${scope})`,
|
|
513
513
|
scopes: [scope],
|
|
514
514
|
fetch: doFetch,
|
|
515
515
|
onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
|
|
516
516
|
const approvalUrl = verificationUriComplete ?? handshakeUrl(audience, userCode);
|
|
517
|
-
|
|
517
|
+
const requestName = scope === "app:code:host:connect" ? "terminal connection" : "scoped request";
|
|
518
|
+
out.log(`Review, then approve ${requestName} ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
|
|
518
519
|
if (approvalBrowser({ open: options.open }).open) {
|
|
519
520
|
await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
|
|
520
521
|
}
|
|
@@ -576,8 +577,8 @@ async function responseBody(response2) {
|
|
|
576
577
|
}
|
|
577
578
|
function apiError(status, body) {
|
|
578
579
|
const error = isRecord(body) && isRecord(body.error) ? body.error : void 0;
|
|
579
|
-
const
|
|
580
|
-
return `read System AI admin changes failed (${status}): ${
|
|
580
|
+
const message2 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
581
|
+
return `read System AI admin changes failed (${status}): ${message2}`;
|
|
581
582
|
}
|
|
582
583
|
function timestamp(value) {
|
|
583
584
|
if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
|
|
@@ -678,8 +679,8 @@ async function responseBody2(res) {
|
|
|
678
679
|
}
|
|
679
680
|
function apiError2(action, status, body) {
|
|
680
681
|
const error = isRecord2(body) && isRecord2(body.error) ? body.error : void 0;
|
|
681
|
-
const
|
|
682
|
-
return `${action} failed (${status}): ${
|
|
682
|
+
const message2 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
683
|
+
return `${action} failed (${status}): ${message2}`;
|
|
683
684
|
}
|
|
684
685
|
function isRecord2(value) {
|
|
685
686
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -859,8 +860,8 @@ async function responseBody3(res) {
|
|
|
859
860
|
}
|
|
860
861
|
function apiError3(action, status, body) {
|
|
861
862
|
const error = isRecord3(body) && isRecord3(body.error) ? body.error : void 0;
|
|
862
|
-
const
|
|
863
|
-
return `${action} failed (${status}): ${
|
|
863
|
+
const message2 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
864
|
+
return `${action} failed (${status}): ${message2}`;
|
|
864
865
|
}
|
|
865
866
|
function isRecord3(value) {
|
|
866
867
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -1540,8 +1541,8 @@ var CAPABILITIES = {
|
|
|
1540
1541
|
"connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"
|
|
1541
1542
|
]
|
|
1542
1543
|
};
|
|
1543
|
-
function printCapabilities(
|
|
1544
|
-
if (
|
|
1544
|
+
function printCapabilities(json = false, out = console) {
|
|
1545
|
+
if (json) {
|
|
1545
1546
|
out.log(JSON.stringify(CAPABILITIES, null, 2));
|
|
1546
1547
|
return;
|
|
1547
1548
|
}
|
|
@@ -1571,8 +1572,8 @@ var CalendarRequestError = class extends Error {
|
|
|
1571
1572
|
status;
|
|
1572
1573
|
/** Machine-readable `error.code` from the response body, when present. */
|
|
1573
1574
|
code;
|
|
1574
|
-
constructor(
|
|
1575
|
-
super(
|
|
1575
|
+
constructor(message2, status, code) {
|
|
1576
|
+
super(message2);
|
|
1576
1577
|
this.name = "CalendarRequestError";
|
|
1577
1578
|
this.status = status;
|
|
1578
1579
|
if (code !== void 0) this.code = code;
|
|
@@ -1739,8 +1740,8 @@ async function calendarJson(ctx, suffix, init) {
|
|
|
1739
1740
|
const body = await response2.json().catch(() => ({}));
|
|
1740
1741
|
if (!response2.ok) {
|
|
1741
1742
|
const code = textField(body.error?.code, 128);
|
|
1742
|
-
const
|
|
1743
|
-
const detail = `${code ? ` ${code}` : ""}${
|
|
1743
|
+
const message2 = textField(body.error?.message, 500);
|
|
1744
|
+
const detail = `${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`;
|
|
1744
1745
|
throw new CalendarRequestError(redactSecrets(`calendar request failed (${response2.status})${detail}`), response2.status, code);
|
|
1745
1746
|
}
|
|
1746
1747
|
return body;
|
|
@@ -1976,8 +1977,8 @@ function pollTimeout(value = 10 * 6e4) {
|
|
|
1976
1977
|
function productionConsent(env, yes, action) {
|
|
1977
1978
|
if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action} for "${env}" without --yes`);
|
|
1978
1979
|
}
|
|
1979
|
-
function printStatus(status,
|
|
1980
|
-
if (
|
|
1980
|
+
function printStatus(status, json, out) {
|
|
1981
|
+
if (json) {
|
|
1981
1982
|
out.log(JSON.stringify(status, null, 2));
|
|
1982
1983
|
return;
|
|
1983
1984
|
}
|
|
@@ -2019,63 +2020,63 @@ function parseAgentOutput(line) {
|
|
|
2019
2020
|
} catch {
|
|
2020
2021
|
throw new HarnessProtocolError("agent emitted invalid JSON");
|
|
2021
2022
|
}
|
|
2022
|
-
const
|
|
2023
|
-
if (!
|
|
2023
|
+
const message2 = record2(value);
|
|
2024
|
+
if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
|
|
2024
2025
|
throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
|
|
2025
2026
|
}
|
|
2026
|
-
if (
|
|
2027
|
+
if (message2.type === "event") {
|
|
2027
2028
|
return {
|
|
2028
2029
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
2029
2030
|
type: "event",
|
|
2030
|
-
kind: boundedText(
|
|
2031
|
-
...
|
|
2031
|
+
kind: boundedText(message2.kind, "event.kind", 120),
|
|
2032
|
+
...message2.payload === void 0 ? {} : { payload: message2.payload }
|
|
2032
2033
|
};
|
|
2033
2034
|
}
|
|
2034
|
-
if (
|
|
2035
|
-
const call = record2(
|
|
2035
|
+
if (message2.type === "inference.request") {
|
|
2036
|
+
const call = record2(message2.call);
|
|
2036
2037
|
if (!call || !Array.isArray(call.messages) || !Number.isSafeInteger(call.maxTokens)) {
|
|
2037
2038
|
throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
|
|
2038
2039
|
}
|
|
2039
2040
|
return {
|
|
2040
2041
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
2041
2042
|
type: "inference.request",
|
|
2042
|
-
requestId: boundedText(
|
|
2043
|
+
requestId: boundedText(message2.requestId, "requestId", 180),
|
|
2043
2044
|
call
|
|
2044
2045
|
};
|
|
2045
2046
|
}
|
|
2046
|
-
if (
|
|
2047
|
-
const input = record2(
|
|
2048
|
-
const tool = String(
|
|
2047
|
+
if (message2.type === "tool.request") {
|
|
2048
|
+
const input = record2(message2.input);
|
|
2049
|
+
const tool = String(message2.tool);
|
|
2049
2050
|
if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
|
|
2050
2051
|
throw new HarnessProtocolError("tool.request requires a registered tool and object input");
|
|
2051
2052
|
}
|
|
2052
2053
|
return {
|
|
2053
2054
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
2054
2055
|
type: "tool.request",
|
|
2055
|
-
requestId: boundedText(
|
|
2056
|
+
requestId: boundedText(message2.requestId, "requestId", 180),
|
|
2056
2057
|
tool,
|
|
2057
2058
|
input
|
|
2058
2059
|
};
|
|
2059
2060
|
}
|
|
2060
|
-
if (
|
|
2061
|
-
if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(
|
|
2061
|
+
if (message2.type === "attempt.complete") {
|
|
2062
|
+
if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(message2.status))) {
|
|
2062
2063
|
throw new HarnessProtocolError("attempt.complete.status is invalid");
|
|
2063
2064
|
}
|
|
2064
2065
|
return {
|
|
2065
2066
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
2066
2067
|
type: "attempt.complete",
|
|
2067
|
-
status:
|
|
2068
|
-
...
|
|
2068
|
+
status: message2.status,
|
|
2069
|
+
...message2.result === void 0 ? {} : { result: message2.result }
|
|
2069
2070
|
};
|
|
2070
2071
|
}
|
|
2071
2072
|
throw new HarnessProtocolError("agent message type is unsupported");
|
|
2072
2073
|
}
|
|
2073
|
-
function encodeAgentInput(
|
|
2074
|
-
return `${JSON.stringify(
|
|
2074
|
+
function encodeAgentInput(message2) {
|
|
2075
|
+
return `${JSON.stringify(message2)}
|
|
2075
2076
|
`;
|
|
2076
2077
|
}
|
|
2077
2078
|
|
|
2078
|
-
// ../harness/dist/chunk-
|
|
2079
|
+
// ../harness/dist/chunk-TA2FKK27.js
|
|
2079
2080
|
var import_child_process = require("child_process");
|
|
2080
2081
|
var import_fs = require("fs");
|
|
2081
2082
|
var import_promises2 = require("fs/promises");
|
|
@@ -2085,6 +2086,11 @@ var import_promises3 = require("fs/promises");
|
|
|
2085
2086
|
var import_os = require("os");
|
|
2086
2087
|
var import_path2 = require("path");
|
|
2087
2088
|
var import_child_process2 = require("child_process");
|
|
2089
|
+
var import_path3 = require("path");
|
|
2090
|
+
var import_promises4 = require("fs/promises");
|
|
2091
|
+
var import_os2 = require("os");
|
|
2092
|
+
var import_path4 = require("path");
|
|
2093
|
+
var import_child_process3 = require("child_process");
|
|
2088
2094
|
var DIGEST_IMAGE = /^[a-z0-9][a-z0-9._/-]*(?::[a-zA-Z0-9._-]+)?@sha256:[0-9a-f]{64}$/;
|
|
2089
2095
|
function assertPinnedImage(image) {
|
|
2090
2096
|
if (!DIGEST_IMAGE.test(image)) throw new TypeError("container image must be pinned by sha256 digest");
|
|
@@ -2128,7 +2134,7 @@ async function selectContainerEngine(requested = "auto", options = {}) {
|
|
|
2128
2134
|
throw new TypeError("no supported container engine found");
|
|
2129
2135
|
}
|
|
2130
2136
|
function inspectRootlessPodman() {
|
|
2131
|
-
return new Promise((
|
|
2137
|
+
return new Promise((resolve33, reject) => {
|
|
2132
2138
|
(0, import_child_process.execFile)(
|
|
2133
2139
|
"podman",
|
|
2134
2140
|
["info", "--format", "{{.Host.Security.Rootless}}"],
|
|
@@ -2138,7 +2144,7 @@ function inspectRootlessPodman() {
|
|
|
2138
2144
|
reject(new TypeError("could not verify that the active Podman service is rootless"));
|
|
2139
2145
|
return;
|
|
2140
2146
|
}
|
|
2141
|
-
|
|
2147
|
+
resolve33(stdout.trim() === "true");
|
|
2142
2148
|
}
|
|
2143
2149
|
);
|
|
2144
2150
|
});
|
|
@@ -2249,9 +2255,9 @@ async function runContainerAttempt(options) {
|
|
|
2249
2255
|
if (bytes.byteLength > 1e6) throw new Error("agent message exceeds 1 MB");
|
|
2250
2256
|
const line = bytes.toString("utf8");
|
|
2251
2257
|
if (!line.trim()) return;
|
|
2252
|
-
const
|
|
2253
|
-
if (
|
|
2254
|
-
const response2 = await options.onMessage(
|
|
2258
|
+
const message2 = parseAgentOutput(line);
|
|
2259
|
+
if (message2.type === "attempt.complete") complete = message2;
|
|
2260
|
+
const response2 = await options.onMessage(message2);
|
|
2255
2261
|
if (response2 && !child.stdin.destroyed) child.stdin.write(encodeAgentInput(response2));
|
|
2256
2262
|
};
|
|
2257
2263
|
try {
|
|
@@ -2299,29 +2305,142 @@ async function runContainerAttempt(options) {
|
|
|
2299
2305
|
options.signal?.removeEventListener("abort", abort);
|
|
2300
2306
|
}
|
|
2301
2307
|
}
|
|
2302
|
-
var
|
|
2303
|
-
|
|
2308
|
+
var SKIP_WORKSPACE_DIRS = /* @__PURE__ */ new Set([
|
|
2309
|
+
".git",
|
|
2310
|
+
".odla",
|
|
2311
|
+
".wrangler",
|
|
2312
|
+
"node_modules",
|
|
2313
|
+
"dist",
|
|
2314
|
+
"coverage"
|
|
2315
|
+
]);
|
|
2316
|
+
var SECRET_WORKSPACE_FILE = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
2317
|
+
function allowedWorkspacePath(relativePath) {
|
|
2318
|
+
const parts = relativePath.split("/");
|
|
2319
|
+
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) ?? "");
|
|
2320
|
+
}
|
|
2321
|
+
async function gitOutput(cwd, args, maxBytes) {
|
|
2322
|
+
const child = (0, import_child_process2.spawn)("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: false });
|
|
2323
|
+
const stdout = [];
|
|
2324
|
+
const stderr = [];
|
|
2325
|
+
let bytes = 0;
|
|
2326
|
+
child.stdout.on("data", (chunk) => {
|
|
2327
|
+
bytes += chunk.byteLength;
|
|
2328
|
+
if (bytes > maxBytes) child.kill("SIGKILL");
|
|
2329
|
+
else stdout.push(chunk);
|
|
2330
|
+
});
|
|
2331
|
+
child.stderr.on("data", (chunk) => {
|
|
2332
|
+
if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
|
|
2333
|
+
});
|
|
2334
|
+
const code = await new Promise((accept, reject) => {
|
|
2335
|
+
child.once("error", reject);
|
|
2336
|
+
child.once("exit", accept);
|
|
2337
|
+
});
|
|
2338
|
+
if (bytes > maxBytes) throw new Error(`git output exceeds ${maxBytes} bytes`);
|
|
2339
|
+
if (code !== 0) throw new Error(`git command failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
|
|
2340
|
+
return Buffer.concat(stdout);
|
|
2341
|
+
}
|
|
2342
|
+
async function gitBlobs(cwd, entries, maxBytes) {
|
|
2343
|
+
const child = (0, import_child_process2.spawn)("git", ["cat-file", "--batch"], { cwd, stdio: ["pipe", "pipe", "pipe"], shell: false });
|
|
2344
|
+
const stdout = [];
|
|
2345
|
+
const stderr = [];
|
|
2346
|
+
let bytes = 0;
|
|
2347
|
+
child.stdout.on("data", (chunk) => {
|
|
2348
|
+
bytes += chunk.byteLength;
|
|
2349
|
+
if (bytes > maxBytes + entries.length * 100) child.kill("SIGKILL");
|
|
2350
|
+
else stdout.push(chunk);
|
|
2351
|
+
});
|
|
2352
|
+
child.stderr.on("data", (chunk) => {
|
|
2353
|
+
if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
|
|
2354
|
+
});
|
|
2355
|
+
child.stdin.end(`${entries.map((entry) => entry.hash).join("\n")}
|
|
2356
|
+
`);
|
|
2357
|
+
const code = await new Promise((accept, reject) => {
|
|
2358
|
+
child.once("error", reject);
|
|
2359
|
+
child.once("exit", accept);
|
|
2360
|
+
});
|
|
2361
|
+
if (bytes > maxBytes + entries.length * 100) throw new Error(`Git tree exceeds ${maxBytes} bytes`);
|
|
2362
|
+
if (code !== 0) throw new Error(`git object read failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
|
|
2363
|
+
const output = Buffer.concat(stdout);
|
|
2364
|
+
const blobs = [];
|
|
2365
|
+
let offset = 0;
|
|
2366
|
+
let total = 0;
|
|
2367
|
+
for (const entry of entries) {
|
|
2368
|
+
const newline = output.indexOf(10, offset);
|
|
2369
|
+
if (newline < 0) throw new Error("git object output is truncated");
|
|
2370
|
+
const match = /^([0-9a-f]{40,64}) blob ([0-9]+)$/.exec(output.subarray(offset, newline).toString("utf8"));
|
|
2371
|
+
if (!match || match[1] !== entry.hash) throw new Error("git object output does not match its inventory");
|
|
2372
|
+
const length = Number(match[2]);
|
|
2373
|
+
if (!Number.isSafeInteger(length) || length < 0 || newline + 1 + length >= output.length) {
|
|
2374
|
+
throw new Error("git object output has an invalid length");
|
|
2375
|
+
}
|
|
2376
|
+
const content = output.subarray(newline + 1, newline + 1 + length);
|
|
2377
|
+
if (output[newline + 1 + length] !== 10) throw new Error("git object output is malformed");
|
|
2378
|
+
total += content.byteLength;
|
|
2379
|
+
if (total > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
|
|
2380
|
+
blobs.push(content);
|
|
2381
|
+
offset = newline + 1 + length + 1;
|
|
2382
|
+
}
|
|
2383
|
+
return blobs;
|
|
2384
|
+
}
|
|
2385
|
+
async function materializeGitTree(source, commitSha, options = {}) {
|
|
2386
|
+
if (!/^[0-9a-f]{40}$/.test(commitSha)) throw new TypeError("Git tree requires an exact commit SHA");
|
|
2387
|
+
const sourceDir = await (0, import_promises3.realpath)((0, import_path2.resolve)(source));
|
|
2388
|
+
const maxFiles = options.maxFiles ?? 2e4;
|
|
2389
|
+
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
2390
|
+
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
2391
|
+
const entries = inventory.flatMap((record5) => {
|
|
2392
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record5);
|
|
2393
|
+
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
2394
|
+
});
|
|
2395
|
+
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
2396
|
+
const root = await (0, import_promises3.mkdtemp)((0, import_path2.join)(options.tempRoot ?? (0, import_os.tmpdir)(), "odla-git-tree-"));
|
|
2397
|
+
const targetRoot = (0, import_path2.join)(root, "source");
|
|
2398
|
+
await (0, import_promises3.mkdir)(targetRoot);
|
|
2399
|
+
let byteCount = 0;
|
|
2400
|
+
try {
|
|
2401
|
+
const blobs = await gitBlobs(sourceDir, entries, maxBytes);
|
|
2402
|
+
for (const [index, entry] of entries.entries()) {
|
|
2403
|
+
const content = blobs[index];
|
|
2404
|
+
byteCount += content.byteLength;
|
|
2405
|
+
if (byteCount > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
|
|
2406
|
+
const target = (0, import_path2.resolve)(targetRoot, entry.path);
|
|
2407
|
+
if (!target.startsWith(`${(0, import_path2.resolve)(targetRoot)}${import_path2.sep}`)) throw new TypeError("Git tree path escapes workspace");
|
|
2408
|
+
await (0, import_promises3.mkdir)((0, import_path2.resolve)(target, ".."), { recursive: true });
|
|
2409
|
+
await (0, import_promises3.writeFile)(target, content, { flag: "wx", mode: entry.mode === "100755" ? 493 : 420 });
|
|
2410
|
+
}
|
|
2411
|
+
return {
|
|
2412
|
+
root,
|
|
2413
|
+
sourceDir: targetRoot,
|
|
2414
|
+
fileCount: entries.length,
|
|
2415
|
+
byteCount,
|
|
2416
|
+
cleanup: () => (0, import_promises3.rm)(root, { recursive: true, force: true })
|
|
2417
|
+
};
|
|
2418
|
+
} catch (error) {
|
|
2419
|
+
await (0, import_promises3.rm)(root, { recursive: true, force: true });
|
|
2420
|
+
throw error;
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2304
2423
|
async function sourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
2305
2424
|
const files = [];
|
|
2306
2425
|
let bytes = 0;
|
|
2307
2426
|
const walk = async (dir) => {
|
|
2308
|
-
for (const entry of await (0,
|
|
2309
|
-
if (entry.isDirectory() &&
|
|
2310
|
-
if (!entry.isDirectory() &&
|
|
2311
|
-
const path = (0,
|
|
2427
|
+
for (const entry of await (0, import_promises4.readdir)(dir, { withFileTypes: true })) {
|
|
2428
|
+
if (entry.isDirectory() && SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
2429
|
+
if (!entry.isDirectory() && SECRET_WORKSPACE_FILE.test(entry.name)) continue;
|
|
2430
|
+
const path = (0, import_path4.join)(dir, entry.name);
|
|
2312
2431
|
if (entry.isSymbolicLink()) continue;
|
|
2313
2432
|
if (entry.isDirectory()) {
|
|
2314
2433
|
await walk(path);
|
|
2315
2434
|
continue;
|
|
2316
2435
|
}
|
|
2317
2436
|
if (!entry.isFile()) continue;
|
|
2318
|
-
const metadata2 = await (0,
|
|
2437
|
+
const metadata2 = await (0, import_promises4.stat)(path);
|
|
2319
2438
|
bytes += metadata2.size;
|
|
2320
2439
|
if (files.length + 1 > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
2321
2440
|
if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
|
|
2322
2441
|
files.push({
|
|
2323
2442
|
source: path,
|
|
2324
|
-
relativePath: (0,
|
|
2443
|
+
relativePath: (0, import_path4.relative)(sourceDir, path),
|
|
2325
2444
|
mode: metadata2.mode & 511,
|
|
2326
2445
|
bytes: metadata2.size
|
|
2327
2446
|
});
|
|
@@ -2330,16 +2449,62 @@ async function sourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
2330
2449
|
await walk(sourceDir);
|
|
2331
2450
|
return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
2332
2451
|
}
|
|
2452
|
+
async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
2453
|
+
const child = (0, import_child_process3.spawn)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
2454
|
+
cwd: sourceDir,
|
|
2455
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2456
|
+
shell: false
|
|
2457
|
+
});
|
|
2458
|
+
const stdout = [];
|
|
2459
|
+
const stderr = [];
|
|
2460
|
+
let outputBytes = 0;
|
|
2461
|
+
child.stdout.on("data", (chunk) => {
|
|
2462
|
+
outputBytes += chunk.byteLength;
|
|
2463
|
+
if (outputBytes > 8 * 1024 * 1024) child.kill("SIGKILL");
|
|
2464
|
+
else stdout.push(chunk);
|
|
2465
|
+
});
|
|
2466
|
+
child.stderr.on("data", (chunk) => {
|
|
2467
|
+
if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
|
|
2468
|
+
});
|
|
2469
|
+
const code = await new Promise((accept, reject) => {
|
|
2470
|
+
child.once("error", reject);
|
|
2471
|
+
child.once("exit", accept);
|
|
2472
|
+
});
|
|
2473
|
+
if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
|
|
2474
|
+
if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
|
|
2475
|
+
const paths = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
|
|
2476
|
+
if (paths.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
2477
|
+
const root = (0, import_path4.resolve)(sourceDir);
|
|
2478
|
+
const files = [];
|
|
2479
|
+
let bytes = 0;
|
|
2480
|
+
for (const relativePath of paths) {
|
|
2481
|
+
if (!allowedWorkspacePath(relativePath)) continue;
|
|
2482
|
+
const source = (0, import_path4.resolve)(root, relativePath);
|
|
2483
|
+
if (!source.startsWith(`${root}${import_path4.sep}`)) throw new TypeError("git file path escapes workspace");
|
|
2484
|
+
let metadata2;
|
|
2485
|
+
try {
|
|
2486
|
+
metadata2 = await (0, import_promises4.lstat)(source);
|
|
2487
|
+
} catch (error) {
|
|
2488
|
+
if (error.code === "ENOENT") continue;
|
|
2489
|
+
throw error;
|
|
2490
|
+
}
|
|
2491
|
+
if (metadata2.isSymbolicLink() || !metadata2.isFile()) continue;
|
|
2492
|
+
bytes += metadata2.size;
|
|
2493
|
+
if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
|
|
2494
|
+
files.push({ source, relativePath, mode: metadata2.mode & 511, bytes: metadata2.size });
|
|
2495
|
+
}
|
|
2496
|
+
return files;
|
|
2497
|
+
}
|
|
2333
2498
|
async function copyTree(files, destination) {
|
|
2334
2499
|
for (const file of files) {
|
|
2335
|
-
const target = (0,
|
|
2336
|
-
await (0,
|
|
2337
|
-
await (0,
|
|
2338
|
-
await (0,
|
|
2500
|
+
const target = (0, import_path4.join)(destination, file.relativePath);
|
|
2501
|
+
await (0, import_promises4.mkdir)((0, import_path4.resolve)(target, ".."), { recursive: true });
|
|
2502
|
+
await (0, import_promises4.copyFile)(file.source, target);
|
|
2503
|
+
await (0, import_promises4.chmod)(target, file.mode);
|
|
2339
2504
|
}
|
|
2340
2505
|
}
|
|
2341
2506
|
async function captureGitDiff(root, maxBytes) {
|
|
2342
|
-
const child = (0,
|
|
2507
|
+
const child = (0, import_child_process3.spawn)("git", [
|
|
2343
2508
|
"diff",
|
|
2344
2509
|
"--no-index",
|
|
2345
2510
|
"--binary",
|
|
@@ -2372,15 +2537,17 @@ async function captureGitDiff(root, maxBytes) {
|
|
|
2372
2537
|
return Buffer.concat(stdout).toString("utf8").replaceAll("a/baseline/", "a/").replaceAll("b/workspace/", "b/").replaceAll("--- a/baseline", "--- a").replaceAll("+++ b/workspace", "+++ b");
|
|
2373
2538
|
}
|
|
2374
2539
|
async function stageWorkspace(source, options = {}) {
|
|
2375
|
-
const sourceDir = await (0,
|
|
2376
|
-
const sourceStat = await (0,
|
|
2540
|
+
const sourceDir = await (0, import_promises4.realpath)((0, import_path4.resolve)(source));
|
|
2541
|
+
const sourceStat = await (0, import_promises4.stat)(sourceDir);
|
|
2377
2542
|
if (!sourceStat.isDirectory()) throw new TypeError("workspace source must be a directory");
|
|
2378
|
-
const root = await (0,
|
|
2379
|
-
const baselineDir = (0,
|
|
2380
|
-
const workspaceDir = (0,
|
|
2381
|
-
await Promise.all([(0,
|
|
2543
|
+
const root = await (0, import_promises4.mkdtemp)((0, import_path4.join)(options.tempRoot ?? (0, import_os2.tmpdir)(), "odla-harness-"));
|
|
2544
|
+
const baselineDir = (0, import_path4.join)(root, "baseline");
|
|
2545
|
+
const workspaceDir = (0, import_path4.join)(root, "workspace");
|
|
2546
|
+
await Promise.all([(0, import_promises4.mkdir)(baselineDir), (0, import_promises4.mkdir)(workspaceDir)]);
|
|
2382
2547
|
try {
|
|
2383
|
-
const
|
|
2548
|
+
const maxFiles = options.maxFiles ?? 2e4;
|
|
2549
|
+
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
2550
|
+
const files = options.gitTrackedAndUnignored ? await gitSourceFiles(sourceDir, maxFiles, maxBytes) : await sourceFiles(sourceDir, maxFiles, maxBytes);
|
|
2384
2551
|
await Promise.all([copyTree(files, baselineDir), copyTree(files, workspaceDir)]);
|
|
2385
2552
|
return {
|
|
2386
2553
|
root,
|
|
@@ -2388,14 +2555,48 @@ async function stageWorkspace(source, options = {}) {
|
|
|
2388
2555
|
workspaceDir,
|
|
2389
2556
|
fileCount: files.length,
|
|
2390
2557
|
byteCount: files.reduce((sum, file) => sum + file.bytes, 0),
|
|
2391
|
-
patch: (
|
|
2392
|
-
cleanup: () => (0,
|
|
2558
|
+
patch: (maxBytes2) => captureGitDiff(root, maxBytes2),
|
|
2559
|
+
cleanup: () => (0, import_promises4.rm)(root, { recursive: true, force: true })
|
|
2393
2560
|
};
|
|
2394
2561
|
} catch (error) {
|
|
2395
|
-
await (0,
|
|
2562
|
+
await (0, import_promises4.rm)(root, { recursive: true, force: true });
|
|
2396
2563
|
throw error;
|
|
2397
2564
|
}
|
|
2398
2565
|
}
|
|
2566
|
+
async function stageWorkspacePair(baselineSource, workspaceSource, options = {}) {
|
|
2567
|
+
const baselineDirSource = await (0, import_promises4.realpath)((0, import_path4.resolve)(baselineSource));
|
|
2568
|
+
const workspaceDirSource = await (0, import_promises4.realpath)((0, import_path4.resolve)(workspaceSource));
|
|
2569
|
+
const maxFiles = options.maxFiles ?? 2e4;
|
|
2570
|
+
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
2571
|
+
const [baselineFiles, workspaceFiles] = await Promise.all([
|
|
2572
|
+
sourceFiles(baselineDirSource, maxFiles, maxBytes),
|
|
2573
|
+
sourceFiles(workspaceDirSource, maxFiles, maxBytes)
|
|
2574
|
+
]);
|
|
2575
|
+
const root = await (0, import_promises4.mkdtemp)((0, import_path4.join)(options.tempRoot ?? (0, import_os2.tmpdir)(), "odla-harness-"));
|
|
2576
|
+
const baselineDir = (0, import_path4.join)(root, "baseline");
|
|
2577
|
+
const workspaceDir = (0, import_path4.join)(root, "workspace");
|
|
2578
|
+
await Promise.all([(0, import_promises4.mkdir)(baselineDir), (0, import_promises4.mkdir)(workspaceDir)]);
|
|
2579
|
+
try {
|
|
2580
|
+
await Promise.all([copyTree(baselineFiles, baselineDir), copyTree(workspaceFiles, workspaceDir)]);
|
|
2581
|
+
return {
|
|
2582
|
+
root,
|
|
2583
|
+
baselineDir,
|
|
2584
|
+
workspaceDir,
|
|
2585
|
+
fileCount: workspaceFiles.length,
|
|
2586
|
+
byteCount: workspaceFiles.reduce((sum, file) => sum + file.bytes, 0),
|
|
2587
|
+
patch: (maxPatchBytes) => captureGitDiff(root, maxPatchBytes),
|
|
2588
|
+
cleanup: () => (0, import_promises4.rm)(root, { recursive: true, force: true })
|
|
2589
|
+
};
|
|
2590
|
+
} catch (error) {
|
|
2591
|
+
await (0, import_promises4.rm)(root, { recursive: true, force: true });
|
|
2592
|
+
throw error;
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
// ../harness/dist/chunk-QEW6NNAO.js
|
|
2597
|
+
var import_crypto = require("crypto");
|
|
2598
|
+
var import_promises5 = require("fs/promises");
|
|
2599
|
+
var import_path5 = require("path");
|
|
2399
2600
|
|
|
2400
2601
|
// ../camel/dist/chunk-7FHPOQVP.js
|
|
2401
2602
|
var CamelError = class extends Error {
|
|
@@ -2415,8 +2616,8 @@ function canonicalJson(value) {
|
|
|
2415
2616
|
}
|
|
2416
2617
|
async function sha256Hex(value) {
|
|
2417
2618
|
const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
|
|
2418
|
-
const
|
|
2419
|
-
return [...new Uint8Array(
|
|
2619
|
+
const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
|
|
2620
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2420
2621
|
}
|
|
2421
2622
|
function utf8Length(value) {
|
|
2422
2623
|
if (typeof value === "string") return new TextEncoder().encode(value).byteLength;
|
|
@@ -2693,8 +2894,8 @@ function freeze(value) {
|
|
|
2693
2894
|
})
|
|
2694
2895
|
});
|
|
2695
2896
|
}
|
|
2696
|
-
function invalid2(
|
|
2697
|
-
return new CamelError("state_conflict",
|
|
2897
|
+
function invalid2(message2) {
|
|
2898
|
+
return new CamelError("state_conflict", message2);
|
|
2698
2899
|
}
|
|
2699
2900
|
var SHA4 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
2700
2901
|
var REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
@@ -2729,25 +2930,22 @@ function validateSnapshot(snapshot, limits) {
|
|
|
2729
2930
|
}
|
|
2730
2931
|
}
|
|
2731
2932
|
|
|
2732
|
-
// ../harness/dist/chunk-
|
|
2733
|
-
var import_child_process3 = require("child_process");
|
|
2734
|
-
var import_promises4 = require("fs/promises");
|
|
2735
|
-
var import_path3 = require("path");
|
|
2933
|
+
// ../harness/dist/chunk-QEW6NNAO.js
|
|
2736
2934
|
var import_child_process4 = require("child_process");
|
|
2935
|
+
var import_promises6 = require("fs/promises");
|
|
2936
|
+
var import_path6 = require("path");
|
|
2937
|
+
var import_child_process5 = require("child_process");
|
|
2737
2938
|
var import_process2 = require("process");
|
|
2738
|
-
var import_crypto = require("crypto");
|
|
2739
2939
|
var import_crypto2 = require("crypto");
|
|
2740
|
-
var import_fs2 = require("fs");
|
|
2741
|
-
var import_promises5 = require("fs/promises");
|
|
2742
|
-
var import_path4 = require("path");
|
|
2743
2940
|
var import_crypto3 = require("crypto");
|
|
2744
|
-
var
|
|
2745
|
-
var import_path5 = require("path");
|
|
2941
|
+
var import_fs2 = require("fs");
|
|
2746
2942
|
var import_promises7 = require("fs/promises");
|
|
2747
|
-
var import_os2 = require("os");
|
|
2748
|
-
var import_path6 = require("path");
|
|
2749
|
-
var import_promises8 = require("fs/promises");
|
|
2750
2943
|
var import_path7 = require("path");
|
|
2944
|
+
var import_promises8 = require("fs/promises");
|
|
2945
|
+
var import_os3 = require("os");
|
|
2946
|
+
var import_path8 = require("path");
|
|
2947
|
+
var import_promises9 = require("fs/promises");
|
|
2948
|
+
var import_path9 = require("path");
|
|
2751
2949
|
|
|
2752
2950
|
// ../camel/dist/chunk-LAXU2AVK.js
|
|
2753
2951
|
function conversionPolicyDigest(policy) {
|
|
@@ -3025,8 +3223,34 @@ function looksLikeDestination(value) {
|
|
|
3025
3223
|
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
|
|
3026
3224
|
}
|
|
3027
3225
|
|
|
3028
|
-
// ../harness/dist/chunk-
|
|
3226
|
+
// ../harness/dist/chunk-QEW6NNAO.js
|
|
3029
3227
|
var import_crypto4 = require("crypto");
|
|
3228
|
+
async function digestStagedWorkspace(root, limits) {
|
|
3229
|
+
const files = [];
|
|
3230
|
+
const walk = async (directory) => {
|
|
3231
|
+
const entries = await (0, import_promises5.readdir)(directory, { withFileTypes: true });
|
|
3232
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
3233
|
+
if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
|
|
3234
|
+
const target = (0, import_path5.resolve)(directory, entry.name);
|
|
3235
|
+
if (entry.isDirectory()) await walk(target);
|
|
3236
|
+
else if (entry.isFile()) {
|
|
3237
|
+
files.push({ path: (0, import_path5.relative)(root, target).split("\\").join("/"), target });
|
|
3238
|
+
if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
};
|
|
3242
|
+
await walk((0, import_path5.resolve)(root));
|
|
3243
|
+
const hash = (0, import_crypto.createHash)("sha256");
|
|
3244
|
+
let bytes = 0;
|
|
3245
|
+
for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
|
|
3246
|
+
const content = await (0, import_promises5.readFile)(file.target);
|
|
3247
|
+
bytes += Buffer.byteLength(file.path) + content.byteLength;
|
|
3248
|
+
if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
|
|
3249
|
+
hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
|
|
3250
|
+
hash.update(content);
|
|
3251
|
+
}
|
|
3252
|
+
return `sha256:${hash.digest("hex")}`;
|
|
3253
|
+
}
|
|
3030
3254
|
var CODE_RUNTIME_PROTOCOL_VERSION = 1;
|
|
3031
3255
|
async function runCodeRuntimeHeartbeatLoop(options) {
|
|
3032
3256
|
const heartbeatMs = options.heartbeatMs ?? 15e3;
|
|
@@ -3098,8 +3322,8 @@ function wait(ms, signal) {
|
|
|
3098
3322
|
});
|
|
3099
3323
|
}
|
|
3100
3324
|
var CodeRuntimeControlError = class extends Error {
|
|
3101
|
-
constructor(
|
|
3102
|
-
super(
|
|
3325
|
+
constructor(message2, status, code = "control_error") {
|
|
3326
|
+
super(message2);
|
|
3103
3327
|
this.status = status;
|
|
3104
3328
|
this.code = code;
|
|
3105
3329
|
}
|
|
@@ -3178,9 +3402,9 @@ function createCodeRuntimeControlClient(options) {
|
|
|
3178
3402
|
}
|
|
3179
3403
|
await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
|
|
3180
3404
|
},
|
|
3181
|
-
reportSessionFailure: async (sessionId,
|
|
3182
|
-
if (!
|
|
3183
|
-
await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message:
|
|
3405
|
+
reportSessionFailure: async (sessionId, message2) => {
|
|
3406
|
+
if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
|
|
3407
|
+
await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
|
|
3184
3408
|
}
|
|
3185
3409
|
};
|
|
3186
3410
|
}
|
|
@@ -3248,9 +3472,9 @@ async function parseSource(value) {
|
|
|
3248
3472
|
return { path: file.path, content: file.content };
|
|
3249
3473
|
});
|
|
3250
3474
|
const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
|
|
3251
|
-
const
|
|
3252
|
-
if (
|
|
3253
|
-
return { ...source, treeDigest:
|
|
3475
|
+
const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
|
|
3476
|
+
if (digest !== snapshot.treeDigest) throw invalid("source digest");
|
|
3477
|
+
return { ...source, treeDigest: digest };
|
|
3254
3478
|
}
|
|
3255
3479
|
function parseReview(value) {
|
|
3256
3480
|
const review = record3(record3(value)?.review);
|
|
@@ -3309,9 +3533,9 @@ function validateRelativePath(path) {
|
|
|
3309
3533
|
}
|
|
3310
3534
|
function resolveCodePath(workspaceDir, path) {
|
|
3311
3535
|
validateRelativePath(path);
|
|
3312
|
-
const root = (0,
|
|
3313
|
-
const target = (0,
|
|
3314
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
3536
|
+
const root = (0, import_path6.resolve)(workspaceDir);
|
|
3537
|
+
const target = (0, import_path6.resolve)(root, path);
|
|
3538
|
+
if (target !== root && !target.startsWith(`${root}${import_path6.sep}`)) throw new TypeError("path escapes the staged workspace");
|
|
3315
3539
|
return target;
|
|
3316
3540
|
}
|
|
3317
3541
|
async function applyCodePatch(workspaceDir, patch2, paths) {
|
|
@@ -3319,7 +3543,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
|
|
|
3319
3543
|
await gitApply(workspaceDir, patch2, false);
|
|
3320
3544
|
for (const path of paths) {
|
|
3321
3545
|
try {
|
|
3322
|
-
const info = await (0,
|
|
3546
|
+
const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
|
|
3323
3547
|
if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
|
|
3324
3548
|
throw new TypeError("patch created a non-regular workspace entry");
|
|
3325
3549
|
}
|
|
@@ -3331,7 +3555,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
|
|
|
3331
3555
|
function gitApply(cwd, patch2, check) {
|
|
3332
3556
|
return new Promise((accept, reject) => {
|
|
3333
3557
|
const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
|
|
3334
|
-
const child = (0,
|
|
3558
|
+
const child = (0, import_child_process4.spawn)("git", args, {
|
|
3335
3559
|
cwd,
|
|
3336
3560
|
shell: false,
|
|
3337
3561
|
stdio: ["pipe", "ignore", "pipe"],
|
|
@@ -3375,7 +3599,7 @@ async function restoreCodeWorkspaceCheckpoint(input) {
|
|
|
3375
3599
|
}
|
|
3376
3600
|
var ARTIFACT_PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
|
|
3377
3601
|
var PRIVATE_ARTIFACT_PART = /^(?:\.git|\.odla|\.wrangler|\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json)$/i;
|
|
3378
|
-
function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0,
|
|
3602
|
+
function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0, import_crypto2.randomUUID)().slice(0, 12)}`) {
|
|
3379
3603
|
assertCodeBuildRecipe(recipe2);
|
|
3380
3604
|
if (/[,\r\n]/.test(workspaceDir)) throw new TypeError("workspace path contains unsupported mount characters");
|
|
3381
3605
|
const uid = typeof import_process2.getuid === "function" ? (0, import_process2.getuid)() : 1e3;
|
|
@@ -3430,7 +3654,7 @@ function createContainerRecipeExecutor(engine) {
|
|
|
3430
3654
|
return {
|
|
3431
3655
|
async run(input) {
|
|
3432
3656
|
await verifyContainerEngineBoundary(engine);
|
|
3433
|
-
const name = `odla-recipe-${(0,
|
|
3657
|
+
const name = `odla-recipe-${(0, import_crypto2.randomUUID)().slice(0, 12)}`;
|
|
3434
3658
|
const args = buildRecipeContainerArgs(engine, input.workspaceDir, input.recipe, name);
|
|
3435
3659
|
return execute(engine, args, name, input.recipe, input.signal);
|
|
3436
3660
|
}
|
|
@@ -3454,7 +3678,7 @@ function parseMemory(value) {
|
|
|
3454
3678
|
function execute(engine, args, name, recipe2, signal) {
|
|
3455
3679
|
return new Promise((accept, reject) => {
|
|
3456
3680
|
const started = Date.now();
|
|
3457
|
-
const child = (0,
|
|
3681
|
+
const child = (0, import_child_process5.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
3458
3682
|
const stdout = [];
|
|
3459
3683
|
const stderr = [];
|
|
3460
3684
|
let bytes = 0;
|
|
@@ -3467,7 +3691,7 @@ function execute(engine, args, name, recipe2, signal) {
|
|
|
3467
3691
|
timedOut = reason === "timeout";
|
|
3468
3692
|
outputLimitExceeded = reason === "output";
|
|
3469
3693
|
const remove = engine === "container" ? ["delete", "--force", name] : ["rm", "-f", name];
|
|
3470
|
-
const killer = (0,
|
|
3694
|
+
const killer = (0, import_child_process5.spawn)(engine, remove, { shell: false, stdio: "ignore" });
|
|
3471
3695
|
killer.unref();
|
|
3472
3696
|
child.kill("SIGTERM");
|
|
3473
3697
|
};
|
|
@@ -3501,32 +3725,6 @@ function execute(engine, args, name, recipe2, signal) {
|
|
|
3501
3725
|
});
|
|
3502
3726
|
});
|
|
3503
3727
|
}
|
|
3504
|
-
async function digestStagedWorkspace(root, limits) {
|
|
3505
|
-
const files = [];
|
|
3506
|
-
const walk = async (directory) => {
|
|
3507
|
-
const entries = await (0, import_promises6.readdir)(directory, { withFileTypes: true });
|
|
3508
|
-
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
3509
|
-
if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
|
|
3510
|
-
const target = (0, import_path5.resolve)(directory, entry.name);
|
|
3511
|
-
if (entry.isDirectory()) await walk(target);
|
|
3512
|
-
else if (entry.isFile()) {
|
|
3513
|
-
files.push({ path: (0, import_path5.relative)(root, target).split("\\").join("/"), target });
|
|
3514
|
-
if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
|
|
3515
|
-
}
|
|
3516
|
-
}
|
|
3517
|
-
};
|
|
3518
|
-
await walk((0, import_path5.resolve)(root));
|
|
3519
|
-
const hash = (0, import_crypto3.createHash)("sha256");
|
|
3520
|
-
let bytes = 0;
|
|
3521
|
-
for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
|
|
3522
|
-
const content = await (0, import_promises6.readFile)(file.target);
|
|
3523
|
-
bytes += Buffer.byteLength(file.path) + content.byteLength;
|
|
3524
|
-
if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
|
|
3525
|
-
hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
|
|
3526
|
-
hash.update(content);
|
|
3527
|
-
}
|
|
3528
|
-
return `sha256:${hash.digest("hex")}`;
|
|
3529
|
-
}
|
|
3530
3728
|
var SHA3 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
3531
3729
|
var DIGEST3 = /^sha256:[0-9a-f]{64}$/;
|
|
3532
3730
|
var ID3 = /^[A-Za-z0-9._:-]{1,160}$/;
|
|
@@ -3574,7 +3772,7 @@ async function verifyCodeCandidate(input) {
|
|
|
3574
3772
|
}
|
|
3575
3773
|
const fields = {
|
|
3576
3774
|
schemaVersion: 1,
|
|
3577
|
-
verificationId: input.verificationId ?? `verify-${(0,
|
|
3775
|
+
verificationId: input.verificationId ?? `verify-${(0, import_crypto3.randomUUID)()}`,
|
|
3578
3776
|
trustedBaseCommitSha: input.trustedBaseCommitSha,
|
|
3579
3777
|
trustedBaseDigest: input.trustedBaseDigest,
|
|
3580
3778
|
patchDigest,
|
|
@@ -3635,8 +3833,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
|
|
|
3635
3833
|
const receipts = [];
|
|
3636
3834
|
for (const artifact of recipe2.expectedArtifacts ?? []) {
|
|
3637
3835
|
try {
|
|
3638
|
-
const path = (0,
|
|
3639
|
-
const info = await (0,
|
|
3836
|
+
const path = (0, import_path7.join)(workspaceDir, artifact.path);
|
|
3837
|
+
const info = await (0, import_promises7.lstat)(path);
|
|
3640
3838
|
if (!info.isFile() || info.isSymbolicLink()) {
|
|
3641
3839
|
receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
|
|
3642
3840
|
} else if (info.size > artifact.maximumBytes) {
|
|
@@ -3653,7 +3851,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
|
|
|
3653
3851
|
}
|
|
3654
3852
|
function hashFile(path) {
|
|
3655
3853
|
return new Promise((accept, reject) => {
|
|
3656
|
-
const hash = (0,
|
|
3854
|
+
const hash = (0, import_crypto3.createHash)("sha256");
|
|
3657
3855
|
const stream = (0, import_fs2.createReadStream)(path);
|
|
3658
3856
|
stream.on("data", (chunk) => {
|
|
3659
3857
|
hash.update(chunk);
|
|
@@ -3711,7 +3909,7 @@ function digestJson(value) {
|
|
|
3711
3909
|
return digestBytes(JSON.stringify(value));
|
|
3712
3910
|
}
|
|
3713
3911
|
function digestBytes(value) {
|
|
3714
|
-
return `sha256:${(0,
|
|
3912
|
+
return `sha256:${(0, import_crypto3.createHash)("sha256").update(value).digest("hex")}`;
|
|
3715
3913
|
}
|
|
3716
3914
|
function integer(value, minimum, maximum) {
|
|
3717
3915
|
return Number.isSafeInteger(value) && value >= minimum && value <= maximum;
|
|
@@ -3732,8 +3930,8 @@ async function prepareRuntimeCheckpoint(input) {
|
|
|
3732
3930
|
policy: {
|
|
3733
3931
|
policyId: "code.runtime",
|
|
3734
3932
|
recipes: input.recipes,
|
|
3735
|
-
maximumFiles:
|
|
3736
|
-
maximumBytes:
|
|
3933
|
+
maximumFiles: 2e4,
|
|
3934
|
+
maximumBytes: 512 * 1024 * 1024
|
|
3737
3935
|
},
|
|
3738
3936
|
recipeExecutor: input.recipeExecutor
|
|
3739
3937
|
});
|
|
@@ -3815,11 +4013,11 @@ var CodeRuntimeCheckpointManager = class {
|
|
|
3815
4013
|
};
|
|
3816
4014
|
var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
3817
4015
|
var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
3818
|
-
async function materializeCodeRuntimeSource(snapshot, tempRoot = (0,
|
|
4016
|
+
async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.tmpdir)()) {
|
|
3819
4017
|
if (!snapshot.files.length || snapshot.files.length > 1e4) throw new TypeError("Code source file count is invalid");
|
|
3820
|
-
const root = await (0,
|
|
3821
|
-
const sourceDir = (0,
|
|
3822
|
-
await (0,
|
|
4018
|
+
const root = await (0, import_promises8.mkdtemp)((0, import_path8.join)(tempRoot, "odla-code-source-"));
|
|
4019
|
+
const sourceDir = (0, import_path8.join)(root, "source");
|
|
4020
|
+
await (0, import_promises8.mkdir)(sourceDir);
|
|
3823
4021
|
const seen = /* @__PURE__ */ new Set();
|
|
3824
4022
|
let bytes = 0;
|
|
3825
4023
|
try {
|
|
@@ -3829,14 +4027,14 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os2.
|
|
|
3829
4027
|
seen.add(file.path);
|
|
3830
4028
|
bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
|
|
3831
4029
|
if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
|
|
3832
|
-
const target = (0,
|
|
3833
|
-
if (!target.startsWith(`${(0,
|
|
3834
|
-
await (0,
|
|
3835
|
-
await (0,
|
|
4030
|
+
const target = (0, import_path8.resolve)(sourceDir, file.path);
|
|
4031
|
+
if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code source path escapes its root");
|
|
4032
|
+
await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
|
|
4033
|
+
await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 420 });
|
|
3836
4034
|
}
|
|
3837
|
-
return { sourceDir, cleanup: () => (0,
|
|
4035
|
+
return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
|
|
3838
4036
|
} catch (cause) {
|
|
3839
|
-
await (0,
|
|
4037
|
+
await (0, import_promises8.rm)(root, { recursive: true, force: true });
|
|
3840
4038
|
throw cause;
|
|
3841
4039
|
}
|
|
3842
4040
|
}
|
|
@@ -3942,15 +4140,15 @@ async function environment(input, options, tool) {
|
|
|
3942
4140
|
{ id: "authority", value: `lease:${input.lease.leaseId}`, readers: input.readers },
|
|
3943
4141
|
{ id: "reader", value: options.readerId, readers: input.readers }
|
|
3944
4142
|
]);
|
|
3945
|
-
const
|
|
4143
|
+
const digest = await destinationRegistryDigest([input.workspaceId]);
|
|
3946
4144
|
const approvals = ["irreversible_mutation", "external_send", "financial", "secret_read"];
|
|
3947
4145
|
if (options.recipeAuthorization === "exact_approval") approvals.push("code_execution");
|
|
3948
4146
|
const policy = createEffectPolicy({
|
|
3949
|
-
destinationRegistries: { [DESTINATIONS]: { digest
|
|
4147
|
+
destinationRegistries: { [DESTINATIONS]: { digest, values: [input.workspaceId] } },
|
|
3950
4148
|
approvalEffects: approvals
|
|
3951
4149
|
});
|
|
3952
4150
|
const fixedArgs = {
|
|
3953
|
-
workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest:
|
|
4151
|
+
workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest },
|
|
3954
4152
|
authority: { role: "authority", value: ingress.control("authority") }
|
|
3955
4153
|
};
|
|
3956
4154
|
return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
|
|
@@ -4021,11 +4219,11 @@ async function read(context, request, options, policy) {
|
|
|
4021
4219
|
const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
|
|
4022
4220
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
4023
4221
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
4024
|
-
const info = await (0,
|
|
4222
|
+
const info = await (0, import_promises9.stat)(target);
|
|
4025
4223
|
if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
|
|
4026
4224
|
throw new TypeError("file is not a bounded regular source file");
|
|
4027
4225
|
}
|
|
4028
|
-
const source = await (0,
|
|
4226
|
+
const source = await (0, import_promises9.readFile)(target);
|
|
4029
4227
|
if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
|
|
4030
4228
|
const lines = source.toString("utf8").split("\n");
|
|
4031
4229
|
const content = lines.slice(startLine - 1, endLine).join("\n");
|
|
@@ -4099,12 +4297,12 @@ function policyContext(context, request, options, extra) {
|
|
|
4099
4297
|
async function registeredFiles(root, limit) {
|
|
4100
4298
|
const paths = [];
|
|
4101
4299
|
const walk = async (directory) => {
|
|
4102
|
-
for (const entry of await (0,
|
|
4300
|
+
for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
|
|
4103
4301
|
if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
|
|
4104
|
-
const target = (0,
|
|
4302
|
+
const target = (0, import_path9.resolve)(directory, entry.name);
|
|
4105
4303
|
if (entry.isDirectory()) await walk(target);
|
|
4106
4304
|
else if (entry.isFile()) {
|
|
4107
|
-
const path = (0,
|
|
4305
|
+
const path = (0, import_path9.relative)(root, target).split("\\").join("/");
|
|
4108
4306
|
try {
|
|
4109
4307
|
validateRelativePath(path);
|
|
4110
4308
|
} catch {
|
|
@@ -4115,7 +4313,7 @@ async function registeredFiles(root, limit) {
|
|
|
4115
4313
|
}
|
|
4116
4314
|
}
|
|
4117
4315
|
};
|
|
4118
|
-
await walk((0,
|
|
4316
|
+
await walk((0, import_path9.resolve)(root));
|
|
4119
4317
|
return paths.sort();
|
|
4120
4318
|
}
|
|
4121
4319
|
function validateOptions(options) {
|
|
@@ -4150,14 +4348,31 @@ function codeCommandMetadata(payload, resume) {
|
|
|
4150
4348
|
}
|
|
4151
4349
|
const planning = trusted?.planningInputDigest;
|
|
4152
4350
|
const attestation = trusted?.attestationDigest;
|
|
4351
|
+
const repository = trusted?.repository;
|
|
4352
|
+
const baseCommitSha = trusted?.commitSha;
|
|
4353
|
+
const sourceTreeDigest = trusted?.treeDigest;
|
|
4354
|
+
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)) {
|
|
4355
|
+
throw new TypeError(`invalid Code ${resume ? "resume" : "start"} trusted base`);
|
|
4356
|
+
}
|
|
4153
4357
|
return {
|
|
4154
4358
|
role,
|
|
4155
4359
|
title,
|
|
4156
4360
|
prompt,
|
|
4157
4361
|
planningInputDigest: typeof planning === "string" && /^sha256:[0-9a-f]{64}$/.test(planning) ? planning : null,
|
|
4158
|
-
attestationDigest: typeof attestation === "string" ? attestation : "resume"
|
|
4362
|
+
attestationDigest: typeof attestation === "string" ? attestation : "resume",
|
|
4363
|
+
repository,
|
|
4364
|
+
baseCommitSha,
|
|
4365
|
+
sourceTreeDigest
|
|
4159
4366
|
};
|
|
4160
4367
|
}
|
|
4368
|
+
function codeLocalSource(payload) {
|
|
4369
|
+
const source = record22(payload.source);
|
|
4370
|
+
if (!source) return null;
|
|
4371
|
+
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) {
|
|
4372
|
+
throw new TypeError("invalid local checkout source descriptor");
|
|
4373
|
+
}
|
|
4374
|
+
return source;
|
|
4375
|
+
}
|
|
4161
4376
|
function codeCheckpointPayload(payload) {
|
|
4162
4377
|
const value = payload.checkpoint;
|
|
4163
4378
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
|
|
@@ -4186,11 +4401,62 @@ function fakeCodeLease(command, metadata2) {
|
|
|
4186
4401
|
};
|
|
4187
4402
|
}
|
|
4188
4403
|
var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4404
|
+
var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
|
|
4405
|
+
async function prepareRuntimeLocalSource(input) {
|
|
4406
|
+
const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
|
|
4407
|
+
if (!available || JSON.stringify(available.descriptor) !== JSON.stringify(descriptor2) || descriptor2.repository.toLowerCase() !== repository.toLowerCase() || descriptor2.headCommitSha !== baseCommitSha) {
|
|
4408
|
+
throw new TypeError("the session's local checkout snapshot is not available on this terminal");
|
|
4409
|
+
}
|
|
4410
|
+
const workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
|
|
4411
|
+
trustedBaseDir: available.trustedBaseDir,
|
|
4412
|
+
trustedBaseCommitSha: baseCommitSha,
|
|
4413
|
+
checkpoint: codeCheckpointPayload(command.payload)
|
|
4414
|
+
})).workspace : await stageWorkspacePair(available.trustedBaseDir, available.sourceDir, SOURCE_LIMITS);
|
|
4415
|
+
const trustedBaseDigest = await digestStagedWorkspace(workspace.baselineDir, SOURCE_LIMITS);
|
|
4416
|
+
if (trustedBaseDigest !== descriptor2.trustedBaseDigest) {
|
|
4417
|
+
await workspace.cleanup();
|
|
4418
|
+
throw new TypeError("trusted Git base digest changed after connection");
|
|
4419
|
+
}
|
|
4420
|
+
if (!resume && await digestStagedWorkspace(workspace.workspaceDir, SOURCE_LIMITS) !== descriptor2.snapshotDigest) {
|
|
4421
|
+
await workspace.cleanup();
|
|
4422
|
+
throw new TypeError("local checkout snapshot digest changed after connection");
|
|
4423
|
+
}
|
|
4424
|
+
return { workspace, sourceDigest: descriptor2.snapshotDigest, trustedBaseDigest };
|
|
4425
|
+
}
|
|
4426
|
+
function createCodeRuntimeToolBroker(input, lease, role) {
|
|
4427
|
+
const broker = createCodeToolBroker({
|
|
4428
|
+
recipes: input.recipes,
|
|
4429
|
+
recipeExecutor: createContainerRecipeExecutor(input.engine),
|
|
4430
|
+
recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
|
|
4431
|
+
readerId: `code-session:${lease.task.taskId}`
|
|
4432
|
+
});
|
|
4433
|
+
return role === "coding" ? broker : { execute: (context, request) => request.tool === "sandbox.read" ? broker.execute(context, request) : Promise.resolve({ requestId: request.requestId, ok: false, content: "review sessions are read-only" }) };
|
|
4434
|
+
}
|
|
4435
|
+
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
4436
|
+
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
4437
|
+
refs.push(eventId);
|
|
4438
|
+
const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
|
|
4439
|
+
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
4440
|
+
}
|
|
4441
|
+
var digestRuntimeValue = (value) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value).digest("hex")}`;
|
|
4442
|
+
var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
|
|
4443
|
+
var runtimeRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4444
|
+
var safeRuntimeJson = (value) => {
|
|
4445
|
+
try {
|
|
4446
|
+
return JSON.stringify(value).slice(0, 1e4);
|
|
4447
|
+
} catch {
|
|
4448
|
+
return "[event]";
|
|
4449
|
+
}
|
|
4450
|
+
};
|
|
4451
|
+
function runtimeResultText(value) {
|
|
4452
|
+
const record32 = runtimeRecord(value);
|
|
4453
|
+
return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
|
|
4454
|
+
}
|
|
4189
4455
|
var CodePiRuntimeEngine = class {
|
|
4190
4456
|
constructor(options) {
|
|
4191
4457
|
this.options = options;
|
|
4192
4458
|
this.#run = options.runAttempt ?? runContainerAttempt;
|
|
4193
|
-
this.#buildPolicyDigest =
|
|
4459
|
+
this.#buildPolicyDigest = digestRuntimeValue(JSON.stringify(options.recipes));
|
|
4194
4460
|
this.#checkpoints = new CodeRuntimeCheckpointManager({
|
|
4195
4461
|
control: options.control,
|
|
4196
4462
|
recipes: options.recipes,
|
|
@@ -4225,19 +4491,35 @@ var CodePiRuntimeEngine = class {
|
|
|
4225
4491
|
}
|
|
4226
4492
|
async #start(command, resume) {
|
|
4227
4493
|
if (this.#active.has(command.sessionId)) throw new TypeError("Code session is already active on this runtime");
|
|
4228
|
-
const
|
|
4229
|
-
const
|
|
4494
|
+
const metadata2 = codeCommandMetadata(command.payload, resume);
|
|
4495
|
+
const requestedLocal = codeLocalSource(command.payload);
|
|
4230
4496
|
let workspace;
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4497
|
+
let sourceDigest;
|
|
4498
|
+
let localTrustedBaseDigest;
|
|
4499
|
+
if (requestedLocal) {
|
|
4500
|
+
const prepared = await prepareRuntimeLocalSource({
|
|
4501
|
+
command,
|
|
4502
|
+
descriptor: requestedLocal,
|
|
4503
|
+
available: this.options.localSource,
|
|
4504
|
+
repository: metadata2.repository,
|
|
4505
|
+
baseCommitSha: metadata2.baseCommitSha,
|
|
4506
|
+
resume
|
|
4507
|
+
});
|
|
4508
|
+
({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
|
|
4509
|
+
} else {
|
|
4510
|
+
const source = await this.options.control.source(command.sessionId);
|
|
4511
|
+
const materialized = await materializeCodeRuntimeSource(source);
|
|
4512
|
+
try {
|
|
4513
|
+
workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
|
|
4514
|
+
trustedBaseDir: materialized.sourceDir,
|
|
4515
|
+
trustedBaseCommitSha: source.commitSha,
|
|
4516
|
+
checkpoint: codeCheckpointPayload(command.payload)
|
|
4517
|
+
})).workspace : await stageWorkspace(materialized.sourceDir);
|
|
4518
|
+
} finally {
|
|
4519
|
+
await materialized.cleanup();
|
|
4520
|
+
}
|
|
4521
|
+
sourceDigest = source.treeDigest;
|
|
4239
4522
|
}
|
|
4240
|
-
const metadata2 = codeCommandMetadata(command.payload, resume);
|
|
4241
4523
|
const abort = new AbortController();
|
|
4242
4524
|
const conversationRefs = [];
|
|
4243
4525
|
const active = {
|
|
@@ -4247,21 +4529,30 @@ var CodePiRuntimeEngine = class {
|
|
|
4247
4529
|
acknowledged: false,
|
|
4248
4530
|
role: metadata2.role,
|
|
4249
4531
|
title: metadata2.title,
|
|
4250
|
-
baseCommitSha:
|
|
4251
|
-
|
|
4532
|
+
baseCommitSha: metadata2.baseCommitSha,
|
|
4533
|
+
repository: metadata2.repository,
|
|
4534
|
+
sourceTreeDigest: metadata2.sourceTreeDigest,
|
|
4535
|
+
trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
|
|
4252
4536
|
maxFiles: 1e4,
|
|
4253
4537
|
maxBytes: 16 * 1024 * 1024
|
|
4254
4538
|
}),
|
|
4255
|
-
planningInputDigest: metadata2.planningInputDigest ??
|
|
4256
|
-
JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree:
|
|
4539
|
+
planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
|
|
4540
|
+
JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })
|
|
4257
4541
|
),
|
|
4258
4542
|
done: Promise.resolve(null)
|
|
4259
4543
|
};
|
|
4260
4544
|
this.#active.set(command.sessionId, active);
|
|
4545
|
+
if (requestedLocal) {
|
|
4546
|
+
await this.#event(command, {
|
|
4547
|
+
type: "message",
|
|
4548
|
+
actor: "system",
|
|
4549
|
+
body: `Source snapshot: local checkout ${requestedLocal.snapshotDigest} \xB7 ${requestedLocal.modified ? "modified" : "clean"} \xB7 Git ${requestedLocal.headCommitSha}`
|
|
4550
|
+
}, conversationRefs);
|
|
4551
|
+
}
|
|
4261
4552
|
active.done = this.#runAttempt(command, metadata2, active).catch(async (cause) => {
|
|
4262
|
-
await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${
|
|
4553
|
+
await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` }, conversationRefs).catch(() => void 0);
|
|
4263
4554
|
await this.#event(command, { type: "status", status: "failed" }, conversationRefs).catch(() => void 0);
|
|
4264
|
-
await this.#failure(command, active,
|
|
4555
|
+
await this.#failure(command, active, runtimeErrorMessage(cause));
|
|
4265
4556
|
return null;
|
|
4266
4557
|
});
|
|
4267
4558
|
return { status: "running", message: resume ? "Pi resumed from a portable checkpoint" : "Pi started" };
|
|
@@ -4281,22 +4572,29 @@ var CodePiRuntimeEngine = class {
|
|
|
4281
4572
|
title: active.title,
|
|
4282
4573
|
prompt,
|
|
4283
4574
|
planningInputDigest: active.planningInputDigest,
|
|
4284
|
-
attestationDigest: "follow-up"
|
|
4575
|
+
attestationDigest: "follow-up",
|
|
4576
|
+
repository: active.repository,
|
|
4577
|
+
baseCommitSha: active.baseCommitSha,
|
|
4578
|
+
sourceTreeDigest: active.sourceTreeDigest
|
|
4285
4579
|
}, active).catch(async (cause) => {
|
|
4286
4580
|
await this.#event(
|
|
4287
4581
|
command,
|
|
4288
|
-
{ type: "message", actor: "system", body: `Pi failed: ${
|
|
4582
|
+
{ type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` },
|
|
4289
4583
|
active.conversationRefs
|
|
4290
4584
|
).catch(() => void 0);
|
|
4291
4585
|
await this.#event(command, { type: "status", status: "failed" }, active.conversationRefs).catch(() => void 0);
|
|
4292
|
-
await this.#failure(command, active,
|
|
4586
|
+
await this.#failure(command, active, runtimeErrorMessage(cause));
|
|
4293
4587
|
return null;
|
|
4294
4588
|
});
|
|
4295
4589
|
return { status: "running", message: "Pi accepted the owner prompt" };
|
|
4296
4590
|
}
|
|
4297
4591
|
async #runAttempt(command, metadata2, active) {
|
|
4298
4592
|
const lease = fakeCodeLease(command, metadata2);
|
|
4299
|
-
const broker =
|
|
4593
|
+
const broker = createCodeRuntimeToolBroker({
|
|
4594
|
+
recipes: this.options.recipes,
|
|
4595
|
+
engine: this.options.engine,
|
|
4596
|
+
recipeAuthorization: this.options.recipeAuthorization
|
|
4597
|
+
}, lease, metadata2.role);
|
|
4300
4598
|
const startedAt = Date.now();
|
|
4301
4599
|
let completionSeen = false;
|
|
4302
4600
|
const result = await this.#run({
|
|
@@ -4356,7 +4654,7 @@ var CodePiRuntimeEngine = class {
|
|
|
4356
4654
|
return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
|
|
4357
4655
|
}
|
|
4358
4656
|
if (output.type === "event") {
|
|
4359
|
-
const payload =
|
|
4657
|
+
const payload = runtimeRecord(output.payload);
|
|
4360
4658
|
if (output.kind === "pi.started") {
|
|
4361
4659
|
await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
|
|
4362
4660
|
} else if (output.kind === "pi.thinking" && payload?.available === true && Number.isSafeInteger(payload.durationMs) && Number(payload.durationMs) >= 0) {
|
|
@@ -4369,12 +4667,12 @@ var CodePiRuntimeEngine = class {
|
|
|
4369
4667
|
await this.#event(command, {
|
|
4370
4668
|
type: "message",
|
|
4371
4669
|
actor: "system",
|
|
4372
|
-
body: `${output.kind}${output.payload === void 0 ? "" : ` ${
|
|
4670
|
+
body: `${output.kind}${output.payload === void 0 ? "" : ` ${safeRuntimeJson(output.payload)}`}`
|
|
4373
4671
|
}, active.conversationRefs);
|
|
4374
4672
|
}
|
|
4375
4673
|
} else if (output.type === "attempt.complete") {
|
|
4376
4674
|
completionSeen = true;
|
|
4377
|
-
const body =
|
|
4675
|
+
const body = runtimeResultText(output.result) ?? `Pi ${output.status}.`;
|
|
4378
4676
|
await this.#event(command, {
|
|
4379
4677
|
type: "message",
|
|
4380
4678
|
actor: output.status === "completed" ? "agent" : "system",
|
|
@@ -4401,15 +4699,6 @@ var CodePiRuntimeEngine = class {
|
|
|
4401
4699
|
}
|
|
4402
4700
|
return result;
|
|
4403
4701
|
}
|
|
4404
|
-
#toolBroker(lease, role) {
|
|
4405
|
-
const broker = createCodeToolBroker({
|
|
4406
|
-
recipes: this.options.recipes,
|
|
4407
|
-
recipeExecutor: createContainerRecipeExecutor(this.options.engine),
|
|
4408
|
-
recipeAuthorization: this.options.recipeAuthorization ?? "registered_recipe",
|
|
4409
|
-
readerId: `code-session:${lease.task.taskId}`
|
|
4410
|
-
});
|
|
4411
|
-
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" }) };
|
|
4412
|
-
}
|
|
4413
4702
|
async #checkpoint(command) {
|
|
4414
4703
|
const active = this.#active.get(command.sessionId);
|
|
4415
4704
|
if (!active) throw new TypeError("Code session workspace is not active on this runtime");
|
|
@@ -4424,26 +4713,9 @@ var CodePiRuntimeEngine = class {
|
|
|
4424
4713
|
}
|
|
4425
4714
|
}
|
|
4426
4715
|
async #event(command, event, refs) {
|
|
4427
|
-
|
|
4428
|
-
refs.push(eventId);
|
|
4429
|
-
const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
|
|
4430
|
-
await this.options.control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
4431
|
-
}
|
|
4432
|
-
};
|
|
4433
|
-
var object2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4434
|
-
var digest = (value) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value).digest("hex")}`;
|
|
4435
|
-
var message2 = (value) => value instanceof Error ? value.message : String(value);
|
|
4436
|
-
var json = (value) => {
|
|
4437
|
-
try {
|
|
4438
|
-
return JSON.stringify(value).slice(0, 1e4);
|
|
4439
|
-
} catch {
|
|
4440
|
-
return "[event]";
|
|
4716
|
+
await appendCodeRuntimeEvent(this.options.control, command, event, refs);
|
|
4441
4717
|
}
|
|
4442
4718
|
};
|
|
4443
|
-
function resultText(value) {
|
|
4444
|
-
const record32 = object2(value);
|
|
4445
|
-
return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
|
|
4446
|
-
}
|
|
4447
4719
|
|
|
4448
4720
|
// src/help.ts
|
|
4449
4721
|
var import_node_fs6 = require("fs");
|
|
@@ -4585,8 +4857,8 @@ async function requestHostedSecurityJson(options, path, init, action) {
|
|
|
4585
4857
|
const body = await response2.json().catch(() => ({}));
|
|
4586
4858
|
if (!response2.ok) {
|
|
4587
4859
|
const code = optionalHostedText(body.error?.code, "error code", 128);
|
|
4588
|
-
const
|
|
4589
|
-
throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${
|
|
4860
|
+
const message2 = optionalHostedText(body.error?.message, "error message", 300);
|
|
4861
|
+
throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
|
|
4590
4862
|
}
|
|
4591
4863
|
return body;
|
|
4592
4864
|
}
|
|
@@ -4775,24 +5047,111 @@ async function defaultReadOrigin(cwd) {
|
|
|
4775
5047
|
return result.stdout;
|
|
4776
5048
|
}
|
|
4777
5049
|
|
|
4778
|
-
// src/code-
|
|
4779
|
-
var
|
|
5050
|
+
// src/code-local-source.ts
|
|
5051
|
+
var import_node_child_process3 = require("child_process");
|
|
5052
|
+
var import_node_crypto = require("crypto");
|
|
5053
|
+
var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
|
|
5054
|
+
async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
|
|
5055
|
+
const headCommitSha = await readHead(cwd);
|
|
5056
|
+
const staged = await stageWorkspace(cwd, { ...SOURCE_LIMITS2, gitTrackedAndUnignored: true });
|
|
5057
|
+
let trusted;
|
|
5058
|
+
try {
|
|
5059
|
+
trusted = await materializeGitTree(cwd, headCommitSha, SOURCE_LIMITS2);
|
|
5060
|
+
const comparison = await stageWorkspacePair(trusted.sourceDir, staged.baselineDir, SOURCE_LIMITS2);
|
|
5061
|
+
let developerPatch;
|
|
5062
|
+
try {
|
|
5063
|
+
developerPatch = await comparison.patch(4 * 1024 * 1024);
|
|
5064
|
+
} finally {
|
|
5065
|
+
await comparison.cleanup();
|
|
5066
|
+
}
|
|
5067
|
+
const descriptor2 = {
|
|
5068
|
+
kind: "local_checkout",
|
|
5069
|
+
repository,
|
|
5070
|
+
headCommitSha,
|
|
5071
|
+
trustedBaseDigest: await digestStagedWorkspace(trusted.sourceDir, SOURCE_LIMITS2),
|
|
5072
|
+
developerPatchDigest: digestText(developerPatch),
|
|
5073
|
+
snapshotDigest: await digestStagedWorkspace(staged.baselineDir, SOURCE_LIMITS2),
|
|
5074
|
+
modified: developerPatch.length > 0,
|
|
5075
|
+
fileCount: staged.fileCount,
|
|
5076
|
+
byteCount: staged.byteCount,
|
|
5077
|
+
capturedAt: Date.now()
|
|
5078
|
+
};
|
|
5079
|
+
return {
|
|
5080
|
+
descriptor: descriptor2,
|
|
5081
|
+
trustedBaseDir: trusted.sourceDir,
|
|
5082
|
+
sourceDir: staged.baselineDir,
|
|
5083
|
+
cleanup: async () => {
|
|
5084
|
+
await Promise.all([staged.cleanup(), trusted.cleanup()]);
|
|
5085
|
+
}
|
|
5086
|
+
};
|
|
5087
|
+
} catch (error) {
|
|
5088
|
+
await Promise.all([staged.cleanup(), trusted?.cleanup()]);
|
|
5089
|
+
throw error;
|
|
5090
|
+
}
|
|
5091
|
+
}
|
|
5092
|
+
async function readGitHead(cwd) {
|
|
5093
|
+
const value = await new Promise((accept, reject) => {
|
|
5094
|
+
(0, import_node_child_process3.execFile)("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
|
|
5095
|
+
if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
|
|
5096
|
+
else accept(stdout.trim());
|
|
5097
|
+
});
|
|
5098
|
+
});
|
|
5099
|
+
if (!/^[0-9a-f]{40}$/.test(value)) throw new Error("code connect could not resolve the checkout HEAD commit");
|
|
5100
|
+
return value;
|
|
5101
|
+
}
|
|
5102
|
+
function digestText(value) {
|
|
5103
|
+
return `sha256:${(0, import_node_crypto.createHash)("sha256").update(value).digest("hex")}`;
|
|
5104
|
+
}
|
|
5105
|
+
|
|
5106
|
+
// src/code-images.ts
|
|
5107
|
+
var import_node_child_process4 = require("child_process");
|
|
5108
|
+
async function prepareCodeImages(engine, images) {
|
|
5109
|
+
for (const image of images) {
|
|
5110
|
+
await new Promise((accept, reject) => {
|
|
5111
|
+
const args = engine === "container" ? ["image", "pull", image] : ["pull", image];
|
|
5112
|
+
(0, import_node_child_process4.execFile)(engine, args, { encoding: "utf8", maxBuffer: 2 * 1024 * 1024, timeout: 10 * 6e4 }, (error) => {
|
|
5113
|
+
if (error) reject(new Error(`could not prepare pinned Code image ${image}`));
|
|
5114
|
+
else accept();
|
|
5115
|
+
});
|
|
5116
|
+
});
|
|
5117
|
+
}
|
|
5118
|
+
}
|
|
5119
|
+
|
|
5120
|
+
// src/code-runtime-config.ts
|
|
5121
|
+
var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:e7858b4f0291543171b2659b47c5d00f97acc4767cafd9b74ae5228899e0cde4";
|
|
4780
5122
|
var CODE_BUILD_RECIPES = Object.freeze([{
|
|
4781
|
-
id: "
|
|
5123
|
+
id: "odla-code-contracts",
|
|
4782
5124
|
image: "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd",
|
|
4783
|
-
command: [
|
|
5125
|
+
command: [
|
|
5126
|
+
"node",
|
|
5127
|
+
"--test",
|
|
5128
|
+
"scripts/lib/directories.test.mjs",
|
|
5129
|
+
"scripts/lib/internal-deps.test.mjs",
|
|
5130
|
+
"scripts/lib/npm-audit.test.mjs",
|
|
5131
|
+
"scripts/lib/npm-release-contract.test.mjs",
|
|
5132
|
+
"scripts/lib/release-surfaces.test.mjs"
|
|
5133
|
+
],
|
|
4784
5134
|
timeoutMs: 12e4,
|
|
4785
5135
|
maxOutputBytes: 1024 * 1024,
|
|
4786
5136
|
cpus: 1,
|
|
4787
5137
|
memory: "512m",
|
|
4788
5138
|
pids: 128
|
|
4789
5139
|
}]);
|
|
5140
|
+
|
|
5141
|
+
// src/code-connect.ts
|
|
4790
5142
|
async function codeConnect(options) {
|
|
4791
5143
|
const cwd = options.cwd ?? process.cwd();
|
|
4792
5144
|
const configPath = (0, import_node_path4.resolve)(cwd, options.configPath);
|
|
4793
5145
|
const cfg = (0, import_node_fs7.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
|
|
4794
|
-
const
|
|
4795
|
-
|
|
5146
|
+
const requestedAppId = options.appId?.trim();
|
|
5147
|
+
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
5148
|
+
throw new Error("--app-id must be a valid odla app id");
|
|
5149
|
+
}
|
|
5150
|
+
if (requestedAppId && cfg && requestedAppId !== cfg.app.id) {
|
|
5151
|
+
throw new Error(`--app-id ${requestedAppId} does not match ${options.configPath} (${cfg.app.id})`);
|
|
5152
|
+
}
|
|
5153
|
+
const appId = requestedAppId ?? cfg?.app.id;
|
|
5154
|
+
const platform = (options.platform ?? cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai").replace(/\/+$/, "");
|
|
4796
5155
|
const appEnv = options.env ?? (cfg ? cfg.envs.includes("dev") ? "dev" : cfg.envs[0] : "dev");
|
|
4797
5156
|
if (appEnv !== "dev" && appEnv !== "prod" || cfg && !cfg.envs.includes(appEnv)) {
|
|
4798
5157
|
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`);
|
|
@@ -4811,53 +5170,77 @@ async function codeConnect(options) {
|
|
|
4811
5170
|
const out = options.stdout ?? console;
|
|
4812
5171
|
const doFetch = options.fetch ?? fetch;
|
|
4813
5172
|
const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
|
|
5173
|
+
await (options.prepareImages ?? prepareCodeImages)(engine, [
|
|
5174
|
+
CODE_PI_IMAGE,
|
|
5175
|
+
...new Set(CODE_BUILD_RECIPES.map((recipe2) => recipe2.image))
|
|
5176
|
+
]);
|
|
4814
5177
|
const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
|
|
4815
5178
|
const hostName = (options.name ?? (0, import_node_os.hostname)()).trim();
|
|
4816
5179
|
if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
|
|
4817
|
-
const
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
5180
|
+
const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
|
|
5181
|
+
const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
|
|
5182
|
+
cwd,
|
|
5183
|
+
repository,
|
|
5184
|
+
options.readGitHead
|
|
5185
|
+
);
|
|
5186
|
+
try {
|
|
5187
|
+
const descriptor2 = localSource.descriptor;
|
|
5188
|
+
const approval = await (options.getToken ?? getScopedPlatformToken)({
|
|
5189
|
+
platform,
|
|
5190
|
+
scope: "app:code:host:connect",
|
|
5191
|
+
email: options.email,
|
|
5192
|
+
open: options.open,
|
|
5193
|
+
fetch: doFetch,
|
|
5194
|
+
stdout: out,
|
|
5195
|
+
openApprovalUrl: options.openApprovalUrl,
|
|
5196
|
+
cache: false,
|
|
5197
|
+
label: `Terminal connection for ${appId ?? repository}/${appEnv}`
|
|
5198
|
+
});
|
|
5199
|
+
const target = appId ? { appId } : { repository };
|
|
5200
|
+
const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
|
|
5201
|
+
method: "POST",
|
|
5202
|
+
headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
|
|
5203
|
+
body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
|
|
5204
|
+
redirect: "error",
|
|
5205
|
+
signal: options.signal
|
|
5206
|
+
});
|
|
5207
|
+
const raw = await response2.json().catch(() => null);
|
|
5208
|
+
if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
|
|
5209
|
+
const connection = parseConnection(raw, appId, appEnv);
|
|
5210
|
+
const capabilities = {
|
|
5211
|
+
protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
|
|
5212
|
+
platform: hostPlatform,
|
|
5213
|
+
arch: process.arch,
|
|
5214
|
+
engines: [engine],
|
|
5215
|
+
cpuCount: (0, import_node_os.cpus)().length,
|
|
5216
|
+
memoryBytes: (0, import_node_os.totalmem)(),
|
|
5217
|
+
source: descriptor2,
|
|
5218
|
+
images: {
|
|
5219
|
+
ready: true,
|
|
5220
|
+
pi: CODE_PI_IMAGE,
|
|
5221
|
+
recipes: CODE_BUILD_RECIPES.map((recipe2) => ({ id: recipe2.id, image: recipe2.image }))
|
|
5222
|
+
}
|
|
5223
|
+
};
|
|
5224
|
+
out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
|
|
5225
|
+
out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
|
|
5226
|
+
out.log(`source: local checkout \xB7 ${descriptor2.headCommitSha.slice(0, 10)} \xB7 ${descriptor2.fileCount} files \xB7 staged copy`);
|
|
5227
|
+
out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
|
|
5228
|
+
out.log(`Studio: ${platform}/studio/apps/${encodeURIComponent(connection.binding.appId)}/${connection.binding.env}/code/sessions`);
|
|
5229
|
+
await (options.runRuntime ?? runCodeRuntime)({
|
|
5230
|
+
endpoint: platform,
|
|
5231
|
+
token: connection.token,
|
|
5232
|
+
engine,
|
|
5233
|
+
capabilities,
|
|
5234
|
+
localSource,
|
|
5235
|
+
heartbeatMs,
|
|
5236
|
+
once: options.once === true,
|
|
5237
|
+
signal: options.signal,
|
|
5238
|
+
stdout: out,
|
|
5239
|
+
fetch: doFetch
|
|
5240
|
+
});
|
|
5241
|
+
} finally {
|
|
5242
|
+
await localSource.cleanup();
|
|
5243
|
+
}
|
|
4861
5244
|
}
|
|
4862
5245
|
async function runCodeRuntime(input) {
|
|
4863
5246
|
const controller = new AbortController();
|
|
@@ -4880,7 +5263,8 @@ async function runCodeRuntime(input) {
|
|
|
4880
5263
|
engine: input.engine,
|
|
4881
5264
|
image: CODE_PI_IMAGE,
|
|
4882
5265
|
recipes: CODE_BUILD_RECIPES,
|
|
4883
|
-
recipeAuthorization: "registered_recipe"
|
|
5266
|
+
recipeAuthorization: "registered_recipe",
|
|
5267
|
+
localSource: input.localSource
|
|
4884
5268
|
});
|
|
4885
5269
|
const reconciler = new CodeRuntimeReconciler(control, commandEngine);
|
|
4886
5270
|
try {
|
|
@@ -4921,8 +5305,8 @@ function parseConnection(value, appId, appEnv) {
|
|
|
4921
5305
|
return root;
|
|
4922
5306
|
}
|
|
4923
5307
|
function apiFailure(action, status, value) {
|
|
4924
|
-
const
|
|
4925
|
-
return `${action} failed (${status})${typeof
|
|
5308
|
+
const message2 = record4(record4(value)?.error)?.message;
|
|
5309
|
+
return `${action} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
4926
5310
|
}
|
|
4927
5311
|
function record4(value) {
|
|
4928
5312
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -4937,6 +5321,8 @@ async function codeCommand(parsed, dependencies) {
|
|
|
4937
5321
|
assertArgs(parsed, [
|
|
4938
5322
|
"config",
|
|
4939
5323
|
"env",
|
|
5324
|
+
"platform",
|
|
5325
|
+
"app-id",
|
|
4940
5326
|
"email",
|
|
4941
5327
|
"open",
|
|
4942
5328
|
"name",
|
|
@@ -4952,6 +5338,8 @@ async function codeCommand(parsed, dependencies) {
|
|
|
4952
5338
|
await (dependencies.codeConnect ?? codeConnect)({
|
|
4953
5339
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
4954
5340
|
env: stringOpt(parsed.options.env),
|
|
5341
|
+
platform: stringOpt(parsed.options.platform),
|
|
5342
|
+
appId: stringOpt(parsed.options["app-id"]),
|
|
4955
5343
|
email: stringOpt(parsed.options.email),
|
|
4956
5344
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
4957
5345
|
name: stringOpt(parsed.options.name),
|
|
@@ -4967,16 +5355,16 @@ async function codeCommand(parsed, dependencies) {
|
|
|
4967
5355
|
}
|
|
4968
5356
|
|
|
4969
5357
|
// src/doctor-checks.ts
|
|
4970
|
-
var
|
|
5358
|
+
var import_node_child_process6 = require("child_process");
|
|
4971
5359
|
var import_node_fs9 = require("fs");
|
|
4972
5360
|
var import_node_path6 = require("path");
|
|
4973
5361
|
|
|
4974
5362
|
// src/wrangler.ts
|
|
4975
|
-
var
|
|
5363
|
+
var import_node_child_process5 = require("child_process");
|
|
4976
5364
|
var import_node_fs8 = require("fs");
|
|
4977
5365
|
var import_node_path5 = require("path");
|
|
4978
5366
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
4979
|
-
const child = (0,
|
|
5367
|
+
const child = (0, import_node_child_process5.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
4980
5368
|
let stdout = "";
|
|
4981
5369
|
let stderr = "";
|
|
4982
5370
|
child.stdout.on("data", (chunk) => stdout += chunk.toString());
|
|
@@ -5067,7 +5455,7 @@ function lintRules(rules, entities, publicRead) {
|
|
|
5067
5455
|
}
|
|
5068
5456
|
return warnings;
|
|
5069
5457
|
}
|
|
5070
|
-
var defaultExec = (cmd, args, cwd) => (0,
|
|
5458
|
+
var defaultExec = (cmd, args, cwd) => (0, import_node_child_process6.execFileSync)(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
5071
5459
|
function trackedSecretFiles(rootDir, exec = defaultExec, localPaths = []) {
|
|
5072
5460
|
let output;
|
|
5073
5461
|
try {
|
|
@@ -5675,8 +6063,8 @@ async function readRegistryApp(cfg, token, doFetch) {
|
|
|
5675
6063
|
});
|
|
5676
6064
|
if (res.status === 404) return null;
|
|
5677
6065
|
if (!res.ok) return null;
|
|
5678
|
-
const
|
|
5679
|
-
return
|
|
6066
|
+
const json = await res.json();
|
|
6067
|
+
return json.app ?? null;
|
|
5680
6068
|
}
|
|
5681
6069
|
async function postJson2(doFetch, url, bearer, body) {
|
|
5682
6070
|
const res = await doFetch(url, {
|
|
@@ -5915,10 +6303,10 @@ async function provision(options) {
|
|
|
5915
6303
|
stdout: out
|
|
5916
6304
|
});
|
|
5917
6305
|
} catch (error) {
|
|
5918
|
-
const
|
|
6306
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
5919
6307
|
const consent = env === "prod" || env === "production" ? " --yes" : "";
|
|
5920
6308
|
throw new Error(
|
|
5921
|
-
`${
|
|
6309
|
+
`${message2}
|
|
5922
6310
|
${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}${consent}" without issuing or rotating again`,
|
|
5923
6311
|
{ cause: error }
|
|
5924
6312
|
);
|
|
@@ -6034,7 +6422,7 @@ async function resolveVaultWrite(options) {
|
|
|
6034
6422
|
}
|
|
6035
6423
|
|
|
6036
6424
|
// src/security-command-context.ts
|
|
6037
|
-
var
|
|
6425
|
+
var import_promises10 = require("readline/promises");
|
|
6038
6426
|
async function hostedSecurityContext(parsed, dependencies) {
|
|
6039
6427
|
const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
6040
6428
|
const cfg = await loadProjectConfig(configPath);
|
|
@@ -6057,12 +6445,12 @@ async function hostedSecurityContext(parsed, dependencies) {
|
|
|
6057
6445
|
);
|
|
6058
6446
|
return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
|
|
6059
6447
|
}
|
|
6060
|
-
async function interactiveConfirmation(
|
|
6061
|
-
if (dependencies.confirm) return dependencies.confirm(
|
|
6448
|
+
async function interactiveConfirmation(message2, dependencies) {
|
|
6449
|
+
if (dependencies.confirm) return dependencies.confirm(message2);
|
|
6062
6450
|
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
6063
|
-
const prompt = (0,
|
|
6451
|
+
const prompt = (0, import_promises10.createInterface)({ input: process.stdin, output: process.stdout });
|
|
6064
6452
|
try {
|
|
6065
|
-
const answer = await prompt.question(`${
|
|
6453
|
+
const answer = await prompt.question(`${message2} [y/N] `);
|
|
6066
6454
|
return /^y(?:es)?$/i.test(answer.trim());
|
|
6067
6455
|
} finally {
|
|
6068
6456
|
prompt.close();
|
|
@@ -6189,7 +6577,7 @@ var import_security2 = require("@odla-ai/security");
|
|
|
6189
6577
|
// src/security.ts
|
|
6190
6578
|
var import_node_path8 = require("path");
|
|
6191
6579
|
var import_security = require("@odla-ai/security");
|
|
6192
|
-
var
|
|
6580
|
+
var import_node3 = require("@odla-ai/security/node");
|
|
6193
6581
|
async function runHostedSecurity(options) {
|
|
6194
6582
|
if (options.sourceDisclosureAck !== "redacted") {
|
|
6195
6583
|
throw new Error("Hosted security requires sourceDisclosureAck=redacted before repository source may leave this process");
|
|
@@ -6212,7 +6600,7 @@ async function runHostedSecurity(options) {
|
|
|
6212
6600
|
scope: selfAudit ? "platform:security:self" : null
|
|
6213
6601
|
};
|
|
6214
6602
|
const token = await injectedToken(options, tokenRequest);
|
|
6215
|
-
const snapshot = await (0,
|
|
6603
|
+
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
6216
6604
|
exclude: !outputRelative.startsWith("../") && !(0, import_node_path8.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
6217
6605
|
});
|
|
6218
6606
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
@@ -6231,7 +6619,7 @@ async function runHostedSecurity(options) {
|
|
|
6231
6619
|
});
|
|
6232
6620
|
const harness = (0, import_security.createSecurityHarness)({
|
|
6233
6621
|
profile,
|
|
6234
|
-
store: new
|
|
6622
|
+
store: new import_node3.FileRunStore((0, import_node_path8.resolve)(output, "state")),
|
|
6235
6623
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
6236
6624
|
validationReasoner: hosted.validationReasoner,
|
|
6237
6625
|
policy: {
|
|
@@ -6241,7 +6629,7 @@ async function runHostedSecurity(options) {
|
|
|
6241
6629
|
}
|
|
6242
6630
|
});
|
|
6243
6631
|
const report2 = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
|
|
6244
|
-
await (0,
|
|
6632
|
+
await (0, import_node3.writeSecurityArtifacts)(output, report2);
|
|
6245
6633
|
const reportDigest = await (0, import_security.securityFingerprint)(report2);
|
|
6246
6634
|
await hosted.complete({
|
|
6247
6635
|
reportDigest,
|
|
@@ -6374,8 +6762,8 @@ async function startHostedSecurityJob(options) {
|
|
|
6374
6762
|
"start hosted security job"
|
|
6375
6763
|
);
|
|
6376
6764
|
} catch (error) {
|
|
6377
|
-
const
|
|
6378
|
-
if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(
|
|
6765
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
6766
|
+
if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message2)) {
|
|
6379
6767
|
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 });
|
|
6380
6768
|
}
|
|
6381
6769
|
throw error;
|