@odla-ai/cli 0.25.22 → 0.26.1

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.
@@ -115,10 +115,10 @@ function isManagedDevVar(line) {
115
115
  const match = line.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
116
116
  return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
117
117
  }
118
- function writePrivateText(path, text) {
118
+ function writePrivateText(path, text2) {
119
119
  mkdirSync(dirname(path), { recursive: true });
120
120
  const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
121
- writeFileSync(temporary, text, { mode: 384 });
121
+ writeFileSync(temporary, text2, { mode: 384 });
122
122
  chmodSync(temporary, 384);
123
123
  renameSync(temporary, path);
124
124
  }
@@ -439,7 +439,9 @@ function audienceBoundEnvToken(token, platform) {
439
439
  }
440
440
  var SCOPE_PURPOSE = {
441
441
  "platform:status:read": "read the platform fleet health and deployment snapshot",
442
+ "platform:chat:credential:write": "rotate the built-in Discussion responder credential",
442
443
  "app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
444
+ "app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
443
445
  "platform:runbook:write": "add or edit odla's operational runbooks",
444
446
  "platform:ai:policy:write": "change System AI model routing",
445
447
  "platform:ai:policy:read": "read System AI model routing",
@@ -537,6 +539,14 @@ async function readSecretStream(kind, stream = process6.stdin) {
537
539
  return value2;
538
540
  }
539
541
 
542
+ // src/principal-presentation.ts
543
+ function unresolvedPrincipalLabel(credentialKind2, principalId) {
544
+ const kind = typeof credentialKind2 === "string" ? credentialKind2.trim() : "";
545
+ const id = typeof principalId === "string" ? principalId.trim() : "";
546
+ const audit = kind && id ? `${kind}:${id}` : kind || id;
547
+ return `Unknown principal${audit ? ` [${audit}]` : ""}`;
548
+ }
549
+
540
550
  // src/admin-ai-audit.ts
541
551
  function adminAiAuditQuery(filters) {
542
552
  if (filters.limit === void 0) return "";
@@ -566,17 +576,17 @@ async function readAdminAiAudit(request2) {
566
576
  String(event.changeKind ?? ""),
567
577
  String(event.purpose ?? event.provider ?? ""),
568
578
  route2,
569
- `${String(event.actorType ?? "")}:${String(event.actorId ?? "")}`
579
+ unresolvedPrincipalLabel(event.actorType, event.actorId)
570
580
  ].join(" "));
571
581
  }
572
582
  }
573
583
  async function responseBody(response2) {
574
- const text = await response2.text();
575
- if (!text) return {};
584
+ const text2 = await response2.text();
585
+ if (!text2) return {};
576
586
  try {
577
- return JSON.parse(text);
587
+ return JSON.parse(text2);
578
588
  } catch {
579
- return { message: text.slice(0, 300) };
589
+ return { message: text2.slice(0, 300) };
580
590
  }
581
591
  }
