@odla-ai/cli 0.14.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -15
- package/dist/bin.cjs +740 -280
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-AN6KZMR5.js → chunk-S3OYXHDG.js} +745 -285
- package/dist/chunk-S3OYXHDG.js.map +1 -0
- package/dist/index.cjs +740 -280
- 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
|
@@ -403,12 +403,13 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
403
403
|
const { token, expiresAt } = await requestToken2({
|
|
404
404
|
endpoint: audience,
|
|
405
405
|
email,
|
|
406
|
-
label: options.label ?? `odla CLI
|
|
406
|
+
label: options.label ?? `odla CLI (${scope})`,
|
|
407
407
|
scopes: [scope],
|
|
408
408
|
fetch: doFetch,
|
|
409
409
|
onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
|
|
410
410
|
const approvalUrl = verificationUriComplete ?? handshakeUrl(audience, userCode);
|
|
411
|
-
|
|
411
|
+
const requestName = scope === "app:code:host:connect" ? "terminal connection" : "scoped request";
|
|
412
|
+
out.log(`Review, then approve ${requestName} ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
|
|
412
413
|
if (approvalBrowser({ open: options.open }).open) {
|
|
413
414
|
await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
|
|
414
415
|
}
|
|
@@ -505,8 +506,8 @@ async function responseBody(response2) {
|
|
|
505
506
|
}
|
|
506
507
|
function apiError(status, body) {
|
|
507
508
|
const error = isRecord(body) && isRecord(body.error) ? body.error : void 0;
|
|
508
|
-
const
|
|
509
|
-
return `read System AI admin changes failed (${status}): ${
|
|
509
|
+
const message2 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
510
|
+
return `read System AI admin changes failed (${status}): ${message2}`;
|
|
510
511
|
}
|
|
511
512
|
function timestamp(value) {
|
|
512
513
|
if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
|
|
@@ -598,8 +599,8 @@ async function responseBody2(res) {
|
|
|
598
599
|
}
|
|
599
600
|
function apiError2(action, status, body) {
|
|
600
601
|
const error = isRecord2(body) && isRecord2(body.error) ? body.error : void 0;
|
|
601
|
-
const
|
|
602
|
-
return `${action} failed (${status}): ${
|
|
602
|
+
const message2 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
603
|
+
return `${action} failed (${status}): ${message2}`;
|
|
603
604
|
}
|
|
604
605
|
function isRecord2(value) {
|
|
605
606
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -779,8 +780,8 @@ async function responseBody3(res) {
|
|
|
779
780
|
}
|
|
780
781
|
function apiError3(action, status, body) {
|
|
781
782
|
const error = isRecord3(body) && isRecord3(body.error) ? body.error : void 0;
|
|
782
|
-
const
|
|
783
|
-
return `${action} failed (${status}): ${
|
|
783
|
+
const message2 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
784
|
+
return `${action} failed (${status}): ${message2}`;
|
|
784
785
|
}
|
|
785
786
|
function isRecord3(value) {
|
|
786
787
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -1110,8 +1111,8 @@ var CAPABILITIES = {
|
|
|
1110
1111
|
"connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"
|
|
1111
1112
|
]
|
|
1112
1113
|
};
|
|
1113
|
-
function printCapabilities(
|
|
1114
|
-
if (
|
|
1114
|
+
function printCapabilities(json = false, out = console) {
|
|
1115
|
+
if (json) {
|
|
1115
1116
|
out.log(JSON.stringify(CAPABILITIES, null, 2));
|
|
1116
1117
|
return;
|
|
1117
1118
|
}
|
|
@@ -1160,8 +1161,8 @@ var CalendarRequestError = class extends Error {
|
|
|
1160
1161
|
status;
|
|
1161
1162
|
/** Machine-readable `error.code` from the response body, when present. */
|
|
1162
1163
|
code;
|
|
1163
|
-
constructor(
|
|
1164
|
-
super(
|
|
1164
|
+
constructor(message2, status, code) {
|
|
1165
|
+
super(message2);
|
|
1165
1166
|
this.name = "CalendarRequestError";
|
|
1166
1167
|
this.status = status;
|
|
1167
1168
|
if (code !== void 0) this.code = code;
|
|
@@ -1309,8 +1310,8 @@ async function calendarJson(ctx, suffix, init) {
|
|
|
1309
1310
|
const body = await response2.json().catch(() => ({}));
|
|
1310
1311
|
if (!response2.ok) {
|
|
1311
1312
|
const code = textField(body.error?.code, 128);
|
|
1312
|
-
const
|
|
1313
|
-
const detail = `${code ? ` ${code}` : ""}${
|
|
1313
|
+
const message2 = textField(body.error?.message, 500);
|
|
1314
|
+
const detail = `${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`;
|
|
1314
1315
|
throw new CalendarRequestError(redactSecrets(`calendar request failed (${response2.status})${detail}`), response2.status, code);
|
|
1315
1316
|
}
|
|
1316
1317
|
return body;
|
|
@@ -1546,8 +1547,8 @@ function pollTimeout(value = 10 * 6e4) {
|
|
|
1546
1547
|
function productionConsent(env, yes, action) {
|
|
1547
1548
|
if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action} for "${env}" without --yes`);
|
|
1548
1549
|
}
|
|
1549
|
-
function printStatus(status,
|
|
1550
|
-
if (
|
|
1550
|
+
function printStatus(status, json, out) {
|
|
1551
|
+
if (json) {
|
|
1551
1552
|
out.log(JSON.stringify(status, null, 2));
|
|
1552
1553
|
return;
|
|
1553
1554
|
}
|
|
@@ -1583,8 +1584,8 @@ async function requestHostedSecurityJson(options, path, init, action) {
|
|
|
1583
1584
|
const body = await response2.json().catch(() => ({}));
|
|
1584
1585
|
if (!response2.ok) {
|
|
1585
1586
|
const code = optionalHostedText(body.error?.code, "error code", 128);
|
|
1586
|
-
const
|
|
1587
|
-
throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${
|
|
1587
|
+
const message2 = optionalHostedText(body.error?.message, "error message", 300);
|
|
1588
|
+
throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
|
|
1588
1589
|
}
|
|
1589
1590
|
return body;
|
|
1590
1591
|
}
|
|
@@ -1773,6 +1774,27 @@ async function defaultReadOrigin(cwd) {
|
|
|
1773
1774
|
return result.stdout;
|
|
1774
1775
|
}
|
|
1775
1776
|
|
|
1777
|
+
// src/code-runtime-config.ts
|
|
1778
|
+
var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:e7858b4f0291543171b2659b47c5d00f97acc4767cafd9b74ae5228899e0cde4";
|
|
1779
|
+
var CODE_BUILD_RECIPES = Object.freeze([{
|
|
1780
|
+
id: "odla-code-contracts",
|
|
1781
|
+
image: "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd",
|
|
1782
|
+
command: [
|
|
1783
|
+
"node",
|
|
1784
|
+
"--test",
|
|
1785
|
+
"scripts/lib/directories.test.mjs",
|
|
1786
|
+
"scripts/lib/internal-deps.test.mjs",
|
|
1787
|
+
"scripts/lib/npm-audit.test.mjs",
|
|
1788
|
+
"scripts/lib/npm-release-contract.test.mjs",
|
|
1789
|
+
"scripts/lib/release-surfaces.test.mjs"
|
|
1790
|
+
],
|
|
1791
|
+
timeoutMs: 12e4,
|
|
1792
|
+
maxOutputBytes: 1024 * 1024,
|
|
1793
|
+
cpus: 1,
|
|
1794
|
+
memory: "512m",
|
|
1795
|
+
pids: 128
|
|
1796
|
+
}]);
|
|
1797
|
+
|
|
1776
1798
|
// src/code-connect.ts
|
|
1777
1799
|
import { existsSync as existsSync4 } from "fs";
|
|
1778
1800
|
import { cpus, hostname, totalmem } from "os";
|
|
@@ -1803,72 +1825,77 @@ function parseAgentOutput(line) {
|
|
|
1803
1825
|
} catch {
|
|
1804
1826
|
throw new HarnessProtocolError("agent emitted invalid JSON");
|
|
1805
1827
|
}
|
|
1806
|
-
const
|
|
1807
|
-
if (!
|
|
1828
|
+
const message2 = record2(value);
|
|
1829
|
+
if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
|
|
1808
1830
|
throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
|
|
1809
1831
|
}
|
|
1810
|
-
if (
|
|
1832
|
+
if (message2.type === "event") {
|
|
1811
1833
|
return {
|
|
1812
1834
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
1813
1835
|
type: "event",
|
|
1814
|
-
kind: boundedText(
|
|
1815
|
-
...
|
|
1836
|
+
kind: boundedText(message2.kind, "event.kind", 120),
|
|
1837
|
+
...message2.payload === void 0 ? {} : { payload: message2.payload }
|
|
1816
1838
|
};
|
|
1817
1839
|
}
|
|
1818
|
-
if (
|
|
1819
|
-
const call = record2(
|
|
1840
|
+
if (message2.type === "inference.request") {
|
|
1841
|
+
const call = record2(message2.call);
|
|
1820
1842
|
if (!call || !Array.isArray(call.messages) || !Number.isSafeInteger(call.maxTokens)) {
|
|
1821
1843
|
throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
|
|
1822
1844
|
}
|
|
1823
1845
|
return {
|
|
1824
1846
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
1825
1847
|
type: "inference.request",
|
|
1826
|
-
requestId: boundedText(
|
|
1848
|
+
requestId: boundedText(message2.requestId, "requestId", 180),
|
|
1827
1849
|
call
|
|
1828
1850
|
};
|
|
1829
1851
|
}
|
|
1830
|
-
if (
|
|
1831
|
-
const input = record2(
|
|
1832
|
-
const tool = String(
|
|
1852
|
+
if (message2.type === "tool.request") {
|
|
1853
|
+
const input = record2(message2.input);
|
|
1854
|
+
const tool = String(message2.tool);
|
|
1833
1855
|
if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
|
|
1834
1856
|
throw new HarnessProtocolError("tool.request requires a registered tool and object input");
|
|
1835
1857
|
}
|
|
1836
1858
|
return {
|
|
1837
1859
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
1838
1860
|
type: "tool.request",
|
|
1839
|
-
requestId: boundedText(
|
|
1861
|
+
requestId: boundedText(message2.requestId, "requestId", 180),
|
|
1840
1862
|
tool,
|
|
1841
1863
|
input
|
|
1842
1864
|
};
|
|
1843
1865
|
}
|
|
1844
|
-
if (
|
|
1845
|
-
if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(
|
|
1866
|
+
if (message2.type === "attempt.complete") {
|
|
1867
|
+
if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(message2.status))) {
|
|
1846
1868
|
throw new HarnessProtocolError("attempt.complete.status is invalid");
|
|
1847
1869
|
}
|
|
1848
1870
|
return {
|
|
1849
1871
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
1850
1872
|
type: "attempt.complete",
|
|
1851
|
-
status:
|
|
1852
|
-
...
|
|
1873
|
+
status: message2.status,
|
|
1874
|
+
...message2.result === void 0 ? {} : { result: message2.result }
|
|
1853
1875
|
};
|
|
1854
1876
|
}
|
|
1855
1877
|
throw new HarnessProtocolError("agent message type is unsupported");
|
|
1856
1878
|
}
|
|
1857
|
-
function encodeAgentInput(
|
|
1858
|
-
return `${JSON.stringify(
|
|
1879
|
+
function encodeAgentInput(message2) {
|
|
1880
|
+
return `${JSON.stringify(message2)}
|
|
1859
1881
|
`;
|
|
1860
1882
|
}
|
|
1861
1883
|
|
|
1862
|
-
// ../harness/dist/chunk-
|
|
1884
|
+
// ../harness/dist/chunk-TA2FKK27.js
|
|
1863
1885
|
import { execFile as execFile2, spawn as spawn2 } from "child_process";
|
|
1864
1886
|
import { constants } from "fs";
|
|
1865
1887
|
import { access } from "fs/promises";
|
|
1866
1888
|
import { delimiter, join as join2 } from "path";
|
|
1867
1889
|
import { getgid, getuid } from "process";
|
|
1868
|
-
import {
|
|
1890
|
+
import { mkdir, mkdtemp, realpath, rm, writeFile } from "fs/promises";
|
|
1869
1891
|
import { tmpdir } from "os";
|
|
1870
|
-
import {
|
|
1892
|
+
import { join as join22, resolve as resolve3, sep } from "path";
|
|
1871
1893
|
import { spawn as spawn22 } from "child_process";
|
|
1894
|
+
import { isAbsolute as isAbsolute3 } from "path";
|
|
1895
|
+
import { chmod, copyFile, lstat, mkdir as mkdir2, mkdtemp as mkdtemp2, readdir, realpath as realpath2, rm as rm2, stat } from "fs/promises";
|
|
1896
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1897
|
+
import { basename, join as join3, relative as relative2, resolve as resolve22, sep as sep2 } from "path";
|
|
1898
|
+
import { spawn as spawn3 } from "child_process";
|
|
1872
1899
|
var DIGEST_IMAGE = /^[a-z0-9][a-z0-9._/-]*(?::[a-zA-Z0-9._-]+)?@sha256:[0-9a-f]{64}$/;
|
|
1873
1900
|
function assertPinnedImage(image) {
|
|
1874
1901
|
if (!DIGEST_IMAGE.test(image)) throw new TypeError("container image must be pinned by sha256 digest");
|
|
@@ -1912,7 +1939,7 @@ async function selectContainerEngine(requested = "auto", options = {}) {
|
|
|
1912
1939
|
throw new TypeError("no supported container engine found");
|
|
1913
1940
|
}
|
|
1914
1941
|
function inspectRootlessPodman() {
|
|
1915
|
-
return new Promise((
|
|
1942
|
+
return new Promise((resolve33, reject) => {
|
|
1916
1943
|
execFile2(
|
|
1917
1944
|
"podman",
|
|
1918
1945
|
["info", "--format", "{{.Host.Security.Rootless}}"],
|
|
@@ -1922,7 +1949,7 @@ function inspectRootlessPodman() {
|
|
|
1922
1949
|
reject(new TypeError("could not verify that the active Podman service is rootless"));
|
|
1923
1950
|
return;
|
|
1924
1951
|
}
|
|
1925
|
-
|
|
1952
|
+
resolve33(stdout.trim() === "true");
|
|
1926
1953
|
}
|
|
1927
1954
|
);
|
|
1928
1955
|
});
|
|
@@ -2033,9 +2060,9 @@ async function runContainerAttempt(options) {
|
|
|
2033
2060
|
if (bytes.byteLength > 1e6) throw new Error("agent message exceeds 1 MB");
|
|
2034
2061
|
const line = bytes.toString("utf8");
|
|
2035
2062
|
if (!line.trim()) return;
|
|
2036
|
-
const
|
|
2037
|
-
if (
|
|
2038
|
-
const response2 = await options.onMessage(
|
|
2063
|
+
const message2 = parseAgentOutput(line);
|
|
2064
|
+
if (message2.type === "attempt.complete") complete = message2;
|
|
2065
|
+
const response2 = await options.onMessage(message2);
|
|
2039
2066
|
if (response2 && !child.stdin.destroyed) child.stdin.write(encodeAgentInput(response2));
|
|
2040
2067
|
};
|
|
2041
2068
|
try {
|
|
@@ -2083,16 +2110,129 @@ async function runContainerAttempt(options) {
|
|
|
2083
2110
|
options.signal?.removeEventListener("abort", abort);
|
|
2084
2111
|
}
|
|
2085
2112
|
}
|
|
2086
|
-
var
|
|
2087
|
-
|
|
2113
|
+
var SKIP_WORKSPACE_DIRS = /* @__PURE__ */ new Set([
|
|
2114
|
+
".git",
|
|
2115
|
+
".odla",
|
|
2116
|
+
".wrangler",
|
|
2117
|
+
"node_modules",
|
|
2118
|
+
"dist",
|
|
2119
|
+
"coverage"
|
|
2120
|
+
]);
|
|
2121
|
+
var SECRET_WORKSPACE_FILE = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
2122
|
+
function allowedWorkspacePath(relativePath) {
|
|
2123
|
+
const parts = relativePath.split("/");
|
|
2124
|
+
return !isAbsolute3(relativePath) && !relativePath.includes("\\") && !relativePath.includes("\0") && !parts.some((part) => !part || part === "." || part === ".." || SKIP_WORKSPACE_DIRS.has(part)) && !SECRET_WORKSPACE_FILE.test(parts.at(-1) ?? "");
|
|
2125
|
+
}
|
|
2126
|
+
async function gitOutput(cwd, args, maxBytes) {
|
|
2127
|
+
const child = spawn22("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: false });
|
|
2128
|
+
const stdout = [];
|
|
2129
|
+
const stderr = [];
|
|
2130
|
+
let bytes = 0;
|
|
2131
|
+
child.stdout.on("data", (chunk) => {
|
|
2132
|
+
bytes += chunk.byteLength;
|
|
2133
|
+
if (bytes > maxBytes) child.kill("SIGKILL");
|
|
2134
|
+
else stdout.push(chunk);
|
|
2135
|
+
});
|
|
2136
|
+
child.stderr.on("data", (chunk) => {
|
|
2137
|
+
if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
|
|
2138
|
+
});
|
|
2139
|
+
const code = await new Promise((accept, reject) => {
|
|
2140
|
+
child.once("error", reject);
|
|
2141
|
+
child.once("exit", accept);
|
|
2142
|
+
});
|
|
2143
|
+
if (bytes > maxBytes) throw new Error(`git output exceeds ${maxBytes} bytes`);
|
|
2144
|
+
if (code !== 0) throw new Error(`git command failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
|
|
2145
|
+
return Buffer.concat(stdout);
|
|
2146
|
+
}
|
|
2147
|
+
async function gitBlobs(cwd, entries, maxBytes) {
|
|
2148
|
+
const child = spawn22("git", ["cat-file", "--batch"], { cwd, stdio: ["pipe", "pipe", "pipe"], shell: false });
|
|
2149
|
+
const stdout = [];
|
|
2150
|
+
const stderr = [];
|
|
2151
|
+
let bytes = 0;
|
|
2152
|
+
child.stdout.on("data", (chunk) => {
|
|
2153
|
+
bytes += chunk.byteLength;
|
|
2154
|
+
if (bytes > maxBytes + entries.length * 100) child.kill("SIGKILL");
|
|
2155
|
+
else stdout.push(chunk);
|
|
2156
|
+
});
|
|
2157
|
+
child.stderr.on("data", (chunk) => {
|
|
2158
|
+
if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
|
|
2159
|
+
});
|
|
2160
|
+
child.stdin.end(`${entries.map((entry) => entry.hash).join("\n")}
|
|
2161
|
+
`);
|
|
2162
|
+
const code = await new Promise((accept, reject) => {
|
|
2163
|
+
child.once("error", reject);
|
|
2164
|
+
child.once("exit", accept);
|
|
2165
|
+
});
|
|
2166
|
+
if (bytes > maxBytes + entries.length * 100) throw new Error(`Git tree exceeds ${maxBytes} bytes`);
|
|
2167
|
+
if (code !== 0) throw new Error(`git object read failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
|
|
2168
|
+
const output = Buffer.concat(stdout);
|
|
2169
|
+
const blobs = [];
|
|
2170
|
+
let offset = 0;
|
|
2171
|
+
let total = 0;
|
|
2172
|
+
for (const entry of entries) {
|
|
2173
|
+
const newline = output.indexOf(10, offset);
|
|
2174
|
+
if (newline < 0) throw new Error("git object output is truncated");
|
|
2175
|
+
const match = /^([0-9a-f]{40,64}) blob ([0-9]+)$/.exec(output.subarray(offset, newline).toString("utf8"));
|
|
2176
|
+
if (!match || match[1] !== entry.hash) throw new Error("git object output does not match its inventory");
|
|
2177
|
+
const length = Number(match[2]);
|
|
2178
|
+
if (!Number.isSafeInteger(length) || length < 0 || newline + 1 + length >= output.length) {
|
|
2179
|
+
throw new Error("git object output has an invalid length");
|
|
2180
|
+
}
|
|
2181
|
+
const content = output.subarray(newline + 1, newline + 1 + length);
|
|
2182
|
+
if (output[newline + 1 + length] !== 10) throw new Error("git object output is malformed");
|
|
2183
|
+
total += content.byteLength;
|
|
2184
|
+
if (total > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
|
|
2185
|
+
blobs.push(content);
|
|
2186
|
+
offset = newline + 1 + length + 1;
|
|
2187
|
+
}
|
|
2188
|
+
return blobs;
|
|
2189
|
+
}
|
|
2190
|
+
async function materializeGitTree(source, commitSha, options = {}) {
|
|
2191
|
+
if (!/^[0-9a-f]{40}$/.test(commitSha)) throw new TypeError("Git tree requires an exact commit SHA");
|
|
2192
|
+
const sourceDir = await realpath(resolve3(source));
|
|
2193
|
+
const maxFiles = options.maxFiles ?? 2e4;
|
|
2194
|
+
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
2195
|
+
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
2196
|
+
const entries = inventory.flatMap((record5) => {
|
|
2197
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record5);
|
|
2198
|
+
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
2199
|
+
});
|
|
2200
|
+
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
2201
|
+
const root = await mkdtemp(join22(options.tempRoot ?? tmpdir(), "odla-git-tree-"));
|
|
2202
|
+
const targetRoot = join22(root, "source");
|
|
2203
|
+
await mkdir(targetRoot);
|
|
2204
|
+
let byteCount = 0;
|
|
2205
|
+
try {
|
|
2206
|
+
const blobs = await gitBlobs(sourceDir, entries, maxBytes);
|
|
2207
|
+
for (const [index, entry] of entries.entries()) {
|
|
2208
|
+
const content = blobs[index];
|
|
2209
|
+
byteCount += content.byteLength;
|
|
2210
|
+
if (byteCount > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
|
|
2211
|
+
const target = resolve3(targetRoot, entry.path);
|
|
2212
|
+
if (!target.startsWith(`${resolve3(targetRoot)}${sep}`)) throw new TypeError("Git tree path escapes workspace");
|
|
2213
|
+
await mkdir(resolve3(target, ".."), { recursive: true });
|
|
2214
|
+
await writeFile(target, content, { flag: "wx", mode: entry.mode === "100755" ? 493 : 420 });
|
|
2215
|
+
}
|
|
2216
|
+
return {
|
|
2217
|
+
root,
|
|
2218
|
+
sourceDir: targetRoot,
|
|
2219
|
+
fileCount: entries.length,
|
|
2220
|
+
byteCount,
|
|
2221
|
+
cleanup: () => rm(root, { recursive: true, force: true })
|
|
2222
|
+
};
|
|
2223
|
+
} catch (error) {
|
|
2224
|
+
await rm(root, { recursive: true, force: true });
|
|
2225
|
+
throw error;
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2088
2228
|
async function sourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
2089
2229
|
const files = [];
|
|
2090
2230
|
let bytes = 0;
|
|
2091
2231
|
const walk = async (dir) => {
|
|
2092
2232
|
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
2093
|
-
if (entry.isDirectory() &&
|
|
2094
|
-
if (!entry.isDirectory() &&
|
|
2095
|
-
const path =
|
|
2233
|
+
if (entry.isDirectory() && SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
2234
|
+
if (!entry.isDirectory() && SECRET_WORKSPACE_FILE.test(entry.name)) continue;
|
|
2235
|
+
const path = join3(dir, entry.name);
|
|
2096
2236
|
if (entry.isSymbolicLink()) continue;
|
|
2097
2237
|
if (entry.isDirectory()) {
|
|
2098
2238
|
await walk(path);
|
|
@@ -2114,16 +2254,62 @@ async function sourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
2114
2254
|
await walk(sourceDir);
|
|
2115
2255
|
return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
2116
2256
|
}
|
|
2257
|
+
async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
2258
|
+
const child = spawn3("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
2259
|
+
cwd: sourceDir,
|
|
2260
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2261
|
+
shell: false
|
|
2262
|
+
});
|
|
2263
|
+
const stdout = [];
|
|
2264
|
+
const stderr = [];
|
|
2265
|
+
let outputBytes = 0;
|
|
2266
|
+
child.stdout.on("data", (chunk) => {
|
|
2267
|
+
outputBytes += chunk.byteLength;
|
|
2268
|
+
if (outputBytes > 8 * 1024 * 1024) child.kill("SIGKILL");
|
|
2269
|
+
else stdout.push(chunk);
|
|
2270
|
+
});
|
|
2271
|
+
child.stderr.on("data", (chunk) => {
|
|
2272
|
+
if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
|
|
2273
|
+
});
|
|
2274
|
+
const code = await new Promise((accept, reject) => {
|
|
2275
|
+
child.once("error", reject);
|
|
2276
|
+
child.once("exit", accept);
|
|
2277
|
+
});
|
|
2278
|
+
if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
|
|
2279
|
+
if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
|
|
2280
|
+
const paths = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
|
|
2281
|
+
if (paths.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
2282
|
+
const root = resolve22(sourceDir);
|
|
2283
|
+
const files = [];
|
|
2284
|
+
let bytes = 0;
|
|
2285
|
+
for (const relativePath of paths) {
|
|
2286
|
+
if (!allowedWorkspacePath(relativePath)) continue;
|
|
2287
|
+
const source = resolve22(root, relativePath);
|
|
2288
|
+
if (!source.startsWith(`${root}${sep2}`)) throw new TypeError("git file path escapes workspace");
|
|
2289
|
+
let metadata2;
|
|
2290
|
+
try {
|
|
2291
|
+
metadata2 = await lstat(source);
|
|
2292
|
+
} catch (error) {
|
|
2293
|
+
if (error.code === "ENOENT") continue;
|
|
2294
|
+
throw error;
|
|
2295
|
+
}
|
|
2296
|
+
if (metadata2.isSymbolicLink() || !metadata2.isFile()) continue;
|
|
2297
|
+
bytes += metadata2.size;
|
|
2298
|
+
if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
|
|
2299
|
+
files.push({ source, relativePath, mode: metadata2.mode & 511, bytes: metadata2.size });
|
|
2300
|
+
}
|
|
2301
|
+
return files;
|
|
2302
|
+
}
|
|
2117
2303
|
async function copyTree(files, destination) {
|
|
2118
2304
|
for (const file of files) {
|
|
2119
|
-
const target =
|
|
2120
|
-
await
|
|
2305
|
+
const target = join3(destination, file.relativePath);
|
|
2306
|
+
await mkdir2(resolve22(target, ".."), { recursive: true });
|
|
2121
2307
|
await copyFile(file.source, target);
|
|
2122
2308
|
await chmod(target, file.mode);
|
|
2123
2309
|
}
|
|
2124
2310
|
}
|
|
2125
2311
|
async function captureGitDiff(root, maxBytes) {
|
|
2126
|
-
const child =
|
|
2312
|
+
const child = spawn3("git", [
|
|
2127
2313
|
"diff",
|
|
2128
2314
|
"--no-index",
|
|
2129
2315
|
"--binary",
|
|
@@ -2156,15 +2342,17 @@ async function captureGitDiff(root, maxBytes) {
|
|
|
2156
2342
|
return Buffer.concat(stdout).toString("utf8").replaceAll("a/baseline/", "a/").replaceAll("b/workspace/", "b/").replaceAll("--- a/baseline", "--- a").replaceAll("+++ b/workspace", "+++ b");
|
|
2157
2343
|
}
|
|
2158
2344
|
async function stageWorkspace(source, options = {}) {
|
|
2159
|
-
const sourceDir = await
|
|
2345
|
+
const sourceDir = await realpath2(resolve22(source));
|
|
2160
2346
|
const sourceStat = await stat(sourceDir);
|
|
2161
2347
|
if (!sourceStat.isDirectory()) throw new TypeError("workspace source must be a directory");
|
|
2162
|
-
const root = await
|
|
2163
|
-
const baselineDir =
|
|
2164
|
-
const workspaceDir =
|
|
2165
|
-
await Promise.all([
|
|
2348
|
+
const root = await mkdtemp2(join3(options.tempRoot ?? tmpdir2(), "odla-harness-"));
|
|
2349
|
+
const baselineDir = join3(root, "baseline");
|
|
2350
|
+
const workspaceDir = join3(root, "workspace");
|
|
2351
|
+
await Promise.all([mkdir2(baselineDir), mkdir2(workspaceDir)]);
|
|
2166
2352
|
try {
|
|
2167
|
-
const
|
|
2353
|
+
const maxFiles = options.maxFiles ?? 2e4;
|
|
2354
|
+
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
2355
|
+
const files = options.gitTrackedAndUnignored ? await gitSourceFiles(sourceDir, maxFiles, maxBytes) : await sourceFiles(sourceDir, maxFiles, maxBytes);
|
|
2168
2356
|
await Promise.all([copyTree(files, baselineDir), copyTree(files, workspaceDir)]);
|
|
2169
2357
|
return {
|
|
2170
2358
|
root,
|
|
@@ -2172,15 +2360,49 @@ async function stageWorkspace(source, options = {}) {
|
|
|
2172
2360
|
workspaceDir,
|
|
2173
2361
|
fileCount: files.length,
|
|
2174
2362
|
byteCount: files.reduce((sum, file) => sum + file.bytes, 0),
|
|
2175
|
-
patch: (
|
|
2176
|
-
cleanup: () =>
|
|
2363
|
+
patch: (maxBytes2) => captureGitDiff(root, maxBytes2),
|
|
2364
|
+
cleanup: () => rm2(root, { recursive: true, force: true })
|
|
2177
2365
|
};
|
|
2178
2366
|
} catch (error) {
|
|
2179
|
-
await
|
|
2367
|
+
await rm2(root, { recursive: true, force: true });
|
|
2368
|
+
throw error;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
async function stageWorkspacePair(baselineSource, workspaceSource, options = {}) {
|
|
2372
|
+
const baselineDirSource = await realpath2(resolve22(baselineSource));
|
|
2373
|
+
const workspaceDirSource = await realpath2(resolve22(workspaceSource));
|
|
2374
|
+
const maxFiles = options.maxFiles ?? 2e4;
|
|
2375
|
+
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
2376
|
+
const [baselineFiles, workspaceFiles] = await Promise.all([
|
|
2377
|
+
sourceFiles(baselineDirSource, maxFiles, maxBytes),
|
|
2378
|
+
sourceFiles(workspaceDirSource, maxFiles, maxBytes)
|
|
2379
|
+
]);
|
|
2380
|
+
const root = await mkdtemp2(join3(options.tempRoot ?? tmpdir2(), "odla-harness-"));
|
|
2381
|
+
const baselineDir = join3(root, "baseline");
|
|
2382
|
+
const workspaceDir = join3(root, "workspace");
|
|
2383
|
+
await Promise.all([mkdir2(baselineDir), mkdir2(workspaceDir)]);
|
|
2384
|
+
try {
|
|
2385
|
+
await Promise.all([copyTree(baselineFiles, baselineDir), copyTree(workspaceFiles, workspaceDir)]);
|
|
2386
|
+
return {
|
|
2387
|
+
root,
|
|
2388
|
+
baselineDir,
|
|
2389
|
+
workspaceDir,
|
|
2390
|
+
fileCount: workspaceFiles.length,
|
|
2391
|
+
byteCount: workspaceFiles.reduce((sum, file) => sum + file.bytes, 0),
|
|
2392
|
+
patch: (maxPatchBytes) => captureGitDiff(root, maxPatchBytes),
|
|
2393
|
+
cleanup: () => rm2(root, { recursive: true, force: true })
|
|
2394
|
+
};
|
|
2395
|
+
} catch (error) {
|
|
2396
|
+
await rm2(root, { recursive: true, force: true });
|
|
2180
2397
|
throw error;
|
|
2181
2398
|
}
|
|
2182
2399
|
}
|
|
2183
2400
|
|
|
2401
|
+
// ../harness/dist/chunk-4EM6NMET.js
|
|
2402
|
+
import { createHash } from "crypto";
|
|
2403
|
+
import { readFile, readdir as readdir2 } from "fs/promises";
|
|
2404
|
+
import { relative as relative3, resolve as resolve4 } from "path";
|
|
2405
|
+
|
|
2184
2406
|
// ../camel/dist/chunk-7FHPOQVP.js
|
|
2185
2407
|
var CamelError = class extends Error {
|
|
2186
2408
|
code;
|
|
@@ -2199,8 +2421,8 @@ function canonicalJson(value) {
|
|
|
2199
2421
|
}
|
|
2200
2422
|
async function sha256Hex(value) {
|
|
2201
2423
|
const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
|
|
2202
|
-
const
|
|
2203
|
-
return [...new Uint8Array(
|
|
2424
|
+
const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
|
|
2425
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2204
2426
|
}
|
|
2205
2427
|
function utf8Length(value) {
|
|
2206
2428
|
if (typeof value === "string") return new TextEncoder().encode(value).byteLength;
|
|
@@ -2477,8 +2699,8 @@ function freeze(value) {
|
|
|
2477
2699
|
})
|
|
2478
2700
|
});
|
|
2479
2701
|
}
|
|
2480
|
-
function invalid2(
|
|
2481
|
-
return new CamelError("state_conflict",
|
|
2702
|
+
function invalid2(message2) {
|
|
2703
|
+
return new CamelError("state_conflict", message2);
|
|
2482
2704
|
}
|
|
2483
2705
|
var SHA4 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
2484
2706
|
var REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
@@ -2513,23 +2735,20 @@ function validateSnapshot(snapshot, limits) {
|
|
|
2513
2735
|
}
|
|
2514
2736
|
}
|
|
2515
2737
|
|
|
2516
|
-
// ../harness/dist/chunk-
|
|
2517
|
-
import { spawn as
|
|
2518
|
-
import { lstat } from "fs/promises";
|
|
2519
|
-
import { resolve as
|
|
2738
|
+
// ../harness/dist/chunk-4EM6NMET.js
|
|
2739
|
+
import { spawn as spawn4 } from "child_process";
|
|
2740
|
+
import { lstat as lstat2 } from "fs/promises";
|
|
2741
|
+
import { resolve as resolve23, sep as sep3 } from "path";
|
|
2520
2742
|
import { spawn as spawn23 } from "child_process";
|
|
2521
2743
|
import { getgid as getgid2, getuid as getuid2 } from "process";
|
|
2522
2744
|
import { randomUUID } from "crypto";
|
|
2523
2745
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
|
|
2524
2746
|
import { createReadStream } from "fs";
|
|
2525
|
-
import { lstat as
|
|
2526
|
-
import { join as
|
|
2527
|
-
import {
|
|
2528
|
-
import {
|
|
2529
|
-
import {
|
|
2530
|
-
import { mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm2, writeFile } from "fs/promises";
|
|
2531
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
2532
|
-
import { dirname as dirname4, join as join23, resolve as resolve32, sep as sep2 } from "path";
|
|
2747
|
+
import { lstat as lstat22 } from "fs/promises";
|
|
2748
|
+
import { join as join4 } from "path";
|
|
2749
|
+
import { mkdir as mkdir3, mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile2 } from "fs/promises";
|
|
2750
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
2751
|
+
import { dirname as dirname4, join as join23, resolve as resolve32, sep as sep22 } from "path";
|
|
2533
2752
|
import { readFile as readFile2, readdir as readdir22, stat as stat2 } from "fs/promises";
|
|
2534
2753
|
import { relative as relative22, resolve as resolve42 } from "path";
|
|
2535
2754
|
|
|
@@ -2809,8 +3028,34 @@ function looksLikeDestination(value) {
|
|
|
2809
3028
|
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
|
|
2810
3029
|
}
|
|
2811
3030
|
|
|
2812
|
-
// ../harness/dist/chunk-
|
|
3031
|
+
// ../harness/dist/chunk-4EM6NMET.js
|
|
2813
3032
|
import { createHash as createHash3 } from "crypto";
|
|
3033
|
+
async function digestStagedWorkspace(root, limits) {
|
|
3034
|
+
const files = [];
|
|
3035
|
+
const walk = async (directory) => {
|
|
3036
|
+
const entries = await readdir2(directory, { withFileTypes: true });
|
|
3037
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
3038
|
+
if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
|
|
3039
|
+
const target = resolve4(directory, entry.name);
|
|
3040
|
+
if (entry.isDirectory()) await walk(target);
|
|
3041
|
+
else if (entry.isFile()) {
|
|
3042
|
+
files.push({ path: relative3(root, target).split("\\").join("/"), target });
|
|
3043
|
+
if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
};
|
|
3047
|
+
await walk(resolve4(root));
|
|
3048
|
+
const hash = createHash("sha256");
|
|
3049
|
+
let bytes = 0;
|
|
3050
|
+
for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
|
|
3051
|
+
const content = await readFile(file.target);
|
|
3052
|
+
bytes += Buffer.byteLength(file.path) + content.byteLength;
|
|
3053
|
+
if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
|
|
3054
|
+
hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
|
|
3055
|
+
hash.update(content);
|
|
3056
|
+
}
|
|
3057
|
+
return `sha256:${hash.digest("hex")}`;
|
|
3058
|
+
}
|
|
2814
3059
|
var CODE_RUNTIME_PROTOCOL_VERSION = 1;
|
|
2815
3060
|
async function runCodeRuntimeHeartbeatLoop(options) {
|
|
2816
3061
|
const heartbeatMs = options.heartbeatMs ?? 15e3;
|
|
@@ -2882,8 +3127,8 @@ function wait(ms, signal) {
|
|
|
2882
3127
|
});
|
|
2883
3128
|
}
|
|
2884
3129
|
var CodeRuntimeControlError = class extends Error {
|
|
2885
|
-
constructor(
|
|
2886
|
-
super(
|
|
3130
|
+
constructor(message2, status, code = "control_error") {
|
|
3131
|
+
super(message2);
|
|
2887
3132
|
this.status = status;
|
|
2888
3133
|
this.code = code;
|
|
2889
3134
|
}
|
|
@@ -2962,9 +3207,9 @@ function createCodeRuntimeControlClient(options) {
|
|
|
2962
3207
|
}
|
|
2963
3208
|
await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
|
|
2964
3209
|
},
|
|
2965
|
-
reportSessionFailure: async (sessionId,
|
|
2966
|
-
if (!
|
|
2967
|
-
await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message:
|
|
3210
|
+
reportSessionFailure: async (sessionId, message2) => {
|
|
3211
|
+
if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
|
|
3212
|
+
await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
|
|
2968
3213
|
}
|
|
2969
3214
|
};
|
|
2970
3215
|
}
|
|
@@ -3031,10 +3276,28 @@ async function parseSource(value) {
|
|
|
3031
3276
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
3032
3277
|
return { path: file.path, content: file.content };
|
|
3033
3278
|
});
|
|
3279
|
+
const referencesValue = snapshot.references === void 0 ? [] : snapshot.references;
|
|
3280
|
+
if (!Array.isArray(referencesValue) || referencesValue.length > 5) throw invalid("reference sources");
|
|
3281
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
3282
|
+
const references = [];
|
|
3283
|
+
for (const item of referencesValue) {
|
|
3284
|
+
const reference = record3(item);
|
|
3285
|
+
if (!reference || typeof reference.alias !== "string" || !/^[a-z][a-z0-9-]{0,39}$/.test(reference.alias) || aliases.has(reference.alias) || reference.alias === "primary" || typeof reference.repository !== "string" || typeof reference.commitSha !== "string" || typeof reference.treeDigest !== "string" || !Array.isArray(reference.files)) throw invalid("reference source");
|
|
3286
|
+
aliases.add(reference.alias);
|
|
3287
|
+
const referenceFiles = reference.files.map((entry) => {
|
|
3288
|
+
const file = record3(entry);
|
|
3289
|
+
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
|
|
3290
|
+
return { path: file.path, content: file.content };
|
|
3291
|
+
});
|
|
3292
|
+
const source2 = { repository: reference.repository, commitSha: reference.commitSha, files: referenceFiles };
|
|
3293
|
+
const referenceDigest = await digestCodeRepositorySnapshot(source2, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
|
|
3294
|
+
if (referenceDigest !== reference.treeDigest) throw invalid("reference source digest");
|
|
3295
|
+
references.push({ alias: reference.alias, ...source2, treeDigest: referenceDigest });
|
|
3296
|
+
}
|
|
3034
3297
|
const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
|
|
3035
|
-
const
|
|
3036
|
-
if (
|
|
3037
|
-
return { ...source, treeDigest:
|
|
3298
|
+
const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
|
|
3299
|
+
if (digest !== snapshot.treeDigest) throw invalid("source digest");
|
|
3300
|
+
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
3038
3301
|
}
|
|
3039
3302
|
function parseReview(value) {
|
|
3040
3303
|
const review = record3(record3(value)?.review);
|
|
@@ -3093,9 +3356,9 @@ function validateRelativePath(path) {
|
|
|
3093
3356
|
}
|
|
3094
3357
|
function resolveCodePath(workspaceDir, path) {
|
|
3095
3358
|
validateRelativePath(path);
|
|
3096
|
-
const root =
|
|
3097
|
-
const target =
|
|
3098
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
3359
|
+
const root = resolve23(workspaceDir);
|
|
3360
|
+
const target = resolve23(root, path);
|
|
3361
|
+
if (target !== root && !target.startsWith(`${root}${sep3}`)) throw new TypeError("path escapes the staged workspace");
|
|
3099
3362
|
return target;
|
|
3100
3363
|
}
|
|
3101
3364
|
async function applyCodePatch(workspaceDir, patch2, paths) {
|
|
@@ -3103,7 +3366,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
|
|
|
3103
3366
|
await gitApply(workspaceDir, patch2, false);
|
|
3104
3367
|
for (const path of paths) {
|
|
3105
3368
|
try {
|
|
3106
|
-
const info = await
|
|
3369
|
+
const info = await lstat2(resolveCodePath(workspaceDir, path));
|
|
3107
3370
|
if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
|
|
3108
3371
|
throw new TypeError("patch created a non-regular workspace entry");
|
|
3109
3372
|
}
|
|
@@ -3115,7 +3378,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
|
|
|
3115
3378
|
function gitApply(cwd, patch2, check) {
|
|
3116
3379
|
return new Promise((accept, reject) => {
|
|
3117
3380
|
const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
|
|
3118
|
-
const child =
|
|
3381
|
+
const child = spawn4("git", args, {
|
|
3119
3382
|
cwd,
|
|
3120
3383
|
shell: false,
|
|
3121
3384
|
stdio: ["pipe", "ignore", "pipe"],
|
|
@@ -3285,32 +3548,6 @@ function execute(engine, args, name, recipe2, signal) {
|
|
|
3285
3548
|
});
|
|
3286
3549
|
});
|
|
3287
3550
|
}
|
|
3288
|
-
async function digestStagedWorkspace(root, limits) {
|
|
3289
|
-
const files = [];
|
|
3290
|
-
const walk = async (directory) => {
|
|
3291
|
-
const entries = await readdir2(directory, { withFileTypes: true });
|
|
3292
|
-
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
3293
|
-
if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
|
|
3294
|
-
const target = resolve22(directory, entry.name);
|
|
3295
|
-
if (entry.isDirectory()) await walk(target);
|
|
3296
|
-
else if (entry.isFile()) {
|
|
3297
|
-
files.push({ path: relative3(root, target).split("\\").join("/"), target });
|
|
3298
|
-
if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
|
|
3299
|
-
}
|
|
3300
|
-
}
|
|
3301
|
-
};
|
|
3302
|
-
await walk(resolve22(root));
|
|
3303
|
-
const hash = createHash("sha256");
|
|
3304
|
-
let bytes = 0;
|
|
3305
|
-
for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
|
|
3306
|
-
const content = await readFile(file.target);
|
|
3307
|
-
bytes += Buffer.byteLength(file.path) + content.byteLength;
|
|
3308
|
-
if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
|
|
3309
|
-
hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
|
|
3310
|
-
hash.update(content);
|
|
3311
|
-
}
|
|
3312
|
-
return `sha256:${hash.digest("hex")}`;
|
|
3313
|
-
}
|
|
3314
3551
|
var SHA3 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
3315
3552
|
var DIGEST3 = /^sha256:[0-9a-f]{64}$/;
|
|
3316
3553
|
var ID3 = /^[A-Za-z0-9._:-]{1,160}$/;
|
|
@@ -3419,8 +3656,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
|
|
|
3419
3656
|
const receipts = [];
|
|
3420
3657
|
for (const artifact of recipe2.expectedArtifacts ?? []) {
|
|
3421
3658
|
try {
|
|
3422
|
-
const path =
|
|
3423
|
-
const info = await
|
|
3659
|
+
const path = join4(workspaceDir, artifact.path);
|
|
3660
|
+
const info = await lstat22(path);
|
|
3424
3661
|
if (!info.isFile() || info.isSymbolicLink()) {
|
|
3425
3662
|
receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
|
|
3426
3663
|
} else if (info.size > artifact.maximumBytes) {
|
|
@@ -3516,8 +3753,8 @@ async function prepareRuntimeCheckpoint(input) {
|
|
|
3516
3753
|
policy: {
|
|
3517
3754
|
policyId: "code.runtime",
|
|
3518
3755
|
recipes: input.recipes,
|
|
3519
|
-
maximumFiles:
|
|
3520
|
-
maximumBytes:
|
|
3756
|
+
maximumFiles: 2e4,
|
|
3757
|
+
maximumBytes: 512 * 1024 * 1024
|
|
3521
3758
|
},
|
|
3522
3759
|
recipeExecutor: input.recipeExecutor
|
|
3523
3760
|
});
|
|
@@ -3591,7 +3828,7 @@ var CodeRuntimeCheckpointManager = class {
|
|
|
3591
3828
|
await this.options.event(command, {
|
|
3592
3829
|
type: "message",
|
|
3593
3830
|
actor: "system",
|
|
3594
|
-
body: `Candidate ${candidate.candidateId} is ready for
|
|
3831
|
+
body: command.payload.sourceSet ? `Candidate ${candidate.candidateId} was verified and delivered to the session PR branch` : `Candidate ${candidate.candidateId} is ready for legacy owner publication approval`
|
|
3595
3832
|
}, pending.refs);
|
|
3596
3833
|
this.#pending.delete(command.commandId);
|
|
3597
3834
|
return true;
|
|
@@ -3599,11 +3836,11 @@ var CodeRuntimeCheckpointManager = class {
|
|
|
3599
3836
|
};
|
|
3600
3837
|
var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
3601
3838
|
var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
3602
|
-
async function materializeCodeRuntimeSource(snapshot, tempRoot =
|
|
3839
|
+
async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir3()) {
|
|
3603
3840
|
if (!snapshot.files.length || snapshot.files.length > 1e4) throw new TypeError("Code source file count is invalid");
|
|
3604
|
-
const root = await
|
|
3841
|
+
const root = await mkdtemp3(join23(tempRoot, "odla-code-source-"));
|
|
3605
3842
|
const sourceDir = join23(root, "source");
|
|
3606
|
-
await
|
|
3843
|
+
await mkdir3(sourceDir);
|
|
3607
3844
|
const seen = /* @__PURE__ */ new Set();
|
|
3608
3845
|
let bytes = 0;
|
|
3609
3846
|
try {
|
|
@@ -3614,16 +3851,55 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir2()) {
|
|
|
3614
3851
|
bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
|
|
3615
3852
|
if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
|
|
3616
3853
|
const target = resolve32(sourceDir, file.path);
|
|
3617
|
-
if (!target.startsWith(`${resolve32(sourceDir)}${
|
|
3618
|
-
await
|
|
3619
|
-
await
|
|
3854
|
+
if (!target.startsWith(`${resolve32(sourceDir)}${sep22}`)) throw new TypeError("Code source path escapes its root");
|
|
3855
|
+
await mkdir3(dirname4(target), { recursive: true });
|
|
3856
|
+
await writeFile2(target, file.content, { flag: "wx", mode: 420 });
|
|
3857
|
+
}
|
|
3858
|
+
for (const reference of snapshot.references ?? []) {
|
|
3859
|
+
validateAlias(reference.alias);
|
|
3860
|
+
if (!reference.files.length || reference.files.length > 1e4) throw new TypeError("Code reference file count is invalid");
|
|
3861
|
+
for (const file of reference.files) {
|
|
3862
|
+
validatePath(file.path);
|
|
3863
|
+
const path = `.odla-references/${reference.alias}/${file.path}`;
|
|
3864
|
+
if (seen.has(path)) throw new TypeError("Code reference repeats a path");
|
|
3865
|
+
seen.add(path);
|
|
3866
|
+
bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
|
|
3867
|
+
if (bytes > 80 * 1024 * 1024) throw new TypeError("Code source set exceeds its byte bound");
|
|
3868
|
+
const target = resolve32(sourceDir, path);
|
|
3869
|
+
if (!target.startsWith(`${resolve32(sourceDir)}${sep22}`)) throw new TypeError("Code reference path escapes its root");
|
|
3870
|
+
await mkdir3(dirname4(target), { recursive: true });
|
|
3871
|
+
await writeFile2(target, file.content, { flag: "wx", mode: 292 });
|
|
3872
|
+
}
|
|
3620
3873
|
}
|
|
3621
|
-
return { sourceDir, cleanup: () =>
|
|
3874
|
+
return { sourceDir, cleanup: () => rm3(root, { recursive: true, force: true }) };
|
|
3622
3875
|
} catch (cause) {
|
|
3623
|
-
await
|
|
3876
|
+
await rm3(root, { recursive: true, force: true });
|
|
3624
3877
|
throw cause;
|
|
3625
3878
|
}
|
|
3626
3879
|
}
|
|
3880
|
+
function validateAlias(alias) {
|
|
3881
|
+
if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias) || alias === "primary") {
|
|
3882
|
+
throw new TypeError("Code reference alias is invalid");
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
async function attachCodeRuntimeReferences(workspace, references) {
|
|
3886
|
+
let bytes = 0;
|
|
3887
|
+
for (const reference of references) {
|
|
3888
|
+
validateAlias(reference.alias);
|
|
3889
|
+
for (const file of reference.files) {
|
|
3890
|
+
validatePath(file.path);
|
|
3891
|
+
const path = `.odla-references/${reference.alias}/${file.path}`;
|
|
3892
|
+
bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
|
|
3893
|
+
if (bytes > 64 * 1024 * 1024) throw new TypeError("Code reference set exceeds its byte bound");
|
|
3894
|
+
for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
|
|
3895
|
+
const target = resolve32(root, path);
|
|
3896
|
+
if (!target.startsWith(`${resolve32(root)}${sep22}`)) throw new TypeError("Code reference path escapes its root");
|
|
3897
|
+
await mkdir3(dirname4(target), { recursive: true });
|
|
3898
|
+
await writeFile2(target, file.content, { flag: "wx", mode: 292 });
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3627
3903
|
function validatePath(path) {
|
|
3628
3904
|
const parts = path.split("/");
|
|
3629
3905
|
if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." || RESERVED2.has(part) || SECRET2.test(part))) {
|
|
@@ -3726,15 +4002,15 @@ async function environment(input, options, tool) {
|
|
|
3726
4002
|
{ id: "authority", value: `lease:${input.lease.leaseId}`, readers: input.readers },
|
|
3727
4003
|
{ id: "reader", value: options.readerId, readers: input.readers }
|
|
3728
4004
|
]);
|
|
3729
|
-
const
|
|
4005
|
+
const digest = await destinationRegistryDigest([input.workspaceId]);
|
|
3730
4006
|
const approvals = ["irreversible_mutation", "external_send", "financial", "secret_read"];
|
|
3731
4007
|
if (options.recipeAuthorization === "exact_approval") approvals.push("code_execution");
|
|
3732
4008
|
const policy = createEffectPolicy({
|
|
3733
|
-
destinationRegistries: { [DESTINATIONS]: { digest
|
|
4009
|
+
destinationRegistries: { [DESTINATIONS]: { digest, values: [input.workspaceId] } },
|
|
3734
4010
|
approvalEffects: approvals
|
|
3735
4011
|
});
|
|
3736
4012
|
const fixedArgs = {
|
|
3737
|
-
workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest:
|
|
4013
|
+
workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest },
|
|
3738
4014
|
authority: { role: "authority", value: ingress.control("authority") }
|
|
3739
4015
|
};
|
|
3740
4016
|
return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
|
|
@@ -3822,6 +4098,9 @@ async function patch(context, request, options, policy) {
|
|
|
3822
4098
|
exactKeys(request.input, ["patch"]);
|
|
3823
4099
|
const value = stringField(request.input, "patch");
|
|
3824
4100
|
const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
|
|
4101
|
+
if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
4102
|
+
throw new TypeError("patch targets a read-only reference source");
|
|
4103
|
+
}
|
|
3825
4104
|
const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
|
|
3826
4105
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
3827
4106
|
await applyCodePatch(context.workspaceDir, value, paths);
|
|
@@ -3907,6 +4186,9 @@ function validateOptions(options) {
|
|
|
3907
4186
|
throw new TypeError("Code tool broker requires a reader and unique registered recipes");
|
|
3908
4187
|
}
|
|
3909
4188
|
for (const recipe2 of options.recipes) assertCodeBuildRecipe(recipe2);
|
|
4189
|
+
if (options.readOnlyPrefixes?.some((prefix) => !/^[A-Za-z0-9_.-]+$/.test(prefix) || prefix === "." || prefix === "..")) {
|
|
4190
|
+
throw new TypeError("Code tool broker read-only prefix is invalid");
|
|
4191
|
+
}
|
|
3910
4192
|
}
|
|
3911
4193
|
function exactKeys(input, allowed) {
|
|
3912
4194
|
if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
|
|
@@ -3934,14 +4216,31 @@ function codeCommandMetadata(payload, resume) {
|
|
|
3934
4216
|
}
|
|
3935
4217
|
const planning = trusted?.planningInputDigest;
|
|
3936
4218
|
const attestation = trusted?.attestationDigest;
|
|
4219
|
+
const repository = trusted?.repository;
|
|
4220
|
+
const baseCommitSha = trusted?.commitSha;
|
|
4221
|
+
const sourceTreeDigest = trusted?.treeDigest;
|
|
4222
|
+
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)) {
|
|
4223
|
+
throw new TypeError(`invalid Code ${resume ? "resume" : "start"} trusted base`);
|
|
4224
|
+
}
|
|
3937
4225
|
return {
|
|
3938
4226
|
role,
|
|
3939
4227
|
title,
|
|
3940
4228
|
prompt,
|
|
3941
4229
|
planningInputDigest: typeof planning === "string" && /^sha256:[0-9a-f]{64}$/.test(planning) ? planning : null,
|
|
3942
|
-
attestationDigest: typeof attestation === "string" ? attestation : "resume"
|
|
4230
|
+
attestationDigest: typeof attestation === "string" ? attestation : "resume",
|
|
4231
|
+
repository,
|
|
4232
|
+
baseCommitSha,
|
|
4233
|
+
sourceTreeDigest
|
|
3943
4234
|
};
|
|
3944
4235
|
}
|
|
4236
|
+
function codeLocalSource(payload) {
|
|
4237
|
+
const source = record22(payload.source);
|
|
4238
|
+
if (!source) return null;
|
|
4239
|
+
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) {
|
|
4240
|
+
throw new TypeError("invalid local checkout source descriptor");
|
|
4241
|
+
}
|
|
4242
|
+
return source;
|
|
4243
|
+
}
|
|
3945
4244
|
function codeCheckpointPayload(payload) {
|
|
3946
4245
|
const value = payload.checkpoint;
|
|
3947
4246
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
|
|
@@ -3970,11 +4269,63 @@ function fakeCodeLease(command, metadata2) {
|
|
|
3970
4269
|
};
|
|
3971
4270
|
}
|
|
3972
4271
|
var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4272
|
+
var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
|
|
4273
|
+
async function prepareRuntimeLocalSource(input) {
|
|
4274
|
+
const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
|
|
4275
|
+
if (!available || JSON.stringify(available.descriptor) !== JSON.stringify(descriptor2) || descriptor2.repository.toLowerCase() !== repository.toLowerCase() || descriptor2.headCommitSha !== baseCommitSha) {
|
|
4276
|
+
throw new TypeError("the session's local checkout snapshot is not available on this terminal");
|
|
4277
|
+
}
|
|
4278
|
+
const workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
|
|
4279
|
+
trustedBaseDir: available.trustedBaseDir,
|
|
4280
|
+
trustedBaseCommitSha: baseCommitSha,
|
|
4281
|
+
checkpoint: codeCheckpointPayload(command.payload)
|
|
4282
|
+
})).workspace : await stageWorkspacePair(available.trustedBaseDir, available.sourceDir, SOURCE_LIMITS);
|
|
4283
|
+
const trustedBaseDigest = await digestStagedWorkspace(workspace.baselineDir, SOURCE_LIMITS);
|
|
4284
|
+
if (trustedBaseDigest !== descriptor2.trustedBaseDigest) {
|
|
4285
|
+
await workspace.cleanup();
|
|
4286
|
+
throw new TypeError("trusted Git base digest changed after connection");
|
|
4287
|
+
}
|
|
4288
|
+
if (!resume && await digestStagedWorkspace(workspace.workspaceDir, SOURCE_LIMITS) !== descriptor2.snapshotDigest) {
|
|
4289
|
+
await workspace.cleanup();
|
|
4290
|
+
throw new TypeError("local checkout snapshot digest changed after connection");
|
|
4291
|
+
}
|
|
4292
|
+
return { workspace, sourceDigest: descriptor2.snapshotDigest, trustedBaseDigest };
|
|
4293
|
+
}
|
|
4294
|
+
function createCodeRuntimeToolBroker(input, lease, role) {
|
|
4295
|
+
const broker = createCodeToolBroker({
|
|
4296
|
+
recipes: input.recipes,
|
|
4297
|
+
recipeExecutor: createContainerRecipeExecutor(input.engine),
|
|
4298
|
+
recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
|
|
4299
|
+
readerId: `code-session:${lease.task.taskId}`,
|
|
4300
|
+
readOnlyPrefixes: [".odla-references"]
|
|
4301
|
+
});
|
|
4302
|
+
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" }) };
|
|
4303
|
+
}
|
|
4304
|
+
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
4305
|
+
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
4306
|
+
refs.push(eventId);
|
|
4307
|
+
const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
|
|
4308
|
+
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
4309
|
+
}
|
|
4310
|
+
var digestRuntimeValue = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
|
|
4311
|
+
var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
|
|
4312
|
+
var runtimeRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4313
|
+
var safeRuntimeJson = (value) => {
|
|
4314
|
+
try {
|
|
4315
|
+
return JSON.stringify(value).slice(0, 1e4);
|
|
4316
|
+
} catch {
|
|
4317
|
+
return "[event]";
|
|
4318
|
+
}
|
|
4319
|
+
};
|
|
4320
|
+
function runtimeResultText(value) {
|
|
4321
|
+
const record32 = runtimeRecord(value);
|
|
4322
|
+
return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
|
|
4323
|
+
}
|
|
3973
4324
|
var CodePiRuntimeEngine = class {
|
|
3974
4325
|
constructor(options) {
|
|
3975
4326
|
this.options = options;
|
|
3976
4327
|
this.#run = options.runAttempt ?? runContainerAttempt;
|
|
3977
|
-
this.#buildPolicyDigest =
|
|
4328
|
+
this.#buildPolicyDigest = digestRuntimeValue(JSON.stringify(options.recipes));
|
|
3978
4329
|
this.#checkpoints = new CodeRuntimeCheckpointManager({
|
|
3979
4330
|
control: options.control,
|
|
3980
4331
|
recipes: options.recipes,
|
|
@@ -4009,19 +4360,43 @@ var CodePiRuntimeEngine = class {
|
|
|
4009
4360
|
}
|
|
4010
4361
|
async #start(command, resume) {
|
|
4011
4362
|
if (this.#active.has(command.sessionId)) throw new TypeError("Code session is already active on this runtime");
|
|
4012
|
-
const
|
|
4013
|
-
const
|
|
4363
|
+
const metadata2 = codeCommandMetadata(command.payload, resume);
|
|
4364
|
+
const requestedLocal = codeLocalSource(command.payload);
|
|
4014
4365
|
let workspace;
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4366
|
+
let sourceDigest;
|
|
4367
|
+
let localTrustedBaseDigest;
|
|
4368
|
+
if (requestedLocal) {
|
|
4369
|
+
const prepared = await prepareRuntimeLocalSource({
|
|
4370
|
+
command,
|
|
4371
|
+
descriptor: requestedLocal,
|
|
4372
|
+
available: this.options.localSource,
|
|
4373
|
+
repository: metadata2.repository,
|
|
4374
|
+
baseCommitSha: metadata2.baseCommitSha,
|
|
4375
|
+
resume
|
|
4376
|
+
});
|
|
4377
|
+
({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
|
|
4378
|
+
if (command.payload.sourceSet) {
|
|
4379
|
+
const selected = await this.options.control.source(command.sessionId);
|
|
4380
|
+
if (selected.repository !== metadata2.repository || selected.commitSha !== metadata2.baseCommitSha || selected.treeDigest !== metadata2.sourceTreeDigest) {
|
|
4381
|
+
await workspace.cleanup();
|
|
4382
|
+
throw new TypeError("Code local source does not match the selected GitHub primary source");
|
|
4383
|
+
}
|
|
4384
|
+
await attachCodeRuntimeReferences(workspace, selected.references ?? []);
|
|
4385
|
+
}
|
|
4386
|
+
} else {
|
|
4387
|
+
const source = await this.options.control.source(command.sessionId);
|
|
4388
|
+
const materialized = await materializeCodeRuntimeSource(source);
|
|
4389
|
+
try {
|
|
4390
|
+
workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
|
|
4391
|
+
trustedBaseDir: materialized.sourceDir,
|
|
4392
|
+
trustedBaseCommitSha: source.commitSha,
|
|
4393
|
+
checkpoint: codeCheckpointPayload(command.payload)
|
|
4394
|
+
})).workspace : await stageWorkspace(materialized.sourceDir);
|
|
4395
|
+
} finally {
|
|
4396
|
+
await materialized.cleanup();
|
|
4397
|
+
}
|
|
4398
|
+
sourceDigest = source.treeDigest;
|
|
4023
4399
|
}
|
|
4024
|
-
const metadata2 = codeCommandMetadata(command.payload, resume);
|
|
4025
4400
|
const abort = new AbortController();
|
|
4026
4401
|
const conversationRefs = [];
|
|
4027
4402
|
const active = {
|
|
@@ -4031,21 +4406,30 @@ var CodePiRuntimeEngine = class {
|
|
|
4031
4406
|
acknowledged: false,
|
|
4032
4407
|
role: metadata2.role,
|
|
4033
4408
|
title: metadata2.title,
|
|
4034
|
-
baseCommitSha:
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4409
|
+
baseCommitSha: metadata2.baseCommitSha,
|
|
4410
|
+
repository: metadata2.repository,
|
|
4411
|
+
sourceTreeDigest: metadata2.sourceTreeDigest,
|
|
4412
|
+
trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
|
|
4413
|
+
maxFiles: 2e4,
|
|
4414
|
+
maxBytes: 512 * 1024 * 1024
|
|
4038
4415
|
}),
|
|
4039
|
-
planningInputDigest: metadata2.planningInputDigest ??
|
|
4040
|
-
JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree:
|
|
4416
|
+
planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
|
|
4417
|
+
JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })
|
|
4041
4418
|
),
|
|
4042
4419
|
done: Promise.resolve(null)
|
|
4043
4420
|
};
|
|
4044
4421
|
this.#active.set(command.sessionId, active);
|
|
4422
|
+
if (requestedLocal) {
|
|
4423
|
+
await this.#event(command, {
|
|
4424
|
+
type: "message",
|
|
4425
|
+
actor: "system",
|
|
4426
|
+
body: `Source snapshot: local checkout ${requestedLocal.snapshotDigest} \xB7 ${requestedLocal.modified ? "modified" : "clean"} \xB7 Git ${requestedLocal.headCommitSha}`
|
|
4427
|
+
}, conversationRefs);
|
|
4428
|
+
}
|
|
4045
4429
|
active.done = this.#runAttempt(command, metadata2, active).catch(async (cause) => {
|
|
4046
|
-
await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${
|
|
4430
|
+
await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` }, conversationRefs).catch(() => void 0);
|
|
4047
4431
|
await this.#event(command, { type: "status", status: "failed" }, conversationRefs).catch(() => void 0);
|
|
4048
|
-
await this.#failure(command, active,
|
|
4432
|
+
await this.#failure(command, active, runtimeErrorMessage(cause));
|
|
4049
4433
|
return null;
|
|
4050
4434
|
});
|
|
4051
4435
|
return { status: "running", message: resume ? "Pi resumed from a portable checkpoint" : "Pi started" };
|
|
@@ -4065,22 +4449,29 @@ var CodePiRuntimeEngine = class {
|
|
|
4065
4449
|
title: active.title,
|
|
4066
4450
|
prompt,
|
|
4067
4451
|
planningInputDigest: active.planningInputDigest,
|
|
4068
|
-
attestationDigest: "follow-up"
|
|
4452
|
+
attestationDigest: "follow-up",
|
|
4453
|
+
repository: active.repository,
|
|
4454
|
+
baseCommitSha: active.baseCommitSha,
|
|
4455
|
+
sourceTreeDigest: active.sourceTreeDigest
|
|
4069
4456
|
}, active).catch(async (cause) => {
|
|
4070
4457
|
await this.#event(
|
|
4071
4458
|
command,
|
|
4072
|
-
{ type: "message", actor: "system", body: `Pi failed: ${
|
|
4459
|
+
{ type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` },
|
|
4073
4460
|
active.conversationRefs
|
|
4074
4461
|
).catch(() => void 0);
|
|
4075
4462
|
await this.#event(command, { type: "status", status: "failed" }, active.conversationRefs).catch(() => void 0);
|
|
4076
|
-
await this.#failure(command, active,
|
|
4463
|
+
await this.#failure(command, active, runtimeErrorMessage(cause));
|
|
4077
4464
|
return null;
|
|
4078
4465
|
});
|
|
4079
4466
|
return { status: "running", message: "Pi accepted the owner prompt" };
|
|
4080
4467
|
}
|
|
4081
4468
|
async #runAttempt(command, metadata2, active) {
|
|
4082
4469
|
const lease = fakeCodeLease(command, metadata2);
|
|
4083
|
-
const broker =
|
|
4470
|
+
const broker = createCodeRuntimeToolBroker({
|
|
4471
|
+
recipes: this.options.recipes,
|
|
4472
|
+
engine: this.options.engine,
|
|
4473
|
+
recipeAuthorization: this.options.recipeAuthorization
|
|
4474
|
+
}, lease, metadata2.role);
|
|
4084
4475
|
const startedAt = Date.now();
|
|
4085
4476
|
let completionSeen = false;
|
|
4086
4477
|
const result = await this.#run({
|
|
@@ -4140,7 +4531,7 @@ var CodePiRuntimeEngine = class {
|
|
|
4140
4531
|
return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
|
|
4141
4532
|
}
|
|
4142
4533
|
if (output.type === "event") {
|
|
4143
|
-
const payload =
|
|
4534
|
+
const payload = runtimeRecord(output.payload);
|
|
4144
4535
|
if (output.kind === "pi.started") {
|
|
4145
4536
|
await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
|
|
4146
4537
|
} else if (output.kind === "pi.thinking" && payload?.available === true && Number.isSafeInteger(payload.durationMs) && Number(payload.durationMs) >= 0) {
|
|
@@ -4153,12 +4544,12 @@ var CodePiRuntimeEngine = class {
|
|
|
4153
4544
|
await this.#event(command, {
|
|
4154
4545
|
type: "message",
|
|
4155
4546
|
actor: "system",
|
|
4156
|
-
body: `${output.kind}${output.payload === void 0 ? "" : ` ${
|
|
4547
|
+
body: `${output.kind}${output.payload === void 0 ? "" : ` ${safeRuntimeJson(output.payload)}`}`
|
|
4157
4548
|
}, active.conversationRefs);
|
|
4158
4549
|
}
|
|
4159
4550
|
} else if (output.type === "attempt.complete") {
|
|
4160
4551
|
completionSeen = true;
|
|
4161
|
-
const body =
|
|
4552
|
+
const body = runtimeResultText(output.result) ?? `Pi ${output.status}.`;
|
|
4162
4553
|
await this.#event(command, {
|
|
4163
4554
|
type: "message",
|
|
4164
4555
|
actor: output.status === "completed" ? "agent" : "system",
|
|
@@ -4185,15 +4576,6 @@ var CodePiRuntimeEngine = class {
|
|
|
4185
4576
|
}
|
|
4186
4577
|
return result;
|
|
4187
4578
|
}
|
|
4188
|
-
#toolBroker(lease, role) {
|
|
4189
|
-
const broker = createCodeToolBroker({
|
|
4190
|
-
recipes: this.options.recipes,
|
|
4191
|
-
recipeExecutor: createContainerRecipeExecutor(this.options.engine),
|
|
4192
|
-
recipeAuthorization: this.options.recipeAuthorization ?? "registered_recipe",
|
|
4193
|
-
readerId: `code-session:${lease.task.taskId}`
|
|
4194
|
-
});
|
|
4195
|
-
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" }) };
|
|
4196
|
-
}
|
|
4197
4579
|
async #checkpoint(command) {
|
|
4198
4580
|
const active = this.#active.get(command.sessionId);
|
|
4199
4581
|
if (!active) throw new TypeError("Code session workspace is not active on this runtime");
|
|
@@ -4208,26 +4590,9 @@ var CodePiRuntimeEngine = class {
|
|
|
4208
4590
|
}
|
|
4209
4591
|
}
|
|
4210
4592
|
async #event(command, event, refs) {
|
|
4211
|
-
|
|
4212
|
-
refs.push(eventId);
|
|
4213
|
-
const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
|
|
4214
|
-
await this.options.control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
4215
|
-
}
|
|
4216
|
-
};
|
|
4217
|
-
var object2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4218
|
-
var digest = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
|
|
4219
|
-
var message2 = (value) => value instanceof Error ? value.message : String(value);
|
|
4220
|
-
var json = (value) => {
|
|
4221
|
-
try {
|
|
4222
|
-
return JSON.stringify(value).slice(0, 1e4);
|
|
4223
|
-
} catch {
|
|
4224
|
-
return "[event]";
|
|
4593
|
+
await appendCodeRuntimeEvent(this.options.control, command, event, refs);
|
|
4225
4594
|
}
|
|
4226
4595
|
};
|
|
4227
|
-
function resultText(value) {
|
|
4228
|
-
const record32 = object2(value);
|
|
4229
|
-
return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
|
|
4230
|
-
}
|
|
4231
4596
|
|
|
4232
4597
|
// src/help.ts
|
|
4233
4598
|
import { readFileSync as readFileSync3 } from "fs";
|
|
@@ -4345,24 +4710,90 @@ Safety:
|
|
|
4345
4710
|
`);
|
|
4346
4711
|
}
|
|
4347
4712
|
|
|
4713
|
+
// src/code-local-source.ts
|
|
4714
|
+
import { execFile as execFile3 } from "child_process";
|
|
4715
|
+
import { createHash as createHash4 } from "crypto";
|
|
4716
|
+
var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
|
|
4717
|
+
async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
|
|
4718
|
+
const headCommitSha = await readHead(cwd);
|
|
4719
|
+
const staged = await stageWorkspace(cwd, { ...SOURCE_LIMITS2, gitTrackedAndUnignored: true });
|
|
4720
|
+
let trusted;
|
|
4721
|
+
try {
|
|
4722
|
+
trusted = await materializeGitTree(cwd, headCommitSha, SOURCE_LIMITS2);
|
|
4723
|
+
const comparison = await stageWorkspacePair(trusted.sourceDir, staged.baselineDir, SOURCE_LIMITS2);
|
|
4724
|
+
let developerPatch;
|
|
4725
|
+
try {
|
|
4726
|
+
developerPatch = await comparison.patch(4 * 1024 * 1024);
|
|
4727
|
+
} finally {
|
|
4728
|
+
await comparison.cleanup();
|
|
4729
|
+
}
|
|
4730
|
+
const descriptor2 = {
|
|
4731
|
+
kind: "local_checkout",
|
|
4732
|
+
repository,
|
|
4733
|
+
headCommitSha,
|
|
4734
|
+
trustedBaseDigest: await digestStagedWorkspace(trusted.sourceDir, SOURCE_LIMITS2),
|
|
4735
|
+
developerPatchDigest: digestText(developerPatch),
|
|
4736
|
+
snapshotDigest: await digestStagedWorkspace(staged.baselineDir, SOURCE_LIMITS2),
|
|
4737
|
+
modified: developerPatch.length > 0,
|
|
4738
|
+
fileCount: staged.fileCount,
|
|
4739
|
+
byteCount: staged.byteCount,
|
|
4740
|
+
capturedAt: Date.now()
|
|
4741
|
+
};
|
|
4742
|
+
return {
|
|
4743
|
+
descriptor: descriptor2,
|
|
4744
|
+
trustedBaseDir: trusted.sourceDir,
|
|
4745
|
+
sourceDir: staged.baselineDir,
|
|
4746
|
+
cleanup: async () => {
|
|
4747
|
+
await Promise.all([staged.cleanup(), trusted.cleanup()]);
|
|
4748
|
+
}
|
|
4749
|
+
};
|
|
4750
|
+
} catch (error) {
|
|
4751
|
+
await Promise.all([staged.cleanup(), trusted?.cleanup()]);
|
|
4752
|
+
throw error;
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
4755
|
+
async function readGitHead(cwd) {
|
|
4756
|
+
const value = await new Promise((accept, reject) => {
|
|
4757
|
+
execFile3("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
|
|
4758
|
+
if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
|
|
4759
|
+
else accept(stdout.trim());
|
|
4760
|
+
});
|
|
4761
|
+
});
|
|
4762
|
+
if (!/^[0-9a-f]{40}$/.test(value)) throw new Error("code connect could not resolve the checkout HEAD commit");
|
|
4763
|
+
return value;
|
|
4764
|
+
}
|
|
4765
|
+
function digestText(value) {
|
|
4766
|
+
return `sha256:${createHash4("sha256").update(value).digest("hex")}`;
|
|
4767
|
+
}
|
|
4768
|
+
|
|
4769
|
+
// src/code-images.ts
|
|
4770
|
+
import { execFile as execFile4 } from "child_process";
|
|
4771
|
+
async function prepareCodeImages(engine, images) {
|
|
4772
|
+
for (const image of images) {
|
|
4773
|
+
await new Promise((accept, reject) => {
|
|
4774
|
+
const args = engine === "container" ? ["image", "pull", image] : ["pull", image];
|
|
4775
|
+
execFile4(engine, args, { encoding: "utf8", maxBuffer: 2 * 1024 * 1024, timeout: 10 * 6e4 }, (error) => {
|
|
4776
|
+
if (error) reject(new Error(`could not prepare pinned Code image ${image}`));
|
|
4777
|
+
else accept();
|
|
4778
|
+
});
|
|
4779
|
+
});
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
|
|
4348
4783
|
// src/code-connect.ts
|
|
4349
|
-
var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:713db58428bc05cb486167c5747de8fd826669c9b283ff78e0139ac194c3d058";
|
|
4350
|
-
var CODE_BUILD_RECIPES = Object.freeze([{
|
|
4351
|
-
id: "node-test",
|
|
4352
|
-
image: "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd",
|
|
4353
|
-
command: ["node", "--test"],
|
|
4354
|
-
timeoutMs: 12e4,
|
|
4355
|
-
maxOutputBytes: 1024 * 1024,
|
|
4356
|
-
cpus: 1,
|
|
4357
|
-
memory: "512m",
|
|
4358
|
-
pids: 128
|
|
4359
|
-
}]);
|
|
4360
4784
|
async function codeConnect(options) {
|
|
4361
4785
|
const cwd = options.cwd ?? process.cwd();
|
|
4362
4786
|
const configPath = resolve5(cwd, options.configPath);
|
|
4363
4787
|
const cfg = existsSync4(configPath) ? await loadProjectConfig(configPath) : null;
|
|
4364
|
-
const
|
|
4365
|
-
|
|
4788
|
+
const requestedAppId = options.appId?.trim();
|
|
4789
|
+
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
4790
|
+
throw new Error("--app-id must be a valid odla app id");
|
|
4791
|
+
}
|
|
4792
|
+
if (requestedAppId && cfg && requestedAppId !== cfg.app.id) {
|
|
4793
|
+
throw new Error(`--app-id ${requestedAppId} does not match ${options.configPath} (${cfg.app.id})`);
|
|
4794
|
+
}
|
|
4795
|
+
const appId = requestedAppId ?? cfg?.app.id;
|
|
4796
|
+
const platform = (options.platform ?? cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai").replace(/\/+$/, "");
|
|
4366
4797
|
const appEnv = options.env ?? (cfg ? cfg.envs.includes("dev") ? "dev" : cfg.envs[0] : "dev");
|
|
4367
4798
|
if (appEnv !== "dev" && appEnv !== "prod" || cfg && !cfg.envs.includes(appEnv)) {
|
|
4368
4799
|
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`);
|
|
@@ -4381,53 +4812,77 @@ async function codeConnect(options) {
|
|
|
4381
4812
|
const out = options.stdout ?? console;
|
|
4382
4813
|
const doFetch = options.fetch ?? fetch;
|
|
4383
4814
|
const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
|
|
4815
|
+
await (options.prepareImages ?? prepareCodeImages)(engine, [
|
|
4816
|
+
CODE_PI_IMAGE,
|
|
4817
|
+
...new Set(CODE_BUILD_RECIPES.map((recipe2) => recipe2.image))
|
|
4818
|
+
]);
|
|
4384
4819
|
const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
|
|
4385
4820
|
const hostName = (options.name ?? hostname()).trim();
|
|
4386
4821
|
if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
|
|
4387
|
-
const
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4822
|
+
const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
|
|
4823
|
+
const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
|
|
4824
|
+
cwd,
|
|
4825
|
+
repository,
|
|
4826
|
+
options.readGitHead
|
|
4827
|
+
);
|
|
4828
|
+
try {
|
|
4829
|
+
const descriptor2 = localSource.descriptor;
|
|
4830
|
+
const approval = await (options.getToken ?? getScopedPlatformToken)({
|
|
4831
|
+
platform,
|
|
4832
|
+
scope: "app:code:host:connect",
|
|
4833
|
+
email: options.email,
|
|
4834
|
+
open: options.open,
|
|
4835
|
+
fetch: doFetch,
|
|
4836
|
+
stdout: out,
|
|
4837
|
+
openApprovalUrl: options.openApprovalUrl,
|
|
4838
|
+
cache: false,
|
|
4839
|
+
label: `Terminal connection for ${appId ?? repository}/${appEnv}`
|
|
4840
|
+
});
|
|
4841
|
+
const target = appId ? { appId } : { repository };
|
|
4842
|
+
const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
|
|
4843
|
+
method: "POST",
|
|
4844
|
+
headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
|
|
4845
|
+
body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
|
|
4846
|
+
redirect: "error",
|
|
4847
|
+
signal: options.signal
|
|
4848
|
+
});
|
|
4849
|
+
const raw = await response2.json().catch(() => null);
|
|
4850
|
+
if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
|
|
4851
|
+
const connection = parseConnection(raw, appId, appEnv);
|
|
4852
|
+
const capabilities = {
|
|
4853
|
+
protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
|
|
4854
|
+
platform: hostPlatform,
|
|
4855
|
+
arch: process.arch,
|
|
4856
|
+
engines: [engine],
|
|
4857
|
+
cpuCount: cpus().length,
|
|
4858
|
+
memoryBytes: totalmem(),
|
|
4859
|
+
source: descriptor2,
|
|
4860
|
+
images: {
|
|
4861
|
+
ready: true,
|
|
4862
|
+
pi: CODE_PI_IMAGE,
|
|
4863
|
+
recipes: CODE_BUILD_RECIPES.map((recipe2) => ({ id: recipe2.id, image: recipe2.image }))
|
|
4864
|
+
}
|
|
4865
|
+
};
|
|
4866
|
+
out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
|
|
4867
|
+
out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
|
|
4868
|
+
out.log(`source: local checkout \xB7 ${descriptor2.headCommitSha.slice(0, 10)} \xB7 ${descriptor2.fileCount} files \xB7 staged copy`);
|
|
4869
|
+
out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
|
|
4870
|
+
out.log(`Studio: ${platform}/studio/apps/${encodeURIComponent(connection.binding.appId)}/${connection.binding.env}/code/sessions`);
|
|
4871
|
+
await (options.runRuntime ?? runCodeRuntime)({
|
|
4872
|
+
endpoint: platform,
|
|
4873
|
+
token: connection.token,
|
|
4874
|
+
engine,
|
|
4875
|
+
capabilities,
|
|
4876
|
+
localSource,
|
|
4877
|
+
heartbeatMs,
|
|
4878
|
+
once: options.once === true,
|
|
4879
|
+
signal: options.signal,
|
|
4880
|
+
stdout: out,
|
|
4881
|
+
fetch: doFetch
|
|
4882
|
+
});
|
|
4883
|
+
} finally {
|
|
4884
|
+
await localSource.cleanup();
|
|
4885
|
+
}
|
|
4431
4886
|
}
|
|
4432
4887
|
async function runCodeRuntime(input) {
|
|
4433
4888
|
const controller = new AbortController();
|
|
@@ -4450,7 +4905,8 @@ async function runCodeRuntime(input) {
|
|
|
4450
4905
|
engine: input.engine,
|
|
4451
4906
|
image: CODE_PI_IMAGE,
|
|
4452
4907
|
recipes: CODE_BUILD_RECIPES,
|
|
4453
|
-
recipeAuthorization: "registered_recipe"
|
|
4908
|
+
recipeAuthorization: "registered_recipe",
|
|
4909
|
+
localSource: input.localSource
|
|
4454
4910
|
});
|
|
4455
4911
|
const reconciler = new CodeRuntimeReconciler(control, commandEngine);
|
|
4456
4912
|
try {
|
|
@@ -4491,8 +4947,8 @@ function parseConnection(value, appId, appEnv) {
|
|
|
4491
4947
|
return root;
|
|
4492
4948
|
}
|
|
4493
4949
|
function apiFailure(action, status, value) {
|
|
4494
|
-
const
|
|
4495
|
-
return `${action} failed (${status})${typeof
|
|
4950
|
+
const message2 = record4(record4(value)?.error)?.message;
|
|
4951
|
+
return `${action} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
4496
4952
|
}
|
|
4497
4953
|
function record4(value) {
|
|
4498
4954
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -4501,14 +4957,14 @@ function record4(value) {
|
|
|
4501
4957
|
// src/doctor-checks.ts
|
|
4502
4958
|
import { execFileSync } from "child_process";
|
|
4503
4959
|
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
4504
|
-
import { join as
|
|
4960
|
+
import { join as join6, resolve as resolve6 } from "path";
|
|
4505
4961
|
|
|
4506
4962
|
// src/wrangler.ts
|
|
4507
|
-
import { spawn as
|
|
4963
|
+
import { spawn as spawn5 } from "child_process";
|
|
4508
4964
|
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
4509
|
-
import { join as
|
|
4965
|
+
import { join as join5 } from "path";
|
|
4510
4966
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
4511
|
-
const child =
|
|
4967
|
+
const child = spawn5(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
4512
4968
|
let stdout = "";
|
|
4513
4969
|
let stderr = "";
|
|
4514
4970
|
child.stdout.on("data", (chunk) => stdout += chunk.toString());
|
|
@@ -4520,7 +4976,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
|
|
|
4520
4976
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
4521
4977
|
function findWranglerConfig(rootDir) {
|
|
4522
4978
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
4523
|
-
const path =
|
|
4979
|
+
const path = join5(rootDir, name);
|
|
4524
4980
|
if (existsSync5(path)) return path;
|
|
4525
4981
|
}
|
|
4526
4982
|
return null;
|
|
@@ -4629,7 +5085,7 @@ function wranglerWarnings(rootDir) {
|
|
|
4629
5085
|
const dir = resolve6(rootDir, assets.directory);
|
|
4630
5086
|
if (dir === resolve6(rootDir)) {
|
|
4631
5087
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
4632
|
-
} else if (existsSync6(
|
|
5088
|
+
} else if (existsSync6(join6(dir, "node_modules"))) {
|
|
4633
5089
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
4634
5090
|
}
|
|
4635
5091
|
}
|
|
@@ -4694,7 +5150,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
4694
5150
|
}
|
|
4695
5151
|
function readPackageJson(rootDir) {
|
|
4696
5152
|
try {
|
|
4697
|
-
return JSON.parse(readFileSync5(
|
|
5153
|
+
return JSON.parse(readFileSync5(join6(rootDir, "package.json"), "utf8"));
|
|
4698
5154
|
} catch {
|
|
4699
5155
|
return null;
|
|
4700
5156
|
}
|
|
@@ -5261,8 +5717,8 @@ async function readRegistryApp(cfg, token, doFetch) {
|
|
|
5261
5717
|
});
|
|
5262
5718
|
if (res.status === 404) return null;
|
|
5263
5719
|
if (!res.ok) return null;
|
|
5264
|
-
const
|
|
5265
|
-
return
|
|
5720
|
+
const json = await res.json();
|
|
5721
|
+
return json.app ?? null;
|
|
5266
5722
|
}
|
|
5267
5723
|
async function postJson2(doFetch, url, bearer, body) {
|
|
5268
5724
|
const res = await doFetch(url, {
|
|
@@ -5428,10 +5884,10 @@ async function provision(options) {
|
|
|
5428
5884
|
stdout: out
|
|
5429
5885
|
});
|
|
5430
5886
|
} catch (error) {
|
|
5431
|
-
const
|
|
5887
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
5432
5888
|
const consent = env === "prod" || env === "production" ? " --yes" : "";
|
|
5433
5889
|
throw new Error(
|
|
5434
|
-
`${
|
|
5890
|
+
`${message2}
|
|
5435
5891
|
${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}${consent}" without issuing or rotating again`,
|
|
5436
5892
|
{ cause: error }
|
|
5437
5893
|
);
|
|
@@ -5547,7 +6003,7 @@ async function resolveVaultWrite(options) {
|
|
|
5547
6003
|
}
|
|
5548
6004
|
|
|
5549
6005
|
// src/security.ts
|
|
5550
|
-
import { isAbsolute as
|
|
6006
|
+
import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve8, sep as sep4 } from "path";
|
|
5551
6007
|
import {
|
|
5552
6008
|
cloudflareAppProfile,
|
|
5553
6009
|
createPlatformSecurityReasoners,
|
|
@@ -5568,7 +6024,7 @@ async function runHostedSecurity(options) {
|
|
|
5568
6024
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
5569
6025
|
const target = resolve8(options.target ?? cfg?.rootDir ?? ".");
|
|
5570
6026
|
const output = resolve8(options.out ?? resolve8(target, ".odla/security/hosted"));
|
|
5571
|
-
const outputRelative = relative4(target, output).split(
|
|
6027
|
+
const outputRelative = relative4(target, output).split(sep4).join("/");
|
|
5572
6028
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
5573
6029
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
5574
6030
|
const tokenRequest = {
|
|
@@ -5580,7 +6036,7 @@ async function runHostedSecurity(options) {
|
|
|
5580
6036
|
};
|
|
5581
6037
|
const token = await injectedToken(options, tokenRequest);
|
|
5582
6038
|
const snapshot = await snapshotDirectory(target, {
|
|
5583
|
-
exclude: !outputRelative.startsWith("../") && !
|
|
6039
|
+
exclude: !outputRelative.startsWith("../") && !isAbsolute4(outputRelative) ? [outputRelative] : []
|
|
5584
6040
|
});
|
|
5585
6041
|
const hosted = await createPlatformSecurityReasoners({
|
|
5586
6042
|
platform,
|
|
@@ -5741,8 +6197,8 @@ async function startHostedSecurityJob(options) {
|
|
|
5741
6197
|
"start hosted security job"
|
|
5742
6198
|
);
|
|
5743
6199
|
} catch (error) {
|
|
5744
|
-
const
|
|
5745
|
-
if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(
|
|
6200
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
6201
|
+
if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message2)) {
|
|
5746
6202
|
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 });
|
|
5747
6203
|
}
|
|
5748
6204
|
throw error;
|
|
@@ -5823,7 +6279,7 @@ function securityExecutionDigest(value) {
|
|
|
5823
6279
|
// src/skill.ts
|
|
5824
6280
|
import { existsSync as existsSync8, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync, writeFileSync as writeFileSync3 } from "fs";
|
|
5825
6281
|
import { homedir } from "os";
|
|
5826
|
-
import { dirname as dirname6, isAbsolute as
|
|
6282
|
+
import { dirname as dirname6, isAbsolute as isAbsolute5, join as join7, relative as relative5, resolve as resolve9, sep as sep5 } from "path";
|
|
5827
6283
|
import { fileURLToPath } from "url";
|
|
5828
6284
|
|
|
5829
6285
|
// src/skill-adapters.ts
|
|
@@ -5899,12 +6355,12 @@ function installSkill(options = {}) {
|
|
|
5899
6355
|
plans.set(target, { target, content, boundary, managedMerge });
|
|
5900
6356
|
};
|
|
5901
6357
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
5902
|
-
for (const rel of files) plan(
|
|
6358
|
+
for (const rel of files) plan(join7(targetDir2, rel), readFileSync6(join7(sourceDir, rel), "utf8"), false, boundary);
|
|
5903
6359
|
};
|
|
5904
6360
|
let targetDir;
|
|
5905
6361
|
if (options.global) {
|
|
5906
|
-
const claudeRoot =
|
|
5907
|
-
const codexRoot = resolve9(options.codexHomeDir ?? process.env.CODEX_HOME ??
|
|
6362
|
+
const claudeRoot = join7(home, ".claude", "skills");
|
|
6363
|
+
const codexRoot = resolve9(options.codexHomeDir ?? process.env.CODEX_HOME ?? join7(home, ".codex"), "skills");
|
|
5908
6364
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
5909
6365
|
for (const harness of harnesses) {
|
|
5910
6366
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
@@ -5912,35 +6368,35 @@ function installSkill(options = {}) {
|
|
|
5912
6368
|
rememberTarget(harness, skillRoot);
|
|
5913
6369
|
}
|
|
5914
6370
|
} else {
|
|
5915
|
-
const sharedRoot =
|
|
6371
|
+
const sharedRoot = join7(root, ".agents", "skills");
|
|
5916
6372
|
planSkillTree(sharedRoot);
|
|
5917
|
-
const claudeRoot =
|
|
6373
|
+
const claudeRoot = join7(root, ".claude", "skills");
|
|
5918
6374
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
5919
6375
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
5920
6376
|
if (harnesses.includes("claude")) {
|
|
5921
6377
|
for (const skill of skillNames(files)) {
|
|
5922
|
-
const canonical = readFileSync6(
|
|
5923
|
-
plan(
|
|
6378
|
+
const canonical = readFileSync6(join7(sourceDir, skill, "SKILL.md"), "utf8");
|
|
6379
|
+
plan(join7(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
|
|
5924
6380
|
}
|
|
5925
6381
|
rememberTarget("claude", claudeRoot);
|
|
5926
6382
|
}
|
|
5927
6383
|
if (harnesses.includes("cursor")) {
|
|
5928
|
-
const cursorRule =
|
|
6384
|
+
const cursorRule = join7(root, ".cursor", "rules", "odla.mdc");
|
|
5929
6385
|
plan(cursorRule, CURSOR_RULE);
|
|
5930
6386
|
rememberTarget("cursor", cursorRule);
|
|
5931
6387
|
}
|
|
5932
6388
|
if (harnesses.includes("agents")) {
|
|
5933
|
-
const agentsFile =
|
|
6389
|
+
const agentsFile = join7(root, "AGENTS.md");
|
|
5934
6390
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5935
6391
|
rememberTarget("agents", agentsFile);
|
|
5936
6392
|
}
|
|
5937
6393
|
if (harnesses.includes("copilot")) {
|
|
5938
|
-
const copilotFile =
|
|
6394
|
+
const copilotFile = join7(root, ".github", "copilot-instructions.md");
|
|
5939
6395
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5940
6396
|
rememberTarget("copilot", copilotFile);
|
|
5941
6397
|
}
|
|
5942
6398
|
if (harnesses.includes("gemini")) {
|
|
5943
|
-
const geminiFile =
|
|
6399
|
+
const geminiFile = join7(root, "GEMINI.md");
|
|
5944
6400
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5945
6401
|
rememberTarget("gemini", geminiFile);
|
|
5946
6402
|
}
|
|
@@ -5996,7 +6452,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
5996
6452
|
};
|
|
5997
6453
|
}
|
|
5998
6454
|
function pathsUnder(root, paths) {
|
|
5999
|
-
return [...paths].map((path) => relative5(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${
|
|
6455
|
+
return [...paths].map((path) => relative5(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep5}`) && !isAbsolute5(path)).sort();
|
|
6000
6456
|
}
|
|
6001
6457
|
function normalizeHarnesses(values, global) {
|
|
6002
6458
|
const requested = values?.length ? values : ["claude"];
|
|
@@ -6042,12 +6498,12 @@ function managedFileContent(path, block, force, boundary) {
|
|
|
6042
6498
|
}
|
|
6043
6499
|
function symlinkedComponent(boundary, target) {
|
|
6044
6500
|
const rel = relative5(boundary, target);
|
|
6045
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
6501
|
+
if (rel === ".." || rel.startsWith(`..${sep5}`) || isAbsolute5(rel)) {
|
|
6046
6502
|
throw new Error(`agent setup target escapes its install root: ${target}`);
|
|
6047
6503
|
}
|
|
6048
6504
|
let current = boundary;
|
|
6049
|
-
for (const part of rel.split(
|
|
6050
|
-
current =
|
|
6505
|
+
for (const part of rel.split(sep5).filter(Boolean)) {
|
|
6506
|
+
current = join7(current, part);
|
|
6051
6507
|
try {
|
|
6052
6508
|
if (lstatSync(current).isSymbolicLink()) return current;
|
|
6053
6509
|
} catch (error) {
|
|
@@ -6064,7 +6520,7 @@ function listFiles(dir) {
|
|
|
6064
6520
|
const results = [];
|
|
6065
6521
|
const walk = (current) => {
|
|
6066
6522
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
6067
|
-
const path =
|
|
6523
|
+
const path = join7(current, entry.name);
|
|
6068
6524
|
if (entry.isDirectory()) walk(path);
|
|
6069
6525
|
else results.push(relative5(dir, path));
|
|
6070
6526
|
}
|
|
@@ -6555,6 +7011,8 @@ async function codeCommand(parsed, dependencies) {
|
|
|
6555
7011
|
assertArgs(parsed, [
|
|
6556
7012
|
"config",
|
|
6557
7013
|
"env",
|
|
7014
|
+
"platform",
|
|
7015
|
+
"app-id",
|
|
6558
7016
|
"email",
|
|
6559
7017
|
"open",
|
|
6560
7018
|
"name",
|
|
@@ -6570,6 +7028,8 @@ async function codeCommand(parsed, dependencies) {
|
|
|
6570
7028
|
await (dependencies.codeConnect ?? codeConnect)({
|
|
6571
7029
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
6572
7030
|
env: stringOpt(parsed.options.env),
|
|
7031
|
+
platform: stringOpt(parsed.options.platform),
|
|
7032
|
+
appId: stringOpt(parsed.options["app-id"]),
|
|
6573
7033
|
email: stringOpt(parsed.options.email),
|
|
6574
7034
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
6575
7035
|
name: stringOpt(parsed.options.name),
|
|
@@ -6627,12 +7087,12 @@ async function hostedSecurityContext(parsed, dependencies) {
|
|
|
6627
7087
|
);
|
|
6628
7088
|
return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
|
|
6629
7089
|
}
|
|
6630
|
-
async function interactiveConfirmation(
|
|
6631
|
-
if (dependencies.confirm) return dependencies.confirm(
|
|
7090
|
+
async function interactiveConfirmation(message2, dependencies) {
|
|
7091
|
+
if (dependencies.confirm) return dependencies.confirm(message2);
|
|
6632
7092
|
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
6633
7093
|
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
6634
7094
|
try {
|
|
6635
|
-
const answer = await prompt.question(`${
|
|
7095
|
+
const answer = await prompt.question(`${message2} [y/N] `);
|
|
6636
7096
|
return /^y(?:es)?$/i.test(answer.trim());
|
|
6637
7097
|
} finally {
|
|
6638
7098
|
prompt.close();
|
|
@@ -7286,4 +7746,4 @@ export {
|
|
|
7286
7746
|
exitCodeFor,
|
|
7287
7747
|
runCli
|
|
7288
7748
|
};
|
|
7289
|
-
//# sourceMappingURL=chunk-
|
|
7749
|
+
//# sourceMappingURL=chunk-S3OYXHDG.js.map
|