@odla-ai/cli 0.25.22 → 0.25.23
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 +30 -10
- package/dist/bin.cjs +625 -225
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-2AFAXLKE.js → chunk-JJ42NV7X.js} +589 -180
- package/dist/chunk-JJ42NV7X.js.map +1 -0
- package/dist/index.cjs +633 -225
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -12
- package/dist/index.d.ts +39 -12
- package/dist/index.js +9 -1
- package/package.json +2 -2
- package/dist/chunk-2AFAXLKE.js.map +0 -1
|
@@ -440,6 +440,7 @@ function audienceBoundEnvToken(token, platform) {
|
|
|
440
440
|
var SCOPE_PURPOSE = {
|
|
441
441
|
"platform:status:read": "read the platform fleet health and deployment snapshot",
|
|
442
442
|
"app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
|
|
443
|
+
"app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
|
|
443
444
|
"platform:runbook:write": "add or edit odla's operational runbooks",
|
|
444
445
|
"platform:ai:policy:write": "change System AI model routing",
|
|
445
446
|
"platform:ai:policy:read": "read System AI model routing",
|
|
@@ -941,6 +942,7 @@ function unique(values) {
|
|
|
941
942
|
var DEFAULT_PLATFORM = "https://odla.ai";
|
|
942
943
|
var DEFAULT_ENVS = ["dev"];
|
|
943
944
|
var DEFAULT_SERVICES = ["db", "ai"];
|
|
945
|
+
var configImportSerial = 0;
|
|
944
946
|
var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
|
|
945
947
|
async function loadProjectConfig(configPath = "odla.config.mjs") {
|
|
946
948
|
const resolved = resolve2(configPath);
|
|
@@ -1148,7 +1150,8 @@ function validId2(value2) {
|
|
|
1148
1150
|
}
|
|
1149
1151
|
async function loadConfigModule(path) {
|
|
1150
1152
|
if (path.endsWith(".json")) return JSON.parse(readFileSync2(path, "utf8"));
|
|
1151
|
-
const
|
|
1153
|
+
const nonce = `${Date.now()}-${configImportSerial++}`;
|
|
1154
|
+
const mod = await import(`${pathToFileURL(path).href}?reload=${nonce}`);
|
|
1152
1155
|
const value2 = mod.default ?? mod.config;
|
|
1153
1156
|
if (typeof value2 === "function") return await value2();
|
|
1154
1157
|
return value2;
|
|
@@ -1638,7 +1641,8 @@ var CAPABILITIES = {
|
|
|
1638
1641
|
"save and explicitly select non-secret named operator contexts with isolated credential caches; resolve and explain platform, app, environment, and credential provenance without authenticating; then run PM, Discussions, o11y, runbook, and identity operations outside a project checkout",
|
|
1639
1642
|
"read one versioned o11y status envelope spanning application RED, exact Worker versions and Cloudflare colos observed in traffic, current live-sync freshness/load, the protected commit-to-visible canary, collector ingest/scheduler trust, provider-owned runtime metrics, account-scoped Durable Object, D1, and R2 evidence under odla-db, and a bounded machine verdict",
|
|
1640
1643
|
"read one canonical platform fleet snapshot over private service bindings, including release identities, probe latency, Cloudflare load/runtime freshness, explicit unknowns, and stable next actions through a read-only capability",
|
|
1641
|
-
"compare one project's checked-in Registry intent with live owner-visible state
|
|
1644
|
+
"compare one project's checked-in Registry intent with live owner-visible state, freeze a secret-free Registry-revision-bound plan through app:config:read, and conditionally apply checkpoint-free actions through exact app:config:write operation routes",
|
|
1645
|
+
"inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
|
|
1642
1646
|
"inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
|
|
1643
1647
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
1644
1648
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
@@ -1685,6 +1689,16 @@ function printGroup(out, heading, items) {
|
|
|
1685
1689
|
out.log("");
|
|
1686
1690
|
}
|
|
1687
1691
|
|
|
1692
|
+
// src/config-operation-error.ts
|
|
1693
|
+
var ConfigOperationCommandError = class extends Error {
|
|
1694
|
+
constructor(message2, code) {
|
|
1695
|
+
super(message2);
|
|
1696
|
+
this.code = code;
|
|
1697
|
+
this.name = "ConfigOperationCommandError";
|
|
1698
|
+
}
|
|
1699
|
+
code;
|
|
1700
|
+
};
|
|
1701
|
+
|
|
1688
1702
|
// src/config-reconcile-desired.ts
|
|
1689
1703
|
import { orderAppServices } from "@odla-ai/apps";
|
|
1690
1704
|
|
|
@@ -1778,8 +1792,25 @@ function managedServiceConfig(cfg, env, service) {
|
|
|
1778
1792
|
return {};
|
|
1779
1793
|
}
|
|
1780
1794
|
|
|
1781
|
-
// src/config-
|
|
1782
|
-
import {
|
|
1795
|
+
// src/config-operation-command.ts
|
|
1796
|
+
import {
|
|
1797
|
+
AppsError,
|
|
1798
|
+
createAppsClient
|
|
1799
|
+
} from "@odla-ai/apps";
|
|
1800
|
+
import { join as join3 } from "path";
|
|
1801
|
+
|
|
1802
|
+
// src/version.ts
|
|
1803
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1804
|
+
function cliVersion() {
|
|
1805
|
+
const pkg = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8"));
|
|
1806
|
+
return pkg.version ?? "unknown";
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
// src/config-operation-validate.ts
|
|
1810
|
+
import {
|
|
1811
|
+
appServiceDefinition as appServiceDefinition2
|
|
1812
|
+
} from "@odla-ai/apps";
|
|
1813
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1783
1814
|
|
|
1784
1815
|
// src/config-reconcile-digest.ts
|
|
1785
1816
|
import { createHash } from "crypto";
|
|
@@ -1790,15 +1821,347 @@ function configDigest(value2) {
|
|
|
1790
1821
|
return `sha256:${createHash("sha256").update(canonicalJson(value2)).digest("hex")}`;
|
|
1791
1822
|
}
|
|
1792
1823
|
function canonicalValue(value2) {
|
|
1824
|
+
if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
|
|
1825
|
+
if (typeof value2 === "number") {
|
|
1826
|
+
if (!Number.isFinite(value2)) throw new TypeError("canonical JSON rejects non-finite numbers");
|
|
1827
|
+
return value2;
|
|
1828
|
+
}
|
|
1793
1829
|
if (Array.isArray(value2)) return value2.map(canonicalValue);
|
|
1794
1830
|
if (value2 && typeof value2 === "object") {
|
|
1831
|
+
const record10 = value2;
|
|
1795
1832
|
return Object.fromEntries(
|
|
1796
|
-
Object.
|
|
1833
|
+
Object.keys(record10).filter((key) => record10[key] !== void 0).sort().map((key) => [key, canonicalValue(record10[key])])
|
|
1797
1834
|
);
|
|
1798
1835
|
}
|
|
1799
|
-
|
|
1836
|
+
throw new TypeError("canonical JSON rejects unsupported values");
|
|
1800
1837
|
}
|
|
1801
1838
|
|
|
1839
|
+
// src/config-operation-validate.ts
|
|
1840
|
+
var DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
1841
|
+
var REVISION = /^registry:[1-9][0-9]*$/;
|
|
1842
|
+
var OPERATION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1843
|
+
var ACTION_ID = /^action-[0-9a-f]{16}$/;
|
|
1844
|
+
var ENV = /^[a-z0-9]{2,12}$/;
|
|
1845
|
+
var SERVICE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
1846
|
+
function readPlan(path) {
|
|
1847
|
+
let value2;
|
|
1848
|
+
try {
|
|
1849
|
+
const raw = readFileSync4(path, "utf8");
|
|
1850
|
+
if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
|
|
1851
|
+
value2 = JSON.parse(raw);
|
|
1852
|
+
} catch (error) {
|
|
1853
|
+
throw new ConfigOperationCommandError(
|
|
1854
|
+
`cannot read --plan ${path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1855
|
+
"invalid_plan"
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1858
|
+
if (!record2(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
|
|
1859
|
+
if (!record2(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
|
|
1860
|
+
invalidPlan("plan scope is invalid");
|
|
1861
|
+
}
|
|
1862
|
+
if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
|
|
1863
|
+
invalidPlan("plan content revisions are invalid");
|
|
1864
|
+
}
|
|
1865
|
+
if (!REVISION.test(String(value2.registryRevision)) || !DIGEST.test(String(value2.planDigest))) {
|
|
1866
|
+
invalidPlan("plan Registry revision or digest is invalid");
|
|
1867
|
+
}
|
|
1868
|
+
if (!Array.isArray(value2.actions) || !value2.actions.length || value2.actions.length > 32) {
|
|
1869
|
+
invalidPlan("plan actions must contain 1-32 entries");
|
|
1870
|
+
}
|
|
1871
|
+
assertActions(value2.actions);
|
|
1872
|
+
const plan = value2;
|
|
1873
|
+
const digest = configDigest({
|
|
1874
|
+
schemaVersion: plan.schemaVersion,
|
|
1875
|
+
desiredRevision: plan.desiredRevision,
|
|
1876
|
+
observedRevision: plan.observedRevision,
|
|
1877
|
+
registryRevision: plan.registryRevision,
|
|
1878
|
+
actions: plan.actions
|
|
1879
|
+
});
|
|
1880
|
+
if (digest !== plan.planDigest) invalidPlan("plan digest does not bind these revisions and actions");
|
|
1881
|
+
return plan;
|
|
1882
|
+
}
|
|
1883
|
+
function assertPlanContext(plan, appId, platformUrl) {
|
|
1884
|
+
if (plan.scope.appId !== appId || plan.scope.platformUrl.replace(/\/$/, "") !== platformUrl.replace(/\/$/, "")) {
|
|
1885
|
+
throw new ConfigOperationCommandError("plan scope does not match the selected project config", "checkpoint_required");
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
function verifyReceipt(receipt, appId, operationId) {
|
|
1889
|
+
if (receipt.appId !== appId || operationId && receipt.operationId !== operationId) {
|
|
1890
|
+
throw new ConfigOperationCommandError("Registry returned a receipt outside the requested scope", "invalid_receipt");
|
|
1891
|
+
}
|
|
1892
|
+
if (receipt.receiptDigest) {
|
|
1893
|
+
const { receiptDigest, ...fields } = receipt;
|
|
1894
|
+
if (configDigest(fields) !== receiptDigest) {
|
|
1895
|
+
throw new ConfigOperationCommandError("config operation receipt digest is invalid", "invalid_receipt");
|
|
1896
|
+
}
|
|
1897
|
+
} else if (receipt.state === "succeeded" || receipt.state === "conflict" || receipt.state === "failed" && !receipt.error?.retryable) {
|
|
1898
|
+
throw new ConfigOperationCommandError("terminal config operation receipt is not digest-authenticated", "invalid_receipt");
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
function assertOperationId(value2) {
|
|
1902
|
+
if (!OPERATION_ID.test(value2)) {
|
|
1903
|
+
throw new ConfigOperationCommandError("operation id must be a UUID", "invalid_operation_id");
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
function assertActions(actions) {
|
|
1907
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1908
|
+
for (const action2 of actions) {
|
|
1909
|
+
if (!record2(action2)) invalidPlan("every plan action must be an object");
|
|
1910
|
+
const id = String(action2.id ?? "");
|
|
1911
|
+
if (!ACTION_ID.test(id) || ids.has(id)) invalidPlan("plan action ids must be unique frozen ids");
|
|
1912
|
+
ids.add(id);
|
|
1913
|
+
if (typeof action2.path !== "string" || typeof action2.reason !== "string" || !action2.reason || action2.reason.length > 500 || typeof action2.requiresApproval !== "boolean" || !["low", "medium", "high"].includes(String(action2.risk)) || !["provision", "command", "studio"].includes(String(action2.applySupport))) {
|
|
1914
|
+
invalidPlan("plan action metadata is invalid");
|
|
1915
|
+
}
|
|
1916
|
+
if (["rename_app", "enable_service", "configure_service", "set_link"].includes(String(action2.kind))) {
|
|
1917
|
+
assertConditionalAction(action2);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
function assertConditionalAction(action2) {
|
|
1922
|
+
if (action2.kind === "rename_app") {
|
|
1923
|
+
if (action2.path !== "app.name" || action2.applySupport !== "command" || typeof action2.before !== "string" || typeof action2.after !== "string" || action2.after !== action2.after.trim() || !action2.after || action2.after.length > 80) invalidPlan("rename action is invalid");
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1926
|
+
if (!action2.env || !ENV.test(action2.env)) invalidPlan("conditional action env is invalid");
|
|
1927
|
+
if (action2.kind === "set_link") {
|
|
1928
|
+
if (action2.path !== `environments.${action2.env}.link` || action2.applySupport !== "provision" || !linkState(action2.before) || !linkState(action2.after)) invalidPlan("link action is invalid");
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
if (!action2.service || !SERVICE.test(action2.service) || !appServiceDefinition2(action2.service)) {
|
|
1932
|
+
invalidPlan("service action names an unknown service");
|
|
1933
|
+
}
|
|
1934
|
+
const base = `environments.${action2.env}.services.${action2.service}`;
|
|
1935
|
+
if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
|
|
1936
|
+
invalidPlan("service action path is invalid");
|
|
1937
|
+
}
|
|
1938
|
+
if (action2.applySupport !== "provision" || !record2(action2.after)) {
|
|
1939
|
+
invalidPlan("service action payload is invalid");
|
|
1940
|
+
}
|
|
1941
|
+
if (action2.kind === "enable_service") {
|
|
1942
|
+
if (action2.after.enabled !== true || action2.before !== null && !record2(action2.before)) {
|
|
1943
|
+
invalidPlan("service enable action is invalid");
|
|
1944
|
+
}
|
|
1945
|
+
} else if (!record2(action2.before)) {
|
|
1946
|
+
invalidPlan("service configure action is invalid");
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
function linkState(value2) {
|
|
1950
|
+
if (value2 === null) return true;
|
|
1951
|
+
if (typeof value2 !== "string") return false;
|
|
1952
|
+
try {
|
|
1953
|
+
const url = new URL(value2.trim());
|
|
1954
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
1955
|
+
} catch {
|
|
1956
|
+
return false;
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
function invalidPlan(message2) {
|
|
1960
|
+
throw new ConfigOperationCommandError(message2, "invalid_plan");
|
|
1961
|
+
}
|
|
1962
|
+
function record2(value2) {
|
|
1963
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// src/config-reconcile-support.ts
|
|
1967
|
+
var CONDITIONAL_KINDS = /* @__PURE__ */ new Set([
|
|
1968
|
+
"rename_app",
|
|
1969
|
+
"enable_service",
|
|
1970
|
+
"configure_service",
|
|
1971
|
+
"set_link"
|
|
1972
|
+
]);
|
|
1973
|
+
function configApplySupport(reconciliation) {
|
|
1974
|
+
if (!reconciliation.observedRevision || !reconciliation.registryRevision) {
|
|
1975
|
+
return {
|
|
1976
|
+
supported: false,
|
|
1977
|
+
checkpointRequired: false,
|
|
1978
|
+
reason: "the app must already exist in a revision-aware Registry"
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
if (!reconciliation.actions.length) {
|
|
1982
|
+
return {
|
|
1983
|
+
supported: false,
|
|
1984
|
+
checkpointRequired: false,
|
|
1985
|
+
reason: "there are no managed changes to apply"
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
const blocked = reconciliation.actions.filter(
|
|
1989
|
+
(action2) => !CONDITIONAL_KINDS.has(action2.kind) || action2.risk !== "low" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
|
|
1990
|
+
);
|
|
1991
|
+
if (blocked.length) {
|
|
1992
|
+
return {
|
|
1993
|
+
supported: false,
|
|
1994
|
+
checkpointRequired: blocked.some(
|
|
1995
|
+
(action2) => action2.risk === "high" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
|
|
1996
|
+
),
|
|
1997
|
+
reason: `${blocked.length} action${blocked.length === 1 ? "" : "s"} remain outside conditional apply`
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
return {
|
|
2001
|
+
supported: true,
|
|
2002
|
+
checkpointRequired: false,
|
|
2003
|
+
reason: "all actions are low-risk, checkpoint-free Registry changes"
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
// src/config-operation-command.ts
|
|
2008
|
+
var IDEMPOTENCY_KEY = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/;
|
|
2009
|
+
var DEFAULT_WAIT_SECONDS = 60;
|
|
2010
|
+
var DEFAULT_INTERVAL_SECONDS = 2;
|
|
2011
|
+
async function configApply(options) {
|
|
2012
|
+
const plan = readPlan(options.planPath);
|
|
2013
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
2014
|
+
assertPlanContext(plan, cfg.app.id, cfg.platformUrl);
|
|
2015
|
+
const support = configApplySupport(plan);
|
|
2016
|
+
if (!support.supported) {
|
|
2017
|
+
throw new ConfigOperationCommandError(
|
|
2018
|
+
support.reason,
|
|
2019
|
+
support.checkpointRequired ? "checkpoint_required" : "invalid_plan"
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
if (configDigest(desiredRegistryState(cfg)) !== plan.desiredRevision) {
|
|
2023
|
+
throw new ConfigOperationCommandError(
|
|
2024
|
+
"project config changed after this plan was frozen; generate and review a fresh plan",
|
|
2025
|
+
"checkpoint_required"
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
const idempotencyKey = options.idempotencyKey ?? `cli:${plan.planDigest.slice(7)}`;
|
|
2029
|
+
if (!IDEMPOTENCY_KEY.test(idempotencyKey)) {
|
|
2030
|
+
throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
|
|
2031
|
+
}
|
|
2032
|
+
const client = await operationClient(cfg, options, "apply");
|
|
2033
|
+
const request2 = {
|
|
2034
|
+
schemaVersion: "odla.config-operation-request/v1",
|
|
2035
|
+
expectedRevision: plan.registryRevision,
|
|
2036
|
+
desiredRevision: plan.desiredRevision,
|
|
2037
|
+
observedRevision: plan.observedRevision,
|
|
2038
|
+
planDigest: plan.planDigest,
|
|
2039
|
+
idempotencyKey,
|
|
2040
|
+
source: { kind: "cli", version: cliVersion() },
|
|
2041
|
+
actions: plan.actions
|
|
2042
|
+
};
|
|
2043
|
+
let receipt;
|
|
2044
|
+
try {
|
|
2045
|
+
receipt = await client.applyConfigOperation(cfg.app.id, request2);
|
|
2046
|
+
} catch (error) {
|
|
2047
|
+
const retained = retainedReceipt(error);
|
|
2048
|
+
if (retained) {
|
|
2049
|
+
verifyReceipt(retained, cfg.app.id);
|
|
2050
|
+
printReceipt(retained, options);
|
|
2051
|
+
throw failureForReceipt(retained);
|
|
2052
|
+
}
|
|
2053
|
+
throw normalizeRequestError(error);
|
|
2054
|
+
}
|
|
2055
|
+
verifyReceipt(receipt, cfg.app.id);
|
|
2056
|
+
printReceipt(receipt, options);
|
|
2057
|
+
assertApplyCompleted(receipt);
|
|
2058
|
+
return receipt;
|
|
2059
|
+
}
|
|
2060
|
+
async function configOperationGet(options) {
|
|
2061
|
+
assertOperationId(options.operationId);
|
|
2062
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
2063
|
+
const client = await operationClient(cfg, options, "read");
|
|
2064
|
+
const receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
|
|
2065
|
+
throw normalizeRequestError(error);
|
|
2066
|
+
});
|
|
2067
|
+
if (!receipt) {
|
|
2068
|
+
throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
|
|
2069
|
+
}
|
|
2070
|
+
verifyReceipt(receipt, cfg.app.id, options.operationId);
|
|
2071
|
+
printReceipt(receipt, options);
|
|
2072
|
+
return receipt;
|
|
2073
|
+
}
|
|
2074
|
+
async function configOperationWait(options) {
|
|
2075
|
+
assertOperationId(options.operationId);
|
|
2076
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
2077
|
+
const client = await operationClient(cfg, options, "wait");
|
|
2078
|
+
const wait2 = options.pollWait ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
|
|
2079
|
+
const now = () => (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
2080
|
+
const deadline = now() + (options.timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
|
|
2081
|
+
const interval = (options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS) * 1e3;
|
|
2082
|
+
let receipt = null;
|
|
2083
|
+
for (; ; ) {
|
|
2084
|
+
receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
|
|
2085
|
+
throw normalizeRequestError(error);
|
|
2086
|
+
});
|
|
2087
|
+
if (!receipt) {
|
|
2088
|
+
throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
|
|
2089
|
+
}
|
|
2090
|
+
verifyReceipt(receipt, cfg.app.id, options.operationId);
|
|
2091
|
+
if (receipt.state !== "running") break;
|
|
2092
|
+
if (now() >= deadline) {
|
|
2093
|
+
printReceipt(receipt, options);
|
|
2094
|
+
throw new ConfigOperationCommandError("config operation is still running", "operation_pending");
|
|
2095
|
+
}
|
|
2096
|
+
await wait2(Math.min(interval, Math.max(0, deadline - now())));
|
|
2097
|
+
}
|
|
2098
|
+
printReceipt(receipt, options);
|
|
2099
|
+
if (receipt.state !== "succeeded") throw failureForReceipt(receipt);
|
|
2100
|
+
return receipt;
|
|
2101
|
+
}
|
|
2102
|
+
async function operationClient(cfg, options, purpose) {
|
|
2103
|
+
const doFetch = options.fetch ?? fetch;
|
|
2104
|
+
const out = options.stdout ?? console;
|
|
2105
|
+
const token = await resolveAdminPlatformToken({
|
|
2106
|
+
platform: cfg.platformUrl,
|
|
2107
|
+
scope: "app:config:write",
|
|
2108
|
+
token: options.token,
|
|
2109
|
+
tokenFile: join3(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
2110
|
+
rootDir: cfg.rootDir,
|
|
2111
|
+
email: options.email,
|
|
2112
|
+
open: options.open,
|
|
2113
|
+
fetch: doFetch,
|
|
2114
|
+
stdout: out,
|
|
2115
|
+
openApprovalUrl: options.openApprovalUrl,
|
|
2116
|
+
label: `odla CLI (${cfg.app.id} config operation ${purpose})`
|
|
2117
|
+
});
|
|
2118
|
+
return createAppsClient({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
2119
|
+
}
|
|
2120
|
+
function printReceipt(receipt, options) {
|
|
2121
|
+
const out = options.stdout ?? console;
|
|
2122
|
+
if (options.json) out.log(JSON.stringify(receipt, null, 2));
|
|
2123
|
+
else {
|
|
2124
|
+
out.log(`config operation ${receipt.operationId}: ${receipt.state}`);
|
|
2125
|
+
out.log(`revision: ${receipt.expectedRevision} \u2192 ${receipt.currentRevision}`);
|
|
2126
|
+
for (const step of receipt.progress) out.log(` ${step.state} ${step.actionId} attempts=${step.attempts}`);
|
|
2127
|
+
out.log(`receipt: ${receipt.receiptDigest ?? "pending"}`);
|
|
2128
|
+
out.log(`studio: ${receipt.studioUrl}`);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
function assertApplyCompleted(receipt) {
|
|
2132
|
+
if (receipt.state === "succeeded") return;
|
|
2133
|
+
throw receipt.state === "running" ? new ConfigOperationCommandError("config operation is still running", "operation_pending") : failureForReceipt(receipt);
|
|
2134
|
+
}
|
|
2135
|
+
function failureForReceipt(receipt) {
|
|
2136
|
+
if (receipt.state === "conflict") return new ConfigOperationCommandError(
|
|
2137
|
+
receipt.error?.message ?? "config operation conflicted with current Registry state",
|
|
2138
|
+
"checkpoint_required"
|
|
2139
|
+
);
|
|
2140
|
+
if (receipt.error?.retryable) return new ConfigOperationCommandError(receipt.error.message, "remote_unavailable");
|
|
2141
|
+
return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
|
|
2142
|
+
}
|
|
2143
|
+
function retainedReceipt(error) {
|
|
2144
|
+
if (!(error instanceof AppsError) || !record3(error.details)) return null;
|
|
2145
|
+
return record3(error.details.operation) ? error.details.operation : null;
|
|
2146
|
+
}
|
|
2147
|
+
function normalizeRequestError(error) {
|
|
2148
|
+
if (!(error instanceof AppsError)) return error instanceof Error ? error : new Error(String(error));
|
|
2149
|
+
if (error.status === 401 || error.status === 403) {
|
|
2150
|
+
return new ConfigOperationCommandError(error.message, "auth_failed");
|
|
2151
|
+
}
|
|
2152
|
+
if (error.status === 409) return new ConfigOperationCommandError(error.message, "checkpoint_required");
|
|
2153
|
+
if (error.status === 429 || error.status >= 500) {
|
|
2154
|
+
return new ConfigOperationCommandError(error.message, "remote_unavailable");
|
|
2155
|
+
}
|
|
2156
|
+
return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
|
|
2157
|
+
}
|
|
2158
|
+
function record3(value2) {
|
|
2159
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
// src/config-reconcile.ts
|
|
2163
|
+
import { appServiceIds as appServiceIds2, orderAppServices as orderAppServices2 } from "@odla-ai/apps";
|
|
2164
|
+
|
|
1802
2165
|
// src/config-reconcile-values.ts
|
|
1803
2166
|
function difference(path, desired, observed, status, reason, desiredSource, observedSource, env, service) {
|
|
1804
2167
|
return { path, env, service, status, desired, observed, desiredSource, observedSource, reason };
|
|
@@ -1898,6 +2261,7 @@ function reconcileConfig(input) {
|
|
|
1898
2261
|
sources: { desired: desiredSource, observed: observedSource },
|
|
1899
2262
|
desiredRevision,
|
|
1900
2263
|
observedRevision,
|
|
2264
|
+
registryRevision: input.observed?.configRevision ?? null,
|
|
1901
2265
|
status: different ? "different" : unmanaged ? "unmanaged" : "in_sync",
|
|
1902
2266
|
summary: {
|
|
1903
2267
|
differences: different,
|
|
@@ -2050,8 +2414,8 @@ function compareOptionalEnvironmentState(env, desired, observed, desiredSource,
|
|
|
2050
2414
|
}
|
|
2051
2415
|
|
|
2052
2416
|
// src/config-reconcile-command.ts
|
|
2053
|
-
import { createAppsClient } from "@odla-ai/apps";
|
|
2054
|
-
import { join as
|
|
2417
|
+
import { createAppsClient as createAppsClient2 } from "@odla-ai/apps";
|
|
2418
|
+
import { join as join4 } from "path";
|
|
2055
2419
|
async function configDiff(options) {
|
|
2056
2420
|
const reconciliation = await inspectConfig(options);
|
|
2057
2421
|
const { actions: _actions, ...read3 } = reconciliation;
|
|
@@ -2065,20 +2429,19 @@ async function configDiff(options) {
|
|
|
2065
2429
|
}
|
|
2066
2430
|
async function configPlan(options) {
|
|
2067
2431
|
const reconciliation = await inspectConfig(options);
|
|
2432
|
+
const apply = configApplySupport(reconciliation);
|
|
2068
2433
|
const planDigest = configDigest({
|
|
2069
|
-
schemaVersion: "odla.config-plan/
|
|
2434
|
+
schemaVersion: "odla.config-plan/v2",
|
|
2070
2435
|
desiredRevision: reconciliation.desiredRevision,
|
|
2071
2436
|
observedRevision: reconciliation.observedRevision,
|
|
2437
|
+
registryRevision: reconciliation.registryRevision,
|
|
2072
2438
|
actions: reconciliation.actions
|
|
2073
2439
|
});
|
|
2074
2440
|
const document = {
|
|
2075
|
-
schemaVersion: "odla.config-plan/
|
|
2441
|
+
schemaVersion: "odla.config-plan/v2",
|
|
2076
2442
|
...reconciliation,
|
|
2077
2443
|
planDigest,
|
|
2078
|
-
apply
|
|
2079
|
-
supported: false,
|
|
2080
|
-
reason: "conditional, resumable config apply is not available yet; use the exact reviewed commands below"
|
|
2081
|
-
},
|
|
2444
|
+
apply,
|
|
2082
2445
|
nextActions: planNextActions(reconciliation, options.configPath)
|
|
2083
2446
|
};
|
|
2084
2447
|
printPlan(document, options);
|
|
@@ -2092,7 +2455,7 @@ async function inspectConfig(options) {
|
|
|
2092
2455
|
platform: cfg.platformUrl,
|
|
2093
2456
|
scope: "app:config:read",
|
|
2094
2457
|
token: options.token,
|
|
2095
|
-
tokenFile:
|
|
2458
|
+
tokenFile: join4(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
2096
2459
|
rootDir: cfg.rootDir,
|
|
2097
2460
|
email: options.email,
|
|
2098
2461
|
open: options.open,
|
|
@@ -2101,7 +2464,7 @@ async function inspectConfig(options) {
|
|
|
2101
2464
|
openApprovalUrl: options.openApprovalUrl,
|
|
2102
2465
|
label: `odla CLI (${cfg.app.id} config read)`
|
|
2103
2466
|
});
|
|
2104
|
-
const client =
|
|
2467
|
+
const client = createAppsClient2({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
2105
2468
|
const observed = await client.resolveApp(cfg.app.id);
|
|
2106
2469
|
return reconcileConfig({
|
|
2107
2470
|
desired: desiredRegistryState(cfg),
|
|
@@ -2137,7 +2500,7 @@ function printPlan(document, options) {
|
|
|
2137
2500
|
}
|
|
2138
2501
|
if (!document.actions.length) out.log(" no managed changes");
|
|
2139
2502
|
out.log(`plan digest: ${document.planDigest}`);
|
|
2140
|
-
out.log(`apply:
|
|
2503
|
+
out.log(`apply: ${document.apply.supported ? "supported" : "blocked"} \u2014 ${document.apply.reason}`);
|
|
2141
2504
|
printNext(out, document.nextActions);
|
|
2142
2505
|
}
|
|
2143
2506
|
function printHeader(out, kind, document) {
|
|
@@ -2145,6 +2508,7 @@ function printHeader(out, kind, document) {
|
|
|
2145
2508
|
out.log(`platform: ${document.scope.platformUrl}`);
|
|
2146
2509
|
out.log(`desired: ${document.desiredRevision}`);
|
|
2147
2510
|
out.log(`observed: ${document.observedRevision ?? "absent"}`);
|
|
2511
|
+
out.log(`registry: ${document.registryRevision ?? "absent"}`);
|
|
2148
2512
|
out.log(
|
|
2149
2513
|
`summary: ${document.summary.differences} different, ${document.summary.unmanaged} unmanaged, ${document.summary.actions} planned actions`
|
|
2150
2514
|
);
|
|
@@ -2177,6 +2541,13 @@ function diffNextActions(reconciliation, configPath) {
|
|
|
2177
2541
|
}
|
|
2178
2542
|
function planNextActions(reconciliation, configPath) {
|
|
2179
2543
|
const next = [];
|
|
2544
|
+
if (configApplySupport(reconciliation).supported) {
|
|
2545
|
+
next.push({
|
|
2546
|
+
code: "apply_frozen_plan",
|
|
2547
|
+
command: "odla-ai config apply --plan <saved-plan.json> --json",
|
|
2548
|
+
description: "Save this JSON document, then conditionally apply these exact revision-bound actions."
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2180
2551
|
if (reconciliation.actions.some((action2) => action2.applySupport === "provision")) {
|
|
2181
2552
|
next.push({
|
|
2182
2553
|
code: "review_provision",
|
|
@@ -2212,13 +2583,13 @@ function quoteArg2(value2) {
|
|
|
2212
2583
|
|
|
2213
2584
|
// src/doctor-checks.ts
|
|
2214
2585
|
import { execFileSync } from "child_process";
|
|
2215
|
-
import { existsSync as existsSync5, readFileSync as
|
|
2216
|
-
import { join as
|
|
2586
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
2587
|
+
import { join as join6, resolve as resolve3 } from "path";
|
|
2217
2588
|
|
|
2218
2589
|
// src/wrangler.ts
|
|
2219
2590
|
import { spawn as spawn2 } from "child_process";
|
|
2220
|
-
import { existsSync as existsSync4, readFileSync as
|
|
2221
|
-
import { join as
|
|
2591
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
|
|
2592
|
+
import { join as join5 } from "path";
|
|
2222
2593
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
2223
2594
|
const child = spawn2(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
2224
2595
|
let stdout = "";
|
|
@@ -2232,7 +2603,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
|
|
|
2232
2603
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
2233
2604
|
function findWranglerConfig(rootDir) {
|
|
2234
2605
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
2235
|
-
const path =
|
|
2606
|
+
const path = join5(rootDir, name);
|
|
2236
2607
|
if (existsSync4(path)) return path;
|
|
2237
2608
|
}
|
|
2238
2609
|
return null;
|
|
@@ -2240,7 +2611,7 @@ function findWranglerConfig(rootDir) {
|
|
|
2240
2611
|
function readWranglerConfig(path) {
|
|
2241
2612
|
if (path.endsWith(".toml")) return null;
|
|
2242
2613
|
try {
|
|
2243
|
-
return JSON.parse(stripJsonComments(
|
|
2614
|
+
return JSON.parse(stripJsonComments(readFileSync5(path, "utf8")));
|
|
2244
2615
|
} catch {
|
|
2245
2616
|
return null;
|
|
2246
2617
|
}
|
|
@@ -2341,7 +2712,7 @@ function wranglerWarnings(rootDir) {
|
|
|
2341
2712
|
const dir = resolve3(rootDir, assets.directory);
|
|
2342
2713
|
if (dir === resolve3(rootDir)) {
|
|
2343
2714
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
2344
|
-
} else if (existsSync5(
|
|
2715
|
+
} else if (existsSync5(join6(dir, "node_modules"))) {
|
|
2345
2716
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
2346
2717
|
}
|
|
2347
2718
|
}
|
|
@@ -2382,7 +2753,7 @@ function o11yProjectWarnings(rootDir) {
|
|
|
2382
2753
|
} else {
|
|
2383
2754
|
let source = "";
|
|
2384
2755
|
try {
|
|
2385
|
-
source =
|
|
2756
|
+
source = readFileSync6(main, "utf8");
|
|
2386
2757
|
} catch {
|
|
2387
2758
|
}
|
|
2388
2759
|
if (!/\bwithObservability\b/.test(source)) {
|
|
@@ -2406,7 +2777,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
2406
2777
|
}
|
|
2407
2778
|
function readPackageJson(rootDir) {
|
|
2408
2779
|
try {
|
|
2409
|
-
return JSON.parse(
|
|
2780
|
+
return JSON.parse(readFileSync6(join6(rootDir, "package.json"), "utf8"));
|
|
2410
2781
|
} catch {
|
|
2411
2782
|
return null;
|
|
2412
2783
|
}
|
|
@@ -2616,7 +2987,7 @@ async function doctor(options) {
|
|
|
2616
2987
|
// src/init.ts
|
|
2617
2988
|
import { existsSync as existsSync6, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2618
2989
|
import { dirname as dirname4, resolve as resolve4 } from "path";
|
|
2619
|
-
import { appServiceDefinition as
|
|
2990
|
+
import { appServiceDefinition as appServiceDefinition3, appServiceIds as appServiceIds3 } from "@odla-ai/apps";
|
|
2620
2991
|
function initProject(options) {
|
|
2621
2992
|
const out = options.stdout ?? console;
|
|
2622
2993
|
const rootDir = resolve4(options.rootDir ?? process.cwd());
|
|
@@ -2630,7 +3001,7 @@ function initProject(options) {
|
|
|
2630
3001
|
const envs = options.envs?.length ? options.envs : ["dev"];
|
|
2631
3002
|
const services = options.services?.length ? options.services : ["db", "ai"];
|
|
2632
3003
|
for (const service of services) {
|
|
2633
|
-
const definition =
|
|
3004
|
+
const definition = appServiceDefinition3(service);
|
|
2634
3005
|
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${appServiceIds3().join(", ")})`);
|
|
2635
3006
|
for (const dependency of definition.requires) {
|
|
2636
3007
|
if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
|
|
@@ -2881,9 +3252,9 @@ async function resolveVaultWrite(options) {
|
|
|
2881
3252
|
}
|
|
2882
3253
|
|
|
2883
3254
|
// src/skill.ts
|
|
2884
|
-
import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync3, readFileSync as
|
|
3255
|
+
import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2885
3256
|
import { homedir } from "os";
|
|
2886
|
-
import { dirname as dirname5, isAbsolute as isAbsolute3, join as
|
|
3257
|
+
import { dirname as dirname5, isAbsolute as isAbsolute3, join as join7, relative as relative2, resolve as resolve5, sep } from "path";
|
|
2887
3258
|
import { fileURLToPath } from "url";
|
|
2888
3259
|
|
|
2889
3260
|
// src/skill-adapters.ts
|
|
@@ -2977,12 +3348,12 @@ function installSkill(options = {}) {
|
|
|
2977
3348
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
2978
3349
|
};
|
|
2979
3350
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
2980
|
-
for (const rel of files) plan(
|
|
3351
|
+
for (const rel of files) plan(join7(targetDir2, rel), readFileSync7(join7(sourceDir, rel), "utf8"), false, boundary);
|
|
2981
3352
|
};
|
|
2982
3353
|
let targetDir;
|
|
2983
3354
|
if (options.global) {
|
|
2984
|
-
const claudeRoot =
|
|
2985
|
-
const codexRoot = resolve5(options.codexHomeDir ?? process.env.CODEX_HOME ??
|
|
3355
|
+
const claudeRoot = join7(home, ".claude", "skills");
|
|
3356
|
+
const codexRoot = resolve5(options.codexHomeDir ?? process.env.CODEX_HOME ?? join7(home, ".codex"), "skills");
|
|
2986
3357
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
2987
3358
|
for (const harness of harnesses) {
|
|
2988
3359
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
@@ -2990,35 +3361,35 @@ function installSkill(options = {}) {
|
|
|
2990
3361
|
rememberTarget(harness, skillRoot);
|
|
2991
3362
|
}
|
|
2992
3363
|
} else {
|
|
2993
|
-
const sharedRoot =
|
|
3364
|
+
const sharedRoot = join7(root, ".agents", "skills");
|
|
2994
3365
|
planSkillTree(sharedRoot);
|
|
2995
|
-
const claudeRoot =
|
|
3366
|
+
const claudeRoot = join7(root, ".claude", "skills");
|
|
2996
3367
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
2997
3368
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
2998
3369
|
if (harnesses.includes("claude")) {
|
|
2999
3370
|
for (const skill of skillNames(files)) {
|
|
3000
|
-
const canonical =
|
|
3001
|
-
plan(
|
|
3371
|
+
const canonical = readFileSync7(join7(sourceDir, skill, "SKILL.md"), "utf8");
|
|
3372
|
+
plan(join7(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
|
|
3002
3373
|
}
|
|
3003
3374
|
rememberTarget("claude", claudeRoot);
|
|
3004
3375
|
}
|
|
3005
3376
|
if (harnesses.includes("cursor")) {
|
|
3006
|
-
const cursorRule =
|
|
3377
|
+
const cursorRule = join7(root, ".cursor", "rules", "odla.mdc");
|
|
3007
3378
|
plan(cursorRule, CURSOR_RULE);
|
|
3008
3379
|
rememberTarget("cursor", cursorRule);
|
|
3009
3380
|
}
|
|
3010
3381
|
if (harnesses.includes("agents")) {
|
|
3011
|
-
const agentsFile =
|
|
3382
|
+
const agentsFile = join7(root, "AGENTS.md");
|
|
3012
3383
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3013
3384
|
rememberTarget("agents", agentsFile);
|
|
3014
3385
|
}
|
|
3015
3386
|
if (harnesses.includes("copilot")) {
|
|
3016
|
-
const copilotFile =
|
|
3387
|
+
const copilotFile = join7(root, ".github", "copilot-instructions.md");
|
|
3017
3388
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3018
3389
|
rememberTarget("copilot", copilotFile);
|
|
3019
3390
|
}
|
|
3020
3391
|
if (harnesses.includes("gemini")) {
|
|
3021
|
-
const geminiFile =
|
|
3392
|
+
const geminiFile = join7(root, "GEMINI.md");
|
|
3022
3393
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3023
3394
|
rememberTarget("gemini", geminiFile);
|
|
3024
3395
|
}
|
|
@@ -3036,7 +3407,7 @@ function installSkill(options = {}) {
|
|
|
3036
3407
|
writtenPaths.add(file.target);
|
|
3037
3408
|
continue;
|
|
3038
3409
|
}
|
|
3039
|
-
const current =
|
|
3410
|
+
const current = readFileSync7(file.target, "utf8");
|
|
3040
3411
|
if (current === file.content) {
|
|
3041
3412
|
unchangedPaths.add(file.target);
|
|
3042
3413
|
} else if (file.managedMerge || options.force) {
|
|
@@ -3053,7 +3424,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
3053
3424
|
);
|
|
3054
3425
|
}
|
|
3055
3426
|
for (const file of plans.values()) {
|
|
3056
|
-
if (!existsSync7(file.target) ||
|
|
3427
|
+
if (!existsSync7(file.target) || readFileSync7(file.target, "utf8") !== file.content) {
|
|
3057
3428
|
mkdirSync3(dirname5(file.target), { recursive: true });
|
|
3058
3429
|
writeFileSync3(file.target, file.content);
|
|
3059
3430
|
}
|
|
@@ -3098,7 +3469,7 @@ function managedFileContent(path, block, force, boundary) {
|
|
|
3098
3469
|
if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
|
|
3099
3470
|
if (!existsSync7(path)) return `${block}
|
|
3100
3471
|
`;
|
|
3101
|
-
const current =
|
|
3472
|
+
const current = readFileSync7(path, "utf8");
|
|
3102
3473
|
const start = "<!-- odla-ai agent setup:start -->";
|
|
3103
3474
|
const end = "<!-- odla-ai agent setup:end -->";
|
|
3104
3475
|
const startAt = current.indexOf(start);
|
|
@@ -3125,7 +3496,7 @@ function symlinkedComponent(boundary, target) {
|
|
|
3125
3496
|
}
|
|
3126
3497
|
let current = boundary;
|
|
3127
3498
|
for (const part of rel.split(sep).filter(Boolean)) {
|
|
3128
|
-
current =
|
|
3499
|
+
current = join7(current, part);
|
|
3129
3500
|
try {
|
|
3130
3501
|
if (lstatSync(current).isSymbolicLink()) return current;
|
|
3131
3502
|
} catch (error) {
|
|
@@ -3142,7 +3513,7 @@ function listFiles(dir) {
|
|
|
3142
3513
|
const results = [];
|
|
3143
3514
|
const walk = (current) => {
|
|
3144
3515
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
3145
|
-
const path =
|
|
3516
|
+
const path = join7(current, entry.name);
|
|
3146
3517
|
if (entry.isDirectory()) walk(path);
|
|
3147
3518
|
else results.push(relative2(dir, path));
|
|
3148
3519
|
}
|
|
@@ -3533,7 +3904,7 @@ import { spawn as spawn3 } from "child_process";
|
|
|
3533
3904
|
import { createHash as createHash2 } from "crypto";
|
|
3534
3905
|
import { copyFile, mkdtemp, readFile, rm, writeFile } from "fs/promises";
|
|
3535
3906
|
import { tmpdir } from "os";
|
|
3536
|
-
import { join as
|
|
3907
|
+
import { join as join8 } from "path";
|
|
3537
3908
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3538
3909
|
var runCodeImageCommand = (command, args, stdio) => new Promise((accept, reject) => {
|
|
3539
3910
|
const child = spawn3(command, [...args], { shell: false, stdio });
|
|
@@ -3592,10 +3963,10 @@ async function embeddedPiImageName() {
|
|
|
3592
3963
|
return `odla-ai/pi-agent:embedded-sha256-${createHash2("sha256").update(bundle).digest("hex")}`;
|
|
3593
3964
|
}
|
|
3594
3965
|
async function buildEmbeddedPiImage(engine, image, run) {
|
|
3595
|
-
const context = await mkdtemp(
|
|
3966
|
+
const context = await mkdtemp(join8(tmpdir(), "odla-code-pi-"));
|
|
3596
3967
|
try {
|
|
3597
|
-
await copyFile(embeddedPiAssetPath(),
|
|
3598
|
-
await writeFile(
|
|
3968
|
+
await copyFile(embeddedPiAssetPath(), join8(context, "pi-agent.js"));
|
|
3969
|
+
await writeFile(join8(context, "Dockerfile"), [
|
|
3599
3970
|
`FROM ${CODE_NODE_IMAGE}`,
|
|
3600
3971
|
"COPY pi-agent.js /opt/odla/pi-agent.js",
|
|
3601
3972
|
"WORKDIR /workspace",
|
|
@@ -3621,7 +3992,7 @@ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
|
3621
3992
|
var HarnessProtocolError = class extends Error {
|
|
3622
3993
|
name = "HarnessProtocolError";
|
|
3623
3994
|
};
|
|
3624
|
-
function
|
|
3995
|
+
function record4(value2) {
|
|
3625
3996
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
3626
3997
|
}
|
|
3627
3998
|
function boundedText(value2, label, max) {
|
|
@@ -3638,7 +4009,7 @@ function parseAgentOutput(line) {
|
|
|
3638
4009
|
} catch {
|
|
3639
4010
|
throw new HarnessProtocolError("agent emitted invalid JSON");
|
|
3640
4011
|
}
|
|
3641
|
-
const message2 =
|
|
4012
|
+
const message2 = record4(value2);
|
|
3642
4013
|
if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
|
|
3643
4014
|
throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
|
|
3644
4015
|
}
|
|
@@ -3651,7 +4022,7 @@ function parseAgentOutput(line) {
|
|
|
3651
4022
|
};
|
|
3652
4023
|
}
|
|
3653
4024
|
if (message2.type === "inference.request") {
|
|
3654
|
-
const call2 =
|
|
4025
|
+
const call2 = record4(message2.call);
|
|
3655
4026
|
if (!call2 || !Array.isArray(call2.messages) || !Number.isSafeInteger(call2.maxTokens)) {
|
|
3656
4027
|
throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
|
|
3657
4028
|
}
|
|
@@ -3663,7 +4034,7 @@ function parseAgentOutput(line) {
|
|
|
3663
4034
|
};
|
|
3664
4035
|
}
|
|
3665
4036
|
if (message2.type === "tool.request") {
|
|
3666
|
-
const input =
|
|
4037
|
+
const input = record4(message2.input);
|
|
3667
4038
|
const tool = String(message2.tool);
|
|
3668
4039
|
if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
|
|
3669
4040
|
throw new HarnessProtocolError("tool.request requires a registered tool and object input");
|
|
@@ -3698,7 +4069,7 @@ function encodeAgentInput(message2) {
|
|
|
3698
4069
|
import { execFile as execFile2, spawn as spawn4 } from "child_process";
|
|
3699
4070
|
import { constants } from "fs";
|
|
3700
4071
|
import { access } from "fs/promises";
|
|
3701
|
-
import { delimiter, join as
|
|
4072
|
+
import { delimiter, join as join9 } from "path";
|
|
3702
4073
|
import { getgid, getuid } from "process";
|
|
3703
4074
|
import { mkdir, mkdtemp as mkdtemp2, realpath, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
3704
4075
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -3716,7 +4087,7 @@ function assertPinnedImage(image) {
|
|
|
3716
4087
|
async function commandAvailable(engine) {
|
|
3717
4088
|
for (const directory of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
3718
4089
|
try {
|
|
3719
|
-
await access(
|
|
4090
|
+
await access(join9(directory, engine), constants.X_OK);
|
|
3720
4091
|
return true;
|
|
3721
4092
|
} catch {
|
|
3722
4093
|
}
|
|
@@ -4006,8 +4377,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
4006
4377
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
4007
4378
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
4008
4379
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
4009
|
-
const entries = inventory.flatMap((
|
|
4010
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
4380
|
+
const entries = inventory.flatMap((record10) => {
|
|
4381
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record10);
|
|
4011
4382
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
4012
4383
|
});
|
|
4013
4384
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -4255,8 +4626,8 @@ function normalize(value2) {
|
|
|
4255
4626
|
if (Array.isArray(value2)) return value2.map(normalize);
|
|
4256
4627
|
if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
|
|
4257
4628
|
if (typeof value2 === "object") {
|
|
4258
|
-
const
|
|
4259
|
-
return Object.fromEntries(Object.keys(
|
|
4629
|
+
const record10 = value2;
|
|
4630
|
+
return Object.fromEntries(Object.keys(record10).filter((key) => record10[key] !== void 0).sort().map((key) => [key, normalize(record10[key])]));
|
|
4260
4631
|
}
|
|
4261
4632
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
4262
4633
|
}
|
|
@@ -4335,7 +4706,7 @@ function normalizeReaders(readers) {
|
|
|
4335
4706
|
}
|
|
4336
4707
|
|
|
4337
4708
|
// ../camel/dist/code.js
|
|
4338
|
-
var
|
|
4709
|
+
var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
|
|
4339
4710
|
var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
4340
4711
|
var ID = /^[A-Za-z0-9._:-]{1,160}$/;
|
|
4341
4712
|
async function digestCodeVerificationReceipt(fields) {
|
|
@@ -4370,18 +4741,18 @@ async function digestCodeVerificationReceipt(fields) {
|
|
|
4370
4741
|
return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
|
|
4371
4742
|
}
|
|
4372
4743
|
function validate(fields) {
|
|
4373
|
-
if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !
|
|
4744
|
+
if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !DIGEST2.test(fields.trustedBaseDigest) || !DIGEST2.test(fields.patchDigest) || !DIGEST2.test(fields.candidateDigest) || !DIGEST2.test(fields.sourceDigest) || !DIGEST2.test(fields.policyDigest) || !DIGEST2.test(fields.changedTestSetDigest) || !Number.isSafeInteger(fields.changedTestCount) || fields.changedTestCount < 0 || fields.changedTestCount > 1e4 || fields.changedTestsRequireReview !== fields.changedTestCount > 0 || fields.recipes.length < 1 || fields.recipes.length > 64) {
|
|
4374
4745
|
throw new CamelError("state_conflict", "Code verification receipt is malformed or outside its bounds.");
|
|
4375
4746
|
}
|
|
4376
4747
|
const ids = /* @__PURE__ */ new Set();
|
|
4377
4748
|
for (const recipe2 of fields.recipes) {
|
|
4378
|
-
if (typeof recipe2.recipeId !== "string" || !ID.test(recipe2.recipeId) || ids.has(recipe2.recipeId) || typeof recipe2.recipeDigest !== "string" || !
|
|
4749
|
+
if (typeof recipe2.recipeId !== "string" || !ID.test(recipe2.recipeId) || ids.has(recipe2.recipeId) || typeof recipe2.recipeDigest !== "string" || !DIGEST2.test(recipe2.recipeDigest) || !["passed", "failed", "timed_out", "output_limited"].includes(recipe2.status) || !Number.isSafeInteger(recipe2.exitCode) || recipe2.exitCode < 0 || recipe2.exitCode > 255 || !Number.isSafeInteger(recipe2.durationMs) || recipe2.durationMs < 0 || recipe2.durationMs > 30 * 6e4) {
|
|
4379
4750
|
throw new CamelError("state_conflict", "Code verification recipe receipt is malformed or outside its bounds.");
|
|
4380
4751
|
}
|
|
4381
4752
|
const artifactIds = /* @__PURE__ */ new Set();
|
|
4382
4753
|
if (recipe2.artifacts.length > 64) throw new CamelError("state_conflict", "Code verification has too many artifacts.");
|
|
4383
4754
|
for (const artifact of recipe2.artifacts) {
|
|
4384
|
-
if (typeof artifact.artifactId !== "string" || !ID.test(artifact.artifactId) || artifactIds.has(artifact.artifactId) || !["verified", "missing", "invalid", "too_large"].includes(artifact.status) || artifact.bytes !== null && (!Number.isSafeInteger(artifact.bytes) || artifact.bytes < 0) || artifact.digest !== null && !
|
|
4755
|
+
if (typeof artifact.artifactId !== "string" || !ID.test(artifact.artifactId) || artifactIds.has(artifact.artifactId) || !["verified", "missing", "invalid", "too_large"].includes(artifact.status) || artifact.bytes !== null && (!Number.isSafeInteger(artifact.bytes) || artifact.bytes < 0) || artifact.digest !== null && !DIGEST2.test(artifact.digest) || artifact.status === "verified" !== (artifact.bytes !== null && artifact.digest !== null) || ["missing", "invalid"].includes(artifact.status) && (artifact.bytes !== null || artifact.digest !== null) || artifact.status === "too_large" && (artifact.bytes === null || artifact.digest !== null)) {
|
|
4385
4756
|
throw new CamelError("state_conflict", "Code verification artifact receipt is malformed.");
|
|
4386
4757
|
}
|
|
4387
4758
|
artifactIds.add(artifact.artifactId);
|
|
@@ -4397,7 +4768,7 @@ function validate(fields) {
|
|
|
4397
4768
|
}
|
|
4398
4769
|
}
|
|
4399
4770
|
var SHA2 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
4400
|
-
var
|
|
4771
|
+
var DIGEST22 = /^sha256:[0-9a-f]{64}$/;
|
|
4401
4772
|
var ID2 = /^[A-Za-z0-9._:-]{1,180}$/;
|
|
4402
4773
|
var MAX_PATCH_BYTES = 256 * 1024;
|
|
4403
4774
|
var MAX_STATE_BYTES = 64 * 1024;
|
|
@@ -4442,14 +4813,14 @@ function normalizeState(value2) {
|
|
|
4442
4813
|
]);
|
|
4443
4814
|
const planCursor = state2.planCursor;
|
|
4444
4815
|
const conversations = strings(state2.conversationRefs, "conversationRefs", ID2, 256, false);
|
|
4445
|
-
const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals",
|
|
4446
|
-
if (planCursor !== null && (typeof planCursor !== "string" || !ID2.test(planCursor)) || typeof state2.planningInputDigest !== "string" || !
|
|
4816
|
+
const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals", DIGEST22, 256, true);
|
|
4817
|
+
if (planCursor !== null && (typeof planCursor !== "string" || !ID2.test(planCursor)) || typeof state2.planningInputDigest !== "string" || !DIGEST22.test(state2.planningInputDigest) || typeof state2.buildPolicyDigest !== "string" || !DIGEST22.test(state2.buildPolicyDigest) || state2.dependencyLayerDigest !== null && (typeof state2.dependencyLayerDigest !== "string" || !DIGEST22.test(state2.dependencyLayerDigest)) || state2.verificationDigest !== null && (typeof state2.verificationDigest !== "string" || !DIGEST22.test(state2.verificationDigest)) || state2.reviewDigest !== null && (typeof state2.reviewDigest !== "string" || !DIGEST22.test(state2.reviewDigest)) || !["candidate_untrusted", "verified", "reviewed"].includes(String(state2.trustStatus)) || !Array.isArray(state2.completedEffects) || state2.completedEffects.length > 256) {
|
|
4447
4818
|
throw invalid2("Portable checkpoint state is malformed or outside its bounds.");
|
|
4448
4819
|
}
|
|
4449
4820
|
const effects = state2.completedEffects.map((item) => {
|
|
4450
4821
|
const effect = object(item, "completed effect");
|
|
4451
4822
|
exact2(effect, ["effectId", "actionDigest", "receiptDigest"]);
|
|
4452
|
-
if (typeof effect.effectId !== "string" || !ID2.test(effect.effectId) || typeof effect.actionDigest !== "string" || !
|
|
4823
|
+
if (typeof effect.effectId !== "string" || !ID2.test(effect.effectId) || typeof effect.actionDigest !== "string" || !DIGEST22.test(effect.actionDigest) || typeof effect.receiptDigest !== "string" || !DIGEST22.test(effect.receiptDigest)) {
|
|
4453
4824
|
throw invalid2("Portable checkpoint effect receipt is malformed.");
|
|
4454
4825
|
}
|
|
4455
4826
|
return {
|
|
@@ -4558,7 +4929,7 @@ import { randomUUID } from "crypto";
|
|
|
4558
4929
|
import { createHash as createHash22, randomUUID as randomUUID2 } from "crypto";
|
|
4559
4930
|
import { createReadStream } from "fs";
|
|
4560
4931
|
import { lstat as lstat22 } from "fs/promises";
|
|
4561
|
-
import { join as
|
|
4932
|
+
import { join as join10 } from "path";
|
|
4562
4933
|
import { mkdir as mkdir3, mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
4563
4934
|
import { tmpdir as tmpdir3 } from "os";
|
|
4564
4935
|
import { dirname as dirname6, join as join23, resolve as resolve32, sep as sep23 } from "path";
|
|
@@ -4979,7 +5350,7 @@ function createCodeRuntimeControlClient(options) {
|
|
|
4979
5350
|
}
|
|
4980
5351
|
const value2 = await response2.json().catch(() => null);
|
|
4981
5352
|
if (!response2.ok) {
|
|
4982
|
-
const problem =
|
|
5353
|
+
const problem = record5(record5(value2)?.error);
|
|
4983
5354
|
throw new CodeRuntimeControlError(
|
|
4984
5355
|
typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
|
|
4985
5356
|
response2.status,
|
|
@@ -5001,12 +5372,12 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5001
5372
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
5002
5373
|
),
|
|
5003
5374
|
infer: async (sessionId, inference) => {
|
|
5004
|
-
const value2 =
|
|
5375
|
+
const value2 = record5(await call2(
|
|
5005
5376
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
5006
5377
|
inference,
|
|
5007
5378
|
modelRequestTimeoutMs
|
|
5008
5379
|
));
|
|
5009
|
-
if (!value2 || value2.requestId !== inference.requestId || !
|
|
5380
|
+
if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
|
|
5010
5381
|
throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
|
|
5011
5382
|
}
|
|
5012
5383
|
return value2;
|
|
@@ -5064,12 +5435,12 @@ function validateHeartbeat(version, capabilities) {
|
|
|
5064
5435
|
}
|
|
5065
5436
|
}
|
|
5066
5437
|
function parseSnapshot(value2) {
|
|
5067
|
-
const root =
|
|
5068
|
-
const host =
|
|
5438
|
+
const root = record5(value2);
|
|
5439
|
+
const host = record5(root?.host);
|
|
5069
5440
|
if (!host || typeof host.hostId !== "string" || typeof host.runtimeVersion !== "string" || !Number.isSafeInteger(host.lastSeenAt) || host.revokedAt !== null || !Array.isArray(root?.bindings) || root.bindings.length > 1024 || !Array.isArray(root?.commands) || root.commands.length > 64) throw invalid("heartbeat");
|
|
5070
5441
|
const bindingIds = /* @__PURE__ */ new Set();
|
|
5071
5442
|
const bindings = root.bindings.map((item) => {
|
|
5072
|
-
const binding =
|
|
5443
|
+
const binding = record5(item);
|
|
5073
5444
|
if (!binding || typeof binding.bindingId !== "string" || typeof binding.appId !== "string" || binding.env !== "dev" && binding.env !== "prod" || typeof binding.offerId !== "string" || binding.hostId !== host.hostId || !Number.isSafeInteger(binding.generation) || Number(binding.generation) < 1 || binding.revokedAt !== null || bindingIds.has(binding.bindingId)) {
|
|
5074
5445
|
throw invalid("binding");
|
|
5075
5446
|
}
|
|
@@ -5079,10 +5450,10 @@ function parseSnapshot(value2) {
|
|
|
5079
5450
|
const commandIds = /* @__PURE__ */ new Set();
|
|
5080
5451
|
const commandSequences = /* @__PURE__ */ new Set();
|
|
5081
5452
|
const commands = root.commands.map((item) => {
|
|
5082
|
-
const command =
|
|
5453
|
+
const command = record5(item);
|
|
5083
5454
|
const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
|
|
5084
5455
|
const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
|
|
5085
|
-
if (!command || typeof command.commandId !== "string" || !/^ccmd_[0-9a-f]{32}$/.test(command.commandId) || typeof command.instanceId !== "string" || typeof command.sessionId !== "string" || !/^csess_[0-9a-f]{32}$/.test(command.sessionId) || typeof command.appId !== "string" || command.env !== "dev" && command.env !== "prod" || command.hostId !== host.hostId || !binding || binding.appId !== command.appId || binding.generation !== command.bindingGeneration || !Number.isSafeInteger(command.sequence) || Number(command.sequence) < 1 || commandIds.has(command.commandId) || commandSequences.has(sequenceKey) || !["start", "prompt", "checkpoint_stop", "resume"].includes(String(command.kind)) || !
|
|
5456
|
+
if (!command || typeof command.commandId !== "string" || !/^ccmd_[0-9a-f]{32}$/.test(command.commandId) || typeof command.instanceId !== "string" || typeof command.sessionId !== "string" || !/^csess_[0-9a-f]{32}$/.test(command.sessionId) || typeof command.appId !== "string" || command.env !== "dev" && command.env !== "prod" || command.hostId !== host.hostId || !binding || binding.appId !== command.appId || binding.generation !== command.bindingGeneration || !Number.isSafeInteger(command.sequence) || Number(command.sequence) < 1 || commandIds.has(command.commandId) || commandSequences.has(sequenceKey) || !["start", "prompt", "checkpoint_stop", "resume"].includes(String(command.kind)) || !record5(command.payload) || !Number.isSafeInteger(command.createdAt)) throw invalid("command");
|
|
5086
5457
|
commandIds.add(command.commandId);
|
|
5087
5458
|
commandSequences.add(sequenceKey);
|
|
5088
5459
|
return command;
|
|
@@ -5090,10 +5461,10 @@ function parseSnapshot(value2) {
|
|
|
5090
5461
|
return { host, bindings, commands };
|
|
5091
5462
|
}
|
|
5092
5463
|
async function parseSource(value2) {
|
|
5093
|
-
const snapshot =
|
|
5464
|
+
const snapshot = record5(record5(value2)?.snapshot);
|
|
5094
5465
|
if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
|
|
5095
5466
|
const files = snapshot.files.map((value22) => {
|
|
5096
|
-
const file =
|
|
5467
|
+
const file = record5(value22);
|
|
5097
5468
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
5098
5469
|
return { path: file.path, content: file.content };
|
|
5099
5470
|
});
|
|
@@ -5102,11 +5473,11 @@ async function parseSource(value2) {
|
|
|
5102
5473
|
const aliases = /* @__PURE__ */ new Set();
|
|
5103
5474
|
const references = [];
|
|
5104
5475
|
for (const item of referencesValue) {
|
|
5105
|
-
const reference =
|
|
5476
|
+
const reference = record5(item);
|
|
5106
5477
|
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");
|
|
5107
5478
|
aliases.add(reference.alias);
|
|
5108
5479
|
const referenceFiles = reference.files.map((entry) => {
|
|
5109
|
-
const file =
|
|
5480
|
+
const file = record5(entry);
|
|
5110
5481
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
|
|
5111
5482
|
return { path: file.path, content: file.content };
|
|
5112
5483
|
});
|
|
@@ -5121,18 +5492,18 @@ async function parseSource(value2) {
|
|
|
5121
5492
|
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
5122
5493
|
}
|
|
5123
5494
|
function parseReview(value2) {
|
|
5124
|
-
const review =
|
|
5495
|
+
const review = record5(record5(value2)?.review);
|
|
5125
5496
|
if (!review || !["approved", "rejected"].includes(String(review.verdict)) || typeof review.reviewDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(review.reviewDigest) || typeof review.provider !== "string" || !review.provider || typeof review.model !== "string" || !review.model || !Number.isSafeInteger(review.policyVersion) || Number(review.policyVersion) < 1) throw invalid("review");
|
|
5126
5497
|
return review;
|
|
5127
5498
|
}
|
|
5128
5499
|
function parseCandidate(value2) {
|
|
5129
|
-
const candidate =
|
|
5500
|
+
const candidate = record5(record5(value2)?.candidate);
|
|
5130
5501
|
if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
|
|
5131
5502
|
throw invalid("candidate");
|
|
5132
5503
|
}
|
|
5133
5504
|
return { candidateId: candidate.candidateId, status: candidate.status };
|
|
5134
5505
|
}
|
|
5135
|
-
var
|
|
5506
|
+
var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
5136
5507
|
var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
|
|
5137
5508
|
var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
5138
5509
|
var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
@@ -5477,7 +5848,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
|
|
|
5477
5848
|
const receipts = [];
|
|
5478
5849
|
for (const artifact of recipe2.expectedArtifacts ?? []) {
|
|
5479
5850
|
try {
|
|
5480
|
-
const path =
|
|
5851
|
+
const path = join10(workspaceDir, artifact.path);
|
|
5481
5852
|
const info = await lstat22(path);
|
|
5482
5853
|
if (!info.isFile() || info.isSymbolicLink()) {
|
|
5483
5854
|
receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
|
|
@@ -6495,13 +6866,6 @@ var CodePiRuntimeEngine = class {
|
|
|
6495
6866
|
}
|
|
6496
6867
|
};
|
|
6497
6868
|
|
|
6498
|
-
// src/version.ts
|
|
6499
|
-
import { readFileSync as readFileSync6 } from "fs";
|
|
6500
|
-
function cliVersion() {
|
|
6501
|
-
const pkg = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8"));
|
|
6502
|
-
return pkg.version ?? "unknown";
|
|
6503
|
-
}
|
|
6504
|
-
|
|
6505
6869
|
// src/code-local-source.ts
|
|
6506
6870
|
import { execFile as execFile3 } from "child_process";
|
|
6507
6871
|
import { createHash as createHash4 } from "crypto";
|
|
@@ -6720,25 +7084,25 @@ async function runCodeRuntime(input) {
|
|
|
6720
7084
|
}
|
|
6721
7085
|
}
|
|
6722
7086
|
function parseConnection(value2, appId, appEnv) {
|
|
6723
|
-
const root =
|
|
6724
|
-
const host =
|
|
6725
|
-
const offer =
|
|
6726
|
-
const binding =
|
|
7087
|
+
const root = record6(value2);
|
|
7088
|
+
const host = record6(root?.host);
|
|
7089
|
+
const offer = record6(root?.offer);
|
|
7090
|
+
const binding = record6(root?.binding);
|
|
6727
7091
|
if (!root || typeof root.token !== "string" || !/^odla_code_host_[0-9a-f]{64}$/.test(root.token) || typeof root.resumed !== "boolean" || !host || !/^chost_[0-9a-f]{32}$/.test(String(host.hostId)) || typeof host.name !== "string" || !offer || !Number.isSafeInteger(offer.slots) || !binding || typeof binding.appId !== "string" || !binding.appId || appId && binding.appId !== appId || binding.env !== appEnv || !Number.isSafeInteger(binding.generation)) {
|
|
6728
7092
|
throw new Error("connect Code host returned an invalid response");
|
|
6729
7093
|
}
|
|
6730
7094
|
return root;
|
|
6731
7095
|
}
|
|
6732
7096
|
function apiFailure(action2, status, value2) {
|
|
6733
|
-
const message2 =
|
|
7097
|
+
const message2 = record6(record6(value2)?.error)?.message;
|
|
6734
7098
|
return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
6735
7099
|
}
|
|
6736
|
-
function
|
|
7100
|
+
function record6(value2) {
|
|
6737
7101
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
6738
7102
|
}
|
|
6739
7103
|
|
|
6740
7104
|
// src/provision.ts
|
|
6741
|
-
import { createAppsClient as
|
|
7105
|
+
import { createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
|
|
6742
7106
|
import { putSecret as putSecret2 } from "@odla-ai/ai";
|
|
6743
7107
|
import process8 from "process";
|
|
6744
7108
|
|
|
@@ -6959,7 +7323,7 @@ async function provision(options) {
|
|
|
6959
7323
|
}
|
|
6960
7324
|
const doFetch = options.fetch ?? fetch;
|
|
6961
7325
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
6962
|
-
const apps =
|
|
7326
|
+
const apps = createAppsClient3({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
6963
7327
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
6964
7328
|
if (existing) {
|
|
6965
7329
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
@@ -7133,7 +7497,7 @@ var COMMAND_SURFACE = {
|
|
|
7133
7497
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
7134
7498
|
capabilities: {},
|
|
7135
7499
|
code: { connect: {} },
|
|
7136
|
-
config: { diff: {}, plan: {} },
|
|
7500
|
+
config: { diff: {}, plan: {}, apply: {} },
|
|
7137
7501
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
7138
7502
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
7139
7503
|
discuss: {
|
|
@@ -7151,6 +7515,7 @@ var COMMAND_SURFACE = {
|
|
|
7151
7515
|
help: {},
|
|
7152
7516
|
init: {},
|
|
7153
7517
|
o11y: { status: {} },
|
|
7518
|
+
operations: { get: {}, wait: {} },
|
|
7154
7519
|
platform: { status: {} },
|
|
7155
7520
|
pm: {
|
|
7156
7521
|
...PM_ENTITIES,
|
|
@@ -7588,17 +7953,17 @@ function addOption(options, name, value2) {
|
|
|
7588
7953
|
|
|
7589
7954
|
// src/operator-context.ts
|
|
7590
7955
|
import { existsSync as existsSync10 } from "fs";
|
|
7591
|
-
import { join as
|
|
7956
|
+
import { join as join12, resolve as resolve11 } from "path";
|
|
7592
7957
|
import process10 from "process";
|
|
7593
7958
|
|
|
7594
7959
|
// src/operator-profiles.ts
|
|
7595
|
-
import { existsSync as existsSync9, readFileSync as
|
|
7960
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
7596
7961
|
import { homedir as homedir2 } from "os";
|
|
7597
|
-
import { dirname as dirname7, join as
|
|
7962
|
+
import { dirname as dirname7, join as join11, resolve as resolve10 } from "path";
|
|
7598
7963
|
import process9 from "process";
|
|
7599
7964
|
function operatorProfileFile() {
|
|
7600
7965
|
return resolve10(
|
|
7601
|
-
clean(process9.env.ODLA_CONTEXT_FILE) ??
|
|
7966
|
+
clean(process9.env.ODLA_CONTEXT_FILE) ?? join11(homedir2(), ".odla", "contexts.json")
|
|
7602
7967
|
);
|
|
7603
7968
|
}
|
|
7604
7969
|
function resolveOperatorProfile(parsed) {
|
|
@@ -7643,10 +8008,10 @@ function removeOperatorProfile(name, file = operatorProfileFile()) {
|
|
|
7643
8008
|
return true;
|
|
7644
8009
|
}
|
|
7645
8010
|
function operatorCredentialFiles(selection) {
|
|
7646
|
-
const base = selection.name ?
|
|
8011
|
+
const base = selection.name ? join11(dirname7(selection.file), "profiles", selection.name) : join11(homedir2(), ".odla");
|
|
7647
8012
|
return {
|
|
7648
|
-
developer:
|
|
7649
|
-
scoped:
|
|
8013
|
+
developer: join11(base, "dev-token.json"),
|
|
8014
|
+
scoped: join11(base, "admin-token.local.json")
|
|
7650
8015
|
};
|
|
7651
8016
|
}
|
|
7652
8017
|
function assertOperatorName(value2, label) {
|
|
@@ -7660,7 +8025,7 @@ function readOperatorProfiles(file) {
|
|
|
7660
8025
|
if (!existsSync9(file)) return emptyProfiles();
|
|
7661
8026
|
let raw;
|
|
7662
8027
|
try {
|
|
7663
|
-
raw = JSON.parse(
|
|
8028
|
+
raw = JSON.parse(readFileSync8(file, "utf8"));
|
|
7664
8029
|
} catch {
|
|
7665
8030
|
throw new Error(`operator context file ${file} is not valid JSON`);
|
|
7666
8031
|
}
|
|
@@ -7757,7 +8122,7 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
7757
8122
|
const rootDir = loaded?.rootDir ?? process10.cwd();
|
|
7758
8123
|
const profileCredentials = operatorCredentialFiles(profile);
|
|
7759
8124
|
const tokenFile = clean2(process10.env.ODLA_DEV_TOKEN_FILE) ? resolve11(process10.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
|
|
7760
|
-
const scopedTokenFile = clean2(process10.env.ODLA_ADMIN_TOKEN_FILE) ? resolve11(process10.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ?
|
|
8125
|
+
const scopedTokenFile = clean2(process10.env.ODLA_ADMIN_TOKEN_FILE) ? resolve11(process10.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? join12(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
|
|
7761
8126
|
const cfg = loaded ? {
|
|
7762
8127
|
...loaded,
|
|
7763
8128
|
platformUrl: platformValue,
|
|
@@ -7779,8 +8144,8 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
7779
8144
|
services: [],
|
|
7780
8145
|
local: {
|
|
7781
8146
|
tokenFile,
|
|
7782
|
-
credentialsFile:
|
|
7783
|
-
devVarsFile:
|
|
8147
|
+
credentialsFile: join12(rootDir, ".odla", "credentials.local.json"),
|
|
8148
|
+
devVarsFile: join12(rootDir, ".dev.vars"),
|
|
7784
8149
|
gitignore: true
|
|
7785
8150
|
}
|
|
7786
8151
|
};
|
|
@@ -8024,7 +8389,7 @@ async function appExport(options) {
|
|
|
8024
8389
|
}
|
|
8025
8390
|
|
|
8026
8391
|
// src/app-import.ts
|
|
8027
|
-
import { readFileSync as
|
|
8392
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
8028
8393
|
import {
|
|
8029
8394
|
buildImportOps,
|
|
8030
8395
|
importMutationId,
|
|
@@ -8048,7 +8413,7 @@ async function appImport(options) {
|
|
|
8048
8413
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
8049
8414
|
const doFetch = options.fetch ?? fetch;
|
|
8050
8415
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
8051
|
-
const text = options.file === "-" ? (options.readStdin ?? (() =>
|
|
8416
|
+
const text = options.file === "-" ? (options.readStdin ?? (() => readFileSync9(0, "utf8")))() : readFileSync9(options.file, "utf8");
|
|
8052
8417
|
const { format, sources } = parseImport(text, options.ns);
|
|
8053
8418
|
if (format === "namespace-map" && options.ns) {
|
|
8054
8419
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
@@ -8559,10 +8924,14 @@ async function secretsCommand(parsed, deps) {
|
|
|
8559
8924
|
async function projectCommand(command, parsed, deps) {
|
|
8560
8925
|
if (command === "config") {
|
|
8561
8926
|
const sub = parsed.positionals[1];
|
|
8562
|
-
if (sub !== "diff" && sub !== "plan") {
|
|
8927
|
+
if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
|
|
8563
8928
|
throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
|
|
8564
8929
|
}
|
|
8565
|
-
assertArgs(
|
|
8930
|
+
assertArgs(
|
|
8931
|
+
parsed,
|
|
8932
|
+
sub === "apply" ? ["config", "plan", "idempotency-key", "token", "email", "open", "json"] : ["config", "token", "email", "open", "json"],
|
|
8933
|
+
2
|
|
8934
|
+
);
|
|
8566
8935
|
const options = {
|
|
8567
8936
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
8568
8937
|
token: stringOpt(parsed.options.token),
|
|
@@ -8574,7 +8943,42 @@ async function projectCommand(command, parsed, deps) {
|
|
|
8574
8943
|
stdout: deps.stdout
|
|
8575
8944
|
};
|
|
8576
8945
|
if (sub === "diff") await configDiff(options);
|
|
8577
|
-
else await configPlan(options);
|
|
8946
|
+
else if (sub === "plan") await configPlan(options);
|
|
8947
|
+
else await configApply({
|
|
8948
|
+
...options,
|
|
8949
|
+
planPath: requiredString(parsed.options.plan, "--plan"),
|
|
8950
|
+
idempotencyKey: stringOpt(parsed.options["idempotency-key"])
|
|
8951
|
+
});
|
|
8952
|
+
return true;
|
|
8953
|
+
}
|
|
8954
|
+
if (command === "operations") {
|
|
8955
|
+
const sub = parsed.positionals[1];
|
|
8956
|
+
if (sub !== "get" && sub !== "wait") {
|
|
8957
|
+
throw new Error(`unknown operations subcommand "${sub ?? ""}". Try "odla-ai operations get <operation-id> --json".`);
|
|
8958
|
+
}
|
|
8959
|
+
assertArgs(
|
|
8960
|
+
parsed,
|
|
8961
|
+
sub === "wait" ? ["config", "token", "email", "open", "json", "interval", "timeout"] : ["config", "token", "email", "open", "json"],
|
|
8962
|
+
3
|
|
8963
|
+
);
|
|
8964
|
+
const operationId = parsed.positionals[2];
|
|
8965
|
+
if (!operationId) throw new Error(`operation id is required`);
|
|
8966
|
+
const options = {
|
|
8967
|
+
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
8968
|
+
operationId,
|
|
8969
|
+
token: stringOpt(parsed.options.token),
|
|
8970
|
+
email: stringOpt(parsed.options.email),
|
|
8971
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
8972
|
+
json: parsed.options.json === true,
|
|
8973
|
+
fetch: deps.fetch,
|
|
8974
|
+
openApprovalUrl: deps.openUrl,
|
|
8975
|
+
stdout: deps.stdout,
|
|
8976
|
+
pollWait: deps.pollWait,
|
|
8977
|
+
intervalSeconds: numberOpt(parsed.options.interval, "--interval"),
|
|
8978
|
+
timeoutSeconds: numberOpt(parsed.options.timeout, "--timeout")
|
|
8979
|
+
};
|
|
8980
|
+
if (sub === "get") await configOperationGet(options);
|
|
8981
|
+
else await configOperationWait(options);
|
|
8578
8982
|
return true;
|
|
8579
8983
|
}
|
|
8580
8984
|
if (command === "init") {
|
|
@@ -8845,8 +9249,9 @@ Usage:
|
|
|
8845
9249
|
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8846
9250
|
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
|
|
8847
9251
|
odla-ai doctor [--config odla.config.mjs]
|
|
8848
|
-
odla-ai config diff [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8849
|
-
odla-ai config plan [--
|
|
9252
|
+
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
9253
|
+
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
9254
|
+
odla-ai operations <get|wait> <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
|
|
8850
9255
|
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
8851
9256
|
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
8852
9257
|
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
@@ -8971,8 +9376,8 @@ Commands:
|
|
|
8971
9376
|
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
8972
9377
|
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
8973
9378
|
doctor Validate and summarize the project config without network calls.
|
|
8974
|
-
config
|
|
8975
|
-
|
|
9379
|
+
config Diff Registry intent, freeze a CAS-bound plan, and conditionally apply its safe actions.
|
|
9380
|
+
operations Inspect or wait on one exact, durable config-operation receipt.
|
|
8976
9381
|
calendar Inspect, connect, or disconnect the live Google booking connection.
|
|
8977
9382
|
app Archive (suspend, data retained), restore, export, import, or
|
|
8978
9383
|
manage the co-owners of the app. Archiving takes every
|
|
@@ -9679,8 +10084,8 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
9679
10084
|
emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
9680
10085
|
}
|
|
9681
10086
|
async function pmGet(ctx, entity, id) {
|
|
9682
|
-
const { record:
|
|
9683
|
-
emit2(ctx,
|
|
10087
|
+
const { record: record10 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
|
|
10088
|
+
emit2(ctx, record10, () => printRecord(ctx, entity, record10));
|
|
9684
10089
|
}
|
|
9685
10090
|
async function pmSet(ctx, entity, id, parsed) {
|
|
9686
10091
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
@@ -9725,9 +10130,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
9725
10130
|
]);
|
|
9726
10131
|
const handoff = {
|
|
9727
10132
|
appId,
|
|
9728
|
-
unmetGoals: goals.filter((
|
|
9729
|
-
activeTasks: tasks.filter((
|
|
9730
|
-
openBugs: bugs.filter((
|
|
10133
|
+
unmetGoals: goals.filter((record10) => record10.status !== "met"),
|
|
10134
|
+
activeTasks: tasks.filter((record10) => record10.column !== "done"),
|
|
10135
|
+
openBugs: bugs.filter((record10) => record10.status !== "fixed" && record10.status !== "wontfix")
|
|
9731
10136
|
};
|
|
9732
10137
|
const result = {
|
|
9733
10138
|
...handoff,
|
|
@@ -9746,10 +10151,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
9746
10151
|
]) {
|
|
9747
10152
|
ctx.out.log(`${label}:`);
|
|
9748
10153
|
if (!records.length) ctx.out.log("- (none)");
|
|
9749
|
-
else for (const
|
|
10154
|
+
else for (const record10 of records) printRecord(
|
|
9750
10155
|
ctx,
|
|
9751
10156
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
9752
|
-
|
|
10157
|
+
record10
|
|
9753
10158
|
);
|
|
9754
10159
|
}
|
|
9755
10160
|
});
|
|
@@ -9990,17 +10395,17 @@ async function platformCommand(parsed, deps = {}) {
|
|
|
9990
10395
|
}
|
|
9991
10396
|
}
|
|
9992
10397
|
function isPlatformStatus(value2) {
|
|
9993
|
-
if (!
|
|
9994
|
-
if (!
|
|
9995
|
-
if (!
|
|
10398
|
+
if (!record7(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
|
|
10399
|
+
if (!record7(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
|
|
10400
|
+
if (!record7(value2.catalog) || !record7(value2.summary)) return false;
|
|
9996
10401
|
return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
|
|
9997
10402
|
}
|
|
9998
10403
|
function apiMessage(value2) {
|
|
9999
|
-
if (!
|
|
10000
|
-
const error =
|
|
10404
|
+
if (!record7(value2)) return "request failed";
|
|
10405
|
+
const error = record7(value2.error) ? value2.error : value2;
|
|
10001
10406
|
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
10002
10407
|
}
|
|
10003
|
-
function
|
|
10408
|
+
function record7(value2) {
|
|
10004
10409
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
10005
10410
|
}
|
|
10006
10411
|
|
|
@@ -10041,7 +10446,7 @@ function statusVerdict(reads) {
|
|
|
10041
10446
|
severity: "degraded"
|
|
10042
10447
|
});
|
|
10043
10448
|
}
|
|
10044
|
-
const performance =
|
|
10449
|
+
const performance = record8(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
10045
10450
|
if (performance?.status === "unavailable") {
|
|
10046
10451
|
reasons.push({
|
|
10047
10452
|
source: "liveSync",
|
|
@@ -10122,7 +10527,7 @@ function statusVerdict(reads) {
|
|
|
10122
10527
|
reasons
|
|
10123
10528
|
};
|
|
10124
10529
|
}
|
|
10125
|
-
function
|
|
10530
|
+
function record8(value2) {
|
|
10126
10531
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
10127
10532
|
}
|
|
10128
10533
|
function numeric2(value2) {
|
|
@@ -10150,7 +10555,7 @@ function printO11yStatus(status, out) {
|
|
|
10150
10555
|
out.log(
|
|
10151
10556
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
10152
10557
|
);
|
|
10153
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
10558
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record9) : [];
|
|
10154
10559
|
const requests = routes.reduce(
|
|
10155
10560
|
(total, row) => total + numeric3(row.requests),
|
|
10156
10561
|
0
|
|
@@ -10162,39 +10567,39 @@ function printO11yStatus(status, out) {
|
|
|
10162
10567
|
out.log(
|
|
10163
10568
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
10164
10569
|
);
|
|
10165
|
-
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(
|
|
10570
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record9) : [];
|
|
10166
10571
|
out.log(
|
|
10167
10572
|
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
10168
10573
|
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
10169
10574
|
).join(", ") : "none observed"}`
|
|
10170
10575
|
);
|
|
10171
10576
|
out.log(liveSyncLine(status.liveSync));
|
|
10172
|
-
const canaryDurations =
|
|
10577
|
+
const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
10173
10578
|
out.log(
|
|
10174
10579
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
10175
10580
|
);
|
|
10176
|
-
const collectorIngest =
|
|
10177
|
-
const collectorStorage =
|
|
10581
|
+
const collectorIngest = record9(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
10582
|
+
const collectorStorage = record9(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
10178
10583
|
out.log(
|
|
10179
10584
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
10180
10585
|
);
|
|
10181
|
-
const providerMetrics =
|
|
10182
|
-
const providerCapacity =
|
|
10183
|
-
const workerMemory =
|
|
10586
|
+
const providerMetrics = record9(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
10587
|
+
const providerCapacity = record9(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
10588
|
+
const workerMemory = record9(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
10184
10589
|
out.log(
|
|
10185
10590
|
`cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors ${optionalBytes(workerMemory.headroomBytes)} isolate memory headroom`
|
|
10186
10591
|
);
|
|
10187
10592
|
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
10188
10593
|
out.log(line);
|
|
10189
10594
|
}
|
|
10190
|
-
const coverage =
|
|
10191
|
-
const coverageCounts =
|
|
10192
|
-
const coverageBudget =
|
|
10595
|
+
const coverage = record9(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
10596
|
+
const coverageCounts = record9(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
10597
|
+
const coverageBudget = record9(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
10193
10598
|
out.log(
|
|
10194
10599
|
`request-coverage ${status.providerReconciliation.httpStatus} ${String(status.providerReconciliation.body.status ?? status.providerReconciliation.body.error ?? "unavailable")} ${optionalPercent(coverage.applicationCoverage)} application/provider ${numeric3(coverageCounts.applicationRequests)}/${numeric3(coverageCounts.providerRequests)} requests \xB1${optionalPercent(coverageBudget.maxRelativeError)} budget`
|
|
10195
10600
|
);
|
|
10196
10601
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
10197
|
-
const providerFreshness =
|
|
10602
|
+
const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
10198
10603
|
out.log(
|
|
10199
10604
|
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
10200
10605
|
);
|
|
@@ -10203,17 +10608,17 @@ function printO11yStatus(status, out) {
|
|
|
10203
10608
|
);
|
|
10204
10609
|
}
|
|
10205
10610
|
function providerCapacityLines(read3) {
|
|
10206
|
-
const resources =
|
|
10207
|
-
const durableObjects =
|
|
10208
|
-
const periodic =
|
|
10209
|
-
const storage =
|
|
10210
|
-
const d1 =
|
|
10211
|
-
const d1Activity =
|
|
10212
|
-
const d1Storage =
|
|
10213
|
-
const d1Latency =
|
|
10214
|
-
const r2 =
|
|
10215
|
-
const r2Operations =
|
|
10216
|
-
const r2Storage =
|
|
10611
|
+
const resources = record9(read3.body.resources) ? read3.body.resources : {};
|
|
10612
|
+
const durableObjects = record9(resources.durableObjects) ? resources.durableObjects : {};
|
|
10613
|
+
const periodic = record9(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
10614
|
+
const storage = record9(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
10615
|
+
const d1 = record9(resources.d1) ? resources.d1 : {};
|
|
10616
|
+
const d1Activity = record9(d1.activity) ? d1.activity : {};
|
|
10617
|
+
const d1Storage = record9(d1.storage) ? d1.storage : {};
|
|
10618
|
+
const d1Latency = record9(d1Activity.latency) ? d1Activity.latency : {};
|
|
10619
|
+
const r2 = record9(resources.r2) ? resources.r2 : {};
|
|
10620
|
+
const r2Operations = record9(r2.operations) ? r2.operations : {};
|
|
10621
|
+
const r2Storage = record9(r2.storage) ? r2.storage : {};
|
|
10217
10622
|
const status = String(
|
|
10218
10623
|
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
10219
10624
|
);
|
|
@@ -10224,11 +10629,11 @@ function providerCapacityLines(read3) {
|
|
|
10224
10629
|
];
|
|
10225
10630
|
}
|
|
10226
10631
|
function liveSyncLine(read3) {
|
|
10227
|
-
const performance =
|
|
10228
|
-
const commitToSend =
|
|
10632
|
+
const performance = record9(read3.body.performance) ? read3.body.performance : {};
|
|
10633
|
+
const commitToSend = record9(performance.commitToSend) ? performance.commitToSend : {};
|
|
10229
10634
|
return `live-sync ${read3.httpStatus} ${String(read3.body.status ?? read3.body.error ?? "unavailable")} ${numeric3(read3.body.activeConnections)} active ${optionalNumeric(commitToSend.p95)} commit-to-send p95 ${numeric3(performance.sendFailures)} send failures`;
|
|
10230
10635
|
}
|
|
10231
|
-
function
|
|
10636
|
+
function record9(value2) {
|
|
10232
10637
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
10233
10638
|
}
|
|
10234
10639
|
function numeric3(value2) {
|
|
@@ -10429,7 +10834,7 @@ function recordInvocation(parsed) {
|
|
|
10429
10834
|
}
|
|
10430
10835
|
|
|
10431
10836
|
// src/runbook-actions.ts
|
|
10432
|
-
import { readFileSync as
|
|
10837
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
10433
10838
|
|
|
10434
10839
|
// src/runbook-requires.ts
|
|
10435
10840
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
@@ -10514,7 +10919,7 @@ async function bySlug(ctx, slug) {
|
|
|
10514
10919
|
function readBody(file, inline) {
|
|
10515
10920
|
if (inline !== void 0) return inline;
|
|
10516
10921
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
10517
|
-
return
|
|
10922
|
+
return readFileSync10(file === "-" ? 0 : file, "utf8");
|
|
10518
10923
|
}
|
|
10519
10924
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
10520
10925
|
async function runbookList(ctx, all, query) {
|
|
@@ -10601,8 +11006,8 @@ async function runbookRemove(ctx, slug) {
|
|
|
10601
11006
|
}
|
|
10602
11007
|
|
|
10603
11008
|
// src/runbook-import.ts
|
|
10604
|
-
import { readFileSync as
|
|
10605
|
-
import { basename as basename2, join as
|
|
11009
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync2, statSync } from "fs";
|
|
11010
|
+
import { basename as basename2, join as join13 } from "path";
|
|
10606
11011
|
function parseRunbook(text, slug) {
|
|
10607
11012
|
let rest = text;
|
|
10608
11013
|
const meta = {};
|
|
@@ -10632,7 +11037,7 @@ function readRunbookDir(dir) {
|
|
|
10632
11037
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
10633
11038
|
return files.map((file) => {
|
|
10634
11039
|
const slug = basename2(file, ".md");
|
|
10635
|
-
const parsed = parseRunbook(
|
|
11040
|
+
const parsed = parseRunbook(readFileSync11(join13(dir, file), "utf8"), slug);
|
|
10636
11041
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
10637
11042
|
});
|
|
10638
11043
|
}
|
|
@@ -10704,8 +11109,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
10704
11109
|
|
|
10705
11110
|
// src/runbook-impact.ts
|
|
10706
11111
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
10707
|
-
import { existsSync as existsSync11, readFileSync as
|
|
10708
|
-
import { join as
|
|
11112
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
|
|
11113
|
+
import { join as join14 } from "path";
|
|
10709
11114
|
|
|
10710
11115
|
// src/runbook-impact-scan.ts
|
|
10711
11116
|
var DECL = /^[+-]\s*export\s+(?:declare\s+)?(?:default\s+)?(?:abstract\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/;
|
|
@@ -10874,10 +11279,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
10874
11279
|
}
|
|
10875
11280
|
function manifestLabeller(root) {
|
|
10876
11281
|
return (workspace) => {
|
|
10877
|
-
const manifest =
|
|
11282
|
+
const manifest = join14(root, workspace, "package.json");
|
|
10878
11283
|
if (!existsSync11(manifest)) return void 0;
|
|
10879
11284
|
try {
|
|
10880
|
-
const name = JSON.parse(
|
|
11285
|
+
const name = JSON.parse(readFileSync12(manifest, "utf8")).name;
|
|
10881
11286
|
return typeof name === "string" ? name : void 0;
|
|
10882
11287
|
} catch {
|
|
10883
11288
|
return void 0;
|
|
@@ -10944,7 +11349,7 @@ function report3(ctx, impacts) {
|
|
|
10944
11349
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
10945
11350
|
const cwd = deps.cwd ?? process.cwd();
|
|
10946
11351
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
10947
|
-
const read3 = deps.readRepoFile ?? ((path) =>
|
|
11352
|
+
const read3 = deps.readRepoFile ?? ((path) => readFileSync12(join14(cwd, path), "utf8"));
|
|
10948
11353
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
10949
11354
|
if (!surfaces.length) {
|
|
10950
11355
|
return ctx.out.log(
|
|
@@ -11077,9 +11482,9 @@ async function runbookComment(ctx, slug, body) {
|
|
|
11077
11482
|
|
|
11078
11483
|
// src/runbook-editor.ts
|
|
11079
11484
|
import { spawnSync } from "child_process";
|
|
11080
|
-
import { mkdtempSync, readFileSync as
|
|
11485
|
+
import { mkdtempSync, readFileSync as readFileSync13, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
11081
11486
|
import { tmpdir as tmpdir4 } from "os";
|
|
11082
|
-
import { join as
|
|
11487
|
+
import { join as join15 } from "path";
|
|
11083
11488
|
import process13 from "process";
|
|
11084
11489
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
11085
11490
|
function resolveEditor(env = process13.env) {
|
|
@@ -11105,13 +11510,13 @@ function editText(initial, slug, deps = {}) {
|
|
|
11105
11510
|
);
|
|
11106
11511
|
if (!interactive())
|
|
11107
11512
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
11108
|
-
const dir = mkdtempSync(
|
|
11109
|
-
const file =
|
|
11513
|
+
const dir = mkdtempSync(join15(tmpdir4(), "odla-runbook-"));
|
|
11514
|
+
const file = join15(dir, `${slug}.md`);
|
|
11110
11515
|
try {
|
|
11111
11516
|
writeFileSync4(file, initial, { mode: 384 });
|
|
11112
11517
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
11113
11518
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
11114
|
-
const edited =
|
|
11519
|
+
const edited = readFileSync13(file, "utf8");
|
|
11115
11520
|
return edited === initial ? null : edited;
|
|
11116
11521
|
} finally {
|
|
11117
11522
|
rmSync2(dir, { recursive: true, force: true });
|
|
@@ -11805,11 +12210,11 @@ async function securityStatus(parsed, dependencies) {
|
|
|
11805
12210
|
// src/cli.ts
|
|
11806
12211
|
function exitCodeFor(err) {
|
|
11807
12212
|
const code = err?.code;
|
|
11808
|
-
if (code === "handshake_pending" || code === "watch_timeout") return 75;
|
|
12213
|
+
if (code === "handshake_pending" || code === "watch_timeout" || code === "operation_pending") return 75;
|
|
11809
12214
|
if (code === "checkpoint_required") return 3;
|
|
11810
12215
|
if (code === "remote_unavailable") return 6;
|
|
11811
12216
|
if (code === "auth_failed") return 5;
|
|
11812
|
-
if (code === "invalid_cursor") return 2;
|
|
12217
|
+
if (code === "invalid_cursor" || code === "invalid_plan" || code === "invalid_operation_id") return 2;
|
|
11813
12218
|
return 1;
|
|
11814
12219
|
}
|
|
11815
12220
|
async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
@@ -11964,7 +12369,11 @@ export {
|
|
|
11964
12369
|
calendarDisconnect,
|
|
11965
12370
|
CAPABILITIES,
|
|
11966
12371
|
printCapabilities,
|
|
12372
|
+
ConfigOperationCommandError,
|
|
11967
12373
|
desiredRegistryState,
|
|
12374
|
+
configApply,
|
|
12375
|
+
configOperationGet,
|
|
12376
|
+
configOperationWait,
|
|
11968
12377
|
reconcileConfig,
|
|
11969
12378
|
configDiff,
|
|
11970
12379
|
configPlan,
|
|
@@ -12005,4 +12414,4 @@ export {
|
|
|
12005
12414
|
exitCodeFor,
|
|
12006
12415
|
runCli
|
|
12007
12416
|
};
|
|
12008
|
-
//# sourceMappingURL=chunk-
|
|
12417
|
+
//# sourceMappingURL=chunk-JJ42NV7X.js.map
|