582
592
  function apiError(status, body) {
@@ -643,7 +653,7 @@ when app/env run actor purpose/role route / policy tokens cost status`);
643
653
  timestamp2(event.created_at),
644
654
  `${String(event.app_id ?? "")}/${String(event.env ?? "")}`,
645
655
  String(event.run_id ?? ""),
646
- `${String(event.actor_type ?? "")}:${String(event.actor_id ?? "")}`,
656
+ unresolvedPrincipalLabel(event.actor_type, event.actor_id),
647
657
  `${String(event.purpose ?? "")}/${String(event.role ?? "")}`,
648
658
  `${String(event.provider ?? "")}/${String(event.model ?? "")}@v${String(event.policy_version ?? "unknown")}`,
649
659
  String(input + output),
@@ -664,12 +674,12 @@ function timestamp2(value2) {
664
674
  return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
665
675
  }
666
676
  async function responseBody2(res) {
667
- const text = await res.text();
668
- if (!text) return {};
677
+ const text2 = await res.text();
678
+ if (!text2) return {};
669
679
  try {
670
- return JSON.parse(text);
680
+ return JSON.parse(text2);
671
681
  } catch {
672
- return { message: text.slice(0, 300) };
682
+ return { message: text2.slice(0, 300) };
673
683
  }
674
684
  }
675
685
  function apiError2(action2, status, body) {
@@ -845,12 +855,12 @@ function catalogModels(body) {
845
855
  return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
846
856
  }
847
857
  async function responseBody3(res) {
848
- const text = await res.text();
849
- if (!text) return {};
858
+ const text2 = await res.text();
859
+ if (!text2) return {};
850
860
  try {
851
- return JSON.parse(text);
861
+ return JSON.parse(text2);
852
862
  } catch {
853
- return { message: text.slice(0, 300) };
863
+ return { message: text2.slice(0, 300) };
854
864
  }
855
865
  }
856
866
  function apiError3(action2, status, body) {
@@ -941,6 +951,7 @@ function unique(values) {
941
951
  var DEFAULT_PLATFORM = "https://odla.ai";
942
952
  var DEFAULT_ENVS = ["dev"];
943
953
  var DEFAULT_SERVICES = ["db", "ai"];
954
+ var configImportSerial = 0;
944
955
  var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
945
956
  async function loadProjectConfig(configPath = "odla.config.mjs") {
946
957
  const resolved = resolve2(configPath);
@@ -1148,7 +1159,8 @@ function validId2(value2) {
1148
1159
  }
1149
1160
  async function loadConfigModule(path) {
1150
1161
  if (path.endsWith(".json")) return JSON.parse(readFileSync2(path, "utf8"));
1151
- const mod = await import(`${pathToFileURL(path).href}?t=${Date.now()}`);
1162
+ const nonce = `${Date.now()}-${configImportSerial++}`;
1163
+ const mod = await import(`${pathToFileURL(path).href}?reload=${nonce}`);
1152
1164
  const value2 = mod.default ?? mod.config;
1153
1165
  if (typeof value2 === "function") return await value2();
1154
1166
  return value2;
@@ -1638,7 +1650,8 @@ var CAPABILITIES = {
1638
1650
  "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
1651
  "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
1652
  "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 and freeze a secret-free plan digest through an exact app:config:read capability",
1653
+ "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",
1654
+ "inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
1642
1655
  "inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
1643
1656
  "run app-attributed hosted security discovery and independent validation without provider keys",
1644
1657
  "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
@@ -1685,6 +1698,16 @@ function printGroup(out, heading, items) {
1685
1698
  out.log("");
1686
1699
  }
1687
1700
 
1701
+ // src/config-operation-error.ts
1702
+ var ConfigOperationCommandError = class extends Error {
1703
+ constructor(message2, code) {
1704
+ super(message2);
1705
+ this.code = code;
1706
+ this.name = "ConfigOperationCommandError";
1707
+ }
1708
+ code;
1709
+ };
1710
+
1688
1711
  // src/config-reconcile-desired.ts
1689
1712
  import { orderAppServices } from "@odla-ai/apps";
1690
1713
 
@@ -1778,8 +1801,25 @@ function managedServiceConfig(cfg, env, service) {
1778
1801
  return {};
1779
1802
  }
1780
1803
 
1781
- // src/config-reconcile.ts
1782
- import { appServiceIds as appServiceIds2, orderAppServices as orderAppServices2 } from "@odla-ai/apps";
1804
+ // src/config-operation-command.ts
1805
+ import {
1806
+ AppsError,
1807
+ createAppsClient
1808
+ } from "@odla-ai/apps";
1809
+ import { join as join3 } from "path";
1810
+
1811
+ // src/version.ts
1812
+ import { readFileSync as readFileSync3 } from "fs";
1813
+ function cliVersion() {
1814
+ const pkg = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8"));
1815
+ return pkg.version ?? "unknown";
1816
+ }
1817
+
1818
+ // src/config-operation-validate.ts
1819
+ import {
1820
+ appServiceDefinition as appServiceDefinition2
1821
+ } from "@odla-ai/apps";
1822
+ import { readFileSync as readFileSync4 } from "fs";
1783
1823
 
1784
1824
  // src/config-reconcile-digest.ts
1785
1825
  import { createHash } from "crypto";
@@ -1790,15 +1830,347 @@ function configDigest(value2) {
1790
1830
  return `sha256:${createHash("sha256").update(canonicalJson(value2)).digest("hex")}`;
1791
1831
  }
1792
1832
  function canonicalValue(value2) {
1833
+ if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
1834
+ if (typeof value2 === "number") {
1835
+ if (!Number.isFinite(value2)) throw new TypeError("canonical JSON rejects non-finite numbers");
1836
+ return value2;
1837
+ }
1793
1838
  if (Array.isArray(value2)) return value2.map(canonicalValue);
1794
1839
  if (value2 && typeof value2 === "object") {
1840
+ const record11 = value2;
1795
1841
  return Object.fromEntries(
1796
- Object.entries(value2).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, canonicalValue(entry)])
1842
+ Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, canonicalValue(record11[key])])
1797
1843
  );
1798
1844
  }
1799
- return value2;
1845
+ throw new TypeError("canonical JSON rejects unsupported values");
1846
+ }
1847
+
1848
+ // src/config-operation-validate.ts
1849
+ var DIGEST = /^sha256:[0-9a-f]{64}$/;
1850
+ var REVISION = /^registry:[1-9][0-9]*$/;
1851
+ 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;
1852
+ var ACTION_ID = /^action-[0-9a-f]{16}$/;
1853
+ var ENV = /^[a-z0-9]{2,12}$/;
1854
+ var SERVICE = /^[a-z][a-z0-9-]{0,39}$/;
1855
+ function readPlan(path) {
1856
+ let value2;
1857
+ try {
1858
+ const raw = readFileSync4(path, "utf8");
1859
+ if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
1860
+ value2 = JSON.parse(raw);
1861
+ } catch (error) {
1862
+ throw new ConfigOperationCommandError(
1863
+ `cannot read --plan ${path}: ${error instanceof Error ? error.message : String(error)}`,
1864
+ "invalid_plan"
1865
+ );
1866
+ }
1867
+ if (!record2(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
1868
+ if (!record2(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
1869
+ invalidPlan("plan scope is invalid");
1870
+ }
1871
+ if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
1872
+ invalidPlan("plan content revisions are invalid");
1873
+ }
1874
+ if (!REVISION.test(String(value2.registryRevision)) || !DIGEST.test(String(value2.planDigest))) {
1875
+ invalidPlan("plan Registry revision or digest is invalid");
1876
+ }
1877
+ if (!Array.isArray(value2.actions) || !value2.actions.length || value2.actions.length > 32) {
1878
+ invalidPlan("plan actions must contain 1-32 entries");
1879
+ }
1880
+ assertActions(value2.actions);
1881
+ const plan = value2;
1882
+ const digest = configDigest({
1883
+ schemaVersion: plan.schemaVersion,
1884
+ desiredRevision: plan.desiredRevision,
1885
+ observedRevision: plan.observedRevision,
1886
+ registryRevision: plan.registryRevision,
1887
+ actions: plan.actions
1888
+ });
1889
+ if (digest !== plan.planDigest) invalidPlan("plan digest does not bind these revisions and actions");
1890
+ return plan;
1891
+ }
1892
+ function assertPlanContext(plan, appId, platformUrl) {
1893
+ if (plan.scope.appId !== appId || plan.scope.platformUrl.replace(/\/$/, "") !== platformUrl.replace(/\/$/, "")) {
1894
+ throw new ConfigOperationCommandError("plan scope does not match the selected project config", "checkpoint_required");
1895
+ }
1896
+ }
1897
+ function verifyReceipt(receipt, appId, operationId) {
1898
+ if (receipt.appId !== appId || operationId && receipt.operationId !== operationId) {
1899
+ throw new ConfigOperationCommandError("Registry returned a receipt outside the requested scope", "invalid_receipt");
1900
+ }
1901
+ if (receipt.receiptDigest) {
1902
+ const { receiptDigest, ...fields } = receipt;
1903
+ if (configDigest(fields) !== receiptDigest) {
1904
+ throw new ConfigOperationCommandError("config operation receipt digest is invalid", "invalid_receipt");
1905
+ }
1906
+ } else if (receipt.state === "succeeded" || receipt.state === "conflict" || receipt.state === "failed" && !receipt.error?.retryable) {
1907
+ throw new ConfigOperationCommandError("terminal config operation receipt is not digest-authenticated", "invalid_receipt");
1908
+ }
1909
+ }
1910
+ function assertOperationId(value2) {
1911
+ if (!OPERATION_ID.test(value2)) {
1912
+ throw new ConfigOperationCommandError("operation id must be a UUID", "invalid_operation_id");
1913
+ }
1914
+ }
1915
+ function assertActions(actions) {
1916
+ const ids = /* @__PURE__ */ new Set();
1917
+ for (const action2 of actions) {
1918
+ if (!record2(action2)) invalidPlan("every plan action must be an object");
1919
+ const id = String(action2.id ?? "");
1920
+ if (!ACTION_ID.test(id) || ids.has(id)) invalidPlan("plan action ids must be unique frozen ids");
1921
+ ids.add(id);
1922
+ 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))) {
1923
+ invalidPlan("plan action metadata is invalid");
1924
+ }
1925
+ if (["rename_app", "enable_service", "configure_service", "set_link"].includes(String(action2.kind))) {
1926
+ assertConditionalAction(action2);
1927
+ }
1928
+ }
1929
+ }
1930
+ function assertConditionalAction(action2) {
1931
+ if (action2.kind === "rename_app") {
1932
+ 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");
1933
+ return;
1934
+ }
1935
+ if (!action2.env || !ENV.test(action2.env)) invalidPlan("conditional action env is invalid");
1936
+ if (action2.kind === "set_link") {
1937
+ if (action2.path !== `environments.${action2.env}.link` || action2.applySupport !== "provision" || !linkState(action2.before) || !linkState(action2.after)) invalidPlan("link action is invalid");
1938
+ return;
1939
+ }
1940
+ if (!action2.service || !SERVICE.test(action2.service) || !appServiceDefinition2(action2.service)) {
1941
+ invalidPlan("service action names an unknown service");
1942
+ }
1943
+ const base = `environments.${action2.env}.services.${action2.service}`;
1944
+ if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
1945
+ invalidPlan("service action path is invalid");
1946
+ }
1947
+ if (action2.applySupport !== "provision" || !record2(action2.after)) {
1948
+ invalidPlan("service action payload is invalid");
1949
+ }
1950
+ if (action2.kind === "enable_service") {
1951
+ if (action2.after.enabled !== true || action2.before !== null && !record2(action2.before)) {
1952
+ invalidPlan("service enable action is invalid");
1953
+ }
1954
+ } else if (!record2(action2.before)) {
1955
+ invalidPlan("service configure action is invalid");
1956
+ }
1957
+ }
1958
+ function linkState(value2) {
1959
+ if (value2 === null) return true;
1960
+ if (typeof value2 !== "string") return false;
1961
+ try {
1962
+ const url = new URL(value2.trim());
1963
+ return url.protocol === "http:" || url.protocol === "https:";
1964
+ } catch {
1965
+ return false;
1966
+ }
1967
+ }
1968
+ function invalidPlan(message2) {
1969
+ throw new ConfigOperationCommandError(message2, "invalid_plan");
1970
+ }
1971
+ function record2(value2) {
1972
+ return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
1973
+ }
1974
+
1975
+ // src/config-reconcile-support.ts
1976
+ var CONDITIONAL_KINDS = /* @__PURE__ */ new Set([
1977
+ "rename_app",
1978
+ "enable_service",
1979
+ "configure_service",
1980
+ "set_link"
1981
+ ]);
1982
+ function configApplySupport(reconciliation) {
1983
+ if (!reconciliation.observedRevision || !reconciliation.registryRevision) {
1984
+ return {
1985
+ supported: false,
1986
+ checkpointRequired: false,
1987
+ reason: "the app must already exist in a revision-aware Registry"
1988
+ };
1989
+ }
1990
+ if (!reconciliation.actions.length) {
1991
+ return {
1992
+ supported: false,
1993
+ checkpointRequired: false,
1994
+ reason: "there are no managed changes to apply"
1995
+ };
1996
+ }
1997
+ const blocked = reconciliation.actions.filter(
1998
+ (action2) => !CONDITIONAL_KINDS.has(action2.kind) || action2.risk !== "low" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
1999
+ );
2000
+ if (blocked.length) {
2001
+ return {
2002
+ supported: false,
2003
+ checkpointRequired: blocked.some(
2004
+ (action2) => action2.risk === "high" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
2005
+ ),
2006
+ reason: `${blocked.length} action${blocked.length === 1 ? "" : "s"} remain outside conditional apply`
2007
+ };
2008
+ }
2009
+ return {
2010
+ supported: true,
2011
+ checkpointRequired: false,
2012
+ reason: "all actions are low-risk, checkpoint-free Registry changes"
2013
+ };
1800
2014
  }
1801
2015
 
2016
+ // src/config-operation-command.ts
2017
+ var IDEMPOTENCY_KEY = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/;
2018
+ var DEFAULT_WAIT_SECONDS = 60;
2019
+ var DEFAULT_INTERVAL_SECONDS = 2;
2020
+ async function configApply(options) {
2021
+ const plan = readPlan(options.planPath);
2022
+ const cfg = await loadProjectConfig(options.configPath);
2023
+ assertPlanContext(plan, cfg.app.id, cfg.platformUrl);
2024
+ const support = configApplySupport(plan);
2025
+ if (!support.supported) {
2026
+ throw new ConfigOperationCommandError(
2027
+ support.reason,
2028
+ support.checkpointRequired ? "checkpoint_required" : "invalid_plan"
2029
+ );
2030
+ }
2031
+ if (configDigest(desiredRegistryState(cfg)) !== plan.desiredRevision) {
2032
+ throw new ConfigOperationCommandError(
2033
+ "project config changed after this plan was frozen; generate and review a fresh plan",
2034
+ "checkpoint_required"
2035
+ );
2036
+ }
2037
+ const idempotencyKey = options.idempotencyKey ?? `cli:${plan.planDigest.slice(7)}`;
2038
+ if (!IDEMPOTENCY_KEY.test(idempotencyKey)) {
2039
+ throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
2040
+ }
2041
+ const client = await operationClient(cfg, options, "apply");
2042
+ const request2 = {
2043
+ schemaVersion: "odla.config-operation-request/v1",
2044
+ expectedRevision: plan.registryRevision,
2045
+ desiredRevision: plan.desiredRevision,
2046
+ observedRevision: plan.observedRevision,
2047
+ planDigest: plan.planDigest,
2048
+ idempotencyKey,
2049
+ source: { kind: "cli", version: cliVersion() },
2050
+ actions: plan.actions
2051
+ };
2052
+ let receipt;
2053
+ try {
2054
+ receipt = await client.applyConfigOperation(cfg.app.id, request2);
2055
+ } catch (error) {
2056
+ const retained = retainedReceipt(error);
2057
+ if (retained) {
2058
+ verifyReceipt(retained, cfg.app.id);
2059
+ printReceipt(retained, options);
2060
+ throw failureForReceipt(retained);
2061
+ }
2062
+ throw normalizeRequestError(error);
2063
+ }
2064
+ verifyReceipt(receipt, cfg.app.id);
2065
+ printReceipt(receipt, options);
2066
+ assertApplyCompleted(receipt);
2067
+ return receipt;
2068
+ }
2069
+ async function configOperationGet(options) {
2070
+ assertOperationId(options.operationId);
2071
+ const cfg = await loadProjectConfig(options.configPath);
2072
+ const client = await operationClient(cfg, options, "read");
2073
+ const receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
2074
+ throw normalizeRequestError(error);
2075
+ });
2076
+ if (!receipt) {
2077
+ throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
2078
+ }
2079
+ verifyReceipt(receipt, cfg.app.id, options.operationId);
2080
+ printReceipt(receipt, options);
2081
+ return receipt;
2082
+ }
2083
+ async function configOperationWait(options) {
2084
+ assertOperationId(options.operationId);
2085
+ const cfg = await loadProjectConfig(options.configPath);
2086
+ const client = await operationClient(cfg, options, "wait");
2087
+ const wait2 = options.pollWait ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
2088
+ const now = () => (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
2089
+ const deadline = now() + (options.timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
2090
+ const interval = (options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS) * 1e3;
2091
+ let receipt = null;
2092
+ for (; ; ) {
2093
+ receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
2094
+ throw normalizeRequestError(error);
2095
+ });
2096
+ if (!receipt) {
2097
+ throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
2098
+ }
2099
+ verifyReceipt(receipt, cfg.app.id, options.operationId);
2100
+ if (receipt.state !== "running") break;
2101
+ if (now() >= deadline) {
2102
+ printReceipt(receipt, options);
2103
+ throw new ConfigOperationCommandError("config operation is still running", "operation_pending");
2104
+ }
2105
+ await wait2(Math.min(interval, Math.max(0, deadline - now())));
2106
+ }
2107
+ printReceipt(receipt, options);
2108
+ if (receipt.state !== "succeeded") throw failureForReceipt(receipt);
2109
+ return receipt;
2110
+ }
2111
+ async function operationClient(cfg, options, purpose) {
2112
+ const doFetch = options.fetch ?? fetch;
2113
+ const out = options.stdout ?? console;
2114
+ const token = await resolveAdminPlatformToken({
2115
+ platform: cfg.platformUrl,
2116
+ scope: "app:config:write",
2117
+ token: options.token,
2118
+ tokenFile: join3(cfg.rootDir, ".odla", "admin-token.local.json"),
2119
+ rootDir: cfg.rootDir,
2120
+ email: options.email,
2121
+ open: options.open,
2122
+ fetch: doFetch,
2123
+ stdout: out,
2124
+ openApprovalUrl: options.openApprovalUrl,
2125
+ label: `odla CLI (${cfg.app.id} config operation ${purpose})`
2126
+ });
2127
+ return createAppsClient({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
2128
+ }
2129
+ function printReceipt(receipt, options) {
2130
+ const out = options.stdout ?? console;
2131
+ if (options.json) out.log(JSON.stringify(receipt, null, 2));
2132
+ else {
2133
+ out.log(`config operation ${receipt.operationId}: ${receipt.state}`);
2134
+ out.log(`revision: ${receipt.expectedRevision} \u2192 ${receipt.currentRevision}`);
2135
+ for (const step of receipt.progress) out.log(` ${step.state} ${step.actionId} attempts=${step.attempts}`);
2136
+ out.log(`receipt: ${receipt.receiptDigest ?? "pending"}`);
2137
+ out.log(`studio: ${receipt.studioUrl}`);
2138
+ }
2139
+ }
2140
+ function assertApplyCompleted(receipt) {
2141
+ if (receipt.state === "succeeded") return;
2142
+ throw receipt.state === "running" ? new ConfigOperationCommandError("config operation is still running", "operation_pending") : failureForReceipt(receipt);
2143
+ }
2144
+ function failureForReceipt(receipt) {
2145
+ if (receipt.state === "conflict") return new ConfigOperationCommandError(
2146
+ receipt.error?.message ?? "config operation conflicted with current Registry state",
2147
+ "checkpoint_required"
2148
+ );
2149
+ if (receipt.error?.retryable) return new ConfigOperationCommandError(receipt.error.message, "remote_unavailable");
2150
+ return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
2151
+ }
2152
+ function retainedReceipt(error) {
2153
+ if (!(error instanceof AppsError) || !record3(error.details)) return null;
2154
+ return record3(error.details.operation) ? error.details.operation : null;
2155
+ }
2156
+ function normalizeRequestError(error) {
2157
+ if (!(error instanceof AppsError)) return error instanceof Error ? error : new Error(String(error));
2158
+ if (error.status === 401 || error.status === 403) {
2159
+ return new ConfigOperationCommandError(error.message, "auth_failed");
2160
+ }
2161
+ if (error.status === 409) return new ConfigOperationCommandError(error.message, "checkpoint_required");
2162
+ if (error.status === 429 || error.status >= 500) {
2163
+ return new ConfigOperationCommandError(error.message, "remote_unavailable");
2164
+ }
2165
+ return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
2166
+ }
2167
+ function record3(value2) {
2168
+ return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
2169
+ }
2170
+
2171
+ // src/config-reconcile.ts
2172
+ import { appServiceIds as appServiceIds2, orderAppServices as orderAppServices2 } from "@odla-ai/apps";
2173
+
1802
2174
  // src/config-reconcile-values.ts
1803
2175
  function difference(path, desired, observed, status, reason, desiredSource, observedSource, env, service) {
1804
2176
  return { path, env, service, status, desired, observed, desiredSource, observedSource, reason };
@@ -1898,6 +2270,7 @@ function reconcileConfig(input) {
1898
2270
  sources: { desired: desiredSource, observed: observedSource },
1899
2271
  desiredRevision,
1900
2272
  observedRevision,
2273
+ registryRevision: input.observed?.configRevision ?? null,
1901
2274
  status: different ? "different" : unmanaged ? "unmanaged" : "in_sync",
1902
2275
  summary: {
1903
2276
  differences: different,
@@ -2050,8 +2423,8 @@ function compareOptionalEnvironmentState(env, desired, observed, desiredSource,
2050
2423
  }
2051
2424
 
2052
2425
  // src/config-reconcile-command.ts
2053
- import { createAppsClient } from "@odla-ai/apps";
2054
- import { join as join3 } from "path";
2426
+ import { createAppsClient as createAppsClient2, studioAppSettingsPath } from "@odla-ai/apps";
2427
+ import { join as join4 } from "path";
2055
2428
  async function configDiff(options) {
2056
2429
  const reconciliation = await inspectConfig(options);
2057
2430
  const { actions: _actions, ...read3 } = reconciliation;
@@ -2065,20 +2438,19 @@ async function configDiff(options) {
2065
2438
  }
2066
2439
  async function configPlan(options) {
2067
2440
  const reconciliation = await inspectConfig(options);
2441
+ const apply = configApplySupport(reconciliation);
2068
2442
  const planDigest = configDigest({
2069
- schemaVersion: "odla.config-plan/v1",
2443
+ schemaVersion: "odla.config-plan/v2",
2070
2444
  desiredRevision: reconciliation.desiredRevision,
2071
2445
  observedRevision: reconciliation.observedRevision,
2446
+ registryRevision: reconciliation.registryRevision,
2072
2447
  actions: reconciliation.actions
2073
2448
  });
2074
2449
  const document = {
2075
- schemaVersion: "odla.config-plan/v1",
2450
+ schemaVersion: "odla.config-plan/v2",
2076
2451
  ...reconciliation,
2077
2452
  planDigest,
2078
- apply: {
2079
- supported: false,
2080
- reason: "conditional, resumable config apply is not available yet; use the exact reviewed commands below"
2081
- },
2453
+ apply,
2082
2454
  nextActions: planNextActions(reconciliation, options.configPath)
2083
2455
  };
2084
2456
  printPlan(document, options);
@@ -2092,7 +2464,7 @@ async function inspectConfig(options) {
2092
2464
  platform: cfg.platformUrl,
2093
2465
  scope: "app:config:read",
2094
2466
  token: options.token,
2095
- tokenFile: join3(cfg.rootDir, ".odla", "admin-token.local.json"),
2467
+ tokenFile: join4(cfg.rootDir, ".odla", "admin-token.local.json"),
2096
2468
  rootDir: cfg.rootDir,
2097
2469
  email: options.email,
2098
2470
  open: options.open,
@@ -2101,7 +2473,7 @@ async function inspectConfig(options) {
2101
2473
  openApprovalUrl: options.openApprovalUrl,
2102
2474
  label: `odla CLI (${cfg.app.id} config read)`
2103
2475
  });
2104
- const client = createAppsClient({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
2476
+ const client = createAppsClient2({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
2105
2477
  const observed = await client.resolveApp(cfg.app.id);
2106
2478
  return reconcileConfig({
2107
2479
  desired: desiredRegistryState(cfg),
@@ -2137,7 +2509,7 @@ function printPlan(document, options) {
2137
2509
  }
2138
2510
  if (!document.actions.length) out.log(" no managed changes");
2139
2511
  out.log(`plan digest: ${document.planDigest}`);
2140
- out.log(`apply: unsupported \u2014 ${document.apply.reason}`);
2512
+ out.log(`apply: ${document.apply.supported ? "supported" : "blocked"} \u2014 ${document.apply.reason}`);
2141
2513
  printNext(out, document.nextActions);
2142
2514
  }
2143
2515
  function printHeader(out, kind, document) {
@@ -2145,6 +2517,7 @@ function printHeader(out, kind, document) {
2145
2517
  out.log(`platform: ${document.scope.platformUrl}`);
2146
2518
  out.log(`desired: ${document.desiredRevision}`);
2147
2519
  out.log(`observed: ${document.observedRevision ?? "absent"}`);
2520
+ out.log(`registry: ${document.registryRevision ?? "absent"}`);
2148
2521
  out.log(
2149
2522
  `summary: ${document.summary.differences} different, ${document.summary.unmanaged} unmanaged, ${document.summary.actions} planned actions`
2150
2523
  );
@@ -2177,6 +2550,13 @@ function diffNextActions(reconciliation, configPath) {
2177
2550
  }
2178
2551
  function planNextActions(reconciliation, configPath) {
2179
2552
  const next = [];
2553
+ if (configApplySupport(reconciliation).supported) {
2554
+ next.push({
2555
+ code: "apply_frozen_plan",
2556
+ command: "odla-ai config apply --plan <saved-plan.json> --json",
2557
+ description: "Save this JSON document, then conditionally apply these exact revision-bound actions."
2558
+ });
2559
+ }
2180
2560
  if (reconciliation.actions.some((action2) => action2.applySupport === "provision")) {
2181
2561
  next.push({
2182
2562
  code: "review_provision",
@@ -2190,35 +2570,39 @@ function planNextActions(reconciliation, configPath) {
2190
2570
  }
2191
2571
  }
2192
2572
  if (reconciliation.actions.some((action2) => action2.applySupport === "studio")) {
2193
- const { appId, platformUrl } = reconciliation.scope;
2194
2573
  next.push({
2195
2574
  code: "review_destructive",
2196
- command: `${platformUrl}/studio/apps/${encodeURIComponent(appId)}/settings`,
2575
+ command: studioSettingsUrl(reconciliation),
2197
2576
  description: "Review service disablement in the owning Studio scope; this plan will not apply it."
2198
2577
  });
2199
2578
  }
2200
2579
  if (!reconciliation.actions.length && reconciliation.summary.unmanaged) {
2201
2580
  next.push({
2202
2581
  code: "declare_or_accept_runtime_state",
2203
- command: `${reconciliation.scope.platformUrl}/studio/apps/${encodeURIComponent(reconciliation.scope.appId)}/settings`,
2582
+ command: studioSettingsUrl(reconciliation),
2204
2583
  description: "Declare the live value in project config or keep it as an explicit runtime-managed setting."
2205
2584
  });
2206
2585
  }
2207
2586
  return next;
2208
2587
  }
2588
+ function studioSettingsUrl(reconciliation) {
2589
+ const environment2 = reconciliation.actions.map((action2) => action2.env).concat(reconciliation.scope.environments).find((candidate) => candidate === "dev" || candidate === "prod") ?? "dev";
2590
+ const origin = reconciliation.scope.platformUrl.replace(/\/$/, "");
2591
+ return `${origin}${studioAppSettingsPath(reconciliation.scope.appId, environment2, "environment")}`;
2592
+ }
2209
2593
  function quoteArg2(value2) {
2210
2594
  return `'${value2.replace(/'/g, `'\\''`)}'`;
