@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.
- package/README.md +61 -11
- package/dist/bin.cjs +1061 -341
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-2AFAXLKE.js → chunk-ZAZBUURZ.js} +1023 -294
- package/dist/chunk-ZAZBUURZ.js.map +1 -0
- package/dist/index.cjs +1069 -341
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -23
- package/dist/index.d.ts +52 -23
- package/dist/index.js +9 -1
- package/package.json +2 -2
- package/skills/odla/references/sdks.md +5 -3
- package/skills/odla-migrate/references/phase-2b-calendar.md +13 -8
- package/dist/chunk-2AFAXLKE.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
CODE_BUILD_RECIPES: () => CODE_BUILD_RECIPES,
|
|
37
37
|
CODE_PI_IMAGE: () => CODE_PI_IMAGE,
|
|
38
38
|
COMMAND_SURFACE: () => COMMAND_SURFACE,
|
|
39
|
+
ConfigOperationCommandError: () => ConfigOperationCommandError,
|
|
39
40
|
GOOGLE_CALENDAR_EVENTS_SCOPE: () => GOOGLE_CALENDAR_EVENTS_SCOPE,
|
|
40
41
|
SYSTEM_AI_PURPOSES: () => SYSTEM_AI_PURPOSES,
|
|
41
42
|
acceptedAfter: () => acceptedAfter,
|
|
@@ -47,7 +48,10 @@ __export(index_exports, {
|
|
|
47
48
|
calendarServiceConfig: () => calendarServiceConfig,
|
|
48
49
|
calendarStatus: () => calendarStatus,
|
|
49
50
|
codeConnect: () => codeConnect,
|
|
51
|
+
configApply: () => configApply,
|
|
50
52
|
configDiff: () => configDiff,
|
|
53
|
+
configOperationGet: () => configOperationGet,
|
|
54
|
+
configOperationWait: () => configOperationWait,
|
|
51
55
|
configPlan: () => configPlan,
|
|
52
56
|
connectGitHubSecuritySource: () => connectGitHubSecuritySource,
|
|
53
57
|
describeProblem: () => describeProblem,
|
|
@@ -246,10 +250,10 @@ function isManagedDevVar(line) {
|
|
|
246
250
|
const match = line.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
|
|
247
251
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
248
252
|
}
|
|
249
|
-
function writePrivateText(path,
|
|
253
|
+
function writePrivateText(path, text2) {
|
|
250
254
|
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
|
|
251
255
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
252
|
-
(0, import_node_fs.writeFileSync)(temporary,
|
|
256
|
+
(0, import_node_fs.writeFileSync)(temporary, text2, { mode: 384 });
|
|
253
257
|
(0, import_node_fs.chmodSync)(temporary, 384);
|
|
254
258
|
(0, import_node_fs.renameSync)(temporary, path);
|
|
255
259
|
}
|
|
@@ -556,7 +560,9 @@ function audienceBoundEnvToken(token, platform) {
|
|
|
556
560
|
}
|
|
557
561
|
var SCOPE_PURPOSE = {
|
|
558
562
|
"platform:status:read": "read the platform fleet health and deployment snapshot",
|
|
563
|
+
"platform:chat:credential:write": "rotate the built-in Discussion responder credential",
|
|
559
564
|
"app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
|
|
565
|
+
"app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
|
|
560
566
|
"platform:runbook:write": "add or edit odla's operational runbooks",
|
|
561
567
|
"platform:ai:policy:write": "change System AI model routing",
|
|
562
568
|
"platform:ai:policy:read": "read System AI model routing",
|
|
@@ -614,6 +620,14 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
614
620
|
return token;
|
|
615
621
|
}
|
|
616
622
|
|
|
623
|
+
// src/principal-presentation.ts
|
|
624
|
+
function unresolvedPrincipalLabel(credentialKind2, principalId) {
|
|
625
|
+
const kind = typeof credentialKind2 === "string" ? credentialKind2.trim() : "";
|
|
626
|
+
const id = typeof principalId === "string" ? principalId.trim() : "";
|
|
627
|
+
const audit = kind && id ? `${kind}:${id}` : kind || id;
|
|
628
|
+
return `Unknown principal${audit ? ` [${audit}]` : ""}`;
|
|
629
|
+
}
|
|
630
|
+
|
|
617
631
|
// src/admin-ai-audit.ts
|
|
618
632
|
function adminAiAuditQuery(filters) {
|
|
619
633
|
if (filters.limit === void 0) return "";
|
|
@@ -643,17 +657,17 @@ async function readAdminAiAudit(request2) {
|
|
|
643
657
|
String(event.changeKind ?? ""),
|
|
644
658
|
String(event.purpose ?? event.provider ?? ""),
|
|
645
659
|
route2,
|
|
646
|
-
|
|
660
|
+
unresolvedPrincipalLabel(event.actorType, event.actorId)
|
|
647
661
|
].join(" "));
|
|
648
662
|
}
|
|
649
663
|
}
|
|
650
664
|
async function responseBody(response2) {
|
|
651
|
-
const
|
|
652
|
-
if (!
|
|
665
|
+
const text2 = await response2.text();
|
|
666
|
+
if (!text2) return {};
|
|
653
667
|
try {
|
|
654
|
-
return JSON.parse(
|
|
668
|
+
return JSON.parse(text2);
|
|
655
669
|
} catch {
|
|
656
|
-
return { message:
|
|
670
|
+
return { message: text2.slice(0, 300) };
|
|
657
671
|
}
|
|
658
672
|
}
|
|
659
673
|
function apiError(status, body) {
|
|
@@ -734,7 +748,7 @@ when app/env run actor purpose/role route / policy tokens cost status`);
|
|
|
734
748
|
timestamp2(event.created_at),
|
|
735
749
|
`${String(event.app_id ?? "")}/${String(event.env ?? "")}`,
|
|
736
750
|
String(event.run_id ?? ""),
|
|
737
|
-
|
|
751
|
+
unresolvedPrincipalLabel(event.actor_type, event.actor_id),
|
|
738
752
|
`${String(event.purpose ?? "")}/${String(event.role ?? "")}`,
|
|
739
753
|
`${String(event.provider ?? "")}/${String(event.model ?? "")}@v${String(event.policy_version ?? "unknown")}`,
|
|
740
754
|
String(input + output),
|
|
@@ -755,12 +769,12 @@ function timestamp2(value2) {
|
|
|
755
769
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
756
770
|
}
|
|
757
771
|
async function responseBody2(res) {
|
|
758
|
-
const
|
|
759
|
-
if (!
|
|
772
|
+
const text2 = await res.text();
|
|
773
|
+
if (!text2) return {};
|
|
760
774
|
try {
|
|
761
|
-
return JSON.parse(
|
|
775
|
+
return JSON.parse(text2);
|
|
762
776
|
} catch {
|
|
763
|
-
return { message:
|
|
777
|
+
return { message: text2.slice(0, 300) };
|
|
764
778
|
}
|
|
765
779
|
}
|
|
766
780
|
function apiError2(action2, status, body) {
|
|
@@ -936,12 +950,12 @@ function catalogModels(body) {
|
|
|
936
950
|
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
937
951
|
}
|
|
938
952
|
async function responseBody3(res) {
|
|
939
|
-
const
|
|
940
|
-
if (!
|
|
953
|
+
const text2 = await res.text();
|
|
954
|
+
if (!text2) return {};
|
|
941
955
|
try {
|
|
942
|
-
return JSON.parse(
|
|
956
|
+
return JSON.parse(text2);
|
|
943
957
|
} catch {
|
|
944
|
-
return { message:
|
|
958
|
+
return { message: text2.slice(0, 300) };
|
|
945
959
|
}
|
|
946
960
|
}
|
|
947
961
|
function apiError3(action2, status, body) {
|
|
@@ -1112,6 +1126,7 @@ function unique(values) {
|
|
|
1112
1126
|
var DEFAULT_PLATFORM = "https://odla.ai";
|
|
1113
1127
|
var DEFAULT_ENVS = ["dev"];
|
|
1114
1128
|
var DEFAULT_SERVICES = ["db", "ai"];
|
|
1129
|
+
var configImportSerial = 0;
|
|
1115
1130
|
var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
|
|
1116
1131
|
async function loadProjectConfig(configPath = "odla.config.mjs") {
|
|
1117
1132
|
const resolved = (0, import_node_path4.resolve)(configPath);
|
|
@@ -1319,7 +1334,8 @@ function validId2(value2) {
|
|
|
1319
1334
|
}
|
|
1320
1335
|
async function loadConfigModule(path) {
|
|
1321
1336
|
if (path.endsWith(".json")) return JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
|
|
1322
|
-
const
|
|
1337
|
+
const nonce = `${Date.now()}-${configImportSerial++}`;
|
|
1338
|
+
const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?reload=${nonce}`);
|
|
1323
1339
|
const value2 = mod.default ?? mod.config;
|
|
1324
1340
|
if (typeof value2 === "function") return await value2();
|
|
1325
1341
|
return value2;
|
|
@@ -1783,8 +1799,8 @@ async function appImport(options) {
|
|
|
1783
1799
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
1784
1800
|
const doFetch = options.fetch ?? fetch;
|
|
1785
1801
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
1786
|
-
const
|
|
1787
|
-
const { format, sources } = (0, import_import.parseImport)(
|
|
1802
|
+
const text2 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs8.readFileSync)(0, "utf8")))() : (0, import_node_fs8.readFileSync)(options.file, "utf8");
|
|
1803
|
+
const { format, sources } = (0, import_import.parseImport)(text2, options.ns);
|
|
1788
1804
|
if (format === "namespace-map" && options.ns) {
|
|
1789
1805
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
1790
1806
|
}
|
|
@@ -1875,7 +1891,10 @@ function report(options, owners, headline) {
|
|
|
1875
1891
|
if (headline) out.log(headline);
|
|
1876
1892
|
out.log(`owners (${owners.length}):`);
|
|
1877
1893
|
for (const o of owners) {
|
|
1878
|
-
|
|
1894
|
+
const name = o.email?.trim() || "Unnamed member";
|
|
1895
|
+
out.log(
|
|
1896
|
+
` ${o.primary ? "\u2605" : "\xB7"} ${name} [${o.ownerId}]${o.primary ? " (primary)" : ""}`
|
|
1897
|
+
);
|
|
1879
1898
|
}
|
|
1880
1899
|
}
|
|
1881
1900
|
async function ownersList(options) {
|
|
@@ -2703,7 +2722,8 @@ var CAPABILITIES = {
|
|
|
2703
2722
|
"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",
|
|
2704
2723
|
"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",
|
|
2705
2724
|
"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",
|
|
2706
|
-
"compare one project's checked-in Registry intent with live owner-visible state
|
|
2725
|
+
"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",
|
|
2726
|
+
"inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
|
|
2707
2727
|
"inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
|
|
2708
2728
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
2709
2729
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
@@ -2750,10 +2770,31 @@ function printGroup(out, heading, items) {
|
|
|
2750
2770
|
out.log("");
|
|
2751
2771
|
}
|
|
2752
2772
|
|
|
2753
|
-
// src/config-
|
|
2773
|
+
// src/config-operation-command.ts
|
|
2754
2774
|
var import_apps6 = require("@odla-ai/apps");
|
|
2755
2775
|
var import_node_path7 = require("path");
|
|
2756
2776
|
|
|
2777
|
+
// src/version.ts
|
|
2778
|
+
var import_node_fs9 = require("fs");
|
|
2779
|
+
function cliVersion() {
|
|
2780
|
+
const pkg = JSON.parse((0, import_node_fs9.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
|
|
2781
|
+
return pkg.version ?? "unknown";
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
// src/config-operation-error.ts
|
|
2785
|
+
var ConfigOperationCommandError = class extends Error {
|
|
2786
|
+
constructor(message2, code) {
|
|
2787
|
+
super(message2);
|
|
2788
|
+
this.code = code;
|
|
2789
|
+
this.name = "ConfigOperationCommandError";
|
|
2790
|
+
}
|
|
2791
|
+
code;
|
|
2792
|
+
};
|
|
2793
|
+
|
|
2794
|
+
// src/config-operation-validate.ts
|
|
2795
|
+
var import_apps3 = require("@odla-ai/apps");
|
|
2796
|
+
var import_node_fs10 = require("fs");
|
|
2797
|
+
|
|
2757
2798
|
// src/config-reconcile-digest.ts
|
|
2758
2799
|
var import_node_crypto = require("crypto");
|
|
2759
2800
|
function canonicalJson(value2) {
|
|
@@ -2763,27 +2804,160 @@ function configDigest(value2) {
|
|
|
2763
2804
|
return `sha256:${(0, import_node_crypto.createHash)("sha256").update(canonicalJson(value2)).digest("hex")}`;
|
|
2764
2805
|
}
|
|
2765
2806
|
function canonicalValue(value2) {
|
|
2807
|
+
if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
|
|
2808
|
+
if (typeof value2 === "number") {
|
|
2809
|
+
if (!Number.isFinite(value2)) throw new TypeError("canonical JSON rejects non-finite numbers");
|
|
2810
|
+
return value2;
|
|
2811
|
+
}
|
|
2766
2812
|
if (Array.isArray(value2)) return value2.map(canonicalValue);
|
|
2767
2813
|
if (value2 && typeof value2 === "object") {
|
|
2814
|
+
const record11 = value2;
|
|
2768
2815
|
return Object.fromEntries(
|
|
2769
|
-
Object.
|
|
2816
|
+
Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, canonicalValue(record11[key])])
|
|
2770
2817
|
);
|
|
2771
2818
|
}
|
|
2772
|
-
|
|
2819
|
+
throw new TypeError("canonical JSON rejects unsupported values");
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
// src/config-operation-validate.ts
|
|
2823
|
+
var DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
2824
|
+
var REVISION = /^registry:[1-9][0-9]*$/;
|
|
2825
|
+
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;
|
|
2826
|
+
var ACTION_ID = /^action-[0-9a-f]{16}$/;
|
|
2827
|
+
var ENV = /^[a-z0-9]{2,12}$/;
|
|
2828
|
+
var SERVICE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
2829
|
+
function readPlan(path) {
|
|
2830
|
+
let value2;
|
|
2831
|
+
try {
|
|
2832
|
+
const raw = (0, import_node_fs10.readFileSync)(path, "utf8");
|
|
2833
|
+
if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
|
|
2834
|
+
value2 = JSON.parse(raw);
|
|
2835
|
+
} catch (error) {
|
|
2836
|
+
throw new ConfigOperationCommandError(
|
|
2837
|
+
`cannot read --plan ${path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
2838
|
+
"invalid_plan"
|
|
2839
|
+
);
|
|
2840
|
+
}
|
|
2841
|
+
if (!record2(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
|
|
2842
|
+
if (!record2(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
|
|
2843
|
+
invalidPlan("plan scope is invalid");
|
|
2844
|
+
}
|
|
2845
|
+
if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
|
|
2846
|
+
invalidPlan("plan content revisions are invalid");
|
|
2847
|
+
}
|
|
2848
|
+
if (!REVISION.test(String(value2.registryRevision)) || !DIGEST.test(String(value2.planDigest))) {
|
|
2849
|
+
invalidPlan("plan Registry revision or digest is invalid");
|
|
2850
|
+
}
|
|
2851
|
+
if (!Array.isArray(value2.actions) || !value2.actions.length || value2.actions.length > 32) {
|
|
2852
|
+
invalidPlan("plan actions must contain 1-32 entries");
|
|
2853
|
+
}
|
|
2854
|
+
assertActions(value2.actions);
|
|
2855
|
+
const plan = value2;
|
|
2856
|
+
const digest = configDigest({
|
|
2857
|
+
schemaVersion: plan.schemaVersion,
|
|
2858
|
+
desiredRevision: plan.desiredRevision,
|
|
2859
|
+
observedRevision: plan.observedRevision,
|
|
2860
|
+
registryRevision: plan.registryRevision,
|
|
2861
|
+
actions: plan.actions
|
|
2862
|
+
});
|
|
2863
|
+
if (digest !== plan.planDigest) invalidPlan("plan digest does not bind these revisions and actions");
|
|
2864
|
+
return plan;
|
|
2865
|
+
}
|
|
2866
|
+
function assertPlanContext(plan, appId, platformUrl) {
|
|
2867
|
+
if (plan.scope.appId !== appId || plan.scope.platformUrl.replace(/\/$/, "") !== platformUrl.replace(/\/$/, "")) {
|
|
2868
|
+
throw new ConfigOperationCommandError("plan scope does not match the selected project config", "checkpoint_required");
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
function verifyReceipt(receipt, appId, operationId) {
|
|
2872
|
+
if (receipt.appId !== appId || operationId && receipt.operationId !== operationId) {
|
|
2873
|
+
throw new ConfigOperationCommandError("Registry returned a receipt outside the requested scope", "invalid_receipt");
|
|
2874
|
+
}
|
|
2875
|
+
if (receipt.receiptDigest) {
|
|
2876
|
+
const { receiptDigest, ...fields } = receipt;
|
|
2877
|
+
if (configDigest(fields) !== receiptDigest) {
|
|
2878
|
+
throw new ConfigOperationCommandError("config operation receipt digest is invalid", "invalid_receipt");
|
|
2879
|
+
}
|
|
2880
|
+
} else if (receipt.state === "succeeded" || receipt.state === "conflict" || receipt.state === "failed" && !receipt.error?.retryable) {
|
|
2881
|
+
throw new ConfigOperationCommandError("terminal config operation receipt is not digest-authenticated", "invalid_receipt");
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
function assertOperationId(value2) {
|
|
2885
|
+
if (!OPERATION_ID.test(value2)) {
|
|
2886
|
+
throw new ConfigOperationCommandError("operation id must be a UUID", "invalid_operation_id");
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
function assertActions(actions) {
|
|
2890
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2891
|
+
for (const action2 of actions) {
|
|
2892
|
+
if (!record2(action2)) invalidPlan("every plan action must be an object");
|
|
2893
|
+
const id = String(action2.id ?? "");
|
|
2894
|
+
if (!ACTION_ID.test(id) || ids.has(id)) invalidPlan("plan action ids must be unique frozen ids");
|
|
2895
|
+
ids.add(id);
|
|
2896
|
+
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))) {
|
|
2897
|
+
invalidPlan("plan action metadata is invalid");
|
|
2898
|
+
}
|
|
2899
|
+
if (["rename_app", "enable_service", "configure_service", "set_link"].includes(String(action2.kind))) {
|
|
2900
|
+
assertConditionalAction(action2);
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
function assertConditionalAction(action2) {
|
|
2905
|
+
if (action2.kind === "rename_app") {
|
|
2906
|
+
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");
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
if (!action2.env || !ENV.test(action2.env)) invalidPlan("conditional action env is invalid");
|
|
2910
|
+
if (action2.kind === "set_link") {
|
|
2911
|
+
if (action2.path !== `environments.${action2.env}.link` || action2.applySupport !== "provision" || !linkState(action2.before) || !linkState(action2.after)) invalidPlan("link action is invalid");
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2914
|
+
if (!action2.service || !SERVICE.test(action2.service) || !(0, import_apps3.appServiceDefinition)(action2.service)) {
|
|
2915
|
+
invalidPlan("service action names an unknown service");
|
|
2916
|
+
}
|
|
2917
|
+
const base = `environments.${action2.env}.services.${action2.service}`;
|
|
2918
|
+
if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
|
|
2919
|
+
invalidPlan("service action path is invalid");
|
|
2920
|
+
}
|
|
2921
|
+
if (action2.applySupport !== "provision" || !record2(action2.after)) {
|
|
2922
|
+
invalidPlan("service action payload is invalid");
|
|
2923
|
+
}
|
|
2924
|
+
if (action2.kind === "enable_service") {
|
|
2925
|
+
if (action2.after.enabled !== true || action2.before !== null && !record2(action2.before)) {
|
|
2926
|
+
invalidPlan("service enable action is invalid");
|
|
2927
|
+
}
|
|
2928
|
+
} else if (!record2(action2.before)) {
|
|
2929
|
+
invalidPlan("service configure action is invalid");
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
function linkState(value2) {
|
|
2933
|
+
if (value2 === null) return true;
|
|
2934
|
+
if (typeof value2 !== "string") return false;
|
|
2935
|
+
try {
|
|
2936
|
+
const url = new URL(value2.trim());
|
|
2937
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
2938
|
+
} catch {
|
|
2939
|
+
return false;
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
function invalidPlan(message2) {
|
|
2943
|
+
throw new ConfigOperationCommandError(message2, "invalid_plan");
|
|
2944
|
+
}
|
|
2945
|
+
function record2(value2) {
|
|
2946
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
2773
2947
|
}
|
|
2774
2948
|
|
|
2775
2949
|
// src/config-reconcile-desired.ts
|
|
2776
|
-
var
|
|
2950
|
+
var import_apps5 = require("@odla-ai/apps");
|
|
2777
2951
|
|
|
2778
2952
|
// src/provision-helpers.ts
|
|
2779
2953
|
var import_ai = require("@odla-ai/ai");
|
|
2780
|
-
var
|
|
2954
|
+
var import_apps4 = require("@odla-ai/apps");
|
|
2781
2955
|
function defaultSecretName(provider) {
|
|
2782
2956
|
const names = import_ai.DEFAULT_SECRET_NAMES;
|
|
2783
2957
|
return names[provider] ?? `${provider}_api_key`;
|
|
2784
2958
|
}
|
|
2785
2959
|
async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
2786
|
-
const tenantId = (0,
|
|
2960
|
+
const tenantId = (0, import_apps4.tenantIdFor)(cfg.app.id, env);
|
|
2787
2961
|
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
|
|
2788
2962
|
headers: { authorization: `Bearer ${token}` }
|
|
2789
2963
|
});
|
|
@@ -2825,7 +2999,7 @@ async function safeText3(res) {
|
|
|
2825
2999
|
// src/config-reconcile-desired.ts
|
|
2826
3000
|
function desiredRegistryState(cfg) {
|
|
2827
3001
|
const environments = {};
|
|
2828
|
-
const services = (0,
|
|
3002
|
+
const services = (0, import_apps5.orderAppServices)(cfg.services);
|
|
2829
3003
|
for (const env of cfg.envs) {
|
|
2830
3004
|
const desiredServices = {};
|
|
2831
3005
|
for (const service of services) {
|
|
@@ -2865,8 +3039,208 @@ function managedServiceConfig(cfg, env, service) {
|
|
|
2865
3039
|
return {};
|
|
2866
3040
|
}
|
|
2867
3041
|
|
|
3042
|
+
// src/config-reconcile-support.ts
|
|
3043
|
+
var CONDITIONAL_KINDS = /* @__PURE__ */ new Set([
|
|
3044
|
+
"rename_app",
|
|
3045
|
+
"enable_service",
|
|
3046
|
+
"configure_service",
|
|
3047
|
+
"set_link"
|
|
3048
|
+
]);
|
|
3049
|
+
function configApplySupport(reconciliation) {
|
|
3050
|
+
if (!reconciliation.observedRevision || !reconciliation.registryRevision) {
|
|
3051
|
+
return {
|
|
3052
|
+
supported: false,
|
|
3053
|
+
checkpointRequired: false,
|
|
3054
|
+
reason: "the app must already exist in a revision-aware Registry"
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
if (!reconciliation.actions.length) {
|
|
3058
|
+
return {
|
|
3059
|
+
supported: false,
|
|
3060
|
+
checkpointRequired: false,
|
|
3061
|
+
reason: "there are no managed changes to apply"
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
const blocked = reconciliation.actions.filter(
|
|
3065
|
+
(action2) => !CONDITIONAL_KINDS.has(action2.kind) || action2.risk !== "low" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
|
|
3066
|
+
);
|
|
3067
|
+
if (blocked.length) {
|
|
3068
|
+
return {
|
|
3069
|
+
supported: false,
|
|
3070
|
+
checkpointRequired: blocked.some(
|
|
3071
|
+
(action2) => action2.risk === "high" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
|
|
3072
|
+
),
|
|
3073
|
+
reason: `${blocked.length} action${blocked.length === 1 ? "" : "s"} remain outside conditional apply`
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
return {
|
|
3077
|
+
supported: true,
|
|
3078
|
+
checkpointRequired: false,
|
|
3079
|
+
reason: "all actions are low-risk, checkpoint-free Registry changes"
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
// src/config-operation-command.ts
|
|
3084
|
+
var IDEMPOTENCY_KEY = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/;
|
|
3085
|
+
var DEFAULT_WAIT_SECONDS = 60;
|
|
3086
|
+
var DEFAULT_INTERVAL_SECONDS = 2;
|
|
3087
|
+
async function configApply(options) {
|
|
3088
|
+
const plan = readPlan(options.planPath);
|
|
3089
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
3090
|
+
assertPlanContext(plan, cfg.app.id, cfg.platformUrl);
|
|
3091
|
+
const support = configApplySupport(plan);
|
|
3092
|
+
if (!support.supported) {
|
|
3093
|
+
throw new ConfigOperationCommandError(
|
|
3094
|
+
support.reason,
|
|
3095
|
+
support.checkpointRequired ? "checkpoint_required" : "invalid_plan"
|
|
3096
|
+
);
|
|
3097
|
+
}
|
|
3098
|
+
if (configDigest(desiredRegistryState(cfg)) !== plan.desiredRevision) {
|
|
3099
|
+
throw new ConfigOperationCommandError(
|
|
3100
|
+
"project config changed after this plan was frozen; generate and review a fresh plan",
|
|
3101
|
+
"checkpoint_required"
|
|
3102
|
+
);
|
|
3103
|
+
}
|
|
3104
|
+
const idempotencyKey = options.idempotencyKey ?? `cli:${plan.planDigest.slice(7)}`;
|
|
3105
|
+
if (!IDEMPOTENCY_KEY.test(idempotencyKey)) {
|
|
3106
|
+
throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
|
|
3107
|
+
}
|
|
3108
|
+
const client = await operationClient(cfg, options, "apply");
|
|
3109
|
+
const request2 = {
|
|
3110
|
+
schemaVersion: "odla.config-operation-request/v1",
|
|
3111
|
+
expectedRevision: plan.registryRevision,
|
|
3112
|
+
desiredRevision: plan.desiredRevision,
|
|
3113
|
+
observedRevision: plan.observedRevision,
|
|
3114
|
+
planDigest: plan.planDigest,
|
|
3115
|
+
idempotencyKey,
|
|
3116
|
+
source: { kind: "cli", version: cliVersion() },
|
|
3117
|
+
actions: plan.actions
|
|
3118
|
+
};
|
|
3119
|
+
let receipt;
|
|
3120
|
+
try {
|
|
3121
|
+
receipt = await client.applyConfigOperation(cfg.app.id, request2);
|
|
3122
|
+
} catch (error) {
|
|
3123
|
+
const retained = retainedReceipt(error);
|
|
3124
|
+
if (retained) {
|
|
3125
|
+
verifyReceipt(retained, cfg.app.id);
|
|
3126
|
+
printReceipt(retained, options);
|
|
3127
|
+
throw failureForReceipt(retained);
|
|
3128
|
+
}
|
|
3129
|
+
throw normalizeRequestError(error);
|
|
3130
|
+
}
|
|
3131
|
+
verifyReceipt(receipt, cfg.app.id);
|
|
3132
|
+
printReceipt(receipt, options);
|
|
3133
|
+
assertApplyCompleted(receipt);
|
|
3134
|
+
return receipt;
|
|
3135
|
+
}
|
|
3136
|
+
async function configOperationGet(options) {
|
|
3137
|
+
assertOperationId(options.operationId);
|
|
3138
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
3139
|
+
const client = await operationClient(cfg, options, "read");
|
|
3140
|
+
const receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
|
|
3141
|
+
throw normalizeRequestError(error);
|
|
3142
|
+
});
|
|
3143
|
+
if (!receipt) {
|
|
3144
|
+
throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
|
|
3145
|
+
}
|
|
3146
|
+
verifyReceipt(receipt, cfg.app.id, options.operationId);
|
|
3147
|
+
printReceipt(receipt, options);
|
|
3148
|
+
return receipt;
|
|
3149
|
+
}
|
|
3150
|
+
async function configOperationWait(options) {
|
|
3151
|
+
assertOperationId(options.operationId);
|
|
3152
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
3153
|
+
const client = await operationClient(cfg, options, "wait");
|
|
3154
|
+
const wait2 = options.pollWait ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
|
|
3155
|
+
const now = () => (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
3156
|
+
const deadline = now() + (options.timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
|
|
3157
|
+
const interval = (options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS) * 1e3;
|
|
3158
|
+
let receipt = null;
|
|
3159
|
+
for (; ; ) {
|
|
3160
|
+
receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
|
|
3161
|
+
throw normalizeRequestError(error);
|
|
3162
|
+
});
|
|
3163
|
+
if (!receipt) {
|
|
3164
|
+
throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
|
|
3165
|
+
}
|
|
3166
|
+
verifyReceipt(receipt, cfg.app.id, options.operationId);
|
|
3167
|
+
if (receipt.state !== "running") break;
|
|
3168
|
+
if (now() >= deadline) {
|
|
3169
|
+
printReceipt(receipt, options);
|
|
3170
|
+
throw new ConfigOperationCommandError("config operation is still running", "operation_pending");
|
|
3171
|
+
}
|
|
3172
|
+
await wait2(Math.min(interval, Math.max(0, deadline - now())));
|
|
3173
|
+
}
|
|
3174
|
+
printReceipt(receipt, options);
|
|
3175
|
+
if (receipt.state !== "succeeded") throw failureForReceipt(receipt);
|
|
3176
|
+
return receipt;
|
|
3177
|
+
}
|
|
3178
|
+
async function operationClient(cfg, options, purpose) {
|
|
3179
|
+
const doFetch = options.fetch ?? fetch;
|
|
3180
|
+
const out = options.stdout ?? console;
|
|
3181
|
+
const token = await resolveAdminPlatformToken({
|
|
3182
|
+
platform: cfg.platformUrl,
|
|
3183
|
+
scope: "app:config:write",
|
|
3184
|
+
token: options.token,
|
|
3185
|
+
tokenFile: (0, import_node_path7.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3186
|
+
rootDir: cfg.rootDir,
|
|
3187
|
+
email: options.email,
|
|
3188
|
+
open: options.open,
|
|
3189
|
+
fetch: doFetch,
|
|
3190
|
+
stdout: out,
|
|
3191
|
+
openApprovalUrl: options.openApprovalUrl,
|
|
3192
|
+
label: `odla CLI (${cfg.app.id} config operation ${purpose})`
|
|
3193
|
+
});
|
|
3194
|
+
return (0, import_apps6.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
3195
|
+
}
|
|
3196
|
+
function printReceipt(receipt, options) {
|
|
3197
|
+
const out = options.stdout ?? console;
|
|
3198
|
+
if (options.json) out.log(JSON.stringify(receipt, null, 2));
|
|
3199
|
+
else {
|
|
3200
|
+
out.log(`config operation ${receipt.operationId}: ${receipt.state}`);
|
|
3201
|
+
out.log(`revision: ${receipt.expectedRevision} \u2192 ${receipt.currentRevision}`);
|
|
3202
|
+
for (const step of receipt.progress) out.log(` ${step.state} ${step.actionId} attempts=${step.attempts}`);
|
|
3203
|
+
out.log(`receipt: ${receipt.receiptDigest ?? "pending"}`);
|
|
3204
|
+
out.log(`studio: ${receipt.studioUrl}`);
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
function assertApplyCompleted(receipt) {
|
|
3208
|
+
if (receipt.state === "succeeded") return;
|
|
3209
|
+
throw receipt.state === "running" ? new ConfigOperationCommandError("config operation is still running", "operation_pending") : failureForReceipt(receipt);
|
|
3210
|
+
}
|
|
3211
|
+
function failureForReceipt(receipt) {
|
|
3212
|
+
if (receipt.state === "conflict") return new ConfigOperationCommandError(
|
|
3213
|
+
receipt.error?.message ?? "config operation conflicted with current Registry state",
|
|
3214
|
+
"checkpoint_required"
|
|
3215
|
+
);
|
|
3216
|
+
if (receipt.error?.retryable) return new ConfigOperationCommandError(receipt.error.message, "remote_unavailable");
|
|
3217
|
+
return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
|
|
3218
|
+
}
|
|
3219
|
+
function retainedReceipt(error) {
|
|
3220
|
+
if (!(error instanceof import_apps6.AppsError) || !record3(error.details)) return null;
|
|
3221
|
+
return record3(error.details.operation) ? error.details.operation : null;
|
|
3222
|
+
}
|
|
3223
|
+
function normalizeRequestError(error) {
|
|
3224
|
+
if (!(error instanceof import_apps6.AppsError)) return error instanceof Error ? error : new Error(String(error));
|
|
3225
|
+
if (error.status === 401 || error.status === 403) {
|
|
3226
|
+
return new ConfigOperationCommandError(error.message, "auth_failed");
|
|
3227
|
+
}
|
|
3228
|
+
if (error.status === 409) return new ConfigOperationCommandError(error.message, "checkpoint_required");
|
|
3229
|
+
if (error.status === 429 || error.status >= 500) {
|
|
3230
|
+
return new ConfigOperationCommandError(error.message, "remote_unavailable");
|
|
3231
|
+
}
|
|
3232
|
+
return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
|
|
3233
|
+
}
|
|
3234
|
+
function record3(value2) {
|
|
3235
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3238
|
+
// src/config-reconcile-command.ts
|
|
3239
|
+
var import_apps8 = require("@odla-ai/apps");
|
|
3240
|
+
var import_node_path8 = require("path");
|
|
3241
|
+
|
|
2868
3242
|
// src/config-reconcile.ts
|
|
2869
|
-
var
|
|
3243
|
+
var import_apps7 = require("@odla-ai/apps");
|
|
2870
3244
|
|
|
2871
3245
|
// src/config-reconcile-values.ts
|
|
2872
3246
|
function difference(path, desired, observed, status, reason, desiredSource, observedSource, env, service) {
|
|
@@ -2967,6 +3341,7 @@ function reconcileConfig(input) {
|
|
|
2967
3341
|
sources: { desired: desiredSource, observed: observedSource },
|
|
2968
3342
|
desiredRevision,
|
|
2969
3343
|
observedRevision,
|
|
3344
|
+
registryRevision: input.observed?.configRevision ?? null,
|
|
2970
3345
|
status: different ? "different" : unmanaged ? "unmanaged" : "in_sync",
|
|
2971
3346
|
summary: {
|
|
2972
3347
|
differences: different,
|
|
@@ -3021,7 +3396,7 @@ function compareEnvironments(desired, observed, desiredSource, observedSource, a
|
|
|
3021
3396
|
function compareServices(env, desired, observed, desiredSource, observedSource, add) {
|
|
3022
3397
|
const wanted = desired.environments[env]?.services ?? {};
|
|
3023
3398
|
const live = observed?.environments[env] ?? {};
|
|
3024
|
-
const knownOrder = (0,
|
|
3399
|
+
const knownOrder = (0, import_apps7.orderAppServices)((0, import_apps7.appServiceIds)());
|
|
3025
3400
|
const services = [.../* @__PURE__ */ new Set([...knownOrder, ...Object.keys(wanted), ...Object.keys(live)])];
|
|
3026
3401
|
for (const service of services) {
|
|
3027
3402
|
const next = wanted[service];
|
|
@@ -3132,20 +3507,19 @@ async function configDiff(options) {
|
|
|
3132
3507
|
}
|
|
3133
3508
|
async function configPlan(options) {
|
|
3134
3509
|
const reconciliation = await inspectConfig(options);
|
|
3510
|
+
const apply = configApplySupport(reconciliation);
|
|
3135
3511
|
const planDigest = configDigest({
|
|
3136
|
-
schemaVersion: "odla.config-plan/
|
|
3512
|
+
schemaVersion: "odla.config-plan/v2",
|
|
3137
3513
|
desiredRevision: reconciliation.desiredRevision,
|
|
3138
3514
|
observedRevision: reconciliation.observedRevision,
|
|
3515
|
+
registryRevision: reconciliation.registryRevision,
|
|
3139
3516
|
actions: reconciliation.actions
|
|
3140
3517
|
});
|
|
3141
3518
|
const document2 = {
|
|
3142
|
-
schemaVersion: "odla.config-plan/
|
|
3519
|
+
schemaVersion: "odla.config-plan/v2",
|
|
3143
3520
|
...reconciliation,
|
|
3144
3521
|
planDigest,
|
|
3145
|
-
apply
|
|
3146
|
-
supported: false,
|
|
3147
|
-
reason: "conditional, resumable config apply is not available yet; use the exact reviewed commands below"
|
|
3148
|
-
},
|
|
3522
|
+
apply,
|
|
3149
3523
|
nextActions: planNextActions(reconciliation, options.configPath)
|
|
3150
3524
|
};
|
|
3151
3525
|
printPlan2(document2, options);
|
|
@@ -3159,7 +3533,7 @@ async function inspectConfig(options) {
|
|
|
3159
3533
|
platform: cfg.platformUrl,
|
|
3160
3534
|
scope: "app:config:read",
|
|
3161
3535
|
token: options.token,
|
|
3162
|
-
tokenFile: (0,
|
|
3536
|
+
tokenFile: (0, import_node_path8.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3163
3537
|
rootDir: cfg.rootDir,
|
|
3164
3538
|
email: options.email,
|
|
3165
3539
|
open: options.open,
|
|
@@ -3168,7 +3542,7 @@ async function inspectConfig(options) {
|
|
|
3168
3542
|
openApprovalUrl: options.openApprovalUrl,
|
|
3169
3543
|
label: `odla CLI (${cfg.app.id} config read)`
|
|
3170
3544
|
});
|
|
3171
|
-
const client = (0,
|
|
3545
|
+
const client = (0, import_apps8.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
3172
3546
|
const observed = await client.resolveApp(cfg.app.id);
|
|
3173
3547
|
return reconcileConfig({
|
|
3174
3548
|
desired: desiredRegistryState(cfg),
|
|
@@ -3204,7 +3578,7 @@ function printPlan2(document2, options) {
|
|
|
3204
3578
|
}
|
|
3205
3579
|
if (!document2.actions.length) out.log(" no managed changes");
|
|
3206
3580
|
out.log(`plan digest: ${document2.planDigest}`);
|
|
3207
|
-
out.log(`apply:
|
|
3581
|
+
out.log(`apply: ${document2.apply.supported ? "supported" : "blocked"} \u2014 ${document2.apply.reason}`);
|
|
3208
3582
|
printNext(out, document2.nextActions);
|
|
3209
3583
|
}
|
|
3210
3584
|
function printHeader(out, kind, document2) {
|
|
@@ -3212,6 +3586,7 @@ function printHeader(out, kind, document2) {
|
|
|
3212
3586
|
out.log(`platform: ${document2.scope.platformUrl}`);
|
|
3213
3587
|
out.log(`desired: ${document2.desiredRevision}`);
|
|
3214
3588
|
out.log(`observed: ${document2.observedRevision ?? "absent"}`);
|
|
3589
|
+
out.log(`registry: ${document2.registryRevision ?? "absent"}`);
|
|
3215
3590
|
out.log(
|
|
3216
3591
|
`summary: ${document2.summary.differences} different, ${document2.summary.unmanaged} unmanaged, ${document2.summary.actions} planned actions`
|
|
3217
3592
|
);
|
|
@@ -3244,6 +3619,13 @@ function diffNextActions(reconciliation, configPath) {
|
|
|
3244
3619
|
}
|
|
3245
3620
|
function planNextActions(reconciliation, configPath) {
|
|
3246
3621
|
const next = [];
|
|
3622
|
+
if (configApplySupport(reconciliation).supported) {
|
|
3623
|
+
next.push({
|
|
3624
|
+
code: "apply_frozen_plan",
|
|
3625
|
+
command: "odla-ai config apply --plan <saved-plan.json> --json",
|
|
3626
|
+
description: "Save this JSON document, then conditionally apply these exact revision-bound actions."
|
|
3627
|
+
});
|
|
3628
|
+
}
|
|
3247
3629
|
if (reconciliation.actions.some((action2) => action2.applySupport === "provision")) {
|
|
3248
3630
|
next.push({
|
|
3249
3631
|
code: "review_provision",
|
|
@@ -3257,35 +3639,39 @@ function planNextActions(reconciliation, configPath) {
|
|
|
3257
3639
|
}
|
|
3258
3640
|
}
|
|
3259
3641
|
if (reconciliation.actions.some((action2) => action2.applySupport === "studio")) {
|
|
3260
|
-
const { appId, platformUrl } = reconciliation.scope;
|
|
3261
3642
|
next.push({
|
|
3262
3643
|
code: "review_destructive",
|
|
3263
|
-
command:
|
|
3644
|
+
command: studioSettingsUrl(reconciliation),
|
|
3264
3645
|
description: "Review service disablement in the owning Studio scope; this plan will not apply it."
|
|
3265
3646
|
});
|
|
3266
3647
|
}
|
|
3267
3648
|
if (!reconciliation.actions.length && reconciliation.summary.unmanaged) {
|
|
3268
3649
|
next.push({
|
|
3269
3650
|
code: "declare_or_accept_runtime_state",
|
|
3270
|
-
command:
|
|
3651
|
+
command: studioSettingsUrl(reconciliation),
|
|
3271
3652
|
description: "Declare the live value in project config or keep it as an explicit runtime-managed setting."
|
|
3272
3653
|
});
|
|
3273
3654
|
}
|
|
3274
3655
|
return next;
|
|
3275
3656
|
}
|
|
3657
|
+
function studioSettingsUrl(reconciliation) {
|
|
3658
|
+
const environment2 = reconciliation.actions.map((action2) => action2.env).concat(reconciliation.scope.environments).find((candidate) => candidate === "dev" || candidate === "prod") ?? "dev";
|
|
3659
|
+
const origin = reconciliation.scope.platformUrl.replace(/\/$/, "");
|
|
3660
|
+
return `${origin}${(0, import_apps8.studioAppSettingsPath)(reconciliation.scope.appId, environment2, "environment")}`;
|
|
3661
|
+
}
|
|
3276
3662
|
function quoteArg2(value2) {
|
|
3277
3663
|
return `'${value2.replace(/'/g, `'\\''`)}'`;
|
|
3278
3664
|
}
|
|
3279
3665
|
|
|
3280
3666
|
// src/doctor-checks.ts
|
|
3281
3667
|
var import_node_child_process3 = require("child_process");
|
|
3282
|
-
var
|
|
3283
|
-
var
|
|
3668
|
+
var import_node_fs12 = require("fs");
|
|
3669
|
+
var import_node_path10 = require("path");
|
|
3284
3670
|
|
|
3285
3671
|
// src/wrangler.ts
|
|
3286
3672
|
var import_node_child_process2 = require("child_process");
|
|
3287
|
-
var
|
|
3288
|
-
var
|
|
3673
|
+
var import_node_fs11 = require("fs");
|
|
3674
|
+
var import_node_path9 = require("path");
|
|
3289
3675
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
3290
3676
|
const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
3291
3677
|
let stdout = "";
|
|
@@ -3299,28 +3685,28 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
|
|
|
3299
3685
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
3300
3686
|
function findWranglerConfig(rootDir) {
|
|
3301
3687
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
3302
|
-
const path = (0,
|
|
3303
|
-
if ((0,
|
|
3688
|
+
const path = (0, import_node_path9.join)(rootDir, name);
|
|
3689
|
+
if ((0, import_node_fs11.existsSync)(path)) return path;
|
|
3304
3690
|
}
|
|
3305
3691
|
return null;
|
|
3306
3692
|
}
|
|
3307
3693
|
function readWranglerConfig(path) {
|
|
3308
3694
|
if (path.endsWith(".toml")) return null;
|
|
3309
3695
|
try {
|
|
3310
|
-
return JSON.parse(stripJsonComments((0,
|
|
3696
|
+
return JSON.parse(stripJsonComments((0, import_node_fs11.readFileSync)(path, "utf8")));
|
|
3311
3697
|
} catch {
|
|
3312
3698
|
return null;
|
|
3313
3699
|
}
|
|
3314
3700
|
}
|
|
3315
|
-
function stripJsonComments(
|
|
3701
|
+
function stripJsonComments(text2) {
|
|
3316
3702
|
let result = "";
|
|
3317
3703
|
let inString = false;
|
|
3318
|
-
for (let i = 0; i <
|
|
3319
|
-
const ch =
|
|
3704
|
+
for (let i = 0; i < text2.length; i++) {
|
|
3705
|
+
const ch = text2[i];
|
|
3320
3706
|
if (inString) {
|
|
3321
3707
|
result += ch;
|
|
3322
3708
|
if (ch === "\\") {
|
|
3323
|
-
result +=
|
|
3709
|
+
result += text2[i + 1] ?? "";
|
|
3324
3710
|
i++;
|
|
3325
3711
|
} else if (ch === '"') {
|
|
3326
3712
|
inString = false;
|
|
@@ -3332,14 +3718,14 @@ function stripJsonComments(text) {
|
|
|
3332
3718
|
result += ch;
|
|
3333
3719
|
continue;
|
|
3334
3720
|
}
|
|
3335
|
-
if (ch === "/" &&
|
|
3336
|
-
while (i <
|
|
3721
|
+
if (ch === "/" && text2[i + 1] === "/") {
|
|
3722
|
+
while (i < text2.length && text2[i] !== "\n") i++;
|
|
3337
3723
|
result += "\n";
|
|
3338
3724
|
continue;
|
|
3339
3725
|
}
|
|
3340
|
-
if (ch === "/" &&
|
|
3726
|
+
if (ch === "/" && text2[i + 1] === "*") {
|
|
3341
3727
|
i += 2;
|
|
3342
|
-
while (i <
|
|
3728
|
+
while (i < text2.length && !(text2[i] === "*" && text2[i + 1] === "/")) i++;
|
|
3343
3729
|
i++;
|
|
3344
3730
|
continue;
|
|
3345
3731
|
}
|
|
@@ -3356,7 +3742,14 @@ async function wranglerLoggedIn(run, cwd) {
|
|
|
3356
3742
|
}
|
|
3357
3743
|
}
|
|
3358
3744
|
function wranglerPutSecret(run, opts) {
|
|
3359
|
-
const args = [
|
|
3745
|
+
const args = [
|
|
3746
|
+
"wrangler",
|
|
3747
|
+
"secret",
|
|
3748
|
+
"put",
|
|
3749
|
+
opts.name,
|
|
3750
|
+
...opts.env ? ["--env", opts.env] : [],
|
|
3751
|
+
...opts.configPath ? ["--config", opts.configPath] : []
|
|
3752
|
+
];
|
|
3360
3753
|
return run("npx", args, { input: opts.value, cwd: opts.cwd });
|
|
3361
3754
|
}
|
|
3362
3755
|
|
|
@@ -3405,10 +3798,10 @@ function wranglerWarnings(rootDir) {
|
|
|
3405
3798
|
for (const { label, block } of blocks) {
|
|
3406
3799
|
const assets = block.assets;
|
|
3407
3800
|
if (assets?.directory) {
|
|
3408
|
-
const dir = (0,
|
|
3409
|
-
if (dir === (0,
|
|
3801
|
+
const dir = (0, import_node_path10.resolve)(rootDir, assets.directory);
|
|
3802
|
+
if (dir === (0, import_node_path10.resolve)(rootDir)) {
|
|
3410
3803
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
3411
|
-
} else if ((0,
|
|
3804
|
+
} else if ((0, import_node_fs12.existsSync)((0, import_node_path10.join)(dir, "node_modules"))) {
|
|
3412
3805
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
3413
3806
|
}
|
|
3414
3807
|
}
|
|
@@ -3443,13 +3836,13 @@ function o11yProjectWarnings(rootDir) {
|
|
|
3443
3836
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
|
|
3444
3837
|
return warnings;
|
|
3445
3838
|
}
|
|
3446
|
-
const main = typeof config.main === "string" ? (0,
|
|
3447
|
-
if (!main || !(0,
|
|
3839
|
+
const main = typeof config.main === "string" ? (0, import_node_path10.resolve)(rootDir, config.main) : null;
|
|
3840
|
+
if (!main || !(0, import_node_fs12.existsSync)(main)) {
|
|
3448
3841
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
|
|
3449
3842
|
} else {
|
|
3450
3843
|
let source = "";
|
|
3451
3844
|
try {
|
|
3452
|
-
source = (0,
|
|
3845
|
+
source = (0, import_node_fs12.readFileSync)(main, "utf8");
|
|
3453
3846
|
} catch {
|
|
3454
3847
|
}
|
|
3455
3848
|
if (!/\bwithObservability\b/.test(source)) {
|
|
@@ -3473,7 +3866,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
3473
3866
|
}
|
|
3474
3867
|
function readPackageJson(rootDir) {
|
|
3475
3868
|
try {
|
|
3476
|
-
return JSON.parse((0,
|
|
3869
|
+
return JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(rootDir, "package.json"), "utf8"));
|
|
3477
3870
|
} catch {
|
|
3478
3871
|
return null;
|
|
3479
3872
|
}
|
|
@@ -3700,14 +4093,14 @@ function harnessOption(value2, flag) {
|
|
|
3700
4093
|
}
|
|
3701
4094
|
|
|
3702
4095
|
// src/init.ts
|
|
3703
|
-
var
|
|
3704
|
-
var
|
|
3705
|
-
var
|
|
4096
|
+
var import_node_fs13 = require("fs");
|
|
4097
|
+
var import_node_path11 = require("path");
|
|
4098
|
+
var import_apps9 = require("@odla-ai/apps");
|
|
3706
4099
|
function initProject(options) {
|
|
3707
4100
|
const out = options.stdout ?? console;
|
|
3708
|
-
const rootDir = (0,
|
|
3709
|
-
const configPath = (0,
|
|
3710
|
-
if ((0,
|
|
4101
|
+
const rootDir = (0, import_node_path11.resolve)(options.rootDir ?? process.cwd());
|
|
4102
|
+
const configPath = (0, import_node_path11.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
|
|
4103
|
+
if ((0, import_node_fs13.existsSync)(configPath) && !options.force) {
|
|
3711
4104
|
throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
|
|
3712
4105
|
}
|
|
3713
4106
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
|
|
@@ -3716,27 +4109,27 @@ function initProject(options) {
|
|
|
3716
4109
|
const envs = options.envs?.length ? options.envs : ["dev"];
|
|
3717
4110
|
const services = options.services?.length ? options.services : ["db", "ai"];
|
|
3718
4111
|
for (const service of services) {
|
|
3719
|
-
const definition = (0,
|
|
3720
|
-
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0,
|
|
4112
|
+
const definition = (0, import_apps9.appServiceDefinition)(service);
|
|
4113
|
+
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps9.appServiceIds)().join(", ")})`);
|
|
3721
4114
|
for (const dependency of definition.requires) {
|
|
3722
4115
|
if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
|
|
3723
4116
|
}
|
|
3724
4117
|
}
|
|
3725
4118
|
const aiProvider = options.aiProvider ?? "anthropic";
|
|
3726
|
-
(0,
|
|
3727
|
-
(0,
|
|
3728
|
-
(0,
|
|
3729
|
-
(0,
|
|
3730
|
-
writeIfMissing((0,
|
|
3731
|
-
writeIfMissing((0,
|
|
4119
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(configPath), { recursive: true });
|
|
4120
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path11.resolve)(rootDir, "src/odla"), { recursive: true });
|
|
4121
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path11.resolve)(rootDir, ".odla"), { recursive: true });
|
|
4122
|
+
(0, import_node_fs13.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
|
|
4123
|
+
writeIfMissing((0, import_node_path11.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
|
|
4124
|
+
writeIfMissing((0, import_node_path11.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
|
|
3732
4125
|
ensureGitignore(rootDir);
|
|
3733
4126
|
out.log(`created ${relativeDisplay(configPath, rootDir)}`);
|
|
3734
4127
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
3735
4128
|
out.log("updated .gitignore for local odla credentials");
|
|
3736
4129
|
}
|
|
3737
|
-
function writeIfMissing(path,
|
|
3738
|
-
if ((0,
|
|
3739
|
-
(0,
|
|
4130
|
+
function writeIfMissing(path, text2) {
|
|
4131
|
+
if ((0, import_node_fs13.existsSync)(path)) return;
|
|
4132
|
+
(0, import_node_fs13.writeFileSync)(path, text2);
|
|
3740
4133
|
}
|
|
3741
4134
|
function configTemplate(input) {
|
|
3742
4135
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -3911,7 +4304,7 @@ function assertWranglerConfig(cfg) {
|
|
|
3911
4304
|
|
|
3912
4305
|
// src/secrets-set.ts
|
|
3913
4306
|
var import_ai2 = require("@odla-ai/ai");
|
|
3914
|
-
var
|
|
4307
|
+
var import_apps10 = require("@odla-ai/apps");
|
|
3915
4308
|
var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
|
|
3916
4309
|
async function secretsSet(options) {
|
|
3917
4310
|
const name = (options.name ?? "").trim();
|
|
@@ -3944,13 +4337,13 @@ async function secretsSetClerkKey(options) {
|
|
|
3944
4337
|
body: JSON.stringify({ value: value2 })
|
|
3945
4338
|
});
|
|
3946
4339
|
if (!res.ok) {
|
|
3947
|
-
const
|
|
3948
|
-
throw new Error(`store Clerk secret key failed (${res.status}): ${
|
|
4340
|
+
const text2 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
4341
|
+
throw new Error(`store Clerk secret key failed (${res.status}): ${text2 || "request failed"}`);
|
|
3949
4342
|
}
|
|
3950
4343
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
3951
4344
|
}
|
|
3952
|
-
function scrubValue(
|
|
3953
|
-
return redactSecrets(
|
|
4345
|
+
function scrubValue(text2, value2) {
|
|
4346
|
+
return redactSecrets(text2).split(value2).join("[value redacted]");
|
|
3954
4347
|
}
|
|
3955
4348
|
async function resolveVaultWrite(options) {
|
|
3956
4349
|
const out = options.stdout ?? console;
|
|
@@ -3963,13 +4356,13 @@ async function resolveVaultWrite(options) {
|
|
|
3963
4356
|
throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
|
|
3964
4357
|
}
|
|
3965
4358
|
const value2 = await secretInputValue(options, "secret");
|
|
3966
|
-
return { cfg, tenantId: (0,
|
|
4359
|
+
return { cfg, tenantId: (0, import_apps10.tenantIdFor)(cfg.app.id, options.env), value: value2, doFetch, out };
|
|
3967
4360
|
}
|
|
3968
4361
|
|
|
3969
4362
|
// src/skill.ts
|
|
3970
|
-
var
|
|
4363
|
+
var import_node_fs14 = require("fs");
|
|
3971
4364
|
var import_node_os2 = require("os");
|
|
3972
|
-
var
|
|
4365
|
+
var import_node_path12 = require("path");
|
|
3973
4366
|
var import_node_url2 = require("url");
|
|
3974
4367
|
|
|
3975
4368
|
// src/skill-adapters.ts
|
|
@@ -4048,8 +4441,8 @@ function installSkill(options = {}) {
|
|
|
4048
4441
|
const files = listFiles(sourceDir);
|
|
4049
4442
|
if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
|
|
4050
4443
|
const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
|
|
4051
|
-
const root = (0,
|
|
4052
|
-
const home = (0,
|
|
4444
|
+
const root = (0, import_node_path12.resolve)(options.dir ?? process.cwd());
|
|
4445
|
+
const home = (0, import_node_path12.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
|
|
4053
4446
|
const plans = /* @__PURE__ */ new Map();
|
|
4054
4447
|
const targets = /* @__PURE__ */ new Map();
|
|
4055
4448
|
const rememberTarget = (harness, target) => {
|
|
@@ -4063,48 +4456,48 @@ function installSkill(options = {}) {
|
|
|
4063
4456
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
4064
4457
|
};
|
|
4065
4458
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
4066
|
-
for (const rel of files) plan((0,
|
|
4459
|
+
for (const rel of files) plan((0, import_node_path12.join)(targetDir2, rel), (0, import_node_fs14.readFileSync)((0, import_node_path12.join)(sourceDir, rel), "utf8"), false, boundary);
|
|
4067
4460
|
};
|
|
4068
4461
|
let targetDir;
|
|
4069
4462
|
if (options.global) {
|
|
4070
|
-
const claudeRoot = (0,
|
|
4071
|
-
const codexRoot = (0,
|
|
4463
|
+
const claudeRoot = (0, import_node_path12.join)(home, ".claude", "skills");
|
|
4464
|
+
const codexRoot = (0, import_node_path12.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path12.join)(home, ".codex"), "skills");
|
|
4072
4465
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
4073
4466
|
for (const harness of harnesses) {
|
|
4074
4467
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
4075
|
-
planSkillTree(skillRoot, harness === "claude" ? home : (0,
|
|
4468
|
+
planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path12.dirname)((0, import_node_path12.dirname)(codexRoot)));
|
|
4076
4469
|
rememberTarget(harness, skillRoot);
|
|
4077
4470
|
}
|
|
4078
4471
|
} else {
|
|
4079
|
-
const sharedRoot = (0,
|
|
4472
|
+
const sharedRoot = (0, import_node_path12.join)(root, ".agents", "skills");
|
|
4080
4473
|
planSkillTree(sharedRoot);
|
|
4081
|
-
const claudeRoot = (0,
|
|
4474
|
+
const claudeRoot = (0, import_node_path12.join)(root, ".claude", "skills");
|
|
4082
4475
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
4083
4476
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
4084
4477
|
if (harnesses.includes("claude")) {
|
|
4085
4478
|
for (const skill of skillNames(files)) {
|
|
4086
|
-
const canonical = (0,
|
|
4087
|
-
plan((0,
|
|
4479
|
+
const canonical = (0, import_node_fs14.readFileSync)((0, import_node_path12.join)(sourceDir, skill, "SKILL.md"), "utf8");
|
|
4480
|
+
plan((0, import_node_path12.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
|
|
4088
4481
|
}
|
|
4089
4482
|
rememberTarget("claude", claudeRoot);
|
|
4090
4483
|
}
|
|
4091
4484
|
if (harnesses.includes("cursor")) {
|
|
4092
|
-
const cursorRule = (0,
|
|
4485
|
+
const cursorRule = (0, import_node_path12.join)(root, ".cursor", "rules", "odla.mdc");
|
|
4093
4486
|
plan(cursorRule, CURSOR_RULE);
|
|
4094
4487
|
rememberTarget("cursor", cursorRule);
|
|
4095
4488
|
}
|
|
4096
4489
|
if (harnesses.includes("agents")) {
|
|
4097
|
-
const agentsFile = (0,
|
|
4490
|
+
const agentsFile = (0, import_node_path12.join)(root, "AGENTS.md");
|
|
4098
4491
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4099
4492
|
rememberTarget("agents", agentsFile);
|
|
4100
4493
|
}
|
|
4101
4494
|
if (harnesses.includes("copilot")) {
|
|
4102
|
-
const copilotFile = (0,
|
|
4495
|
+
const copilotFile = (0, import_node_path12.join)(root, ".github", "copilot-instructions.md");
|
|
4103
4496
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4104
4497
|
rememberTarget("copilot", copilotFile);
|
|
4105
4498
|
}
|
|
4106
4499
|
if (harnesses.includes("gemini")) {
|
|
4107
|
-
const geminiFile = (0,
|
|
4500
|
+
const geminiFile = (0, import_node_path12.join)(root, "GEMINI.md");
|
|
4108
4501
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4109
4502
|
rememberTarget("gemini", geminiFile);
|
|
4110
4503
|
}
|
|
@@ -4118,11 +4511,11 @@ function installSkill(options = {}) {
|
|
|
4118
4511
|
conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
|
|
4119
4512
|
continue;
|
|
4120
4513
|
}
|
|
4121
|
-
if (!(0,
|
|
4514
|
+
if (!(0, import_node_fs14.existsSync)(file.target)) {
|
|
4122
4515
|
writtenPaths.add(file.target);
|
|
4123
4516
|
continue;
|
|
4124
4517
|
}
|
|
4125
|
-
const current = (0,
|
|
4518
|
+
const current = (0, import_node_fs14.readFileSync)(file.target, "utf8");
|
|
4126
4519
|
if (current === file.content) {
|
|
4127
4520
|
unchangedPaths.add(file.target);
|
|
4128
4521
|
} else if (file.managedMerge || options.force) {
|
|
@@ -4139,9 +4532,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
4139
4532
|
);
|
|
4140
4533
|
}
|
|
4141
4534
|
for (const file of plans.values()) {
|
|
4142
|
-
if (!(0,
|
|
4143
|
-
(0,
|
|
4144
|
-
(0,
|
|
4535
|
+
if (!(0, import_node_fs14.existsSync)(file.target) || (0, import_node_fs14.readFileSync)(file.target, "utf8") !== file.content) {
|
|
4536
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(file.target), { recursive: true });
|
|
4537
|
+
(0, import_node_fs14.writeFileSync)(file.target, file.content);
|
|
4145
4538
|
}
|
|
4146
4539
|
}
|
|
4147
4540
|
const skills = skillNames(files);
|
|
@@ -4160,7 +4553,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
4160
4553
|
};
|
|
4161
4554
|
}
|
|
4162
4555
|
function pathsUnder(root, paths) {
|
|
4163
|
-
return [...paths].map((path) => (0,
|
|
4556
|
+
return [...paths].map((path) => (0, import_node_path12.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path12.sep}`) && !(0, import_node_path12.isAbsolute)(path)).sort();
|
|
4164
4557
|
}
|
|
4165
4558
|
function normalizeHarnesses(values, global) {
|
|
4166
4559
|
const requested = values?.length ? values : ["claude"];
|
|
@@ -4182,9 +4575,9 @@ function normalizeHarnesses(values, global) {
|
|
|
4182
4575
|
function managedFileContent(path, block, force, boundary) {
|
|
4183
4576
|
const symlink = symlinkedComponent(boundary, path);
|
|
4184
4577
|
if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
|
|
4185
|
-
if (!(0,
|
|
4578
|
+
if (!(0, import_node_fs14.existsSync)(path)) return `${block}
|
|
4186
4579
|
`;
|
|
4187
|
-
const current = (0,
|
|
4580
|
+
const current = (0, import_node_fs14.readFileSync)(path, "utf8");
|
|
4188
4581
|
const start = "<!-- odla-ai agent setup:start -->";
|
|
4189
4582
|
const end = "<!-- odla-ai agent setup:end -->";
|
|
4190
4583
|
const startAt = current.indexOf(start);
|
|
@@ -4205,15 +4598,15 @@ function managedFileContent(path, block, force, boundary) {
|
|
|
4205
4598
|
return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
|
|
4206
4599
|
}
|
|
4207
4600
|
function symlinkedComponent(boundary, target) {
|
|
4208
|
-
const rel = (0,
|
|
4209
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
4601
|
+
const rel = (0, import_node_path12.relative)(boundary, target);
|
|
4602
|
+
if (rel === ".." || rel.startsWith(`..${import_node_path12.sep}`) || (0, import_node_path12.isAbsolute)(rel)) {
|
|
4210
4603
|
throw new Error(`agent setup target escapes its install root: ${target}`);
|
|
4211
4604
|
}
|
|
4212
4605
|
let current = boundary;
|
|
4213
|
-
for (const part of rel.split(
|
|
4214
|
-
current = (0,
|
|
4606
|
+
for (const part of rel.split(import_node_path12.sep).filter(Boolean)) {
|
|
4607
|
+
current = (0, import_node_path12.join)(current, part);
|
|
4215
4608
|
try {
|
|
4216
|
-
if ((0,
|
|
4609
|
+
if ((0, import_node_fs14.lstatSync)(current).isSymbolicLink()) return current;
|
|
4217
4610
|
} catch (error) {
|
|
4218
4611
|
if (error.code !== "ENOENT") throw error;
|
|
4219
4612
|
}
|
|
@@ -4224,13 +4617,13 @@ function skillNames(files) {
|
|
|
4224
4617
|
return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
|
|
4225
4618
|
}
|
|
4226
4619
|
function listFiles(dir) {
|
|
4227
|
-
if (!(0,
|
|
4620
|
+
if (!(0, import_node_fs14.existsSync)(dir)) return [];
|
|
4228
4621
|
const results = [];
|
|
4229
4622
|
const walk = (current) => {
|
|
4230
|
-
for (const entry of (0,
|
|
4231
|
-
const path = (0,
|
|
4623
|
+
for (const entry of (0, import_node_fs14.readdirSync)(current, { withFileTypes: true })) {
|
|
4624
|
+
const path = (0, import_node_path12.join)(current, entry.name);
|
|
4232
4625
|
if (entry.isDirectory()) walk(path);
|
|
4233
|
-
else results.push((0,
|
|
4626
|
+
else results.push((0, import_node_path12.relative)(dir, path));
|
|
4234
4627
|
}
|
|
4235
4628
|
};
|
|
4236
4629
|
walk(dir);
|
|
@@ -4425,10 +4818,14 @@ async function secretsCommand(parsed, deps) {
|
|
|
4425
4818
|
async function projectCommand(command, parsed, deps) {
|
|
4426
4819
|
if (command === "config") {
|
|
4427
4820
|
const sub = parsed.positionals[1];
|
|
4428
|
-
if (sub !== "diff" && sub !== "plan") {
|
|
4821
|
+
if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
|
|
4429
4822
|
throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
|
|
4430
4823
|
}
|
|
4431
|
-
assertArgs(
|
|
4824
|
+
assertArgs(
|
|
4825
|
+
parsed,
|
|
4826
|
+
sub === "apply" ? ["config", "plan", "idempotency-key", "token", "email", "open", "json"] : ["config", "token", "email", "open", "json"],
|
|
4827
|
+
2
|
|
4828
|
+
);
|
|
4432
4829
|
const options = {
|
|
4433
4830
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
4434
4831
|
token: stringOpt(parsed.options.token),
|
|
@@ -4440,7 +4837,42 @@ async function projectCommand(command, parsed, deps) {
|
|
|
4440
4837
|
stdout: deps.stdout
|
|
4441
4838
|
};
|
|
4442
4839
|
if (sub === "diff") await configDiff(options);
|
|
4443
|
-
else await configPlan(options);
|
|
4840
|
+
else if (sub === "plan") await configPlan(options);
|
|
4841
|
+
else await configApply({
|
|
4842
|
+
...options,
|
|
4843
|
+
planPath: requiredString(parsed.options.plan, "--plan"),
|
|
4844
|
+
idempotencyKey: stringOpt(parsed.options["idempotency-key"])
|
|
4845
|
+
});
|
|
4846
|
+
return true;
|
|
4847
|
+
}
|
|
4848
|
+
if (command === "operations") {
|
|
4849
|
+
const sub = parsed.positionals[1];
|
|
4850
|
+
if (sub !== "get" && sub !== "wait") {
|
|
4851
|
+
throw new Error(`unknown operations subcommand "${sub ?? ""}". Try "odla-ai operations get <operation-id> --json".`);
|
|
4852
|
+
}
|
|
4853
|
+
assertArgs(
|
|
4854
|
+
parsed,
|
|
4855
|
+
sub === "wait" ? ["config", "token", "email", "open", "json", "interval", "timeout"] : ["config", "token", "email", "open", "json"],
|
|
4856
|
+
3
|
|
4857
|
+
);
|
|
4858
|
+
const operationId = parsed.positionals[2];
|
|
4859
|
+
if (!operationId) throw new Error(`operation id is required`);
|
|
4860
|
+
const options = {
|
|
4861
|
+
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
4862
|
+
operationId,
|
|
4863
|
+
token: stringOpt(parsed.options.token),
|
|
4864
|
+
email: stringOpt(parsed.options.email),
|
|
4865
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
4866
|
+
json: parsed.options.json === true,
|
|
4867
|
+
fetch: deps.fetch,
|
|
4868
|
+
openApprovalUrl: deps.openUrl,
|
|
4869
|
+
stdout: deps.stdout,
|
|
4870
|
+
pollWait: deps.pollWait,
|
|
4871
|
+
intervalSeconds: numberOpt(parsed.options.interval, "--interval"),
|
|
4872
|
+
timeoutSeconds: numberOpt(parsed.options.timeout, "--timeout")
|
|
4873
|
+
};
|
|
4874
|
+
if (sub === "get") await configOperationGet(options);
|
|
4875
|
+
else await configOperationWait(options);
|
|
4444
4876
|
return true;
|
|
4445
4877
|
}
|
|
4446
4878
|
if (command === "init") {
|
|
@@ -4501,9 +4933,9 @@ async function projectCommand(command, parsed, deps) {
|
|
|
4501
4933
|
}
|
|
4502
4934
|
|
|
4503
4935
|
// src/code-connect.ts
|
|
4504
|
-
var
|
|
4936
|
+
var import_node_fs15 = require("fs");
|
|
4505
4937
|
var import_node_os4 = require("os");
|
|
4506
|
-
var
|
|
4938
|
+
var import_node_path14 = require("path");
|
|
4507
4939
|
|
|
4508
4940
|
// ../harness/dist/chunk-QTUEF2HZ.js
|
|
4509
4941
|
var HARNESS_PROTOCOL_VERSION = 1;
|
|
@@ -4513,7 +4945,7 @@ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
|
4513
4945
|
var HarnessProtocolError = class extends Error {
|
|
4514
4946
|
name = "HarnessProtocolError";
|
|
4515
4947
|
};
|
|
4516
|
-
function
|
|
4948
|
+
function record4(value2) {
|
|
4517
4949
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
4518
4950
|
}
|
|
4519
4951
|
function boundedText(value2, label, max) {
|
|
@@ -4530,7 +4962,7 @@ function parseAgentOutput(line) {
|
|
|
4530
4962
|
} catch {
|
|
4531
4963
|
throw new HarnessProtocolError("agent emitted invalid JSON");
|
|
4532
4964
|
}
|
|
4533
|
-
const message2 =
|
|
4965
|
+
const message2 = record4(value2);
|
|
4534
4966
|
if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
|
|
4535
4967
|
throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
|
|
4536
4968
|
}
|
|
@@ -4543,7 +4975,7 @@ function parseAgentOutput(line) {
|
|
|
4543
4975
|
};
|
|
4544
4976
|
}
|
|
4545
4977
|
if (message2.type === "inference.request") {
|
|
4546
|
-
const call2 =
|
|
4978
|
+
const call2 = record4(message2.call);
|
|
4547
4979
|
if (!call2 || !Array.isArray(call2.messages) || !Number.isSafeInteger(call2.maxTokens)) {
|
|
4548
4980
|
throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
|
|
4549
4981
|
}
|
|
@@ -4555,7 +4987,7 @@ function parseAgentOutput(line) {
|
|
|
4555
4987
|
};
|
|
4556
4988
|
}
|
|
4557
4989
|
if (message2.type === "tool.request") {
|
|
4558
|
-
const input =
|
|
4990
|
+
const input = record4(message2.input);
|
|
4559
4991
|
const tool = String(message2.tool);
|
|
4560
4992
|
if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
|
|
4561
4993
|
throw new HarnessProtocolError("tool.request requires a registered tool and object input");
|
|
@@ -4739,8 +5171,8 @@ async function runContainerAttempt(options) {
|
|
|
4739
5171
|
let stopped = false;
|
|
4740
5172
|
let exited = false;
|
|
4741
5173
|
child.stderr.setEncoding("utf8");
|
|
4742
|
-
child.stderr.on("data", (
|
|
4743
|
-
if (stderr.length < 64 * 1024) stderr +=
|
|
5174
|
+
child.stderr.on("data", (text2) => {
|
|
5175
|
+
if (stderr.length < 64 * 1024) stderr += text2.slice(0, 64 * 1024 - stderr.length);
|
|
4744
5176
|
});
|
|
4745
5177
|
const stop = (reason) => {
|
|
4746
5178
|
if (stopped || exited) return;
|
|
@@ -4898,8 +5330,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
4898
5330
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
4899
5331
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
4900
5332
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
4901
|
-
const entries = inventory.flatMap((
|
|
4902
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
5333
|
+
const entries = inventory.flatMap((record11) => {
|
|
5334
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record11);
|
|
4903
5335
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
4904
5336
|
});
|
|
4905
5337
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -5147,8 +5579,8 @@ function normalize(value2) {
|
|
|
5147
5579
|
if (Array.isArray(value2)) return value2.map(normalize);
|
|
5148
5580
|
if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
|
|
5149
5581
|
if (typeof value2 === "object") {
|
|
5150
|
-
const
|
|
5151
|
-
return Object.fromEntries(Object.keys(
|
|
5582
|
+
const record11 = value2;
|
|
5583
|
+
return Object.fromEntries(Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, normalize(record11[key])]));
|
|
5152
5584
|
}
|
|
5153
5585
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
5154
5586
|
}
|
|
@@ -5227,7 +5659,7 @@ function normalizeReaders(readers) {
|
|
|
5227
5659
|
}
|
|
5228
5660
|
|
|
5229
5661
|
// ../camel/dist/code.js
|
|
5230
|
-
var
|
|
5662
|
+
var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
|
|
5231
5663
|
var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
5232
5664
|
var ID = /^[A-Za-z0-9._:-]{1,160}$/;
|
|
5233
5665
|
async function digestCodeVerificationReceipt(fields) {
|
|
@@ -5262,18 +5694,18 @@ async function digestCodeVerificationReceipt(fields) {
|
|
|
5262
5694
|
return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
|
|
5263
5695
|
}
|
|
5264
5696
|
function validate(fields) {
|
|
5265
|
-
if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !
|
|
5697
|
+
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) {
|
|
5266
5698
|
throw new CamelError("state_conflict", "Code verification receipt is malformed or outside its bounds.");
|
|
5267
5699
|
}
|
|
5268
5700
|
const ids = /* @__PURE__ */ new Set();
|
|
5269
5701
|
for (const recipe2 of fields.recipes) {
|
|
5270
|
-
if (typeof recipe2.recipeId !== "string" || !ID.test(recipe2.recipeId) || ids.has(recipe2.recipeId) || typeof recipe2.recipeDigest !== "string" || !
|
|
5702
|
+
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) {
|
|
5271
5703
|
throw new CamelError("state_conflict", "Code verification recipe receipt is malformed or outside its bounds.");
|
|
5272
5704
|
}
|
|
5273
5705
|
const artifactIds = /* @__PURE__ */ new Set();
|
|
5274
5706
|
if (recipe2.artifacts.length > 64) throw new CamelError("state_conflict", "Code verification has too many artifacts.");
|
|
5275
5707
|
for (const artifact of recipe2.artifacts) {
|
|
5276
|
-
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 && !
|
|
5708
|
+
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)) {
|
|
5277
5709
|
throw new CamelError("state_conflict", "Code verification artifact receipt is malformed.");
|
|
5278
5710
|
}
|
|
5279
5711
|
artifactIds.add(artifact.artifactId);
|
|
@@ -5289,7 +5721,7 @@ function validate(fields) {
|
|
|
5289
5721
|
}
|
|
5290
5722
|
}
|
|
5291
5723
|
var SHA2 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
5292
|
-
var
|
|
5724
|
+
var DIGEST22 = /^sha256:[0-9a-f]{64}$/;
|
|
5293
5725
|
var ID2 = /^[A-Za-z0-9._:-]{1,180}$/;
|
|
5294
5726
|
var MAX_PATCH_BYTES = 256 * 1024;
|
|
5295
5727
|
var MAX_STATE_BYTES = 64 * 1024;
|
|
@@ -5334,14 +5766,14 @@ function normalizeState(value2) {
|
|
|
5334
5766
|
]);
|
|
5335
5767
|
const planCursor = state2.planCursor;
|
|
5336
5768
|
const conversations = strings(state2.conversationRefs, "conversationRefs", ID2, 256, false);
|
|
5337
|
-
const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals",
|
|
5338
|
-
if (planCursor !== null && (typeof planCursor !== "string" || !ID2.test(planCursor)) || typeof state2.planningInputDigest !== "string" || !
|
|
5769
|
+
const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals", DIGEST22, 256, true);
|
|
5770
|
+
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) {
|
|
5339
5771
|
throw invalid2("Portable checkpoint state is malformed or outside its bounds.");
|
|
5340
5772
|
}
|
|
5341
5773
|
const effects = state2.completedEffects.map((item) => {
|
|
5342
5774
|
const effect = object(item, "completed effect");
|
|
5343
5775
|
exact2(effect, ["effectId", "actionDigest", "receiptDigest"]);
|
|
5344
|
-
if (typeof effect.effectId !== "string" || !ID2.test(effect.effectId) || typeof effect.actionDigest !== "string" || !
|
|
5776
|
+
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)) {
|
|
5345
5777
|
throw invalid2("Portable checkpoint effect receipt is malformed.");
|
|
5346
5778
|
}
|
|
5347
5779
|
return {
|
|
@@ -5572,8 +6004,8 @@ function boundedInteger(value2, spec) {
|
|
|
5572
6004
|
}
|
|
5573
6005
|
function boundedNumber(value2, spec) {
|
|
5574
6006
|
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.");
|
|
5575
|
-
const
|
|
5576
|
-
if (/e/i.test(
|
|
6007
|
+
const text2 = String(value2);
|
|
6008
|
+
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.");
|
|
5577
6009
|
return value2;
|
|
5578
6010
|
}
|
|
5579
6011
|
function enumMember(value2, spec) {
|
|
@@ -5729,8 +6161,8 @@ function validateUnsafeSelector(path, value2, tool) {
|
|
|
5729
6161
|
return void 0;
|
|
5730
6162
|
}
|
|
5731
6163
|
function looksLikeDestination(value2) {
|
|
5732
|
-
const
|
|
5733
|
-
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(
|
|
6164
|
+
const text2 = value2.trim();
|
|
6165
|
+
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text2) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text2);
|
|
5734
6166
|
}
|
|
5735
6167
|
|
|
5736
6168
|
// ../harness/dist/chunk-GMVZ4LZH.js
|
|
@@ -5871,7 +6303,7 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5871
6303
|
}
|
|
5872
6304
|
const value2 = await response2.json().catch(() => null);
|
|
5873
6305
|
if (!response2.ok) {
|
|
5874
|
-
const problem =
|
|
6306
|
+
const problem = record5(record5(value2)?.error);
|
|
5875
6307
|
throw new CodeRuntimeControlError(
|
|
5876
6308
|
typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
|
|
5877
6309
|
response2.status,
|
|
@@ -5893,12 +6325,12 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5893
6325
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
5894
6326
|
),
|
|
5895
6327
|
infer: async (sessionId, inference) => {
|
|
5896
|
-
const value2 =
|
|
6328
|
+
const value2 = record5(await call2(
|
|
5897
6329
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
5898
6330
|
inference,
|
|
5899
6331
|
modelRequestTimeoutMs
|
|
5900
6332
|
));
|
|
5901
|
-
if (!value2 || value2.requestId !== inference.requestId || !
|
|
6333
|
+
if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
|
|
5902
6334
|
throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
|
|
5903
6335
|
}
|
|
5904
6336
|
return value2;
|
|
@@ -5956,12 +6388,12 @@ function validateHeartbeat(version, capabilities) {
|
|
|
5956
6388
|
}
|
|
5957
6389
|
}
|
|
5958
6390
|
function parseSnapshot(value2) {
|
|
5959
|
-
const root =
|
|
5960
|
-
const host =
|
|
6391
|
+
const root = record5(value2);
|
|
6392
|
+
const host = record5(root?.host);
|
|
5961
6393
|
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");
|
|
5962
6394
|
const bindingIds = /* @__PURE__ */ new Set();
|
|
5963
6395
|
const bindings = root.bindings.map((item) => {
|
|
5964
|
-
const binding =
|
|
6396
|
+
const binding = record5(item);
|
|
5965
6397
|
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)) {
|
|
5966
6398
|
throw invalid("binding");
|
|
5967
6399
|
}
|
|
@@ -5971,10 +6403,10 @@ function parseSnapshot(value2) {
|
|
|
5971
6403
|
const commandIds = /* @__PURE__ */ new Set();
|
|
5972
6404
|
const commandSequences = /* @__PURE__ */ new Set();
|
|
5973
6405
|
const commands = root.commands.map((item) => {
|
|
5974
|
-
const command =
|
|
6406
|
+
const command = record5(item);
|
|
5975
6407
|
const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
|
|
5976
6408
|
const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
|
|
5977
|
-
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)) || !
|
|
6409
|
+
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");
|
|
5978
6410
|
commandIds.add(command.commandId);
|
|
5979
6411
|
commandSequences.add(sequenceKey);
|
|
5980
6412
|
return command;
|
|
@@ -5982,10 +6414,10 @@ function parseSnapshot(value2) {
|
|
|
5982
6414
|
return { host, bindings, commands };
|
|
5983
6415
|
}
|
|
5984
6416
|
async function parseSource(value2) {
|
|
5985
|
-
const snapshot =
|
|
6417
|
+
const snapshot = record5(record5(value2)?.snapshot);
|
|
5986
6418
|
if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
|
|
5987
6419
|
const files = snapshot.files.map((value22) => {
|
|
5988
|
-
const file =
|
|
6420
|
+
const file = record5(value22);
|
|
5989
6421
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
5990
6422
|
return { path: file.path, content: file.content };
|
|
5991
6423
|
});
|
|
@@ -5994,11 +6426,11 @@ async function parseSource(value2) {
|
|
|
5994
6426
|
const aliases = /* @__PURE__ */ new Set();
|
|
5995
6427
|
const references = [];
|
|
5996
6428
|
for (const item of referencesValue) {
|
|
5997
|
-
const reference =
|
|
6429
|
+
const reference = record5(item);
|
|
5998
6430
|
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");
|
|
5999
6431
|
aliases.add(reference.alias);
|
|
6000
6432
|
const referenceFiles = reference.files.map((entry) => {
|
|
6001
|
-
const file =
|
|
6433
|
+
const file = record5(entry);
|
|
6002
6434
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
|
|
6003
6435
|
return { path: file.path, content: file.content };
|
|
6004
6436
|
});
|
|
@@ -6013,18 +6445,18 @@ async function parseSource(value2) {
|
|
|
6013
6445
|
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
6014
6446
|
}
|
|
6015
6447
|
function parseReview(value2) {
|
|
6016
|
-
const review =
|
|
6448
|
+
const review = record5(record5(value2)?.review);
|
|
6017
6449
|
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");
|
|
6018
6450
|
return review;
|
|
6019
6451
|
}
|
|
6020
6452
|
function parseCandidate(value2) {
|
|
6021
|
-
const candidate =
|
|
6453
|
+
const candidate = record5(record5(value2)?.candidate);
|
|
6022
6454
|
if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
|
|
6023
6455
|
throw invalid("candidate");
|
|
6024
6456
|
}
|
|
6025
6457
|
return { candidateId: candidate.candidateId, status: candidate.status };
|
|
6026
6458
|
}
|
|
6027
|
-
var
|
|
6459
|
+
var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
6028
6460
|
var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
|
|
6029
6461
|
var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
6030
6462
|
var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
@@ -6099,8 +6531,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
6099
6531
|
});
|
|
6100
6532
|
let stderr = "";
|
|
6101
6533
|
child.stderr.setEncoding("utf8");
|
|
6102
|
-
child.stderr.on("data", (
|
|
6103
|
-
if (stderr.length < 4e3) stderr +=
|
|
6534
|
+
child.stderr.on("data", (text2) => {
|
|
6535
|
+
if (stderr.length < 4e3) stderr += text2.slice(0, 4e3);
|
|
6104
6536
|
});
|
|
6105
6537
|
child.once("error", reject);
|
|
6106
6538
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(`patch did not apply: ${stderr.trim().slice(0, 500)}`)));
|
|
@@ -7271,10 +7703,10 @@ var CodePiRuntimeEngine = class {
|
|
|
7271
7703
|
task: lease.task,
|
|
7272
7704
|
limits: this.options.limits,
|
|
7273
7705
|
signal: active.abort.signal,
|
|
7274
|
-
onStderr: (
|
|
7706
|
+
onStderr: (text2) => this.#event(command, {
|
|
7275
7707
|
type: "message",
|
|
7276
7708
|
actor: "system",
|
|
7277
|
-
body:
|
|
7709
|
+
body: text2.slice(0, 4e3)
|
|
7278
7710
|
}, active.conversationRefs),
|
|
7279
7711
|
onMessage: async (output) => {
|
|
7280
7712
|
if (output.type === "inference.request") {
|
|
@@ -7387,13 +7819,6 @@ var CodePiRuntimeEngine = class {
|
|
|
7387
7819
|
}
|
|
7388
7820
|
};
|
|
7389
7821
|
|
|
7390
|
-
// src/version.ts
|
|
7391
|
-
var import_node_fs13 = require("fs");
|
|
7392
|
-
function cliVersion() {
|
|
7393
|
-
const pkg = JSON.parse((0, import_node_fs13.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
|
|
7394
|
-
return pkg.version ?? "unknown";
|
|
7395
|
-
}
|
|
7396
|
-
|
|
7397
7822
|
// src/security-hosted-github.ts
|
|
7398
7823
|
var import_node_child_process4 = require("child_process");
|
|
7399
7824
|
var import_node_util2 = require("util");
|
|
@@ -7669,7 +8094,7 @@ var import_node_child_process6 = require("child_process");
|
|
|
7669
8094
|
var import_node_crypto3 = require("crypto");
|
|
7670
8095
|
var import_promises10 = require("fs/promises");
|
|
7671
8096
|
var import_node_os3 = require("os");
|
|
7672
|
-
var
|
|
8097
|
+
var import_node_path13 = require("path");
|
|
7673
8098
|
var import_node_url3 = require("url");
|
|
7674
8099
|
|
|
7675
8100
|
// src/code-runtime-config.ts
|
|
@@ -7755,10 +8180,10 @@ async function embeddedPiImageName() {
|
|
|
7755
8180
|
return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto3.createHash)("sha256").update(bundle).digest("hex")}`;
|
|
7756
8181
|
}
|
|
7757
8182
|
async function buildEmbeddedPiImage(engine, image, run) {
|
|
7758
|
-
const context = await (0, import_promises10.mkdtemp)((0,
|
|
8183
|
+
const context = await (0, import_promises10.mkdtemp)((0, import_node_path13.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
|
|
7759
8184
|
try {
|
|
7760
|
-
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0,
|
|
7761
|
-
await (0, import_promises10.writeFile)((0,
|
|
8185
|
+
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path13.join)(context, "pi-agent.js"));
|
|
8186
|
+
await (0, import_promises10.writeFile)((0, import_node_path13.join)(context, "Dockerfile"), [
|
|
7762
8187
|
`FROM ${CODE_NODE_IMAGE}`,
|
|
7763
8188
|
"COPY pi-agent.js /opt/odla/pi-agent.js",
|
|
7764
8189
|
"WORKDIR /workspace",
|
|
@@ -7774,8 +8199,8 @@ async function buildEmbeddedPiImage(engine, image, run) {
|
|
|
7774
8199
|
// src/code-connect.ts
|
|
7775
8200
|
async function codeConnect(options) {
|
|
7776
8201
|
const cwd = options.cwd ?? process.cwd();
|
|
7777
|
-
const configPath = (0,
|
|
7778
|
-
const cfg = (0,
|
|
8202
|
+
const configPath = (0, import_node_path14.resolve)(cwd, options.configPath);
|
|
8203
|
+
const cfg = (0, import_node_fs15.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
|
|
7779
8204
|
const requestedAppId = options.appId?.trim();
|
|
7780
8205
|
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
7781
8206
|
throw new Error("--app-id must be a valid odla app id");
|
|
@@ -7933,20 +8358,20 @@ async function runCodeRuntime(input) {
|
|
|
7933
8358
|
}
|
|
7934
8359
|
}
|
|
7935
8360
|
function parseConnection(value2, appId, appEnv) {
|
|
7936
|
-
const root =
|
|
7937
|
-
const host =
|
|
7938
|
-
const offer =
|
|
7939
|
-
const binding =
|
|
8361
|
+
const root = record6(value2);
|
|
8362
|
+
const host = record6(root?.host);
|
|
8363
|
+
const offer = record6(root?.offer);
|
|
8364
|
+
const binding = record6(root?.binding);
|
|
7940
8365
|
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)) {
|
|
7941
8366
|
throw new Error("connect Code host returned an invalid response");
|
|
7942
8367
|
}
|
|
7943
8368
|
return root;
|
|
7944
8369
|
}
|
|
7945
8370
|
function apiFailure(action2, status, value2) {
|
|
7946
|
-
const message2 =
|
|
8371
|
+
const message2 = record6(record6(value2)?.error)?.message;
|
|
7947
8372
|
return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
7948
8373
|
}
|
|
7949
|
-
function
|
|
8374
|
+
function record6(value2) {
|
|
7950
8375
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
7951
8376
|
}
|
|
7952
8377
|
|
|
@@ -8161,8 +8586,10 @@ Usage:
|
|
|
8161
8586
|
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8162
8587
|
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
|
|
8163
8588
|
odla-ai doctor [--config odla.config.mjs]
|
|
8164
|
-
odla-ai config diff [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8165
|
-
odla-ai config plan [--
|
|
8589
|
+
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8590
|
+
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
8591
|
+
odla-ai operations get <operation-id> [--json]
|
|
8592
|
+
odla-ai operations wait <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
|
|
8166
8593
|
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
8167
8594
|
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
8168
8595
|
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
@@ -8213,6 +8640,7 @@ Usage:
|
|
|
8213
8640
|
odla-ai context remove <name> --yes [--json]
|
|
8214
8641
|
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
8215
8642
|
odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
8643
|
+
odla-ai platform chat-credentials rotate [--context <name>] [--email <odla-account>] [--wrangler-config <path>] [--expected-version <id>] [--json] --yes
|
|
8216
8644
|
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
8217
8645
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
8218
8646
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
@@ -8276,9 +8704,9 @@ Commands:
|
|
|
8276
8704
|
does and what it guarantees is JSDoc, rendered per package at
|
|
8277
8705
|
https://odla.ai/docs and shipped in the installed .d.ts. Answering
|
|
8278
8706
|
a question usually needs both.
|
|
8279
|
-
whoami Report
|
|
8280
|
-
|
|
8281
|
-
|
|
8707
|
+
whoami Report the actual principal name, handle and kind, its manager,
|
|
8708
|
+
credential kind, accountable owner, and platform-admin status.
|
|
8709
|
+
A manager relationship never supplies authority by itself.
|
|
8282
8710
|
context Explain selected config, platform, app, environment, and
|
|
8283
8711
|
developer-token provenance without printing credentials or
|
|
8284
8712
|
starting a device handshake. Operator work can run outside a
|
|
@@ -8287,8 +8715,8 @@ Commands:
|
|
|
8287
8715
|
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
8288
8716
|
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
8289
8717
|
doctor Validate and summarize the project config without network calls.
|
|
8290
|
-
config
|
|
8291
|
-
|
|
8718
|
+
config Diff Registry intent, freeze a CAS-bound plan, and conditionally apply its safe actions.
|
|
8719
|
+
operations Inspect or wait on one exact, durable config-operation receipt.
|
|
8292
8720
|
calendar Inspect, connect, or disconnect the live Google booking connection.
|
|
8293
8721
|
app Archive (suspend, data retained), restore, export, import, or
|
|
8294
8722
|
manage the co-owners of the app. Archiving takes every
|
|
@@ -8398,6 +8826,58 @@ Safety:
|
|
|
8398
8826
|
`);
|
|
8399
8827
|
}
|
|
8400
8828
|
|
|
8829
|
+
// src/discuss-principals.ts
|
|
8830
|
+
function mergeDiscussPrincipals(target, source) {
|
|
8831
|
+
Object.assign(target.authors, source.authors ?? {});
|
|
8832
|
+
Object.assign(target.principals, source.principals ?? {});
|
|
8833
|
+
}
|
|
8834
|
+
var withAt = (handle) => handle.startsWith("@") ? handle : `@${handle}`;
|
|
8835
|
+
function discussPrincipalLabel(principalId, authorKind, projection) {
|
|
8836
|
+
const profile = projection.principals?.[principalId];
|
|
8837
|
+
if (profile) {
|
|
8838
|
+
const identity = `${profile.displayName} (${withAt(profile.handle)})`;
|
|
8839
|
+
if (profile.kind === "agent") {
|
|
8840
|
+
return profile.managerDisplayName ? `${identity} \u2014 agent managed by ${profile.managerDisplayName}` : `${identity} \u2014 agent`;
|
|
8841
|
+
}
|
|
8842
|
+
return profile.kind === "service" ? `${identity} \u2014 service` : identity;
|
|
8843
|
+
}
|
|
8844
|
+
const legacyName = projection.authors?.[principalId]?.trim();
|
|
8845
|
+
if (legacyName) {
|
|
8846
|
+
return authorKind === "bot" ? `${legacyName} \u2014 agent` : legacyName;
|
|
8847
|
+
}
|
|
8848
|
+
return authorKind === "bot" ? "Unnamed agent" : "Unnamed member";
|
|
8849
|
+
}
|
|
8850
|
+
|
|
8851
|
+
// src/discuss-read-render.ts
|
|
8852
|
+
function discussBodyWithRefs(post) {
|
|
8853
|
+
if (!post.refs || post.refs.length === 0) return post.body;
|
|
8854
|
+
let out = "";
|
|
8855
|
+
let cursor = 0;
|
|
8856
|
+
for (const ref of [...post.refs].sort((a, b) => a.start - b.start)) {
|
|
8857
|
+
if (ref.start < cursor || ref.end > post.body.length) continue;
|
|
8858
|
+
out += post.body.slice(cursor, ref.start) + `@[${ref.label}](${ref.kind}/${ref.id})`;
|
|
8859
|
+
cursor = ref.end;
|
|
8860
|
+
}
|
|
8861
|
+
return out + post.body.slice(cursor);
|
|
8862
|
+
}
|
|
8863
|
+
function renderDiscussRead(ctx, topic, posts, projection) {
|
|
8864
|
+
const status = topic.resolved ? "resolved" : "open";
|
|
8865
|
+
ctx.out.log(`${topic.subject} [${status}] ${topic.appId ?? ""}`);
|
|
8866
|
+
for (const post of posts) {
|
|
8867
|
+
const who = discussPrincipalLabel(
|
|
8868
|
+
post.authorId,
|
|
8869
|
+
post.authorKind,
|
|
8870
|
+
projection
|
|
8871
|
+
);
|
|
8872
|
+
ctx.out.log(`
|
|
8873
|
+
\u2014 ${who}`);
|
|
8874
|
+
ctx.out.log(discussBodyWithRefs(post));
|
|
8875
|
+
for (const file of post.attachments ?? []) {
|
|
8876
|
+
ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
|
|
8877
|
+
}
|
|
8878
|
+
}
|
|
8879
|
+
}
|
|
8880
|
+
|
|
8401
8881
|
// src/discuss-actions.ts
|
|
8402
8882
|
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
8403
8883
|
async function request(ctx, method, path, body) {
|
|
@@ -8416,17 +8896,6 @@ function emit(ctx, value2, human) {
|
|
|
8416
8896
|
else human();
|
|
8417
8897
|
}
|
|
8418
8898
|
var state = (topic) => topic.resolved ? "resolved" : "open";
|
|
8419
|
-
function bodyWithRefs(post) {
|
|
8420
|
-
if (!post.refs || post.refs.length === 0) return post.body;
|
|
8421
|
-
let out = "";
|
|
8422
|
-
let cursor = 0;
|
|
8423
|
-
for (const ref of [...post.refs].sort((a, b) => a.start - b.start)) {
|
|
8424
|
-
if (ref.start < cursor || ref.end > post.body.length) continue;
|
|
8425
|
-
out += post.body.slice(cursor, ref.start) + `@[${ref.label}](${ref.kind}/${ref.id})`;
|
|
8426
|
-
cursor = ref.end;
|
|
8427
|
-
}
|
|
8428
|
-
return out + post.body.slice(cursor);
|
|
8429
|
-
}
|
|
8430
8899
|
function content(parsed) {
|
|
8431
8900
|
const markup = stringOpt(parsed.options.markup);
|
|
8432
8901
|
if (markup) return { markup };
|
|
@@ -8481,11 +8950,19 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8481
8950
|
offset: requestedOffset ?? "0"
|
|
8482
8951
|
});
|
|
8483
8952
|
const page = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
|
|
8484
|
-
emit(
|
|
8953
|
+
emit(
|
|
8954
|
+
ctx,
|
|
8955
|
+
page,
|
|
8956
|
+
() => renderDiscussRead(ctx, page.topic, page.posts, page)
|
|
8957
|
+
);
|
|
8485
8958
|
return;
|
|
8486
8959
|
}
|
|
8487
8960
|
for (let scan = 0; scan < 3; scan++) {
|
|
8488
8961
|
const posts = /* @__PURE__ */ new Map();
|
|
8962
|
+
const projection = {
|
|
8963
|
+
authors: {},
|
|
8964
|
+
principals: {}
|
|
8965
|
+
};
|
|
8489
8966
|
let topic = null;
|
|
8490
8967
|
let offset = 0;
|
|
8491
8968
|
for (; ; ) {
|
|
@@ -8496,6 +8973,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8496
8973
|
);
|
|
8497
8974
|
topic = page.topic;
|
|
8498
8975
|
for (const post of page.posts) posts.set(post.id, post);
|
|
8976
|
+
mergeDiscussPrincipals(projection, page);
|
|
8499
8977
|
if (posts.size > 1e4) throw new Error("discuss read failed: conversation exceeds 10000 posts");
|
|
8500
8978
|
if (!page.page?.hasMore) break;
|
|
8501
8979
|
if (page.page.nextOffset === null || page.page.nextOffset <= offset) {
|
|
@@ -8508,24 +8986,18 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8508
8986
|
);
|
|
8509
8987
|
const expected = Number.isInteger(topic.replyCount) ? topic.replyCount + 1 : ordered.length;
|
|
8510
8988
|
if (ordered.length === expected) {
|
|
8511
|
-
const result = { topic, posts: ordered };
|
|
8512
|
-
emit(
|
|
8989
|
+
const result = { topic, posts: ordered, ...projection };
|
|
8990
|
+
emit(
|
|
8991
|
+
ctx,
|
|
8992
|
+
result,
|
|
8993
|
+
() => renderDiscussRead(ctx, result.topic, result.posts, result)
|
|
8994
|
+
);
|
|
8513
8995
|
return;
|
|
8514
8996
|
}
|
|
8515
8997
|
if (offset === 0) throw new Error("discuss read failed: registry did not provide forward post pages");
|
|
8516
8998
|
}
|
|
8517
8999
|
throw new Error("discuss read failed: conversation changed during every complete-read attempt");
|
|
8518
9000
|
}
|
|
8519
|
-
function renderRead(ctx, topic, posts) {
|
|
8520
|
-
ctx.out.log(`${topic.subject} [${state(topic)}] ${topic.appId ?? ""}`);
|
|
8521
|
-
for (const post of posts) {
|
|
8522
|
-
const who = post.authorKind === "bot" ? `${post.authorId} (agent)` : post.authorId;
|
|
8523
|
-
ctx.out.log(`
|
|
8524
|
-
\u2014 ${who}`);
|
|
8525
|
-
ctx.out.log(bodyWithRefs(post));
|
|
8526
|
-
for (const file of post.attachments ?? []) ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
|
|
8527
|
-
}
|
|
8528
|
-
}
|
|
8529
9001
|
async function discussPost(ctx, parsed) {
|
|
8530
9002
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8531
9003
|
if (!appId) throw new Error("discuss post needs --app <appId>");
|
|
@@ -8750,6 +9222,8 @@ async function discussWatch(ctx, topicId, parsed) {
|
|
|
8750
9222
|
found: true,
|
|
8751
9223
|
cursor,
|
|
8752
9224
|
events: matching,
|
|
9225
|
+
...page.authors ? { authors: page.authors } : {},
|
|
9226
|
+
...page.principals ? { principals: page.principals } : {},
|
|
8753
9227
|
...posts && posts.length > 0 ? { posts } : {},
|
|
8754
9228
|
...topics && topics.length > 0 ? { topics } : {}
|
|
8755
9229
|
});
|
|
@@ -8782,7 +9256,11 @@ function report2(ctx, parsed, result) {
|
|
|
8782
9256
|
ctx.out.log(JSON.stringify(result, null, 2));
|
|
8783
9257
|
} else if (parsed.options.jsonl !== true && result.found) {
|
|
8784
9258
|
for (const post of result.posts ?? []) {
|
|
8785
|
-
const who =
|
|
9259
|
+
const who = discussPrincipalLabel(
|
|
9260
|
+
post.authorId,
|
|
9261
|
+
post.authorKind,
|
|
9262
|
+
result
|
|
9263
|
+
);
|
|
8786
9264
|
ctx.out.log(`\u2014 ${who}
|
|
8787
9265
|
${post.body}`);
|
|
8788
9266
|
}
|
|
@@ -8927,8 +9405,8 @@ function collectFields(parsed, allowClear) {
|
|
|
8927
9405
|
if (allowClear) out[spec.key] = null;
|
|
8928
9406
|
continue;
|
|
8929
9407
|
}
|
|
8930
|
-
const
|
|
8931
|
-
out[spec.key] = spec.num ? Number(
|
|
9408
|
+
const text2 = stringOpt(value2);
|
|
9409
|
+
out[spec.key] = spec.num ? Number(text2) : text2;
|
|
8932
9410
|
}
|
|
8933
9411
|
return out;
|
|
8934
9412
|
}
|
|
@@ -8995,8 +9473,8 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
8995
9473
|
emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
8996
9474
|
}
|
|
8997
9475
|
async function pmGet(ctx, entity, id) {
|
|
8998
|
-
const { record:
|
|
8999
|
-
emit2(ctx,
|
|
9476
|
+
const { record: record11 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
|
|
9477
|
+
emit2(ctx, record11, () => printRecord(ctx, entity, record11));
|
|
9000
9478
|
}
|
|
9001
9479
|
async function pmSet(ctx, entity, id, parsed) {
|
|
9002
9480
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
@@ -9041,9 +9519,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
9041
9519
|
]);
|
|
9042
9520
|
const handoff = {
|
|
9043
9521
|
appId,
|
|
9044
|
-
unmetGoals: goals.filter((
|
|
9045
|
-
activeTasks: tasks.filter((
|
|
9046
|
-
openBugs: bugs.filter((
|
|
9522
|
+
unmetGoals: goals.filter((record11) => record11.status !== "met"),
|
|
9523
|
+
activeTasks: tasks.filter((record11) => record11.column !== "done"),
|
|
9524
|
+
openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
|
|
9047
9525
|
};
|
|
9048
9526
|
const result = {
|
|
9049
9527
|
...handoff,
|
|
@@ -9062,10 +9540,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
9062
9540
|
]) {
|
|
9063
9541
|
ctx.out.log(`${label}:`);
|
|
9064
9542
|
if (!records.length) ctx.out.log("- (none)");
|
|
9065
|
-
else for (const
|
|
9543
|
+
else for (const record11 of records) printRecord(
|
|
9066
9544
|
ctx,
|
|
9067
9545
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
9068
|
-
|
|
9546
|
+
record11
|
|
9069
9547
|
);
|
|
9070
9548
|
}
|
|
9071
9549
|
});
|
|
@@ -9256,19 +9734,180 @@ function age(input) {
|
|
|
9256
9734
|
return `${Math.round(input / 6e4)}m`;
|
|
9257
9735
|
}
|
|
9258
9736
|
|
|
9737
|
+
// src/platform-chat-credential-command.ts
|
|
9738
|
+
var import_node_process10 = __toESM(require("process"), 1);
|
|
9739
|
+
async function rotatePlatformChatCredential(parsed, deps) {
|
|
9740
|
+
assertArgs(
|
|
9741
|
+
parsed,
|
|
9742
|
+
[
|
|
9743
|
+
"config",
|
|
9744
|
+
"context",
|
|
9745
|
+
"platform",
|
|
9746
|
+
"token",
|
|
9747
|
+
"email",
|
|
9748
|
+
"open",
|
|
9749
|
+
"json",
|
|
9750
|
+
"yes",
|
|
9751
|
+
"wrangler-config",
|
|
9752
|
+
"expected-version"
|
|
9753
|
+
],
|
|
9754
|
+
3
|
|
9755
|
+
);
|
|
9756
|
+
if (parsed.options.yes !== true) {
|
|
9757
|
+
throw new Error(
|
|
9758
|
+
"platform chat-credentials rotate changes the production chat secret; pass --yes"
|
|
9759
|
+
);
|
|
9760
|
+
}
|
|
9761
|
+
const context = await resolveOperatorContext(parsed, {
|
|
9762
|
+
allowMissingConfig: true
|
|
9763
|
+
});
|
|
9764
|
+
const platform = context.platform.value;
|
|
9765
|
+
const doFetch = deps.fetch ?? fetch;
|
|
9766
|
+
const out = deps.stdout ?? console;
|
|
9767
|
+
const run = deps.runner ?? defaultRunner;
|
|
9768
|
+
const cwd = import_node_process10.default.cwd();
|
|
9769
|
+
const wranglerConfig = stringOpt(parsed.options["wrangler-config"]) ?? "packages/chat-agent/wrangler.jsonc";
|
|
9770
|
+
if (!await wranglerLoggedIn(run, cwd)) {
|
|
9771
|
+
throw new Error(
|
|
9772
|
+
'Wrangler is not authenticated; run "npx wrangler login" and retry'
|
|
9773
|
+
);
|
|
9774
|
+
}
|
|
9775
|
+
const priorHealth = await doFetch(`${platform}/health/services/chat`);
|
|
9776
|
+
const priorVersion = priorHealth.headers.get("x-odla-worker-version-id");
|
|
9777
|
+
await priorHealth.body?.cancel().catch(() => {
|
|
9778
|
+
});
|
|
9779
|
+
if (!priorVersion) {
|
|
9780
|
+
throw new Error(
|
|
9781
|
+
"chat health did not identify its current Worker version; refusing to rotate"
|
|
9782
|
+
);
|
|
9783
|
+
}
|
|
9784
|
+
const expectedVersion = stringOpt(parsed.options["expected-version"]);
|
|
9785
|
+
if (expectedVersion && priorVersion !== expectedVersion) {
|
|
9786
|
+
throw new Error(
|
|
9787
|
+
`chat health answered from version ${priorVersion}; expected ${expectedVersion}`
|
|
9788
|
+
);
|
|
9789
|
+
}
|
|
9790
|
+
const token = await resolveAdminPlatformToken({
|
|
9791
|
+
platform,
|
|
9792
|
+
scope: "platform:chat:credential:write",
|
|
9793
|
+
token: stringOpt(parsed.options.token),
|
|
9794
|
+
tokenFile: context.credentials.scopedTokenFile,
|
|
9795
|
+
email: stringOpt(parsed.options.email),
|
|
9796
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
9797
|
+
fetch: doFetch,
|
|
9798
|
+
stdout: out,
|
|
9799
|
+
openApprovalUrl: deps.openUrl,
|
|
9800
|
+
label: "odla CLI (rotate built-in Discussion responder)"
|
|
9801
|
+
});
|
|
9802
|
+
const mintedResponse = await doFetch(
|
|
9803
|
+
`${platform}/registry/platform/chat-credentials/rotate`,
|
|
9804
|
+
{
|
|
9805
|
+
method: "POST",
|
|
9806
|
+
headers: { authorization: `Bearer ${token}` }
|
|
9807
|
+
}
|
|
9808
|
+
);
|
|
9809
|
+
const minted = await mintedResponse.json().catch(() => null);
|
|
9810
|
+
if (!mintedResponse.ok) {
|
|
9811
|
+
throw new Error(
|
|
9812
|
+
`mint Discussion credential failed (HTTP ${mintedResponse.status}): ${apiMessage(minted)}`
|
|
9813
|
+
);
|
|
9814
|
+
}
|
|
9815
|
+
if (!isPlatformChatCredential(minted)) {
|
|
9816
|
+
throw new Error(
|
|
9817
|
+
"platform returned an invalid odla.platform-chat-credential/v1 envelope"
|
|
9818
|
+
);
|
|
9819
|
+
}
|
|
9820
|
+
const put = await wranglerPutSecret(run, {
|
|
9821
|
+
name: "ODLA_BOT_TOKENS",
|
|
9822
|
+
value: JSON.stringify({
|
|
9823
|
+
[minted.appId]: { [minted.principalId]: minted.key }
|
|
9824
|
+
}),
|
|
9825
|
+
configPath: wranglerConfig,
|
|
9826
|
+
cwd
|
|
9827
|
+
});
|
|
9828
|
+
if (put.code !== 0) {
|
|
9829
|
+
throw new Error(
|
|
9830
|
+
"Wrangler could not replace ODLA_BOT_TOKENS; the new scoped key was not activated"
|
|
9831
|
+
);
|
|
9832
|
+
}
|
|
9833
|
+
const version = await waitForRotatedChatHealth({
|
|
9834
|
+
fetch: doFetch,
|
|
9835
|
+
platform,
|
|
9836
|
+
priorVersion,
|
|
9837
|
+
wait: deps.pollWait
|
|
9838
|
+
});
|
|
9839
|
+
const result = {
|
|
9840
|
+
schemaVersion: "odla.platform-chat-credential-rotation/v1",
|
|
9841
|
+
appId: minted.appId,
|
|
9842
|
+
appIncarnation: minted.appIncarnation,
|
|
9843
|
+
principalId: minted.principalId,
|
|
9844
|
+
health: { ok: true, service: "odla-chat-agent", version }
|
|
9845
|
+
};
|
|
9846
|
+
if (parsed.options.json === true) {
|
|
9847
|
+
out.log(JSON.stringify(result, null, 2));
|
|
9848
|
+
} else {
|
|
9849
|
+
out.log(
|
|
9850
|
+
`rotated ${result.appId} ${result.principalId} ${result.health.version}`
|
|
9851
|
+
);
|
|
9852
|
+
}
|
|
9853
|
+
}
|
|
9854
|
+
async function waitForRotatedChatHealth(opts) {
|
|
9855
|
+
const wait2 = opts.wait ?? ((milliseconds) => new Promise((resolve12) => setTimeout(resolve12, milliseconds)));
|
|
9856
|
+
let lastStatus = 0;
|
|
9857
|
+
let lastVersion = null;
|
|
9858
|
+
let lastError = "private_service_unready";
|
|
9859
|
+
for (let attempt = 0; attempt < 60; attempt++) {
|
|
9860
|
+
const response2 = await opts.fetch(
|
|
9861
|
+
`${opts.platform}/health/services/chat`
|
|
9862
|
+
);
|
|
9863
|
+
const body = await response2.json().catch(() => null);
|
|
9864
|
+
const version = response2.headers.get("x-odla-worker-version-id");
|
|
9865
|
+
if (response2.ok && record7(body) && body.ok === true && body.service === "odla-chat-agent" && version && version !== opts.priorVersion) {
|
|
9866
|
+
return version;
|
|
9867
|
+
}
|
|
9868
|
+
lastStatus = response2.status;
|
|
9869
|
+
lastVersion = version;
|
|
9870
|
+
lastError = record7(body) && typeof body.error === "string" ? body.error : "private_service_unready";
|
|
9871
|
+
if (attempt < 59) await wait2(5e3);
|
|
9872
|
+
}
|
|
9873
|
+
throw new Error(
|
|
9874
|
+
`chat health did not converge on the rotated Worker deployment (HTTP ${lastStatus}, version ${lastVersion ?? "unknown"}, ${lastError})`
|
|
9875
|
+
);
|
|
9876
|
+
}
|
|
9877
|
+
function isPlatformChatCredential(value2) {
|
|
9878
|
+
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_");
|
|
9879
|
+
}
|
|
9880
|
+
function apiMessage(value2) {
|
|
9881
|
+
if (!record7(value2)) return "request failed";
|
|
9882
|
+
const error = record7(value2.error) ? value2.error : value2;
|
|
9883
|
+
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
9884
|
+
}
|
|
9885
|
+
function record7(value2) {
|
|
9886
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
9887
|
+
}
|
|
9888
|
+
|
|
9259
9889
|
// src/platform-command.ts
|
|
9260
9890
|
async function platformCommand(parsed, deps = {}) {
|
|
9891
|
+
const action2 = parsed.positionals[1];
|
|
9892
|
+
if (action2 === "status") {
|
|
9893
|
+
return platformStatus(parsed, deps);
|
|
9894
|
+
}
|
|
9895
|
+
if (action2 === "chat-credentials" && parsed.positionals[2] === "rotate") {
|
|
9896
|
+
return rotatePlatformChatCredential(parsed, deps);
|
|
9897
|
+
}
|
|
9898
|
+
throw new Error(
|
|
9899
|
+
`unknown platform action "${[
|
|
9900
|
+
action2,
|
|
9901
|
+
parsed.positionals[2]
|
|
9902
|
+
].filter(Boolean).join(" ")}". Try "odla-ai platform status --json".`
|
|
9903
|
+
);
|
|
9904
|
+
}
|
|
9905
|
+
async function platformStatus(parsed, deps) {
|
|
9261
9906
|
assertArgs(
|
|
9262
9907
|
parsed,
|
|
9263
9908
|
["config", "context", "platform", "token", "email", "open", "json"],
|
|
9264
9909
|
2
|
|
9265
9910
|
);
|
|
9266
|
-
const action2 = parsed.positionals[1];
|
|
9267
|
-
if (action2 !== "status") {
|
|
9268
|
-
throw new Error(
|
|
9269
|
-
`unknown platform action "${action2 ?? ""}". Try "odla-ai platform status --json".`
|
|
9270
|
-
);
|
|
9271
|
-
}
|
|
9272
9911
|
const context = await resolveOperatorContext(parsed, {
|
|
9273
9912
|
allowMissingConfig: true
|
|
9274
9913
|
});
|
|
@@ -9293,7 +9932,7 @@ async function platformCommand(parsed, deps = {}) {
|
|
|
9293
9932
|
const body = await response2.json().catch(() => null);
|
|
9294
9933
|
if (!response2.ok) {
|
|
9295
9934
|
throw new Error(
|
|
9296
|
-
`read platform status failed (HTTP ${response2.status}): ${
|
|
9935
|
+
`read platform status failed (HTTP ${response2.status}): ${apiMessage2(body)}`
|
|
9297
9936
|
);
|
|
9298
9937
|
}
|
|
9299
9938
|
if (!isPlatformStatus(body)) {
|
|
@@ -9306,17 +9945,17 @@ async function platformCommand(parsed, deps = {}) {
|
|
|
9306
9945
|
}
|
|
9307
9946
|
}
|
|
9308
9947
|
function isPlatformStatus(value2) {
|
|
9309
|
-
if (!
|
|
9310
|
-
if (!
|
|
9311
|
-
if (!
|
|
9948
|
+
if (!record8(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
|
|
9949
|
+
if (!record8(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
|
|
9950
|
+
if (!record8(value2.catalog) || !record8(value2.summary)) return false;
|
|
9312
9951
|
return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
|
|
9313
9952
|
}
|
|
9314
|
-
function
|
|
9315
|
-
if (!
|
|
9316
|
-
const error =
|
|
9953
|
+
function apiMessage2(value2) {
|
|
9954
|
+
if (!record8(value2)) return "request failed";
|
|
9955
|
+
const error = record8(value2.error) ? value2.error : value2;
|
|
9317
9956
|
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
9318
9957
|
}
|
|
9319
|
-
function
|
|
9958
|
+
function record8(value2) {
|
|
9320
9959
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
9321
9960
|
}
|
|
9322
9961
|
|
|
@@ -9357,7 +9996,7 @@ function statusVerdict(reads) {
|
|
|
9357
9996
|
severity: "degraded"
|
|
9358
9997
|
});
|
|
9359
9998
|
}
|
|
9360
|
-
const performance =
|
|
9999
|
+
const performance = record9(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
9361
10000
|
if (performance?.status === "unavailable") {
|
|
9362
10001
|
reasons.push({
|
|
9363
10002
|
source: "liveSync",
|
|
@@ -9438,7 +10077,7 @@ function statusVerdict(reads) {
|
|
|
9438
10077
|
reasons
|
|
9439
10078
|
};
|
|
9440
10079
|
}
|
|
9441
|
-
function
|
|
10080
|
+
function record9(value2) {
|
|
9442
10081
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
9443
10082
|
}
|
|
9444
10083
|
function numeric2(value2) {
|
|
@@ -9466,7 +10105,7 @@ function printO11yStatus(status, out) {
|
|
|
9466
10105
|
out.log(
|
|
9467
10106
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
9468
10107
|
);
|
|
9469
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
10108
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record10) : [];
|
|
9470
10109
|
const requests = routes.reduce(
|
|
9471
10110
|
(total, row) => total + numeric3(row.requests),
|
|
9472
10111
|
0
|
|
@@ -9478,39 +10117,39 @@ function printO11yStatus(status, out) {
|
|
|
9478
10117
|
out.log(
|
|
9479
10118
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
9480
10119
|
);
|
|
9481
|
-
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(
|
|
10120
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record10) : [];
|
|
9482
10121
|
out.log(
|
|
9483
10122
|
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
9484
10123
|
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
9485
10124
|
).join(", ") : "none observed"}`
|
|
9486
10125
|
);
|
|
9487
10126
|
out.log(liveSyncLine(status.liveSync));
|
|
9488
|
-
const canaryDurations =
|
|
10127
|
+
const canaryDurations = record10(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
9489
10128
|
out.log(
|
|
9490
10129
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
9491
10130
|
);
|
|
9492
|
-
const collectorIngest =
|
|
9493
|
-
const collectorStorage =
|
|
10131
|
+
const collectorIngest = record10(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
10132
|
+
const collectorStorage = record10(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
9494
10133
|
out.log(
|
|
9495
10134
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
9496
10135
|
);
|
|
9497
|
-
const providerMetrics =
|
|
9498
|
-
const providerCapacity =
|
|
9499
|
-
const workerMemory =
|
|
10136
|
+
const providerMetrics = record10(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
10137
|
+
const providerCapacity = record10(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
10138
|
+
const workerMemory = record10(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
9500
10139
|
out.log(
|
|
9501
10140
|
`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`
|
|
9502
10141
|
);
|
|
9503
10142
|
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
9504
10143
|
out.log(line);
|
|
9505
10144
|
}
|
|
9506
|
-
const coverage =
|
|
9507
|
-
const coverageCounts =
|
|
9508
|
-
const coverageBudget =
|
|
10145
|
+
const coverage = record10(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
10146
|
+
const coverageCounts = record10(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
10147
|
+
const coverageBudget = record10(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
9509
10148
|
out.log(
|
|
9510
10149
|
`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`
|
|
9511
10150
|
);
|
|
9512
10151
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
9513
|
-
const providerFreshness =
|
|
10152
|
+
const providerFreshness = record10(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
9514
10153
|
out.log(
|
|
9515
10154
|
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
9516
10155
|
);
|
|
@@ -9519,17 +10158,17 @@ function printO11yStatus(status, out) {
|
|
|
9519
10158
|
);
|
|
9520
10159
|
}
|
|
9521
10160
|
function providerCapacityLines(read3) {
|
|
9522
|
-
const resources =
|
|
9523
|
-
const durableObjects =
|
|
9524
|
-
const periodic =
|
|
9525
|
-
const storage =
|
|
9526
|
-
const d1 =
|
|
9527
|
-
const d1Activity =
|
|
9528
|
-
const d1Storage =
|
|
9529
|
-
const d1Latency =
|
|
9530
|
-
const r2 =
|
|
9531
|
-
const r2Operations =
|
|
9532
|
-
const r2Storage =
|
|
10161
|
+
const resources = record10(read3.body.resources) ? read3.body.resources : {};
|
|
10162
|
+
const durableObjects = record10(resources.durableObjects) ? resources.durableObjects : {};
|
|
10163
|
+
const periodic = record10(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
10164
|
+
const storage = record10(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
10165
|
+
const d1 = record10(resources.d1) ? resources.d1 : {};
|
|
10166
|
+
const d1Activity = record10(d1.activity) ? d1.activity : {};
|
|
10167
|
+
const d1Storage = record10(d1.storage) ? d1.storage : {};
|
|
10168
|
+
const d1Latency = record10(d1Activity.latency) ? d1Activity.latency : {};
|
|
10169
|
+
const r2 = record10(resources.r2) ? resources.r2 : {};
|
|
10170
|
+
const r2Operations = record10(r2.operations) ? r2.operations : {};
|
|
10171
|
+
const r2Storage = record10(r2.storage) ? r2.storage : {};
|
|
9533
10172
|
const status = String(
|
|
9534
10173
|
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
9535
10174
|
);
|
|
@@ -9540,11 +10179,11 @@ function providerCapacityLines(read3) {
|
|
|
9540
10179
|
];
|
|
9541
10180
|
}
|
|
9542
10181
|
function liveSyncLine(read3) {
|
|
9543
|
-
const performance =
|
|
9544
|
-
const commitToSend =
|
|
10182
|
+
const performance = record10(read3.body.performance) ? read3.body.performance : {};
|
|
10183
|
+
const commitToSend = record10(performance.commitToSend) ? performance.commitToSend : {};
|
|
9545
10184
|
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`;
|
|
9546
10185
|
}
|
|
9547
|
-
function
|
|
10186
|
+
function record10(value2) {
|
|
9548
10187
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
9549
10188
|
}
|
|
9550
10189
|
function numeric3(value2) {
|
|
@@ -9713,23 +10352,23 @@ function statusMinutes(value2) {
|
|
|
9713
10352
|
}
|
|
9714
10353
|
async function read2(url, headers, doFetch) {
|
|
9715
10354
|
const response2 = await doFetch(url, { headers });
|
|
9716
|
-
const
|
|
10355
|
+
const text2 = await response2.text();
|
|
9717
10356
|
let body = {};
|
|
9718
|
-
if (
|
|
10357
|
+
if (text2) {
|
|
9719
10358
|
try {
|
|
9720
|
-
const value2 = JSON.parse(
|
|
10359
|
+
const value2 = JSON.parse(text2);
|
|
9721
10360
|
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
9722
10361
|
} catch {
|
|
9723
|
-
body = { message:
|
|
10362
|
+
body = { message: text2.slice(0, 300) };
|
|
9724
10363
|
}
|
|
9725
10364
|
}
|
|
9726
10365
|
return { httpStatus: response2.status, body };
|
|
9727
10366
|
}
|
|
9728
10367
|
|
|
9729
10368
|
// src/provision.ts
|
|
9730
|
-
var
|
|
10369
|
+
var import_apps12 = require("@odla-ai/apps");
|
|
9731
10370
|
var import_ai3 = require("@odla-ai/ai");
|
|
9732
|
-
var
|
|
10371
|
+
var import_node_process11 = __toESM(require("process"), 1);
|
|
9733
10372
|
|
|
9734
10373
|
// src/integration-provision.ts
|
|
9735
10374
|
var import_db3 = require("@odla-ai/db");
|
|
@@ -9784,9 +10423,9 @@ async function responseText(res) {
|
|
|
9784
10423
|
}
|
|
9785
10424
|
|
|
9786
10425
|
// src/provision-credentials.ts
|
|
9787
|
-
var
|
|
10426
|
+
var import_apps11 = require("@odla-ai/apps");
|
|
9788
10427
|
async function provisionEnvCredentials(opts) {
|
|
9789
|
-
const tenantId = (0,
|
|
10428
|
+
const tenantId = (0, import_apps11.tenantIdFor)(opts.cfg.app.id, opts.env);
|
|
9790
10429
|
const prior = opts.credentials?.envs[opts.env];
|
|
9791
10430
|
let credentials = opts.credentials;
|
|
9792
10431
|
let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
|
|
@@ -9948,7 +10587,7 @@ async function provision(options) {
|
|
|
9948
10587
|
}
|
|
9949
10588
|
const doFetch = options.fetch ?? fetch;
|
|
9950
10589
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
9951
|
-
const apps = (0,
|
|
10590
|
+
const apps = (0, import_apps12.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
9952
10591
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
9953
10592
|
if (existing) {
|
|
9954
10593
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
@@ -9959,7 +10598,7 @@ async function provision(options) {
|
|
|
9959
10598
|
for (const env of cfg.envs) {
|
|
9960
10599
|
await assertTenantAdminAccess(doFetch, cfg, env, token);
|
|
9961
10600
|
}
|
|
9962
|
-
const serviceOrder = (0,
|
|
10601
|
+
const serviceOrder = (0, import_apps12.orderAppServices)(cfg.services);
|
|
9963
10602
|
for (const env of cfg.envs) {
|
|
9964
10603
|
for (const service of serviceOrder) {
|
|
9965
10604
|
if (service === "ai") {
|
|
@@ -9992,7 +10631,7 @@ async function provision(options) {
|
|
|
9992
10631
|
}
|
|
9993
10632
|
}
|
|
9994
10633
|
for (const env of cfg.envs) {
|
|
9995
|
-
const tenantId = (0,
|
|
10634
|
+
const tenantId = (0, import_apps12.tenantIdFor)(cfg.app.id, env);
|
|
9996
10635
|
credentials = await provisionEnvCredentials({
|
|
9997
10636
|
cfg,
|
|
9998
10637
|
env,
|
|
@@ -10036,7 +10675,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10036
10675
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
10037
10676
|
}
|
|
10038
10677
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
10039
|
-
const key =
|
|
10678
|
+
const key = import_node_process11.default.env[cfg.ai.keyEnv];
|
|
10040
10679
|
if (key) {
|
|
10041
10680
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
10042
10681
|
await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -10077,8 +10716,8 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10077
10716
|
}
|
|
10078
10717
|
|
|
10079
10718
|
// src/record.ts
|
|
10080
|
-
var
|
|
10081
|
-
var
|
|
10719
|
+
var import_node_fs16 = require("fs");
|
|
10720
|
+
var import_node_process12 = __toESM(require("process"), 1);
|
|
10082
10721
|
|
|
10083
10722
|
// src/surface.ts
|
|
10084
10723
|
var PM_ACTIONS = {
|
|
@@ -10126,7 +10765,7 @@ var COMMAND_SURFACE = {
|
|
|
10126
10765
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
10127
10766
|
capabilities: {},
|
|
10128
10767
|
code: { connect: {} },
|
|
10129
|
-
config: { diff: {}, plan: {} },
|
|
10768
|
+
config: { diff: {}, plan: {}, apply: {} },
|
|
10130
10769
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
10131
10770
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
10132
10771
|
discuss: {
|
|
@@ -10144,7 +10783,11 @@ var COMMAND_SURFACE = {
|
|
|
10144
10783
|
help: {},
|
|
10145
10784
|
init: {},
|
|
10146
10785
|
o11y: { status: {} },
|
|
10147
|
-
|
|
10786
|
+
operations: { get: {}, wait: {} },
|
|
10787
|
+
platform: {
|
|
10788
|
+
status: {},
|
|
10789
|
+
"chat-credentials": { rotate: {} }
|
|
10790
|
+
},
|
|
10148
10791
|
pm: {
|
|
10149
10792
|
...PM_ENTITIES,
|
|
10150
10793
|
handoff: {}
|
|
@@ -10232,7 +10875,7 @@ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
|
10232
10875
|
|
|
10233
10876
|
// src/record.ts
|
|
10234
10877
|
function recordInvocation(parsed) {
|
|
10235
|
-
const file =
|
|
10878
|
+
const file = import_node_process12.default.env.ODLA_CLI_RECORD;
|
|
10236
10879
|
if (!file) return;
|
|
10237
10880
|
try {
|
|
10238
10881
|
const entry = {
|
|
@@ -10240,14 +10883,14 @@ function recordInvocation(parsed) {
|
|
|
10240
10883
|
options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
|
|
10241
10884
|
};
|
|
10242
10885
|
if (!entry.path.length) return;
|
|
10243
|
-
(0,
|
|
10886
|
+
(0, import_node_fs16.appendFileSync)(file, `${JSON.stringify(entry)}
|
|
10244
10887
|
`);
|
|
10245
10888
|
} catch {
|
|
10246
10889
|
}
|
|
10247
10890
|
}
|
|
10248
10891
|
|
|
10249
10892
|
// src/runbook-actions.ts
|
|
10250
|
-
var
|
|
10893
|
+
var import_node_fs17 = require("fs");
|
|
10251
10894
|
|
|
10252
10895
|
// src/runbook-requires.ts
|
|
10253
10896
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
@@ -10332,7 +10975,7 @@ async function bySlug(ctx, slug) {
|
|
|
10332
10975
|
function readBody(file, inline) {
|
|
10333
10976
|
if (inline !== void 0) return inline;
|
|
10334
10977
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
10335
|
-
return (0,
|
|
10978
|
+
return (0, import_node_fs17.readFileSync)(file === "-" ? 0 : file, "utf8");
|
|
10336
10979
|
}
|
|
10337
10980
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
10338
10981
|
async function runbookList(ctx, all, query) {
|
|
@@ -10362,12 +11005,17 @@ async function runbookNew(ctx, slug, title, body, summary, requires) {
|
|
|
10362
11005
|
});
|
|
10363
11006
|
ctx.out.log(ctx.json ? JSON.stringify(created, null, 2) : `created ${slug} (${created.id})`);
|
|
10364
11007
|
}
|
|
10365
|
-
async function runbookEdit(ctx, slug, body, note, requires) {
|
|
11008
|
+
async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
|
|
10366
11009
|
const runbook = await bySlug(ctx, slug);
|
|
10367
11010
|
const result = await call(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
|
|
10368
11011
|
// An empty --requires clears the declaration; omitting the flag leaves
|
|
10369
11012
|
// whatever is there, so an ordinary body edit never drops it.
|
|
10370
|
-
patch: {
|
|
11013
|
+
patch: {
|
|
11014
|
+
body,
|
|
11015
|
+
expectedVersion: expectedVersion ?? runbook.version,
|
|
11016
|
+
...note ? { note } : {},
|
|
11017
|
+
...requires === void 0 ? {} : { requires: requires || null }
|
|
11018
|
+
}
|
|
10371
11019
|
});
|
|
10372
11020
|
if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
|
|
10373
11021
|
ctx.out.log(`${slug} \u2192 v${result.record?.version ?? runbook.version + 1}`);
|
|
@@ -10419,14 +11067,14 @@ async function runbookRemove(ctx, slug) {
|
|
|
10419
11067
|
}
|
|
10420
11068
|
|
|
10421
11069
|
// src/runbook-import.ts
|
|
10422
|
-
var
|
|
10423
|
-
var
|
|
10424
|
-
function parseRunbook(
|
|
10425
|
-
let rest =
|
|
11070
|
+
var import_node_fs18 = require("fs");
|
|
11071
|
+
var import_node_path15 = require("path");
|
|
11072
|
+
function parseRunbook(text2, slug) {
|
|
11073
|
+
let rest = text2;
|
|
10426
11074
|
const meta = {};
|
|
10427
|
-
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(
|
|
11075
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text2);
|
|
10428
11076
|
if (fm) {
|
|
10429
|
-
rest =
|
|
11077
|
+
rest = text2.slice(fm[0].length);
|
|
10430
11078
|
for (const line of fm[1].split(/\r?\n/)) {
|
|
10431
11079
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
|
|
10432
11080
|
if (!pair) continue;
|
|
@@ -10445,12 +11093,12 @@ function parseRunbook(text, slug) {
|
|
|
10445
11093
|
};
|
|
10446
11094
|
}
|
|
10447
11095
|
function readRunbookDir(dir) {
|
|
10448
|
-
if (!(0,
|
|
10449
|
-
const files = (0,
|
|
11096
|
+
if (!(0, import_node_fs18.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
|
|
11097
|
+
const files = (0, import_node_fs18.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
|
|
10450
11098
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
10451
11099
|
return files.map((file) => {
|
|
10452
|
-
const slug = (0,
|
|
10453
|
-
const parsed = parseRunbook((0,
|
|
11100
|
+
const slug = (0, import_node_path15.basename)(file, ".md");
|
|
11101
|
+
const parsed = parseRunbook((0, import_node_fs18.readFileSync)((0, import_node_path15.join)(dir, file), "utf8"), slug);
|
|
10454
11102
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
10455
11103
|
});
|
|
10456
11104
|
}
|
|
@@ -10507,6 +11155,7 @@ async function upsert(ctx, r, visibility) {
|
|
|
10507
11155
|
patch: {
|
|
10508
11156
|
title: r.title,
|
|
10509
11157
|
body: r.body,
|
|
11158
|
+
expectedVersion: found.version,
|
|
10510
11159
|
...r.summary ? { summary: r.summary } : {},
|
|
10511
11160
|
...r.tags ? { tags: r.tags } : {},
|
|
10512
11161
|
note: `imported from ${r.file}`
|
|
@@ -10522,8 +11171,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
10522
11171
|
|
|
10523
11172
|
// src/runbook-impact.ts
|
|
10524
11173
|
var import_node_child_process7 = require("child_process");
|
|
10525
|
-
var
|
|
10526
|
-
var
|
|
11174
|
+
var import_node_fs19 = require("fs");
|
|
11175
|
+
var import_node_path16 = require("path");
|
|
10527
11176
|
|
|
10528
11177
|
// src/runbook-impact-scan.ts
|
|
10529
11178
|
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$]*)/;
|
|
@@ -10692,10 +11341,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
10692
11341
|
}
|
|
10693
11342
|
function manifestLabeller(root) {
|
|
10694
11343
|
return (workspace) => {
|
|
10695
|
-
const manifest = (0,
|
|
10696
|
-
if (!(0,
|
|
11344
|
+
const manifest = (0, import_node_path16.join)(root, workspace, "package.json");
|
|
11345
|
+
if (!(0, import_node_fs19.existsSync)(manifest)) return void 0;
|
|
10697
11346
|
try {
|
|
10698
|
-
const name = JSON.parse((0,
|
|
11347
|
+
const name = JSON.parse((0, import_node_fs19.readFileSync)(manifest, "utf8")).name;
|
|
10699
11348
|
return typeof name === "string" ? name : void 0;
|
|
10700
11349
|
} catch {
|
|
10701
11350
|
return void 0;
|
|
@@ -10762,7 +11411,7 @@ function report3(ctx, impacts) {
|
|
|
10762
11411
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
10763
11412
|
const cwd = deps.cwd ?? process.cwd();
|
|
10764
11413
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
10765
|
-
const read3 = deps.readRepoFile ?? ((path) => (0,
|
|
11414
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs19.readFileSync)((0, import_node_path16.join)(cwd, path), "utf8"));
|
|
10766
11415
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
10767
11416
|
if (!surfaces.length) {
|
|
10768
11417
|
return ctx.out.log(
|
|
@@ -10895,12 +11544,12 @@ async function runbookComment(ctx, slug, body) {
|
|
|
10895
11544
|
|
|
10896
11545
|
// src/runbook-editor.ts
|
|
10897
11546
|
var import_node_child_process8 = require("child_process");
|
|
10898
|
-
var
|
|
11547
|
+
var import_node_fs20 = require("fs");
|
|
10899
11548
|
var import_node_os5 = require("os");
|
|
10900
|
-
var
|
|
10901
|
-
var
|
|
11549
|
+
var import_node_path17 = require("path");
|
|
11550
|
+
var import_node_process13 = __toESM(require("process"), 1);
|
|
10902
11551
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
10903
|
-
function resolveEditor(env =
|
|
11552
|
+
function resolveEditor(env = import_node_process13.default.env) {
|
|
10904
11553
|
for (const name of EDITOR_ENV) {
|
|
10905
11554
|
const value2 = env[name];
|
|
10906
11555
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -10914,8 +11563,8 @@ function defaultRun(command, path) {
|
|
|
10914
11563
|
return result.status ?? 0;
|
|
10915
11564
|
}
|
|
10916
11565
|
function editText(initial, slug, deps = {}) {
|
|
10917
|
-
const env = deps.env ??
|
|
10918
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
11566
|
+
const env = deps.env ?? import_node_process13.default.env;
|
|
11567
|
+
const interactive = deps.interactive ?? (() => Boolean(import_node_process13.default.stdin.isTTY));
|
|
10919
11568
|
const editor = resolveEditor(env);
|
|
10920
11569
|
if (!editor)
|
|
10921
11570
|
throw new Error(
|
|
@@ -10923,16 +11572,16 @@ function editText(initial, slug, deps = {}) {
|
|
|
10923
11572
|
);
|
|
10924
11573
|
if (!interactive())
|
|
10925
11574
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
10926
|
-
const dir = (0,
|
|
10927
|
-
const file = (0,
|
|
11575
|
+
const dir = (0, import_node_fs20.mkdtempSync)((0, import_node_path17.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
|
|
11576
|
+
const file = (0, import_node_path17.join)(dir, `${slug}.md`);
|
|
10928
11577
|
try {
|
|
10929
|
-
(0,
|
|
11578
|
+
(0, import_node_fs20.writeFileSync)(file, initial, { mode: 384 });
|
|
10930
11579
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
10931
11580
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
10932
|
-
const edited = (0,
|
|
11581
|
+
const edited = (0, import_node_fs20.readFileSync)(file, "utf8");
|
|
10933
11582
|
return edited === initial ? null : edited;
|
|
10934
11583
|
} finally {
|
|
10935
|
-
(0,
|
|
11584
|
+
(0, import_node_fs20.rmSync)(dir, { recursive: true, force: true });
|
|
10936
11585
|
}
|
|
10937
11586
|
}
|
|
10938
11587
|
var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
@@ -10947,27 +11596,90 @@ async function editRunbook(ctx, slug, deps = {}) {
|
|
|
10947
11596
|
const found = page.records[0];
|
|
10948
11597
|
if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
|
|
10949
11598
|
ctx.out.log(`opening ${slug} v${found.version} in your editor\u2026`);
|
|
10950
|
-
|
|
11599
|
+
const body = await editText(found.body, slug, deps);
|
|
11600
|
+
return body === null ? null : { body, expectedVersion: found.version };
|
|
10951
11601
|
}
|
|
10952
11602
|
|
|
10953
11603
|
// src/whoami-command.ts
|
|
11604
|
+
var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
11605
|
+
function principalKind(value2, machine) {
|
|
11606
|
+
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
11607
|
+
}
|
|
11608
|
+
function credentialKind(value2, machine, scopes) {
|
|
11609
|
+
if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
|
|
11610
|
+
if (machine) return "machine";
|
|
11611
|
+
if (scopes.length) return "device";
|
|
11612
|
+
return "unknown";
|
|
11613
|
+
}
|
|
11614
|
+
function managerOf(value2) {
|
|
11615
|
+
if (!value2 || typeof value2 !== "object") return null;
|
|
11616
|
+
const row = value2;
|
|
11617
|
+
const principalId = text(row.principalId);
|
|
11618
|
+
if (!principalId) return null;
|
|
11619
|
+
return {
|
|
11620
|
+
principalId,
|
|
11621
|
+
displayName: text(row.displayName) ?? "Unnamed member",
|
|
11622
|
+
handle: text(row.handle) ?? ""
|
|
11623
|
+
};
|
|
11624
|
+
}
|
|
11625
|
+
function unnamedPrincipal(kind) {
|
|
11626
|
+
if (kind === "agent") return "Unnamed agent";
|
|
11627
|
+
if (kind === "service") return "Unnamed service";
|
|
11628
|
+
return "Unnamed member";
|
|
11629
|
+
}
|
|
10954
11630
|
async function fetchIdentity(platformUrl, token, doFetch) {
|
|
10955
11631
|
const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
|
|
10956
11632
|
headers: { authorization: `Bearer ${token}` }
|
|
10957
11633
|
});
|
|
10958
11634
|
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
10959
11635
|
const body = await res.json();
|
|
11636
|
+
const developerId = text(body.developerId) ?? "";
|
|
11637
|
+
const machine = body.machine === true;
|
|
11638
|
+
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
11639
|
+
const principalId = text(body.principalId) ?? developerId;
|
|
11640
|
+
const email = text(body.email);
|
|
11641
|
+
const kind = principalKind(body.principalKind, machine);
|
|
11642
|
+
const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
11643
|
+
const handle = text(body.handle) ?? "";
|
|
11644
|
+
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
10960
11645
|
return {
|
|
10961
|
-
developerId
|
|
10962
|
-
|
|
11646
|
+
developerId,
|
|
11647
|
+
principalId,
|
|
11648
|
+
principalKind: kind,
|
|
11649
|
+
displayName,
|
|
11650
|
+
handle,
|
|
11651
|
+
manager: managerOf(body.manager),
|
|
11652
|
+
credential: {
|
|
11653
|
+
id: text(credential2.id),
|
|
11654
|
+
kind: credentialKind(credential2.kind, machine, scopes)
|
|
11655
|
+
},
|
|
11656
|
+
email,
|
|
10963
11657
|
admin: body.admin === true,
|
|
10964
|
-
machine
|
|
10965
|
-
scopes
|
|
11658
|
+
machine,
|
|
11659
|
+
scopes
|
|
10966
11660
|
};
|
|
10967
11661
|
}
|
|
10968
|
-
function
|
|
10969
|
-
if (identity.machine) return "machine (platform admin secret)";
|
|
10970
|
-
|
|
11662
|
+
function credentialLabel(identity) {
|
|
11663
|
+
if (identity.credential.kind === "machine") return "machine (platform admin secret)";
|
|
11664
|
+
if (identity.credential.kind === "device")
|
|
11665
|
+
return identity.scopes.length ? "device (scoped)" : "device";
|
|
11666
|
+
if (identity.credential.kind === "clerk") return "clerk";
|
|
11667
|
+
return "unknown (legacy server)";
|
|
11668
|
+
}
|
|
11669
|
+
function namedPrincipal(identity) {
|
|
11670
|
+
return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
|
|
11671
|
+
}
|
|
11672
|
+
function namedManager(manager) {
|
|
11673
|
+
return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
|
|
11674
|
+
}
|
|
11675
|
+
function accountableOwner(identity) {
|
|
11676
|
+
if (identity.principalKind === "agent" && identity.manager) {
|
|
11677
|
+
return namedManager(identity.manager);
|
|
11678
|
+
}
|
|
11679
|
+
if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
|
|
11680
|
+
return namedPrincipal(identity);
|
|
11681
|
+
}
|
|
11682
|
+
return identity.email ?? "Unnamed member";
|
|
10971
11683
|
}
|
|
10972
11684
|
async function whoamiCommand(parsed, deps = {}) {
|
|
10973
11685
|
assertArgs(
|
|
@@ -10998,9 +11710,19 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
10998
11710
|
return;
|
|
10999
11711
|
}
|
|
11000
11712
|
out.log(`platform: ${cfg.platformUrl}`);
|
|
11001
|
-
out.log(`
|
|
11713
|
+
out.log(`principal: ${namedPrincipal(identity)}`);
|
|
11714
|
+
out.log(`principal id: ${identity.principalId}`);
|
|
11715
|
+
out.log(`kind: ${identity.principalKind}`);
|
|
11716
|
+
if (identity.principalKind === "agent")
|
|
11717
|
+
out.log(
|
|
11718
|
+
`manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
|
|
11719
|
+
);
|
|
11720
|
+
out.log(`owner: ${accountableOwner(identity)}`);
|
|
11721
|
+
out.log(`owner id: ${identity.developerId}`);
|
|
11002
11722
|
out.log(`email: ${identity.email ?? "(none)"}`);
|
|
11003
|
-
out.log(`credential: ${
|
|
11723
|
+
out.log(`credential: ${credentialLabel(identity)}`);
|
|
11724
|
+
if (identity.credential.id)
|
|
11725
|
+
out.log(`credential id: ${identity.credential.id}`);
|
|
11004
11726
|
out.log(`admin: ${identity.admin ? "yes" : "no"}`);
|
|
11005
11727
|
if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
|
|
11006
11728
|
if (!identity.admin) {
|
|
@@ -11149,14 +11871,16 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
11149
11871
|
const name = requireSlug(slug, "edit");
|
|
11150
11872
|
const file = stringOpt(parsed.options.file);
|
|
11151
11873
|
const inline = stringOpt(parsed.options.body);
|
|
11152
|
-
const
|
|
11153
|
-
if (
|
|
11874
|
+
const edited = file === void 0 && inline === void 0 ? await editRunbook(ctx, name) : readBody(file, inline);
|
|
11875
|
+
if (edited === null) return ctx.out.log(`${name} unchanged; nothing written`);
|
|
11876
|
+
const body = typeof edited === "string" ? edited : edited.body;
|
|
11154
11877
|
return runbookEdit(
|
|
11155
11878
|
ctx,
|
|
11156
11879
|
name,
|
|
11157
11880
|
body,
|
|
11158
11881
|
stringOpt(parsed.options.note),
|
|
11159
|
-
parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? ""
|
|
11882
|
+
parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? "",
|
|
11883
|
+
typeof edited === "string" ? void 0 : edited.expectedVersion
|
|
11160
11884
|
);
|
|
11161
11885
|
}
|
|
11162
11886
|
case "import": {
|
|
@@ -11344,7 +12068,7 @@ function hostedSeverity(value2, flag) {
|
|
|
11344
12068
|
var import_security2 = require("@odla-ai/security");
|
|
11345
12069
|
|
|
11346
12070
|
// src/security.ts
|
|
11347
|
-
var
|
|
12071
|
+
var import_node_path18 = require("path");
|
|
11348
12072
|
var import_security = require("@odla-ai/security");
|
|
11349
12073
|
var import_node3 = require("@odla-ai/security/node");
|
|
11350
12074
|
async function runHostedSecurity(options) {
|
|
@@ -11356,9 +12080,9 @@ async function runHostedSecurity(options) {
|
|
|
11356
12080
|
const appId = selfAudit ? "odla-ai" : cfg.app.id;
|
|
11357
12081
|
const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
|
|
11358
12082
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
11359
|
-
const target = (0,
|
|
11360
|
-
const output = (0,
|
|
11361
|
-
const outputRelative = (0,
|
|
12083
|
+
const target = (0, import_node_path18.resolve)(options.target ?? cfg?.rootDir ?? ".");
|
|
12084
|
+
const output = (0, import_node_path18.resolve)(options.out ?? (0, import_node_path18.resolve)(target, ".odla/security/hosted"));
|
|
12085
|
+
const outputRelative = (0, import_node_path18.relative)(target, output).split(import_node_path18.sep).join("/");
|
|
11362
12086
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
11363
12087
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
11364
12088
|
const tokenRequest = {
|
|
@@ -11370,7 +12094,7 @@ async function runHostedSecurity(options) {
|
|
|
11370
12094
|
};
|
|
11371
12095
|
const token = await injectedToken(options, tokenRequest);
|
|
11372
12096
|
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
11373
|
-
exclude: !outputRelative.startsWith("../") && !(0,
|
|
12097
|
+
exclude: !outputRelative.startsWith("../") && !(0, import_node_path18.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
11374
12098
|
});
|
|
11375
12099
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
11376
12100
|
platform,
|
|
@@ -11388,7 +12112,7 @@ async function runHostedSecurity(options) {
|
|
|
11388
12112
|
});
|
|
11389
12113
|
const harness = (0, import_security.createSecurityHarness)({
|
|
11390
12114
|
profile,
|
|
11391
|
-
store: new import_node3.FileRunStore((0,
|
|
12115
|
+
store: new import_node3.FileRunStore((0, import_node_path18.resolve)(output, "state")),
|
|
11392
12116
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
11393
12117
|
validationReasoner: hosted.validationReasoner,
|
|
11394
12118
|
policy: {
|
|
@@ -11412,7 +12136,7 @@ async function runHostedSecurity(options) {
|
|
|
11412
12136
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
11413
12137
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
11414
12138
|
if (!env || !declared.includes(env)) {
|
|
11415
|
-
const shown = (0,
|
|
12139
|
+
const shown = (0, import_node_path18.relative)(rootDir, configPath) || configPath;
|
|
11416
12140
|
throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
|
|
11417
12141
|
}
|
|
11418
12142
|
return env;
|
|
@@ -11441,7 +12165,7 @@ function printSummary(out, appId, env, run, report4, output) {
|
|
|
11441
12165
|
out.log(` coverage: ${report4.coverageStatus} ${complete}/${report4.coverage.length} blocked=${report4.metrics.blockedCells} shallow=${report4.metrics.shallowCells} unscheduled=${report4.metrics.unscheduledCells} budget_exhausted=${report4.metrics.budgetExhaustedCells}`);
|
|
11442
12166
|
if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
|
|
11443
12167
|
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
|
|
11444
|
-
out.log(` report: ${(0,
|
|
12168
|
+
out.log(` report: ${(0, import_node_path18.resolve)(output, "REPORT.md")}`);
|
|
11445
12169
|
}
|
|
11446
12170
|
function formatBudget(usage) {
|
|
11447
12171
|
return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
|
|
@@ -11892,11 +12616,11 @@ async function securityStatus(parsed, dependencies) {
|
|
|
11892
12616
|
// src/cli.ts
|
|
11893
12617
|
function exitCodeFor(err) {
|
|
11894
12618
|
const code = err?.code;
|
|
11895
|
-
if (code === "handshake_pending" || code === "watch_timeout") return 75;
|
|
12619
|
+
if (code === "handshake_pending" || code === "watch_timeout" || code === "operation_pending") return 75;
|
|
11896
12620
|
if (code === "checkpoint_required") return 3;
|
|
11897
12621
|
if (code === "remote_unavailable") return 6;
|
|
11898
12622
|
if (code === "auth_failed") return 5;
|
|
11899
|
-
if (code === "invalid_cursor") return 2;
|
|
12623
|
+
if (code === "invalid_cursor" || code === "invalid_plan" || code === "invalid_operation_id") return 2;
|
|
11900
12624
|
return 1;
|
|
11901
12625
|
}
|
|
11902
12626
|
async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
@@ -12043,6 +12767,7 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
12043
12767
|
CODE_BUILD_RECIPES,
|
|
12044
12768
|
CODE_PI_IMAGE,
|
|
12045
12769
|
COMMAND_SURFACE,
|
|
12770
|
+
ConfigOperationCommandError,
|
|
12046
12771
|
GOOGLE_CALENDAR_EVENTS_SCOPE,
|
|
12047
12772
|
SYSTEM_AI_PURPOSES,
|
|
12048
12773
|
acceptedAfter,
|
|
@@ -12054,7 +12779,10 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
12054
12779
|
calendarServiceConfig,
|
|
12055
12780
|
calendarStatus,
|
|
12056
12781
|
codeConnect,
|
|
12782
|
+
configApply,
|
|
12057
12783
|
configDiff,
|
|
12784
|
+
configOperationGet,
|
|
12785
|
+
configOperationWait,
|
|
12058
12786
|
configPlan,
|
|
12059
12787
|
connectGitHubSecuritySource,
|
|
12060
12788
|
describeProblem,
|