@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/bin.cjs
CHANGED
|
@@ -182,10 +182,10 @@ function isManagedDevVar(line) {
|
|
|
182
182
|
const match = line.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
|
|
183
183
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
184
184
|
}
|
|
185
|
-
function writePrivateText(path,
|
|
185
|
+
function writePrivateText(path, text2) {
|
|
186
186
|
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
|
|
187
187
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
188
|
-
(0, import_node_fs.writeFileSync)(temporary,
|
|
188
|
+
(0, import_node_fs.writeFileSync)(temporary, text2, { mode: 384 });
|
|
189
189
|
(0, import_node_fs.chmodSync)(temporary, 384);
|
|
190
190
|
(0, import_node_fs.renameSync)(temporary, path);
|
|
191
191
|
}
|
|
@@ -492,7 +492,9 @@ function audienceBoundEnvToken(token, platform) {
|
|
|
492
492
|
}
|
|
493
493
|
var SCOPE_PURPOSE = {
|
|
494
494
|
"platform:status:read": "read the platform fleet health and deployment snapshot",
|
|
495
|
+
"platform:chat:credential:write": "rotate the built-in Discussion responder credential",
|
|
495
496
|
"app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
|
|
497
|
+
"app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
|
|
496
498
|
"platform:runbook:write": "add or edit odla's operational runbooks",
|
|
497
499
|
"platform:ai:policy:write": "change System AI model routing",
|
|
498
500
|
"platform:ai:policy:read": "read System AI model routing",
|
|
@@ -550,6 +552,14 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
550
552
|
return token;
|
|
551
553
|
}
|
|
552
554
|
|
|
555
|
+
// src/principal-presentation.ts
|
|
556
|
+
function unresolvedPrincipalLabel(credentialKind2, principalId) {
|
|
557
|
+
const kind = typeof credentialKind2 === "string" ? credentialKind2.trim() : "";
|
|
558
|
+
const id = typeof principalId === "string" ? principalId.trim() : "";
|
|
559
|
+
const audit = kind && id ? `${kind}:${id}` : kind || id;
|
|
560
|
+
return `Unknown principal${audit ? ` [${audit}]` : ""}`;
|
|
561
|
+
}
|
|
562
|
+
|
|
553
563
|
// src/admin-ai-audit.ts
|
|
554
564
|
function adminAiAuditQuery(filters) {
|
|
555
565
|
if (filters.limit === void 0) return "";
|
|
@@ -579,17 +589,17 @@ async function readAdminAiAudit(request2) {
|
|
|
579
589
|
String(event.changeKind ?? ""),
|
|
580
590
|
String(event.purpose ?? event.provider ?? ""),
|
|
581
591
|
route2,
|
|
582
|
-
|
|
592
|
+
unresolvedPrincipalLabel(event.actorType, event.actorId)
|
|
583
593
|
].join(" "));
|
|
584
594
|
}
|
|
585
595
|
}
|
|
586
596
|
async function responseBody(response2) {
|
|
587
|
-
const
|
|
588
|
-
if (!
|
|
597
|
+
const text2 = await response2.text();
|
|
598
|
+
if (!text2) return {};
|
|
589
599
|
try {
|
|
590
|
-
return JSON.parse(
|
|
600
|
+
return JSON.parse(text2);
|
|
591
601
|
} catch {
|
|
592
|
-
return { message:
|
|
602
|
+
return { message: text2.slice(0, 300) };
|
|
593
603
|
}
|
|
594
604
|
}
|
|
595
605
|
function apiError(status, body) {
|
|
@@ -670,7 +680,7 @@ when app/env run actor purpose/role route / policy tokens cost status`);
|
|
|
670
680
|
timestamp2(event.created_at),
|
|
671
681
|
`${String(event.app_id ?? "")}/${String(event.env ?? "")}`,
|
|
672
682
|
String(event.run_id ?? ""),
|
|
673
|
-
|
|
683
|
+
unresolvedPrincipalLabel(event.actor_type, event.actor_id),
|
|
674
684
|
`${String(event.purpose ?? "")}/${String(event.role ?? "")}`,
|
|
675
685
|
`${String(event.provider ?? "")}/${String(event.model ?? "")}@v${String(event.policy_version ?? "unknown")}`,
|
|
676
686
|
String(input + output),
|
|
@@ -691,12 +701,12 @@ function timestamp2(value2) {
|
|
|
691
701
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
692
702
|
}
|
|
693
703
|
async function responseBody2(res) {
|
|
694
|
-
const
|
|
695
|
-
if (!
|
|
704
|
+
const text2 = await res.text();
|
|
705
|
+
if (!text2) return {};
|
|
696
706
|
try {
|
|
697
|
-
return JSON.parse(
|
|
707
|
+
return JSON.parse(text2);
|
|
698
708
|
} catch {
|
|
699
|
-
return { message:
|
|
709
|
+
return { message: text2.slice(0, 300) };
|
|
700
710
|
}
|
|
701
711
|
}
|
|
702
712
|
function apiError2(action2, status, body) {
|
|
@@ -872,12 +882,12 @@ function catalogModels(body) {
|
|
|
872
882
|
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
873
883
|
}
|
|
874
884
|
async function responseBody3(res) {
|
|
875
|
-
const
|
|
876
|
-
if (!
|
|
885
|
+
const text2 = await res.text();
|
|
886
|
+
if (!text2) return {};
|
|
877
887
|
try {
|
|
878
|
-
return JSON.parse(
|
|
888
|
+
return JSON.parse(text2);
|
|
879
889
|
} catch {
|
|
880
|
-
return { message:
|
|
890
|
+
return { message: text2.slice(0, 300) };
|
|
881
891
|
}
|
|
882
892
|
}
|
|
883
893
|
function apiError3(action2, status, body) {
|
|
@@ -1048,6 +1058,7 @@ function unique(values) {
|
|
|
1048
1058
|
var DEFAULT_PLATFORM = "https://odla.ai";
|
|
1049
1059
|
var DEFAULT_ENVS = ["dev"];
|
|
1050
1060
|
var DEFAULT_SERVICES = ["db", "ai"];
|
|
1061
|
+
var configImportSerial = 0;
|
|
1051
1062
|
var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
|
|
1052
1063
|
async function loadProjectConfig(configPath = "odla.config.mjs") {
|
|
1053
1064
|
const resolved = (0, import_node_path4.resolve)(configPath);
|
|
@@ -1255,7 +1266,8 @@ function validId2(value2) {
|
|
|
1255
1266
|
}
|
|
1256
1267
|
async function loadConfigModule(path) {
|
|
1257
1268
|
if (path.endsWith(".json")) return JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
|
|
1258
|
-
const
|
|
1269
|
+
const nonce = `${Date.now()}-${configImportSerial++}`;
|
|
1270
|
+
const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?reload=${nonce}`);
|
|
1259
1271
|
const value2 = mod.default ?? mod.config;
|
|
1260
1272
|
if (typeof value2 === "function") return await value2();
|
|
1261
1273
|
return value2;
|
|
@@ -1719,8 +1731,8 @@ async function appImport(options) {
|
|
|
1719
1731
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
1720
1732
|
const doFetch = options.fetch ?? fetch;
|
|
1721
1733
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
1722
|
-
const
|
|
1723
|
-
const { format, sources } = (0, import_import.parseImport)(
|
|
1734
|
+
const text2 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs8.readFileSync)(0, "utf8")))() : (0, import_node_fs8.readFileSync)(options.file, "utf8");
|
|
1735
|
+
const { format, sources } = (0, import_import.parseImport)(text2, options.ns);
|
|
1724
1736
|
if (format === "namespace-map" && options.ns) {
|
|
1725
1737
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
1726
1738
|
}
|
|
@@ -1811,7 +1823,10 @@ function report(options, owners, headline) {
|
|
|
1811
1823
|
if (headline) out.log(headline);
|
|
1812
1824
|
out.log(`owners (${owners.length}):`);
|
|
1813
1825
|
for (const o of owners) {
|
|
1814
|
-
|
|
1826
|
+
const name = o.email?.trim() || "Unnamed member";
|
|
1827
|
+
out.log(
|
|
1828
|
+
` ${o.primary ? "\u2605" : "\xB7"} ${name} [${o.ownerId}]${o.primary ? " (primary)" : ""}`
|
|
1829
|
+
);
|
|
1815
1830
|
}
|
|
1816
1831
|
}
|
|
1817
1832
|
async function ownersList(options) {
|
|
@@ -2639,7 +2654,8 @@ var CAPABILITIES = {
|
|
|
2639
2654
|
"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",
|
|
2640
2655
|
"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",
|
|
2641
2656
|
"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",
|
|
2642
|
-
"compare one project's checked-in Registry intent with live owner-visible state
|
|
2657
|
+
"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",
|
|
2658
|
+
"inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
|
|
2643
2659
|
"inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
|
|
2644
2660
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
2645
2661
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
@@ -2686,10 +2702,31 @@ function printGroup(out, heading, items) {
|
|
|
2686
2702
|
out.log("");
|
|
2687
2703
|
}
|
|
2688
2704
|
|
|
2689
|
-
// src/config-
|
|
2705
|
+
// src/config-operation-command.ts
|
|
2690
2706
|
var import_apps6 = require("@odla-ai/apps");
|
|
2691
2707
|
var import_node_path7 = require("path");
|
|
2692
2708
|
|
|
2709
|
+
// src/version.ts
|
|
2710
|
+
var import_node_fs9 = require("fs");
|
|
2711
|
+
function cliVersion() {
|
|
2712
|
+
const pkg = JSON.parse((0, import_node_fs9.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
|
|
2713
|
+
return pkg.version ?? "unknown";
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
// src/config-operation-error.ts
|
|
2717
|
+
var ConfigOperationCommandError = class extends Error {
|
|
2718
|
+
constructor(message2, code) {
|
|
2719
|
+
super(message2);
|
|
2720
|
+
this.code = code;
|
|
2721
|
+
this.name = "ConfigOperationCommandError";
|
|
2722
|
+
}
|
|
2723
|
+
code;
|
|
2724
|
+
};
|
|
2725
|
+
|
|
2726
|
+
// src/config-operation-validate.ts
|
|
2727
|
+
var import_apps3 = require("@odla-ai/apps");
|
|
2728
|
+
var import_node_fs10 = require("fs");
|
|
2729
|
+
|
|
2693
2730
|
// src/config-reconcile-digest.ts
|
|
2694
2731
|
var import_node_crypto = require("crypto");
|
|
2695
2732
|
function canonicalJson(value2) {
|
|
@@ -2699,27 +2736,160 @@ function configDigest(value2) {
|
|
|
2699
2736
|
return `sha256:${(0, import_node_crypto.createHash)("sha256").update(canonicalJson(value2)).digest("hex")}`;
|
|
2700
2737
|
}
|
|
2701
2738
|
function canonicalValue(value2) {
|
|
2739
|
+
if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
|
|
2740
|
+
if (typeof value2 === "number") {
|
|
2741
|
+
if (!Number.isFinite(value2)) throw new TypeError("canonical JSON rejects non-finite numbers");
|
|
2742
|
+
return value2;
|
|
2743
|
+
}
|
|
2702
2744
|
if (Array.isArray(value2)) return value2.map(canonicalValue);
|
|
2703
2745
|
if (value2 && typeof value2 === "object") {
|
|
2746
|
+
const record11 = value2;
|
|
2704
2747
|
return Object.fromEntries(
|
|
2705
|
-
Object.
|
|
2748
|
+
Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, canonicalValue(record11[key])])
|
|
2706
2749
|
);
|
|
2707
2750
|
}
|
|
2708
|
-
|
|
2751
|
+
throw new TypeError("canonical JSON rejects unsupported values");
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
// src/config-operation-validate.ts
|
|
2755
|
+
var DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
2756
|
+
var REVISION = /^registry:[1-9][0-9]*$/;
|
|
2757
|
+
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;
|
|
2758
|
+
var ACTION_ID = /^action-[0-9a-f]{16}$/;
|
|
2759
|
+
var ENV = /^[a-z0-9]{2,12}$/;
|
|
2760
|
+
var SERVICE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
2761
|
+
function readPlan(path) {
|
|
2762
|
+
let value2;
|
|
2763
|
+
try {
|
|
2764
|
+
const raw = (0, import_node_fs10.readFileSync)(path, "utf8");
|
|
2765
|
+
if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
|
|
2766
|
+
value2 = JSON.parse(raw);
|
|
2767
|
+
} catch (error) {
|
|
2768
|
+
throw new ConfigOperationCommandError(
|
|
2769
|
+
`cannot read --plan ${path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
2770
|
+
"invalid_plan"
|
|
2771
|
+
);
|
|
2772
|
+
}
|
|
2773
|
+
if (!record2(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
|
|
2774
|
+
if (!record2(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
|
|
2775
|
+
invalidPlan("plan scope is invalid");
|
|
2776
|
+
}
|
|
2777
|
+
if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
|
|
2778
|
+
invalidPlan("plan content revisions are invalid");
|
|
2779
|
+
}
|
|
2780
|
+
if (!REVISION.test(String(value2.registryRevision)) || !DIGEST.test(String(value2.planDigest))) {
|
|
2781
|
+
invalidPlan("plan Registry revision or digest is invalid");
|
|
2782
|
+
}
|
|
2783
|
+
if (!Array.isArray(value2.actions) || !value2.actions.length || value2.actions.length > 32) {
|
|
2784
|
+
invalidPlan("plan actions must contain 1-32 entries");
|
|
2785
|
+
}
|
|
2786
|
+
assertActions(value2.actions);
|
|
2787
|
+
const plan = value2;
|
|
2788
|
+
const digest = configDigest({
|
|
2789
|
+
schemaVersion: plan.schemaVersion,
|
|
2790
|
+
desiredRevision: plan.desiredRevision,
|
|
2791
|
+
observedRevision: plan.observedRevision,
|
|
2792
|
+
registryRevision: plan.registryRevision,
|
|
2793
|
+
actions: plan.actions
|
|
2794
|
+
});
|
|
2795
|
+
if (digest !== plan.planDigest) invalidPlan("plan digest does not bind these revisions and actions");
|
|
2796
|
+
return plan;
|
|
2797
|
+
}
|
|
2798
|
+
function assertPlanContext(plan, appId, platformUrl) {
|
|
2799
|
+
if (plan.scope.appId !== appId || plan.scope.platformUrl.replace(/\/$/, "") !== platformUrl.replace(/\/$/, "")) {
|
|
2800
|
+
throw new ConfigOperationCommandError("plan scope does not match the selected project config", "checkpoint_required");
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
function verifyReceipt(receipt, appId, operationId) {
|
|
2804
|
+
if (receipt.appId !== appId || operationId && receipt.operationId !== operationId) {
|
|
2805
|
+
throw new ConfigOperationCommandError("Registry returned a receipt outside the requested scope", "invalid_receipt");
|
|
2806
|
+
}
|
|
2807
|
+
if (receipt.receiptDigest) {
|
|
2808
|
+
const { receiptDigest, ...fields } = receipt;
|
|
2809
|
+
if (configDigest(fields) !== receiptDigest) {
|
|
2810
|
+
throw new ConfigOperationCommandError("config operation receipt digest is invalid", "invalid_receipt");
|
|
2811
|
+
}
|
|
2812
|
+
} else if (receipt.state === "succeeded" || receipt.state === "conflict" || receipt.state === "failed" && !receipt.error?.retryable) {
|
|
2813
|
+
throw new ConfigOperationCommandError("terminal config operation receipt is not digest-authenticated", "invalid_receipt");
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
function assertOperationId(value2) {
|
|
2817
|
+
if (!OPERATION_ID.test(value2)) {
|
|
2818
|
+
throw new ConfigOperationCommandError("operation id must be a UUID", "invalid_operation_id");
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
function assertActions(actions) {
|
|
2822
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2823
|
+
for (const action2 of actions) {
|
|
2824
|
+
if (!record2(action2)) invalidPlan("every plan action must be an object");
|
|
2825
|
+
const id = String(action2.id ?? "");
|
|
2826
|
+
if (!ACTION_ID.test(id) || ids.has(id)) invalidPlan("plan action ids must be unique frozen ids");
|
|
2827
|
+
ids.add(id);
|
|
2828
|
+
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))) {
|
|
2829
|
+
invalidPlan("plan action metadata is invalid");
|
|
2830
|
+
}
|
|
2831
|
+
if (["rename_app", "enable_service", "configure_service", "set_link"].includes(String(action2.kind))) {
|
|
2832
|
+
assertConditionalAction(action2);
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
function assertConditionalAction(action2) {
|
|
2837
|
+
if (action2.kind === "rename_app") {
|
|
2838
|
+
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");
|
|
2839
|
+
return;
|
|
2840
|
+
}
|
|
2841
|
+
if (!action2.env || !ENV.test(action2.env)) invalidPlan("conditional action env is invalid");
|
|
2842
|
+
if (action2.kind === "set_link") {
|
|
2843
|
+
if (action2.path !== `environments.${action2.env}.link` || action2.applySupport !== "provision" || !linkState(action2.before) || !linkState(action2.after)) invalidPlan("link action is invalid");
|
|
2844
|
+
return;
|
|
2845
|
+
}
|
|
2846
|
+
if (!action2.service || !SERVICE.test(action2.service) || !(0, import_apps3.appServiceDefinition)(action2.service)) {
|
|
2847
|
+
invalidPlan("service action names an unknown service");
|
|
2848
|
+
}
|
|
2849
|
+
const base = `environments.${action2.env}.services.${action2.service}`;
|
|
2850
|
+
if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
|
|
2851
|
+
invalidPlan("service action path is invalid");
|
|
2852
|
+
}
|
|
2853
|
+
if (action2.applySupport !== "provision" || !record2(action2.after)) {
|
|
2854
|
+
invalidPlan("service action payload is invalid");
|
|
2855
|
+
}
|
|
2856
|
+
if (action2.kind === "enable_service") {
|
|
2857
|
+
if (action2.after.enabled !== true || action2.before !== null && !record2(action2.before)) {
|
|
2858
|
+
invalidPlan("service enable action is invalid");
|
|
2859
|
+
}
|
|
2860
|
+
} else if (!record2(action2.before)) {
|
|
2861
|
+
invalidPlan("service configure action is invalid");
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
function linkState(value2) {
|
|
2865
|
+
if (value2 === null) return true;
|
|
2866
|
+
if (typeof value2 !== "string") return false;
|
|
2867
|
+
try {
|
|
2868
|
+
const url = new URL(value2.trim());
|
|
2869
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
2870
|
+
} catch {
|
|
2871
|
+
return false;
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
function invalidPlan(message2) {
|
|
2875
|
+
throw new ConfigOperationCommandError(message2, "invalid_plan");
|
|
2876
|
+
}
|
|
2877
|
+
function record2(value2) {
|
|
2878
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
2709
2879
|
}
|
|
2710
2880
|
|
|
2711
2881
|
// src/config-reconcile-desired.ts
|
|
2712
|
-
var
|
|
2882
|
+
var import_apps5 = require("@odla-ai/apps");
|
|
2713
2883
|
|
|
2714
2884
|
// src/provision-helpers.ts
|
|
2715
2885
|
var import_ai = require("@odla-ai/ai");
|
|
2716
|
-
var
|
|
2886
|
+
var import_apps4 = require("@odla-ai/apps");
|
|
2717
2887
|
function defaultSecretName(provider) {
|
|
2718
2888
|
const names = import_ai.DEFAULT_SECRET_NAMES;
|
|
2719
2889
|
return names[provider] ?? `${provider}_api_key`;
|
|
2720
2890
|
}
|
|
2721
2891
|
async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
2722
|
-
const tenantId = (0,
|
|
2892
|
+
const tenantId = (0, import_apps4.tenantIdFor)(cfg.app.id, env);
|
|
2723
2893
|
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
|
|
2724
2894
|
headers: { authorization: `Bearer ${token}` }
|
|
2725
2895
|
});
|
|
@@ -2761,7 +2931,7 @@ async function safeText3(res) {
|
|
|
2761
2931
|
// src/config-reconcile-desired.ts
|
|
2762
2932
|
function desiredRegistryState(cfg) {
|
|
2763
2933
|
const environments = {};
|
|
2764
|
-
const services = (0,
|
|
2934
|
+
const services = (0, import_apps5.orderAppServices)(cfg.services);
|
|
2765
2935
|
for (const env of cfg.envs) {
|
|
2766
2936
|
const desiredServices = {};
|
|
2767
2937
|
for (const service of services) {
|
|
@@ -2801,8 +2971,208 @@ function managedServiceConfig(cfg, env, service) {
|
|
|
2801
2971
|
return {};
|
|
2802
2972
|
}
|
|
2803
2973
|
|
|
2974
|
+
// src/config-reconcile-support.ts
|
|
2975
|
+
var CONDITIONAL_KINDS = /* @__PURE__ */ new Set([
|
|
2976
|
+
"rename_app",
|
|
2977
|
+
"enable_service",
|
|
2978
|
+
"configure_service",
|
|
2979
|
+
"set_link"
|
|
2980
|
+
]);
|
|
2981
|
+
function configApplySupport(reconciliation) {
|
|
2982
|
+
if (!reconciliation.observedRevision || !reconciliation.registryRevision) {
|
|
2983
|
+
return {
|
|
2984
|
+
supported: false,
|
|
2985
|
+
checkpointRequired: false,
|
|
2986
|
+
reason: "the app must already exist in a revision-aware Registry"
|
|
2987
|
+
};
|
|
2988
|
+
}
|
|
2989
|
+
if (!reconciliation.actions.length) {
|
|
2990
|
+
return {
|
|
2991
|
+
supported: false,
|
|
2992
|
+
checkpointRequired: false,
|
|
2993
|
+
reason: "there are no managed changes to apply"
|
|
2994
|
+
};
|
|
2995
|
+
}
|
|
2996
|
+
const blocked = reconciliation.actions.filter(
|
|
2997
|
+
(action2) => !CONDITIONAL_KINDS.has(action2.kind) || action2.risk !== "low" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
|
|
2998
|
+
);
|
|
2999
|
+
if (blocked.length) {
|
|
3000
|
+
return {
|
|
3001
|
+
supported: false,
|
|
3002
|
+
checkpointRequired: blocked.some(
|
|
3003
|
+
(action2) => action2.risk === "high" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
|
|
3004
|
+
),
|
|
3005
|
+
reason: `${blocked.length} action${blocked.length === 1 ? "" : "s"} remain outside conditional apply`
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
3008
|
+
return {
|
|
3009
|
+
supported: true,
|
|
3010
|
+
checkpointRequired: false,
|
|
3011
|
+
reason: "all actions are low-risk, checkpoint-free Registry changes"
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
// src/config-operation-command.ts
|
|
3016
|
+
var IDEMPOTENCY_KEY = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/;
|
|
3017
|
+
var DEFAULT_WAIT_SECONDS = 60;
|
|
3018
|
+
var DEFAULT_INTERVAL_SECONDS = 2;
|
|
3019
|
+
async function configApply(options) {
|
|
3020
|
+
const plan = readPlan(options.planPath);
|
|
3021
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
3022
|
+
assertPlanContext(plan, cfg.app.id, cfg.platformUrl);
|
|
3023
|
+
const support = configApplySupport(plan);
|
|
3024
|
+
if (!support.supported) {
|
|
3025
|
+
throw new ConfigOperationCommandError(
|
|
3026
|
+
support.reason,
|
|
3027
|
+
support.checkpointRequired ? "checkpoint_required" : "invalid_plan"
|
|
3028
|
+
);
|
|
3029
|
+
}
|
|
3030
|
+
if (configDigest(desiredRegistryState(cfg)) !== plan.desiredRevision) {
|
|
3031
|
+
throw new ConfigOperationCommandError(
|
|
3032
|
+
"project config changed after this plan was frozen; generate and review a fresh plan",
|
|
3033
|
+
"checkpoint_required"
|
|
3034
|
+
);
|
|
3035
|
+
}
|
|
3036
|
+
const idempotencyKey = options.idempotencyKey ?? `cli:${plan.planDigest.slice(7)}`;
|
|
3037
|
+
if (!IDEMPOTENCY_KEY.test(idempotencyKey)) {
|
|
3038
|
+
throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
|
|
3039
|
+
}
|
|
3040
|
+
const client = await operationClient(cfg, options, "apply");
|
|
3041
|
+
const request2 = {
|
|
3042
|
+
schemaVersion: "odla.config-operation-request/v1",
|
|
3043
|
+
expectedRevision: plan.registryRevision,
|
|
3044
|
+
desiredRevision: plan.desiredRevision,
|
|
3045
|
+
observedRevision: plan.observedRevision,
|
|
3046
|
+
planDigest: plan.planDigest,
|
|
3047
|
+
idempotencyKey,
|
|
3048
|
+
source: { kind: "cli", version: cliVersion() },
|
|
3049
|
+
actions: plan.actions
|
|
3050
|
+
};
|
|
3051
|
+
let receipt;
|
|
3052
|
+
try {
|
|
3053
|
+
receipt = await client.applyConfigOperation(cfg.app.id, request2);
|
|
3054
|
+
} catch (error) {
|
|
3055
|
+
const retained = retainedReceipt(error);
|
|
3056
|
+
if (retained) {
|
|
3057
|
+
verifyReceipt(retained, cfg.app.id);
|
|
3058
|
+
printReceipt(retained, options);
|
|
3059
|
+
throw failureForReceipt(retained);
|
|
3060
|
+
}
|
|
3061
|
+
throw normalizeRequestError(error);
|
|
3062
|
+
}
|
|
3063
|
+
verifyReceipt(receipt, cfg.app.id);
|
|
3064
|
+
printReceipt(receipt, options);
|
|
3065
|
+
assertApplyCompleted(receipt);
|
|
3066
|
+
return receipt;
|
|
3067
|
+
}
|
|
3068
|
+
async function configOperationGet(options) {
|
|
3069
|
+
assertOperationId(options.operationId);
|
|
3070
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
3071
|
+
const client = await operationClient(cfg, options, "read");
|
|
3072
|
+
const receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
|
|
3073
|
+
throw normalizeRequestError(error);
|
|
3074
|
+
});
|
|
3075
|
+
if (!receipt) {
|
|
3076
|
+
throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
|
|
3077
|
+
}
|
|
3078
|
+
verifyReceipt(receipt, cfg.app.id, options.operationId);
|
|
3079
|
+
printReceipt(receipt, options);
|
|
3080
|
+
return receipt;
|
|
3081
|
+
}
|
|
3082
|
+
async function configOperationWait(options) {
|
|
3083
|
+
assertOperationId(options.operationId);
|
|
3084
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
3085
|
+
const client = await operationClient(cfg, options, "wait");
|
|
3086
|
+
const wait2 = options.pollWait ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
|
|
3087
|
+
const now = () => (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
3088
|
+
const deadline = now() + (options.timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
|
|
3089
|
+
const interval = (options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS) * 1e3;
|
|
3090
|
+
let receipt = null;
|
|
3091
|
+
for (; ; ) {
|
|
3092
|
+
receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
|
|
3093
|
+
throw normalizeRequestError(error);
|
|
3094
|
+
});
|
|
3095
|
+
if (!receipt) {
|
|
3096
|
+
throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
|
|
3097
|
+
}
|
|
3098
|
+
verifyReceipt(receipt, cfg.app.id, options.operationId);
|
|
3099
|
+
if (receipt.state !== "running") break;
|
|
3100
|
+
if (now() >= deadline) {
|
|
3101
|
+
printReceipt(receipt, options);
|
|
3102
|
+
throw new ConfigOperationCommandError("config operation is still running", "operation_pending");
|
|
3103
|
+
}
|
|
3104
|
+
await wait2(Math.min(interval, Math.max(0, deadline - now())));
|
|
3105
|
+
}
|
|
3106
|
+
printReceipt(receipt, options);
|
|
3107
|
+
if (receipt.state !== "succeeded") throw failureForReceipt(receipt);
|
|
3108
|
+
return receipt;
|
|
3109
|
+
}
|
|
3110
|
+
async function operationClient(cfg, options, purpose) {
|
|
3111
|
+
const doFetch = options.fetch ?? fetch;
|
|
3112
|
+
const out = options.stdout ?? console;
|
|
3113
|
+
const token = await resolveAdminPlatformToken({
|
|
3114
|
+
platform: cfg.platformUrl,
|
|
3115
|
+
scope: "app:config:write",
|
|
3116
|
+
token: options.token,
|
|
3117
|
+
tokenFile: (0, import_node_path7.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3118
|
+
rootDir: cfg.rootDir,
|
|
3119
|
+
email: options.email,
|
|
3120
|
+
open: options.open,
|
|
3121
|
+
fetch: doFetch,
|
|
3122
|
+
stdout: out,
|
|
3123
|
+
openApprovalUrl: options.openApprovalUrl,
|
|
3124
|
+
label: `odla CLI (${cfg.app.id} config operation ${purpose})`
|
|
3125
|
+
});
|
|
3126
|
+
return (0, import_apps6.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
3127
|
+
}
|
|
3128
|
+
function printReceipt(receipt, options) {
|
|
3129
|
+
const out = options.stdout ?? console;
|
|
3130
|
+
if (options.json) out.log(JSON.stringify(receipt, null, 2));
|
|
3131
|
+
else {
|
|
3132
|
+
out.log(`config operation ${receipt.operationId}: ${receipt.state}`);
|
|
3133
|
+
out.log(`revision: ${receipt.expectedRevision} \u2192 ${receipt.currentRevision}`);
|
|
3134
|
+
for (const step of receipt.progress) out.log(` ${step.state} ${step.actionId} attempts=${step.attempts}`);
|
|
3135
|
+
out.log(`receipt: ${receipt.receiptDigest ?? "pending"}`);
|
|
3136
|
+
out.log(`studio: ${receipt.studioUrl}`);
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
function assertApplyCompleted(receipt) {
|
|
3140
|
+
if (receipt.state === "succeeded") return;
|
|
3141
|
+
throw receipt.state === "running" ? new ConfigOperationCommandError("config operation is still running", "operation_pending") : failureForReceipt(receipt);
|
|
3142
|
+
}
|
|
3143
|
+
function failureForReceipt(receipt) {
|
|
3144
|
+
if (receipt.state === "conflict") return new ConfigOperationCommandError(
|
|
3145
|
+
receipt.error?.message ?? "config operation conflicted with current Registry state",
|
|
3146
|
+
"checkpoint_required"
|
|
3147
|
+
);
|
|
3148
|
+
if (receipt.error?.retryable) return new ConfigOperationCommandError(receipt.error.message, "remote_unavailable");
|
|
3149
|
+
return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
|
|
3150
|
+
}
|
|
3151
|
+
function retainedReceipt(error) {
|
|
3152
|
+
if (!(error instanceof import_apps6.AppsError) || !record3(error.details)) return null;
|
|
3153
|
+
return record3(error.details.operation) ? error.details.operation : null;
|
|
3154
|
+
}
|
|
3155
|
+
function normalizeRequestError(error) {
|
|
3156
|
+
if (!(error instanceof import_apps6.AppsError)) return error instanceof Error ? error : new Error(String(error));
|
|
3157
|
+
if (error.status === 401 || error.status === 403) {
|
|
3158
|
+
return new ConfigOperationCommandError(error.message, "auth_failed");
|
|
3159
|
+
}
|
|
3160
|
+
if (error.status === 409) return new ConfigOperationCommandError(error.message, "checkpoint_required");
|
|
3161
|
+
if (error.status === 429 || error.status >= 500) {
|
|
3162
|
+
return new ConfigOperationCommandError(error.message, "remote_unavailable");
|
|
3163
|
+
}
|
|
3164
|
+
return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
|
|
3165
|
+
}
|
|
3166
|
+
function record3(value2) {
|
|
3167
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
// src/config-reconcile-command.ts
|
|
3171
|
+
var import_apps8 = require("@odla-ai/apps");
|
|
3172
|
+
var import_node_path8 = require("path");
|
|
3173
|
+
|
|
2804
3174
|
// src/config-reconcile.ts
|
|
2805
|
-
var
|
|
3175
|
+
var import_apps7 = require("@odla-ai/apps");
|
|
2806
3176
|
|
|
2807
3177
|
// src/config-reconcile-values.ts
|
|
2808
3178
|
function difference(path, desired, observed, status, reason, desiredSource, observedSource, env, service) {
|
|
@@ -2903,6 +3273,7 @@ function reconcileConfig(input) {
|
|
|
2903
3273
|
sources: { desired: desiredSource, observed: observedSource },
|
|
2904
3274
|
desiredRevision,
|
|
2905
3275
|
observedRevision,
|
|
3276
|
+
registryRevision: input.observed?.configRevision ?? null,
|
|
2906
3277
|
status: different ? "different" : unmanaged ? "unmanaged" : "in_sync",
|
|
2907
3278
|
summary: {
|
|
2908
3279
|
differences: different,
|
|
@@ -2957,7 +3328,7 @@ function compareEnvironments(desired, observed, desiredSource, observedSource, a
|
|
|
2957
3328
|
function compareServices(env, desired, observed, desiredSource, observedSource, add) {
|
|
2958
3329
|
const wanted = desired.environments[env]?.services ?? {};
|
|
2959
3330
|
const live = observed?.environments[env] ?? {};
|
|
2960
|
-
const knownOrder = (0,
|
|
3331
|
+
const knownOrder = (0, import_apps7.orderAppServices)((0, import_apps7.appServiceIds)());
|
|
2961
3332
|
const services = [.../* @__PURE__ */ new Set([...knownOrder, ...Object.keys(wanted), ...Object.keys(live)])];
|
|
2962
3333
|
for (const service of services) {
|
|
2963
3334
|
const next = wanted[service];
|
|
@@ -3068,20 +3439,19 @@ async function configDiff(options) {
|
|
|
3068
3439
|
}
|
|
3069
3440
|
async function configPlan(options) {
|
|
3070
3441
|
const reconciliation = await inspectConfig(options);
|
|
3442
|
+
const apply = configApplySupport(reconciliation);
|
|
3071
3443
|
const planDigest = configDigest({
|
|
3072
|
-
schemaVersion: "odla.config-plan/
|
|
3444
|
+
schemaVersion: "odla.config-plan/v2",
|
|
3073
3445
|
desiredRevision: reconciliation.desiredRevision,
|
|
3074
3446
|
observedRevision: reconciliation.observedRevision,
|
|
3447
|
+
registryRevision: reconciliation.registryRevision,
|
|
3075
3448
|
actions: reconciliation.actions
|
|
3076
3449
|
});
|
|
3077
3450
|
const document2 = {
|
|
3078
|
-
schemaVersion: "odla.config-plan/
|
|
3451
|
+
schemaVersion: "odla.config-plan/v2",
|
|
3079
3452
|
...reconciliation,
|
|
3080
3453
|
planDigest,
|
|
3081
|
-
apply
|
|
3082
|
-
supported: false,
|
|
3083
|
-
reason: "conditional, resumable config apply is not available yet; use the exact reviewed commands below"
|
|
3084
|
-
},
|
|
3454
|
+
apply,
|
|
3085
3455
|
nextActions: planNextActions(reconciliation, options.configPath)
|
|
3086
3456
|
};
|
|
3087
3457
|
printPlan2(document2, options);
|
|
@@ -3095,7 +3465,7 @@ async function inspectConfig(options) {
|
|
|
3095
3465
|
platform: cfg.platformUrl,
|
|
3096
3466
|
scope: "app:config:read",
|
|
3097
3467
|
token: options.token,
|
|
3098
|
-
tokenFile: (0,
|
|
3468
|
+
tokenFile: (0, import_node_path8.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3099
3469
|
rootDir: cfg.rootDir,
|
|
3100
3470
|
email: options.email,
|
|
3101
3471
|
open: options.open,
|
|
@@ -3104,7 +3474,7 @@ async function inspectConfig(options) {
|
|
|
3104
3474
|
openApprovalUrl: options.openApprovalUrl,
|
|
3105
3475
|
label: `odla CLI (${cfg.app.id} config read)`
|
|
3106
3476
|
});
|
|
3107
|
-
const client = (0,
|
|
3477
|
+
const client = (0, import_apps8.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
3108
3478
|
const observed = await client.resolveApp(cfg.app.id);
|
|
3109
3479
|
return reconcileConfig({
|
|
3110
3480
|
desired: desiredRegistryState(cfg),
|
|
@@ -3140,7 +3510,7 @@ function printPlan2(document2, options) {
|
|
|
3140
3510
|
}
|
|
3141
3511
|
if (!document2.actions.length) out.log(" no managed changes");
|
|
3142
3512
|
out.log(`plan digest: ${document2.planDigest}`);
|
|
3143
|
-
out.log(`apply:
|
|
3513
|
+
out.log(`apply: ${document2.apply.supported ? "supported" : "blocked"} \u2014 ${document2.apply.reason}`);
|
|
3144
3514
|
printNext(out, document2.nextActions);
|
|
3145
3515
|
}
|
|
3146
3516
|
function printHeader(out, kind, document2) {
|
|
@@ -3148,6 +3518,7 @@ function printHeader(out, kind, document2) {
|
|
|
3148
3518
|
out.log(`platform: ${document2.scope.platformUrl}`);
|
|
3149
3519
|
out.log(`desired: ${document2.desiredRevision}`);
|
|
3150
3520
|
out.log(`observed: ${document2.observedRevision ?? "absent"}`);
|
|
3521
|
+
out.log(`registry: ${document2.registryRevision ?? "absent"}`);
|
|
3151
3522
|
out.log(
|
|
3152
3523
|
`summary: ${document2.summary.differences} different, ${document2.summary.unmanaged} unmanaged, ${document2.summary.actions} planned actions`
|
|
3153
3524
|
);
|
|
@@ -3180,6 +3551,13 @@ function diffNextActions(reconciliation, configPath) {
|
|
|
3180
3551
|
}
|
|
3181
3552
|
function planNextActions(reconciliation, configPath) {
|
|
3182
3553
|
const next = [];
|
|
3554
|
+
if (configApplySupport(reconciliation).supported) {
|
|
3555
|
+
next.push({
|
|
3556
|
+
code: "apply_frozen_plan",
|
|
3557
|
+
command: "odla-ai config apply --plan <saved-plan.json> --json",
|
|
3558
|
+
description: "Save this JSON document, then conditionally apply these exact revision-bound actions."
|
|
3559
|
+
});
|
|
3560
|
+
}
|
|
3183
3561
|
if (reconciliation.actions.some((action2) => action2.applySupport === "provision")) {
|
|
3184
3562
|
next.push({
|
|
3185
3563
|
code: "review_provision",
|
|
@@ -3193,35 +3571,39 @@ function planNextActions(reconciliation, configPath) {
|
|
|
3193
3571
|
}
|
|
3194
3572
|
}
|
|
3195
3573
|
if (reconciliation.actions.some((action2) => action2.applySupport === "studio")) {
|
|
3196
|
-
const { appId, platformUrl } = reconciliation.scope;
|
|
3197
3574
|
next.push({
|
|
3198
3575
|
code: "review_destructive",
|
|
3199
|
-
command:
|
|
3576
|
+
command: studioSettingsUrl(reconciliation),
|
|
3200
3577
|
description: "Review service disablement in the owning Studio scope; this plan will not apply it."
|
|
3201
3578
|
});
|
|
3202
3579
|
}
|
|
3203
3580
|
if (!reconciliation.actions.length && reconciliation.summary.unmanaged) {
|
|
3204
3581
|
next.push({
|
|
3205
3582
|
code: "declare_or_accept_runtime_state",
|
|
3206
|
-
command:
|
|
3583
|
+
command: studioSettingsUrl(reconciliation),
|
|
3207
3584
|
description: "Declare the live value in project config or keep it as an explicit runtime-managed setting."
|
|
3208
3585
|
});
|
|
3209
3586
|
}
|
|
3210
3587
|
return next;
|
|
3211
3588
|
}
|
|
3589
|
+
function studioSettingsUrl(reconciliation) {
|
|
3590
|
+
const environment2 = reconciliation.actions.map((action2) => action2.env).concat(reconciliation.scope.environments).find((candidate) => candidate === "dev" || candidate === "prod") ?? "dev";
|
|
3591
|
+
const origin = reconciliation.scope.platformUrl.replace(/\/$/, "");
|
|
3592
|
+
return `${origin}${(0, import_apps8.studioAppSettingsPath)(reconciliation.scope.appId, environment2, "environment")}`;
|
|
3593
|
+
}
|
|
3212
3594
|
function quoteArg2(value2) {
|
|
3213
3595
|
return `'${value2.replace(/'/g, `'\\''`)}'`;
|
|
3214
3596
|
}
|
|
3215
3597
|
|
|
3216
3598
|
// src/doctor-checks.ts
|
|
3217
3599
|
var import_node_child_process3 = require("child_process");
|
|
3218
|
-
var
|
|
3219
|
-
var
|
|
3600
|
+
var import_node_fs12 = require("fs");
|
|
3601
|
+
var import_node_path10 = require("path");
|
|
3220
3602
|
|
|
3221
3603
|
// src/wrangler.ts
|
|
3222
3604
|
var import_node_child_process2 = require("child_process");
|
|
3223
|
-
var
|
|
3224
|
-
var
|
|
3605
|
+
var import_node_fs11 = require("fs");
|
|
3606
|
+
var import_node_path9 = require("path");
|
|
3225
3607
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
3226
3608
|
const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
3227
3609
|
let stdout = "";
|
|
@@ -3235,28 +3617,28 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
|
|
|
3235
3617
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
3236
3618
|
function findWranglerConfig(rootDir) {
|
|
3237
3619
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
3238
|
-
const path = (0,
|
|
3239
|
-
if ((0,
|
|
3620
|
+
const path = (0, import_node_path9.join)(rootDir, name);
|
|
3621
|
+
if ((0, import_node_fs11.existsSync)(path)) return path;
|
|
3240
3622
|
}
|
|
3241
3623
|
return null;
|
|
3242
3624
|
}
|
|
3243
3625
|
function readWranglerConfig(path) {
|
|
3244
3626
|
if (path.endsWith(".toml")) return null;
|
|
3245
3627
|
try {
|
|
3246
|
-
return JSON.parse(stripJsonComments((0,
|
|
3628
|
+
return JSON.parse(stripJsonComments((0, import_node_fs11.readFileSync)(path, "utf8")));
|
|
3247
3629
|
} catch {
|
|
3248
3630
|
return null;
|
|
3249
3631
|
}
|
|
3250
3632
|
}
|
|
3251
|
-
function stripJsonComments(
|
|
3633
|
+
function stripJsonComments(text2) {
|
|
3252
3634
|
let result = "";
|
|
3253
3635
|
let inString = false;
|
|
3254
|
-
for (let i = 0; i <
|
|
3255
|
-
const ch =
|
|
3636
|
+
for (let i = 0; i < text2.length; i++) {
|
|
3637
|
+
const ch = text2[i];
|
|
3256
3638
|
if (inString) {
|
|
3257
3639
|
result += ch;
|
|
3258
3640
|
if (ch === "\\") {
|
|
3259
|
-
result +=
|
|
3641
|
+
result += text2[i + 1] ?? "";
|
|
3260
3642
|
i++;
|
|
3261
3643
|
} else if (ch === '"') {
|
|
3262
3644
|
inString = false;
|
|
@@ -3268,14 +3650,14 @@ function stripJsonComments(text) {
|
|
|
3268
3650
|
result += ch;
|
|
3269
3651
|
continue;
|
|
3270
3652
|
}
|
|
3271
|
-
if (ch === "/" &&
|
|
3272
|
-
while (i <
|
|
3653
|
+
if (ch === "/" && text2[i + 1] === "/") {
|
|
3654
|
+
while (i < text2.length && text2[i] !== "\n") i++;
|
|
3273
3655
|
result += "\n";
|
|
3274
3656
|
continue;
|
|
3275
3657
|
}
|
|
3276
|
-
if (ch === "/" &&
|
|
3658
|
+
if (ch === "/" && text2[i + 1] === "*") {
|
|
3277
3659
|
i += 2;
|
|
3278
|
-
while (i <
|
|
3660
|
+
while (i < text2.length && !(text2[i] === "*" && text2[i + 1] === "/")) i++;
|
|
3279
3661
|
i++;
|
|
3280
3662
|
continue;
|
|
3281
3663
|
}
|
|
@@ -3292,7 +3674,14 @@ async function wranglerLoggedIn(run, cwd) {
|
|
|
3292
3674
|
}
|
|
3293
3675
|
}
|
|
3294
3676
|
function wranglerPutSecret(run, opts) {
|
|
3295
|
-
const args = [
|
|
3677
|
+
const args = [
|
|
3678
|
+
"wrangler",
|
|
3679
|
+
"secret",
|
|
3680
|
+
"put",
|
|
3681
|
+
opts.name,
|
|
3682
|
+
...opts.env ? ["--env", opts.env] : [],
|
|
3683
|
+
...opts.configPath ? ["--config", opts.configPath] : []
|
|
3684
|
+
];
|
|
3296
3685
|
return run("npx", args, { input: opts.value, cwd: opts.cwd });
|
|
3297
3686
|
}
|
|
3298
3687
|
|
|
@@ -3341,10 +3730,10 @@ function wranglerWarnings(rootDir) {
|
|
|
3341
3730
|
for (const { label, block } of blocks) {
|
|
3342
3731
|
const assets = block.assets;
|
|
3343
3732
|
if (assets?.directory) {
|
|
3344
|
-
const dir = (0,
|
|
3345
|
-
if (dir === (0,
|
|
3733
|
+
const dir = (0, import_node_path10.resolve)(rootDir, assets.directory);
|
|
3734
|
+
if (dir === (0, import_node_path10.resolve)(rootDir)) {
|
|
3346
3735
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
3347
|
-
} else if ((0,
|
|
3736
|
+
} else if ((0, import_node_fs12.existsSync)((0, import_node_path10.join)(dir, "node_modules"))) {
|
|
3348
3737
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
3349
3738
|
}
|
|
3350
3739
|
}
|
|
@@ -3379,13 +3768,13 @@ function o11yProjectWarnings(rootDir) {
|
|
|
3379
3768
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
|
|
3380
3769
|
return warnings;
|
|
3381
3770
|
}
|
|
3382
|
-
const main = typeof config.main === "string" ? (0,
|
|
3383
|
-
if (!main || !(0,
|
|
3771
|
+
const main = typeof config.main === "string" ? (0, import_node_path10.resolve)(rootDir, config.main) : null;
|
|
3772
|
+
if (!main || !(0, import_node_fs12.existsSync)(main)) {
|
|
3384
3773
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
|
|
3385
3774
|
} else {
|
|
3386
3775
|
let source = "";
|
|
3387
3776
|
try {
|
|
3388
|
-
source = (0,
|
|
3777
|
+
source = (0, import_node_fs12.readFileSync)(main, "utf8");
|
|
3389
3778
|
} catch {
|
|
3390
3779
|
}
|
|
3391
3780
|
if (!/\bwithObservability\b/.test(source)) {
|
|
@@ -3409,7 +3798,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
3409
3798
|
}
|
|
3410
3799
|
function readPackageJson(rootDir) {
|
|
3411
3800
|
try {
|
|
3412
|
-
return JSON.parse((0,
|
|
3801
|
+
return JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(rootDir, "package.json"), "utf8"));
|
|
3413
3802
|
} catch {
|
|
3414
3803
|
return null;
|
|
3415
3804
|
}
|
|
@@ -3636,14 +4025,14 @@ function harnessOption(value2, flag) {
|
|
|
3636
4025
|
}
|
|
3637
4026
|
|
|
3638
4027
|
// src/init.ts
|
|
3639
|
-
var
|
|
3640
|
-
var
|
|
3641
|
-
var
|
|
4028
|
+
var import_node_fs13 = require("fs");
|
|
4029
|
+
var import_node_path11 = require("path");
|
|
4030
|
+
var import_apps9 = require("@odla-ai/apps");
|
|
3642
4031
|
function initProject(options) {
|
|
3643
4032
|
const out = options.stdout ?? console;
|
|
3644
|
-
const rootDir = (0,
|
|
3645
|
-
const configPath = (0,
|
|
3646
|
-
if ((0,
|
|
4033
|
+
const rootDir = (0, import_node_path11.resolve)(options.rootDir ?? process.cwd());
|
|
4034
|
+
const configPath = (0, import_node_path11.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
|
|
4035
|
+
if ((0, import_node_fs13.existsSync)(configPath) && !options.force) {
|
|
3647
4036
|
throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
|
|
3648
4037
|
}
|
|
3649
4038
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
|
|
@@ -3652,27 +4041,27 @@ function initProject(options) {
|
|
|
3652
4041
|
const envs = options.envs?.length ? options.envs : ["dev"];
|
|
3653
4042
|
const services = options.services?.length ? options.services : ["db", "ai"];
|
|
3654
4043
|
for (const service of services) {
|
|
3655
|
-
const definition = (0,
|
|
3656
|
-
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0,
|
|
4044
|
+
const definition = (0, import_apps9.appServiceDefinition)(service);
|
|
4045
|
+
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps9.appServiceIds)().join(", ")})`);
|
|
3657
4046
|
for (const dependency of definition.requires) {
|
|
3658
4047
|
if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
|
|
3659
4048
|
}
|
|
3660
4049
|
}
|
|
3661
4050
|
const aiProvider = options.aiProvider ?? "anthropic";
|
|
3662
|
-
(0,
|
|
3663
|
-
(0,
|
|
3664
|
-
(0,
|
|
3665
|
-
(0,
|
|
3666
|
-
writeIfMissing((0,
|
|
3667
|
-
writeIfMissing((0,
|
|
4051
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(configPath), { recursive: true });
|
|
4052
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path11.resolve)(rootDir, "src/odla"), { recursive: true });
|
|
4053
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path11.resolve)(rootDir, ".odla"), { recursive: true });
|
|
4054
|
+
(0, import_node_fs13.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
|
|
4055
|
+
writeIfMissing((0, import_node_path11.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
|
|
4056
|
+
writeIfMissing((0, import_node_path11.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
|
|
3668
4057
|
ensureGitignore(rootDir);
|
|
3669
4058
|
out.log(`created ${relativeDisplay(configPath, rootDir)}`);
|
|
3670
4059
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
3671
4060
|
out.log("updated .gitignore for local odla credentials");
|
|
3672
4061
|
}
|
|
3673
|
-
function writeIfMissing(path,
|
|
3674
|
-
if ((0,
|
|
3675
|
-
(0,
|
|
4062
|
+
function writeIfMissing(path, text2) {
|
|
4063
|
+
if ((0, import_node_fs13.existsSync)(path)) return;
|
|
4064
|
+
(0, import_node_fs13.writeFileSync)(path, text2);
|
|
3676
4065
|
}
|
|
3677
4066
|
function configTemplate(input) {
|
|
3678
4067
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -3847,7 +4236,7 @@ function assertWranglerConfig(cfg) {
|
|
|
3847
4236
|
|
|
3848
4237
|
// src/secrets-set.ts
|
|
3849
4238
|
var import_ai2 = require("@odla-ai/ai");
|
|
3850
|
-
var
|
|
4239
|
+
var import_apps10 = require("@odla-ai/apps");
|
|
3851
4240
|
var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
|
|
3852
4241
|
async function secretsSet(options) {
|
|
3853
4242
|
const name = (options.name ?? "").trim();
|
|
@@ -3880,13 +4269,13 @@ async function secretsSetClerkKey(options) {
|
|
|
3880
4269
|
body: JSON.stringify({ value: value2 })
|
|
3881
4270
|
});
|
|
3882
4271
|
if (!res.ok) {
|
|
3883
|
-
const
|
|
3884
|
-
throw new Error(`store Clerk secret key failed (${res.status}): ${
|
|
4272
|
+
const text2 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
4273
|
+
throw new Error(`store Clerk secret key failed (${res.status}): ${text2 || "request failed"}`);
|
|
3885
4274
|
}
|
|
3886
4275
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
3887
4276
|
}
|
|
3888
|
-
function scrubValue(
|
|
3889
|
-
return redactSecrets(
|
|
4277
|
+
function scrubValue(text2, value2) {
|
|
4278
|
+
return redactSecrets(text2).split(value2).join("[value redacted]");
|
|
3890
4279
|
}
|
|
3891
4280
|
async function resolveVaultWrite(options) {
|
|
3892
4281
|
const out = options.stdout ?? console;
|
|
@@ -3899,13 +4288,13 @@ async function resolveVaultWrite(options) {
|
|
|
3899
4288
|
throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
|
|
3900
4289
|
}
|
|
3901
4290
|
const value2 = await secretInputValue(options, "secret");
|
|
3902
|
-
return { cfg, tenantId: (0,
|
|
4291
|
+
return { cfg, tenantId: (0, import_apps10.tenantIdFor)(cfg.app.id, options.env), value: value2, doFetch, out };
|
|
3903
4292
|
}
|
|
3904
4293
|
|
|
3905
4294
|
// src/skill.ts
|
|
3906
|
-
var
|
|
4295
|
+
var import_node_fs14 = require("fs");
|
|
3907
4296
|
var import_node_os2 = require("os");
|
|
3908
|
-
var
|
|
4297
|
+
var import_node_path12 = require("path");
|
|
3909
4298
|
var import_node_url2 = require("url");
|
|
3910
4299
|
|
|
3911
4300
|
// src/skill-adapters.ts
|
|
@@ -3984,8 +4373,8 @@ function installSkill(options = {}) {
|
|
|
3984
4373
|
const files = listFiles(sourceDir);
|
|
3985
4374
|
if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
|
|
3986
4375
|
const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
|
|
3987
|
-
const root = (0,
|
|
3988
|
-
const home = (0,
|
|
4376
|
+
const root = (0, import_node_path12.resolve)(options.dir ?? process.cwd());
|
|
4377
|
+
const home = (0, import_node_path12.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
|
|
3989
4378
|
const plans = /* @__PURE__ */ new Map();
|
|
3990
4379
|
const targets = /* @__PURE__ */ new Map();
|
|
3991
4380
|
const rememberTarget = (harness, target) => {
|
|
@@ -3999,48 +4388,48 @@ function installSkill(options = {}) {
|
|
|
3999
4388
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
4000
4389
|
};
|
|
4001
4390
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
4002
|
-
for (const rel of files) plan((0,
|
|
4391
|
+
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);
|
|
4003
4392
|
};
|
|
4004
4393
|
let targetDir;
|
|
4005
4394
|
if (options.global) {
|
|
4006
|
-
const claudeRoot = (0,
|
|
4007
|
-
const codexRoot = (0,
|
|
4395
|
+
const claudeRoot = (0, import_node_path12.join)(home, ".claude", "skills");
|
|
4396
|
+
const codexRoot = (0, import_node_path12.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path12.join)(home, ".codex"), "skills");
|
|
4008
4397
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
4009
4398
|
for (const harness of harnesses) {
|
|
4010
4399
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
4011
|
-
planSkillTree(skillRoot, harness === "claude" ? home : (0,
|
|
4400
|
+
planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path12.dirname)((0, import_node_path12.dirname)(codexRoot)));
|
|
4012
4401
|
rememberTarget(harness, skillRoot);
|
|
4013
4402
|
}
|
|
4014
4403
|
} else {
|
|
4015
|
-
const sharedRoot = (0,
|
|
4404
|
+
const sharedRoot = (0, import_node_path12.join)(root, ".agents", "skills");
|
|
4016
4405
|
planSkillTree(sharedRoot);
|
|
4017
|
-
const claudeRoot = (0,
|
|
4406
|
+
const claudeRoot = (0, import_node_path12.join)(root, ".claude", "skills");
|
|
4018
4407
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
4019
4408
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
4020
4409
|
if (harnesses.includes("claude")) {
|
|
4021
4410
|
for (const skill of skillNames(files)) {
|
|
4022
|
-
const canonical = (0,
|
|
4023
|
-
plan((0,
|
|
4411
|
+
const canonical = (0, import_node_fs14.readFileSync)((0, import_node_path12.join)(sourceDir, skill, "SKILL.md"), "utf8");
|
|
4412
|
+
plan((0, import_node_path12.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
|
|
4024
4413
|
}
|
|
4025
4414
|
rememberTarget("claude", claudeRoot);
|
|
4026
4415
|
}
|
|
4027
4416
|
if (harnesses.includes("cursor")) {
|
|
4028
|
-
const cursorRule = (0,
|
|
4417
|
+
const cursorRule = (0, import_node_path12.join)(root, ".cursor", "rules", "odla.mdc");
|
|
4029
4418
|
plan(cursorRule, CURSOR_RULE);
|
|
4030
4419
|
rememberTarget("cursor", cursorRule);
|
|
4031
4420
|
}
|
|
4032
4421
|
if (harnesses.includes("agents")) {
|
|
4033
|
-
const agentsFile = (0,
|
|
4422
|
+
const agentsFile = (0, import_node_path12.join)(root, "AGENTS.md");
|
|
4034
4423
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4035
4424
|
rememberTarget("agents", agentsFile);
|
|
4036
4425
|
}
|
|
4037
4426
|
if (harnesses.includes("copilot")) {
|
|
4038
|
-
const copilotFile = (0,
|
|
4427
|
+
const copilotFile = (0, import_node_path12.join)(root, ".github", "copilot-instructions.md");
|
|
4039
4428
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4040
4429
|
rememberTarget("copilot", copilotFile);
|
|
4041
4430
|
}
|
|
4042
4431
|
if (harnesses.includes("gemini")) {
|
|
4043
|
-
const geminiFile = (0,
|
|
4432
|
+
const geminiFile = (0, import_node_path12.join)(root, "GEMINI.md");
|
|
4044
4433
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4045
4434
|
rememberTarget("gemini", geminiFile);
|
|
4046
4435
|
}
|
|
@@ -4054,11 +4443,11 @@ function installSkill(options = {}) {
|
|
|
4054
4443
|
conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
|
|
4055
4444
|
continue;
|
|
4056
4445
|
}
|
|
4057
|
-
if (!(0,
|
|
4446
|
+
if (!(0, import_node_fs14.existsSync)(file.target)) {
|
|
4058
4447
|
writtenPaths.add(file.target);
|
|
4059
4448
|
continue;
|
|
4060
4449
|
}
|
|
4061
|
-
const current = (0,
|
|
4450
|
+
const current = (0, import_node_fs14.readFileSync)(file.target, "utf8");
|
|
4062
4451
|
if (current === file.content) {
|
|
4063
4452
|
unchangedPaths.add(file.target);
|
|
4064
4453
|
} else if (file.managedMerge || options.force) {
|
|
@@ -4075,9 +4464,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
4075
4464
|
);
|
|
4076
4465
|
}
|
|
4077
4466
|
for (const file of plans.values()) {
|
|
4078
|
-
if (!(0,
|
|
4079
|
-
(0,
|
|
4080
|
-
(0,
|
|
4467
|
+
if (!(0, import_node_fs14.existsSync)(file.target) || (0, import_node_fs14.readFileSync)(file.target, "utf8") !== file.content) {
|
|
4468
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(file.target), { recursive: true });
|
|
4469
|
+
(0, import_node_fs14.writeFileSync)(file.target, file.content);
|
|
4081
4470
|
}
|
|
4082
4471
|
}
|
|
4083
4472
|
const skills = skillNames(files);
|
|
@@ -4096,7 +4485,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
4096
4485
|
};
|
|
4097
4486
|
}
|
|
4098
4487
|
function pathsUnder(root, paths) {
|
|
4099
|
-
return [...paths].map((path) => (0,
|
|
4488
|
+
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();
|
|
4100
4489
|
}
|
|
4101
4490
|
function normalizeHarnesses(values, global) {
|
|
4102
4491
|
const requested = values?.length ? values : ["claude"];
|
|
@@ -4118,9 +4507,9 @@ function normalizeHarnesses(values, global) {
|
|
|
4118
4507
|
function managedFileContent(path, block, force, boundary) {
|
|
4119
4508
|
const symlink = symlinkedComponent(boundary, path);
|
|
4120
4509
|
if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
|
|
4121
|
-
if (!(0,
|
|
4510
|
+
if (!(0, import_node_fs14.existsSync)(path)) return `${block}
|
|
4122
4511
|
`;
|
|
4123
|
-
const current = (0,
|
|
4512
|
+
const current = (0, import_node_fs14.readFileSync)(path, "utf8");
|
|
4124
4513
|
const start = "<!-- odla-ai agent setup:start -->";
|
|
4125
4514
|
const end = "<!-- odla-ai agent setup:end -->";
|
|
4126
4515
|
const startAt = current.indexOf(start);
|
|
@@ -4141,15 +4530,15 @@ function managedFileContent(path, block, force, boundary) {
|
|
|
4141
4530
|
return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
|
|
4142
4531
|
}
|
|
4143
4532
|
function symlinkedComponent(boundary, target) {
|
|
4144
|
-
const rel = (0,
|
|
4145
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
4533
|
+
const rel = (0, import_node_path12.relative)(boundary, target);
|
|
4534
|
+
if (rel === ".." || rel.startsWith(`..${import_node_path12.sep}`) || (0, import_node_path12.isAbsolute)(rel)) {
|
|
4146
4535
|
throw new Error(`agent setup target escapes its install root: ${target}`);
|
|
4147
4536
|
}
|
|
4148
4537
|
let current = boundary;
|
|
4149
|
-
for (const part of rel.split(
|
|
4150
|
-
current = (0,
|
|
4538
|
+
for (const part of rel.split(import_node_path12.sep).filter(Boolean)) {
|
|
4539
|
+
current = (0, import_node_path12.join)(current, part);
|
|
4151
4540
|
try {
|
|
4152
|
-
if ((0,
|
|
4541
|
+
if ((0, import_node_fs14.lstatSync)(current).isSymbolicLink()) return current;
|
|
4153
4542
|
} catch (error) {
|
|
4154
4543
|
if (error.code !== "ENOENT") throw error;
|
|
4155
4544
|
}
|
|
@@ -4160,13 +4549,13 @@ function skillNames(files) {
|
|
|
4160
4549
|
return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
|
|
4161
4550
|
}
|
|
4162
4551
|
function listFiles(dir) {
|
|
4163
|
-
if (!(0,
|
|
4552
|
+
if (!(0, import_node_fs14.existsSync)(dir)) return [];
|
|
4164
4553
|
const results = [];
|
|
4165
4554
|
const walk = (current) => {
|
|
4166
|
-
for (const entry of (0,
|
|
4167
|
-
const path = (0,
|
|
4555
|
+
for (const entry of (0, import_node_fs14.readdirSync)(current, { withFileTypes: true })) {
|
|
4556
|
+
const path = (0, import_node_path12.join)(current, entry.name);
|
|
4168
4557
|
if (entry.isDirectory()) walk(path);
|
|
4169
|
-
else results.push((0,
|
|
4558
|
+
else results.push((0, import_node_path12.relative)(dir, path));
|
|
4170
4559
|
}
|
|
4171
4560
|
};
|
|
4172
4561
|
walk(dir);
|
|
@@ -4361,10 +4750,14 @@ async function secretsCommand(parsed, deps) {
|
|
|
4361
4750
|
async function projectCommand(command, parsed, deps) {
|
|
4362
4751
|
if (command === "config") {
|
|
4363
4752
|
const sub = parsed.positionals[1];
|
|
4364
|
-
if (sub !== "diff" && sub !== "plan") {
|
|
4753
|
+
if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
|
|
4365
4754
|
throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
|
|
4366
4755
|
}
|
|
4367
|
-
assertArgs(
|
|
4756
|
+
assertArgs(
|
|
4757
|
+
parsed,
|
|
4758
|
+
sub === "apply" ? ["config", "plan", "idempotency-key", "token", "email", "open", "json"] : ["config", "token", "email", "open", "json"],
|
|
4759
|
+
2
|
|
4760
|
+
);
|
|
4368
4761
|
const options = {
|
|
4369
4762
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
4370
4763
|
token: stringOpt(parsed.options.token),
|
|
@@ -4376,7 +4769,42 @@ async function projectCommand(command, parsed, deps) {
|
|
|
4376
4769
|
stdout: deps.stdout
|
|
4377
4770
|
};
|
|
4378
4771
|
if (sub === "diff") await configDiff(options);
|
|
4379
|
-
else await configPlan(options);
|
|
4772
|
+
else if (sub === "plan") await configPlan(options);
|
|
4773
|
+
else await configApply({
|
|
4774
|
+
...options,
|
|
4775
|
+
planPath: requiredString(parsed.options.plan, "--plan"),
|
|
4776
|
+
idempotencyKey: stringOpt(parsed.options["idempotency-key"])
|
|
4777
|
+
});
|
|
4778
|
+
return true;
|
|
4779
|
+
}
|
|
4780
|
+
if (command === "operations") {
|
|
4781
|
+
const sub = parsed.positionals[1];
|
|
4782
|
+
if (sub !== "get" && sub !== "wait") {
|
|
4783
|
+
throw new Error(`unknown operations subcommand "${sub ?? ""}". Try "odla-ai operations get <operation-id> --json".`);
|
|
4784
|
+
}
|
|
4785
|
+
assertArgs(
|
|
4786
|
+
parsed,
|
|
4787
|
+
sub === "wait" ? ["config", "token", "email", "open", "json", "interval", "timeout"] : ["config", "token", "email", "open", "json"],
|
|
4788
|
+
3
|
|
4789
|
+
);
|
|
4790
|
+
const operationId = parsed.positionals[2];
|
|
4791
|
+
if (!operationId) throw new Error(`operation id is required`);
|
|
4792
|
+
const options = {
|
|
4793
|
+
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
4794
|
+
operationId,
|
|
4795
|
+
token: stringOpt(parsed.options.token),
|
|
4796
|
+
email: stringOpt(parsed.options.email),
|
|
4797
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
4798
|
+
json: parsed.options.json === true,
|
|
4799
|
+
fetch: deps.fetch,
|
|
4800
|
+
openApprovalUrl: deps.openUrl,
|
|
4801
|
+
stdout: deps.stdout,
|
|
4802
|
+
pollWait: deps.pollWait,
|
|
4803
|
+
intervalSeconds: numberOpt(parsed.options.interval, "--interval"),
|
|
4804
|
+
timeoutSeconds: numberOpt(parsed.options.timeout, "--timeout")
|
|
4805
|
+
};
|
|
4806
|
+
if (sub === "get") await configOperationGet(options);
|
|
4807
|
+
else await configOperationWait(options);
|
|
4380
4808
|
return true;
|
|
4381
4809
|
}
|
|
4382
4810
|
if (command === "init") {
|
|
@@ -4437,9 +4865,9 @@ async function projectCommand(command, parsed, deps) {
|
|
|
4437
4865
|
}
|
|
4438
4866
|
|
|
4439
4867
|
// src/code-connect.ts
|
|
4440
|
-
var
|
|
4868
|
+
var import_node_fs15 = require("fs");
|
|
4441
4869
|
var import_node_os4 = require("os");
|
|
4442
|
-
var
|
|
4870
|
+
var import_node_path14 = require("path");
|
|
4443
4871
|
|
|
4444
4872
|
// ../harness/dist/chunk-QTUEF2HZ.js
|
|
4445
4873
|
var HARNESS_PROTOCOL_VERSION = 1;
|
|
@@ -4449,7 +4877,7 @@ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
|
4449
4877
|
var HarnessProtocolError = class extends Error {
|
|
4450
4878
|
name = "HarnessProtocolError";
|
|
4451
4879
|
};
|
|
4452
|
-
function
|
|
4880
|
+
function record4(value2) {
|
|
4453
4881
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
4454
4882
|
}
|
|
4455
4883
|
function boundedText(value2, label, max) {
|
|
@@ -4466,7 +4894,7 @@ function parseAgentOutput(line) {
|
|
|
4466
4894
|
} catch {
|
|
4467
4895
|
throw new HarnessProtocolError("agent emitted invalid JSON");
|
|
4468
4896
|
}
|
|
4469
|
-
const message2 =
|
|
4897
|
+
const message2 = record4(value2);
|
|
4470
4898
|
if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
|
|
4471
4899
|
throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
|
|
4472
4900
|
}
|
|
@@ -4479,7 +4907,7 @@ function parseAgentOutput(line) {
|
|
|
4479
4907
|
};
|
|
4480
4908
|
}
|
|
4481
4909
|
if (message2.type === "inference.request") {
|
|
4482
|
-
const call2 =
|
|
4910
|
+
const call2 = record4(message2.call);
|
|
4483
4911
|
if (!call2 || !Array.isArray(call2.messages) || !Number.isSafeInteger(call2.maxTokens)) {
|
|
4484
4912
|
throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
|
|
4485
4913
|
}
|
|
@@ -4491,7 +4919,7 @@ function parseAgentOutput(line) {
|
|
|
4491
4919
|
};
|
|
4492
4920
|
}
|
|
4493
4921
|
if (message2.type === "tool.request") {
|
|
4494
|
-
const input =
|
|
4922
|
+
const input = record4(message2.input);
|
|
4495
4923
|
const tool = String(message2.tool);
|
|
4496
4924
|
if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
|
|
4497
4925
|
throw new HarnessProtocolError("tool.request requires a registered tool and object input");
|
|
@@ -4675,8 +5103,8 @@ async function runContainerAttempt(options) {
|
|
|
4675
5103
|
let stopped = false;
|
|
4676
5104
|
let exited = false;
|
|
4677
5105
|
child.stderr.setEncoding("utf8");
|
|
4678
|
-
child.stderr.on("data", (
|
|
4679
|
-
if (stderr.length < 64 * 1024) stderr +=
|
|
5106
|
+
child.stderr.on("data", (text2) => {
|
|
5107
|
+
if (stderr.length < 64 * 1024) stderr += text2.slice(0, 64 * 1024 - stderr.length);
|
|
4680
5108
|
});
|
|
4681
5109
|
const stop = (reason) => {
|
|
4682
5110
|
if (stopped || exited) return;
|
|
@@ -4834,8 +5262,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
4834
5262
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
4835
5263
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
4836
5264
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
4837
|
-
const entries = inventory.flatMap((
|
|
4838
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
5265
|
+
const entries = inventory.flatMap((record11) => {
|
|
5266
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record11);
|
|
4839
5267
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
4840
5268
|
});
|
|
4841
5269
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -5083,8 +5511,8 @@ function normalize(value2) {
|
|
|
5083
5511
|
if (Array.isArray(value2)) return value2.map(normalize);
|
|
5084
5512
|
if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
|
|
5085
5513
|
if (typeof value2 === "object") {
|
|
5086
|
-
const
|
|
5087
|
-
return Object.fromEntries(Object.keys(
|
|
5514
|
+
const record11 = value2;
|
|
5515
|
+
return Object.fromEntries(Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, normalize(record11[key])]));
|
|
5088
5516
|
}
|
|
5089
5517
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
5090
5518
|
}
|
|
@@ -5163,7 +5591,7 @@ function normalizeReaders(readers) {
|
|
|
5163
5591
|
}
|
|
5164
5592
|
|
|
5165
5593
|
// ../camel/dist/code.js
|
|
5166
|
-
var
|
|
5594
|
+
var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
|
|
5167
5595
|
var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
5168
5596
|
var ID = /^[A-Za-z0-9._:-]{1,160}$/;
|
|
5169
5597
|
async function digestCodeVerificationReceipt(fields) {
|
|
@@ -5198,18 +5626,18 @@ async function digestCodeVerificationReceipt(fields) {
|
|
|
5198
5626
|
return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
|
|
5199
5627
|
}
|
|
5200
5628
|
function validate(fields) {
|
|
5201
|
-
if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !
|
|
5629
|
+
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) {
|
|
5202
5630
|
throw new CamelError("state_conflict", "Code verification receipt is malformed or outside its bounds.");
|
|
5203
5631
|
}
|
|
5204
5632
|
const ids = /* @__PURE__ */ new Set();
|
|
5205
5633
|
for (const recipe2 of fields.recipes) {
|
|
5206
|
-
if (typeof recipe2.recipeId !== "string" || !ID.test(recipe2.recipeId) || ids.has(recipe2.recipeId) || typeof recipe2.recipeDigest !== "string" || !
|
|
5634
|
+
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) {
|
|
5207
5635
|
throw new CamelError("state_conflict", "Code verification recipe receipt is malformed or outside its bounds.");
|
|
5208
5636
|
}
|
|
5209
5637
|
const artifactIds = /* @__PURE__ */ new Set();
|
|
5210
5638
|
if (recipe2.artifacts.length > 64) throw new CamelError("state_conflict", "Code verification has too many artifacts.");
|
|
5211
5639
|
for (const artifact of recipe2.artifacts) {
|
|
5212
|
-
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 && !
|
|
5640
|
+
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)) {
|
|
5213
5641
|
throw new CamelError("state_conflict", "Code verification artifact receipt is malformed.");
|
|
5214
5642
|
}
|
|
5215
5643
|
artifactIds.add(artifact.artifactId);
|
|
@@ -5225,7 +5653,7 @@ function validate(fields) {
|
|
|
5225
5653
|
}
|
|
5226
5654
|
}
|
|
5227
5655
|
var SHA2 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
5228
|
-
var
|
|
5656
|
+
var DIGEST22 = /^sha256:[0-9a-f]{64}$/;
|
|
5229
5657
|
var ID2 = /^[A-Za-z0-9._:-]{1,180}$/;
|
|
5230
5658
|
var MAX_PATCH_BYTES = 256 * 1024;
|
|
5231
5659
|
var MAX_STATE_BYTES = 64 * 1024;
|
|
@@ -5270,14 +5698,14 @@ function normalizeState(value2) {
|
|
|
5270
5698
|
]);
|
|
5271
5699
|
const planCursor = state2.planCursor;
|
|
5272
5700
|
const conversations = strings(state2.conversationRefs, "conversationRefs", ID2, 256, false);
|
|
5273
|
-
const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals",
|
|
5274
|
-
if (planCursor !== null && (typeof planCursor !== "string" || !ID2.test(planCursor)) || typeof state2.planningInputDigest !== "string" || !
|
|
5701
|
+
const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals", DIGEST22, 256, true);
|
|
5702
|
+
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) {
|
|
5275
5703
|
throw invalid2("Portable checkpoint state is malformed or outside its bounds.");
|
|
5276
5704
|
}
|
|
5277
5705
|
const effects = state2.completedEffects.map((item) => {
|
|
5278
5706
|
const effect = object(item, "completed effect");
|
|
5279
5707
|
exact2(effect, ["effectId", "actionDigest", "receiptDigest"]);
|
|
5280
|
-
if (typeof effect.effectId !== "string" || !ID2.test(effect.effectId) || typeof effect.actionDigest !== "string" || !
|
|
5708
|
+
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)) {
|
|
5281
5709
|
throw invalid2("Portable checkpoint effect receipt is malformed.");
|
|
5282
5710
|
}
|
|
5283
5711
|
return {
|
|
@@ -5508,8 +5936,8 @@ function boundedInteger(value2, spec) {
|
|
|
5508
5936
|
}
|
|
5509
5937
|
function boundedNumber(value2, spec) {
|
|
5510
5938
|
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.");
|
|
5511
|
-
const
|
|
5512
|
-
if (/e/i.test(
|
|
5939
|
+
const text2 = String(value2);
|
|
5940
|
+
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.");
|
|
5513
5941
|
return value2;
|
|
5514
5942
|
}
|
|
5515
5943
|
function enumMember(value2, spec) {
|
|
@@ -5665,8 +6093,8 @@ function validateUnsafeSelector(path, value2, tool) {
|
|
|
5665
6093
|
return void 0;
|
|
5666
6094
|
}
|
|
5667
6095
|
function looksLikeDestination(value2) {
|
|
5668
|
-
const
|
|
5669
|
-
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(
|
|
6096
|
+
const text2 = value2.trim();
|
|
6097
|
+
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text2) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text2);
|
|
5670
6098
|
}
|
|
5671
6099
|
|
|
5672
6100
|
// ../harness/dist/chunk-GMVZ4LZH.js
|
|
@@ -5807,7 +6235,7 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5807
6235
|
}
|
|
5808
6236
|
const value2 = await response2.json().catch(() => null);
|
|
5809
6237
|
if (!response2.ok) {
|
|
5810
|
-
const problem =
|
|
6238
|
+
const problem = record5(record5(value2)?.error);
|
|
5811
6239
|
throw new CodeRuntimeControlError(
|
|
5812
6240
|
typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
|
|
5813
6241
|
response2.status,
|
|
@@ -5829,12 +6257,12 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5829
6257
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
5830
6258
|
),
|
|
5831
6259
|
infer: async (sessionId, inference) => {
|
|
5832
|
-
const value2 =
|
|
6260
|
+
const value2 = record5(await call2(
|
|
5833
6261
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
5834
6262
|
inference,
|
|
5835
6263
|
modelRequestTimeoutMs
|
|
5836
6264
|
));
|
|
5837
|
-
if (!value2 || value2.requestId !== inference.requestId || !
|
|
6265
|
+
if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
|
|
5838
6266
|
throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
|
|
5839
6267
|
}
|
|
5840
6268
|
return value2;
|
|
@@ -5892,12 +6320,12 @@ function validateHeartbeat(version, capabilities) {
|
|
|
5892
6320
|
}
|
|
5893
6321
|
}
|
|
5894
6322
|
function parseSnapshot(value2) {
|
|
5895
|
-
const root =
|
|
5896
|
-
const host =
|
|
6323
|
+
const root = record5(value2);
|
|
6324
|
+
const host = record5(root?.host);
|
|
5897
6325
|
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");
|
|
5898
6326
|
const bindingIds = /* @__PURE__ */ new Set();
|
|
5899
6327
|
const bindings = root.bindings.map((item) => {
|
|
5900
|
-
const binding =
|
|
6328
|
+
const binding = record5(item);
|
|
5901
6329
|
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)) {
|
|
5902
6330
|
throw invalid("binding");
|
|
5903
6331
|
}
|
|
@@ -5907,10 +6335,10 @@ function parseSnapshot(value2) {
|
|
|
5907
6335
|
const commandIds = /* @__PURE__ */ new Set();
|
|
5908
6336
|
const commandSequences = /* @__PURE__ */ new Set();
|
|
5909
6337
|
const commands = root.commands.map((item) => {
|
|
5910
|
-
const command =
|
|
6338
|
+
const command = record5(item);
|
|
5911
6339
|
const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
|
|
5912
6340
|
const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
|
|
5913
|
-
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)) || !
|
|
6341
|
+
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");
|
|
5914
6342
|
commandIds.add(command.commandId);
|
|
5915
6343
|
commandSequences.add(sequenceKey);
|
|
5916
6344
|
return command;
|
|
@@ -5918,10 +6346,10 @@ function parseSnapshot(value2) {
|
|
|
5918
6346
|
return { host, bindings, commands };
|
|
5919
6347
|
}
|
|
5920
6348
|
async function parseSource(value2) {
|
|
5921
|
-
const snapshot =
|
|
6349
|
+
const snapshot = record5(record5(value2)?.snapshot);
|
|
5922
6350
|
if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
|
|
5923
6351
|
const files = snapshot.files.map((value22) => {
|
|
5924
|
-
const file =
|
|
6352
|
+
const file = record5(value22);
|
|
5925
6353
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
5926
6354
|
return { path: file.path, content: file.content };
|
|
5927
6355
|
});
|
|
@@ -5930,11 +6358,11 @@ async function parseSource(value2) {
|
|
|
5930
6358
|
const aliases = /* @__PURE__ */ new Set();
|
|
5931
6359
|
const references = [];
|
|
5932
6360
|
for (const item of referencesValue) {
|
|
5933
|
-
const reference =
|
|
6361
|
+
const reference = record5(item);
|
|
5934
6362
|
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");
|
|
5935
6363
|
aliases.add(reference.alias);
|
|
5936
6364
|
const referenceFiles = reference.files.map((entry) => {
|
|
5937
|
-
const file =
|
|
6365
|
+
const file = record5(entry);
|
|
5938
6366
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
|
|
5939
6367
|
return { path: file.path, content: file.content };
|
|
5940
6368
|
});
|
|
@@ -5949,18 +6377,18 @@ async function parseSource(value2) {
|
|
|
5949
6377
|
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
5950
6378
|
}
|
|
5951
6379
|
function parseReview(value2) {
|
|
5952
|
-
const review =
|
|
6380
|
+
const review = record5(record5(value2)?.review);
|
|
5953
6381
|
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");
|
|
5954
6382
|
return review;
|
|
5955
6383
|
}
|
|
5956
6384
|
function parseCandidate(value2) {
|
|
5957
|
-
const candidate =
|
|
6385
|
+
const candidate = record5(record5(value2)?.candidate);
|
|
5958
6386
|
if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
|
|
5959
6387
|
throw invalid("candidate");
|
|
5960
6388
|
}
|
|
5961
6389
|
return { candidateId: candidate.candidateId, status: candidate.status };
|
|
5962
6390
|
}
|
|
5963
|
-
var
|
|
6391
|
+
var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
5964
6392
|
var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
|
|
5965
6393
|
var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
5966
6394
|
var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
@@ -6035,8 +6463,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
6035
6463
|
});
|
|
6036
6464
|
let stderr = "";
|
|
6037
6465
|
child.stderr.setEncoding("utf8");
|
|
6038
|
-
child.stderr.on("data", (
|
|
6039
|
-
if (stderr.length < 4e3) stderr +=
|
|
6466
|
+
child.stderr.on("data", (text2) => {
|
|
6467
|
+
if (stderr.length < 4e3) stderr += text2.slice(0, 4e3);
|
|
6040
6468
|
});
|
|
6041
6469
|
child.once("error", reject);
|
|
6042
6470
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(`patch did not apply: ${stderr.trim().slice(0, 500)}`)));
|
|
@@ -7207,10 +7635,10 @@ var CodePiRuntimeEngine = class {
|
|
|
7207
7635
|
task: lease.task,
|
|
7208
7636
|
limits: this.options.limits,
|
|
7209
7637
|
signal: active.abort.signal,
|
|
7210
|
-
onStderr: (
|
|
7638
|
+
onStderr: (text2) => this.#event(command, {
|
|
7211
7639
|
type: "message",
|
|
7212
7640
|
actor: "system",
|
|
7213
|
-
body:
|
|
7641
|
+
body: text2.slice(0, 4e3)
|
|
7214
7642
|
}, active.conversationRefs),
|
|
7215
7643
|
onMessage: async (output) => {
|
|
7216
7644
|
if (output.type === "inference.request") {
|
|
@@ -7323,13 +7751,6 @@ var CodePiRuntimeEngine = class {
|
|
|
7323
7751
|
}
|
|
7324
7752
|
};
|
|
7325
7753
|
|
|
7326
|
-
// src/version.ts
|
|
7327
|
-
var import_node_fs13 = require("fs");
|
|
7328
|
-
function cliVersion() {
|
|
7329
|
-
const pkg = JSON.parse((0, import_node_fs13.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
|
|
7330
|
-
return pkg.version ?? "unknown";
|
|
7331
|
-
}
|
|
7332
|
-
|
|
7333
7754
|
// src/security-hosted-github.ts
|
|
7334
7755
|
var import_node_child_process4 = require("child_process");
|
|
7335
7756
|
var import_node_util2 = require("util");
|
|
@@ -7605,7 +8026,7 @@ var import_node_child_process6 = require("child_process");
|
|
|
7605
8026
|
var import_node_crypto3 = require("crypto");
|
|
7606
8027
|
var import_promises10 = require("fs/promises");
|
|
7607
8028
|
var import_node_os3 = require("os");
|
|
7608
|
-
var
|
|
8029
|
+
var import_node_path13 = require("path");
|
|
7609
8030
|
var import_node_url3 = require("url");
|
|
7610
8031
|
|
|
7611
8032
|
// src/code-runtime-config.ts
|
|
@@ -7691,10 +8112,10 @@ async function embeddedPiImageName() {
|
|
|
7691
8112
|
return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto3.createHash)("sha256").update(bundle).digest("hex")}`;
|
|
7692
8113
|
}
|
|
7693
8114
|
async function buildEmbeddedPiImage(engine, image, run) {
|
|
7694
|
-
const context = await (0, import_promises10.mkdtemp)((0,
|
|
8115
|
+
const context = await (0, import_promises10.mkdtemp)((0, import_node_path13.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
|
|
7695
8116
|
try {
|
|
7696
|
-
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0,
|
|
7697
|
-
await (0, import_promises10.writeFile)((0,
|
|
8117
|
+
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path13.join)(context, "pi-agent.js"));
|
|
8118
|
+
await (0, import_promises10.writeFile)((0, import_node_path13.join)(context, "Dockerfile"), [
|
|
7698
8119
|
`FROM ${CODE_NODE_IMAGE}`,
|
|
7699
8120
|
"COPY pi-agent.js /opt/odla/pi-agent.js",
|
|
7700
8121
|
"WORKDIR /workspace",
|
|
@@ -7710,8 +8131,8 @@ async function buildEmbeddedPiImage(engine, image, run) {
|
|
|
7710
8131
|
// src/code-connect.ts
|
|
7711
8132
|
async function codeConnect(options) {
|
|
7712
8133
|
const cwd = options.cwd ?? process.cwd();
|
|
7713
|
-
const configPath = (0,
|
|
7714
|
-
const cfg = (0,
|
|
8134
|
+
const configPath = (0, import_node_path14.resolve)(cwd, options.configPath);
|
|
8135
|
+
const cfg = (0, import_node_fs15.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
|
|
7715
8136
|
const requestedAppId = options.appId?.trim();
|
|
7716
8137
|
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
7717
8138
|
throw new Error("--app-id must be a valid odla app id");
|
|
@@ -7869,20 +8290,20 @@ async function runCodeRuntime(input) {
|
|
|
7869
8290
|
}
|
|
7870
8291
|
}
|
|
7871
8292
|
function parseConnection(value2, appId, appEnv) {
|
|
7872
|
-
const root =
|
|
7873
|
-
const host =
|
|
7874
|
-
const offer =
|
|
7875
|
-
const binding =
|
|
8293
|
+
const root = record6(value2);
|
|
8294
|
+
const host = record6(root?.host);
|
|
8295
|
+
const offer = record6(root?.offer);
|
|
8296
|
+
const binding = record6(root?.binding);
|
|
7876
8297
|
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)) {
|
|
7877
8298
|
throw new Error("connect Code host returned an invalid response");
|
|
7878
8299
|
}
|
|
7879
8300
|
return root;
|
|
7880
8301
|
}
|
|
7881
8302
|
function apiFailure(action2, status, value2) {
|
|
7882
|
-
const message2 =
|
|
8303
|
+
const message2 = record6(record6(value2)?.error)?.message;
|
|
7883
8304
|
return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
7884
8305
|
}
|
|
7885
|
-
function
|
|
8306
|
+
function record6(value2) {
|
|
7886
8307
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
7887
8308
|
}
|
|
7888
8309
|
|
|
@@ -8097,8 +8518,10 @@ Usage:
|
|
|
8097
8518
|
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8098
8519
|
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
|
|
8099
8520
|
odla-ai doctor [--config odla.config.mjs]
|
|
8100
|
-
odla-ai config diff [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8101
|
-
odla-ai config plan [--
|
|
8521
|
+
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8522
|
+
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
8523
|
+
odla-ai operations get <operation-id> [--json]
|
|
8524
|
+
odla-ai operations wait <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
|
|
8102
8525
|
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
8103
8526
|
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
8104
8527
|
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
@@ -8149,6 +8572,7 @@ Usage:
|
|
|
8149
8572
|
odla-ai context remove <name> --yes [--json]
|
|
8150
8573
|
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
8151
8574
|
odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
8575
|
+
odla-ai platform chat-credentials rotate [--context <name>] [--email <odla-account>] [--wrangler-config <path>] [--expected-version <id>] [--json] --yes
|
|
8152
8576
|
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
8153
8577
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
8154
8578
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
@@ -8212,9 +8636,9 @@ Commands:
|
|
|
8212
8636
|
does and what it guarantees is JSDoc, rendered per package at
|
|
8213
8637
|
https://odla.ai/docs and shipped in the installed .d.ts. Answering
|
|
8214
8638
|
a question usually needs both.
|
|
8215
|
-
whoami Report
|
|
8216
|
-
|
|
8217
|
-
|
|
8639
|
+
whoami Report the actual principal name, handle and kind, its manager,
|
|
8640
|
+
credential kind, accountable owner, and platform-admin status.
|
|
8641
|
+
A manager relationship never supplies authority by itself.
|
|
8218
8642
|
context Explain selected config, platform, app, environment, and
|
|
8219
8643
|
developer-token provenance without printing credentials or
|
|
8220
8644
|
starting a device handshake. Operator work can run outside a
|
|
@@ -8223,8 +8647,8 @@ Commands:
|
|
|
8223
8647
|
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
8224
8648
|
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
8225
8649
|
doctor Validate and summarize the project config without network calls.
|
|
8226
|
-
config
|
|
8227
|
-
|
|
8650
|
+
config Diff Registry intent, freeze a CAS-bound plan, and conditionally apply its safe actions.
|
|
8651
|
+
operations Inspect or wait on one exact, durable config-operation receipt.
|
|
8228
8652
|
calendar Inspect, connect, or disconnect the live Google booking connection.
|
|
8229
8653
|
app Archive (suspend, data retained), restore, export, import, or
|
|
8230
8654
|
manage the co-owners of the app. Archiving takes every
|
|
@@ -8334,6 +8758,58 @@ Safety:
|
|
|
8334
8758
|
`);
|
|
8335
8759
|
}
|
|
8336
8760
|
|
|
8761
|
+
// src/discuss-principals.ts
|
|
8762
|
+
function mergeDiscussPrincipals(target, source) {
|
|
8763
|
+
Object.assign(target.authors, source.authors ?? {});
|
|
8764
|
+
Object.assign(target.principals, source.principals ?? {});
|
|
8765
|
+
}
|
|
8766
|
+
var withAt = (handle) => handle.startsWith("@") ? handle : `@${handle}`;
|
|
8767
|
+
function discussPrincipalLabel(principalId, authorKind, projection) {
|
|
8768
|
+
const profile = projection.principals?.[principalId];
|
|
8769
|
+
if (profile) {
|
|
8770
|
+
const identity = `${profile.displayName} (${withAt(profile.handle)})`;
|
|
8771
|
+
if (profile.kind === "agent") {
|
|
8772
|
+
return profile.managerDisplayName ? `${identity} \u2014 agent managed by ${profile.managerDisplayName}` : `${identity} \u2014 agent`;
|
|
8773
|
+
}
|
|
8774
|
+
return profile.kind === "service" ? `${identity} \u2014 service` : identity;
|
|
8775
|
+
}
|
|
8776
|
+
const legacyName = projection.authors?.[principalId]?.trim();
|
|
8777
|
+
if (legacyName) {
|
|
8778
|
+
return authorKind === "bot" ? `${legacyName} \u2014 agent` : legacyName;
|
|
8779
|
+
}
|
|
8780
|
+
return authorKind === "bot" ? "Unnamed agent" : "Unnamed member";
|
|
8781
|
+
}
|
|
8782
|
+
|
|
8783
|
+
// src/discuss-read-render.ts
|
|
8784
|
+
function discussBodyWithRefs(post) {
|
|
8785
|
+
if (!post.refs || post.refs.length === 0) return post.body;
|
|
8786
|
+
let out = "";
|
|
8787
|
+
let cursor = 0;
|
|
8788
|
+
for (const ref of [...post.refs].sort((a, b) => a.start - b.start)) {
|
|
8789
|
+
if (ref.start < cursor || ref.end > post.body.length) continue;
|
|
8790
|
+
out += post.body.slice(cursor, ref.start) + `@[${ref.label}](${ref.kind}/${ref.id})`;
|
|
8791
|
+
cursor = ref.end;
|
|
8792
|
+
}
|
|
8793
|
+
return out + post.body.slice(cursor);
|
|
8794
|
+
}
|
|
8795
|
+
function renderDiscussRead(ctx, topic, posts, projection) {
|
|
8796
|
+
const status = topic.resolved ? "resolved" : "open";
|
|
8797
|
+
ctx.out.log(`${topic.subject} [${status}] ${topic.appId ?? ""}`);
|
|
8798
|
+
for (const post of posts) {
|
|
8799
|
+
const who = discussPrincipalLabel(
|
|
8800
|
+
post.authorId,
|
|
8801
|
+
post.authorKind,
|
|
8802
|
+
projection
|
|
8803
|
+
);
|
|
8804
|
+
ctx.out.log(`
|
|
8805
|
+
\u2014 ${who}`);
|
|
8806
|
+
ctx.out.log(discussBodyWithRefs(post));
|
|
8807
|
+
for (const file of post.attachments ?? []) {
|
|
8808
|
+
ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
|
|
8809
|
+
}
|
|
8810
|
+
}
|
|
8811
|
+
}
|
|
8812
|
+
|
|
8337
8813
|
// src/discuss-actions.ts
|
|
8338
8814
|
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
8339
8815
|
async function request(ctx, method, path, body) {
|
|
@@ -8352,17 +8828,6 @@ function emit(ctx, value2, human) {
|
|
|
8352
8828
|
else human();
|
|
8353
8829
|
}
|
|
8354
8830
|
var state = (topic) => topic.resolved ? "resolved" : "open";
|
|
8355
|
-
function bodyWithRefs(post) {
|
|
8356
|
-
if (!post.refs || post.refs.length === 0) return post.body;
|
|
8357
|
-
let out = "";
|
|
8358
|
-
let cursor = 0;
|
|
8359
|
-
for (const ref of [...post.refs].sort((a, b) => a.start - b.start)) {
|
|
8360
|
-
if (ref.start < cursor || ref.end > post.body.length) continue;
|
|
8361
|
-
out += post.body.slice(cursor, ref.start) + `@[${ref.label}](${ref.kind}/${ref.id})`;
|
|
8362
|
-
cursor = ref.end;
|
|
8363
|
-
}
|
|
8364
|
-
return out + post.body.slice(cursor);
|
|
8365
|
-
}
|
|
8366
8831
|
function content(parsed) {
|
|
8367
8832
|
const markup = stringOpt(parsed.options.markup);
|
|
8368
8833
|
if (markup) return { markup };
|
|
@@ -8417,11 +8882,19 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8417
8882
|
offset: requestedOffset ?? "0"
|
|
8418
8883
|
});
|
|
8419
8884
|
const page = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
|
|
8420
|
-
emit(
|
|
8885
|
+
emit(
|
|
8886
|
+
ctx,
|
|
8887
|
+
page,
|
|
8888
|
+
() => renderDiscussRead(ctx, page.topic, page.posts, page)
|
|
8889
|
+
);
|
|
8421
8890
|
return;
|
|
8422
8891
|
}
|
|
8423
8892
|
for (let scan = 0; scan < 3; scan++) {
|
|
8424
8893
|
const posts = /* @__PURE__ */ new Map();
|
|
8894
|
+
const projection = {
|
|
8895
|
+
authors: {},
|
|
8896
|
+
principals: {}
|
|
8897
|
+
};
|
|
8425
8898
|
let topic = null;
|
|
8426
8899
|
let offset = 0;
|
|
8427
8900
|
for (; ; ) {
|
|
@@ -8432,6 +8905,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8432
8905
|
);
|
|
8433
8906
|
topic = page.topic;
|
|
8434
8907
|
for (const post of page.posts) posts.set(post.id, post);
|
|
8908
|
+
mergeDiscussPrincipals(projection, page);
|
|
8435
8909
|
if (posts.size > 1e4) throw new Error("discuss read failed: conversation exceeds 10000 posts");
|
|
8436
8910
|
if (!page.page?.hasMore) break;
|
|
8437
8911
|
if (page.page.nextOffset === null || page.page.nextOffset <= offset) {
|
|
@@ -8444,24 +8918,18 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8444
8918
|
);
|
|
8445
8919
|
const expected = Number.isInteger(topic.replyCount) ? topic.replyCount + 1 : ordered.length;
|
|
8446
8920
|
if (ordered.length === expected) {
|
|
8447
|
-
const result = { topic, posts: ordered };
|
|
8448
|
-
emit(
|
|
8921
|
+
const result = { topic, posts: ordered, ...projection };
|
|
8922
|
+
emit(
|
|
8923
|
+
ctx,
|
|
8924
|
+
result,
|
|
8925
|
+
() => renderDiscussRead(ctx, result.topic, result.posts, result)
|
|
8926
|
+
);
|
|
8449
8927
|
return;
|
|
8450
8928
|
}
|
|
8451
8929
|
if (offset === 0) throw new Error("discuss read failed: registry did not provide forward post pages");
|
|
8452
8930
|
}
|
|
8453
8931
|
throw new Error("discuss read failed: conversation changed during every complete-read attempt");
|
|
8454
8932
|
}
|
|
8455
|
-
function renderRead(ctx, topic, posts) {
|
|
8456
|
-
ctx.out.log(`${topic.subject} [${state(topic)}] ${topic.appId ?? ""}`);
|
|
8457
|
-
for (const post of posts) {
|
|
8458
|
-
const who = post.authorKind === "bot" ? `${post.authorId} (agent)` : post.authorId;
|
|
8459
|
-
ctx.out.log(`
|
|
8460
|
-
\u2014 ${who}`);
|
|
8461
|
-
ctx.out.log(bodyWithRefs(post));
|
|
8462
|
-
for (const file of post.attachments ?? []) ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
|
|
8463
|
-
}
|
|
8464
|
-
}
|
|
8465
8933
|
async function discussPost(ctx, parsed) {
|
|
8466
8934
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8467
8935
|
if (!appId) throw new Error("discuss post needs --app <appId>");
|
|
@@ -8686,6 +9154,8 @@ async function discussWatch(ctx, topicId, parsed) {
|
|
|
8686
9154
|
found: true,
|
|
8687
9155
|
cursor,
|
|
8688
9156
|
events: matching,
|
|
9157
|
+
...page.authors ? { authors: page.authors } : {},
|
|
9158
|
+
...page.principals ? { principals: page.principals } : {},
|
|
8689
9159
|
...posts && posts.length > 0 ? { posts } : {},
|
|
8690
9160
|
...topics && topics.length > 0 ? { topics } : {}
|
|
8691
9161
|
});
|
|
@@ -8718,7 +9188,11 @@ function report2(ctx, parsed, result) {
|
|
|
8718
9188
|
ctx.out.log(JSON.stringify(result, null, 2));
|
|
8719
9189
|
} else if (parsed.options.jsonl !== true && result.found) {
|
|
8720
9190
|
for (const post of result.posts ?? []) {
|
|
8721
|
-
const who =
|
|
9191
|
+
const who = discussPrincipalLabel(
|
|
9192
|
+
post.authorId,
|
|
9193
|
+
post.authorKind,
|
|
9194
|
+
result
|
|
9195
|
+
);
|
|
8722
9196
|
ctx.out.log(`\u2014 ${who}
|
|
8723
9197
|
${post.body}`);
|
|
8724
9198
|
}
|
|
@@ -8863,8 +9337,8 @@ function collectFields(parsed, allowClear) {
|
|
|
8863
9337
|
if (allowClear) out[spec.key] = null;
|
|
8864
9338
|
continue;
|
|
8865
9339
|
}
|
|
8866
|
-
const
|
|
8867
|
-
out[spec.key] = spec.num ? Number(
|
|
9340
|
+
const text2 = stringOpt(value2);
|
|
9341
|
+
out[spec.key] = spec.num ? Number(text2) : text2;
|
|
8868
9342
|
}
|
|
8869
9343
|
return out;
|
|
8870
9344
|
}
|
|
@@ -8931,8 +9405,8 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
8931
9405
|
emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
8932
9406
|
}
|
|
8933
9407
|
async function pmGet(ctx, entity, id) {
|
|
8934
|
-
const { record:
|
|
8935
|
-
emit2(ctx,
|
|
9408
|
+
const { record: record11 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
|
|
9409
|
+
emit2(ctx, record11, () => printRecord(ctx, entity, record11));
|
|
8936
9410
|
}
|
|
8937
9411
|
async function pmSet(ctx, entity, id, parsed) {
|
|
8938
9412
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
@@ -8977,9 +9451,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
8977
9451
|
]);
|
|
8978
9452
|
const handoff = {
|
|
8979
9453
|
appId,
|
|
8980
|
-
unmetGoals: goals.filter((
|
|
8981
|
-
activeTasks: tasks.filter((
|
|
8982
|
-
openBugs: bugs.filter((
|
|
9454
|
+
unmetGoals: goals.filter((record11) => record11.status !== "met"),
|
|
9455
|
+
activeTasks: tasks.filter((record11) => record11.column !== "done"),
|
|
9456
|
+
openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
|
|
8983
9457
|
};
|
|
8984
9458
|
const result = {
|
|
8985
9459
|
...handoff,
|
|
@@ -8998,10 +9472,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
8998
9472
|
]) {
|
|
8999
9473
|
ctx.out.log(`${label}:`);
|
|
9000
9474
|
if (!records.length) ctx.out.log("- (none)");
|
|
9001
|
-
else for (const
|
|
9475
|
+
else for (const record11 of records) printRecord(
|
|
9002
9476
|
ctx,
|
|
9003
9477
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
9004
|
-
|
|
9478
|
+
record11
|
|
9005
9479
|
);
|
|
9006
9480
|
}
|
|
9007
9481
|
});
|
|
@@ -9192,19 +9666,180 @@ function age(input) {
|
|
|
9192
9666
|
return `${Math.round(input / 6e4)}m`;
|
|
9193
9667
|
}
|
|
9194
9668
|
|
|
9669
|
+
// src/platform-chat-credential-command.ts
|
|
9670
|
+
var import_node_process10 = __toESM(require("process"), 1);
|
|
9671
|
+
async function rotatePlatformChatCredential(parsed, deps) {
|
|
9672
|
+
assertArgs(
|
|
9673
|
+
parsed,
|
|
9674
|
+
[
|
|
9675
|
+
"config",
|
|
9676
|
+
"context",
|
|
9677
|
+
"platform",
|
|
9678
|
+
"token",
|
|
9679
|
+
"email",
|
|
9680
|
+
"open",
|
|
9681
|
+
"json",
|
|
9682
|
+
"yes",
|
|
9683
|
+
"wrangler-config",
|
|
9684
|
+
"expected-version"
|
|
9685
|
+
],
|
|
9686
|
+
3
|
|
9687
|
+
);
|
|
9688
|
+
if (parsed.options.yes !== true) {
|
|
9689
|
+
throw new Error(
|
|
9690
|
+
"platform chat-credentials rotate changes the production chat secret; pass --yes"
|
|
9691
|
+
);
|
|
9692
|
+
}
|
|
9693
|
+
const context = await resolveOperatorContext(parsed, {
|
|
9694
|
+
allowMissingConfig: true
|
|
9695
|
+
});
|
|
9696
|
+
const platform = context.platform.value;
|
|
9697
|
+
const doFetch = deps.fetch ?? fetch;
|
|
9698
|
+
const out = deps.stdout ?? console;
|
|
9699
|
+
const run = deps.runner ?? defaultRunner;
|
|
9700
|
+
const cwd = import_node_process10.default.cwd();
|
|
9701
|
+
const wranglerConfig = stringOpt(parsed.options["wrangler-config"]) ?? "packages/chat-agent/wrangler.jsonc";
|
|
9702
|
+
if (!await wranglerLoggedIn(run, cwd)) {
|
|
9703
|
+
throw new Error(
|
|
9704
|
+
'Wrangler is not authenticated; run "npx wrangler login" and retry'
|
|
9705
|
+
);
|
|
9706
|
+
}
|
|
9707
|
+
const priorHealth = await doFetch(`${platform}/health/services/chat`);
|
|
9708
|
+
const priorVersion = priorHealth.headers.get("x-odla-worker-version-id");
|
|
9709
|
+
await priorHealth.body?.cancel().catch(() => {
|
|
9710
|
+
});
|
|
9711
|
+
if (!priorVersion) {
|
|
9712
|
+
throw new Error(
|
|
9713
|
+
"chat health did not identify its current Worker version; refusing to rotate"
|
|
9714
|
+
);
|
|
9715
|
+
}
|
|
9716
|
+
const expectedVersion = stringOpt(parsed.options["expected-version"]);
|
|
9717
|
+
if (expectedVersion && priorVersion !== expectedVersion) {
|
|
9718
|
+
throw new Error(
|
|
9719
|
+
`chat health answered from version ${priorVersion}; expected ${expectedVersion}`
|
|
9720
|
+
);
|
|
9721
|
+
}
|
|
9722
|
+
const token = await resolveAdminPlatformToken({
|
|
9723
|
+
platform,
|
|
9724
|
+
scope: "platform:chat:credential:write",
|
|
9725
|
+
token: stringOpt(parsed.options.token),
|
|
9726
|
+
tokenFile: context.credentials.scopedTokenFile,
|
|
9727
|
+
email: stringOpt(parsed.options.email),
|
|
9728
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
9729
|
+
fetch: doFetch,
|
|
9730
|
+
stdout: out,
|
|
9731
|
+
openApprovalUrl: deps.openUrl,
|
|
9732
|
+
label: "odla CLI (rotate built-in Discussion responder)"
|
|
9733
|
+
});
|
|
9734
|
+
const mintedResponse = await doFetch(
|
|
9735
|
+
`${platform}/registry/platform/chat-credentials/rotate`,
|
|
9736
|
+
{
|
|
9737
|
+
method: "POST",
|
|
9738
|
+
headers: { authorization: `Bearer ${token}` }
|
|
9739
|
+
}
|
|
9740
|
+
);
|
|
9741
|
+
const minted = await mintedResponse.json().catch(() => null);
|
|
9742
|
+
if (!mintedResponse.ok) {
|
|
9743
|
+
throw new Error(
|
|
9744
|
+
`mint Discussion credential failed (HTTP ${mintedResponse.status}): ${apiMessage(minted)}`
|
|
9745
|
+
);
|
|
9746
|
+
}
|
|
9747
|
+
if (!isPlatformChatCredential(minted)) {
|
|
9748
|
+
throw new Error(
|
|
9749
|
+
"platform returned an invalid odla.platform-chat-credential/v1 envelope"
|
|
9750
|
+
);
|
|
9751
|
+
}
|
|
9752
|
+
const put = await wranglerPutSecret(run, {
|
|
9753
|
+
name: "ODLA_BOT_TOKENS",
|
|
9754
|
+
value: JSON.stringify({
|
|
9755
|
+
[minted.appId]: { [minted.principalId]: minted.key }
|
|
9756
|
+
}),
|
|
9757
|
+
configPath: wranglerConfig,
|
|
9758
|
+
cwd
|
|
9759
|
+
});
|
|
9760
|
+
if (put.code !== 0) {
|
|
9761
|
+
throw new Error(
|
|
9762
|
+
"Wrangler could not replace ODLA_BOT_TOKENS; the new scoped key was not activated"
|
|
9763
|
+
);
|
|
9764
|
+
}
|
|
9765
|
+
const version = await waitForRotatedChatHealth({
|
|
9766
|
+
fetch: doFetch,
|
|
9767
|
+
platform,
|
|
9768
|
+
priorVersion,
|
|
9769
|
+
wait: deps.pollWait
|
|
9770
|
+
});
|
|
9771
|
+
const result = {
|
|
9772
|
+
schemaVersion: "odla.platform-chat-credential-rotation/v1",
|
|
9773
|
+
appId: minted.appId,
|
|
9774
|
+
appIncarnation: minted.appIncarnation,
|
|
9775
|
+
principalId: minted.principalId,
|
|
9776
|
+
health: { ok: true, service: "odla-chat-agent", version }
|
|
9777
|
+
};
|
|
9778
|
+
if (parsed.options.json === true) {
|
|
9779
|
+
out.log(JSON.stringify(result, null, 2));
|
|
9780
|
+
} else {
|
|
9781
|
+
out.log(
|
|
9782
|
+
`rotated ${result.appId} ${result.principalId} ${result.health.version}`
|
|
9783
|
+
);
|
|
9784
|
+
}
|
|
9785
|
+
}
|
|
9786
|
+
async function waitForRotatedChatHealth(opts) {
|
|
9787
|
+
const wait2 = opts.wait ?? ((milliseconds) => new Promise((resolve12) => setTimeout(resolve12, milliseconds)));
|
|
9788
|
+
let lastStatus = 0;
|
|
9789
|
+
let lastVersion = null;
|
|
9790
|
+
let lastError = "private_service_unready";
|
|
9791
|
+
for (let attempt = 0; attempt < 60; attempt++) {
|
|
9792
|
+
const response2 = await opts.fetch(
|
|
9793
|
+
`${opts.platform}/health/services/chat`
|
|
9794
|
+
);
|
|
9795
|
+
const body = await response2.json().catch(() => null);
|
|
9796
|
+
const version = response2.headers.get("x-odla-worker-version-id");
|
|
9797
|
+
if (response2.ok && record7(body) && body.ok === true && body.service === "odla-chat-agent" && version && version !== opts.priorVersion) {
|
|
9798
|
+
return version;
|
|
9799
|
+
}
|
|
9800
|
+
lastStatus = response2.status;
|
|
9801
|
+
lastVersion = version;
|
|
9802
|
+
lastError = record7(body) && typeof body.error === "string" ? body.error : "private_service_unready";
|
|
9803
|
+
if (attempt < 59) await wait2(5e3);
|
|
9804
|
+
}
|
|
9805
|
+
throw new Error(
|
|
9806
|
+
`chat health did not converge on the rotated Worker deployment (HTTP ${lastStatus}, version ${lastVersion ?? "unknown"}, ${lastError})`
|
|
9807
|
+
);
|
|
9808
|
+
}
|
|
9809
|
+
function isPlatformChatCredential(value2) {
|
|
9810
|
+
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_");
|
|
9811
|
+
}
|
|
9812
|
+
function apiMessage(value2) {
|
|
9813
|
+
if (!record7(value2)) return "request failed";
|
|
9814
|
+
const error = record7(value2.error) ? value2.error : value2;
|
|
9815
|
+
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
9816
|
+
}
|
|
9817
|
+
function record7(value2) {
|
|
9818
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
9819
|
+
}
|
|
9820
|
+
|
|
9195
9821
|
// src/platform-command.ts
|
|
9196
9822
|
async function platformCommand(parsed, deps = {}) {
|
|
9823
|
+
const action2 = parsed.positionals[1];
|
|
9824
|
+
if (action2 === "status") {
|
|
9825
|
+
return platformStatus(parsed, deps);
|
|
9826
|
+
}
|
|
9827
|
+
if (action2 === "chat-credentials" && parsed.positionals[2] === "rotate") {
|
|
9828
|
+
return rotatePlatformChatCredential(parsed, deps);
|
|
9829
|
+
}
|
|
9830
|
+
throw new Error(
|
|
9831
|
+
`unknown platform action "${[
|
|
9832
|
+
action2,
|
|
9833
|
+
parsed.positionals[2]
|
|
9834
|
+
].filter(Boolean).join(" ")}". Try "odla-ai platform status --json".`
|
|
9835
|
+
);
|
|
9836
|
+
}
|
|
9837
|
+
async function platformStatus(parsed, deps) {
|
|
9197
9838
|
assertArgs(
|
|
9198
9839
|
parsed,
|
|
9199
9840
|
["config", "context", "platform", "token", "email", "open", "json"],
|
|
9200
9841
|
2
|
|
9201
9842
|
);
|
|
9202
|
-
const action2 = parsed.positionals[1];
|
|
9203
|
-
if (action2 !== "status") {
|
|
9204
|
-
throw new Error(
|
|
9205
|
-
`unknown platform action "${action2 ?? ""}". Try "odla-ai platform status --json".`
|
|
9206
|
-
);
|
|
9207
|
-
}
|
|
9208
9843
|
const context = await resolveOperatorContext(parsed, {
|
|
9209
9844
|
allowMissingConfig: true
|
|
9210
9845
|
});
|
|
@@ -9229,7 +9864,7 @@ async function platformCommand(parsed, deps = {}) {
|
|
|
9229
9864
|
const body = await response2.json().catch(() => null);
|
|
9230
9865
|
if (!response2.ok) {
|
|
9231
9866
|
throw new Error(
|
|
9232
|
-
`read platform status failed (HTTP ${response2.status}): ${
|
|
9867
|
+
`read platform status failed (HTTP ${response2.status}): ${apiMessage2(body)}`
|
|
9233
9868
|
);
|
|
9234
9869
|
}
|
|
9235
9870
|
if (!isPlatformStatus(body)) {
|
|
@@ -9242,17 +9877,17 @@ async function platformCommand(parsed, deps = {}) {
|
|
|
9242
9877
|
}
|
|
9243
9878
|
}
|
|
9244
9879
|
function isPlatformStatus(value2) {
|
|
9245
|
-
if (!
|
|
9246
|
-
if (!
|
|
9247
|
-
if (!
|
|
9880
|
+
if (!record8(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
|
|
9881
|
+
if (!record8(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
|
|
9882
|
+
if (!record8(value2.catalog) || !record8(value2.summary)) return false;
|
|
9248
9883
|
return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
|
|
9249
9884
|
}
|
|
9250
|
-
function
|
|
9251
|
-
if (!
|
|
9252
|
-
const error =
|
|
9885
|
+
function apiMessage2(value2) {
|
|
9886
|
+
if (!record8(value2)) return "request failed";
|
|
9887
|
+
const error = record8(value2.error) ? value2.error : value2;
|
|
9253
9888
|
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
9254
9889
|
}
|
|
9255
|
-
function
|
|
9890
|
+
function record8(value2) {
|
|
9256
9891
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
9257
9892
|
}
|
|
9258
9893
|
|
|
@@ -9293,7 +9928,7 @@ function statusVerdict(reads) {
|
|
|
9293
9928
|
severity: "degraded"
|
|
9294
9929
|
});
|
|
9295
9930
|
}
|
|
9296
|
-
const performance =
|
|
9931
|
+
const performance = record9(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
9297
9932
|
if (performance?.status === "unavailable") {
|
|
9298
9933
|
reasons.push({
|
|
9299
9934
|
source: "liveSync",
|
|
@@ -9374,7 +10009,7 @@ function statusVerdict(reads) {
|
|
|
9374
10009
|
reasons
|
|
9375
10010
|
};
|
|
9376
10011
|
}
|
|
9377
|
-
function
|
|
10012
|
+
function record9(value2) {
|
|
9378
10013
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
9379
10014
|
}
|
|
9380
10015
|
function numeric2(value2) {
|
|
@@ -9402,7 +10037,7 @@ function printO11yStatus(status, out) {
|
|
|
9402
10037
|
out.log(
|
|
9403
10038
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
9404
10039
|
);
|
|
9405
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
10040
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record10) : [];
|
|
9406
10041
|
const requests = routes.reduce(
|
|
9407
10042
|
(total, row) => total + numeric3(row.requests),
|
|
9408
10043
|
0
|
|
@@ -9414,39 +10049,39 @@ function printO11yStatus(status, out) {
|
|
|
9414
10049
|
out.log(
|
|
9415
10050
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
9416
10051
|
);
|
|
9417
|
-
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(
|
|
10052
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record10) : [];
|
|
9418
10053
|
out.log(
|
|
9419
10054
|
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
9420
10055
|
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
9421
10056
|
).join(", ") : "none observed"}`
|
|
9422
10057
|
);
|
|
9423
10058
|
out.log(liveSyncLine(status.liveSync));
|
|
9424
|
-
const canaryDurations =
|
|
10059
|
+
const canaryDurations = record10(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
9425
10060
|
out.log(
|
|
9426
10061
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
9427
10062
|
);
|
|
9428
|
-
const collectorIngest =
|
|
9429
|
-
const collectorStorage =
|
|
10063
|
+
const collectorIngest = record10(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
10064
|
+
const collectorStorage = record10(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
9430
10065
|
out.log(
|
|
9431
10066
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
9432
10067
|
);
|
|
9433
|
-
const providerMetrics =
|
|
9434
|
-
const providerCapacity =
|
|
9435
|
-
const workerMemory =
|
|
10068
|
+
const providerMetrics = record10(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
10069
|
+
const providerCapacity = record10(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
10070
|
+
const workerMemory = record10(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
9436
10071
|
out.log(
|
|
9437
10072
|
`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`
|
|
9438
10073
|
);
|
|
9439
10074
|
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
9440
10075
|
out.log(line);
|
|
9441
10076
|
}
|
|
9442
|
-
const coverage =
|
|
9443
|
-
const coverageCounts =
|
|
9444
|
-
const coverageBudget =
|
|
10077
|
+
const coverage = record10(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
10078
|
+
const coverageCounts = record10(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
10079
|
+
const coverageBudget = record10(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
9445
10080
|
out.log(
|
|
9446
10081
|
`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`
|
|
9447
10082
|
);
|
|
9448
10083
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
9449
|
-
const providerFreshness =
|
|
10084
|
+
const providerFreshness = record10(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
9450
10085
|
out.log(
|
|
9451
10086
|
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
9452
10087
|
);
|
|
@@ -9455,17 +10090,17 @@ function printO11yStatus(status, out) {
|
|
|
9455
10090
|
);
|
|
9456
10091
|
}
|
|
9457
10092
|
function providerCapacityLines(read3) {
|
|
9458
|
-
const resources =
|
|
9459
|
-
const durableObjects =
|
|
9460
|
-
const periodic =
|
|
9461
|
-
const storage =
|
|
9462
|
-
const d1 =
|
|
9463
|
-
const d1Activity =
|
|
9464
|
-
const d1Storage =
|
|
9465
|
-
const d1Latency =
|
|
9466
|
-
const r2 =
|
|
9467
|
-
const r2Operations =
|
|
9468
|
-
const r2Storage =
|
|
10093
|
+
const resources = record10(read3.body.resources) ? read3.body.resources : {};
|
|
10094
|
+
const durableObjects = record10(resources.durableObjects) ? resources.durableObjects : {};
|
|
10095
|
+
const periodic = record10(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
10096
|
+
const storage = record10(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
10097
|
+
const d1 = record10(resources.d1) ? resources.d1 : {};
|
|
10098
|
+
const d1Activity = record10(d1.activity) ? d1.activity : {};
|
|
10099
|
+
const d1Storage = record10(d1.storage) ? d1.storage : {};
|
|
10100
|
+
const d1Latency = record10(d1Activity.latency) ? d1Activity.latency : {};
|
|
10101
|
+
const r2 = record10(resources.r2) ? resources.r2 : {};
|
|
10102
|
+
const r2Operations = record10(r2.operations) ? r2.operations : {};
|
|
10103
|
+
const r2Storage = record10(r2.storage) ? r2.storage : {};
|
|
9469
10104
|
const status = String(
|
|
9470
10105
|
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
9471
10106
|
);
|
|
@@ -9476,11 +10111,11 @@ function providerCapacityLines(read3) {
|
|
|
9476
10111
|
];
|
|
9477
10112
|
}
|
|
9478
10113
|
function liveSyncLine(read3) {
|
|
9479
|
-
const performance =
|
|
9480
|
-
const commitToSend =
|
|
10114
|
+
const performance = record10(read3.body.performance) ? read3.body.performance : {};
|
|
10115
|
+
const commitToSend = record10(performance.commitToSend) ? performance.commitToSend : {};
|
|
9481
10116
|
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`;
|
|
9482
10117
|
}
|
|
9483
|
-
function
|
|
10118
|
+
function record10(value2) {
|
|
9484
10119
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
9485
10120
|
}
|
|
9486
10121
|
function numeric3(value2) {
|
|
@@ -9649,23 +10284,23 @@ function statusMinutes(value2) {
|
|
|
9649
10284
|
}
|
|
9650
10285
|
async function read2(url, headers, doFetch) {
|
|
9651
10286
|
const response2 = await doFetch(url, { headers });
|
|
9652
|
-
const
|
|
10287
|
+
const text2 = await response2.text();
|
|
9653
10288
|
let body = {};
|
|
9654
|
-
if (
|
|
10289
|
+
if (text2) {
|
|
9655
10290
|
try {
|
|
9656
|
-
const value2 = JSON.parse(
|
|
10291
|
+
const value2 = JSON.parse(text2);
|
|
9657
10292
|
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
9658
10293
|
} catch {
|
|
9659
|
-
body = { message:
|
|
10294
|
+
body = { message: text2.slice(0, 300) };
|
|
9660
10295
|
}
|
|
9661
10296
|
}
|
|
9662
10297
|
return { httpStatus: response2.status, body };
|
|
9663
10298
|
}
|
|
9664
10299
|
|
|
9665
10300
|
// src/provision.ts
|
|
9666
|
-
var
|
|
10301
|
+
var import_apps12 = require("@odla-ai/apps");
|
|
9667
10302
|
var import_ai3 = require("@odla-ai/ai");
|
|
9668
|
-
var
|
|
10303
|
+
var import_node_process11 = __toESM(require("process"), 1);
|
|
9669
10304
|
|
|
9670
10305
|
// src/integration-provision.ts
|
|
9671
10306
|
var import_db3 = require("@odla-ai/db");
|
|
@@ -9720,9 +10355,9 @@ async function responseText(res) {
|
|
|
9720
10355
|
}
|
|
9721
10356
|
|
|
9722
10357
|
// src/provision-credentials.ts
|
|
9723
|
-
var
|
|
10358
|
+
var import_apps11 = require("@odla-ai/apps");
|
|
9724
10359
|
async function provisionEnvCredentials(opts) {
|
|
9725
|
-
const tenantId = (0,
|
|
10360
|
+
const tenantId = (0, import_apps11.tenantIdFor)(opts.cfg.app.id, opts.env);
|
|
9726
10361
|
const prior = opts.credentials?.envs[opts.env];
|
|
9727
10362
|
let credentials = opts.credentials;
|
|
9728
10363
|
let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
|
|
@@ -9884,7 +10519,7 @@ async function provision(options) {
|
|
|
9884
10519
|
}
|
|
9885
10520
|
const doFetch = options.fetch ?? fetch;
|
|
9886
10521
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
9887
|
-
const apps = (0,
|
|
10522
|
+
const apps = (0, import_apps12.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
9888
10523
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
9889
10524
|
if (existing) {
|
|
9890
10525
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
@@ -9895,7 +10530,7 @@ async function provision(options) {
|
|
|
9895
10530
|
for (const env of cfg.envs) {
|
|
9896
10531
|
await assertTenantAdminAccess(doFetch, cfg, env, token);
|
|
9897
10532
|
}
|
|
9898
|
-
const serviceOrder = (0,
|
|
10533
|
+
const serviceOrder = (0, import_apps12.orderAppServices)(cfg.services);
|
|
9899
10534
|
for (const env of cfg.envs) {
|
|
9900
10535
|
for (const service of serviceOrder) {
|
|
9901
10536
|
if (service === "ai") {
|
|
@@ -9928,7 +10563,7 @@ async function provision(options) {
|
|
|
9928
10563
|
}
|
|
9929
10564
|
}
|
|
9930
10565
|
for (const env of cfg.envs) {
|
|
9931
|
-
const tenantId = (0,
|
|
10566
|
+
const tenantId = (0, import_apps12.tenantIdFor)(cfg.app.id, env);
|
|
9932
10567
|
credentials = await provisionEnvCredentials({
|
|
9933
10568
|
cfg,
|
|
9934
10569
|
env,
|
|
@@ -9972,7 +10607,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
9972
10607
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
9973
10608
|
}
|
|
9974
10609
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
9975
|
-
const key =
|
|
10610
|
+
const key = import_node_process11.default.env[cfg.ai.keyEnv];
|
|
9976
10611
|
if (key) {
|
|
9977
10612
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
9978
10613
|
await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -10013,8 +10648,8 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10013
10648
|
}
|
|
10014
10649
|
|
|
10015
10650
|
// src/record.ts
|
|
10016
|
-
var
|
|
10017
|
-
var
|
|
10651
|
+
var import_node_fs16 = require("fs");
|
|
10652
|
+
var import_node_process12 = __toESM(require("process"), 1);
|
|
10018
10653
|
|
|
10019
10654
|
// src/surface.ts
|
|
10020
10655
|
var PM_ACTIONS = {
|
|
@@ -10062,7 +10697,7 @@ var COMMAND_SURFACE = {
|
|
|
10062
10697
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
10063
10698
|
capabilities: {},
|
|
10064
10699
|
code: { connect: {} },
|
|
10065
|
-
config: { diff: {}, plan: {} },
|
|
10700
|
+
config: { diff: {}, plan: {}, apply: {} },
|
|
10066
10701
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
10067
10702
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
10068
10703
|
discuss: {
|
|
@@ -10080,7 +10715,11 @@ var COMMAND_SURFACE = {
|
|
|
10080
10715
|
help: {},
|
|
10081
10716
|
init: {},
|
|
10082
10717
|
o11y: { status: {} },
|
|
10083
|
-
|
|
10718
|
+
operations: { get: {}, wait: {} },
|
|
10719
|
+
platform: {
|
|
10720
|
+
status: {},
|
|
10721
|
+
"chat-credentials": { rotate: {} }
|
|
10722
|
+
},
|
|
10084
10723
|
pm: {
|
|
10085
10724
|
...PM_ENTITIES,
|
|
10086
10725
|
handoff: {}
|
|
@@ -10159,7 +10798,7 @@ function invocationPath(words2) {
|
|
|
10159
10798
|
|
|
10160
10799
|
// src/record.ts
|
|
10161
10800
|
function recordInvocation(parsed) {
|
|
10162
|
-
const file =
|
|
10801
|
+
const file = import_node_process12.default.env.ODLA_CLI_RECORD;
|
|
10163
10802
|
if (!file) return;
|
|
10164
10803
|
try {
|
|
10165
10804
|
const entry = {
|
|
@@ -10167,14 +10806,14 @@ function recordInvocation(parsed) {
|
|
|
10167
10806
|
options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
|
|
10168
10807
|
};
|
|
10169
10808
|
if (!entry.path.length) return;
|
|
10170
|
-
(0,
|
|
10809
|
+
(0, import_node_fs16.appendFileSync)(file, `${JSON.stringify(entry)}
|
|
10171
10810
|
`);
|
|
10172
10811
|
} catch {
|
|
10173
10812
|
}
|
|
10174
10813
|
}
|
|
10175
10814
|
|
|
10176
10815
|
// src/runbook-actions.ts
|
|
10177
|
-
var
|
|
10816
|
+
var import_node_fs17 = require("fs");
|
|
10178
10817
|
|
|
10179
10818
|
// src/runbook-requires.ts
|
|
10180
10819
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
@@ -10259,7 +10898,7 @@ async function bySlug(ctx, slug) {
|
|
|
10259
10898
|
function readBody(file, inline) {
|
|
10260
10899
|
if (inline !== void 0) return inline;
|
|
10261
10900
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
10262
|
-
return (0,
|
|
10901
|
+
return (0, import_node_fs17.readFileSync)(file === "-" ? 0 : file, "utf8");
|
|
10263
10902
|
}
|
|
10264
10903
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
10265
10904
|
async function runbookList(ctx, all, query) {
|
|
@@ -10289,12 +10928,17 @@ async function runbookNew(ctx, slug, title, body, summary, requires) {
|
|
|
10289
10928
|
});
|
|
10290
10929
|
ctx.out.log(ctx.json ? JSON.stringify(created, null, 2) : `created ${slug} (${created.id})`);
|
|
10291
10930
|
}
|
|
10292
|
-
async function runbookEdit(ctx, slug, body, note, requires) {
|
|
10931
|
+
async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
|
|
10293
10932
|
const runbook = await bySlug(ctx, slug);
|
|
10294
10933
|
const result = await call(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
|
|
10295
10934
|
// An empty --requires clears the declaration; omitting the flag leaves
|
|
10296
10935
|
// whatever is there, so an ordinary body edit never drops it.
|
|
10297
|
-
patch: {
|
|
10936
|
+
patch: {
|
|
10937
|
+
body,
|
|
10938
|
+
expectedVersion: expectedVersion ?? runbook.version,
|
|
10939
|
+
...note ? { note } : {},
|
|
10940
|
+
...requires === void 0 ? {} : { requires: requires || null }
|
|
10941
|
+
}
|
|
10298
10942
|
});
|
|
10299
10943
|
if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
|
|
10300
10944
|
ctx.out.log(`${slug} \u2192 v${result.record?.version ?? runbook.version + 1}`);
|
|
@@ -10346,14 +10990,14 @@ async function runbookRemove(ctx, slug) {
|
|
|
10346
10990
|
}
|
|
10347
10991
|
|
|
10348
10992
|
// src/runbook-import.ts
|
|
10349
|
-
var
|
|
10350
|
-
var
|
|
10351
|
-
function parseRunbook(
|
|
10352
|
-
let rest =
|
|
10993
|
+
var import_node_fs18 = require("fs");
|
|
10994
|
+
var import_node_path15 = require("path");
|
|
10995
|
+
function parseRunbook(text2, slug) {
|
|
10996
|
+
let rest = text2;
|
|
10353
10997
|
const meta = {};
|
|
10354
|
-
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(
|
|
10998
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text2);
|
|
10355
10999
|
if (fm) {
|
|
10356
|
-
rest =
|
|
11000
|
+
rest = text2.slice(fm[0].length);
|
|
10357
11001
|
for (const line of fm[1].split(/\r?\n/)) {
|
|
10358
11002
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
|
|
10359
11003
|
if (!pair) continue;
|
|
@@ -10372,12 +11016,12 @@ function parseRunbook(text, slug) {
|
|
|
10372
11016
|
};
|
|
10373
11017
|
}
|
|
10374
11018
|
function readRunbookDir(dir) {
|
|
10375
|
-
if (!(0,
|
|
10376
|
-
const files = (0,
|
|
11019
|
+
if (!(0, import_node_fs18.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
|
|
11020
|
+
const files = (0, import_node_fs18.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
|
|
10377
11021
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
10378
11022
|
return files.map((file) => {
|
|
10379
|
-
const slug = (0,
|
|
10380
|
-
const parsed = parseRunbook((0,
|
|
11023
|
+
const slug = (0, import_node_path15.basename)(file, ".md");
|
|
11024
|
+
const parsed = parseRunbook((0, import_node_fs18.readFileSync)((0, import_node_path15.join)(dir, file), "utf8"), slug);
|
|
10381
11025
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
10382
11026
|
});
|
|
10383
11027
|
}
|
|
@@ -10434,6 +11078,7 @@ async function upsert(ctx, r, visibility) {
|
|
|
10434
11078
|
patch: {
|
|
10435
11079
|
title: r.title,
|
|
10436
11080
|
body: r.body,
|
|
11081
|
+
expectedVersion: found.version,
|
|
10437
11082
|
...r.summary ? { summary: r.summary } : {},
|
|
10438
11083
|
...r.tags ? { tags: r.tags } : {},
|
|
10439
11084
|
note: `imported from ${r.file}`
|
|
@@ -10449,8 +11094,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
10449
11094
|
|
|
10450
11095
|
// src/runbook-impact.ts
|
|
10451
11096
|
var import_node_child_process7 = require("child_process");
|
|
10452
|
-
var
|
|
10453
|
-
var
|
|
11097
|
+
var import_node_fs19 = require("fs");
|
|
11098
|
+
var import_node_path16 = require("path");
|
|
10454
11099
|
|
|
10455
11100
|
// src/runbook-impact-scan.ts
|
|
10456
11101
|
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$]*)/;
|
|
@@ -10619,10 +11264,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
10619
11264
|
}
|
|
10620
11265
|
function manifestLabeller(root) {
|
|
10621
11266
|
return (workspace) => {
|
|
10622
|
-
const manifest = (0,
|
|
10623
|
-
if (!(0,
|
|
11267
|
+
const manifest = (0, import_node_path16.join)(root, workspace, "package.json");
|
|
11268
|
+
if (!(0, import_node_fs19.existsSync)(manifest)) return void 0;
|
|
10624
11269
|
try {
|
|
10625
|
-
const name = JSON.parse((0,
|
|
11270
|
+
const name = JSON.parse((0, import_node_fs19.readFileSync)(manifest, "utf8")).name;
|
|
10626
11271
|
return typeof name === "string" ? name : void 0;
|
|
10627
11272
|
} catch {
|
|
10628
11273
|
return void 0;
|
|
@@ -10689,7 +11334,7 @@ function report3(ctx, impacts) {
|
|
|
10689
11334
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
10690
11335
|
const cwd = deps.cwd ?? process.cwd();
|
|
10691
11336
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
10692
|
-
const read3 = deps.readRepoFile ?? ((path) => (0,
|
|
11337
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs19.readFileSync)((0, import_node_path16.join)(cwd, path), "utf8"));
|
|
10693
11338
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
10694
11339
|
if (!surfaces.length) {
|
|
10695
11340
|
return ctx.out.log(
|
|
@@ -10822,12 +11467,12 @@ async function runbookComment(ctx, slug, body) {
|
|
|
10822
11467
|
|
|
10823
11468
|
// src/runbook-editor.ts
|
|
10824
11469
|
var import_node_child_process8 = require("child_process");
|
|
10825
|
-
var
|
|
11470
|
+
var import_node_fs20 = require("fs");
|
|
10826
11471
|
var import_node_os5 = require("os");
|
|
10827
|
-
var
|
|
10828
|
-
var
|
|
11472
|
+
var import_node_path17 = require("path");
|
|
11473
|
+
var import_node_process13 = __toESM(require("process"), 1);
|
|
10829
11474
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
10830
|
-
function resolveEditor(env =
|
|
11475
|
+
function resolveEditor(env = import_node_process13.default.env) {
|
|
10831
11476
|
for (const name of EDITOR_ENV) {
|
|
10832
11477
|
const value2 = env[name];
|
|
10833
11478
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -10841,8 +11486,8 @@ function defaultRun(command, path) {
|
|
|
10841
11486
|
return result.status ?? 0;
|
|
10842
11487
|
}
|
|
10843
11488
|
function editText(initial, slug, deps = {}) {
|
|
10844
|
-
const env = deps.env ??
|
|
10845
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
11489
|
+
const env = deps.env ?? import_node_process13.default.env;
|
|
11490
|
+
const interactive = deps.interactive ?? (() => Boolean(import_node_process13.default.stdin.isTTY));
|
|
10846
11491
|
const editor = resolveEditor(env);
|
|
10847
11492
|
if (!editor)
|
|
10848
11493
|
throw new Error(
|
|
@@ -10850,16 +11495,16 @@ function editText(initial, slug, deps = {}) {
|
|
|
10850
11495
|
);
|
|
10851
11496
|
if (!interactive())
|
|
10852
11497
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
10853
|
-
const dir = (0,
|
|
10854
|
-
const file = (0,
|
|
11498
|
+
const dir = (0, import_node_fs20.mkdtempSync)((0, import_node_path17.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
|
|
11499
|
+
const file = (0, import_node_path17.join)(dir, `${slug}.md`);
|
|
10855
11500
|
try {
|
|
10856
|
-
(0,
|
|
11501
|
+
(0, import_node_fs20.writeFileSync)(file, initial, { mode: 384 });
|
|
10857
11502
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
10858
11503
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
10859
|
-
const edited = (0,
|
|
11504
|
+
const edited = (0, import_node_fs20.readFileSync)(file, "utf8");
|
|
10860
11505
|
return edited === initial ? null : edited;
|
|
10861
11506
|
} finally {
|
|
10862
|
-
(0,
|
|
11507
|
+
(0, import_node_fs20.rmSync)(dir, { recursive: true, force: true });
|
|
10863
11508
|
}
|
|
10864
11509
|
}
|
|
10865
11510
|
var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
@@ -10874,27 +11519,90 @@ async function editRunbook(ctx, slug, deps = {}) {
|
|
|
10874
11519
|
const found = page.records[0];
|
|
10875
11520
|
if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
|
|
10876
11521
|
ctx.out.log(`opening ${slug} v${found.version} in your editor\u2026`);
|
|
10877
|
-
|
|
11522
|
+
const body = await editText(found.body, slug, deps);
|
|
11523
|
+
return body === null ? null : { body, expectedVersion: found.version };
|
|
10878
11524
|
}
|
|
10879
11525
|
|
|
10880
11526
|
// src/whoami-command.ts
|
|
11527
|
+
var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
11528
|
+
function principalKind(value2, machine) {
|
|
11529
|
+
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
11530
|
+
}
|
|
11531
|
+
function credentialKind(value2, machine, scopes) {
|
|
11532
|
+
if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
|
|
11533
|
+
if (machine) return "machine";
|
|
11534
|
+
if (scopes.length) return "device";
|
|
11535
|
+
return "unknown";
|
|
11536
|
+
}
|
|
11537
|
+
function managerOf(value2) {
|
|
11538
|
+
if (!value2 || typeof value2 !== "object") return null;
|
|
11539
|
+
const row = value2;
|
|
11540
|
+
const principalId = text(row.principalId);
|
|
11541
|
+
if (!principalId) return null;
|
|
11542
|
+
return {
|
|
11543
|
+
principalId,
|
|
11544
|
+
displayName: text(row.displayName) ?? "Unnamed member",
|
|
11545
|
+
handle: text(row.handle) ?? ""
|
|
11546
|
+
};
|
|
11547
|
+
}
|
|
11548
|
+
function unnamedPrincipal(kind) {
|
|
11549
|
+
if (kind === "agent") return "Unnamed agent";
|
|
11550
|
+
if (kind === "service") return "Unnamed service";
|
|
11551
|
+
return "Unnamed member";
|
|
11552
|
+
}
|
|
10881
11553
|
async function fetchIdentity(platformUrl, token, doFetch) {
|
|
10882
11554
|
const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
|
|
10883
11555
|
headers: { authorization: `Bearer ${token}` }
|
|
10884
11556
|
});
|
|
10885
11557
|
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
10886
11558
|
const body = await res.json();
|
|
11559
|
+
const developerId = text(body.developerId) ?? "";
|
|
11560
|
+
const machine = body.machine === true;
|
|
11561
|
+
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
11562
|
+
const principalId = text(body.principalId) ?? developerId;
|
|
11563
|
+
const email = text(body.email);
|
|
11564
|
+
const kind = principalKind(body.principalKind, machine);
|
|
11565
|
+
const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
11566
|
+
const handle = text(body.handle) ?? "";
|
|
11567
|
+
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
10887
11568
|
return {
|
|
10888
|
-
developerId
|
|
10889
|
-
|
|
11569
|
+
developerId,
|
|
11570
|
+
principalId,
|
|
11571
|
+
principalKind: kind,
|
|
11572
|
+
displayName,
|
|
11573
|
+
handle,
|
|
11574
|
+
manager: managerOf(body.manager),
|
|
11575
|
+
credential: {
|
|
11576
|
+
id: text(credential2.id),
|
|
11577
|
+
kind: credentialKind(credential2.kind, machine, scopes)
|
|
11578
|
+
},
|
|
11579
|
+
email,
|
|
10890
11580
|
admin: body.admin === true,
|
|
10891
|
-
machine
|
|
10892
|
-
scopes
|
|
11581
|
+
machine,
|
|
11582
|
+
scopes
|
|
10893
11583
|
};
|
|
10894
11584
|
}
|
|
10895
|
-
function
|
|
10896
|
-
if (identity.machine) return "machine (platform admin secret)";
|
|
10897
|
-
|
|
11585
|
+
function credentialLabel(identity) {
|
|
11586
|
+
if (identity.credential.kind === "machine") return "machine (platform admin secret)";
|
|
11587
|
+
if (identity.credential.kind === "device")
|
|
11588
|
+
return identity.scopes.length ? "device (scoped)" : "device";
|
|
11589
|
+
if (identity.credential.kind === "clerk") return "clerk";
|
|
11590
|
+
return "unknown (legacy server)";
|
|
11591
|
+
}
|
|
11592
|
+
function namedPrincipal(identity) {
|
|
11593
|
+
return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
|
|
11594
|
+
}
|
|
11595
|
+
function namedManager(manager) {
|
|
11596
|
+
return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
|
|
11597
|
+
}
|
|
11598
|
+
function accountableOwner(identity) {
|
|
11599
|
+
if (identity.principalKind === "agent" && identity.manager) {
|
|
11600
|
+
return namedManager(identity.manager);
|
|
11601
|
+
}
|
|
11602
|
+
if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
|
|
11603
|
+
return namedPrincipal(identity);
|
|
11604
|
+
}
|
|
11605
|
+
return identity.email ?? "Unnamed member";
|
|
10898
11606
|
}
|
|
10899
11607
|
async function whoamiCommand(parsed, deps = {}) {
|
|
10900
11608
|
assertArgs(
|
|
@@ -10925,9 +11633,19 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
10925
11633
|
return;
|
|
10926
11634
|
}
|
|
10927
11635
|
out.log(`platform: ${cfg.platformUrl}`);
|
|
10928
|
-
out.log(`
|
|
11636
|
+
out.log(`principal: ${namedPrincipal(identity)}`);
|
|
11637
|
+
out.log(`principal id: ${identity.principalId}`);
|
|
11638
|
+
out.log(`kind: ${identity.principalKind}`);
|
|
11639
|
+
if (identity.principalKind === "agent")
|
|
11640
|
+
out.log(
|
|
11641
|
+
`manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
|
|
11642
|
+
);
|
|
11643
|
+
out.log(`owner: ${accountableOwner(identity)}`);
|
|
11644
|
+
out.log(`owner id: ${identity.developerId}`);
|
|
10929
11645
|
out.log(`email: ${identity.email ?? "(none)"}`);
|
|
10930
|
-
out.log(`credential: ${
|
|
11646
|
+
out.log(`credential: ${credentialLabel(identity)}`);
|
|
11647
|
+
if (identity.credential.id)
|
|
11648
|
+
out.log(`credential id: ${identity.credential.id}`);
|
|
10931
11649
|
out.log(`admin: ${identity.admin ? "yes" : "no"}`);
|
|
10932
11650
|
if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
|
|
10933
11651
|
if (!identity.admin) {
|
|
@@ -11076,14 +11794,16 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
11076
11794
|
const name = requireSlug(slug, "edit");
|
|
11077
11795
|
const file = stringOpt(parsed.options.file);
|
|
11078
11796
|
const inline = stringOpt(parsed.options.body);
|
|
11079
|
-
const
|
|
11080
|
-
if (
|
|
11797
|
+
const edited = file === void 0 && inline === void 0 ? await editRunbook(ctx, name) : readBody(file, inline);
|
|
11798
|
+
if (edited === null) return ctx.out.log(`${name} unchanged; nothing written`);
|
|
11799
|
+
const body = typeof edited === "string" ? edited : edited.body;
|
|
11081
11800
|
return runbookEdit(
|
|
11082
11801
|
ctx,
|
|
11083
11802
|
name,
|
|
11084
11803
|
body,
|
|
11085
11804
|
stringOpt(parsed.options.note),
|
|
11086
|
-
parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? ""
|
|
11805
|
+
parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? "",
|
|
11806
|
+
typeof edited === "string" ? void 0 : edited.expectedVersion
|
|
11087
11807
|
);
|
|
11088
11808
|
}
|
|
11089
11809
|
case "import": {
|
|
@@ -11271,7 +11991,7 @@ function hostedSeverity(value2, flag) {
|
|
|
11271
11991
|
var import_security2 = require("@odla-ai/security");
|
|
11272
11992
|
|
|
11273
11993
|
// src/security.ts
|
|
11274
|
-
var
|
|
11994
|
+
var import_node_path18 = require("path");
|
|
11275
11995
|
var import_security = require("@odla-ai/security");
|
|
11276
11996
|
var import_node3 = require("@odla-ai/security/node");
|
|
11277
11997
|
async function runHostedSecurity(options) {
|
|
@@ -11283,9 +12003,9 @@ async function runHostedSecurity(options) {
|
|
|
11283
12003
|
const appId = selfAudit ? "odla-ai" : cfg.app.id;
|
|
11284
12004
|
const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
|
|
11285
12005
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
11286
|
-
const target = (0,
|
|
11287
|
-
const output = (0,
|
|
11288
|
-
const outputRelative = (0,
|
|
12006
|
+
const target = (0, import_node_path18.resolve)(options.target ?? cfg?.rootDir ?? ".");
|
|
12007
|
+
const output = (0, import_node_path18.resolve)(options.out ?? (0, import_node_path18.resolve)(target, ".odla/security/hosted"));
|
|
12008
|
+
const outputRelative = (0, import_node_path18.relative)(target, output).split(import_node_path18.sep).join("/");
|
|
11289
12009
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
11290
12010
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
11291
12011
|
const tokenRequest = {
|
|
@@ -11297,7 +12017,7 @@ async function runHostedSecurity(options) {
|
|
|
11297
12017
|
};
|
|
11298
12018
|
const token = await injectedToken(options, tokenRequest);
|
|
11299
12019
|
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
11300
|
-
exclude: !outputRelative.startsWith("../") && !(0,
|
|
12020
|
+
exclude: !outputRelative.startsWith("../") && !(0, import_node_path18.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
11301
12021
|
});
|
|
11302
12022
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
11303
12023
|
platform,
|
|
@@ -11315,7 +12035,7 @@ async function runHostedSecurity(options) {
|
|
|
11315
12035
|
});
|
|
11316
12036
|
const harness = (0, import_security.createSecurityHarness)({
|
|
11317
12037
|
profile,
|
|
11318
|
-
store: new import_node3.FileRunStore((0,
|
|
12038
|
+
store: new import_node3.FileRunStore((0, import_node_path18.resolve)(output, "state")),
|
|
11319
12039
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
11320
12040
|
validationReasoner: hosted.validationReasoner,
|
|
11321
12041
|
policy: {
|
|
@@ -11339,7 +12059,7 @@ async function runHostedSecurity(options) {
|
|
|
11339
12059
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
11340
12060
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
11341
12061
|
if (!env || !declared.includes(env)) {
|
|
11342
|
-
const shown = (0,
|
|
12062
|
+
const shown = (0, import_node_path18.relative)(rootDir, configPath) || configPath;
|
|
11343
12063
|
throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
|
|
11344
12064
|
}
|
|
11345
12065
|
return env;
|
|
@@ -11368,7 +12088,7 @@ function printSummary(out, appId, env, run, report4, output) {
|
|
|
11368
12088
|
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}`);
|
|
11369
12089
|
if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
|
|
11370
12090
|
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
|
|
11371
|
-
out.log(` report: ${(0,
|
|
12091
|
+
out.log(` report: ${(0, import_node_path18.resolve)(output, "REPORT.md")}`);
|
|
11372
12092
|
}
|
|
11373
12093
|
function formatBudget(usage) {
|
|
11374
12094
|
return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
|
|
@@ -11808,11 +12528,11 @@ async function securityStatus(parsed, dependencies) {
|
|
|
11808
12528
|
// src/cli.ts
|
|
11809
12529
|
function exitCodeFor(err) {
|
|
11810
12530
|
const code = err?.code;
|
|
11811
|
-
if (code === "handshake_pending" || code === "watch_timeout") return 75;
|
|
12531
|
+
if (code === "handshake_pending" || code === "watch_timeout" || code === "operation_pending") return 75;
|
|
11812
12532
|
if (code === "checkpoint_required") return 3;
|
|
11813
12533
|
if (code === "remote_unavailable") return 6;
|
|
11814
12534
|
if (code === "auth_failed") return 5;
|
|
11815
|
-
if (code === "invalid_cursor") return 2;
|
|
12535
|
+
if (code === "invalid_cursor" || code === "invalid_plan" || code === "invalid_operation_id") return 2;
|
|
11816
12536
|
return 1;
|
|
11817
12537
|
}
|
|
11818
12538
|
async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|