2211
2595
  }
2212
2596
 
2213
2597
  // src/doctor-checks.ts
2214
2598
  import { execFileSync } from "child_process";
2215
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
2216
- import { join as join5, resolve as resolve3 } from "path";
2599
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
2600
+ import { join as join6, resolve as resolve3 } from "path";
2217
2601
 
2218
2602
  // src/wrangler.ts
2219
2603
  import { spawn as spawn2 } from "child_process";
2220
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
2221
- import { join as join4 } from "path";
2604
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
2605
+ import { join as join5 } from "path";
2222
2606
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
2223
2607
  const child = spawn2(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
2224
2608
  let stdout = "";
@@ -2232,7 +2616,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
2232
2616
  var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
2233
2617
  function findWranglerConfig(rootDir) {
2234
2618
  for (const name of WRANGLER_CONFIG_FILES) {
2235
- const path = join4(rootDir, name);
2619
+ const path = join5(rootDir, name);
2236
2620
  if (existsSync4(path)) return path;
2237
2621
  }
2238
2622
  return null;
@@ -2240,20 +2624,20 @@ function findWranglerConfig(rootDir) {
2240
2624
  function readWranglerConfig(path) {
2241
2625
  if (path.endsWith(".toml")) return null;
2242
2626
  try {
2243
- return JSON.parse(stripJsonComments(readFileSync3(path, "utf8")));
2627
+ return JSON.parse(stripJsonComments(readFileSync5(path, "utf8")));
2244
2628
  } catch {
2245
2629
  return null;
2246
2630
  }
2247
2631
  }
2248
- function stripJsonComments(text) {
2632
+ function stripJsonComments(text2) {
2249
2633
  let result = "";
2250
2634
  let inString = false;
2251
- for (let i = 0; i < text.length; i++) {
2252
- const ch = text[i];
2635
+ for (let i = 0; i < text2.length; i++) {
2636
+ const ch = text2[i];
2253
2637
  if (inString) {
2254
2638
  result += ch;
2255
2639
  if (ch === "\\") {
2256
- result += text[i + 1] ?? "";
2640
+ result += text2[i + 1] ?? "";
2257
2641
  i++;
2258
2642
  } else if (ch === '"') {
2259
2643
  inString = false;
@@ -2265,14 +2649,14 @@ function stripJsonComments(text) {
2265
2649
  result += ch;
2266
2650
  continue;
2267
2651
  }
2268
- if (ch === "/" && text[i + 1] === "/") {
2269
- while (i < text.length && text[i] !== "\n") i++;
2652
+ if (ch === "/" && text2[i + 1] === "/") {
2653
+ while (i < text2.length && text2[i] !== "\n") i++;
2270
2654
  result += "\n";
2271
2655
  continue;
2272
2656
  }
2273
- if (ch === "/" && text[i + 1] === "*") {
2657
+ if (ch === "/" && text2[i + 1] === "*") {
2274
2658
  i += 2;
2275
- while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++;
2659
+ while (i < text2.length && !(text2[i] === "*" && text2[i + 1] === "/")) i++;
2276
2660
  i++;
2277
2661
  continue;
2278
2662
  }
@@ -2289,7 +2673,14 @@ async function wranglerLoggedIn(run, cwd) {
2289
2673
  }
2290
2674
  }
2291
2675
  function wranglerPutSecret(run, opts) {
2292
- const args = ["wrangler", "secret", "put", opts.name, ...opts.env ? ["--env", opts.env] : []];
2676
+ const args = [
2677
+ "wrangler",
2678
+ "secret",
2679
+ "put",
2680
+ opts.name,
2681
+ ...opts.env ? ["--env", opts.env] : [],
2682
+ ...opts.configPath ? ["--config", opts.configPath] : []
2683
+ ];
2293
2684
  return run("npx", args, { input: opts.value, cwd: opts.cwd });
2294
2685
  }
2295
2686
 
@@ -2341,7 +2732,7 @@ function wranglerWarnings(rootDir) {
2341
2732
  const dir = resolve3(rootDir, assets.directory);
2342
2733
  if (dir === resolve3(rootDir)) {
2343
2734
  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(join5(dir, "node_modules"))) {
2735
+ } else if (existsSync5(join6(dir, "node_modules"))) {
2345
2736
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
2346
2737
  }
2347
2738
  }
@@ -2382,7 +2773,7 @@ function o11yProjectWarnings(rootDir) {
2382
2773
  } else {
2383
2774
  let source = "";
2384
2775
  try {
2385
- source = readFileSync4(main, "utf8");
2776
+ source = readFileSync6(main, "utf8");
2386
2777
  } catch {
2387
2778
  }
2388
2779
  if (!/\bwithObservability\b/.test(source)) {
@@ -2406,7 +2797,7 @@ function calendarProjectWarnings(rootDir) {
2406
2797
  }
2407
2798
  function readPackageJson(rootDir) {
2408
2799
  try {
2409
- return JSON.parse(readFileSync4(join5(rootDir, "package.json"), "utf8"));
2800
+ return JSON.parse(readFileSync6(join6(rootDir, "package.json"), "utf8"));
2410
2801
  } catch {
2411
2802
  return null;
2412
2803
  }
@@ -2616,7 +3007,7 @@ async function doctor(options) {
2616
3007
  // src/init.ts
2617
3008
  import { existsSync as existsSync6, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
2618
3009
  import { dirname as dirname4, resolve as resolve4 } from "path";
2619
- import { appServiceDefinition as appServiceDefinition2, appServiceIds as appServiceIds3 } from "@odla-ai/apps";
3010
+ import { appServiceDefinition as appServiceDefinition3, appServiceIds as appServiceIds3 } from "@odla-ai/apps";
2620
3011
  function initProject(options) {
2621
3012
  const out = options.stdout ?? console;
2622
3013
  const rootDir = resolve4(options.rootDir ?? process.cwd());
@@ -2630,7 +3021,7 @@ function initProject(options) {
2630
3021
  const envs = options.envs?.length ? options.envs : ["dev"];
2631
3022
  const services = options.services?.length ? options.services : ["db", "ai"];
2632
3023
  for (const service of services) {
2633
- const definition = appServiceDefinition2(service);
3024
+ const definition = appServiceDefinition3(service);
2634
3025
  if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${appServiceIds3().join(", ")})`);
2635
3026
  for (const dependency of definition.requires) {
2636
3027
  if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
@@ -2648,9 +3039,9 @@ function initProject(options) {
2648
3039
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
2649
3040
  out.log("updated .gitignore for local odla credentials");
2650
3041
  }
2651
- function writeIfMissing(path, text) {
3042
+ function writeIfMissing(path, text2) {
2652
3043
  if (existsSync6(path)) return;
2653
- writeFileSync2(path, text);
3044
+ writeFileSync2(path, text2);
2654
3045
  }
2655
3046
  function configTemplate(input) {
2656
3047
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -2858,13 +3249,13 @@ async function secretsSetClerkKey(options) {
2858
3249
  body: JSON.stringify({ value: value2 })
2859
3250
  });
2860
3251
  if (!res.ok) {
2861
- const text = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
2862
- throw new Error(`store Clerk secret key failed (${res.status}): ${text || "request failed"}`);
3252
+ const text2 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
3253
+ throw new Error(`store Clerk secret key failed (${res.status}): ${text2 || "request failed"}`);
2863
3254
  }
2864
3255
  out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
2865
3256
  }
2866
- function scrubValue(text, value2) {
2867
- return redactSecrets(text).split(value2).join("[value redacted]");
3257
+ function scrubValue(text2, value2) {
3258
+ return redactSecrets(text2).split(value2).join("[value redacted]");
2868
3259
  }
2869
3260
  async function resolveVaultWrite(options) {
2870
3261
  const out = options.stdout ?? console;
@@ -2881,9 +3272,9 @@ async function resolveVaultWrite(options) {
2881
3272
  }
2882
3273
 
2883
3274
  // src/skill.ts
2884
- import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync, writeFileSync as writeFileSync3 } from "fs";
3275
+ import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync, writeFileSync as writeFileSync3 } from "fs";
2885
3276
  import { homedir } from "os";
2886
- import { dirname as dirname5, isAbsolute as isAbsolute3, join as join6, relative as relative2, resolve as resolve5, sep } from "path";
3277
+ import { dirname as dirname5, isAbsolute as isAbsolute3, join as join7, relative as relative2, resolve as resolve5, sep } from "path";
2887
3278
  import { fileURLToPath } from "url";
2888
3279
 
2889
3280
  // src/skill-adapters.ts
@@ -2977,12 +3368,12 @@ function installSkill(options = {}) {
2977
3368
  plans.set(target, { target, content: content2, boundary, managedMerge });
2978
3369
  };
2979
3370
  const planSkillTree = (targetDir2, boundary = root) => {
2980
- for (const rel of files) plan(join6(targetDir2, rel), readFileSync5(join6(sourceDir, rel), "utf8"), false, boundary);
3371
+ for (const rel of files) plan(join7(targetDir2, rel), readFileSync7(join7(sourceDir, rel), "utf8"), false, boundary);
2981
3372
  };
2982
3373
  let targetDir;
2983
3374
  if (options.global) {
2984
- const claudeRoot = join6(home, ".claude", "skills");
2985
- const codexRoot = resolve5(options.codexHomeDir ?? process.env.CODEX_HOME ?? join6(home, ".codex"), "skills");
3375
+ const claudeRoot = join7(home, ".claude", "skills");
3376
+ const codexRoot = resolve5(options.codexHomeDir ?? process.env.CODEX_HOME ?? join7(home, ".codex"), "skills");
2986
3377
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
2987
3378
  for (const harness of harnesses) {
2988
3379
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
@@ -2990,35 +3381,35 @@ function installSkill(options = {}) {
2990
3381
  rememberTarget(harness, skillRoot);
2991
3382
  }
2992
3383
  } else {
2993
- const sharedRoot = join6(root, ".agents", "skills");
3384
+ const sharedRoot = join7(root, ".agents", "skills");
2994
3385
  planSkillTree(sharedRoot);
2995
- const claudeRoot = join6(root, ".claude", "skills");
3386
+ const claudeRoot = join7(root, ".claude", "skills");
2996
3387
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
2997
3388
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
2998
3389
  if (harnesses.includes("claude")) {
2999
3390
  for (const skill of skillNames(files)) {
3000
- const canonical = readFileSync5(join6(sourceDir, skill, "SKILL.md"), "utf8");
3001
- plan(join6(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
3391
+ const canonical = readFileSync7(join7(sourceDir, skill, "SKILL.md"), "utf8");
3392
+ plan(join7(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
3002
3393
  }
3003
3394
  rememberTarget("claude", claudeRoot);
3004
3395
  }
3005
3396
  if (harnesses.includes("cursor")) {
3006
- const cursorRule = join6(root, ".cursor", "rules", "odla.mdc");
3397
+ const cursorRule = join7(root, ".cursor", "rules", "odla.mdc");
3007
3398
  plan(cursorRule, CURSOR_RULE);
3008
3399
  rememberTarget("cursor", cursorRule);
3009
3400
  }
3010
3401
  if (harnesses.includes("agents")) {
3011
- const agentsFile = join6(root, "AGENTS.md");
3402
+ const agentsFile = join7(root, "AGENTS.md");
3012
3403
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3013
3404
  rememberTarget("agents", agentsFile);
3014
3405
  }
3015
3406
  if (harnesses.includes("copilot")) {
3016
- const copilotFile = join6(root, ".github", "copilot-instructions.md");
3407
+ const copilotFile = join7(root, ".github", "copilot-instructions.md");
3017
3408
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3018
3409
  rememberTarget("copilot", copilotFile);
3019
3410
  }
3020
3411
  if (harnesses.includes("gemini")) {
3021
- const geminiFile = join6(root, "GEMINI.md");
3412
+ const geminiFile = join7(root, "GEMINI.md");
3022
3413
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3023
3414
  rememberTarget("gemini", geminiFile);
3024
3415
  }
@@ -3036,7 +3427,7 @@ function installSkill(options = {}) {
3036
3427
  writtenPaths.add(file.target);
3037
3428
  continue;
3038
3429
  }
3039
- const current = readFileSync5(file.target, "utf8");
3430
+ const current = readFileSync7(file.target, "utf8");
3040
3431
  if (current === file.content) {
3041
3432
  unchangedPaths.add(file.target);
3042
3433
  } else if (file.managedMerge || options.force) {
@@ -3053,7 +3444,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
3053
3444
  );
3054
3445
  }
3055
3446
  for (const file of plans.values()) {
3056
- if (!existsSync7(file.target) || readFileSync5(file.target, "utf8") !== file.content) {
3447
+ if (!existsSync7(file.target) || readFileSync7(file.target, "utf8") !== file.content) {
3057
3448
  mkdirSync3(dirname5(file.target), { recursive: true });
3058
3449
  writeFileSync3(file.target, file.content);
3059
3450
  }
@@ -3098,7 +3489,7 @@ function managedFileContent(path, block, force, boundary) {
3098
3489
  if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
3099
3490
  if (!existsSync7(path)) return `${block}
3100
3491
  `;
3101
- const current = readFileSync5(path, "utf8");
3492
+ const current = readFileSync7(path, "utf8");
3102
3493
  const start = "<!-- odla-ai agent setup:start -->";
3103
3494
  const end = "<!-- odla-ai agent setup:end -->";
3104
3495
  const startAt = current.indexOf(start);
@@ -3125,7 +3516,7 @@ function symlinkedComponent(boundary, target) {
3125
3516
  }
3126
3517
  let current = boundary;
3127
3518
  for (const part of rel.split(sep).filter(Boolean)) {
3128
- current = join6(current, part);
3519
+ current = join7(current, part);
3129
3520
  try {
3130
3521
  if (lstatSync(current).isSymbolicLink()) return current;
3131
3522
  } catch (error) {
@@ -3142,7 +3533,7 @@ function listFiles(dir) {
3142
3533
  const results = [];
3143
3534
  const walk = (current) => {
3144
3535
  for (const entry of readdirSync(current, { withFileTypes: true })) {
3145
- const path = join6(current, entry.name);
3536
+ const path = join7(current, entry.name);
3146
3537
  if (entry.isDirectory()) walk(path);
3147
3538
  else results.push(relative2(dir, path));
3148
3539
  }
@@ -3533,7 +3924,7 @@ import { spawn as spawn3 } from "child_process";
3533
3924
  import { createHash as createHash2 } from "crypto";
3534
3925
  import { copyFile, mkdtemp, readFile, rm, writeFile } from "fs/promises";
3535
3926
  import { tmpdir } from "os";
3536
- import { join as join7 } from "path";
3927
+ import { join as join8 } from "path";
3537
3928
  import { fileURLToPath as fileURLToPath2 } from "url";
3538
3929
  var runCodeImageCommand = (command, args, stdio) => new Promise((accept, reject) => {
3539
3930
  const child = spawn3(command, [...args], { shell: false, stdio });
@@ -3592,10 +3983,10 @@ async function embeddedPiImageName() {
3592
3983
  return `odla-ai/pi-agent:embedded-sha256-${createHash2("sha256").update(bundle).digest("hex")}`;
3593
3984
  }
3594
3985
  async function buildEmbeddedPiImage(engine, image, run) {
3595
- const context = await mkdtemp(join7(tmpdir(), "odla-code-pi-"));
3986
+ const context = await mkdtemp(join8(tmpdir(), "odla-code-pi-"));
3596
3987
  try {
3597
- await copyFile(embeddedPiAssetPath(), join7(context, "pi-agent.js"));
3598
- await writeFile(join7(context, "Dockerfile"), [
3988
+ await copyFile(embeddedPiAssetPath(), join8(context, "pi-agent.js"));
3989
+ await writeFile(join8(context, "Dockerfile"), [
3599
3990
  `FROM ${CODE_NODE_IMAGE}`,
3600
3991
  "COPY pi-agent.js /opt/odla/pi-agent.js",
3601
3992
  "WORKDIR /workspace",
@@ -3621,7 +4012,7 @@ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
3621
4012
  var HarnessProtocolError = class extends Error {
3622
4013
  name = "HarnessProtocolError";
3623
4014
  };
3624
- function record2(value2) {
4015
+ function record4(value2) {
3625
4016
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
3626
4017
  }
3627
4018
  function boundedText(value2, label, max) {
@@ -3638,7 +4029,7 @@ function parseAgentOutput(line) {
3638
4029
  } catch {
3639
4030
  throw new HarnessProtocolError("agent emitted invalid JSON");
3640
4031
  }
3641
- const message2 = record2(value2);
4032
+ const message2 = record4(value2);
3642
4033
  if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
3643
4034
  throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
3644
4035
  }
@@ -3651,7 +4042,7 @@ function parseAgentOutput(line) {
3651
4042
  };
3652
4043
  }
3653
4044
  if (message2.type === "inference.request") {
3654
- const call2 = record2(message2.call);
4045
+ const call2 = record4(message2.call);
3655
4046
  if (!call2 || !Array.isArray(call2.messages) || !Number.isSafeInteger(call2.maxTokens)) {
3656
4047
  throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
3657
4048
  }
@@ -3663,7 +4054,7 @@ function parseAgentOutput(line) {
3663
4054
  };
3664
4055
  }
3665
4056
  if (message2.type === "tool.request") {
3666
- const input = record2(message2.input);
4057
+ const input = record4(message2.input);
3667
4058
  const tool = String(message2.tool);
3668
4059
  if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
3669
4060
  throw new HarnessProtocolError("tool.request requires a registered tool and object input");
@@ -3698,7 +4089,7 @@ function encodeAgentInput(message2) {
3698
4089
  import { execFile as execFile2, spawn as spawn4 } from "child_process";
3699
4090
  import { constants } from "fs";
3700
4091
  import { access } from "fs/promises";
3701
- import { delimiter, join as join8 } from "path";
4092
+ import { delimiter, join as join9 } from "path";
3702
4093
  import { getgid, getuid } from "process";
3703
4094
  import { mkdir, mkdtemp as mkdtemp2, realpath, rm as rm2, writeFile as writeFile2 } from "fs/promises";
3704
4095
  import { tmpdir as tmpdir2 } from "os";
@@ -3716,7 +4107,7 @@ function assertPinnedImage(image) {
3716
4107
  async function commandAvailable(engine) {
3717
4108
  for (const directory of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
3718
4109
  try {
3719
- await access(join8(directory, engine), constants.X_OK);
4110
+ await access(join9(directory, engine), constants.X_OK);
3720
4111
  return true;
3721
4112
  } catch {
3722
4113
  }
@@ -3847,8 +4238,8 @@ async function runContainerAttempt(options) {
3847
4238
  let stopped = false;
3848
4239
  let exited = false;
3849
4240
  child.stderr.setEncoding("utf8");
3850
- child.stderr.on("data", (text) => {
3851
- if (stderr.length < 64 * 1024) stderr += text.slice(0, 64 * 1024 - stderr.length);
4241
+ child.stderr.on("data", (text2) => {
4242
+ if (stderr.length < 64 * 1024) stderr += text2.slice(0, 64 * 1024 - stderr.length);
3852
4243
  });
3853
4244
  const stop = (reason) => {
3854
4245
  if (stopped || exited) return;
@@ -4006,8 +4397,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
4006
4397
  const maxFiles = options.maxFiles ?? 2e4;
4007
4398
  const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
4008
4399
  const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
4009
- const entries = inventory.flatMap((record8) => {
4010
- const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record8);
4400
+ const entries = inventory.flatMap((record11) => {
4401
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record11);
4011
4402
  return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
4012
4403
  });
4013
4404
  if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
@@ -4255,8 +4646,8 @@ function normalize(value2) {
4255
4646
  if (Array.isArray(value2)) return value2.map(normalize);
4256
4647
  if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
4257
4648
  if (typeof value2 === "object") {
4258
- const record8 = value2;
4259
- return Object.fromEntries(Object.keys(record8).filter((key) => record8[key] !== void 0).sort().map((key) => [key, normalize(record8[key])]));
4649
+ const record11 = value2;
4650
+ return Object.fromEntries(Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, normalize(record11[key])]));
4260
4651
  }
4261
4652
  throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
4262
4653
  }
@@ -4335,7 +4726,7 @@ function normalizeReaders(readers) {
4335
4726
  }
4336
4727
 
4337
4728
  // ../camel/dist/code.js
4338
- var DIGEST = /^sha256:[0-9a-f]{64}$/;
4729
+ var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
4339
4730
  var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
4340
4731
  var ID = /^[A-Za-z0-9._:-]{1,160}$/;
4341
4732
  async function digestCodeVerificationReceipt(fields) {
@@ -4370,18 +4761,18 @@ async function digestCodeVerificationReceipt(fields) {
4370
4761
  return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
4371
4762
  }
4372
4763
  function validate(fields) {
4373
- if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !DIGEST.test(fields.trustedBaseDigest) || !DIGEST.test(fields.patchDigest) || !DIGEST.test(fields.candidateDigest) || !DIGEST.test(fields.sourceDigest) || !DIGEST.test(fields.policyDigest) || !DIGEST.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) {
4764
+ 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
4765
  throw new CamelError("state_conflict", "Code verification receipt is malformed or outside its bounds.");
4375
4766
  }
4376
4767
  const ids = /* @__PURE__ */ new Set();
4377
4768
  for (const recipe2 of fields.recipes) {
4378
- if (typeof recipe2.recipeId !== "string" || !ID.test(recipe2.recipeId) || ids.has(recipe2.recipeId) || typeof recipe2.recipeDigest !== "string" || !DIGEST.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) {
4769
+ 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
4770
  throw new CamelError("state_conflict", "Code verification recipe receipt is malformed or outside its bounds.");
4380
4771
  }
4381
4772
  const artifactIds = /* @__PURE__ */ new Set();
4382
4773
  if (recipe2.artifacts.length > 64) throw new CamelError("state_conflict", "Code verification has too many artifacts.");
4383
4774
  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 && !DIGEST.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)) {
4775
+ 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
4776
  throw new CamelError("state_conflict", "Code verification artifact receipt is malformed.");
4386
4777
  }
4387
4778
  artifactIds.add(artifact.artifactId);
@@ -4397,7 +4788,7 @@ function validate(fields) {
4397
4788
  }
4398
4789
  }
4399
4790
  var SHA2 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
4400
- var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
4791
+ var DIGEST22 = /^sha256:[0-9a-f]{64}$/;
4401
4792
  var ID2 = /^[A-Za-z0-9._:-]{1,180}$/;
4402
4793
  var MAX_PATCH_BYTES = 256 * 1024;
4403
4794
  var MAX_STATE_BYTES = 64 * 1024;
@@ -4442,14 +4833,14 @@ function normalizeState(value2) {
4442
4833
  ]);
4443
4834
  const planCursor = state2.planCursor;
4444
4835
  const conversations = strings(state2.conversationRefs, "conversationRefs", ID2, 256, false);
4445
- const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals", DIGEST2, 256, true);
4446
- if (planCursor !== null && (typeof planCursor !== "string" || !ID2.test(planCursor)) || typeof state2.planningInputDigest !== "string" || !DIGEST2.test(state2.planningInputDigest) || typeof state2.buildPolicyDigest !== "string" || !DIGEST2.test(state2.buildPolicyDigest) || state2.dependencyLayerDigest !== null && (typeof state2.dependencyLayerDigest !== "string" || !DIGEST2.test(state2.dependencyLayerDigest)) || state2.verificationDigest !== null && (typeof state2.verificationDigest !== "string" || !DIGEST2.test(state2.verificationDigest)) || state2.reviewDigest !== null && (typeof state2.reviewDigest !== "string" || !DIGEST2.test(state2.reviewDigest)) || !["candidate_untrusted", "verified", "reviewed"].includes(String(state2.trustStatus)) || !Array.isArray(state2.completedEffects) || state2.completedEffects.length > 256) {
4836
+ const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals", DIGEST22, 256, true);
4837
+ 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
4838
  throw invalid2("Portable checkpoint state is malformed or outside its bounds.");
4448
4839
  }
4449
4840
  const effects = state2.completedEffects.map((item) => {
4450
4841
  const effect = object(item, "completed effect");
4451
4842
  exact2(effect, ["effectId", "actionDigest", "receiptDigest"]);
4452
- if (typeof effect.effectId !== "string" || !ID2.test(effect.effectId) || typeof effect.actionDigest !== "string" || !DIGEST2.test(effect.actionDigest) || typeof effect.receiptDigest !== "string" || !DIGEST2.test(effect.receiptDigest)) {
4843
+ 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
4844
  throw invalid2("Portable checkpoint effect receipt is malformed.");
4454
4845
  }
4455
4846
  return {
@@ -4558,7 +4949,7 @@ import { randomUUID } from "crypto";
4558
4949
  import { createHash as createHash22, randomUUID as randomUUID2 } from "crypto";
4559
4950
  import { createReadStream } from "fs";
4560
4951
  import { lstat as lstat22 } from "fs/promises";
4561
- import { join as join9 } from "path";
4952
+ import { join as join10 } from "path";
4562
4953
  import { mkdir as mkdir3, mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
4563
4954
  import { tmpdir as tmpdir3 } from "os";
4564
4955
  import { dirname as dirname6, join as join23, resolve as resolve32, sep as sep23 } from "path";
@@ -4680,8 +5071,8 @@ function boundedInteger(value2, spec) {
4680
5071
  }
4681
5072
  function boundedNumber(value2, spec) {
4682
5073
  if (spec.kind !== "finite_number" || typeof value2 !== "number" || !Number.isFinite(value2) || value2 < spec.minimum || value2 > spec.maximum) throw new CamelError("conversion_rejected", "Finite-number conversion rejected the structured value.");
4683
- const text = String(value2);
4684
- if (/e/i.test(text) || (text.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
5074
+ const text2 = String(value2);
5075
+ if (/e/i.test(text2) || (text2.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
4685
5076
  return value2;
4686
5077
  }
4687
5078
  function enumMember(value2, spec) {
@@ -4837,8 +5228,8 @@ function validateUnsafeSelector(path, value2, tool) {
4837
5228
  return void 0;
4838
5229
  }
4839
5230
  function looksLikeDestination(value2) {
4840
- const text = value2.trim();
4841
- return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
5231
+ const text2 = value2.trim();
5232
+ return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text2) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text2);
4842
5233
  }
4843
5234
 
4844
5235
  // ../harness/dist/chunk-GMVZ4LZH.js
@@ -4979,7 +5370,7 @@ function createCodeRuntimeControlClient(options) {
4979
5370
  }
4980
5371
  const value2 = await response2.json().catch(() => null);
4981
5372
  if (!response2.ok) {
4982
- const problem = record3(record3(value2)?.error);
5373
+ const problem = record5(record5(value2)?.error);
4983
5374
  throw new CodeRuntimeControlError(
4984
5375
  typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
4985
5376
  response2.status,
@@ -5001,12 +5392,12 @@ function createCodeRuntimeControlClient(options) {
5001
5392
  await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
5002
5393
  ),
5003
5394
  infer: async (sessionId, inference) => {
5004
- const value2 = record3(await call2(
5395
+ const value2 = record5(await call2(
5005
5396
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
5006
5397
  inference,
5007
5398
  modelRequestTimeoutMs
5008
5399
  ));
5009
- if (!value2 || value2.requestId !== inference.requestId || !record3(value2.response) || !record3(value2.receipt)) {
5400
+ if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
5010
5401
  throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
5011
5402
  }
5012
5403
  return value2;
@@ -5064,12 +5455,12 @@ function validateHeartbeat(version, capabilities) {
5064
5455
  }
5065
5456
  }
5066
5457
  function parseSnapshot(value2) {
5067
- const root = record3(value2);
5068
- const host = record3(root?.host);
5458
+ const root = record5(value2);
5459
+ const host = record5(root?.host);
5069
5460
  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
5461
  const bindingIds = /* @__PURE__ */ new Set();
5071
5462
  const bindings = root.bindings.map((item) => {
5072
- const binding = record3(item);
5463
+ const binding = record5(item);
5073
5464
  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
5465
  throw invalid("binding");
5075
5466
  }
@@ -5079,10 +5470,10 @@ function parseSnapshot(value2) {
5079
5470
  const commandIds = /* @__PURE__ */ new Set();
5080
5471
  const commandSequences = /* @__PURE__ */ new Set();
5081
5472
  const commands = root.commands.map((item) => {
5082
- const command = record3(item);
5473
+ const command = record5(item);
5083
5474
  const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
5084
5475
  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)) || !record3(command.payload) || !Number.isSafeInteger(command.createdAt)) throw invalid("command");
5476
+ 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
5477
  commandIds.add(command.commandId);
5087
5478
  commandSequences.add(sequenceKey);
5088
5479
  return command;
@@ -5090,10 +5481,10 @@ function parseSnapshot(value2) {
5090
5481
  return { host, bindings, commands };
5091
5482
  }
5092
5483
  async function parseSource(value2) {
5093
- const snapshot = record3(record3(value2)?.snapshot);
5484
+ const snapshot = record5(record5(value2)?.snapshot);
5094
5485
  if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
5095
5486
  const files = snapshot.files.map((value22) => {
5096
- const file = record3(value22);
5487
+ const file = record5(value22);
5097
5488
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
5098
5489
  return { path: file.path, content: file.content };
5099
5490
  });
@@ -5102,11 +5493,11 @@ async function parseSource(value2) {
5102
5493
  const aliases = /* @__PURE__ */ new Set();
5103
5494
  const references = [];
5104
5495
  for (const item of referencesValue) {
5105
- const reference = record3(item);
5496
+ const reference = record5(item);
5106
5497
  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
5498
  aliases.add(reference.alias);
5108
5499
  const referenceFiles = reference.files.map((entry) => {
5109
- const file = record3(entry);
5500
+ const file = record5(entry);
5110
5501
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
5111
5502
  return { path: file.path, content: file.content };
5112
5503
  });
@@ -5121,18 +5512,18 @@ async function parseSource(value2) {
5121
5512
  return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
5122
5513
  }
5123
5514
  function parseReview(value2) {
5124
- const review = record3(record3(value2)?.review);
5515
+ const review = record5(record5(value2)?.review);
5125
5516
  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
5517
  return review;
5127
5518
  }
5128
5519
  function parseCandidate(value2) {
5129
- const candidate = record3(record3(value2)?.candidate);
5520
+ const candidate = record5(record5(value2)?.candidate);
5130
5521
  if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
5131
5522
  throw invalid("candidate");
5132
5523
  }
5133
5524
  return { candidateId: candidate.candidateId, status: candidate.status };
5134
5525
  }
5135
- var record3 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
5526
+ var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
5136
5527
  var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
5137
5528
  var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
5138
5529
  var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
@@ -5207,8 +5598,8 @@ function gitApply(cwd, patch2, check) {
5207
5598
  });
5208
5599
  let stderr = "";
5209
5600
  child.stderr.setEncoding("utf8");
5210
- child.stderr.on("data", (text) => {
5211
- if (stderr.length < 4e3) stderr += text.slice(0, 4e3);
5601
+ child.stderr.on("data", (text2) => {
5602
+ if (stderr.length < 4e3) stderr += text2.slice(0, 4e3);
5212
5603
  });
5213
5604
  child.once("error", reject);
5214
5605
  child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(`patch did not apply: ${stderr.trim().slice(0, 500)}`)));
@@ -5477,7 +5868,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
5477
5868
  const receipts = [];
5478
5869
  for (const artifact of recipe2.expectedArtifacts ?? []) {
5479
5870
  try {
5480
- const path = join9(workspaceDir, artifact.path);
5871
+ const path = join10(workspaceDir, artifact.path);
5481
5872
  const info = await lstat22(path);
5482
5873
  if (!info.isFile() || info.isSymbolicLink()) {
5483
5874
  receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
@@ -6379,10 +6770,10 @@ var CodePiRuntimeEngine = class {
6379
6770
  task: lease.task,
6380
6771
  limits: this.options.limits,
6381
6772
  signal: active.abort.signal,
6382
- onStderr: (text) => this.#event(command, {
6773
+ onStderr: (text2) => this.#event(command, {
6383
6774
  type: "message",
6384
6775
  actor: "system",
6385
- body: text.slice(0, 4e3)
6776
+ body: text2.slice(0, 4e3)
6386
6777
  }, active.conversationRefs),
6387
6778
  onMessage: async (output) => {
6388
6779
  if (output.type === "inference.request") {
@@ -6495,13 +6886,6 @@ var CodePiRuntimeEngine = class {
6495
6886
  }
6496
6887
  };
6497
6888
 
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
6889
  // src/code-local-source.ts
6506
6890
  import { execFile as execFile3 } from "child_process";
6507
6891
  import { createHash as createHash4 } from "crypto";
@@ -6720,25 +7104,25 @@ async function runCodeRuntime(input) {
6720
7104
  }
6721
7105
  }
6722
7106
  function parseConnection(value2, appId, appEnv) {
6723
- const root = record4(value2);
6724
- const host = record4(root?.host);
6725
- const offer = record4(root?.offer);
6726
- const binding = record4(root?.binding);
7107
+ const root = record6(value2);
7108
+ const host = record6(root?.host);
7109
+ const offer = record6(root?.offer);
7110
+ const binding = record6(root?.binding);
6727
7111
  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
7112
  throw new Error("connect Code host returned an invalid response");
6729
7113
  }
6730
7114
  return root;
6731
7115
  }
6732
7116
  function apiFailure(action2, status, value2) {
6733
- const message2 = record4(record4(value2)?.error)?.message;
7117
+ const message2 = record6(record6(value2)?.error)?.message;
6734
7118
  return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
6735
7119
  }
6736
- function record4(value2) {
7120
+ function record6(value2) {
6737
7121
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
6738
7122
  }
6739
7123
 
6740
7124
  // src/provision.ts
6741
- import { createAppsClient as createAppsClient2, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
7125
+ import { createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
6742
7126
  import { putSecret as putSecret2 } from "@odla-ai/ai";
6743
7127
  import process8 from "process";
6744
7128
 
@@ -6959,7 +7343,7 @@ async function provision(options) {
6959
7343
  }
6960
7344
  const doFetch = options.fetch ?? fetch;
6961
7345
  const token = await getDeveloperToken(cfg, options, doFetch, out);
6962
- const apps = createAppsClient2({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
7346
+ const apps = createAppsClient3({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
6963
7347
  const existing = await apps.resolveApp(cfg.app.id);
6964
7348
  if (existing) {
6965
7349
  out.log(`app: ${cfg.app.id} already exists`);
@@ -7133,7 +7517,7 @@ var COMMAND_SURFACE = {
7133
7517
  calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
7134
7518
  capabilities: {},
7135
7519
  code: { connect: {} },
7136
- config: { diff: {}, plan: {} },
7520
+ config: { diff: {}, plan: {}, apply: {} },
7137
7521
  context: { show: {}, list: {}, save: {}, remove: {} },
7138
7522
  // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
7139
7523
  discuss: {
@@ -7151,7 +7535,11 @@ var COMMAND_SURFACE = {
7151
7535
  help: {},
7152
7536
  init: {},
7153
7537
  o11y: { status: {} },
7154
- platform: { status: {} },
7538
+ operations: { get: {}, wait: {} },
7539
+ platform: {
7540
+ status: {},
7541
+ "chat-credentials": { rotate: {} }
7542
+ },
7155
7543
  pm: {
7156
7544
  ...PM_ENTITIES,
7157
7545
  handoff: {}
@@ -7588,17 +7976,17 @@ function addOption(options, name, value2) {
7588
7976
 
7589
7977
  // src/operator-context.ts
7590
7978
  import { existsSync as existsSync10 } from "fs";
7591
- import { join as join11, resolve as resolve11 } from "path";
7979
+ import { join as join12, resolve as resolve11 } from "path";
7592
7980
  import process10 from "process";
7593
7981
 
7594
7982
  // src/operator-profiles.ts
7595
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
7983
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
7596
7984
  import { homedir as homedir2 } from "os";
7597
- import { dirname as dirname7, join as join10, resolve as resolve10 } from "path";
7985
+ import { dirname as dirname7, join as join11, resolve as resolve10 } from "path";
7598
7986
  import process9 from "process";
7599
7987
  function operatorProfileFile() {
7600
7988
  return resolve10(
7601
- clean(process9.env.ODLA_CONTEXT_FILE) ?? join10(homedir2(), ".odla", "contexts.json")
7989
+ clean(process9.env.ODLA_CONTEXT_FILE) ?? join11(homedir2(), ".odla", "contexts.json")
7602
7990
  );
7603
7991
  }
7604
7992
  function resolveOperatorProfile(parsed) {
@@ -7643,10 +8031,10 @@ function removeOperatorProfile(name, file = operatorProfileFile()) {
7643
8031
  return true;
7644
8032
  }
7645
8033
  function operatorCredentialFiles(selection) {
7646
- const base = selection.name ? join10(dirname7(selection.file), "profiles", selection.name) : join10(homedir2(), ".odla");
8034
+ const base = selection.name ? join11(dirname7(selection.file), "profiles", selection.name) : join11(homedir2(), ".odla");
7647
8035
  return {
7648
- developer: join10(base, "dev-token.json"),
7649
- scoped: join10(base, "admin-token.local.json")
8036
+ developer: join11(base, "dev-token.json"),
8037
+ scoped: join11(base, "admin-token.local.json")
7650
8038
  };
7651
8039
  }
7652
8040
  function assertOperatorName(value2, label) {
@@ -7660,7 +8048,7 @@ function readOperatorProfiles(file) {
7660
8048
  if (!existsSync9(file)) return emptyProfiles();
7661
8049
  let raw;
7662
8050
  try {
7663
- raw = JSON.parse(readFileSync7(file, "utf8"));
8051
+ raw = JSON.parse(readFileSync8(file, "utf8"));
7664
8052
  } catch {
7665
8053
  throw new Error(`operator context file ${file} is not valid JSON`);
7666
8054
  }
@@ -7757,7 +8145,7 @@ async function resolveOperatorContext(parsed, options = {}) {
7757
8145
  const rootDir = loaded?.rootDir ?? process10.cwd();
7758
8146
  const profileCredentials = operatorCredentialFiles(profile);
7759
8147
  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 ? join11(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
8148
+ 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
8149
  const cfg = loaded ? {
7762
8150
  ...loaded,
7763
8151
  platformUrl: platformValue,
@@ -7779,8 +8167,8 @@ async function resolveOperatorContext(parsed, options = {}) {
7779
8167
  services: [],
7780
8168
  local: {
7781
8169
  tokenFile,
7782
- credentialsFile: join11(rootDir, ".odla", "credentials.local.json"),
7783
- devVarsFile: join11(rootDir, ".dev.vars"),
8170
+ credentialsFile: join12(rootDir, ".odla", "credentials.local.json"),
8171
+ devVarsFile: join12(rootDir, ".dev.vars"),
7784
8172
  gitignore: true
7785
8173
  }
7786
8174
  };
@@ -8024,7 +8412,7 @@ async function appExport(options) {
8024
8412
  }
8025
8413
 
8026
8414
  // src/app-import.ts
8027
- import { readFileSync as readFileSync8 } from "fs";
8415
+ import { readFileSync as readFileSync9 } from "fs";
8028
8416
  import {
8029
8417
  buildImportOps,
8030
8418
  importMutationId,
@@ -8048,8 +8436,8 @@ async function appImport(options) {
8048
8436
  const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
8049
8437
  const doFetch = options.fetch ?? fetch;
8050
8438
  const { tenant } = resolveTenant(cfg, options.env);
8051
- const text = options.file === "-" ? (options.readStdin ?? (() => readFileSync8(0, "utf8")))() : readFileSync8(options.file, "utf8");
8052
- const { format, sources } = parseImport(text, options.ns);
8439
+ const text2 = options.file === "-" ? (options.readStdin ?? (() => readFileSync9(0, "utf8")))() : readFileSync9(options.file, "utf8");
8440
+ const { format, sources } = parseImport(text2, options.ns);
8053
8441
  if (format === "namespace-map" && options.ns) {
8054
8442
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
8055
8443
  }
@@ -8140,7 +8528,10 @@ function report(options, owners, headline) {
8140
8528
  if (headline) out.log(headline);
8141
8529
  out.log(`owners (${owners.length}):`);
8142
8530
  for (const o of owners) {
8143
- out.log(` ${o.primary ? "\u2605" : "\xB7"} ${o.email ?? o.ownerId}${o.primary ? " (primary)" : ""}`);
8531
+ const name = o.email?.trim() || "Unnamed member";
8532
+ out.log(
8533
+ ` ${o.primary ? "\u2605" : "\xB7"} ${name} [${o.ownerId}]${o.primary ? " (primary)" : ""}`
8534
+ );
8144
8535
  }
8145
8536
  }
8146
8537
  async function ownersList(options) {
@@ -8559,10 +8950,14 @@ async function secretsCommand(parsed, deps) {
8559
8950
  async function projectCommand(command, parsed, deps) {
8560
8951
  if (command === "config") {
8561
8952
  const sub = parsed.positionals[1];
8562
- if (sub !== "diff" && sub !== "plan") {
8953
+ if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
8563
8954
  throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
8564
8955
  }
8565
- assertArgs(parsed, ["config", "token", "email", "open", "json"], 2);
8956
+ assertArgs(
8957
+ parsed,
8958
+ sub === "apply" ? ["config", "plan", "idempotency-key", "token", "email", "open", "json"] : ["config", "token", "email", "open", "json"],
8959
+ 2
8960
+ );
8566
8961
  const options = {
8567
8962
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
8568
8963
  token: stringOpt(parsed.options.token),
@@ -8574,7 +8969,42 @@ async function projectCommand(command, parsed, deps) {
8574
8969
  stdout: deps.stdout
8575
8970
  };
8576
8971
  if (sub === "diff") await configDiff(options);
8577
- else await configPlan(options);
8972
+ else if (sub === "plan") await configPlan(options);
8973
+ else await configApply({
8974
+ ...options,
8975
+ planPath: requiredString(parsed.options.plan, "--plan"),
8976
+ idempotencyKey: stringOpt(parsed.options["idempotency-key"])
8977
+ });
8978
+ return true;
8979
+ }
8980
+ if (command === "operations") {
8981
+ const sub = parsed.positionals[1];
8982
+ if (sub !== "get" && sub !== "wait") {
8983
+ throw new Error(`unknown operations subcommand "${sub ?? ""}". Try "odla-ai operations get <operation-id> --json".`);
8984
+ }
8985
+ assertArgs(
8986
+ parsed,
8987
+ sub === "wait" ? ["config", "token", "email", "open", "json", "interval", "timeout"] : ["config", "token", "email", "open", "json"],
8988
+ 3
8989
+ );
8990
+ const operationId = parsed.positionals[2];
8991
+ if (!operationId) throw new Error(`operation id is required`);
8992
+ const options = {
8993
+ configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
8994
+ operationId,
8995
+ token: stringOpt(parsed.options.token),
8996
+ email: stringOpt(parsed.options.email),
8997
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
8998
+ json: parsed.options.json === true,
8999
+ fetch: deps.fetch,
9000
+ openApprovalUrl: deps.openUrl,
9001
+ stdout: deps.stdout,
9002
+ pollWait: deps.pollWait,
9003
+ intervalSeconds: numberOpt(parsed.options.interval, "--interval"),
9004
+ timeoutSeconds: numberOpt(parsed.options.timeout, "--timeout")
9005
+ };
9006
+ if (sub === "get") await configOperationGet(options);
9007
+ else await configOperationWait(options);
8578
9008
  return true;
8579
9009
  }
8580
9010
  if (command === "init") {
@@ -8845,8 +9275,10 @@ Usage:
8845
9275
  odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
8846
9276
  odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
8847
9277
  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 [--config odla.config.mjs] [--email <odla-account>] [--json]
9278
+ odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
9279
+ odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
9280
+ odla-ai operations get <operation-id> [--json]
9281
+ odla-ai operations wait <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
8850
9282
  odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
8851
9283
  odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
8852
9284
  odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
@@ -8897,6 +9329,7 @@ Usage:
8897
9329
  odla-ai context remove <name> --yes [--json]
8898
9330
  odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
8899
9331
  odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
9332
+ odla-ai platform chat-credentials rotate [--context <name>] [--email <odla-account>] [--wrangler-config <path>] [--expected-version <id>] [--json] --yes
8900
9333
  odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
8901
9334
  odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
8902
9335
  odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
@@ -8960,9 +9393,9 @@ Commands:
8960
9393
  does and what it guarantees is JSDoc, rendered per package at
8961
9394
  https://odla.ai/docs and shipped in the installed .d.ts. Answering
8962
9395
  a question usually needs both.
8963
- whoami Report who this terminal is authenticated as, and whether it holds
8964
- platform admin. A handshake-minted device token is never admin,
8965
- however the human who approved it is configured.
9396
+ whoami Report the actual principal name, handle and kind, its manager,
9397
+ credential kind, accountable owner, and platform-admin status.
9398
+ A manager relationship never supplies authority by itself.
8966
9399
  context Explain selected config, platform, app, environment, and
8967
9400
  developer-token provenance without printing credentials or
8968
9401
  starting a device handshake. Operator work can run outside a
@@ -8971,8 +9404,8 @@ Commands:
8971
9404
  setup Install offline odla runbooks for common coding-agent harnesses.
8972
9405
  init Create a generic odla.config.mjs plus starter schema/rules files.
8973
9406
  doctor Validate and summarize the project config without network calls.
8974
- config Read-only diff and revision-bound plan for checked-in Registry
8975
- intent; runtime-owned fields and excluded coverage stay explicit.
9407
+ config Diff Registry intent, freeze a CAS-bound plan, and conditionally apply its safe actions.
9408
+ operations Inspect or wait on one exact, durable config-operation receipt.
8976
9409
  calendar Inspect, connect, or disconnect the live Google booking connection.
8977
9410
  app Archive (suspend, data retained), restore, export, import, or
8978
9411
  manage the co-owners of the app. Archiving takes every
@@ -9082,6 +9515,58 @@ Safety:
9082
9515
  `);
9083
9516
  }
9084
9517
 
9518
+ // src/discuss-principals.ts
9519
+ function mergeDiscussPrincipals(target, source) {
9520
+ Object.assign(target.authors, source.authors ?? {});
9521
+ Object.assign(target.principals, source.principals ?? {});
9522
+ }
9523
+ var withAt = (handle) => handle.startsWith("@") ? handle : `@${handle}`;
9524
+ function discussPrincipalLabel(principalId, authorKind, projection) {
9525
+ const profile = projection.principals?.[principalId];
9526
+ if (profile) {
9527
+ const identity = `${profile.displayName} (${withAt(profile.handle)})`;
9528
+ if (profile.kind === "agent") {
9529
+ return profile.managerDisplayName ? `${identity} \u2014 agent managed by ${profile.managerDisplayName}` : `${identity} \u2014 agent`;
9530
+ }
9531
+ return profile.kind === "service" ? `${identity} \u2014 service` : identity;
9532
+ }
9533
+ const legacyName = projection.authors?.[principalId]?.trim();
9534
+ if (legacyName) {
9535
+ return authorKind === "bot" ? `${legacyName} \u2014 agent` : legacyName;
9536
+ }
9537
+ return authorKind === "bot" ? "Unnamed agent" : "Unnamed member";
9538
+ }
9539
+
9540
+ // src/discuss-read-render.ts
9541
+ function discussBodyWithRefs(post) {
9542
+ if (!post.refs || post.refs.length === 0) return post.body;
9543
+ let out = "";
9544
+ let cursor = 0;
9545
+ for (const ref of [...post.refs].sort((a, b) => a.start - b.start)) {
9546
+ if (ref.start < cursor || ref.end > post.body.length) continue;
9547
+ out += post.body.slice(cursor, ref.start) + `@[${ref.label}](${ref.kind}/${ref.id})`;
9548
+ cursor = ref.end;
9549
+ }
9550
+ return out + post.body.slice(cursor);
9551
+ }
9552
+ function renderDiscussRead(ctx, topic, posts, projection) {
9553
+ const status = topic.resolved ? "resolved" : "open";
9554
+ ctx.out.log(`${topic.subject} [${status}] ${topic.appId ?? ""}`);
9555
+ for (const post of posts) {
9556
+ const who = discussPrincipalLabel(
9557
+ post.authorId,
9558
+ post.authorKind,
9559
+ projection
9560
+ );
9561
+ ctx.out.log(`
9562
+ \u2014 ${who}`);
9563
+ ctx.out.log(discussBodyWithRefs(post));
9564
+ for (const file of post.attachments ?? []) {
9565
+ ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
9566
+ }
9567
+ }
9568
+ }
9569
+
9085
9570
  // src/discuss-actions.ts
9086
9571
  var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
9087
9572
  async function request(ctx, method, path, body) {
@@ -9100,17 +9585,6 @@ function emit(ctx, value2, human) {
9100
9585
  else human();
9101
9586
  }
9102
9587
  var state = (topic) => topic.resolved ? "resolved" : "open";
9103
- function bodyWithRefs(post) {
9104
- if (!post.refs || post.refs.length === 0) return post.body;
9105
- let out = "";
9106
- let cursor = 0;
9107
- for (const ref of [...post.refs].sort((a, b) => a.start - b.start)) {
9108
- if (ref.start < cursor || ref.end > post.body.length) continue;
9109
- out += post.body.slice(cursor, ref.start) + `@[${ref.label}](${ref.kind}/${ref.id})`;
9110
- cursor = ref.end;
9111
- }
9112
- return out + post.body.slice(cursor);
9113
- }
9114
9588
  function content(parsed) {
9115
9589
  const markup = stringOpt(parsed.options.markup);
9116
9590
  if (markup) return { markup };
@@ -9165,11 +9639,19 @@ async function discussRead(ctx, id, parsed) {
9165
9639
  offset: requestedOffset ?? "0"
9166
9640
  });
9167
9641
  const page = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
9168
- emit(ctx, page, () => renderRead(ctx, page.topic, page.posts));
9642
+ emit(
9643
+ ctx,
9644
+ page,
9645
+ () => renderDiscussRead(ctx, page.topic, page.posts, page)
9646
+ );
9169
9647
  return;
9170
9648
  }
9171
9649
  for (let scan = 0; scan < 3; scan++) {
9172
9650
  const posts = /* @__PURE__ */ new Map();
9651
+ const projection = {
9652
+ authors: {},
9653
+ principals: {}
9654
+ };
9173
9655
  let topic = null;
9174
9656
  let offset = 0;
9175
9657
  for (; ; ) {
@@ -9180,6 +9662,7 @@ async function discussRead(ctx, id, parsed) {
9180
9662
  );
9181
9663
  topic = page.topic;
9182
9664
  for (const post of page.posts) posts.set(post.id, post);
9665
+ mergeDiscussPrincipals(projection, page);
9183
9666
  if (posts.size > 1e4) throw new Error("discuss read failed: conversation exceeds 10000 posts");
9184
9667
  if (!page.page?.hasMore) break;
9185
9668
  if (page.page.nextOffset === null || page.page.nextOffset <= offset) {
@@ -9192,24 +9675,18 @@ async function discussRead(ctx, id, parsed) {
9192
9675
  );
9193
9676
  const expected = Number.isInteger(topic.replyCount) ? topic.replyCount + 1 : ordered.length;
9194
9677
  if (ordered.length === expected) {
9195
- const result = { topic, posts: ordered };
9196
- emit(ctx, result, () => renderRead(ctx, result.topic, result.posts));
9678
+ const result = { topic, posts: ordered, ...projection };
9679
+ emit(
9680
+ ctx,
9681
+ result,
9682
+ () => renderDiscussRead(ctx, result.topic, result.posts, result)
9683
+ );
9197
9684
  return;
9198
9685
  }
9199
9686
  if (offset === 0) throw new Error("discuss read failed: registry did not provide forward post pages");
9200
9687
  }
9201
9688
  throw new Error("discuss read failed: conversation changed during every complete-read attempt");
9202
9689
  }
9203
- function renderRead(ctx, topic, posts) {
9204
- ctx.out.log(`${topic.subject} [${state(topic)}] ${topic.appId ?? ""}`);
9205
- for (const post of posts) {
9206
- const who = post.authorKind === "bot" ? `${post.authorId} (agent)` : post.authorId;
9207
- ctx.out.log(`
9208
- \u2014 ${who}`);
9209
- ctx.out.log(bodyWithRefs(post));
9210
- for (const file of post.attachments ?? []) ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
9211
- }
9212
- }
9213
9690
  async function discussPost(ctx, parsed) {
9214
9691
  const appId = stringOpt(parsed.options.app) ?? ctx.appId;
9215
9692
  if (!appId) throw new Error("discuss post needs --app <appId>");
@@ -9434,6 +9911,8 @@ async function discussWatch(ctx, topicId, parsed) {
9434
9911
  found: true,
9435
9912
  cursor,
9436
9913
  events: matching,
9914
+ ...page.authors ? { authors: page.authors } : {},
9915
+ ...page.principals ? { principals: page.principals } : {},
9437
9916
  ...posts && posts.length > 0 ? { posts } : {},
9438
9917
  ...topics && topics.length > 0 ? { topics } : {}
9439
9918
  });
@@ -9466,7 +9945,11 @@ function report2(ctx, parsed, result) {
9466
9945
  ctx.out.log(JSON.stringify(result, null, 2));
9467
9946
  } else if (parsed.options.jsonl !== true && result.found) {
9468
9947
  for (const post of result.posts ?? []) {
9469
- const who = post.authorKind === "bot" ? `${post.authorId} (agent)` : post.authorId;
9948
+ const who = discussPrincipalLabel(
9949
+ post.authorId,
9950
+ post.authorKind,
9951
+ result
9952
+ );
9470
9953
  ctx.out.log(`\u2014 ${who}
9471
9954
  ${post.body}`);
9472
9955
  }
@@ -9611,8 +10094,8 @@ function collectFields(parsed, allowClear) {
9611
10094
  if (allowClear) out[spec.key] = null;
9612
10095
  continue;
9613
10096
  }
9614
- const text = stringOpt(value2);
9615
- out[spec.key] = spec.num ? Number(text) : text;
10097
+ const text2 = stringOpt(value2);
10098
+ out[spec.key] = spec.num ? Number(text2) : text2;
9616
10099
  }
9617
10100
  return out;
9618
10101
  }
@@ -9679,8 +10162,8 @@ async function pmAdd(ctx, entity, parsed) {
9679
10162
  emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
9680
10163
  }
9681
10164
  async function pmGet(ctx, entity, id) {
9682
- const { record: record8 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
9683
- emit2(ctx, record8, () => printRecord(ctx, entity, record8));
10165
+ const { record: record11 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
10166
+ emit2(ctx, record11, () => printRecord(ctx, entity, record11));
9684
10167
  }
9685
10168
  async function pmSet(ctx, entity, id, parsed) {
9686
10169
  const patch2 = collectEntityFields(entity, parsed, true);
@@ -9725,9 +10208,9 @@ async function pmHandoff(ctx, parsed) {
9725
10208
  ]);
9726
10209
  const handoff = {
9727
10210
  appId,
9728
- unmetGoals: goals.filter((record8) => record8.status !== "met"),
9729
- activeTasks: tasks.filter((record8) => record8.column !== "done"),
9730
- openBugs: bugs.filter((record8) => record8.status !== "fixed" && record8.status !== "wontfix")
10211
+ unmetGoals: goals.filter((record11) => record11.status !== "met"),
10212
+ activeTasks: tasks.filter((record11) => record11.column !== "done"),
10213
+ openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
9731
10214
  };
9732
10215
  const result = {
9733
10216
  ...handoff,
@@ -9746,10 +10229,10 @@ async function pmHandoff(ctx, parsed) {
9746
10229
  ]) {
9747
10230
  ctx.out.log(`${label}:`);
9748
10231
  if (!records.length) ctx.out.log("- (none)");
9749
- else for (const record8 of records) printRecord(
10232
+ else for (const record11 of records) printRecord(
9750
10233
  ctx,
9751
10234
  label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
9752
- record8
10235
+ record11
9753
10236
  );
9754
10237
  }
9755
10238
  });
@@ -9940,19 +10423,180 @@ function age(input) {
9940
10423
  return `${Math.round(input / 6e4)}m`;
9941
10424
  }
9942
10425
 
10426
+ // src/platform-chat-credential-command.ts
10427
+ import process12 from "process";
10428
+ async function rotatePlatformChatCredential(parsed, deps) {
10429
+ assertArgs(
10430
+ parsed,
10431
+ [
10432
+ "config",
10433
+ "context",
10434
+ "platform",
10435
+ "token",
10436
+ "email",
10437
+ "open",
10438
+ "json",
10439
+ "yes",
10440
+ "wrangler-config",
10441
+ "expected-version"
10442
+ ],
10443
+ 3
10444
+ );
10445
+ if (parsed.options.yes !== true) {
10446
+ throw new Error(
10447
+ "platform chat-credentials rotate changes the production chat secret; pass --yes"
10448
+ );
10449
+ }
10450
+ const context = await resolveOperatorContext(parsed, {
10451
+ allowMissingConfig: true
10452
+ });
10453
+ const platform = context.platform.value;
10454
+ const doFetch = deps.fetch ?? fetch;
10455
+ const out = deps.stdout ?? console;
10456
+ const run = deps.runner ?? defaultRunner;
10457
+ const cwd = process12.cwd();
10458
+ const wranglerConfig = stringOpt(parsed.options["wrangler-config"]) ?? "packages/chat-agent/wrangler.jsonc";
10459
+ if (!await wranglerLoggedIn(run, cwd)) {
10460
+ throw new Error(
10461
+ 'Wrangler is not authenticated; run "npx wrangler login" and retry'
10462
+ );
10463
+ }
10464
+ const priorHealth = await doFetch(`${platform}/health/services/chat`);
10465
+ const priorVersion = priorHealth.headers.get("x-odla-worker-version-id");
10466
+ await priorHealth.body?.cancel().catch(() => {
10467
+ });
10468
+ if (!priorVersion) {
10469
+ throw new Error(
10470
+ "chat health did not identify its current Worker version; refusing to rotate"
10471
+ );
10472
+ }
10473
+ const expectedVersion = stringOpt(parsed.options["expected-version"]);
10474
+ if (expectedVersion && priorVersion !== expectedVersion) {
10475
+ throw new Error(
10476
+ `chat health answered from version ${priorVersion}; expected ${expectedVersion}`
10477
+ );
10478
+ }
10479
+ const token = await resolveAdminPlatformToken({
10480
+ platform,
10481
+ scope: "platform:chat:credential:write",
10482
+ token: stringOpt(parsed.options.token),
10483
+ tokenFile: context.credentials.scopedTokenFile,
10484
+ email: stringOpt(parsed.options.email),
10485
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
10486
+ fetch: doFetch,
10487
+ stdout: out,
10488
+ openApprovalUrl: deps.openUrl,
10489
+ label: "odla CLI (rotate built-in Discussion responder)"
10490
+ });
10491
+ const mintedResponse = await doFetch(
10492
+ `${platform}/registry/platform/chat-credentials/rotate`,
10493
+ {
10494
+ method: "POST",
10495
+ headers: { authorization: `Bearer ${token}` }
10496
+ }
10497
+ );
10498
+ const minted = await mintedResponse.json().catch(() => null);
10499
+ if (!mintedResponse.ok) {
10500
+ throw new Error(
10501
+ `mint Discussion credential failed (HTTP ${mintedResponse.status}): ${apiMessage(minted)}`
10502
+ );
10503
+ }
10504
+ if (!isPlatformChatCredential(minted)) {
10505
+ throw new Error(
10506
+ "platform returned an invalid odla.platform-chat-credential/v1 envelope"
10507
+ );
10508
+ }
10509
+ const put = await wranglerPutSecret(run, {
10510
+ name: "ODLA_BOT_TOKENS",
10511
+ value: JSON.stringify({
10512
+ [minted.appId]: { [minted.principalId]: minted.key }
10513
+ }),
10514
+ configPath: wranglerConfig,
10515
+ cwd
10516
+ });
10517
+ if (put.code !== 0) {
10518
+ throw new Error(
10519
+ "Wrangler could not replace ODLA_BOT_TOKENS; the new scoped key was not activated"
10520
+ );
10521
+ }
10522
+ const version = await waitForRotatedChatHealth({
10523
+ fetch: doFetch,
10524
+ platform,
10525
+ priorVersion,
10526
+ wait: deps.pollWait
10527
+ });
10528
+ const result = {
10529
+ schemaVersion: "odla.platform-chat-credential-rotation/v1",
10530
+ appId: minted.appId,
10531
+ appIncarnation: minted.appIncarnation,
10532
+ principalId: minted.principalId,
10533
+ health: { ok: true, service: "odla-chat-agent", version }
10534
+ };
10535
+ if (parsed.options.json === true) {
10536
+ out.log(JSON.stringify(result, null, 2));
10537
+ } else {
10538
+ out.log(
10539
+ `rotated ${result.appId} ${result.principalId} ${result.health.version}`
10540
+ );
10541
+ }
10542
+ }
10543
+ async function waitForRotatedChatHealth(opts) {
10544
+ const wait2 = opts.wait ?? ((milliseconds) => new Promise((resolve12) => setTimeout(resolve12, milliseconds)));
10545
+ let lastStatus = 0;
10546
+ let lastVersion = null;
10547
+ let lastError = "private_service_unready";
10548
+ for (let attempt = 0; attempt < 60; attempt++) {
10549
+ const response2 = await opts.fetch(
10550
+ `${opts.platform}/health/services/chat`
10551
+ );
10552
+ const body = await response2.json().catch(() => null);
10553
+ const version = response2.headers.get("x-odla-worker-version-id");
10554
+ if (response2.ok && record7(body) && body.ok === true && body.service === "odla-chat-agent" && version && version !== opts.priorVersion) {
10555
+ return version;
10556
+ }
10557
+ lastStatus = response2.status;
10558
+ lastVersion = version;
10559
+ lastError = record7(body) && typeof body.error === "string" ? body.error : "private_service_unready";
10560
+ if (attempt < 59) await wait2(5e3);
10561
+ }
10562
+ throw new Error(
10563
+ `chat health did not converge on the rotated Worker deployment (HTTP ${lastStatus}, version ${lastVersion ?? "unknown"}, ${lastError})`
10564
+ );
10565
+ }
10566
+ function isPlatformChatCredential(value2) {
10567
+ return record7(value2) && value2.schemaVersion === "odla.platform-chat-credential/v1" && value2.appId === "odla-pm" && typeof value2.appIncarnation === "string" && value2.appIncarnation.length > 0 && value2.principalId === "agent_odla" && typeof value2.key === "string" && value2.key.startsWith("odla_sk_");
10568
+ }
10569
+ function apiMessage(value2) {
10570
+ if (!record7(value2)) return "request failed";
10571
+ const error = record7(value2.error) ? value2.error : value2;
10572
+ return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
10573
+ }
10574
+ function record7(value2) {
10575
+ return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
10576
+ }
10577
+
9943
10578
  // src/platform-command.ts
9944
10579
  async function platformCommand(parsed, deps = {}) {
10580
+ const action2 = parsed.positionals[1];
10581
+ if (action2 === "status") {
10582
+ return platformStatus(parsed, deps);
10583
+ }
10584
+ if (action2 === "chat-credentials" && parsed.positionals[2] === "rotate") {
10585
+ return rotatePlatformChatCredential(parsed, deps);
10586
+ }
10587
+ throw new Error(
10588
+ `unknown platform action "${[
10589
+ action2,
10590
+ parsed.positionals[2]
10591
+ ].filter(Boolean).join(" ")}". Try "odla-ai platform status --json".`
10592
+ );
10593
+ }
10594
+ async function platformStatus(parsed, deps) {
9945
10595
  assertArgs(
9946
10596
  parsed,
9947
10597
  ["config", "context", "platform", "token", "email", "open", "json"],
9948
10598
  2
9949
10599
  );
9950
- const action2 = parsed.positionals[1];
9951
- if (action2 !== "status") {
9952
- throw new Error(
9953
- `unknown platform action "${action2 ?? ""}". Try "odla-ai platform status --json".`
9954
- );
9955
- }
9956
10600
  const context = await resolveOperatorContext(parsed, {
9957
10601
  allowMissingConfig: true
9958
10602
  });
@@ -9977,7 +10621,7 @@ async function platformCommand(parsed, deps = {}) {
9977
10621
  const body = await response2.json().catch(() => null);
9978
10622
  if (!response2.ok) {
9979
10623
  throw new Error(
9980
- `read platform status failed (HTTP ${response2.status}): ${apiMessage(body)}`
10624
+ `read platform status failed (HTTP ${response2.status}): ${apiMessage2(body)}`
9981
10625
  );
9982
10626
  }
9983
10627
  if (!isPlatformStatus(body)) {
@@ -9990,17 +10634,17 @@ async function platformCommand(parsed, deps = {}) {
9990
10634
  }
9991
10635
  }
9992
10636
  function isPlatformStatus(value2) {
9993
- if (!record5(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
9994
- if (!record5(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
9995
- if (!record5(value2.catalog) || !record5(value2.summary)) return false;
10637
+ if (!record8(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
10638
+ if (!record8(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
10639
+ if (!record8(value2.catalog) || !record8(value2.summary)) return false;
9996
10640
  return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
9997
10641
  }
9998
- function apiMessage(value2) {
9999
- if (!record5(value2)) return "request failed";
10000
- const error = record5(value2.error) ? value2.error : value2;
10642
+ function apiMessage2(value2) {
10643
+ if (!record8(value2)) return "request failed";
10644
+ const error = record8(value2.error) ? value2.error : value2;
10001
10645
  return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
10002
10646
  }
10003
- function record5(value2) {
10647
+ function record8(value2) {
10004
10648
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
10005
10649
  }
10006
10650
 
@@ -10041,7 +10685,7 @@ function statusVerdict(reads) {
10041
10685
  severity: "degraded"
10042
10686
  });
10043
10687
  }
10044
- const performance = record6(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
10688
+ const performance = record9(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
10045
10689
  if (performance?.status === "unavailable") {
10046
10690
  reasons.push({
10047
10691
  source: "liveSync",
@@ -10122,7 +10766,7 @@ function statusVerdict(reads) {
10122
10766
  reasons
10123
10767
  };
10124
10768
  }
10125
- function record6(value2) {
10769
+ function record9(value2) {
10126
10770
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
10127
10771
  }
10128
10772
  function numeric2(value2) {
@@ -10150,7 +10794,7 @@ function printO11yStatus(status, out) {
10150
10794
  out.log(
10151
10795
  `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
10152
10796
  );
10153
- const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record7) : [];
10797
+ const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record10) : [];
10154
10798
  const requests = routes.reduce(
10155
10799
  (total, row) => total + numeric3(row.requests),
10156
10800
  0
@@ -10162,39 +10806,39 @@ function printO11yStatus(status, out) {
10162
10806
  out.log(
10163
10807
  `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
10164
10808
  );
10165
- const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record7) : [];
10809
+ const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record10) : [];
10166
10810
  out.log(
10167
10811
  `application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
10168
10812
  (row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
10169
10813
  ).join(", ") : "none observed"}`
10170
10814
  );
10171
10815
  out.log(liveSyncLine(status.liveSync));
10172
- const canaryDurations = record7(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
10816
+ const canaryDurations = record10(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
10173
10817
  out.log(
10174
10818
  `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
10175
10819
  );
10176
- const collectorIngest = record7(status.collector.body.ingest) ? status.collector.body.ingest : {};
10177
- const collectorStorage = record7(collectorIngest.storage) ? collectorIngest.storage : {};
10820
+ const collectorIngest = record10(status.collector.body.ingest) ? status.collector.body.ingest : {};
10821
+ const collectorStorage = record10(collectorIngest.storage) ? collectorIngest.storage : {};
10178
10822
  out.log(
10179
10823
  `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
10180
10824
  );
10181
- const providerMetrics = record7(status.provider.body.metrics) ? status.provider.body.metrics : {};
10182
- const providerCapacity = record7(status.provider.body.capacity) ? status.provider.body.capacity : {};
10183
- const workerMemory = record7(providerCapacity.memory) ? providerCapacity.memory : {};
10825
+ const providerMetrics = record10(status.provider.body.metrics) ? status.provider.body.metrics : {};
10826
+ const providerCapacity = record10(status.provider.body.capacity) ? status.provider.body.capacity : {};
10827
+ const workerMemory = record10(providerCapacity.memory) ? providerCapacity.memory : {};
10184
10828
  out.log(
10185
10829
  `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
10830
  );
10187
10831
  for (const line of providerCapacityLines(status.providerCapacity)) {
10188
10832
  out.log(line);
10189
10833
  }
10190
- const coverage = record7(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
10191
- const coverageCounts = record7(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
10192
- const coverageBudget = record7(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
10834
+ const coverage = record10(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
10835
+ const coverageCounts = record10(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
10836
+ const coverageBudget = record10(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
10193
10837
  out.log(
10194
10838
  `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
10839
  );
10196
10840
  const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
10197
- const providerFreshness = record7(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
10841
+ const providerFreshness = record10(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
10198
10842
  out.log(
10199
10843
  `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
10200
10844
  );
@@ -10203,17 +10847,17 @@ function printO11yStatus(status, out) {
10203
10847
  );
10204
10848
  }
10205
10849
  function providerCapacityLines(read3) {
10206
- const resources = record7(read3.body.resources) ? read3.body.resources : {};
10207
- const durableObjects = record7(resources.durableObjects) ? resources.durableObjects : {};
10208
- const periodic = record7(durableObjects.periodic) ? durableObjects.periodic : {};
10209
- const storage = record7(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
10210
- const d1 = record7(resources.d1) ? resources.d1 : {};
10211
- const d1Activity = record7(d1.activity) ? d1.activity : {};
10212
- const d1Storage = record7(d1.storage) ? d1.storage : {};
10213
- const d1Latency = record7(d1Activity.latency) ? d1Activity.latency : {};
10214
- const r2 = record7(resources.r2) ? resources.r2 : {};
10215
- const r2Operations = record7(r2.operations) ? r2.operations : {};
10216
- const r2Storage = record7(r2.storage) ? r2.storage : {};
10850
+ const resources = record10(read3.body.resources) ? read3.body.resources : {};
10851
+ const durableObjects = record10(resources.durableObjects) ? resources.durableObjects : {};
10852
+ const periodic = record10(durableObjects.periodic) ? durableObjects.periodic : {};
10853
+ const storage = record10(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
10854
+ const d1 = record10(resources.d1) ? resources.d1 : {};
10855
+ const d1Activity = record10(d1.activity) ? d1.activity : {};
10856
+ const d1Storage = record10(d1.storage) ? d1.storage : {};
10857
+ const d1Latency = record10(d1Activity.latency) ? d1Activity.latency : {};
10858
+ const r2 = record10(resources.r2) ? resources.r2 : {};
10859
+ const r2Operations = record10(r2.operations) ? r2.operations : {};
10860
+ const r2Storage = record10(r2.storage) ? r2.storage : {};
10217
10861
  const status = String(
10218
10862
  read3.body.status ?? read3.body.error ?? "unavailable"
10219
10863
  );
@@ -10224,11 +10868,11 @@ function providerCapacityLines(read3) {
10224
10868
  ];
10225
10869
  }
10226
10870
  function liveSyncLine(read3) {
10227
- const performance = record7(read3.body.performance) ? read3.body.performance : {};
10228
- const commitToSend = record7(performance.commitToSend) ? performance.commitToSend : {};
10871
+ const performance = record10(read3.body.performance) ? read3.body.performance : {};
10872
+ const commitToSend = record10(performance.commitToSend) ? performance.commitToSend : {};
10229
10873
  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
10874
  }
10231
- function record7(value2) {
10875
+ function record10(value2) {
10232
10876
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
10233
10877
  }
10234
10878
  function numeric3(value2) {
@@ -10397,14 +11041,14 @@ function statusMinutes(value2) {
10397
11041
  }
10398
11042
  async function read2(url, headers, doFetch) {
10399
11043
  const response2 = await doFetch(url, { headers });
10400
- const text = await response2.text();
11044
+ const text2 = await response2.text();
10401
11045
  let body = {};
10402
- if (text) {
11046
+ if (text2) {
10403
11047
  try {
10404
- const value2 = JSON.parse(text);
11048
+ const value2 = JSON.parse(text2);
10405
11049
  body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
10406
11050
  } catch {
10407
- body = { message: text.slice(0, 300) };
11051
+ body = { message: text2.slice(0, 300) };
10408
11052
  }
10409
11053
  }
10410
11054
  return { httpStatus: response2.status, body };
@@ -10412,9 +11056,9 @@ async function read2(url, headers, doFetch) {
10412
11056
 
10413
11057
  // src/record.ts
10414
11058
  import { appendFileSync } from "fs";
10415
- import process12 from "process";
11059
+ import process13 from "process";
10416
11060
  function recordInvocation(parsed) {
10417
- const file = process12.env.ODLA_CLI_RECORD;
11061
+ const file = process13.env.ODLA_CLI_RECORD;
10418
11062
  if (!file) return;
10419
11063
  try {
10420
11064
  const entry = {
@@ -10429,7 +11073,7 @@ function recordInvocation(parsed) {
10429
11073
  }
10430
11074
 
10431
11075
  // src/runbook-actions.ts
10432
- import { readFileSync as readFileSync9 } from "fs";
11076
+ import { readFileSync as readFileSync10 } from "fs";
10433
11077
 
10434
11078
  // src/runbook-requires.ts
10435
11079
  var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
@@ -10514,7 +11158,7 @@ async function bySlug(ctx, slug) {
10514
11158
  function readBody(file, inline) {
10515
11159
  if (inline !== void 0) return inline;
10516
11160
  if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
10517
- return readFileSync9(file === "-" ? 0 : file, "utf8");
11161
+ return readFileSync10(file === "-" ? 0 : file, "utf8");
10518
11162
  }
10519
11163
  var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
10520
11164
  async function runbookList(ctx, all, query) {
@@ -10544,12 +11188,17 @@ async function runbookNew(ctx, slug, title, body, summary, requires) {
10544
11188
  });
10545
11189
  ctx.out.log(ctx.json ? JSON.stringify(created, null, 2) : `created ${slug} (${created.id})`);
10546
11190
  }
10547
- async function runbookEdit(ctx, slug, body, note, requires) {
11191
+ async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
10548
11192
  const runbook = await bySlug(ctx, slug);
10549
11193
  const result = await call(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
10550
11194
  // An empty --requires clears the declaration; omitting the flag leaves
10551
11195
  // whatever is there, so an ordinary body edit never drops it.
10552
- patch: { body, ...note ? { note } : {}, ...requires === void 0 ? {} : { requires: requires || null } }
11196
+ patch: {
11197
+ body,
11198
+ expectedVersion: expectedVersion ?? runbook.version,
11199
+ ...note ? { note } : {},
11200
+ ...requires === void 0 ? {} : { requires: requires || null }
11201
+ }
10553
11202
  });
10554
11203
  if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
10555
11204
  ctx.out.log(`${slug} \u2192 v${result.record?.version ?? runbook.version + 1}`);
@@ -10601,14 +11250,14 @@ async function runbookRemove(ctx, slug) {
10601
11250
  }
10602
11251
 
10603
11252
  // src/runbook-import.ts
10604
- import { readFileSync as readFileSync10, readdirSync as readdirSync2, statSync } from "fs";
10605
- import { basename as basename2, join as join12 } from "path";
10606
- function parseRunbook(text, slug) {
10607
- let rest = text;
11253
+ import { readFileSync as readFileSync11, readdirSync as readdirSync2, statSync } from "fs";
11254
+ import { basename as basename2, join as join13 } from "path";
11255
+ function parseRunbook(text2, slug) {
11256
+ let rest = text2;
10608
11257
  const meta = {};
10609
- const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
11258
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text2);
10610
11259
  if (fm) {
10611
- rest = text.slice(fm[0].length);
11260
+ rest = text2.slice(fm[0].length);
10612
11261
  for (const line of fm[1].split(/\r?\n/)) {
10613
11262
  const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
10614
11263
  if (!pair) continue;
@@ -10632,7 +11281,7 @@ function readRunbookDir(dir) {
10632
11281
  if (!files.length) throw new Error(`no .md files in ${dir}`);
10633
11282
  return files.map((file) => {
10634
11283
  const slug = basename2(file, ".md");
10635
- const parsed = parseRunbook(readFileSync10(join12(dir, file), "utf8"), slug);
11284
+ const parsed = parseRunbook(readFileSync11(join13(dir, file), "utf8"), slug);
10636
11285
  return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
10637
11286
  });
10638
11287
  }
@@ -10689,6 +11338,7 @@ async function upsert(ctx, r, visibility) {
10689
11338
  patch: {
10690
11339
  title: r.title,
10691
11340
  body: r.body,
11341
+ expectedVersion: found.version,
10692
11342
  ...r.summary ? { summary: r.summary } : {},
10693
11343
  ...r.tags ? { tags: r.tags } : {},
10694
11344
  note: `imported from ${r.file}`
@@ -10704,8 +11354,8 @@ async function upsert(ctx, r, visibility) {
10704
11354
 
10705
11355
  // src/runbook-impact.ts
10706
11356
  import { execFileSync as execFileSync2 } from "child_process";
10707
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
10708
- import { join as join13 } from "path";
11357
+ import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
11358
+ import { join as join14 } from "path";
10709
11359
 
10710
11360
  // src/runbook-impact-scan.ts
10711
11361
  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 +11524,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
10874
11524
  }
10875
11525
  function manifestLabeller(root) {
10876
11526
  return (workspace) => {
10877
- const manifest = join13(root, workspace, "package.json");
11527
+ const manifest = join14(root, workspace, "package.json");
10878
11528
  if (!existsSync11(manifest)) return void 0;
10879
11529
  try {
10880
- const name = JSON.parse(readFileSync11(manifest, "utf8")).name;
11530
+ const name = JSON.parse(readFileSync12(manifest, "utf8")).name;
10881
11531
  return typeof name === "string" ? name : void 0;
10882
11532
  } catch {
10883
11533
  return void 0;
@@ -10944,7 +11594,7 @@ function report3(ctx, impacts) {
10944
11594
  async function runbookImpact(ctx, options, deps = {}) {
10945
11595
  const cwd = deps.cwd ?? process.cwd();
10946
11596
  const runGit = deps.runGit ?? gitRunner(cwd);
10947
- const read3 = deps.readRepoFile ?? ((path) => readFileSync11(join13(cwd, path), "utf8"));
11597
+ const read3 = deps.readRepoFile ?? ((path) => readFileSync12(join14(cwd, path), "utf8"));
10948
11598
  const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
10949
11599
  if (!surfaces.length) {
10950
11600
  return ctx.out.log(
@@ -11077,12 +11727,12 @@ async function runbookComment(ctx, slug, body) {
11077
11727
 
11078
11728
  // src/runbook-editor.ts
11079
11729
  import { spawnSync } from "child_process";
11080
- import { mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
11730
+ import { mkdtempSync, readFileSync as readFileSync13, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
11081
11731
  import { tmpdir as tmpdir4 } from "os";
11082
- import { join as join14 } from "path";
11083
- import process13 from "process";
11732
+ import { join as join15 } from "path";
11733
+ import process14 from "process";
11084
11734
  var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
11085
- function resolveEditor(env = process13.env) {
11735
+ function resolveEditor(env = process14.env) {
11086
11736
  for (const name of EDITOR_ENV) {
11087
11737
  const value2 = env[name];
11088
11738
  if (value2 && value2.trim()) return value2.trim();
@@ -11096,8 +11746,8 @@ function defaultRun(command, path) {
11096
11746
  return result.status ?? 0;
11097
11747
  }
11098
11748
  function editText(initial, slug, deps = {}) {
11099
- const env = deps.env ?? process13.env;
11100
- const interactive = deps.interactive ?? (() => Boolean(process13.stdin.isTTY));
11749
+ const env = deps.env ?? process14.env;
11750
+ const interactive = deps.interactive ?? (() => Boolean(process14.stdin.isTTY));
11101
11751
  const editor = resolveEditor(env);
11102
11752
  if (!editor)
11103
11753
  throw new Error(
@@ -11105,13 +11755,13 @@ function editText(initial, slug, deps = {}) {
11105
11755
  );
11106
11756
  if (!interactive())
11107
11757
  throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
11108
- const dir = mkdtempSync(join14(tmpdir4(), "odla-runbook-"));
11109
- const file = join14(dir, `${slug}.md`);
11758
+ const dir = mkdtempSync(join15(tmpdir4(), "odla-runbook-"));
11759
+ const file = join15(dir, `${slug}.md`);
11110
11760
  try {
11111
11761
  writeFileSync4(file, initial, { mode: 384 });
11112
11762
  const code = defaultRunOrInjected(deps)(editor, file);
11113
11763
  if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
11114
- const edited = readFileSync12(file, "utf8");
11764
+ const edited = readFileSync13(file, "utf8");
11115
11765
  return edited === initial ? null : edited;
11116
11766
  } finally {
11117
11767
  rmSync2(dir, { recursive: true, force: true });
@@ -11129,27 +11779,90 @@ async function editRunbook(ctx, slug, deps = {}) {
11129
11779
  const found = page.records[0];
11130
11780
  if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
11131
11781
  ctx.out.log(`opening ${slug} v${found.version} in your editor\u2026`);
11132
- return editText(found.body, slug, deps);
11782
+ const body = await editText(found.body, slug, deps);
11783
+ return body === null ? null : { body, expectedVersion: found.version };
11133
11784
  }
11134
11785
 
11135
11786
  // src/whoami-command.ts
11787
+ var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
11788
+ function principalKind(value2, machine) {
11789
+ return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
11790
+ }
11791
+ function credentialKind(value2, machine, scopes) {
11792
+ if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
11793
+ if (machine) return "machine";
11794
+ if (scopes.length) return "device";
11795
+ return "unknown";
11796
+ }
11797
+ function managerOf(value2) {
11798
+ if (!value2 || typeof value2 !== "object") return null;
11799
+ const row = value2;
11800
+ const principalId = text(row.principalId);
11801
+ if (!principalId) return null;
11802
+ return {
11803
+ principalId,
11804
+ displayName: text(row.displayName) ?? "Unnamed member",
11805
+ handle: text(row.handle) ?? ""
11806
+ };
11807
+ }
11808
+ function unnamedPrincipal(kind) {
11809
+ if (kind === "agent") return "Unnamed agent";
11810
+ if (kind === "service") return "Unnamed service";
11811
+ return "Unnamed member";
11812
+ }
11136
11813
  async function fetchIdentity(platformUrl, token, doFetch) {
11137
11814
  const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
11138
11815
  headers: { authorization: `Bearer ${token}` }
11139
11816
  });
11140
11817
  if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
11141
11818
  const body = await res.json();
11819
+ const developerId = text(body.developerId) ?? "";
11820
+ const machine = body.machine === true;
11821
+ const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
11822
+ const principalId = text(body.principalId) ?? developerId;
11823
+ const email = text(body.email);
11824
+ const kind = principalKind(body.principalKind, machine);
11825
+ const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
11826
+ const handle = text(body.handle) ?? "";
11827
+ const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
11142
11828
  return {
11143
- developerId: String(body.developerId ?? ""),
11144
- email: body.email ?? null,
11829
+ developerId,
11830
+ principalId,
11831
+ principalKind: kind,
11832
+ displayName,
11833
+ handle,
11834
+ manager: managerOf(body.manager),
11835
+ credential: {
11836
+ id: text(credential2.id),
11837
+ kind: credentialKind(credential2.kind, machine, scopes)
11838
+ },
11839
+ email,
11145
11840
  admin: body.admin === true,
11146
- machine: body.machine === true,
11147
- scopes: Array.isArray(body.scopes) ? body.scopes.map(String) : []
11841
+ machine,
11842
+ scopes
11148
11843
  };
11149
11844
  }
11150
- function credentialKind(identity) {
11151
- if (identity.machine) return "machine (platform admin secret)";
11152
- return identity.scopes.length ? "device token (scoped)" : "device token or session";
11845
+ function credentialLabel(identity) {
11846
+ if (identity.credential.kind === "machine") return "machine (platform admin secret)";
11847
+ if (identity.credential.kind === "device")
11848
+ return identity.scopes.length ? "device (scoped)" : "device";
11849
+ if (identity.credential.kind === "clerk") return "clerk";
11850
+ return "unknown (legacy server)";
11851
+ }
11852
+ function namedPrincipal(identity) {
11853
+ return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
11854
+ }
11855
+ function namedManager(manager) {
11856
+ return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
11857
+ }
11858
+ function accountableOwner(identity) {
11859
+ if (identity.principalKind === "agent" && identity.manager) {
11860
+ return namedManager(identity.manager);
11861
+ }
11862
+ if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
11863
+ return namedPrincipal(identity);
11864
+ }
11865
+ return identity.email ?? "Unnamed member";
11153
11866
  }
11154
11867
  async function whoamiCommand(parsed, deps = {}) {
11155
11868
  assertArgs(
@@ -11180,9 +11893,19 @@ async function whoamiCommand(parsed, deps = {}) {
11180
11893
  return;
11181
11894
  }
11182
11895
  out.log(`platform: ${cfg.platformUrl}`);
11183
- out.log(`developer: ${identity.developerId}`);
11896
+ out.log(`principal: ${namedPrincipal(identity)}`);
11897
+ out.log(`principal id: ${identity.principalId}`);
11898
+ out.log(`kind: ${identity.principalKind}`);
11899
+ if (identity.principalKind === "agent")
11900
+ out.log(
11901
+ `manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
11902
+ );
11903
+ out.log(`owner: ${accountableOwner(identity)}`);
11904
+ out.log(`owner id: ${identity.developerId}`);
11184
11905
  out.log(`email: ${identity.email ?? "(none)"}`);
11185
- out.log(`credential: ${credentialKind(identity)}`);
11906
+ out.log(`credential: ${credentialLabel(identity)}`);
11907
+ if (identity.credential.id)
11908
+ out.log(`credential id: ${identity.credential.id}`);
11186
11909
  out.log(`admin: ${identity.admin ? "yes" : "no"}`);
11187
11910
  if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
11188
11911
  if (!identity.admin) {
@@ -11331,14 +12054,16 @@ async function runbookCommand(parsed, deps = {}) {
11331
12054
  const name = requireSlug(slug, "edit");
11332
12055
  const file = stringOpt(parsed.options.file);
11333
12056
  const inline = stringOpt(parsed.options.body);
11334
- const body = file === void 0 && inline === void 0 ? await editRunbook(ctx, name) : readBody(file, inline);
11335
- if (body === null) return ctx.out.log(`${name} unchanged; nothing written`);
12057
+ const edited = file === void 0 && inline === void 0 ? await editRunbook(ctx, name) : readBody(file, inline);
12058
+ if (edited === null) return ctx.out.log(`${name} unchanged; nothing written`);
12059
+ const body = typeof edited === "string" ? edited : edited.body;
11336
12060
  return runbookEdit(
11337
12061
  ctx,
11338
12062
  name,
11339
12063
  body,
11340
12064
  stringOpt(parsed.options.note),
11341
- parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? ""
12065
+ parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? "",
12066
+ typeof edited === "string" ? void 0 : edited.expectedVersion
11342
12067
  );
11343
12068
  }
11344
12069
  case "import": {
@@ -11805,11 +12530,11 @@ async function securityStatus(parsed, dependencies) {
11805
12530
  // src/cli.ts
11806
12531
  function exitCodeFor(err) {
11807
12532
  const code = err?.code;
11808
- if (code === "handshake_pending" || code === "watch_timeout") return 75;
12533
+ if (code === "handshake_pending" || code === "watch_timeout" || code === "operation_pending") return 75;
11809
12534
  if (code === "checkpoint_required") return 3;
11810
12535
  if (code === "remote_unavailable") return 6;
11811
12536
  if (code === "auth_failed") return 5;
11812
- if (code === "invalid_cursor") return 2;
12537
+ if (code === "invalid_cursor" || code === "invalid_plan" || code === "invalid_operation_id") return 2;
11813
12538
  return 1;
11814
12539
  }
11815
12540
  async function runCli(argv = process.argv.slice(2), dependencies = {}) {
@@ -11964,7 +12689,11 @@ export {
11964
12689
  calendarDisconnect,
11965
12690
  CAPABILITIES,
11966
12691
  printCapabilities,
12692
+ ConfigOperationCommandError,
11967
12693
  desiredRegistryState,
12694
+ configApply,
12695
+ configOperationGet,
12696
+ configOperationWait,
11968
12697
  reconcileConfig,
11969
12698
  configDiff,
11970
12699
  configPlan,
@@ -12005,4 +12734,4 @@ export {
12005
12734
  exitCodeFor,
12006
12735
  runCli
12007
12736
  };
12008
- //# sourceMappingURL=chunk-2AFAXLKE.js.map
12737
+ //# sourceMappingURL=chunk-ZAZBUURZ.js.map