@odla-ai/cli 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -15
- package/dist/bin.cjs +665 -277
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-AN6KZMR5.js → chunk-E3CCYDIT.js} +670 -282
- package/dist/chunk-E3CCYDIT.js.map +1 -0
- package/dist/index.cjs +665 -277
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +53 -8
- package/dist/index.d.ts +53 -8
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/odla-migrate/references/phase-2-db.md +2 -2
- package/dist/chunk-AN6KZMR5.js.map +0 -1
|
@@ -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-QEW6NNAO.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-QEW6NNAO.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-QEW6NNAO.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
|
}
|
|
@@ -3032,9 +3277,9 @@ async function parseSource(value) {
|
|
|
3032
3277
|
return { path: file.path, content: file.content };
|
|
3033
3278
|
});
|
|
3034
3279
|
const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
|
|
3035
|
-
const
|
|
3036
|
-
if (
|
|
3037
|
-
return { ...source, treeDigest:
|
|
3280
|
+
const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
|
|
3281
|
+
if (digest !== snapshot.treeDigest) throw invalid("source digest");
|
|
3282
|
+
return { ...source, treeDigest: digest };
|
|
3038
3283
|
}
|
|
3039
3284
|
function parseReview(value) {
|
|
3040
3285
|
const review = record3(record3(value)?.review);
|
|
@@ -3093,9 +3338,9 @@ function validateRelativePath(path) {
|
|
|
3093
3338
|
}
|
|
3094
3339
|
function resolveCodePath(workspaceDir, path) {
|
|
3095
3340
|
validateRelativePath(path);
|
|
3096
|
-
const root =
|
|
3097
|
-
const target =
|
|
3098
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
3341
|
+
const root = resolve23(workspaceDir);
|
|
3342
|
+
const target = resolve23(root, path);
|
|
3343
|
+
if (target !== root && !target.startsWith(`${root}${sep3}`)) throw new TypeError("path escapes the staged workspace");
|
|
3099
3344
|
return target;
|
|
3100
3345
|
}
|
|
3101
3346
|
async function applyCodePatch(workspaceDir, patch2, paths) {
|
|
@@ -3103,7 +3348,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
|
|
|
3103
3348
|
await gitApply(workspaceDir, patch2, false);
|
|
3104
3349
|
for (const path of paths) {
|
|
3105
3350
|
try {
|
|
3106
|
-
const info = await
|
|
3351
|
+
const info = await lstat2(resolveCodePath(workspaceDir, path));
|
|
3107
3352
|
if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
|
|
3108
3353
|
throw new TypeError("patch created a non-regular workspace entry");
|
|
3109
3354
|
}
|
|
@@ -3115,7 +3360,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
|
|
|
3115
3360
|
function gitApply(cwd, patch2, check) {
|
|
3116
3361
|
return new Promise((accept, reject) => {
|
|
3117
3362
|
const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
|
|
3118
|
-
const child =
|
|
3363
|
+
const child = spawn4("git", args, {
|
|
3119
3364
|
cwd,
|
|
3120
3365
|
shell: false,
|
|
3121
3366
|
stdio: ["pipe", "ignore", "pipe"],
|
|
@@ -3285,32 +3530,6 @@ function execute(engine, args, name, recipe2, signal) {
|
|
|
3285
3530
|
});
|
|
3286
3531
|
});
|
|
3287
3532
|
}
|
|
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
3533
|
var SHA3 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
3315
3534
|
var DIGEST3 = /^sha256:[0-9a-f]{64}$/;
|
|
3316
3535
|
var ID3 = /^[A-Za-z0-9._:-]{1,160}$/;
|
|
@@ -3419,8 +3638,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
|
|
|
3419
3638
|
const receipts = [];
|
|
3420
3639
|
for (const artifact of recipe2.expectedArtifacts ?? []) {
|
|
3421
3640
|
try {
|
|
3422
|
-
const path =
|
|
3423
|
-
const info = await
|
|
3641
|
+
const path = join4(workspaceDir, artifact.path);
|
|
3642
|
+
const info = await lstat22(path);
|
|
3424
3643
|
if (!info.isFile() || info.isSymbolicLink()) {
|
|
3425
3644
|
receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
|
|
3426
3645
|
} else if (info.size > artifact.maximumBytes) {
|
|
@@ -3516,8 +3735,8 @@ async function prepareRuntimeCheckpoint(input) {
|
|
|
3516
3735
|
policy: {
|
|
3517
3736
|
policyId: "code.runtime",
|
|
3518
3737
|
recipes: input.recipes,
|
|
3519
|
-
maximumFiles:
|
|
3520
|
-
maximumBytes:
|
|
3738
|
+
maximumFiles: 2e4,
|
|
3739
|
+
maximumBytes: 512 * 1024 * 1024
|
|
3521
3740
|
},
|
|
3522
3741
|
recipeExecutor: input.recipeExecutor
|
|
3523
3742
|
});
|
|
@@ -3599,11 +3818,11 @@ var CodeRuntimeCheckpointManager = class {
|
|
|
3599
3818
|
};
|
|
3600
3819
|
var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
3601
3820
|
var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
3602
|
-
async function materializeCodeRuntimeSource(snapshot, tempRoot =
|
|
3821
|
+
async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir3()) {
|
|
3603
3822
|
if (!snapshot.files.length || snapshot.files.length > 1e4) throw new TypeError("Code source file count is invalid");
|
|
3604
|
-
const root = await
|
|
3823
|
+
const root = await mkdtemp3(join23(tempRoot, "odla-code-source-"));
|
|
3605
3824
|
const sourceDir = join23(root, "source");
|
|
3606
|
-
await
|
|
3825
|
+
await mkdir3(sourceDir);
|
|
3607
3826
|
const seen = /* @__PURE__ */ new Set();
|
|
3608
3827
|
let bytes = 0;
|
|
3609
3828
|
try {
|
|
@@ -3614,13 +3833,13 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir2()) {
|
|
|
3614
3833
|
bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
|
|
3615
3834
|
if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
|
|
3616
3835
|
const target = resolve32(sourceDir, file.path);
|
|
3617
|
-
if (!target.startsWith(`${resolve32(sourceDir)}${
|
|
3618
|
-
await
|
|
3619
|
-
await
|
|
3836
|
+
if (!target.startsWith(`${resolve32(sourceDir)}${sep22}`)) throw new TypeError("Code source path escapes its root");
|
|
3837
|
+
await mkdir3(dirname4(target), { recursive: true });
|
|
3838
|
+
await writeFile2(target, file.content, { flag: "wx", mode: 420 });
|
|
3620
3839
|
}
|
|
3621
|
-
return { sourceDir, cleanup: () =>
|
|
3840
|
+
return { sourceDir, cleanup: () => rm3(root, { recursive: true, force: true }) };
|
|
3622
3841
|
} catch (cause) {
|
|
3623
|
-
await
|
|
3842
|
+
await rm3(root, { recursive: true, force: true });
|
|
3624
3843
|
throw cause;
|
|
3625
3844
|
}
|
|
3626
3845
|
}
|
|
@@ -3726,15 +3945,15 @@ async function environment(input, options, tool) {
|
|
|
3726
3945
|
{ id: "authority", value: `lease:${input.lease.leaseId}`, readers: input.readers },
|
|
3727
3946
|
{ id: "reader", value: options.readerId, readers: input.readers }
|
|
3728
3947
|
]);
|
|
3729
|
-
const
|
|
3948
|
+
const digest = await destinationRegistryDigest([input.workspaceId]);
|
|
3730
3949
|
const approvals = ["irreversible_mutation", "external_send", "financial", "secret_read"];
|
|
3731
3950
|
if (options.recipeAuthorization === "exact_approval") approvals.push("code_execution");
|
|
3732
3951
|
const policy = createEffectPolicy({
|
|
3733
|
-
destinationRegistries: { [DESTINATIONS]: { digest
|
|
3952
|
+
destinationRegistries: { [DESTINATIONS]: { digest, values: [input.workspaceId] } },
|
|
3734
3953
|
approvalEffects: approvals
|
|
3735
3954
|
});
|
|
3736
3955
|
const fixedArgs = {
|
|
3737
|
-
workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest:
|
|
3956
|
+
workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest },
|
|
3738
3957
|
authority: { role: "authority", value: ingress.control("authority") }
|
|
3739
3958
|
};
|
|
3740
3959
|
return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
|
|
@@ -3934,14 +4153,31 @@ function codeCommandMetadata(payload, resume) {
|
|
|
3934
4153
|
}
|
|
3935
4154
|
const planning = trusted?.planningInputDigest;
|
|
3936
4155
|
const attestation = trusted?.attestationDigest;
|
|
4156
|
+
const repository = trusted?.repository;
|
|
4157
|
+
const baseCommitSha = trusted?.commitSha;
|
|
4158
|
+
const sourceTreeDigest = trusted?.treeDigest;
|
|
4159
|
+
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)) {
|
|
4160
|
+
throw new TypeError(`invalid Code ${resume ? "resume" : "start"} trusted base`);
|
|
4161
|
+
}
|
|
3937
4162
|
return {
|
|
3938
4163
|
role,
|
|
3939
4164
|
title,
|
|
3940
4165
|
prompt,
|
|
3941
4166
|
planningInputDigest: typeof planning === "string" && /^sha256:[0-9a-f]{64}$/.test(planning) ? planning : null,
|
|
3942
|
-
attestationDigest: typeof attestation === "string" ? attestation : "resume"
|
|
4167
|
+
attestationDigest: typeof attestation === "string" ? attestation : "resume",
|
|
4168
|
+
repository,
|
|
4169
|
+
baseCommitSha,
|
|
4170
|
+
sourceTreeDigest
|
|
3943
4171
|
};
|
|
3944
4172
|
}
|
|
4173
|
+
function codeLocalSource(payload) {
|
|
4174
|
+
const source = record22(payload.source);
|
|
4175
|
+
if (!source) return null;
|
|
4176
|
+
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) {
|
|
4177
|
+
throw new TypeError("invalid local checkout source descriptor");
|
|
4178
|
+
}
|
|
4179
|
+
return source;
|
|
4180
|
+
}
|
|
3945
4181
|
function codeCheckpointPayload(payload) {
|
|
3946
4182
|
const value = payload.checkpoint;
|
|
3947
4183
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
|
|
@@ -3970,11 +4206,62 @@ function fakeCodeLease(command, metadata2) {
|
|
|
3970
4206
|
};
|
|
3971
4207
|
}
|
|
3972
4208
|
var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4209
|
+
var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
|
|
4210
|
+
async function prepareRuntimeLocalSource(input) {
|
|
4211
|
+
const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
|
|
4212
|
+
if (!available || JSON.stringify(available.descriptor) !== JSON.stringify(descriptor2) || descriptor2.repository.toLowerCase() !== repository.toLowerCase() || descriptor2.headCommitSha !== baseCommitSha) {
|
|
4213
|
+
throw new TypeError("the session's local checkout snapshot is not available on this terminal");
|
|
4214
|
+
}
|
|
4215
|
+
const workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
|
|
4216
|
+
trustedBaseDir: available.trustedBaseDir,
|
|
4217
|
+
trustedBaseCommitSha: baseCommitSha,
|
|
4218
|
+
checkpoint: codeCheckpointPayload(command.payload)
|
|
4219
|
+
})).workspace : await stageWorkspacePair(available.trustedBaseDir, available.sourceDir, SOURCE_LIMITS);
|
|
4220
|
+
const trustedBaseDigest = await digestStagedWorkspace(workspace.baselineDir, SOURCE_LIMITS);
|
|
4221
|
+
if (trustedBaseDigest !== descriptor2.trustedBaseDigest) {
|
|
4222
|
+
await workspace.cleanup();
|
|
4223
|
+
throw new TypeError("trusted Git base digest changed after connection");
|
|
4224
|
+
}
|
|
4225
|
+
if (!resume && await digestStagedWorkspace(workspace.workspaceDir, SOURCE_LIMITS) !== descriptor2.snapshotDigest) {
|
|
4226
|
+
await workspace.cleanup();
|
|
4227
|
+
throw new TypeError("local checkout snapshot digest changed after connection");
|
|
4228
|
+
}
|
|
4229
|
+
return { workspace, sourceDigest: descriptor2.snapshotDigest, trustedBaseDigest };
|
|
4230
|
+
}
|
|
4231
|
+
function createCodeRuntimeToolBroker(input, lease, role) {
|
|
4232
|
+
const broker = createCodeToolBroker({
|
|
4233
|
+
recipes: input.recipes,
|
|
4234
|
+
recipeExecutor: createContainerRecipeExecutor(input.engine),
|
|
4235
|
+
recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
|
|
4236
|
+
readerId: `code-session:${lease.task.taskId}`
|
|
4237
|
+
});
|
|
4238
|
+
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" }) };
|
|
4239
|
+
}
|
|
4240
|
+
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
4241
|
+
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
4242
|
+
refs.push(eventId);
|
|
4243
|
+
const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
|
|
4244
|
+
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
4245
|
+
}
|
|
4246
|
+
var digestRuntimeValue = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
|
|
4247
|
+
var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
|
|
4248
|
+
var runtimeRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4249
|
+
var safeRuntimeJson = (value) => {
|
|
4250
|
+
try {
|
|
4251
|
+
return JSON.stringify(value).slice(0, 1e4);
|
|
4252
|
+
} catch {
|
|
4253
|
+
return "[event]";
|
|
4254
|
+
}
|
|
4255
|
+
};
|
|
4256
|
+
function runtimeResultText(value) {
|
|
4257
|
+
const record32 = runtimeRecord(value);
|
|
4258
|
+
return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
|
|
4259
|
+
}
|
|
3973
4260
|
var CodePiRuntimeEngine = class {
|
|
3974
4261
|
constructor(options) {
|
|
3975
4262
|
this.options = options;
|
|
3976
4263
|
this.#run = options.runAttempt ?? runContainerAttempt;
|
|
3977
|
-
this.#buildPolicyDigest =
|
|
4264
|
+
this.#buildPolicyDigest = digestRuntimeValue(JSON.stringify(options.recipes));
|
|
3978
4265
|
this.#checkpoints = new CodeRuntimeCheckpointManager({
|
|
3979
4266
|
control: options.control,
|
|
3980
4267
|
recipes: options.recipes,
|
|
@@ -4009,19 +4296,35 @@ var CodePiRuntimeEngine = class {
|
|
|
4009
4296
|
}
|
|
4010
4297
|
async #start(command, resume) {
|
|
4011
4298
|
if (this.#active.has(command.sessionId)) throw new TypeError("Code session is already active on this runtime");
|
|
4012
|
-
const
|
|
4013
|
-
const
|
|
4299
|
+
const metadata2 = codeCommandMetadata(command.payload, resume);
|
|
4300
|
+
const requestedLocal = codeLocalSource(command.payload);
|
|
4014
4301
|
let workspace;
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4302
|
+
let sourceDigest;
|
|
4303
|
+
let localTrustedBaseDigest;
|
|
4304
|
+
if (requestedLocal) {
|
|
4305
|
+
const prepared = await prepareRuntimeLocalSource({
|
|
4306
|
+
command,
|
|
4307
|
+
descriptor: requestedLocal,
|
|
4308
|
+
available: this.options.localSource,
|
|
4309
|
+
repository: metadata2.repository,
|
|
4310
|
+
baseCommitSha: metadata2.baseCommitSha,
|
|
4311
|
+
resume
|
|
4312
|
+
});
|
|
4313
|
+
({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
|
|
4314
|
+
} else {
|
|
4315
|
+
const source = await this.options.control.source(command.sessionId);
|
|
4316
|
+
const materialized = await materializeCodeRuntimeSource(source);
|
|
4317
|
+
try {
|
|
4318
|
+
workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
|
|
4319
|
+
trustedBaseDir: materialized.sourceDir,
|
|
4320
|
+
trustedBaseCommitSha: source.commitSha,
|
|
4321
|
+
checkpoint: codeCheckpointPayload(command.payload)
|
|
4322
|
+
})).workspace : await stageWorkspace(materialized.sourceDir);
|
|
4323
|
+
} finally {
|
|
4324
|
+
await materialized.cleanup();
|
|
4325
|
+
}
|
|
4326
|
+
sourceDigest = source.treeDigest;
|
|
4023
4327
|
}
|
|
4024
|
-
const metadata2 = codeCommandMetadata(command.payload, resume);
|
|
4025
4328
|
const abort = new AbortController();
|
|
4026
4329
|
const conversationRefs = [];
|
|
4027
4330
|
const active = {
|
|
@@ -4031,21 +4334,30 @@ var CodePiRuntimeEngine = class {
|
|
|
4031
4334
|
acknowledged: false,
|
|
4032
4335
|
role: metadata2.role,
|
|
4033
4336
|
title: metadata2.title,
|
|
4034
|
-
baseCommitSha:
|
|
4035
|
-
|
|
4337
|
+
baseCommitSha: metadata2.baseCommitSha,
|
|
4338
|
+
repository: metadata2.repository,
|
|
4339
|
+
sourceTreeDigest: metadata2.sourceTreeDigest,
|
|
4340
|
+
trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
|
|
4036
4341
|
maxFiles: 1e4,
|
|
4037
4342
|
maxBytes: 16 * 1024 * 1024
|
|
4038
4343
|
}),
|
|
4039
|
-
planningInputDigest: metadata2.planningInputDigest ??
|
|
4040
|
-
JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree:
|
|
4344
|
+
planningInputDigest: metadata2.planningInputDigest ?? digestRuntimeValue(
|
|
4345
|
+
JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: sourceDigest })
|
|
4041
4346
|
),
|
|
4042
4347
|
done: Promise.resolve(null)
|
|
4043
4348
|
};
|
|
4044
4349
|
this.#active.set(command.sessionId, active);
|
|
4350
|
+
if (requestedLocal) {
|
|
4351
|
+
await this.#event(command, {
|
|
4352
|
+
type: "message",
|
|
4353
|
+
actor: "system",
|
|
4354
|
+
body: `Source snapshot: local checkout ${requestedLocal.snapshotDigest} \xB7 ${requestedLocal.modified ? "modified" : "clean"} \xB7 Git ${requestedLocal.headCommitSha}`
|
|
4355
|
+
}, conversationRefs);
|
|
4356
|
+
}
|
|
4045
4357
|
active.done = this.#runAttempt(command, metadata2, active).catch(async (cause) => {
|
|
4046
|
-
await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${
|
|
4358
|
+
await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` }, conversationRefs).catch(() => void 0);
|
|
4047
4359
|
await this.#event(command, { type: "status", status: "failed" }, conversationRefs).catch(() => void 0);
|
|
4048
|
-
await this.#failure(command, active,
|
|
4360
|
+
await this.#failure(command, active, runtimeErrorMessage(cause));
|
|
4049
4361
|
return null;
|
|
4050
4362
|
});
|
|
4051
4363
|
return { status: "running", message: resume ? "Pi resumed from a portable checkpoint" : "Pi started" };
|
|
@@ -4065,22 +4377,29 @@ var CodePiRuntimeEngine = class {
|
|
|
4065
4377
|
title: active.title,
|
|
4066
4378
|
prompt,
|
|
4067
4379
|
planningInputDigest: active.planningInputDigest,
|
|
4068
|
-
attestationDigest: "follow-up"
|
|
4380
|
+
attestationDigest: "follow-up",
|
|
4381
|
+
repository: active.repository,
|
|
4382
|
+
baseCommitSha: active.baseCommitSha,
|
|
4383
|
+
sourceTreeDigest: active.sourceTreeDigest
|
|
4069
4384
|
}, active).catch(async (cause) => {
|
|
4070
4385
|
await this.#event(
|
|
4071
4386
|
command,
|
|
4072
|
-
{ type: "message", actor: "system", body: `Pi failed: ${
|
|
4387
|
+
{ type: "message", actor: "system", body: `Pi failed: ${runtimeErrorMessage(cause)}` },
|
|
4073
4388
|
active.conversationRefs
|
|
4074
4389
|
).catch(() => void 0);
|
|
4075
4390
|
await this.#event(command, { type: "status", status: "failed" }, active.conversationRefs).catch(() => void 0);
|
|
4076
|
-
await this.#failure(command, active,
|
|
4391
|
+
await this.#failure(command, active, runtimeErrorMessage(cause));
|
|
4077
4392
|
return null;
|
|
4078
4393
|
});
|
|
4079
4394
|
return { status: "running", message: "Pi accepted the owner prompt" };
|
|
4080
4395
|
}
|
|
4081
4396
|
async #runAttempt(command, metadata2, active) {
|
|
4082
4397
|
const lease = fakeCodeLease(command, metadata2);
|
|
4083
|
-
const broker =
|
|
4398
|
+
const broker = createCodeRuntimeToolBroker({
|
|
4399
|
+
recipes: this.options.recipes,
|
|
4400
|
+
engine: this.options.engine,
|
|
4401
|
+
recipeAuthorization: this.options.recipeAuthorization
|
|
4402
|
+
}, lease, metadata2.role);
|
|
4084
4403
|
const startedAt = Date.now();
|
|
4085
4404
|
let completionSeen = false;
|
|
4086
4405
|
const result = await this.#run({
|
|
@@ -4140,7 +4459,7 @@ var CodePiRuntimeEngine = class {
|
|
|
4140
4459
|
return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
|
|
4141
4460
|
}
|
|
4142
4461
|
if (output.type === "event") {
|
|
4143
|
-
const payload =
|
|
4462
|
+
const payload = runtimeRecord(output.payload);
|
|
4144
4463
|
if (output.kind === "pi.started") {
|
|
4145
4464
|
await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
|
|
4146
4465
|
} else if (output.kind === "pi.thinking" && payload?.available === true && Number.isSafeInteger(payload.durationMs) && Number(payload.durationMs) >= 0) {
|
|
@@ -4153,12 +4472,12 @@ var CodePiRuntimeEngine = class {
|
|
|
4153
4472
|
await this.#event(command, {
|
|
4154
4473
|
type: "message",
|
|
4155
4474
|
actor: "system",
|
|
4156
|
-
body: `${output.kind}${output.payload === void 0 ? "" : ` ${
|
|
4475
|
+
body: `${output.kind}${output.payload === void 0 ? "" : ` ${safeRuntimeJson(output.payload)}`}`
|
|
4157
4476
|
}, active.conversationRefs);
|
|
4158
4477
|
}
|
|
4159
4478
|
} else if (output.type === "attempt.complete") {
|
|
4160
4479
|
completionSeen = true;
|
|
4161
|
-
const body =
|
|
4480
|
+
const body = runtimeResultText(output.result) ?? `Pi ${output.status}.`;
|
|
4162
4481
|
await this.#event(command, {
|
|
4163
4482
|
type: "message",
|
|
4164
4483
|
actor: output.status === "completed" ? "agent" : "system",
|
|
@@ -4185,15 +4504,6 @@ var CodePiRuntimeEngine = class {
|
|
|
4185
4504
|
}
|
|
4186
4505
|
return result;
|
|
4187
4506
|
}
|
|
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
4507
|
async #checkpoint(command) {
|
|
4198
4508
|
const active = this.#active.get(command.sessionId);
|
|
4199
4509
|
if (!active) throw new TypeError("Code session workspace is not active on this runtime");
|
|
@@ -4208,26 +4518,9 @@ var CodePiRuntimeEngine = class {
|
|
|
4208
4518
|
}
|
|
4209
4519
|
}
|
|
4210
4520
|
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]";
|
|
4521
|
+
await appendCodeRuntimeEvent(this.options.control, command, event, refs);
|
|
4225
4522
|
}
|
|
4226
4523
|
};
|
|
4227
|
-
function resultText(value) {
|
|
4228
|
-
const record32 = object2(value);
|
|
4229
|
-
return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
|
|
4230
|
-
}
|
|
4231
4524
|
|
|
4232
4525
|
// src/help.ts
|
|
4233
4526
|
import { readFileSync as readFileSync3 } from "fs";
|
|
@@ -4345,24 +4638,90 @@ Safety:
|
|
|
4345
4638
|
`);
|
|
4346
4639
|
}
|
|
4347
4640
|
|
|
4641
|
+
// src/code-local-source.ts
|
|
4642
|
+
import { execFile as execFile3 } from "child_process";
|
|
4643
|
+
import { createHash as createHash4 } from "crypto";
|
|
4644
|
+
var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
|
|
4645
|
+
async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
|
|
4646
|
+
const headCommitSha = await readHead(cwd);
|
|
4647
|
+
const staged = await stageWorkspace(cwd, { ...SOURCE_LIMITS2, gitTrackedAndUnignored: true });
|
|
4648
|
+
let trusted;
|
|
4649
|
+
try {
|
|
4650
|
+
trusted = await materializeGitTree(cwd, headCommitSha, SOURCE_LIMITS2);
|
|
4651
|
+
const comparison = await stageWorkspacePair(trusted.sourceDir, staged.baselineDir, SOURCE_LIMITS2);
|
|
4652
|
+
let developerPatch;
|
|
4653
|
+
try {
|
|
4654
|
+
developerPatch = await comparison.patch(4 * 1024 * 1024);
|
|
4655
|
+
} finally {
|
|
4656
|
+
await comparison.cleanup();
|
|
4657
|
+
}
|
|
4658
|
+
const descriptor2 = {
|
|
4659
|
+
kind: "local_checkout",
|
|
4660
|
+
repository,
|
|
4661
|
+
headCommitSha,
|
|
4662
|
+
trustedBaseDigest: await digestStagedWorkspace(trusted.sourceDir, SOURCE_LIMITS2),
|
|
4663
|
+
developerPatchDigest: digestText(developerPatch),
|
|
4664
|
+
snapshotDigest: await digestStagedWorkspace(staged.baselineDir, SOURCE_LIMITS2),
|
|
4665
|
+
modified: developerPatch.length > 0,
|
|
4666
|
+
fileCount: staged.fileCount,
|
|
4667
|
+
byteCount: staged.byteCount,
|
|
4668
|
+
capturedAt: Date.now()
|
|
4669
|
+
};
|
|
4670
|
+
return {
|
|
4671
|
+
descriptor: descriptor2,
|
|
4672
|
+
trustedBaseDir: trusted.sourceDir,
|
|
4673
|
+
sourceDir: staged.baselineDir,
|
|
4674
|
+
cleanup: async () => {
|
|
4675
|
+
await Promise.all([staged.cleanup(), trusted.cleanup()]);
|
|
4676
|
+
}
|
|
4677
|
+
};
|
|
4678
|
+
} catch (error) {
|
|
4679
|
+
await Promise.all([staged.cleanup(), trusted?.cleanup()]);
|
|
4680
|
+
throw error;
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
async function readGitHead(cwd) {
|
|
4684
|
+
const value = await new Promise((accept, reject) => {
|
|
4685
|
+
execFile3("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
|
|
4686
|
+
if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
|
|
4687
|
+
else accept(stdout.trim());
|
|
4688
|
+
});
|
|
4689
|
+
});
|
|
4690
|
+
if (!/^[0-9a-f]{40}$/.test(value)) throw new Error("code connect could not resolve the checkout HEAD commit");
|
|
4691
|
+
return value;
|
|
4692
|
+
}
|
|
4693
|
+
function digestText(value) {
|
|
4694
|
+
return `sha256:${createHash4("sha256").update(value).digest("hex")}`;
|
|
4695
|
+
}
|
|
4696
|
+
|
|
4697
|
+
// src/code-images.ts
|
|
4698
|
+
import { execFile as execFile4 } from "child_process";
|
|
4699
|
+
async function prepareCodeImages(engine, images) {
|
|
4700
|
+
for (const image of images) {
|
|
4701
|
+
await new Promise((accept, reject) => {
|
|
4702
|
+
const args = engine === "container" ? ["image", "pull", image] : ["pull", image];
|
|
4703
|
+
execFile4(engine, args, { encoding: "utf8", maxBuffer: 2 * 1024 * 1024, timeout: 10 * 6e4 }, (error) => {
|
|
4704
|
+
if (error) reject(new Error(`could not prepare pinned Code image ${image}`));
|
|
4705
|
+
else accept();
|
|
4706
|
+
});
|
|
4707
|
+
});
|
|
4708
|
+
}
|
|
4709
|
+
}
|
|
4710
|
+
|
|
4348
4711
|
// 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
4712
|
async function codeConnect(options) {
|
|
4361
4713
|
const cwd = options.cwd ?? process.cwd();
|
|
4362
4714
|
const configPath = resolve5(cwd, options.configPath);
|
|
4363
4715
|
const cfg = existsSync4(configPath) ? await loadProjectConfig(configPath) : null;
|
|
4364
|
-
const
|
|
4365
|
-
|
|
4716
|
+
const requestedAppId = options.appId?.trim();
|
|
4717
|
+
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
4718
|
+
throw new Error("--app-id must be a valid odla app id");
|
|
4719
|
+
}
|
|
4720
|
+
if (requestedAppId && cfg && requestedAppId !== cfg.app.id) {
|
|
4721
|
+
throw new Error(`--app-id ${requestedAppId} does not match ${options.configPath} (${cfg.app.id})`);
|
|
4722
|
+
}
|
|
4723
|
+
const appId = requestedAppId ?? cfg?.app.id;
|
|
4724
|
+
const platform = (options.platform ?? cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai").replace(/\/+$/, "");
|
|
4366
4725
|
const appEnv = options.env ?? (cfg ? cfg.envs.includes("dev") ? "dev" : cfg.envs[0] : "dev");
|
|
4367
4726
|
if (appEnv !== "dev" && appEnv !== "prod" || cfg && !cfg.envs.includes(appEnv)) {
|
|
4368
4727
|
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 +4740,77 @@ async function codeConnect(options) {
|
|
|
4381
4740
|
const out = options.stdout ?? console;
|
|
4382
4741
|
const doFetch = options.fetch ?? fetch;
|
|
4383
4742
|
const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
|
|
4743
|
+
await (options.prepareImages ?? prepareCodeImages)(engine, [
|
|
4744
|
+
CODE_PI_IMAGE,
|
|
4745
|
+
...new Set(CODE_BUILD_RECIPES.map((recipe2) => recipe2.image))
|
|
4746
|
+
]);
|
|
4384
4747
|
const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
|
|
4385
4748
|
const hostName = (options.name ?? hostname()).trim();
|
|
4386
4749
|
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
|
-
|
|
4750
|
+
const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
|
|
4751
|
+
const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
|
|
4752
|
+
cwd,
|
|
4753
|
+
repository,
|
|
4754
|
+
options.readGitHead
|
|
4755
|
+
);
|
|
4756
|
+
try {
|
|
4757
|
+
const descriptor2 = localSource.descriptor;
|
|
4758
|
+
const approval = await (options.getToken ?? getScopedPlatformToken)({
|
|
4759
|
+
platform,
|
|
4760
|
+
scope: "app:code:host:connect",
|
|
4761
|
+
email: options.email,
|
|
4762
|
+
open: options.open,
|
|
4763
|
+
fetch: doFetch,
|
|
4764
|
+
stdout: out,
|
|
4765
|
+
openApprovalUrl: options.openApprovalUrl,
|
|
4766
|
+
cache: false,
|
|
4767
|
+
label: `Terminal connection for ${appId ?? repository}/${appEnv}`
|
|
4768
|
+
});
|
|
4769
|
+
const target = appId ? { appId } : { repository };
|
|
4770
|
+
const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
|
|
4771
|
+
method: "POST",
|
|
4772
|
+
headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
|
|
4773
|
+
body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
|
|
4774
|
+
redirect: "error",
|
|
4775
|
+
signal: options.signal
|
|
4776
|
+
});
|
|
4777
|
+
const raw = await response2.json().catch(() => null);
|
|
4778
|
+
if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
|
|
4779
|
+
const connection = parseConnection(raw, appId, appEnv);
|
|
4780
|
+
const capabilities = {
|
|
4781
|
+
protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
|
|
4782
|
+
platform: hostPlatform,
|
|
4783
|
+
arch: process.arch,
|
|
4784
|
+
engines: [engine],
|
|
4785
|
+
cpuCount: cpus().length,
|
|
4786
|
+
memoryBytes: totalmem(),
|
|
4787
|
+
source: descriptor2,
|
|
4788
|
+
images: {
|
|
4789
|
+
ready: true,
|
|
4790
|
+
pi: CODE_PI_IMAGE,
|
|
4791
|
+
recipes: CODE_BUILD_RECIPES.map((recipe2) => ({ id: recipe2.id, image: recipe2.image }))
|
|
4792
|
+
}
|
|
4793
|
+
};
|
|
4794
|
+
out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
|
|
4795
|
+
out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
|
|
4796
|
+
out.log(`source: local checkout \xB7 ${descriptor2.headCommitSha.slice(0, 10)} \xB7 ${descriptor2.fileCount} files \xB7 staged copy`);
|
|
4797
|
+
out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
|
|
4798
|
+
out.log(`Studio: ${platform}/studio/apps/${encodeURIComponent(connection.binding.appId)}/${connection.binding.env}/code/sessions`);
|
|
4799
|
+
await (options.runRuntime ?? runCodeRuntime)({
|
|
4800
|
+
endpoint: platform,
|
|
4801
|
+
token: connection.token,
|
|
4802
|
+
engine,
|
|
4803
|
+
capabilities,
|
|
4804
|
+
localSource,
|
|
4805
|
+
heartbeatMs,
|
|
4806
|
+
once: options.once === true,
|
|
4807
|
+
signal: options.signal,
|
|
4808
|
+
stdout: out,
|
|
4809
|
+
fetch: doFetch
|
|
4810
|
+
});
|
|
4811
|
+
} finally {
|
|
4812
|
+
await localSource.cleanup();
|
|
4813
|
+
}
|
|
4431
4814
|
}
|
|
4432
4815
|
async function runCodeRuntime(input) {
|
|
4433
4816
|
const controller = new AbortController();
|
|
@@ -4450,7 +4833,8 @@ async function runCodeRuntime(input) {
|
|
|
4450
4833
|
engine: input.engine,
|
|
4451
4834
|
image: CODE_PI_IMAGE,
|
|
4452
4835
|
recipes: CODE_BUILD_RECIPES,
|
|
4453
|
-
recipeAuthorization: "registered_recipe"
|
|
4836
|
+
recipeAuthorization: "registered_recipe",
|
|
4837
|
+
localSource: input.localSource
|
|
4454
4838
|
});
|
|
4455
4839
|
const reconciler = new CodeRuntimeReconciler(control, commandEngine);
|
|
4456
4840
|
try {
|
|
@@ -4491,8 +4875,8 @@ function parseConnection(value, appId, appEnv) {
|
|
|
4491
4875
|
return root;
|
|
4492
4876
|
}
|
|
4493
4877
|
function apiFailure(action, status, value) {
|
|
4494
|
-
const
|
|
4495
|
-
return `${action} failed (${status})${typeof
|
|
4878
|
+
const message2 = record4(record4(value)?.error)?.message;
|
|
4879
|
+
return `${action} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
4496
4880
|
}
|
|
4497
4881
|
function record4(value) {
|
|
4498
4882
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -4501,14 +4885,14 @@ function record4(value) {
|
|
|
4501
4885
|
// src/doctor-checks.ts
|
|
4502
4886
|
import { execFileSync } from "child_process";
|
|
4503
4887
|
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
4504
|
-
import { join as
|
|
4888
|
+
import { join as join6, resolve as resolve6 } from "path";
|
|
4505
4889
|
|
|
4506
4890
|
// src/wrangler.ts
|
|
4507
|
-
import { spawn as
|
|
4891
|
+
import { spawn as spawn5 } from "child_process";
|
|
4508
4892
|
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
4509
|
-
import { join as
|
|
4893
|
+
import { join as join5 } from "path";
|
|
4510
4894
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
4511
|
-
const child =
|
|
4895
|
+
const child = spawn5(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
4512
4896
|
let stdout = "";
|
|
4513
4897
|
let stderr = "";
|
|
4514
4898
|
child.stdout.on("data", (chunk) => stdout += chunk.toString());
|
|
@@ -4520,7 +4904,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
|
|
|
4520
4904
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
4521
4905
|
function findWranglerConfig(rootDir) {
|
|
4522
4906
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
4523
|
-
const path =
|
|
4907
|
+
const path = join5(rootDir, name);
|
|
4524
4908
|
if (existsSync5(path)) return path;
|
|
4525
4909
|
}
|
|
4526
4910
|
return null;
|
|
@@ -4629,7 +5013,7 @@ function wranglerWarnings(rootDir) {
|
|
|
4629
5013
|
const dir = resolve6(rootDir, assets.directory);
|
|
4630
5014
|
if (dir === resolve6(rootDir)) {
|
|
4631
5015
|
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(
|
|
5016
|
+
} else if (existsSync6(join6(dir, "node_modules"))) {
|
|
4633
5017
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
4634
5018
|
}
|
|
4635
5019
|
}
|
|
@@ -4694,7 +5078,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
4694
5078
|
}
|
|
4695
5079
|
function readPackageJson(rootDir) {
|
|
4696
5080
|
try {
|
|
4697
|
-
return JSON.parse(readFileSync5(
|
|
5081
|
+
return JSON.parse(readFileSync5(join6(rootDir, "package.json"), "utf8"));
|
|
4698
5082
|
} catch {
|
|
4699
5083
|
return null;
|
|
4700
5084
|
}
|
|
@@ -5261,8 +5645,8 @@ async function readRegistryApp(cfg, token, doFetch) {
|
|
|
5261
5645
|
});
|
|
5262
5646
|
if (res.status === 404) return null;
|
|
5263
5647
|
if (!res.ok) return null;
|
|
5264
|
-
const
|
|
5265
|
-
return
|
|
5648
|
+
const json = await res.json();
|
|
5649
|
+
return json.app ?? null;
|
|
5266
5650
|
}
|
|
5267
5651
|
async function postJson2(doFetch, url, bearer, body) {
|
|
5268
5652
|
const res = await doFetch(url, {
|
|
@@ -5428,10 +5812,10 @@ async function provision(options) {
|
|
|
5428
5812
|
stdout: out
|
|
5429
5813
|
});
|
|
5430
5814
|
} catch (error) {
|
|
5431
|
-
const
|
|
5815
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
5432
5816
|
const consent = env === "prod" || env === "production" ? " --yes" : "";
|
|
5433
5817
|
throw new Error(
|
|
5434
|
-
`${
|
|
5818
|
+
`${message2}
|
|
5435
5819
|
${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}${consent}" without issuing or rotating again`,
|
|
5436
5820
|
{ cause: error }
|
|
5437
5821
|
);
|
|
@@ -5547,7 +5931,7 @@ async function resolveVaultWrite(options) {
|
|
|
5547
5931
|
}
|
|
5548
5932
|
|
|
5549
5933
|
// src/security.ts
|
|
5550
|
-
import { isAbsolute as
|
|
5934
|
+
import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve8, sep as sep4 } from "path";
|
|
5551
5935
|
import {
|
|
5552
5936
|
cloudflareAppProfile,
|
|
5553
5937
|
createPlatformSecurityReasoners,
|
|
@@ -5568,7 +5952,7 @@ async function runHostedSecurity(options) {
|
|
|
5568
5952
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
5569
5953
|
const target = resolve8(options.target ?? cfg?.rootDir ?? ".");
|
|
5570
5954
|
const output = resolve8(options.out ?? resolve8(target, ".odla/security/hosted"));
|
|
5571
|
-
const outputRelative = relative4(target, output).split(
|
|
5955
|
+
const outputRelative = relative4(target, output).split(sep4).join("/");
|
|
5572
5956
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
5573
5957
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
5574
5958
|
const tokenRequest = {
|
|
@@ -5580,7 +5964,7 @@ async function runHostedSecurity(options) {
|
|
|
5580
5964
|
};
|
|
5581
5965
|
const token = await injectedToken(options, tokenRequest);
|
|
5582
5966
|
const snapshot = await snapshotDirectory(target, {
|
|
5583
|
-
exclude: !outputRelative.startsWith("../") && !
|
|
5967
|
+
exclude: !outputRelative.startsWith("../") && !isAbsolute4(outputRelative) ? [outputRelative] : []
|
|
5584
5968
|
});
|
|
5585
5969
|
const hosted = await createPlatformSecurityReasoners({
|
|
5586
5970
|
platform,
|
|
@@ -5741,8 +6125,8 @@ async function startHostedSecurityJob(options) {
|
|
|
5741
6125
|
"start hosted security job"
|
|
5742
6126
|
);
|
|
5743
6127
|
} catch (error) {
|
|
5744
|
-
const
|
|
5745
|
-
if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(
|
|
6128
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
6129
|
+
if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message2)) {
|
|
5746
6130
|
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
6131
|
}
|
|
5748
6132
|
throw error;
|
|
@@ -5823,7 +6207,7 @@ function securityExecutionDigest(value) {
|
|
|
5823
6207
|
// src/skill.ts
|
|
5824
6208
|
import { existsSync as existsSync8, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync, writeFileSync as writeFileSync3 } from "fs";
|
|
5825
6209
|
import { homedir } from "os";
|
|
5826
|
-
import { dirname as dirname6, isAbsolute as
|
|
6210
|
+
import { dirname as dirname6, isAbsolute as isAbsolute5, join as join7, relative as relative5, resolve as resolve9, sep as sep5 } from "path";
|
|
5827
6211
|
import { fileURLToPath } from "url";
|
|
5828
6212
|
|
|
5829
6213
|
// src/skill-adapters.ts
|
|
@@ -5899,12 +6283,12 @@ function installSkill(options = {}) {
|
|
|
5899
6283
|
plans.set(target, { target, content, boundary, managedMerge });
|
|
5900
6284
|
};
|
|
5901
6285
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
5902
|
-
for (const rel of files) plan(
|
|
6286
|
+
for (const rel of files) plan(join7(targetDir2, rel), readFileSync6(join7(sourceDir, rel), "utf8"), false, boundary);
|
|
5903
6287
|
};
|
|
5904
6288
|
let targetDir;
|
|
5905
6289
|
if (options.global) {
|
|
5906
|
-
const claudeRoot =
|
|
5907
|
-
const codexRoot = resolve9(options.codexHomeDir ?? process.env.CODEX_HOME ??
|
|
6290
|
+
const claudeRoot = join7(home, ".claude", "skills");
|
|
6291
|
+
const codexRoot = resolve9(options.codexHomeDir ?? process.env.CODEX_HOME ?? join7(home, ".codex"), "skills");
|
|
5908
6292
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
5909
6293
|
for (const harness of harnesses) {
|
|
5910
6294
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
@@ -5912,35 +6296,35 @@ function installSkill(options = {}) {
|
|
|
5912
6296
|
rememberTarget(harness, skillRoot);
|
|
5913
6297
|
}
|
|
5914
6298
|
} else {
|
|
5915
|
-
const sharedRoot =
|
|
6299
|
+
const sharedRoot = join7(root, ".agents", "skills");
|
|
5916
6300
|
planSkillTree(sharedRoot);
|
|
5917
|
-
const claudeRoot =
|
|
6301
|
+
const claudeRoot = join7(root, ".claude", "skills");
|
|
5918
6302
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
5919
6303
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
5920
6304
|
if (harnesses.includes("claude")) {
|
|
5921
6305
|
for (const skill of skillNames(files)) {
|
|
5922
|
-
const canonical = readFileSync6(
|
|
5923
|
-
plan(
|
|
6306
|
+
const canonical = readFileSync6(join7(sourceDir, skill, "SKILL.md"), "utf8");
|
|
6307
|
+
plan(join7(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
|
|
5924
6308
|
}
|
|
5925
6309
|
rememberTarget("claude", claudeRoot);
|
|
5926
6310
|
}
|
|
5927
6311
|
if (harnesses.includes("cursor")) {
|
|
5928
|
-
const cursorRule =
|
|
6312
|
+
const cursorRule = join7(root, ".cursor", "rules", "odla.mdc");
|
|
5929
6313
|
plan(cursorRule, CURSOR_RULE);
|
|
5930
6314
|
rememberTarget("cursor", cursorRule);
|
|
5931
6315
|
}
|
|
5932
6316
|
if (harnesses.includes("agents")) {
|
|
5933
|
-
const agentsFile =
|
|
6317
|
+
const agentsFile = join7(root, "AGENTS.md");
|
|
5934
6318
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5935
6319
|
rememberTarget("agents", agentsFile);
|
|
5936
6320
|
}
|
|
5937
6321
|
if (harnesses.includes("copilot")) {
|
|
5938
|
-
const copilotFile =
|
|
6322
|
+
const copilotFile = join7(root, ".github", "copilot-instructions.md");
|
|
5939
6323
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5940
6324
|
rememberTarget("copilot", copilotFile);
|
|
5941
6325
|
}
|
|
5942
6326
|
if (harnesses.includes("gemini")) {
|
|
5943
|
-
const geminiFile =
|
|
6327
|
+
const geminiFile = join7(root, "GEMINI.md");
|
|
5944
6328
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5945
6329
|
rememberTarget("gemini", geminiFile);
|
|
5946
6330
|
}
|
|
@@ -5996,7 +6380,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
5996
6380
|
};
|
|
5997
6381
|
}
|
|
5998
6382
|
function pathsUnder(root, paths) {
|
|
5999
|
-
return [...paths].map((path) => relative5(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${
|
|
6383
|
+
return [...paths].map((path) => relative5(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep5}`) && !isAbsolute5(path)).sort();
|
|
6000
6384
|
}
|
|
6001
6385
|
function normalizeHarnesses(values, global) {
|
|
6002
6386
|
const requested = values?.length ? values : ["claude"];
|
|
@@ -6042,12 +6426,12 @@ function managedFileContent(path, block, force, boundary) {
|
|
|
6042
6426
|
}
|
|
6043
6427
|
function symlinkedComponent(boundary, target) {
|
|
6044
6428
|
const rel = relative5(boundary, target);
|
|
6045
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
6429
|
+
if (rel === ".." || rel.startsWith(`..${sep5}`) || isAbsolute5(rel)) {
|
|
6046
6430
|
throw new Error(`agent setup target escapes its install root: ${target}`);
|
|
6047
6431
|
}
|
|
6048
6432
|
let current = boundary;
|
|
6049
|
-
for (const part of rel.split(
|
|
6050
|
-
current =
|
|
6433
|
+
for (const part of rel.split(sep5).filter(Boolean)) {
|
|
6434
|
+
current = join7(current, part);
|
|
6051
6435
|
try {
|
|
6052
6436
|
if (lstatSync(current).isSymbolicLink()) return current;
|
|
6053
6437
|
} catch (error) {
|
|
@@ -6064,7 +6448,7 @@ function listFiles(dir) {
|
|
|
6064
6448
|
const results = [];
|
|
6065
6449
|
const walk = (current) => {
|
|
6066
6450
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
6067
|
-
const path =
|
|
6451
|
+
const path = join7(current, entry.name);
|
|
6068
6452
|
if (entry.isDirectory()) walk(path);
|
|
6069
6453
|
else results.push(relative5(dir, path));
|
|
6070
6454
|
}
|
|
@@ -6555,6 +6939,8 @@ async function codeCommand(parsed, dependencies) {
|
|
|
6555
6939
|
assertArgs(parsed, [
|
|
6556
6940
|
"config",
|
|
6557
6941
|
"env",
|
|
6942
|
+
"platform",
|
|
6943
|
+
"app-id",
|
|
6558
6944
|
"email",
|
|
6559
6945
|
"open",
|
|
6560
6946
|
"name",
|
|
@@ -6570,6 +6956,8 @@ async function codeCommand(parsed, dependencies) {
|
|
|
6570
6956
|
await (dependencies.codeConnect ?? codeConnect)({
|
|
6571
6957
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
6572
6958
|
env: stringOpt(parsed.options.env),
|
|
6959
|
+
platform: stringOpt(parsed.options.platform),
|
|
6960
|
+
appId: stringOpt(parsed.options["app-id"]),
|
|
6573
6961
|
email: stringOpt(parsed.options.email),
|
|
6574
6962
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
6575
6963
|
name: stringOpt(parsed.options.name),
|
|
@@ -6627,12 +7015,12 @@ async function hostedSecurityContext(parsed, dependencies) {
|
|
|
6627
7015
|
);
|
|
6628
7016
|
return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
|
|
6629
7017
|
}
|
|
6630
|
-
async function interactiveConfirmation(
|
|
6631
|
-
if (dependencies.confirm) return dependencies.confirm(
|
|
7018
|
+
async function interactiveConfirmation(message2, dependencies) {
|
|
7019
|
+
if (dependencies.confirm) return dependencies.confirm(message2);
|
|
6632
7020
|
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
6633
7021
|
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
6634
7022
|
try {
|
|
6635
|
-
const answer = await prompt.question(`${
|
|
7023
|
+
const answer = await prompt.question(`${message2} [y/N] `);
|
|
6636
7024
|
return /^y(?:es)?$/i.test(answer.trim());
|
|
6637
7025
|
} finally {
|
|
6638
7026
|
prompt.close();
|
|
@@ -7286,4 +7674,4 @@ export {
|
|
|
7286
7674
|
exitCodeFor,
|
|
7287
7675
|
runCli
|
|
7288
7676
|
};
|
|
7289
|
-
//# sourceMappingURL=chunk-
|
|
7677
|
+
//# sourceMappingURL=chunk-E3CCYDIT.js.map
|