@odla-ai/cli 0.33.0 → 0.34.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 +204 -112
- package/REQUIREMENTS.md +6 -0
- package/dist/bin.cjs +1027 -527
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-YSQORU5J.js → chunk-RUEM7ZTA.js} +998 -523
- package/dist/chunk-RUEM7ZTA.js.map +1 -0
- package/dist/{cli-U436OLYW.js → cli-NKNQLWOM.js} +2 -2
- package/dist/index.cjs +1001 -524
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +173 -4
- package/dist/index.d.ts +173 -4
- package/dist/index.js +5 -1
- package/package.json +2 -2
- package/skills/odla/SKILL.md +65 -35
- package/skills/odla/references/agent-identity.md +5 -5
- package/skills/odla/references/build.md +16 -15
- package/skills/odla/references/co-owners.md +1 -1
- package/skills/odla/references/pm-work-intake.md +12 -12
- package/skills/odla/references/pm.md +17 -17
- package/skills/odla/references/sdks.md +2 -2
- package/skills/odla-migrate/SKILL.md +23 -5
- package/skills/odla-migrate/references/phase-2-chapter.md +1 -1
- package/skills/odla-migrate/references/phase-2-db.md +11 -10
- package/skills/odla-migrate/references/phase-3-auth.md +2 -2
- package/skills/odla-migrate/references/phase-3b-user-sync.md +2 -2
- package/skills/odla-migrate/references/phase-4-ai.md +3 -3
- package/skills/odla-migrate/references/phase-5-cutover.md +5 -5
- package/skills/odla-migrate/references/project-state.md +4 -4
- package/skills/odla-migrate/references/secrets-map.md +6 -6
- package/skills/odla-migrate/references/troubleshooting.md +23 -23
- package/skills/odla-o11y-debug/SKILL.md +3 -3
- package/dist/chunk-YSQORU5J.js.map +0 -1
- /package/dist/{cli-U436OLYW.js.map → cli-NKNQLWOM.js.map} +0 -0
|
@@ -40,9 +40,10 @@ function approvalLines(prompt) {
|
|
|
40
40
|
lines.push(` No browser was opened (${prompt.browserSkipped}).`);
|
|
41
41
|
}
|
|
42
42
|
lines.push("");
|
|
43
|
-
lines.push(" AGENTS:
|
|
44
|
-
lines.push("
|
|
45
|
-
lines.push("
|
|
43
|
+
lines.push(" AGENTS: immediately give the human this URL as a clickable approval action and repeat the code.");
|
|
44
|
+
lines.push(" Keep this CLI process running and wait on this same process; the CLI owns protocol polling.");
|
|
45
|
+
lines.push(" Do not use OS open, browser control, curl, a shell wait loop, detached execution, or a substitute handshake.");
|
|
46
|
+
lines.push(" You cannot approve it yourself. If this process exits, a later invocation creates a new code.");
|
|
46
47
|
lines.push("");
|
|
47
48
|
return lines;
|
|
48
49
|
}
|
|
@@ -257,10 +258,10 @@ function isManagedDevVar(line) {
|
|
|
257
258
|
const match = line.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
|
|
258
259
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
259
260
|
}
|
|
260
|
-
function writePrivateText(path,
|
|
261
|
+
function writePrivateText(path, text3) {
|
|
261
262
|
mkdirSync(dirname2(path), { recursive: true });
|
|
262
263
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
263
|
-
writeFileSync(temporary,
|
|
264
|
+
writeFileSync(temporary, text3, { mode: 384 });
|
|
264
265
|
chmodSync(temporary, 384);
|
|
265
266
|
renameSync(temporary, path);
|
|
266
267
|
}
|
|
@@ -375,7 +376,7 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
375
376
|
}
|
|
376
377
|
function cachedGrantCovers(cached, required) {
|
|
377
378
|
if (required.optionalProjectCapabilities.length === 0) return true;
|
|
378
|
-
return required.projectIds.every((
|
|
379
|
+
return required.projectIds.every((id2) => cached.projectIds?.includes(id2)) && required.optionalProjectCapabilities.every(
|
|
379
380
|
(capability) => cached.optionalProjectCapabilities?.includes(capability)
|
|
380
381
|
);
|
|
381
382
|
}
|
|
@@ -534,8 +535,8 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
534
535
|
// src/principal-presentation.ts
|
|
535
536
|
function unresolvedPrincipalLabel(credentialKind2, principalId) {
|
|
536
537
|
const kind = typeof credentialKind2 === "string" ? credentialKind2.trim() : "";
|
|
537
|
-
const
|
|
538
|
-
const audit = kind &&
|
|
538
|
+
const id2 = typeof principalId === "string" ? principalId.trim() : "";
|
|
539
|
+
const audit = kind && id2 ? `${kind}:${id2}` : kind || id2;
|
|
539
540
|
return `Unknown principal${audit ? ` [${audit}]` : ""}`;
|
|
540
541
|
}
|
|
541
542
|
|
|
@@ -547,38 +548,38 @@ function adminAiAuditQuery(filters) {
|
|
|
547
548
|
}
|
|
548
549
|
return `?limit=${filters.limit}`;
|
|
549
550
|
}
|
|
550
|
-
async function readAdminAiAudit(
|
|
551
|
-
const response2 = await
|
|
552
|
-
headers:
|
|
551
|
+
async function readAdminAiAudit(request3) {
|
|
552
|
+
const response2 = await request3.fetch(`${request3.platform}/registry/platform/ai-audit${request3.query}`, {
|
|
553
|
+
headers: request3.headers
|
|
553
554
|
});
|
|
554
555
|
const body = await responseBody(response2);
|
|
555
556
|
if (!response2.ok) throw new Error(apiError(response2.status, body));
|
|
556
|
-
if (
|
|
557
|
-
|
|
557
|
+
if (request3.json) {
|
|
558
|
+
request3.stdout.log(JSON.stringify(body, null, 2));
|
|
558
559
|
return;
|
|
559
560
|
}
|
|
560
561
|
const events = isRecord(body) && Array.isArray(body.events) ? body.events.filter(isRecord) : [];
|
|
561
|
-
|
|
562
|
+
request3.stdout.log("when change target before -> after actor");
|
|
562
563
|
for (const event of events) {
|
|
563
564
|
const before = isRecord(event.oldPolicy) ? event.oldPolicy : void 0;
|
|
564
565
|
const after = isRecord(event.newPolicy) ? event.newPolicy : void 0;
|
|
565
|
-
const
|
|
566
|
-
|
|
566
|
+
const route3 = before && after ? `${String(before.provider)}/${String(before.model)}@v${String(before.version)} -> ${String(after.provider)}/${String(after.model)}@v${String(after.version)}` : "value not retained";
|
|
567
|
+
request3.stdout.log([
|
|
567
568
|
timestamp(event.createdAt),
|
|
568
569
|
String(event.changeKind ?? ""),
|
|
569
570
|
String(event.purpose ?? event.provider ?? ""),
|
|
570
|
-
|
|
571
|
+
route3,
|
|
571
572
|
unresolvedPrincipalLabel(event.actorType, event.actorId)
|
|
572
573
|
].join(" "));
|
|
573
574
|
}
|
|
574
575
|
}
|
|
575
576
|
async function responseBody(response2) {
|
|
576
|
-
const
|
|
577
|
-
if (!
|
|
577
|
+
const text3 = await response2.text();
|
|
578
|
+
if (!text3) return {};
|
|
578
579
|
try {
|
|
579
|
-
return JSON.parse(
|
|
580
|
+
return JSON.parse(text3);
|
|
580
581
|
} catch {
|
|
581
|
-
return { message:
|
|
582
|
+
return { message: text3.slice(0, 300) };
|
|
582
583
|
}
|
|
583
584
|
}
|
|
584
585
|
function apiError(status, body) {
|
|
@@ -619,14 +620,14 @@ function adminAiUsageQuery(filters) {
|
|
|
619
620
|
const query = params.toString();
|
|
620
621
|
return query ? `?${query}` : "";
|
|
621
622
|
}
|
|
622
|
-
async function readAdminAiUsage(
|
|
623
|
-
const res = await
|
|
624
|
-
headers:
|
|
623
|
+
async function readAdminAiUsage(request3) {
|
|
624
|
+
const res = await request3.fetch(`${request3.platform}/registry/platform/ai-usage${request3.query}`, {
|
|
625
|
+
headers: request3.headers
|
|
625
626
|
});
|
|
626
627
|
const body = await responseBody2(res);
|
|
627
628
|
if (!res.ok) throw new Error(apiError2("read platform AI usage", res.status, body));
|
|
628
|
-
if (
|
|
629
|
-
else printUsage(body,
|
|
629
|
+
if (request3.json) request3.stdout.log(JSON.stringify(body, null, 2));
|
|
630
|
+
else printUsage(body, request3.stdout);
|
|
630
631
|
}
|
|
631
632
|
function usageLimit(value2) {
|
|
632
633
|
if (!Number.isSafeInteger(value2) || value2 < 1 || value2 > 500) {
|
|
@@ -680,12 +681,12 @@ function timestamp2(value2) {
|
|
|
680
681
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
681
682
|
}
|
|
682
683
|
async function responseBody2(res) {
|
|
683
|
-
const
|
|
684
|
-
if (!
|
|
684
|
+
const text3 = await res.text();
|
|
685
|
+
if (!text3) return {};
|
|
685
686
|
try {
|
|
686
|
-
return JSON.parse(
|
|
687
|
+
return JSON.parse(text3);
|
|
687
688
|
} catch {
|
|
688
|
-
return { message:
|
|
689
|
+
return { message: text3.slice(0, 300) };
|
|
689
690
|
}
|
|
690
691
|
}
|
|
691
692
|
function apiError2(action2, status, body) {
|
|
@@ -861,12 +862,12 @@ function catalogModels(body) {
|
|
|
861
862
|
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
862
863
|
}
|
|
863
864
|
async function responseBody3(res) {
|
|
864
|
-
const
|
|
865
|
-
if (!
|
|
865
|
+
const text3 = await res.text();
|
|
866
|
+
if (!text3) return {};
|
|
866
867
|
try {
|
|
867
|
-
return JSON.parse(
|
|
868
|
+
return JSON.parse(text3);
|
|
868
869
|
} catch {
|
|
869
|
-
return { message:
|
|
870
|
+
return { message: text3.slice(0, 300) };
|
|
870
871
|
}
|
|
871
872
|
}
|
|
872
873
|
function apiError3(action2, status, body) {
|
|
@@ -998,7 +999,7 @@ function calendarServiceConfig(cfg, env) {
|
|
|
998
999
|
if (!google) throw new Error("calendar.google is required when the calendar service is enabled");
|
|
999
1000
|
const configured = google.availabilityCalendars?.[env] ?? google.calendars?.[env];
|
|
1000
1001
|
if (!configured?.length) throw new Error(`calendar.google.availabilityCalendars.${env} is required`);
|
|
1001
|
-
const availability = unique(configured.map((
|
|
1002
|
+
const availability = unique(configured.map((id2) => id2.trim()));
|
|
1002
1003
|
return {
|
|
1003
1004
|
provider: "google",
|
|
1004
1005
|
access: "book",
|
|
@@ -1047,7 +1048,7 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1047
1048
|
if (ids.length > 10) {
|
|
1048
1049
|
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
|
|
1049
1050
|
}
|
|
1050
|
-
if (ids.some((
|
|
1051
|
+
if (ids.some((id2) => !safeText2(id2, 1024))) {
|
|
1051
1052
|
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} contains an invalid calendar id`);
|
|
1052
1053
|
}
|
|
1053
1054
|
}
|
|
@@ -1189,6 +1190,175 @@ function unique2(values) {
|
|
|
1189
1190
|
return [...new Set(values.filter(Boolean))];
|
|
1190
1191
|
}
|
|
1191
1192
|
|
|
1193
|
+
// src/monitoring-validation.ts
|
|
1194
|
+
var CADENCES = /* @__PURE__ */ new Set(["1m", "2m", "5m", "10m", "15m", "30m", "1h"]);
|
|
1195
|
+
var WINDOWS = /* @__PURE__ */ new Set(["7d", "28d", "30d"]);
|
|
1196
|
+
var SHORT = /* @__PURE__ */ new Set(["30m", "1h", "6h", "12h", "1d"]);
|
|
1197
|
+
var LONG = /* @__PURE__ */ new Set(["1d", "3d", "7d"]);
|
|
1198
|
+
var DAYS = /* @__PURE__ */ new Set(["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]);
|
|
1199
|
+
var O11Y_METRICS = /* @__PURE__ */ new Set(["error_rate", "latency_p95", "synthetic_success", "synthetic_publish_to_visible"]);
|
|
1200
|
+
var COMPARATORS = /* @__PURE__ */ new Set(["gt", "gte", "lt", "lte"]);
|
|
1201
|
+
function validateMonitoringConfig(cfg, envs, services, path) {
|
|
1202
|
+
if (!cfg.o11y) return;
|
|
1203
|
+
if (!record(cfg.o11y)) fail(path, "o11y must be an object");
|
|
1204
|
+
only(cfg.o11y, ["service", "endpoint", "version", "monitoring"], `${path}: o11y`);
|
|
1205
|
+
const monitoring = cfg.o11y.monitoring;
|
|
1206
|
+
if (!monitoring) return;
|
|
1207
|
+
if (!services.includes("o11y")) fail(path, 'o11y.monitoring requires "o11y" in services');
|
|
1208
|
+
if (!record(monitoring)) fail(path, "o11y.monitoring must be an object");
|
|
1209
|
+
only(monitoring, ["probes", "slos", "notifications"], `${path}: o11y.monitoring`);
|
|
1210
|
+
if (monitoring.probes !== void 0 && (!Array.isArray(monitoring.probes) || monitoring.probes.length > 50)) {
|
|
1211
|
+
fail(path, "o11y.monitoring.probes must contain at most 50 probes");
|
|
1212
|
+
}
|
|
1213
|
+
const probeIds = /* @__PURE__ */ new Set();
|
|
1214
|
+
(monitoring.probes ?? []).forEach((probe, index) => validateProbe(probe, index, envs, path, probeIds));
|
|
1215
|
+
if (!Array.isArray(monitoring.slos) || monitoring.slos.length < 1 || monitoring.slos.length > 50) {
|
|
1216
|
+
fail(path, "o11y.monitoring.slos must contain 1 through 50 SLOs");
|
|
1217
|
+
}
|
|
1218
|
+
const sloIds = /* @__PURE__ */ new Set();
|
|
1219
|
+
monitoring.slos.forEach((slo, index) => validateSlo(slo, index, path, probeIds, sloIds));
|
|
1220
|
+
if (monitoring.notifications !== void 0) validateNotifications(monitoring.notifications, envs, path);
|
|
1221
|
+
}
|
|
1222
|
+
function validateProbe(value2, index, envs, path, ids) {
|
|
1223
|
+
const label = `${path}: o11y.monitoring.probes[${index}]`;
|
|
1224
|
+
if (!record(value2)) fail(label, "must be an object");
|
|
1225
|
+
only(value2, ["id", "route", "envs", "every", "timeout", "ready", "expect", "enabled"], label);
|
|
1226
|
+
if (!id(value2.id)) fail(label, "id must be lowercase letters, numbers, and hyphens");
|
|
1227
|
+
if (ids.has(value2.id)) fail(label, `id duplicates ${value2.id}`);
|
|
1228
|
+
ids.add(value2.id);
|
|
1229
|
+
if (!route(value2.route)) fail(label, "route must be a relative absolute path without credentials or a fragment");
|
|
1230
|
+
if (!CADENCES.has(String(value2.every))) fail(label, `every must be one of ${[...CADENCES].join(", ")}`);
|
|
1231
|
+
if (value2.timeout !== void 0 && (!Number.isSafeInteger(value2.timeout) || Number(value2.timeout) < 1e3 || Number(value2.timeout) > 6e4)) fail(label, "timeout must be an integer from 1000 through 60000");
|
|
1232
|
+
if (value2.envs !== void 0 && (!Array.isArray(value2.envs) || value2.envs.length < 1 || value2.envs.some((env) => typeof env !== "string" || !envs.includes(env) && env !== "prod"))) fail(label, "envs must contain configured environment names");
|
|
1233
|
+
if (value2.ready !== void 0) {
|
|
1234
|
+
if (!record(value2.ready) || !text(value2.ready.selector, 300)) fail(label, "ready.selector is required");
|
|
1235
|
+
only(value2.ready, ["selector"], `${label}.ready`);
|
|
1236
|
+
}
|
|
1237
|
+
if (!record(value2.expect)) fail(label, "expect must be an object");
|
|
1238
|
+
only(value2.expect, ["status", "titleIncludes", "textIncludes", "accessibility"], `${label}.expect`);
|
|
1239
|
+
if (!Number.isSafeInteger(value2.expect.status) || Number(value2.expect.status) < 100 || Number(value2.expect.status) > 599) fail(label, "expect.status must be an HTTP status from 100 through 599");
|
|
1240
|
+
if (value2.expect.titleIncludes !== void 0 && !text(value2.expect.titleIncludes, 300)) fail(label, "expect.titleIncludes is invalid");
|
|
1241
|
+
if (value2.expect.textIncludes !== void 0 && (!Array.isArray(value2.expect.textIncludes) || value2.expect.textIncludes.length > 10 || value2.expect.textIncludes.some((item) => !text(item, 500)))) fail(label, "expect.textIncludes must contain at most 10 bounded strings");
|
|
1242
|
+
if (value2.expect.accessibility !== void 0) validateAccessibility(value2.expect.accessibility, label);
|
|
1243
|
+
}
|
|
1244
|
+
function validateAccessibility(value2, label) {
|
|
1245
|
+
if (!Array.isArray(value2) || value2.length > 10) fail(label, "expect.accessibility must contain at most 10 assertions");
|
|
1246
|
+
for (const item of value2) {
|
|
1247
|
+
if (!record(item) || !text(item.role, 80) || !text(item.name, 300)) fail(label, "expect.accessibility entries need role and name");
|
|
1248
|
+
only(item, ["role", "name"], `${label}.expect.accessibility`);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
function validateSlo(value2, index, path, probes, ids) {
|
|
1252
|
+
const label = `${path}: o11y.monitoring.slos[${index}]`;
|
|
1253
|
+
if (!record(value2)) fail(label, "must be an object");
|
|
1254
|
+
only(value2, ["id", "name", "indicator", "target", "window", "alerts", "enabled"], label);
|
|
1255
|
+
if (!id(value2.id) || ids.has(value2.id)) fail(label, "id must be unique lowercase letters, numbers, and hyphens");
|
|
1256
|
+
ids.add(value2.id);
|
|
1257
|
+
if (value2.name !== void 0 && !text(value2.name, 160)) fail(label, "name is invalid");
|
|
1258
|
+
validateIndicator(value2.indicator, label, probes);
|
|
1259
|
+
if (typeof value2.target !== "number" || !Number.isFinite(value2.target) || value2.target <= 0 || value2.target >= 1) fail(label, "target must be a fraction greater than 0 and less than 1");
|
|
1260
|
+
if (!WINDOWS.has(String(value2.window))) fail(label, `window must be one of ${[...WINDOWS].join(", ")}`);
|
|
1261
|
+
if (value2.alerts !== void 0) validateAlerts(value2.alerts, label);
|
|
1262
|
+
}
|
|
1263
|
+
function validateIndicator(value2, label, probes) {
|
|
1264
|
+
if (!record(value2)) fail(label, "indicator must be an object");
|
|
1265
|
+
if (value2.type === "probe-success") {
|
|
1266
|
+
only(value2, ["type", "probes"], `${label}.indicator`);
|
|
1267
|
+
if (!Array.isArray(value2.probes) || value2.probes.length < 1) fail(label, "probe-success must select at least one probe");
|
|
1268
|
+
if (value2.probes.some((probe) => typeof probe !== "string" || !probes.has(probe))) fail(label, "indicator references an unknown probe");
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
if (value2.type !== "o11y-metric") fail(label, "indicator.type must be probe-success or o11y-metric");
|
|
1272
|
+
only(value2, ["type", "metric", "comparator", "threshold", "every", "observationWindow", "route"], `${label}.indicator`);
|
|
1273
|
+
if (!O11Y_METRICS.has(String(value2.metric))) fail(label, "indicator.metric is unsupported");
|
|
1274
|
+
if (!COMPARATORS.has(String(value2.comparator))) fail(label, "indicator.comparator is unsupported");
|
|
1275
|
+
if (typeof value2.threshold !== "number" || !Number.isFinite(value2.threshold)) fail(label, "indicator.threshold must be finite");
|
|
1276
|
+
if (!CADENCES.has(String(value2.every)) || !CADENCES.has(String(value2.observationWindow))) fail(label, "indicator cadence and observationWindow must be supported durations");
|
|
1277
|
+
if (value2.route !== void 0 && !routePattern(value2.route)) fail(label, "indicator.route must be an exact route template or trailing-* prefix");
|
|
1278
|
+
if ((value2.metric === "synthetic_success" || value2.metric === "synthetic_publish_to_visible") && value2.route !== void 0) fail(label, "synthetic indicators cannot select a route");
|
|
1279
|
+
}
|
|
1280
|
+
function validateAlerts(value2, label) {
|
|
1281
|
+
if (!record(value2)) fail(label, "alerts must be an object");
|
|
1282
|
+
only(value2, ["spike", "trend"], `${label}.alerts`);
|
|
1283
|
+
if (value2.spike !== void 0) {
|
|
1284
|
+
if (!record(value2.spike)) fail(label, "alerts.spike must be an object");
|
|
1285
|
+
only(value2.spike, ["badChecks", "withinChecks", "recoverAfter"], `${label}.alerts.spike`);
|
|
1286
|
+
const bad = positive(value2.spike.badChecks, 2), within = positive(value2.spike.withinChecks, 3), recover = positive(value2.spike.recoverAfter, 2);
|
|
1287
|
+
if (bad > within || within > 20 || recover > 20) fail(label, "alerts.spike requires badChecks <= withinChecks <= 20 and recoverAfter <= 20");
|
|
1288
|
+
}
|
|
1289
|
+
if (value2.trend !== void 0) {
|
|
1290
|
+
if (!record(value2.trend)) fail(label, "alerts.trend must be an object");
|
|
1291
|
+
only(value2.trend, ["burnRate", "shortWindow", "longWindow", "minBadChecks"], `${label}.alerts.trend`);
|
|
1292
|
+
const burn = value2.trend.burnRate ?? 1;
|
|
1293
|
+
if (typeof burn !== "number" || !Number.isFinite(burn) || burn <= 0 || burn > 1e3) fail(label, "alerts.trend.burnRate must be greater than 0");
|
|
1294
|
+
if (value2.trend.shortWindow !== void 0 && !SHORT.has(String(value2.trend.shortWindow))) fail(label, "alerts.trend.shortWindow is unsupported");
|
|
1295
|
+
if (value2.trend.longWindow !== void 0 && !LONG.has(String(value2.trend.longWindow))) fail(label, "alerts.trend.longWindow is unsupported");
|
|
1296
|
+
if (positive(value2.trend.minBadChecks, 2) > 100) fail(label, "alerts.trend.minBadChecks must be at most 100");
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
function validateNotifications(value2, envs, path) {
|
|
1300
|
+
if (!record(value2)) fail(path, "o11y.monitoring.notifications must map environments to policies");
|
|
1301
|
+
for (const [env, policy] of Object.entries(value2)) {
|
|
1302
|
+
const label = `${path}: o11y.monitoring.notifications.${env}`;
|
|
1303
|
+
if (!envs.includes(env) && env !== "prod") fail(label, "is not a configured environment");
|
|
1304
|
+
if (!record(policy)) fail(label, "must be an object");
|
|
1305
|
+
only(policy, ["email", "timezone", "daily", "weekly"], label);
|
|
1306
|
+
if (!Array.isArray(policy.email) || policy.email.length < 1 || policy.email.length > 10 || policy.email.some((email) => !emailAddress(email))) fail(label, "email must contain 1 through 10 email addresses");
|
|
1307
|
+
if (!timezone(policy.timezone)) fail(label, "timezone must be an IANA timezone");
|
|
1308
|
+
if (policy.daily !== void 0 && policy.daily !== false && !clock(policy.daily)) fail(label, "daily must be HH:MM or false");
|
|
1309
|
+
if (policy.weekly !== void 0 && policy.weekly !== false) {
|
|
1310
|
+
if (!record(policy.weekly) || !DAYS.has(String(policy.weekly.day)) || !clock(policy.weekly.at)) fail(label, "weekly needs a weekday and HH:MM time");
|
|
1311
|
+
only(policy.weekly, ["day", "at"], `${label}.weekly`);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
function fail(label, message2) {
|
|
1316
|
+
throw new Error(`${label}: ${message2}`);
|
|
1317
|
+
}
|
|
1318
|
+
function record(value2) {
|
|
1319
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
1320
|
+
}
|
|
1321
|
+
function only(value2, keys, label) {
|
|
1322
|
+
const extra = Object.keys(value2).find((key) => !keys.includes(key));
|
|
1323
|
+
if (extra) fail(label, `${extra} is not supported`);
|
|
1324
|
+
}
|
|
1325
|
+
function id(value2) {
|
|
1326
|
+
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1327
|
+
}
|
|
1328
|
+
function text(value2, max) {
|
|
1329
|
+
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1330
|
+
}
|
|
1331
|
+
function positive(value2, fallback) {
|
|
1332
|
+
return value2 === void 0 ? fallback : Number.isSafeInteger(value2) && Number(value2) > 0 ? Number(value2) : Infinity;
|
|
1333
|
+
}
|
|
1334
|
+
function route(value2) {
|
|
1335
|
+
if (typeof value2 !== "string" || value2.length > 2048 || !value2.startsWith("/") || value2.startsWith("//")) return false;
|
|
1336
|
+
try {
|
|
1337
|
+
const url = new URL(value2, "https://probe.invalid");
|
|
1338
|
+
return url.origin === "https://probe.invalid" && !url.hash;
|
|
1339
|
+
} catch {
|
|
1340
|
+
return false;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
function routePattern(value2) {
|
|
1344
|
+
return typeof value2 === "string" && value2.length <= 160 && /^\/[A-Za-z0-9_./:-]+\*?$/.test(value2) && !value2.slice(0, -1).includes("*");
|
|
1345
|
+
}
|
|
1346
|
+
function emailAddress(value2) {
|
|
1347
|
+
return typeof value2 === "string" && value2.length <= 254 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value2);
|
|
1348
|
+
}
|
|
1349
|
+
function clock(value2) {
|
|
1350
|
+
return typeof value2 === "string" && /^(?:[01]\d|2[0-3]):[0-5]\d$/.test(value2);
|
|
1351
|
+
}
|
|
1352
|
+
function timezone(value2) {
|
|
1353
|
+
if (typeof value2 !== "string" || value2.length > 100) return false;
|
|
1354
|
+
try {
|
|
1355
|
+
new Intl.DateTimeFormat("en", { timeZone: value2 }).format(0);
|
|
1356
|
+
return true;
|
|
1357
|
+
} catch {
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1192
1362
|
// src/config.ts
|
|
1193
1363
|
var DEFAULT_PLATFORM = "https://odla.ai";
|
|
1194
1364
|
var DEFAULT_ENVS = ["dev"];
|
|
@@ -1209,6 +1379,7 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
|
1209
1379
|
const services = unique3(raw.services?.length ? raw.services : DEFAULT_SERVICES);
|
|
1210
1380
|
validateServices(services, resolved);
|
|
1211
1381
|
validateCalendarConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1382
|
+
validateMonitoringConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1212
1383
|
const local = {
|
|
1213
1384
|
tokenFile: resolve2(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
|
|
1214
1385
|
credentialsFile: resolve2(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
|
|
@@ -1610,7 +1781,7 @@ async function adminCommand(parsed, deps = {}) {
|
|
|
1610
1781
|
import process11 from "process";
|
|
1611
1782
|
|
|
1612
1783
|
// src/whoami-command.ts
|
|
1613
|
-
var
|
|
1784
|
+
var text2 = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
1614
1785
|
function principalKind(value2, machine) {
|
|
1615
1786
|
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
1616
1787
|
}
|
|
@@ -1623,12 +1794,12 @@ function credentialKind(value2, machine, scopes) {
|
|
|
1623
1794
|
function managerOf(value2) {
|
|
1624
1795
|
if (!value2 || typeof value2 !== "object") return null;
|
|
1625
1796
|
const row = value2;
|
|
1626
|
-
const principalId =
|
|
1797
|
+
const principalId = text2(row.principalId);
|
|
1627
1798
|
if (!principalId) return null;
|
|
1628
1799
|
return {
|
|
1629
1800
|
principalId,
|
|
1630
|
-
displayName:
|
|
1631
|
-
handle:
|
|
1801
|
+
displayName: text2(row.displayName) ?? "Unnamed member",
|
|
1802
|
+
handle: text2(row.handle) ?? ""
|
|
1632
1803
|
};
|
|
1633
1804
|
}
|
|
1634
1805
|
function unnamedPrincipal(kind) {
|
|
@@ -1642,14 +1813,14 @@ async function fetchIdentity(platformUrl, token, doFetch) {
|
|
|
1642
1813
|
});
|
|
1643
1814
|
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
1644
1815
|
const body = await res.json();
|
|
1645
|
-
const developerId =
|
|
1816
|
+
const developerId = text2(body.developerId) ?? "";
|
|
1646
1817
|
const machine = body.machine === true;
|
|
1647
1818
|
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
1648
|
-
const principalId =
|
|
1649
|
-
const email =
|
|
1819
|
+
const principalId = text2(body.principalId) ?? developerId;
|
|
1820
|
+
const email = text2(body.email);
|
|
1650
1821
|
const kind = principalKind(body.principalKind, machine);
|
|
1651
|
-
const displayName =
|
|
1652
|
-
const handle =
|
|
1822
|
+
const displayName = text2(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
1823
|
+
const handle = text2(body.handle) ?? "";
|
|
1653
1824
|
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
1654
1825
|
return {
|
|
1655
1826
|
developerId,
|
|
@@ -1659,7 +1830,7 @@ async function fetchIdentity(platformUrl, token, doFetch) {
|
|
|
1659
1830
|
handle,
|
|
1660
1831
|
manager: managerOf(body.manager),
|
|
1661
1832
|
credential: {
|
|
1662
|
-
id:
|
|
1833
|
+
id: text2(credential2.id),
|
|
1663
1834
|
kind: credentialKind(credential2.kind, machine, scopes)
|
|
1664
1835
|
},
|
|
1665
1836
|
email,
|
|
@@ -1854,13 +2025,13 @@ async function agentCommand(parsed, deps = {}) {
|
|
|
1854
2025
|
const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
|
|
1855
2026
|
const headers = { authorization: `Bearer ${credential2}` };
|
|
1856
2027
|
if (action2 === "retry") {
|
|
1857
|
-
const
|
|
1858
|
-
const res2 = await doFetch(`${base}/${encodeURIComponent(
|
|
2028
|
+
const id2 = parsed.positionals[2];
|
|
2029
|
+
const res2 = await doFetch(`${base}/${encodeURIComponent(id2)}/retry`, { method: "POST", headers });
|
|
1859
2030
|
const body2 = await readJson(res2);
|
|
1860
2031
|
if (!res2.ok) throw new Error(`agent retry failed (${res2.status}): ${errorMessage(body2)}`);
|
|
1861
2032
|
const result2 = { v: 1, appId: cfg.app.id, env, tenant, ...body2 };
|
|
1862
2033
|
if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
|
|
1863
|
-
else out.log(`${tenant}: requeued ${
|
|
2034
|
+
else out.log(`${tenant}: requeued ${id2}`);
|
|
1864
2035
|
return;
|
|
1865
2036
|
}
|
|
1866
2037
|
const state2 = stringOpt(parsed.options.state);
|
|
@@ -1944,8 +2115,8 @@ async function appImport(options) {
|
|
|
1944
2115
|
const out = options.stdout ?? console;
|
|
1945
2116
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
1946
2117
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
1947
|
-
const
|
|
1948
|
-
const { format, sources } = parseImport(
|
|
2118
|
+
const text3 = options.file === "-" ? (options.readStdin ?? (() => readFileSync4(0, "utf8")))() : readFileSync4(options.file, "utf8");
|
|
2119
|
+
const { format, sources } = parseImport(text3, options.ns);
|
|
1949
2120
|
if (format === "namespace-map" && options.ns) {
|
|
1950
2121
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
1951
2122
|
}
|
|
@@ -2161,7 +2332,7 @@ var EXTENSIONS = {
|
|
|
2161
2332
|
"text/html": "html",
|
|
2162
2333
|
"application/json": "json"
|
|
2163
2334
|
};
|
|
2164
|
-
var encode = (
|
|
2335
|
+
var encode = (text3) => new TextEncoder().encode(text3);
|
|
2165
2336
|
function assetFileName(uuid, mime) {
|
|
2166
2337
|
const ext = EXTENSIONS[mime.split(";")[0].trim().toLowerCase()] ?? "bin";
|
|
2167
2338
|
return `${uuid.replace(/[^a-zA-Z0-9._-]/g, "_")}.${ext}`;
|
|
@@ -2323,18 +2494,18 @@ async function readCalendarStatus(ctx) {
|
|
|
2323
2494
|
}
|
|
2324
2495
|
async function discoverGoogleCalendars(ctx) {
|
|
2325
2496
|
const raw = await calendarJson(ctx, "/calendars", {});
|
|
2326
|
-
const value2 =
|
|
2497
|
+
const value2 = record2(raw);
|
|
2327
2498
|
if (!value2 || !Array.isArray(value2.calendars)) throw new Error("calendar discovery returned an invalid response");
|
|
2328
2499
|
return value2.calendars.map((item, index) => {
|
|
2329
|
-
const calendar =
|
|
2330
|
-
const
|
|
2331
|
-
if (!calendar || !
|
|
2500
|
+
const calendar = record2(item);
|
|
2501
|
+
const id2 = textField(calendar?.id, 1024);
|
|
2502
|
+
if (!calendar || !id2) throw new Error(`calendar discovery returned an invalid calendar at index ${index}`);
|
|
2332
2503
|
const role = calendar.accessRole;
|
|
2333
2504
|
if (role !== void 0 && role !== "freeBusyReader" && role !== "reader" && role !== "writer" && role !== "owner") {
|
|
2334
2505
|
throw new Error(`calendar discovery returned an invalid access role at index ${index}`);
|
|
2335
2506
|
}
|
|
2336
2507
|
return {
|
|
2337
|
-
id,
|
|
2508
|
+
id: id2,
|
|
2338
2509
|
...optionalText("summary", calendar.summary, 500),
|
|
2339
2510
|
...typeof calendar.primary === "boolean" ? { primary: calendar.primary } : {},
|
|
2340
2511
|
...typeof calendar.selected === "boolean" ? { selected: calendar.selected } : {},
|
|
@@ -2362,10 +2533,10 @@ async function pollCalendarConnection(ctx, attemptId) {
|
|
|
2362
2533
|
}
|
|
2363
2534
|
function parseCalendarStatus(raw, env) {
|
|
2364
2535
|
const outer = wrapped(raw, "calendar");
|
|
2365
|
-
const value2 =
|
|
2366
|
-
const connection =
|
|
2367
|
-
const config =
|
|
2368
|
-
const googleConfig =
|
|
2536
|
+
const value2 = record2(outer.attempt) ?? record2(outer.status) ?? outer;
|
|
2537
|
+
const connection = record2(value2.connection) ?? {};
|
|
2538
|
+
const config = record2(value2.config) ?? record2(outer.config) ?? {};
|
|
2539
|
+
const googleConfig = record2(config.google) ?? config;
|
|
2369
2540
|
const stateValue = calendarState(value2.status ?? value2.state ?? connection.status ?? connection.state);
|
|
2370
2541
|
if (!stateValue) {
|
|
2371
2542
|
throw new Error("calendar status returned an invalid connection state");
|
|
@@ -2378,7 +2549,7 @@ function parseCalendarStatus(raw, env) {
|
|
|
2378
2549
|
if (accessValue !== void 0 && accessValue !== "book" && accessValue !== "read") {
|
|
2379
2550
|
throw new Error("calendar status returned unsupported access");
|
|
2380
2551
|
}
|
|
2381
|
-
const errorValue =
|
|
2552
|
+
const errorValue = record2(value2.error) ?? record2(connection.error);
|
|
2382
2553
|
const errorCode2 = textField(value2.lastErrorCode, 128);
|
|
2383
2554
|
const bookingPageValue = Object.hasOwn(value2, "bookingPageUrl") ? value2.bookingPageUrl : Object.hasOwn(config, "bookingPageUrl") ? config.bookingPageUrl : googleConfig.bookingPageUrl;
|
|
2384
2555
|
const connected = typeof (value2.connected ?? connection.connected) === "boolean" ? Boolean(value2.connected ?? connection.connected) : ["healthy", "degraded"].includes(stateValue);
|
|
@@ -2448,11 +2619,11 @@ async function calendarJson(ctx, suffix, init) {
|
|
|
2448
2619
|
return body;
|
|
2449
2620
|
}
|
|
2450
2621
|
function wrapped(raw, key) {
|
|
2451
|
-
const outer =
|
|
2622
|
+
const outer = record2(raw);
|
|
2452
2623
|
if (!outer) throw new Error("calendar returned an invalid response");
|
|
2453
|
-
return
|
|
2624
|
+
return record2(outer[key]) ?? outer;
|
|
2454
2625
|
}
|
|
2455
|
-
function
|
|
2626
|
+
function record2(value2) {
|
|
2456
2627
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
2457
2628
|
}
|
|
2458
2629
|
function textField(value2, max) {
|
|
@@ -2465,9 +2636,9 @@ function calendarIds(value2) {
|
|
|
2465
2636
|
if (!Array.isArray(value2)) return [];
|
|
2466
2637
|
return [...new Set(value2.flatMap((item) => {
|
|
2467
2638
|
if (typeof item === "string") return textField(item, 4096) ? [item] : [];
|
|
2468
|
-
const calendar =
|
|
2469
|
-
const
|
|
2470
|
-
return
|
|
2639
|
+
const calendar = record2(item);
|
|
2640
|
+
const id2 = textField(calendar?.id, 4096);
|
|
2641
|
+
return id2 && calendar?.selected !== false ? [id2] : [];
|
|
2471
2642
|
}))];
|
|
2472
2643
|
}
|
|
2473
2644
|
function timestamp3(value2) {
|
|
@@ -2702,6 +2873,7 @@ var CAPABILITIES = {
|
|
|
2702
2873
|
"validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
|
|
2703
2874
|
"save and explicitly select non-secret named operator contexts with isolated credential caches; resolve and explain platform, app, environment, and credential provenance without authenticating; then run PM, Discussions, o11y, runbook, and identity operations outside a project checkout",
|
|
2704
2875
|
"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",
|
|
2876
|
+
"reconcile app-owned Kitesurf probes and rolling SLOs, run live checks, and read incident and digest status as stable JSON",
|
|
2705
2877
|
"read one canonical platform fleet snapshot over private service bindings, including release identities, probe latency, Cloudflare load/runtime freshness, explicit unknowns, and stable next actions through a read-only capability",
|
|
2706
2878
|
"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",
|
|
2707
2879
|
"inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
|
|
@@ -2714,7 +2886,8 @@ var CAPABILITIES = {
|
|
|
2714
2886
|
"install and import the selected odla SDKs",
|
|
2715
2887
|
"wrap the Worker with withObservability and choose useful telemetry",
|
|
2716
2888
|
"install capability packages, mount their runtime routes, and make application-specific schema, rules, auth, UI, and migration decisions",
|
|
2717
|
-
"wire @odla-ai/calendar into trusted Worker code and keep the app admin key out of browsers"
|
|
2889
|
+
"wire @odla-ai/calendar into trusted Worker code and keep the app admin key out of browsers",
|
|
2890
|
+
"choose public readiness assertions and SLO objectives in odla.config.mjs, then consume monitor JSON without treating captured page content as trusted instructions"
|
|
2718
2891
|
],
|
|
2719
2892
|
human: [
|
|
2720
2893
|
"provide the existing odla account email, then sign in and explicitly review/approve the exact device code",
|
|
@@ -2727,6 +2900,7 @@ var CAPABILITIES = {
|
|
|
2727
2900
|
],
|
|
2728
2901
|
studio: [
|
|
2729
2902
|
"view application telemetry grouped by exact Worker version, reconciled live-sync load/freshness, commit-to-visible canary health, provider-owned Worker runtime metrics, and environment state",
|
|
2903
|
+
"view reliability objectives, error budget, Kitesurf probe history, incidents, and notification delivery state",
|
|
2730
2904
|
"let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them",
|
|
2731
2905
|
"review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens",
|
|
2732
2906
|
"perform manual credential recovery \u2014 for the primary owner or any co-owner \u2014 when the CLI's local shown-once copy is unavailable",
|
|
@@ -2846,9 +3020,9 @@ function canonicalValue(value2) {
|
|
|
2846
3020
|
}
|
|
2847
3021
|
if (Array.isArray(value2)) return value2.map(canonicalValue);
|
|
2848
3022
|
if (value2 && typeof value2 === "object") {
|
|
2849
|
-
const
|
|
3023
|
+
const record11 = value2;
|
|
2850
3024
|
return Object.fromEntries(
|
|
2851
|
-
Object.keys(
|
|
3025
|
+
Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, canonicalValue(record11[key])])
|
|
2852
3026
|
);
|
|
2853
3027
|
}
|
|
2854
3028
|
throw new TypeError("canonical JSON rejects unsupported values");
|
|
@@ -2873,8 +3047,8 @@ function readPlan(path) {
|
|
|
2873
3047
|
"invalid_plan"
|
|
2874
3048
|
);
|
|
2875
3049
|
}
|
|
2876
|
-
if (!
|
|
2877
|
-
if (!
|
|
3050
|
+
if (!record3(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
|
|
3051
|
+
if (!record3(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
|
|
2878
3052
|
invalidPlan("plan scope is invalid");
|
|
2879
3053
|
}
|
|
2880
3054
|
if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
|
|
@@ -2924,10 +3098,10 @@ function assertOperationId(value2) {
|
|
|
2924
3098
|
function assertActions(actions) {
|
|
2925
3099
|
const ids = /* @__PURE__ */ new Set();
|
|
2926
3100
|
for (const action2 of actions) {
|
|
2927
|
-
if (!
|
|
2928
|
-
const
|
|
2929
|
-
if (!ACTION_ID.test(
|
|
2930
|
-
ids.add(
|
|
3101
|
+
if (!record3(action2)) invalidPlan("every plan action must be an object");
|
|
3102
|
+
const id2 = String(action2.id ?? "");
|
|
3103
|
+
if (!ACTION_ID.test(id2) || ids.has(id2)) invalidPlan("plan action ids must be unique frozen ids");
|
|
3104
|
+
ids.add(id2);
|
|
2931
3105
|
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))) {
|
|
2932
3106
|
invalidPlan("plan action metadata is invalid");
|
|
2933
3107
|
}
|
|
@@ -2953,14 +3127,14 @@ function assertConditionalAction(action2) {
|
|
|
2953
3127
|
if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
|
|
2954
3128
|
invalidPlan("service action path is invalid");
|
|
2955
3129
|
}
|
|
2956
|
-
if (action2.applySupport !== "provision" || !
|
|
3130
|
+
if (action2.applySupport !== "provision" || !record3(action2.after)) {
|
|
2957
3131
|
invalidPlan("service action payload is invalid");
|
|
2958
3132
|
}
|
|
2959
3133
|
if (action2.kind === "enable_service") {
|
|
2960
|
-
if (action2.after.enabled !== true || action2.before !== null && !
|
|
3134
|
+
if (action2.after.enabled !== true || action2.before !== null && !record3(action2.before)) {
|
|
2961
3135
|
invalidPlan("service enable action is invalid");
|
|
2962
3136
|
}
|
|
2963
|
-
} else if (!
|
|
3137
|
+
} else if (!record3(action2.before)) {
|
|
2964
3138
|
invalidPlan("service configure action is invalid");
|
|
2965
3139
|
}
|
|
2966
3140
|
}
|
|
@@ -2977,7 +3151,7 @@ function linkState(value2) {
|
|
|
2977
3151
|
function invalidPlan(message2) {
|
|
2978
3152
|
throw new ConfigOperationCommandError(message2, "invalid_plan");
|
|
2979
3153
|
}
|
|
2980
|
-
function
|
|
3154
|
+
function record3(value2) {
|
|
2981
3155
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
2982
3156
|
}
|
|
2983
3157
|
|
|
@@ -3016,9 +3190,9 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
3016
3190
|
}
|
|
3017
3191
|
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
3018
3192
|
}
|
|
3019
|
-
function errorCode(
|
|
3193
|
+
function errorCode(text3) {
|
|
3020
3194
|
try {
|
|
3021
|
-
const body = JSON.parse(
|
|
3195
|
+
const body = JSON.parse(text3);
|
|
3022
3196
|
return typeof body.error?.code === "string" ? body.error.code : null;
|
|
3023
3197
|
} catch {
|
|
3024
3198
|
return null;
|
|
@@ -3161,7 +3335,7 @@ async function configApply(options) {
|
|
|
3161
3335
|
throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
|
|
3162
3336
|
}
|
|
3163
3337
|
const client = await operationClient(cfg, options, "apply");
|
|
3164
|
-
const
|
|
3338
|
+
const request3 = {
|
|
3165
3339
|
schemaVersion: "odla.config-operation-request/v1",
|
|
3166
3340
|
expectedRevision: plan.registryRevision,
|
|
3167
3341
|
desiredRevision: plan.desiredRevision,
|
|
@@ -3173,7 +3347,7 @@ async function configApply(options) {
|
|
|
3173
3347
|
};
|
|
3174
3348
|
let receipt;
|
|
3175
3349
|
try {
|
|
3176
|
-
receipt = await client.applyConfigOperation(cfg.app.id,
|
|
3350
|
+
receipt = await client.applyConfigOperation(cfg.app.id, request3);
|
|
3177
3351
|
} catch (error) {
|
|
3178
3352
|
const retained = retainedReceipt(error);
|
|
3179
3353
|
if (retained) {
|
|
@@ -3272,8 +3446,8 @@ function failureForReceipt(receipt) {
|
|
|
3272
3446
|
return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
|
|
3273
3447
|
}
|
|
3274
3448
|
function retainedReceipt(error) {
|
|
3275
|
-
if (!(error instanceof AppsError) || !
|
|
3276
|
-
return
|
|
3449
|
+
if (!(error instanceof AppsError) || !record4(error.details)) return null;
|
|
3450
|
+
return record4(error.details.operation) ? error.details.operation : null;
|
|
3277
3451
|
}
|
|
3278
3452
|
function normalizeRequestError(error) {
|
|
3279
3453
|
if (!(error instanceof AppsError)) return error instanceof Error ? error : new Error(String(error));
|
|
@@ -3286,7 +3460,7 @@ function normalizeRequestError(error) {
|
|
|
3286
3460
|
}
|
|
3287
3461
|
return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
|
|
3288
3462
|
}
|
|
3289
|
-
function
|
|
3463
|
+
function record4(value2) {
|
|
3290
3464
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
3291
3465
|
}
|
|
3292
3466
|
|
|
@@ -3753,15 +3927,15 @@ function readWranglerConfig(path) {
|
|
|
3753
3927
|
return null;
|
|
3754
3928
|
}
|
|
3755
3929
|
}
|
|
3756
|
-
function stripJsonComments(
|
|
3930
|
+
function stripJsonComments(text3) {
|
|
3757
3931
|
let result = "";
|
|
3758
3932
|
let inString = false;
|
|
3759
|
-
for (let i = 0; i <
|
|
3760
|
-
const ch =
|
|
3933
|
+
for (let i = 0; i < text3.length; i++) {
|
|
3934
|
+
const ch = text3[i];
|
|
3761
3935
|
if (inString) {
|
|
3762
3936
|
result += ch;
|
|
3763
3937
|
if (ch === "\\") {
|
|
3764
|
-
result +=
|
|
3938
|
+
result += text3[i + 1] ?? "";
|
|
3765
3939
|
i++;
|
|
3766
3940
|
} else if (ch === '"') {
|
|
3767
3941
|
inString = false;
|
|
@@ -3773,14 +3947,14 @@ function stripJsonComments(text2) {
|
|
|
3773
3947
|
result += ch;
|
|
3774
3948
|
continue;
|
|
3775
3949
|
}
|
|
3776
|
-
if (ch === "/" &&
|
|
3777
|
-
while (i <
|
|
3950
|
+
if (ch === "/" && text3[i + 1] === "/") {
|
|
3951
|
+
while (i < text3.length && text3[i] !== "\n") i++;
|
|
3778
3952
|
result += "\n";
|
|
3779
3953
|
continue;
|
|
3780
3954
|
}
|
|
3781
|
-
if (ch === "/" &&
|
|
3955
|
+
if (ch === "/" && text3[i + 1] === "*") {
|
|
3782
3956
|
i += 2;
|
|
3783
|
-
while (i <
|
|
3957
|
+
while (i < text3.length && !(text3[i] === "*" && text3[i + 1] === "/")) i++;
|
|
3784
3958
|
i++;
|
|
3785
3959
|
continue;
|
|
3786
3960
|
}
|
|
@@ -3819,7 +3993,7 @@ async function wranglerRuntimeTarget(run, opts) {
|
|
|
3819
3993
|
throw new Error(`wrangler is not logged in \u2014 run "wrangler login" (a browser step for the human)`);
|
|
3820
3994
|
}
|
|
3821
3995
|
const discovered = [...new Set(`${whoami.stdout}
|
|
3822
|
-
${whoami.stderr}`.match(/\b[a-f0-9]{32}\b/gi)?.map((
|
|
3996
|
+
${whoami.stderr}`.match(/\b[a-f0-9]{32}\b/gi)?.map((id2) => id2.toLowerCase()) ?? [])];
|
|
3823
3997
|
const accountId = configuredAccount.toLowerCase() || (discovered.length === 1 ? discovered[0] : "");
|
|
3824
3998
|
if (!/^[a-f0-9]{32}$/.test(accountId) || configuredAccount && discovered.length > 0 && !discovered.includes(accountId)) {
|
|
3825
3999
|
throw new Error("Wrangler account is ambiguous or does not match account_id in the config");
|
|
@@ -4294,9 +4468,9 @@ function initProject(options) {
|
|
|
4294
4468
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
4295
4469
|
out.log("updated .gitignore for local odla credentials");
|
|
4296
4470
|
}
|
|
4297
|
-
function writeIfMissing(path,
|
|
4471
|
+
function writeIfMissing(path, text3) {
|
|
4298
4472
|
if (existsSync8(path)) return;
|
|
4299
|
-
writeFileSync2(path,
|
|
4473
|
+
writeFileSync2(path, text3);
|
|
4300
4474
|
}
|
|
4301
4475
|
function configTemplate(input) {
|
|
4302
4476
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -4506,13 +4680,13 @@ async function secretsSetClerkKey(options) {
|
|
|
4506
4680
|
body: JSON.stringify({ value: value2 })
|
|
4507
4681
|
});
|
|
4508
4682
|
if (!res.ok) {
|
|
4509
|
-
const
|
|
4510
|
-
throw new Error(`store Clerk secret key failed (${res.status}): ${
|
|
4683
|
+
const text3 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
4684
|
+
throw new Error(`store Clerk secret key failed (${res.status}): ${text3 || "request failed"}`);
|
|
4511
4685
|
}
|
|
4512
4686
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
4513
4687
|
}
|
|
4514
|
-
function scrubValue(
|
|
4515
|
-
return redactSecrets(
|
|
4688
|
+
function scrubValue(text3, value2) {
|
|
4689
|
+
return redactSecrets(text3).split(value2).join("[value redacted]");
|
|
4516
4690
|
}
|
|
4517
4691
|
async function resolveVaultWrite(options) {
|
|
4518
4692
|
const out = options.stdout ?? console;
|
|
@@ -4607,18 +4781,29 @@ For work that creates an odla app or adds odla services, read and follow
|
|
|
4607
4781
|
\`.agents/skills/odla-o11y-debug/SKILL.md\`.
|
|
4608
4782
|
|
|
4609
4783
|
Track the work in odla's PM as you go. Before project-mutating work, run
|
|
4610
|
-
\`npx @odla-ai/cli pm next --app <appId>\`, confirm alignment to an open goal,
|
|
4784
|
+
\`npx --yes @odla-ai/cli@latest pm next --app <appId>\`, confirm alignment to an open goal,
|
|
4611
4785
|
and atomically claim a refined Ready task. Record decisions when you make them
|
|
4612
4786
|
and file bugs when you notice them. The conventions and the full command set are
|
|
4613
4787
|
in \`.agents/skills/odla/references/pm.md\`.
|
|
4614
4788
|
|
|
4615
|
-
Use the human's signed-in odla account email for device authorization;
|
|
4616
|
-
infer it from git config, commit metadata, or GitHub. If authorization is
|
|
4617
|
-
already active, run
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4789
|
+
Use the human's known signed-in odla account email for device authorization;
|
|
4790
|
+
never infer it from git config, commit metadata, or GitHub. If authorization is
|
|
4791
|
+
not already active, run this exact command as one foreground process:
|
|
4792
|
+
|
|
4793
|
+
\`npx --yes @odla-ai/cli@latest auth login --app <appId> --email <odla-account> --no-open --wait 600\`
|
|
4794
|
+
|
|
4795
|
+
When it prints the approval URL and code, immediately give the human a clickable
|
|
4796
|
+
link, name the code they must verify, and tell them to click **Approve**. Keep
|
|
4797
|
+
that CLI process running and wait on the same tool process for its result. The
|
|
4798
|
+
CLI owns protocol polling: never call the OS \`open\` command, use browser
|
|
4799
|
+
control, curl a handshake endpoint, build a shell wait/poll loop, detach the
|
|
4800
|
+
process, or start another handshake while it is alive. The device code exists
|
|
4801
|
+
only in that process. If it exits 75 before approval, the old code cannot be
|
|
4802
|
+
collected; start one fresh foreground command and surface its new URL.
|
|
4803
|
+
|
|
4804
|
+
Never file an odla project or product defect in GitHub Issues: run
|
|
4805
|
+
\`npx --yes @odla-ai/cli@latest bug report --app <appId> ...\` so the bug lands
|
|
4806
|
+
in odla PM with the rest of the project's goals, tasks, and decisions.
|
|
4622
4807
|
|
|
4623
4808
|
The setup runbooks and their references are installed in this repository, pinned
|
|
4624
4809
|
to this CLI version. Use them as your setup context.
|
|
@@ -4630,9 +4815,9 @@ When this repository has an \`appId\`, pass it: app-scoped discovery includes
|
|
|
4630
4815
|
that project's instructions plus the shared platform procedures.
|
|
4631
4816
|
|
|
4632
4817
|
\`\`\`
|
|
4633
|
-
npx odla-ai runbook ask "<what you are about to do>" --app <appId>
|
|
4634
|
-
npx odla-ai runbook list
|
|
4635
|
-
npx odla-ai runbook get <slug>
|
|
4818
|
+
npx --yes @odla-ai/cli@latest runbook ask "<what you are about to do>" --app <appId>
|
|
4819
|
+
npx --yes @odla-ai/cli@latest runbook list
|
|
4820
|
+
npx --yes @odla-ai/cli@latest runbook get <slug>
|
|
4636
4821
|
\`\`\`
|
|
4637
4822
|
|
|
4638
4823
|
Never scrape odla.ai HTML for any of this. The CLI reads the same content
|
|
@@ -4646,8 +4831,8 @@ alwaysApply: false
|
|
|
4646
4831
|
|
|
4647
4832
|
${PROJECT_INSTRUCTIONS}
|
|
4648
4833
|
`;
|
|
4649
|
-
function claudeAdapter(skill,
|
|
4650
|
-
const match =
|
|
4834
|
+
function claudeAdapter(skill, canonical2) {
|
|
4835
|
+
const match = canonical2.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
4651
4836
|
if (!match) throw new Error(`bundled skill ${skill} has no YAML frontmatter`);
|
|
4652
4837
|
const lines = match[1].split(/\r?\n/);
|
|
4653
4838
|
const frontmatter = [];
|
|
@@ -4717,8 +4902,8 @@ function installSkill(options = {}) {
|
|
|
4717
4902
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
4718
4903
|
if (harnesses.includes("claude")) {
|
|
4719
4904
|
for (const skill of skillNames(files)) {
|
|
4720
|
-
const
|
|
4721
|
-
plan(join9(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill,
|
|
4905
|
+
const canonical2 = readFileSync8(join9(sourceDir, skill, "SKILL.md"), "utf8");
|
|
4906
|
+
plan(join9(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
|
|
4722
4907
|
}
|
|
4723
4908
|
rememberTarget("claude", claudeRoot);
|
|
4724
4909
|
}
|
|
@@ -4994,7 +5179,7 @@ function assertCalendarHealthy(status, expected) {
|
|
|
4994
5179
|
if (!status.writable) throw new Error('calendar grant does not cover booking writes; run "odla-ai calendar connect" to re-consent');
|
|
4995
5180
|
const hasEventsScope = status.grantedScopes.some((scope) => scope === GOOGLE_CALENDAR_EVENTS_SCOPE);
|
|
4996
5181
|
if (!hasEventsScope) throw new Error("calendar connection is missing calendar.events consent");
|
|
4997
|
-
const missing = expected.availabilityCalendars.filter((
|
|
5182
|
+
const missing = expected.availabilityCalendars.filter((id2) => !status.calendars.includes(id2));
|
|
4998
5183
|
if (missing.length) throw new Error(`calendar connection is missing configured calendars: ${missing.join(", ")}`);
|
|
4999
5184
|
}
|
|
5000
5185
|
async function getJson(doFetch, url, bearer) {
|
|
@@ -5394,8 +5579,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
5394
5579
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
5395
5580
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
5396
5581
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
5397
|
-
const entries = inventory.flatMap((
|
|
5398
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
5582
|
+
const entries = inventory.flatMap((record11) => {
|
|
5583
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record11);
|
|
5399
5584
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
5400
5585
|
});
|
|
5401
5586
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -5643,8 +5828,8 @@ function normalize(value2) {
|
|
|
5643
5828
|
if (Array.isArray(value2)) return value2.map(normalize);
|
|
5644
5829
|
if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
|
|
5645
5830
|
if (typeof value2 === "object") {
|
|
5646
|
-
const
|
|
5647
|
-
return Object.fromEntries(Object.keys(
|
|
5831
|
+
const record11 = value2;
|
|
5832
|
+
return Object.fromEntries(Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, normalize(record11[key])]));
|
|
5648
5833
|
}
|
|
5649
5834
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
5650
5835
|
}
|
|
@@ -5718,7 +5903,7 @@ function copyRef(ref) {
|
|
|
5718
5903
|
function normalizeReaders(readers) {
|
|
5719
5904
|
if (readers.kind === "public") return Object.freeze({ kind: "public" });
|
|
5720
5905
|
const principalIds = [...new Set(readers.principalIds)].sort();
|
|
5721
|
-
if (principalIds.some((
|
|
5906
|
+
if (principalIds.some((id2) => !id2)) throw new CamelError("reader_mismatch", "Reader principal IDs must be non-empty.");
|
|
5722
5907
|
return Object.freeze({ kind: "principals", principalIds: Object.freeze(principalIds) });
|
|
5723
5908
|
}
|
|
5724
5909
|
|
|
@@ -5728,7 +5913,7 @@ var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
|
5728
5913
|
var ID = /^[A-Za-z0-9._:-]{1,160}$/;
|
|
5729
5914
|
async function digestCodeVerificationReceipt(fields) {
|
|
5730
5915
|
validate(fields);
|
|
5731
|
-
const
|
|
5916
|
+
const canonical2 = {
|
|
5732
5917
|
schemaVersion: fields.schemaVersion,
|
|
5733
5918
|
verificationId: fields.verificationId,
|
|
5734
5919
|
trustedBaseCommitSha: fields.trustedBaseCommitSha,
|
|
@@ -5755,7 +5940,7 @@ async function digestCodeVerificationReceipt(fields) {
|
|
|
5755
5940
|
changedTestsRequireReview: fields.changedTestsRequireReview,
|
|
5756
5941
|
outcome: fields.outcome
|
|
5757
5942
|
};
|
|
5758
|
-
return `sha256:${await sha256Hex(canonicalJson2(
|
|
5943
|
+
return `sha256:${await sha256Hex(canonicalJson2(canonical2))}`;
|
|
5759
5944
|
}
|
|
5760
5945
|
function validate(fields) {
|
|
5761
5946
|
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) {
|
|
@@ -5973,7 +6158,7 @@ async function createConversionRegistry(config) {
|
|
|
5973
6158
|
if (await conversionPolicyDigest(definition) !== policy.digest) throw new CamelError("state_conflict", "Conversion policy digest mismatch.");
|
|
5974
6159
|
if (policy.output.kind === "registered_id") {
|
|
5975
6160
|
const registry = config.registeredIds?.[policy.output.registryId];
|
|
5976
|
-
const validValues = registry && Object.entries(registry.values).every(([candidate,
|
|
6161
|
+
const validValues = registry && Object.entries(registry.values).every(([candidate, id2]) => candidate.length > 0 && typeof id2 === "string" && id2.length > 0);
|
|
5977
6162
|
if (!registry || !validValues || registry.digest !== policy.output.registryDigest || await registeredIdRegistryDigest(registry.values) !== registry.digest) {
|
|
5978
6163
|
throw new CamelError("state_conflict", "Registered-ID registry digest mismatch.");
|
|
5979
6164
|
}
|
|
@@ -5981,63 +6166,63 @@ async function createConversionRegistry(config) {
|
|
|
5981
6166
|
policies.set(policy.conversionId, Object.freeze(policy));
|
|
5982
6167
|
}
|
|
5983
6168
|
const outputCounts = /* @__PURE__ */ new Map();
|
|
5984
|
-
const get = (
|
|
5985
|
-
const policy = policies.get(
|
|
6169
|
+
const get = (id2, kind) => {
|
|
6170
|
+
const policy = policies.get(id2);
|
|
5986
6171
|
if (!policy || policy.output.kind !== kind) throw new CamelError("conversion_rejected", "Conversion policy is missing or has the wrong output kind.");
|
|
5987
6172
|
return policy;
|
|
5988
6173
|
};
|
|
5989
|
-
const checked = (source,
|
|
5990
|
-
const policy = get(
|
|
6174
|
+
const checked = (source, id2, kind) => {
|
|
6175
|
+
const policy = get(id2, kind);
|
|
5991
6176
|
if (utf8Length(source.value) > policy.maximumSourceBytes) throw new CamelError("limit_exceeded", "Unsafe conversion input exceeds its byte bound.");
|
|
5992
6177
|
return policy;
|
|
5993
6178
|
};
|
|
5994
|
-
const
|
|
6179
|
+
const emit4 = (source, policy, value2) => convert(source, policy, value2, outputCounts);
|
|
5995
6180
|
const operations = Object.freeze({
|
|
5996
|
-
boolean: async (value2,
|
|
5997
|
-
const policy = checked(value2,
|
|
5998
|
-
return
|
|
6181
|
+
boolean: async (value2, id2) => {
|
|
6182
|
+
const policy = checked(value2, id2, "boolean");
|
|
6183
|
+
return emit4(value2, policy, requireBoolean(value2.value));
|
|
5999
6184
|
},
|
|
6000
|
-
integer: async (value2,
|
|
6001
|
-
const policy = checked(value2,
|
|
6002
|
-
return
|
|
6185
|
+
integer: async (value2, id2) => {
|
|
6186
|
+
const policy = checked(value2, id2, "integer");
|
|
6187
|
+
return emit4(value2, policy, boundedInteger(value2.value, policy.output));
|
|
6003
6188
|
},
|
|
6004
|
-
finiteNumber: async (value2,
|
|
6005
|
-
const policy = checked(value2,
|
|
6006
|
-
return
|
|
6189
|
+
finiteNumber: async (value2, id2) => {
|
|
6190
|
+
const policy = checked(value2, id2, "finite_number");
|
|
6191
|
+
return emit4(value2, policy, boundedNumber(value2.value, policy.output));
|
|
6007
6192
|
},
|
|
6008
|
-
enum: async (value2,
|
|
6009
|
-
const policy = checked(value2,
|
|
6010
|
-
return
|
|
6193
|
+
enum: async (value2, id2) => {
|
|
6194
|
+
const policy = checked(value2, id2, "enum");
|
|
6195
|
+
return emit4(value2, policy, enumMember(value2.value, policy.output));
|
|
6011
6196
|
},
|
|
6012
|
-
date: async (value2,
|
|
6013
|
-
const policy = checked(value2,
|
|
6014
|
-
return
|
|
6197
|
+
date: async (value2, id2) => {
|
|
6198
|
+
const policy = checked(value2, id2, "date");
|
|
6199
|
+
return emit4(value2, policy, canonicalDate(value2.value, policy.output));
|
|
6015
6200
|
},
|
|
6016
|
-
registeredId: async (value2,
|
|
6017
|
-
const policy = checked(value2,
|
|
6201
|
+
registeredId: async (value2, id2) => {
|
|
6202
|
+
const policy = checked(value2, id2, "registered_id");
|
|
6018
6203
|
const registry = config.registeredIds?.[policy.output.registryId];
|
|
6019
6204
|
const output = typeof value2.value === "string" ? registry?.values[value2.value] : void 0;
|
|
6020
6205
|
if (!output) throw new CamelError("conversion_rejected", "Registered-ID conversion rejected the candidate.");
|
|
6021
|
-
return
|
|
6206
|
+
return emit4(value2, policy, output);
|
|
6022
6207
|
},
|
|
6023
|
-
digest: async (value2,
|
|
6024
|
-
const policy = checked(value2,
|
|
6208
|
+
digest: async (value2, id2) => {
|
|
6209
|
+
const policy = checked(value2, id2, "digest");
|
|
6025
6210
|
if (!(value2.value instanceof Uint8Array)) throw new CamelError("conversion_rejected", "Digest conversion requires bytes.");
|
|
6026
|
-
return
|
|
6211
|
+
return emit4(value2, policy, await sha256Hex(value2.value));
|
|
6027
6212
|
},
|
|
6028
|
-
measure: async (value2, metric,
|
|
6029
|
-
const policy = checked(value2,
|
|
6213
|
+
measure: async (value2, metric, id2) => {
|
|
6214
|
+
const policy = checked(value2, id2, "integer");
|
|
6030
6215
|
const measured = measure(value2.value, metric);
|
|
6031
|
-
return
|
|
6216
|
+
return emit4(value2, policy, boundedInteger(measured, policy.output));
|
|
6032
6217
|
},
|
|
6033
|
-
test: async (value2, predicateId,
|
|
6034
|
-
const policy = checked(value2,
|
|
6218
|
+
test: async (value2, predicateId, id2) => {
|
|
6219
|
+
const policy = checked(value2, id2, "boolean");
|
|
6035
6220
|
const predicate = config.predicates?.[predicateId];
|
|
6036
6221
|
if (!predicate) throw new CamelError("conversion_rejected", "Predicate is not registered.");
|
|
6037
|
-
return
|
|
6222
|
+
return emit4(value2, policy, evaluatePredicate(value2.value, predicate, config.registeredIds));
|
|
6038
6223
|
}
|
|
6039
6224
|
});
|
|
6040
|
-
return Object.freeze({ operations, policy: (
|
|
6225
|
+
return Object.freeze({ operations, policy: (id2) => policies.get(id2) ?? missingPolicy() });
|
|
6041
6226
|
}
|
|
6042
6227
|
async function convert(source, policy, value2, counts) {
|
|
6043
6228
|
const sourceKey = sourceIdentity(source);
|
|
@@ -6080,8 +6265,8 @@ function boundedInteger(value2, spec) {
|
|
|
6080
6265
|
}
|
|
6081
6266
|
function boundedNumber(value2, spec) {
|
|
6082
6267
|
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.");
|
|
6083
|
-
const
|
|
6084
|
-
if (/e/i.test(
|
|
6268
|
+
const text3 = String(value2);
|
|
6269
|
+
if (/e/i.test(text3) || (text3.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
|
|
6085
6270
|
return value2;
|
|
6086
6271
|
}
|
|
6087
6272
|
function enumMember(value2, spec) {
|
|
@@ -6132,10 +6317,10 @@ function createCamelIngress(constants2 = []) {
|
|
|
6132
6317
|
const ingress = {
|
|
6133
6318
|
userInstruction: (value2, input) => createSafeInternal(value2, "user_instruction", metadata("user_instruction", input.id, input.readers)),
|
|
6134
6319
|
systemPolicy: (value2, input) => createSafeInternal(value2, "system_policy", metadata("system_policy", input.id, input.readers)),
|
|
6135
|
-
control: (
|
|
6136
|
-
const item = byId.get(
|
|
6320
|
+
control: (id2) => {
|
|
6321
|
+
const item = byId.get(id2);
|
|
6137
6322
|
if (!item) throw new CamelError("permission_denied", "Unknown control constant.");
|
|
6138
|
-
return createSafeInternal(item.value, "harness_constant", metadata("harness",
|
|
6323
|
+
return createSafeInternal(item.value, "harness_constant", metadata("harness", id2, item.readers));
|
|
6139
6324
|
},
|
|
6140
6325
|
external: (value2, label) => createUnsafeInternal(value2, label),
|
|
6141
6326
|
quarantinedOutput: (value2, input) => {
|
|
@@ -6159,9 +6344,9 @@ function assertNoUnsafeConstant(value2, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
6159
6344
|
}
|
|
6160
6345
|
for (const child of Object.values(value2)) assertNoUnsafeConstant(child, seen);
|
|
6161
6346
|
}
|
|
6162
|
-
function metadata(kind,
|
|
6163
|
-
if (!
|
|
6164
|
-
return { readers, provenance: [{ kind, id }] };
|
|
6347
|
+
function metadata(kind, id2, readers) {
|
|
6348
|
+
if (!id2) throw new CamelError("state_conflict", "Provenance IDs must be non-empty.");
|
|
6349
|
+
return { readers, provenance: [{ kind, id: id2 }] };
|
|
6165
6350
|
}
|
|
6166
6351
|
|
|
6167
6352
|
// ../camel/dist/policy.js
|
|
@@ -6225,7 +6410,7 @@ function isControlOwned(value2) {
|
|
|
6225
6410
|
return value2.label.safeBasis === "system_policy" || value2.label.safeBasis === "harness_constant";
|
|
6226
6411
|
}
|
|
6227
6412
|
function copyRegistries(registries) {
|
|
6228
|
-
return Object.freeze(Object.fromEntries(Object.entries(registries).map(([
|
|
6413
|
+
return Object.freeze(Object.fromEntries(Object.entries(registries).map(([id2, registry]) => [id2, Object.freeze({ digest: registry.digest, values: Object.freeze([...registry.values]) })])));
|
|
6229
6414
|
}
|
|
6230
6415
|
function validateUnsafeSelector(path, value2, tool) {
|
|
6231
6416
|
const policy = tool.unsafeSelectorPolicy;
|
|
@@ -6237,8 +6422,8 @@ function validateUnsafeSelector(path, value2, tool) {
|
|
|
6237
6422
|
return void 0;
|
|
6238
6423
|
}
|
|
6239
6424
|
function looksLikeDestination(value2) {
|
|
6240
|
-
const
|
|
6241
|
-
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(
|
|
6425
|
+
const text3 = value2.trim();
|
|
6426
|
+
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
|
|
6242
6427
|
}
|
|
6243
6428
|
|
|
6244
6429
|
// ../harness/dist/chunk-ANNX7VGK.js
|
|
@@ -6248,9 +6433,9 @@ import { join as join33 } from "path";
|
|
|
6248
6433
|
|
|
6249
6434
|
// ../graph/dist/chunk-PS2SO4UP.js
|
|
6250
6435
|
var nodeId = (kind, name) => `${kind}:${name}`;
|
|
6251
|
-
function parseNodeId(
|
|
6252
|
-
const at =
|
|
6253
|
-
return at < 0 ? { kind: "", name:
|
|
6436
|
+
function parseNodeId(id2) {
|
|
6437
|
+
const at = id2.indexOf(":");
|
|
6438
|
+
return at < 0 ? { kind: "", name: id2 } : { kind: id2.slice(0, at), name: id2.slice(at + 1) };
|
|
6254
6439
|
}
|
|
6255
6440
|
var GraphBuilder = class {
|
|
6256
6441
|
byId = /* @__PURE__ */ new Map();
|
|
@@ -6258,14 +6443,14 @@ var GraphBuilder = class {
|
|
|
6258
6443
|
seen = /* @__PURE__ */ new Set();
|
|
6259
6444
|
/** Add or enrich a node. Later attributes win; the kind never changes. */
|
|
6260
6445
|
node(kind, name, attrs) {
|
|
6261
|
-
const
|
|
6262
|
-
const existing = this.byId.get(
|
|
6446
|
+
const id2 = nodeId(kind, name);
|
|
6447
|
+
const existing = this.byId.get(id2);
|
|
6263
6448
|
if (existing) {
|
|
6264
|
-
if (attrs) this.byId.set(
|
|
6265
|
-
return
|
|
6449
|
+
if (attrs) this.byId.set(id2, { ...existing, attrs: { ...existing.attrs, ...attrs } });
|
|
6450
|
+
return id2;
|
|
6266
6451
|
}
|
|
6267
|
-
this.byId.set(
|
|
6268
|
-
return
|
|
6452
|
+
this.byId.set(id2, { id: id2, kind, name, ...attrs ? { attrs } : {} });
|
|
6453
|
+
return id2;
|
|
6269
6454
|
}
|
|
6270
6455
|
/**
|
|
6271
6456
|
* Add a directed edge, minting either endpoint if it is not known yet.
|
|
@@ -6275,10 +6460,10 @@ var GraphBuilder = class {
|
|
|
6275
6460
|
* by how often someone repeated an import.
|
|
6276
6461
|
*/
|
|
6277
6462
|
edge(from, kind, to, attrs) {
|
|
6278
|
-
for (const
|
|
6279
|
-
if (!this.byId.has(
|
|
6280
|
-
const parsed = parseNodeId(
|
|
6281
|
-
this.byId.set(
|
|
6463
|
+
for (const id2 of [from, to]) {
|
|
6464
|
+
if (!this.byId.has(id2)) {
|
|
6465
|
+
const parsed = parseNodeId(id2);
|
|
6466
|
+
this.byId.set(id2, { id: id2, kind: parsed.kind, name: parsed.name });
|
|
6282
6467
|
}
|
|
6283
6468
|
}
|
|
6284
6469
|
const key = `${from} ${kind} ${to}`;
|
|
@@ -6311,18 +6496,18 @@ function nodesOfKind(graph, kind) {
|
|
|
6311
6496
|
|
|
6312
6497
|
// ../graph/dist/index.js
|
|
6313
6498
|
var follows = (kinds, edge) => !kinds || kinds.includes(edge.kind);
|
|
6314
|
-
function incident(graph,
|
|
6499
|
+
function incident(graph, id2, traversal = {}) {
|
|
6315
6500
|
const direction = traversal.direction ?? "out";
|
|
6316
|
-
const forward = direction === "out" || direction === "both" ? graph.out.get(
|
|
6317
|
-
const backward = direction === "in" || direction === "both" ? graph.in.get(
|
|
6501
|
+
const forward = direction === "out" || direction === "both" ? graph.out.get(id2) ?? [] : [];
|
|
6502
|
+
const backward = direction === "in" || direction === "both" ? graph.in.get(id2) ?? [] : [];
|
|
6318
6503
|
return [...forward, ...backward].filter((edge) => follows(traversal.kinds, edge));
|
|
6319
6504
|
}
|
|
6320
6505
|
var otherEnd = (edge, from) => edge.from === from ? edge.to : edge.from;
|
|
6321
|
-
function neighbors(graph,
|
|
6506
|
+
function neighbors(graph, id2, traversal = {}) {
|
|
6322
6507
|
const seen = /* @__PURE__ */ new Set();
|
|
6323
|
-
for (const edge of incident(graph,
|
|
6324
|
-
const other = otherEnd(edge,
|
|
6325
|
-
if (other !==
|
|
6508
|
+
for (const edge of incident(graph, id2, traversal)) {
|
|
6509
|
+
const other = otherEnd(edge, id2);
|
|
6510
|
+
if (other !== id2) seen.add(other);
|
|
6326
6511
|
}
|
|
6327
6512
|
return [...seen];
|
|
6328
6513
|
}
|
|
@@ -6406,9 +6591,9 @@ async function extractImports(builder, input) {
|
|
|
6406
6591
|
const sources = input.paths.filter(isSourcePath);
|
|
6407
6592
|
const known = new Set(sources);
|
|
6408
6593
|
for (const path of sources) {
|
|
6409
|
-
let
|
|
6594
|
+
let text3;
|
|
6410
6595
|
try {
|
|
6411
|
-
|
|
6596
|
+
text3 = await input.read(path);
|
|
6412
6597
|
} catch {
|
|
6413
6598
|
continue;
|
|
6414
6599
|
}
|
|
@@ -6416,13 +6601,13 @@ async function extractImports(builder, input) {
|
|
|
6416
6601
|
const file = builder.node(FILE, path, pkg ? { pkg } : void 0);
|
|
6417
6602
|
if (pkg) builder.edge(builder.node(PACKAGE, pkg), CONTAINS, file);
|
|
6418
6603
|
const specifiers = /* @__PURE__ */ new Set();
|
|
6419
|
-
for (const match of
|
|
6420
|
-
for (const match of
|
|
6604
|
+
for (const match of text3.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
|
|
6605
|
+
for (const match of text3.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
|
|
6421
6606
|
for (const specifier of specifiers) {
|
|
6422
6607
|
const resolved = resolveImport(path, specifier, known);
|
|
6423
6608
|
if (resolved) builder.edge(file, IMPORTS, nodeId(FILE, resolved));
|
|
6424
6609
|
}
|
|
6425
|
-
for (const name of exportedNames(
|
|
6610
|
+
for (const name of exportedNames(text3)) {
|
|
6426
6611
|
builder.edge(file, EXPORTS, builder.node(SYMBOL, name));
|
|
6427
6612
|
}
|
|
6428
6613
|
}
|
|
@@ -6463,16 +6648,16 @@ async function extractData(builder, input) {
|
|
|
6463
6648
|
};
|
|
6464
6649
|
for (const path of input.paths) {
|
|
6465
6650
|
if (!SOURCE_FILE.test(path) || input.ignore?.(path)) continue;
|
|
6466
|
-
let
|
|
6651
|
+
let text3;
|
|
6467
6652
|
try {
|
|
6468
|
-
|
|
6653
|
+
text3 = await input.read(path);
|
|
6469
6654
|
} catch {
|
|
6470
6655
|
continue;
|
|
6471
6656
|
}
|
|
6472
|
-
for (const statement of
|
|
6657
|
+
for (const statement of text3.matchAll(STATEMENT)) {
|
|
6473
6658
|
const verb = statement[1].toUpperCase().replace(/\s+/g, " ");
|
|
6474
6659
|
const start = statement.index ?? 0;
|
|
6475
|
-
const rest =
|
|
6660
|
+
const rest = text3.slice(start + statement[0].length, start + STATEMENT_WINDOW);
|
|
6476
6661
|
if (verb === "SELECT") {
|
|
6477
6662
|
for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
|
|
6478
6663
|
continue;
|
|
@@ -6488,16 +6673,16 @@ async function extractData(builder, input) {
|
|
|
6488
6673
|
for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
|
|
6489
6674
|
}
|
|
6490
6675
|
}
|
|
6491
|
-
for (const match of
|
|
6492
|
-
touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(
|
|
6676
|
+
for (const match of text3.matchAll(NS_CONST)) {
|
|
6677
|
+
touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text3, match.index ?? 0));
|
|
6493
6678
|
}
|
|
6494
|
-
for (const match of
|
|
6495
|
-
touch(path, match[1], NAMESPACE, accessFor(
|
|
6679
|
+
for (const match of text3.matchAll(NS_LITERAL)) {
|
|
6680
|
+
touch(path, match[1], NAMESPACE, accessFor(text3, match.index ?? 0));
|
|
6496
6681
|
}
|
|
6497
6682
|
}
|
|
6498
6683
|
}
|
|
6499
|
-
function accessFor(
|
|
6500
|
-
const window =
|
|
6684
|
+
function accessFor(text3, index) {
|
|
6685
|
+
const window = text3.slice(Math.max(0, index - 160), index + 40);
|
|
6501
6686
|
return /\b(?:transact|update|delete|create|insert|Ops)\b/.test(window) ? WRITES : READS;
|
|
6502
6687
|
}
|
|
6503
6688
|
async function buildCodeGraph(input) {
|
|
@@ -6628,13 +6813,13 @@ function createCodeRuntimeControlClient(options) {
|
|
|
6628
6813
|
if (!Number.isSafeInteger(modelRequestTimeoutMs) || modelRequestTimeoutMs < 3e4 || modelRequestTimeoutMs > 30 * 6e4) {
|
|
6629
6814
|
throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
|
|
6630
6815
|
}
|
|
6631
|
-
const
|
|
6816
|
+
const request3 = options.fetch ?? fetch;
|
|
6632
6817
|
const call2 = async (path, body, timeoutMs = requestTimeoutMs) => {
|
|
6633
6818
|
const timeout = AbortSignal.timeout(timeoutMs);
|
|
6634
6819
|
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
6635
6820
|
let response2;
|
|
6636
6821
|
try {
|
|
6637
|
-
response2 = await
|
|
6822
|
+
response2 = await request3(`${endpoint}${path}`, {
|
|
6638
6823
|
method: "POST",
|
|
6639
6824
|
headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json" },
|
|
6640
6825
|
body: JSON.stringify(body),
|
|
@@ -6647,7 +6832,7 @@ function createCodeRuntimeControlClient(options) {
|
|
|
6647
6832
|
}
|
|
6648
6833
|
const value2 = await response2.json().catch(() => null);
|
|
6649
6834
|
if (!response2.ok) {
|
|
6650
|
-
const problem =
|
|
6835
|
+
const problem = record5(record5(value2)?.error);
|
|
6651
6836
|
throw new CodeRuntimeControlError(
|
|
6652
6837
|
typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
|
|
6653
6838
|
response2.status,
|
|
@@ -6669,12 +6854,12 @@ function createCodeRuntimeControlClient(options) {
|
|
|
6669
6854
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
6670
6855
|
),
|
|
6671
6856
|
infer: async (sessionId, inference) => {
|
|
6672
|
-
const value2 =
|
|
6857
|
+
const value2 = record5(await call2(
|
|
6673
6858
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
6674
6859
|
inference,
|
|
6675
6860
|
modelRequestTimeoutMs
|
|
6676
6861
|
));
|
|
6677
|
-
if (!value2 || value2.requestId !== inference.requestId || !
|
|
6862
|
+
if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
|
|
6678
6863
|
throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
|
|
6679
6864
|
}
|
|
6680
6865
|
return value2;
|
|
@@ -6742,12 +6927,12 @@ function validateHeartbeat(version, capabilities) {
|
|
|
6742
6927
|
}
|
|
6743
6928
|
}
|
|
6744
6929
|
function parseSnapshot(value2) {
|
|
6745
|
-
const root =
|
|
6746
|
-
const host =
|
|
6930
|
+
const root = record5(value2);
|
|
6931
|
+
const host = record5(root?.host);
|
|
6747
6932
|
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");
|
|
6748
6933
|
const bindingIds = /* @__PURE__ */ new Set();
|
|
6749
6934
|
const bindings = root.bindings.map((item) => {
|
|
6750
|
-
const binding =
|
|
6935
|
+
const binding = record5(item);
|
|
6751
6936
|
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)) {
|
|
6752
6937
|
throw invalid("binding");
|
|
6753
6938
|
}
|
|
@@ -6757,10 +6942,10 @@ function parseSnapshot(value2) {
|
|
|
6757
6942
|
const commandIds = /* @__PURE__ */ new Set();
|
|
6758
6943
|
const commandSequences = /* @__PURE__ */ new Set();
|
|
6759
6944
|
const commands = root.commands.map((item) => {
|
|
6760
|
-
const command =
|
|
6945
|
+
const command = record5(item);
|
|
6761
6946
|
const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
|
|
6762
6947
|
const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
|
|
6763
|
-
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)) || !
|
|
6948
|
+
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");
|
|
6764
6949
|
commandIds.add(command.commandId);
|
|
6765
6950
|
commandSequences.add(sequenceKey);
|
|
6766
6951
|
return command;
|
|
@@ -6768,10 +6953,10 @@ function parseSnapshot(value2) {
|
|
|
6768
6953
|
return { host, bindings, commands };
|
|
6769
6954
|
}
|
|
6770
6955
|
async function parseSource(value2) {
|
|
6771
|
-
const snapshot =
|
|
6956
|
+
const snapshot = record5(record5(value2)?.snapshot);
|
|
6772
6957
|
if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
|
|
6773
6958
|
const files = snapshot.files.map((value22) => {
|
|
6774
|
-
const file =
|
|
6959
|
+
const file = record5(value22);
|
|
6775
6960
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
6776
6961
|
return { path: file.path, content: file.content };
|
|
6777
6962
|
});
|
|
@@ -6780,11 +6965,11 @@ async function parseSource(value2) {
|
|
|
6780
6965
|
const aliases = /* @__PURE__ */ new Set();
|
|
6781
6966
|
const references = [];
|
|
6782
6967
|
for (const item of referencesValue) {
|
|
6783
|
-
const reference =
|
|
6968
|
+
const reference = record5(item);
|
|
6784
6969
|
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");
|
|
6785
6970
|
aliases.add(reference.alias);
|
|
6786
6971
|
const referenceFiles = reference.files.map((entry) => {
|
|
6787
|
-
const file =
|
|
6972
|
+
const file = record5(entry);
|
|
6788
6973
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
|
|
6789
6974
|
return { path: file.path, content: file.content };
|
|
6790
6975
|
});
|
|
@@ -6799,18 +6984,18 @@ async function parseSource(value2) {
|
|
|
6799
6984
|
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
6800
6985
|
}
|
|
6801
6986
|
function parseReview(value2) {
|
|
6802
|
-
const review =
|
|
6987
|
+
const review = record5(record5(value2)?.review);
|
|
6803
6988
|
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");
|
|
6804
6989
|
return review;
|
|
6805
6990
|
}
|
|
6806
6991
|
function parseCandidate(value2) {
|
|
6807
|
-
const candidate =
|
|
6992
|
+
const candidate = record5(record5(value2)?.candidate);
|
|
6808
6993
|
if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
|
|
6809
6994
|
throw invalid("candidate");
|
|
6810
6995
|
}
|
|
6811
6996
|
return { candidateId: candidate.candidateId, status: candidate.status };
|
|
6812
6997
|
}
|
|
6813
|
-
var
|
|
6998
|
+
var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
6814
6999
|
var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
|
|
6815
7000
|
var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
6816
7001
|
var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
@@ -6906,8 +7091,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
6906
7091
|
});
|
|
6907
7092
|
let stderr = "";
|
|
6908
7093
|
child.stderr.setEncoding("utf8");
|
|
6909
|
-
child.stderr.on("data", (
|
|
6910
|
-
if (stderr.length < 4e3) stderr +=
|
|
7094
|
+
child.stderr.on("data", (text3) => {
|
|
7095
|
+
if (stderr.length < 4e3) stderr += text3.slice(0, 4e3);
|
|
6911
7096
|
});
|
|
6912
7097
|
child.once("error", reject);
|
|
6913
7098
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr.trim().slice(0, 500)))));
|
|
@@ -7775,7 +7960,7 @@ async function runCodeAgentAttempt(options) {
|
|
|
7775
7960
|
}
|
|
7776
7961
|
}
|
|
7777
7962
|
async function handleCodeRuntimeInference(input) {
|
|
7778
|
-
const { command, metadata: metadata2, request:
|
|
7963
|
+
const { command, metadata: metadata2, request: request3, state: state2 } = input;
|
|
7779
7964
|
if (state2.tokens >= metadata2.maxTokensPerInteraction) {
|
|
7780
7965
|
if (!state2.noticeEmitted) {
|
|
7781
7966
|
state2.noticeEmitted = true;
|
|
@@ -7788,7 +7973,7 @@ async function handleCodeRuntimeInference(input) {
|
|
|
7788
7973
|
return {
|
|
7789
7974
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
7790
7975
|
type: "inference.response",
|
|
7791
|
-
requestId:
|
|
7976
|
+
requestId: request3.requestId,
|
|
7792
7977
|
response: {
|
|
7793
7978
|
id: `budget:${command.commandId}`,
|
|
7794
7979
|
provider: "openai",
|
|
@@ -7802,9 +7987,9 @@ async function handleCodeRuntimeInference(input) {
|
|
|
7802
7987
|
}
|
|
7803
7988
|
const startedAt = Date.now();
|
|
7804
7989
|
const response2 = await input.control.infer(command.sessionId, {
|
|
7805
|
-
requestId:
|
|
7990
|
+
requestId: request3.requestId,
|
|
7806
7991
|
interactionId: command.commandId,
|
|
7807
|
-
call:
|
|
7992
|
+
call: request3.call
|
|
7808
7993
|
});
|
|
7809
7994
|
state2.tokens += response2.receipt.inputTokens + response2.receipt.outputTokens;
|
|
7810
7995
|
await input.event({
|
|
@@ -7821,14 +8006,14 @@ async function handleCodeRuntimeInference(input) {
|
|
|
7821
8006
|
return {
|
|
7822
8007
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
7823
8008
|
type: "inference.response",
|
|
7824
|
-
requestId:
|
|
8009
|
+
requestId: request3.requestId,
|
|
7825
8010
|
response: response2.response
|
|
7826
8011
|
};
|
|
7827
8012
|
}
|
|
7828
8013
|
function createCodeRuntimeInference(options) {
|
|
7829
8014
|
let seq = 0;
|
|
7830
8015
|
return {
|
|
7831
|
-
chat: async (
|
|
8016
|
+
chat: async (request3) => {
|
|
7832
8017
|
const requestId = `${options.command.commandId}:${++seq}`;
|
|
7833
8018
|
const answer = await handleCodeRuntimeInference({
|
|
7834
8019
|
command: options.command,
|
|
@@ -7840,7 +8025,7 @@ function createCodeRuntimeInference(options) {
|
|
|
7840
8025
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
7841
8026
|
type: "inference.request",
|
|
7842
8027
|
requestId,
|
|
7843
|
-
call:
|
|
8028
|
+
call: request3
|
|
7844
8029
|
}
|
|
7845
8030
|
});
|
|
7846
8031
|
if (answer.type !== "inference.response") throw new TypeError("brokered inference returned the wrong frame");
|
|
@@ -8046,9 +8231,9 @@ async function safePrefix(base, paths, prefix) {
|
|
|
8046
8231
|
function descriptor(name, effect, argumentRoles) {
|
|
8047
8232
|
return { name, version: 1, effect, inputSchema: { type: "object" }, argumentRoles, policyId: `odla.code.${name}.v1` };
|
|
8048
8233
|
}
|
|
8049
|
-
async function conversionPolicy(
|
|
8234
|
+
async function conversionPolicy(id2, output) {
|
|
8050
8235
|
const definition = {
|
|
8051
|
-
conversionId:
|
|
8236
|
+
conversionId: id2,
|
|
8052
8237
|
version: 1,
|
|
8053
8238
|
output,
|
|
8054
8239
|
maximumSourceBytes: 1e6,
|
|
@@ -8057,18 +8242,18 @@ async function conversionPolicy(id, output) {
|
|
|
8057
8242
|
};
|
|
8058
8243
|
return { ...definition, digest: await conversionPolicyDigest(definition) };
|
|
8059
8244
|
}
|
|
8060
|
-
async function registeredPolicy(
|
|
8245
|
+
async function registeredPolicy(id2, registryId, values) {
|
|
8061
8246
|
const mapping = Object.fromEntries(values.map((value2) => [value2, value2]));
|
|
8062
|
-
return conversionPolicy(
|
|
8247
|
+
return conversionPolicy(id2, {
|
|
8063
8248
|
kind: "registered_id",
|
|
8064
8249
|
registryId,
|
|
8065
8250
|
registryDigest: await registeredIdRegistryDigest(mapping)
|
|
8066
8251
|
});
|
|
8067
8252
|
}
|
|
8068
8253
|
async function conversionRegistry(policies, values) {
|
|
8069
|
-
const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([
|
|
8254
|
+
const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([id2, entries]) => {
|
|
8070
8255
|
const mapping = Object.fromEntries(entries.map((value2) => [value2, value2]));
|
|
8071
|
-
return [
|
|
8256
|
+
return [id2, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
|
|
8072
8257
|
})));
|
|
8073
8258
|
return createConversionRegistry({ policies, registeredIds });
|
|
8074
8259
|
}
|
|
@@ -8122,10 +8307,10 @@ function decision(input, policy, approvalConsumed, tool, actionDigest) {
|
|
|
8122
8307
|
actionDigest: actionDigest ?? (policy.outcome === "require_approval" ? policy.actionDigest : "")
|
|
8123
8308
|
};
|
|
8124
8309
|
}
|
|
8125
|
-
function policyContext(context,
|
|
8310
|
+
function policyContext(context, request3, options, extra) {
|
|
8126
8311
|
return {
|
|
8127
8312
|
lease: context.lease,
|
|
8128
|
-
request:
|
|
8313
|
+
request: request3,
|
|
8129
8314
|
workspaceId: `workspace:${context.lease.task.attemptId}`,
|
|
8130
8315
|
readers: { kind: "principals", principalIds: [options.readerId] },
|
|
8131
8316
|
...extra
|
|
@@ -8144,8 +8329,8 @@ function optionalInteger(value2) {
|
|
|
8144
8329
|
if (!Number.isSafeInteger(value2) || value2 < 1) throw new TypeError("line bounds must be positive integers");
|
|
8145
8330
|
return value2;
|
|
8146
8331
|
}
|
|
8147
|
-
function response(
|
|
8148
|
-
return { requestId:
|
|
8332
|
+
function response(request3, ok, content2, details) {
|
|
8333
|
+
return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
|
|
8149
8334
|
}
|
|
8150
8335
|
var cache = /* @__PURE__ */ new Map();
|
|
8151
8336
|
function workspaceGraphs(workspaceDir, paths) {
|
|
@@ -8161,7 +8346,7 @@ function workspaceGraphs(workspaceDir, paths) {
|
|
|
8161
8346
|
cache.set(workspaceDir, built);
|
|
8162
8347
|
return built;
|
|
8163
8348
|
}
|
|
8164
|
-
var shortId = (
|
|
8349
|
+
var shortId = (id2) => id2.slice(id2.indexOf(":") + 1);
|
|
8165
8350
|
function renderOverview(graphs, prefix) {
|
|
8166
8351
|
const rows = rollup(graphs.graph, FILE, prefix === void 0 ? {} : { prefix });
|
|
8167
8352
|
if (rows.length === 0) return prefix ? `No source under "${prefix}".` : "No source files.";
|
|
@@ -8170,19 +8355,19 @@ function renderOverview(graphs, prefix) {
|
|
|
8170
8355
|
return [`${total} source files. Directories, largest first \u2014 read one with sandbox.list --prefix.`, ...lines].join("\n");
|
|
8171
8356
|
}
|
|
8172
8357
|
function renderWhereIs(graphs, symbol) {
|
|
8173
|
-
const sites = neighbors(graphs.graph, nodeId(SYMBOL, symbol), { direction: "in", kinds: ["exports"] }).map((
|
|
8174
|
-
path: shortId(
|
|
8175
|
-
pkg: neighbors(graphs.graph,
|
|
8176
|
-
dependents: incident(graphs.graph,
|
|
8358
|
+
const sites = neighbors(graphs.graph, nodeId(SYMBOL, symbol), { direction: "in", kinds: ["exports"] }).map((id2) => ({
|
|
8359
|
+
path: shortId(id2),
|
|
8360
|
+
pkg: neighbors(graphs.graph, id2, { direction: "in", kinds: ["contains"] })[0],
|
|
8361
|
+
dependents: incident(graphs.graph, id2, { direction: "in", kinds: [IMPORTS] }).length
|
|
8177
8362
|
})).sort((left, right) => right.dependents - left.dependents || left.path.localeCompare(right.path));
|
|
8178
8363
|
if (sites.length === 0) return `No exported symbol named "${symbol}". Try sandbox.search for a textual match.`;
|
|
8179
8364
|
return sites.slice(0, 20).map((site) => `${site.path}${site.pkg ? ` [${shortId(site.pkg)}]` : ""} ${site.dependents} dependents`).join("\n");
|
|
8180
8365
|
}
|
|
8181
8366
|
function renderWhoImports(graphs, path) {
|
|
8182
|
-
const
|
|
8183
|
-
const importers = neighbors(graphs.graph,
|
|
8367
|
+
const id2 = nodeId(FILE, path);
|
|
8368
|
+
const importers = neighbors(graphs.graph, id2, { direction: "in", kinds: [IMPORTS] });
|
|
8184
8369
|
if (importers.length === 0) {
|
|
8185
|
-
return graphs.graph.nodes.has(
|
|
8370
|
+
return graphs.graph.nodes.has(id2) ? `Nothing imports ${path}. It is a leaf.` : `${path} is not a source file in this workspace.`;
|
|
8186
8371
|
}
|
|
8187
8372
|
return importers.slice(0, 40).map(shortId).sort().join("\n");
|
|
8188
8373
|
}
|
|
@@ -8205,11 +8390,11 @@ var GRAPH_TOOLS = /* @__PURE__ */ new Set([
|
|
|
8205
8390
|
"sandbox.who_imports",
|
|
8206
8391
|
"sandbox.who_touches"
|
|
8207
8392
|
]);
|
|
8208
|
-
async function read(context,
|
|
8209
|
-
exactKeys(
|
|
8210
|
-
const path = stringField(
|
|
8211
|
-
const startLine = optionalInteger(
|
|
8212
|
-
const endLine = optionalInteger(
|
|
8393
|
+
async function read(context, request3, options, policy) {
|
|
8394
|
+
exactKeys(request3.input, ["path", "startLine", "endLine"]);
|
|
8395
|
+
const path = stringField(request3.input, "path");
|
|
8396
|
+
const startLine = optionalInteger(request3.input.startLine) ?? 1;
|
|
8397
|
+
const endLine = optionalInteger(request3.input.endLine) ?? startLine + (options.maxReadLines ?? 2e3) - 1;
|
|
8213
8398
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
8214
8399
|
throw new TypeError("requested line range exceeds its bound");
|
|
8215
8400
|
}
|
|
@@ -8217,8 +8402,8 @@ async function read(context, request2, options, policy) {
|
|
|
8217
8402
|
if (!paths.includes(path)) {
|
|
8218
8403
|
throw new TypeError(`no such file in the staged workspace: "${path}". Use sandbox.overview, sandbox.where_is or sandbox.search to find the correct path.`);
|
|
8219
8404
|
}
|
|
8220
|
-
const allowed = await policy.read(policyContext(context,
|
|
8221
|
-
if (!allowed) return response(
|
|
8405
|
+
const allowed = await policy.read(policyContext(context, request3, options, { paths, path, startLine, endLine }));
|
|
8406
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8222
8407
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
8223
8408
|
const info = await stat2(target);
|
|
8224
8409
|
if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
|
|
@@ -8231,74 +8416,74 @@ async function read(context, request2, options, policy) {
|
|
|
8231
8416
|
if (Buffer.byteLength(content2) > (options.maxReadBytes ?? 128 * 1024)) {
|
|
8232
8417
|
throw new TypeError("read result exceeds its byte bound");
|
|
8233
8418
|
}
|
|
8234
|
-
return response(
|
|
8419
|
+
return response(request3, true, content2, { path, startLine, endLine: Math.min(endLine, lines.length) });
|
|
8235
8420
|
}
|
|
8236
|
-
async function list(context,
|
|
8237
|
-
exactKeys(
|
|
8238
|
-
const raw =
|
|
8421
|
+
async function list(context, request3, options, policy) {
|
|
8422
|
+
exactKeys(request3.input, ["prefix", "maxEntries"]);
|
|
8423
|
+
const raw = request3.input.prefix;
|
|
8239
8424
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
8240
|
-
const maxEntries = optionalInteger(
|
|
8425
|
+
const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
|
|
8241
8426
|
if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
|
|
8242
8427
|
const paths = await registeredFiles(context.workspaceDir, 2e4);
|
|
8243
|
-
const allowed = await policy.list(policyContext(context,
|
|
8244
|
-
if (!allowed) return response(
|
|
8428
|
+
const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
|
|
8429
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8245
8430
|
const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
|
|
8246
8431
|
if (!entries.length) {
|
|
8247
|
-
return response(
|
|
8432
|
+
return response(request3, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
|
|
8248
8433
|
}
|
|
8249
8434
|
const truncated = entries.length < paths.length && entries.length === maxEntries;
|
|
8250
8435
|
const hint = !prefix && paths.length > 500 ? `
|
|
8251
8436
|
\u2026 ${paths.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
|
|
8252
8437
|
return response(
|
|
8253
|
-
|
|
8438
|
+
request3,
|
|
8254
8439
|
true,
|
|
8255
8440
|
`${entries.join("\n")}${truncated ? `
|
|
8256
8441
|
\u2026 truncated at ${maxEntries} entries` : ""}${hint}`,
|
|
8257
8442
|
{ count: entries.length, truncated }
|
|
8258
8443
|
);
|
|
8259
8444
|
}
|
|
8260
|
-
async function search(context,
|
|
8261
|
-
exactKeys(
|
|
8262
|
-
const query = stringField(
|
|
8445
|
+
async function search(context, request3, options, policy) {
|
|
8446
|
+
exactKeys(request3.input, ["query", "prefix", "maxResults", "caseSensitive"]);
|
|
8447
|
+
const query = stringField(request3.input, "query");
|
|
8263
8448
|
if (query.length > 512) throw new TypeError("search query exceeds its bound");
|
|
8264
|
-
const raw =
|
|
8449
|
+
const raw = request3.input.prefix;
|
|
8265
8450
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
8266
|
-
const maxResults = optionalInteger(
|
|
8451
|
+
const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
|
|
8267
8452
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
8268
|
-
const caseSensitive =
|
|
8453
|
+
const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
|
|
8269
8454
|
const paths = await registeredFiles(context.workspaceDir, 2e4);
|
|
8270
|
-
const allowed = await policy.search(policyContext(context,
|
|
8271
|
-
if (!allowed) return response(
|
|
8455
|
+
const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
|
|
8456
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8272
8457
|
const matches = await searchWorkspace(context.workspaceDir, paths, {
|
|
8273
8458
|
query,
|
|
8274
8459
|
maxResults,
|
|
8275
8460
|
caseSensitive,
|
|
8276
8461
|
...prefix ? { prefix } : {}
|
|
8277
8462
|
});
|
|
8278
|
-
if (!matches.length) return response(
|
|
8279
|
-
return response(
|
|
8463
|
+
if (!matches.length) return response(request3, true, `No match for "${query}".`, { count: 0 });
|
|
8464
|
+
return response(request3, true, matches.map((match) => `${match.path}:${match.line}: ${match.text}`).join("\n"), {
|
|
8280
8465
|
count: matches.length
|
|
8281
8466
|
});
|
|
8282
8467
|
}
|
|
8283
|
-
async function graphQuery(context,
|
|
8284
|
-
exactKeys(
|
|
8285
|
-
const raw =
|
|
8468
|
+
async function graphQuery(context, request3, options, policy) {
|
|
8469
|
+
exactKeys(request3.input, ["query"]);
|
|
8470
|
+
const raw = request3.input.query;
|
|
8286
8471
|
const query = typeof raw === "string" ? raw : "";
|
|
8287
8472
|
if (query.length > 512) throw new TypeError("query exceeds its bound");
|
|
8288
|
-
const allowed = await policy.graph(policyContext(context,
|
|
8289
|
-
tool:
|
|
8473
|
+
const allowed = await policy.graph(policyContext(context, request3, options, {
|
|
8474
|
+
tool: request3.tool,
|
|
8290
8475
|
selector: query
|
|
8291
8476
|
}));
|
|
8292
|
-
if (!allowed) return response(
|
|
8477
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8293
8478
|
const paths = await registeredFiles(context.workspaceDir, 2e4);
|
|
8294
8479
|
const graphs = await workspaceGraphs(context.workspaceDir, paths);
|
|
8295
|
-
if (
|
|
8296
|
-
return response(
|
|
8480
|
+
if (request3.tool === "sandbox.overview") {
|
|
8481
|
+
return response(request3, true, renderOverview(graphs, query || void 0));
|
|
8297
8482
|
}
|
|
8298
|
-
if (!query) throw new TypeError(`${
|
|
8299
|
-
if (
|
|
8300
|
-
if (
|
|
8301
|
-
return response(
|
|
8483
|
+
if (!query) throw new TypeError(`${request3.tool} requires a query`);
|
|
8484
|
+
if (request3.tool === "sandbox.where_is") return response(request3, true, renderWhereIs(graphs, query));
|
|
8485
|
+
if (request3.tool === "sandbox.who_imports") return response(request3, true, renderWhoImports(graphs, query));
|
|
8486
|
+
return response(request3, true, renderWhoTouches(graphs, query));
|
|
8302
8487
|
}
|
|
8303
8488
|
function createCodeToolBroker(options) {
|
|
8304
8489
|
validateOptions(options);
|
|
@@ -8306,24 +8491,24 @@ function createCodeToolBroker(options) {
|
|
|
8306
8491
|
const policy = createCodePolicyGate(options);
|
|
8307
8492
|
let tail = Promise.resolve();
|
|
8308
8493
|
return {
|
|
8309
|
-
execute(context,
|
|
8310
|
-
const result = tail.then(() =>
|
|
8494
|
+
execute(context, request3) {
|
|
8495
|
+
const result = tail.then(() => route2(context, request3, options, recipes, policy));
|
|
8311
8496
|
tail = result.then(() => void 0, () => void 0);
|
|
8312
8497
|
return result;
|
|
8313
8498
|
}
|
|
8314
8499
|
};
|
|
8315
8500
|
}
|
|
8316
|
-
async function
|
|
8501
|
+
async function route2(context, request3, options, recipes, policy) {
|
|
8317
8502
|
try {
|
|
8318
8503
|
if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
|
|
8319
|
-
if (
|
|
8320
|
-
if (
|
|
8321
|
-
if (
|
|
8322
|
-
if (GRAPH_TOOLS.has(
|
|
8323
|
-
if (
|
|
8324
|
-
return await recipe(context,
|
|
8504
|
+
if (request3.tool === "sandbox.read") return await read(context, request3, options, policy);
|
|
8505
|
+
if (request3.tool === "sandbox.list") return await list(context, request3, options, policy);
|
|
8506
|
+
if (request3.tool === "sandbox.search") return await search(context, request3, options, policy);
|
|
8507
|
+
if (GRAPH_TOOLS.has(request3.tool)) return await graphQuery(context, request3, options, policy);
|
|
8508
|
+
if (request3.tool === "sandbox.apply_patch") return await patch(context, request3, options, policy);
|
|
8509
|
+
return await recipe(context, request3, options, recipes, policy);
|
|
8325
8510
|
} catch (reason) {
|
|
8326
|
-
return response(
|
|
8511
|
+
return response(request3, false, toolFailureMessage(reason));
|
|
8327
8512
|
}
|
|
8328
8513
|
}
|
|
8329
8514
|
function toolFailureMessage(reason) {
|
|
@@ -8335,34 +8520,34 @@ function toolFailureMessage(reason) {
|
|
|
8335
8520
|
if (code === "EACCES" || code === "EPERM") return "that path is not readable through this tool";
|
|
8336
8521
|
return "tool failed closed";
|
|
8337
8522
|
}
|
|
8338
|
-
async function patch(context,
|
|
8339
|
-
exactKeys(
|
|
8340
|
-
const value2 = stringField(
|
|
8523
|
+
async function patch(context, request3, options, policy) {
|
|
8524
|
+
exactKeys(request3.input, ["patch"]);
|
|
8525
|
+
const value2 = stringField(request3.input, "patch");
|
|
8341
8526
|
const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
|
|
8342
8527
|
if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
8343
8528
|
throw new TypeError("patch targets a read-only reference source");
|
|
8344
8529
|
}
|
|
8345
|
-
const allowed = await policy.patch(policyContext(context,
|
|
8346
|
-
if (!allowed) return response(
|
|
8530
|
+
const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
|
|
8531
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8347
8532
|
await applyCodePatch(context.workspaceDir, value2, paths);
|
|
8348
|
-
return response(
|
|
8533
|
+
return response(request3, true, `Applied patch to ${paths.length} file(s).`, { paths });
|
|
8349
8534
|
}
|
|
8350
|
-
async function recipe(context,
|
|
8351
|
-
exactKeys(
|
|
8352
|
-
const recipeId = stringField(
|
|
8535
|
+
async function recipe(context, request3, options, recipes, policy) {
|
|
8536
|
+
exactKeys(request3.input, ["recipeId"]);
|
|
8537
|
+
const recipeId = stringField(request3.input, "recipeId");
|
|
8353
8538
|
const digestLimits = {
|
|
8354
8539
|
maxFiles: options.maxRecipeWorkspaceFiles ?? 2e4,
|
|
8355
8540
|
maxBytes: options.maxRecipeWorkspaceBytes ?? 512 * 1024 * 1024
|
|
8356
8541
|
};
|
|
8357
8542
|
const sourceDigest = await digestStagedWorkspace(context.workspaceDir, digestLimits);
|
|
8358
|
-
const allowed = await policy.recipe(policyContext(context,
|
|
8543
|
+
const allowed = await policy.recipe(policyContext(context, request3, options, {
|
|
8359
8544
|
recipeIds: [...recipes.keys()].sort(),
|
|
8360
8545
|
recipeId,
|
|
8361
8546
|
sourceDigest
|
|
8362
8547
|
}));
|
|
8363
|
-
if (!allowed) return response(
|
|
8548
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8364
8549
|
const selected = recipes.get(recipeId);
|
|
8365
|
-
if (!selected) return response(
|
|
8550
|
+
if (!selected) return response(request3, false, "build recipe is not registered");
|
|
8366
8551
|
const staged = await stageWorkspace(context.workspaceDir, {
|
|
8367
8552
|
maxFiles: digestLimits.maxFiles,
|
|
8368
8553
|
maxBytes: digestLimits.maxBytes
|
|
@@ -8379,7 +8564,7 @@ async function recipe(context, request2, options, recipes, policy) {
|
|
|
8379
8564
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
8380
8565
|
const ok = result.exitCode === 0 && !result.outputLimitExceeded && !result.timedOut;
|
|
8381
8566
|
const status = result.timedOut ? "timed out" : result.outputLimitExceeded ? "exceeded output limit" : ok ? "passed" : `failed with exit ${result.exitCode}`;
|
|
8382
|
-
return response(
|
|
8567
|
+
return response(request3, ok, `Recipe ${recipeId} ${status}.${output ? `
|
|
8383
8568
|
${output}` : ""}`, {
|
|
8384
8569
|
recipeId,
|
|
8385
8570
|
exitCode: result.exitCode,
|
|
@@ -8429,7 +8614,7 @@ async function runGoal(spec, attempt) {
|
|
|
8429
8614
|
const startedAt = now();
|
|
8430
8615
|
const attempts = [];
|
|
8431
8616
|
const boardErrors = [];
|
|
8432
|
-
const
|
|
8617
|
+
const emit4 = async (event) => {
|
|
8433
8618
|
if (!spec.onEvent) return;
|
|
8434
8619
|
try {
|
|
8435
8620
|
await spec.onEvent(event);
|
|
@@ -8442,7 +8627,7 @@ async function runGoal(spec, attempt) {
|
|
|
8442
8627
|
let costKnown = false;
|
|
8443
8628
|
const finish2 = async (stoppedReason) => {
|
|
8444
8629
|
const met = stoppedReason === "proof_passed";
|
|
8445
|
-
await
|
|
8630
|
+
await emit4(met ? { type: "goal_met", attempts: attempts.length, tokens, ...costKnown ? { costUsd } : {} } : {
|
|
8446
8631
|
type: "goal_abandoned",
|
|
8447
8632
|
reason: stoppedReason,
|
|
8448
8633
|
attempts: attempts.length,
|
|
@@ -8463,7 +8648,7 @@ async function runGoal(spec, attempt) {
|
|
|
8463
8648
|
if (spec.signal?.aborted) return finish2("cancelled");
|
|
8464
8649
|
if (spec.budget.deadline !== void 0 && now() >= spec.budget.deadline) return finish2("deadline");
|
|
8465
8650
|
const prompt = index === 1 ? openingPrompt(spec) : retryPrompt(spec, attempts.at(-1));
|
|
8466
|
-
await
|
|
8651
|
+
await emit4({ type: "attempt_started", attempt: index, prompt });
|
|
8467
8652
|
const outcome = await attempt({
|
|
8468
8653
|
attempt: index,
|
|
8469
8654
|
prompt,
|
|
@@ -8483,7 +8668,7 @@ async function runGoal(spec, attempt) {
|
|
|
8483
8668
|
...outcome.error === void 0 ? {} : { error: outcome.error }
|
|
8484
8669
|
});
|
|
8485
8670
|
if (outcome.gatePassed) return finish2("proof_passed");
|
|
8486
|
-
await
|
|
8671
|
+
await emit4({
|
|
8487
8672
|
type: "attempt_failed",
|
|
8488
8673
|
attempt: index,
|
|
8489
8674
|
feedback: outcome.feedback,
|
|
@@ -8532,7 +8717,7 @@ function createCodeRuntimeToolBroker(input, lease, role) {
|
|
|
8532
8717
|
readerId: `code-session:${lease.task.taskId}`,
|
|
8533
8718
|
readOnlyPrefixes: [".odla-references"]
|
|
8534
8719
|
});
|
|
8535
|
-
return role === "coding" ? broker : { execute: (context,
|
|
8720
|
+
return role === "coding" ? broker : { execute: (context, request3) => request3.tool === "sandbox.read" ? broker.execute(context, request3) : Promise.resolve({ requestId: request3.requestId, ok: false, content: "review sessions are read-only" }) };
|
|
8536
8721
|
}
|
|
8537
8722
|
var POSITIVE = (value2) => Number.isFinite(value2) && Number(value2) > 0 ? Number(value2) : void 0;
|
|
8538
8723
|
function codeGoalSpec(payload) {
|
|
@@ -8920,18 +9105,18 @@ var CodePiRuntimeEngine = class {
|
|
|
8920
9105
|
/** Report every brokered effect as it starts and finishes. */
|
|
8921
9106
|
#observed(command, active, broker) {
|
|
8922
9107
|
return {
|
|
8923
|
-
execute: async (context,
|
|
9108
|
+
execute: async (context, request3) => {
|
|
8924
9109
|
const startedAt = Date.now();
|
|
8925
9110
|
await this.#event(
|
|
8926
9111
|
command,
|
|
8927
|
-
{ type: "tool", phase: "started", tool:
|
|
9112
|
+
{ type: "tool", phase: "started", tool: request3.tool },
|
|
8928
9113
|
active.conversationRefs
|
|
8929
9114
|
).catch(() => void 0);
|
|
8930
|
-
const response2 = await broker.execute(context,
|
|
9115
|
+
const response2 = await broker.execute(context, request3);
|
|
8931
9116
|
await this.#event(command, {
|
|
8932
9117
|
type: "tool",
|
|
8933
9118
|
phase: "completed",
|
|
8934
|
-
tool:
|
|
9119
|
+
tool: request3.tool,
|
|
8935
9120
|
ok: response2.ok,
|
|
8936
9121
|
durationMs: Date.now() - startedAt
|
|
8937
9122
|
}, active.conversationRefs).catch(() => void 0);
|
|
@@ -9071,7 +9256,7 @@ async function waitForHostedPoll(milliseconds, signal) {
|
|
|
9071
9256
|
}
|
|
9072
9257
|
function isValidHostedSecurityPlan(value2, env) {
|
|
9073
9258
|
if (!value2 || value2.env !== env || !/^sha256:[a-f0-9]{64}$/.test(value2.planDigest) || value2.consentContract !== "odla.hosted-security-consent.v1" || value2.reportProjection !== "odla.hosted-security-report.v1" || value2.redactionContract !== "odla.best-effort-credential-pattern-redaction.v1" || typeof value2.promptBundle !== "string" || value2.promptBundle.length < 1 || value2.promptBundle.length > 100 || typeof value2.ready !== "boolean" || typeof value2.independent !== "boolean" || value2.sourceDisclosure !== "redacted" || value2.targetExecution !== false || !Number.isSafeInteger(value2.reportRetentionDays) || value2.reportRetentionDays < 1) return false;
|
|
9074
|
-
const validRoute = (
|
|
9259
|
+
const validRoute = (route3, purpose) => !!route3 && route3.purpose === purpose && typeof route3.enabled === "boolean" && typeof route3.credentialReady === "boolean" && typeof route3.provider === "string" && route3.provider.length > 0 && route3.provider.length <= 100 && typeof route3.model === "string" && route3.model.length > 0 && route3.model.length <= 200 && Number.isSafeInteger(route3.policyVersion) && route3.policyVersion >= 1 && Number.isSafeInteger(route3.maxCallsPerRun) && route3.maxCallsPerRun >= 1 && Number.isSafeInteger(route3.maxInputBytes) && route3.maxInputBytes >= 1 && Number.isSafeInteger(route3.maxOutputTokens) && route3.maxOutputTokens >= 1;
|
|
9075
9260
|
return validRoute(value2.routes?.discovery, "security.discovery") && validRoute(value2.routes?.validation, "security.validation");
|
|
9076
9261
|
}
|
|
9077
9262
|
function hostedSecurityCredential(value2) {
|
|
@@ -9420,20 +9605,20 @@ async function runCodeRuntime(input) {
|
|
|
9420
9605
|
}
|
|
9421
9606
|
}
|
|
9422
9607
|
function parseConnection(value2, appId, appEnv) {
|
|
9423
|
-
const root =
|
|
9424
|
-
const host =
|
|
9425
|
-
const offer =
|
|
9426
|
-
const binding =
|
|
9608
|
+
const root = record6(value2);
|
|
9609
|
+
const host = record6(root?.host);
|
|
9610
|
+
const offer = record6(root?.offer);
|
|
9611
|
+
const binding = record6(root?.binding);
|
|
9427
9612
|
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)) {
|
|
9428
9613
|
throw new Error("connect Code host returned an invalid response");
|
|
9429
9614
|
}
|
|
9430
9615
|
return root;
|
|
9431
9616
|
}
|
|
9432
9617
|
function apiFailure(action2, status, value2) {
|
|
9433
|
-
const message2 =
|
|
9618
|
+
const message2 = record6(record6(value2)?.error)?.message;
|
|
9434
9619
|
return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
9435
9620
|
}
|
|
9436
|
-
function
|
|
9621
|
+
function record6(value2) {
|
|
9437
9622
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
9438
9623
|
}
|
|
9439
9624
|
|
|
@@ -9652,9 +9837,9 @@ async function credentialCommand(parsed, deps = {}) {
|
|
|
9652
9837
|
}, doFetch, out, { optionalProjectCapabilities: ["app.manage"] });
|
|
9653
9838
|
const base = `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials`;
|
|
9654
9839
|
if (action2 === "revoke") {
|
|
9655
|
-
const
|
|
9656
|
-
if (!
|
|
9657
|
-
const response3 = await doFetch(`${base}/${encodeURIComponent(
|
|
9840
|
+
const id2 = parsed.positionals[2];
|
|
9841
|
+
if (!id2) throw new Error("credentials revoke requires the exact receipt id from credentials list");
|
|
9842
|
+
const response3 = await doFetch(`${base}/${encodeURIComponent(id2)}`, {
|
|
9658
9843
|
method: "DELETE",
|
|
9659
9844
|
headers: { authorization: `Bearer ${token}` }
|
|
9660
9845
|
});
|
|
@@ -9762,6 +9947,12 @@ Usage:
|
|
|
9762
9947
|
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
9763
9948
|
odla-ai context remove <name> --yes [--json]
|
|
9764
9949
|
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
9950
|
+
odla-ai monitor plan [--config odla.config.mjs] [--env prod] [--json]
|
|
9951
|
+
odla-ai monitor apply [--config odla.config.mjs] [--env prod] [--json] [--yes]
|
|
9952
|
+
odla-ai monitor run <probe-id> [--app <id>] [--env prod] [--json]
|
|
9953
|
+
odla-ai monitor status [--app <id>] [--context <name>] [--env prod] [--json]
|
|
9954
|
+
odla-ai monitor incidents [--app <id>] [--env prod] [--limit 100] [--runs] [--json]
|
|
9955
|
+
odla-ai monitor report [--app <id>] [--env prod] [--period daily|weekly] [--json]
|
|
9765
9956
|
odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
9766
9957
|
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
9767
9958
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
@@ -9781,8 +9972,8 @@ Usage:
|
|
|
9781
9972
|
odla-ai runbook revert <slug> --version <n> [--app <id>]
|
|
9782
9973
|
odla-ai runbook rm <slug> [--app <id>]
|
|
9783
9974
|
odla-ai capabilities [--json]
|
|
9784
|
-
odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
|
|
9785
|
-
odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
9975
|
+
odla-ai code connect [--env dev|prod] [--email <odla-account>] [--no-open] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
|
|
9976
|
+
odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--no-open] [--json]
|
|
9786
9977
|
odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
|
|
9787
9978
|
odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
|
|
9788
9979
|
[--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
|
|
@@ -9801,7 +9992,7 @@ Usage:
|
|
|
9801
9992
|
odla-ai security report <job-id> [--json]
|
|
9802
9993
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
9803
9994
|
odla-ai security run [target] --self --ack-redacted-source
|
|
9804
|
-
odla-ai provision [--live] [--config odla.config.mjs] [--email <odla-account>] [--request-grant] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
9995
|
+
odla-ai provision [--live] [--config odla.config.mjs] [--email <odla-account>] [--request-grant] [--no-open] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
9805
9996
|
odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
|
|
9806
9997
|
odla-ai credentials revoke <receipt-id> [--config odla.config.mjs] [--json]
|
|
9807
9998
|
odla-ai smoke [--config odla.config.mjs] [--env dev] [--runtime] [--email <odla-account>] [--no-open]
|
|
@@ -9816,7 +10007,7 @@ function printHelp(output = console) {
|
|
|
9816
10007
|
output.log(`odla-ai
|
|
9817
10008
|
${USAGE_SECTION}
|
|
9818
10009
|
Commands:
|
|
9819
|
-
auth Start a fresh, exact-project agent authorization
|
|
10010
|
+
auth Start a fresh, exact-project agent authorization for human review.
|
|
9820
10011
|
The email is the signed-in odla account, never git or GitHub
|
|
9821
10012
|
identity. The approval screen confirms the agent name first.
|
|
9822
10013
|
agent Inspect durable agent wakeups and explicitly requeue a
|
|
@@ -9899,6 +10090,9 @@ Commands:
|
|
|
9899
10090
|
canary, collector ingest/scheduler trust, Cloudflare-owned
|
|
9900
10091
|
runtime metrics, and a machine verdict.
|
|
9901
10092
|
--json keeps auth progress on stderr for unattended agents.
|
|
10093
|
+
monitor Reconcile checked-in Kitesurf routes, rolling SLOs, spike/trend
|
|
10094
|
+
policies, and email digests; run probes manually and expose
|
|
10095
|
+
stable status, incident, and report JSON to agents and CI.
|
|
9902
10096
|
platform Read canonical fleet health, releases, provider load/freshness,
|
|
9903
10097
|
explicit unknowns, and next actions through a read-only grant.
|
|
9904
10098
|
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
@@ -9943,23 +10137,25 @@ Safety:
|
|
|
9943
10137
|
the metadata file. Flags and specific ODLA_* scope variables beat a selected
|
|
9944
10138
|
context, which beats project config. There is no ambient current context.
|
|
9945
10139
|
"context show" reports only provenance and cache state and never authenticates.
|
|
9946
|
-
Every real CLI handshake prints one canonical /studio?code= approval URL
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
10140
|
+
Every real CLI handshake prints one canonical /studio?code= approval URL.
|
|
10141
|
+
Interactive humans may let the CLI attempt its best-effort browser launch.
|
|
10142
|
+
Agent-driven commands must pass --no-open --wait 600, immediately surface the
|
|
10143
|
+
exact URL and code as a clickable human approval action, and keep that CLI
|
|
10144
|
+
process alive. Wait only on that same process: the CLI owns protocol polling.
|
|
10145
|
+
Do not call OS open, use browser control, curl handshake endpoints, build a
|
|
10146
|
+
shell wait loop, detach the command, or start a substitute handshake. The
|
|
10147
|
+
device code remains only in the running process. If the process exits 75, its
|
|
10148
|
+
old code cannot be collected; a later invocation requests a new code. Older
|
|
10149
|
+
clients' persisted pending state is discarded.
|
|
9955
10150
|
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
9956
10151
|
The email is a non-secret identity hint: never provide a password or session
|
|
9957
10152
|
token. It is the email shown by the signed-in odla account \u2014 never infer it
|
|
9958
10153
|
from git config, a commit author, or GitHub. The matching account must already exist, be signed in, explicitly
|
|
9959
10154
|
review the exact code, and finish any current request before claiming another.
|
|
9960
|
-
Use "auth login --app <id> --email <odla-account>
|
|
9961
|
-
a deliberate fresh request; it ignores cached
|
|
9962
|
-
focused authorization sequence
|
|
10155
|
+
Use "auth login --app <id> --email <odla-account> --no-open --wait 600" when
|
|
10156
|
+
an outside agent needs a deliberate fresh request; it ignores cached
|
|
10157
|
+
credentials and uses the same focused authorization sequence as every
|
|
10158
|
+
first-time command.
|
|
9963
10159
|
If provision reports that the current agent principal has no live app.manage
|
|
9964
10160
|
grant, run it once with --request-grant. That flag ignores ODLA_DEV_TOKEN and
|
|
9965
10161
|
the local cache, prints and opens a fresh exact-project owner-review URL, then
|
|
@@ -10102,7 +10298,7 @@ async function discussList(ctx, parsed) {
|
|
|
10102
10298
|
}
|
|
10103
10299
|
});
|
|
10104
10300
|
}
|
|
10105
|
-
async function discussRead(ctx,
|
|
10301
|
+
async function discussRead(ctx, id2, parsed) {
|
|
10106
10302
|
const requestedLimit = stringOpt(parsed.options.limit);
|
|
10107
10303
|
const requestedOffset = stringOpt(parsed.options.offset);
|
|
10108
10304
|
if (requestedLimit !== void 0 || requestedOffset !== void 0) {
|
|
@@ -10110,7 +10306,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
10110
10306
|
limit: requestedLimit ?? "200",
|
|
10111
10307
|
offset: requestedOffset ?? "0"
|
|
10112
10308
|
});
|
|
10113
|
-
const page2 = await request(ctx, "GET", `/topics/${encodeURIComponent(
|
|
10309
|
+
const page2 = await request(ctx, "GET", `/topics/${encodeURIComponent(id2)}?${query}`);
|
|
10114
10310
|
emit(
|
|
10115
10311
|
ctx,
|
|
10116
10312
|
page2,
|
|
@@ -10130,7 +10326,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
10130
10326
|
const page2 = await request(
|
|
10131
10327
|
ctx,
|
|
10132
10328
|
"GET",
|
|
10133
|
-
`/topics/${encodeURIComponent(
|
|
10329
|
+
`/topics/${encodeURIComponent(id2)}?limit=200&offset=${offset}`
|
|
10134
10330
|
);
|
|
10135
10331
|
topic = page2.topic;
|
|
10136
10332
|
for (const post of page2.posts) posts.set(post.id, post);
|
|
@@ -10172,20 +10368,20 @@ async function discussPost(ctx, parsed) {
|
|
|
10172
10368
|
});
|
|
10173
10369
|
emit(ctx, created, () => ctx.out.log(`opened topic ${created.id}`));
|
|
10174
10370
|
}
|
|
10175
|
-
async function discussReply(ctx,
|
|
10371
|
+
async function discussReply(ctx, id2, parsed) {
|
|
10176
10372
|
const created = await request(
|
|
10177
10373
|
ctx,
|
|
10178
10374
|
"POST",
|
|
10179
|
-
`/topics/${encodeURIComponent(
|
|
10375
|
+
`/topics/${encodeURIComponent(id2)}/replies`,
|
|
10180
10376
|
{ ...content(parsed), mutationId: writeMutationId(parsed) }
|
|
10181
10377
|
);
|
|
10182
10378
|
emit(ctx, created, () => ctx.out.log(`replied ${created.id}`));
|
|
10183
10379
|
}
|
|
10184
|
-
async function discussResolve(ctx,
|
|
10380
|
+
async function discussResolve(ctx, id2, resolved, parsed) {
|
|
10185
10381
|
const result = await request(
|
|
10186
10382
|
ctx,
|
|
10187
10383
|
"PATCH",
|
|
10188
|
-
`/topics/${encodeURIComponent(
|
|
10384
|
+
`/topics/${encodeURIComponent(id2)}`,
|
|
10189
10385
|
{ resolved, mutationId: writeMutationId(parsed) }
|
|
10190
10386
|
);
|
|
10191
10387
|
emit(ctx, result, () => ctx.out.log(`${resolved ? "resolved" : "reopened"} ${result.id}`));
|
|
@@ -10459,9 +10655,9 @@ var ALLOWED = [
|
|
|
10459
10655
|
"context",
|
|
10460
10656
|
"open"
|
|
10461
10657
|
];
|
|
10462
|
-
function requireId(
|
|
10463
|
-
if (!
|
|
10464
|
-
return
|
|
10658
|
+
function requireId(id2, action2) {
|
|
10659
|
+
if (!id2) throw new Error(`"discuss ${action2}" needs a topic id`);
|
|
10660
|
+
return id2;
|
|
10465
10661
|
}
|
|
10466
10662
|
async function buildContext(parsed, deps) {
|
|
10467
10663
|
const context = await resolveOperatorContext(parsed, {
|
|
@@ -10496,7 +10692,7 @@ async function buildContext(parsed, deps) {
|
|
|
10496
10692
|
async function discussCommand(parsed, deps = {}) {
|
|
10497
10693
|
assertArgs(parsed, ALLOWED, 3);
|
|
10498
10694
|
const action2 = parsed.positionals[1];
|
|
10499
|
-
const
|
|
10695
|
+
const id2 = parsed.positionals[2];
|
|
10500
10696
|
if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
|
|
10501
10697
|
const ctx = await buildContext(parsed, deps);
|
|
10502
10698
|
switch (action2) {
|
|
@@ -10506,17 +10702,17 @@ async function discussCommand(parsed, deps = {}) {
|
|
|
10506
10702
|
case "topics":
|
|
10507
10703
|
return discussList(ctx, parsed);
|
|
10508
10704
|
case "read":
|
|
10509
|
-
return discussRead(ctx, requireId(
|
|
10705
|
+
return discussRead(ctx, requireId(id2, "read"), parsed);
|
|
10510
10706
|
case "post":
|
|
10511
10707
|
return discussPost(ctx, parsed);
|
|
10512
10708
|
case "reply":
|
|
10513
|
-
return discussReply(ctx, requireId(
|
|
10709
|
+
return discussReply(ctx, requireId(id2, "reply"), parsed);
|
|
10514
10710
|
case "resolve":
|
|
10515
|
-
return discussResolve(ctx, requireId(
|
|
10711
|
+
return discussResolve(ctx, requireId(id2, "resolve"), parsed.options.reopen !== true, parsed);
|
|
10516
10712
|
case "who":
|
|
10517
10713
|
return discussWho(ctx, parsed);
|
|
10518
10714
|
case "watch": {
|
|
10519
|
-
const result = await discussWatch(ctx,
|
|
10715
|
+
const result = await discussWatch(ctx, id2, parsed);
|
|
10520
10716
|
if (!result.found) throw new WatchTimeoutError(result.cursor);
|
|
10521
10717
|
return;
|
|
10522
10718
|
}
|
|
@@ -10587,8 +10783,8 @@ function collectFields(parsed, allowClear) {
|
|
|
10587
10783
|
if (allowClear) out[spec.key] = null;
|
|
10588
10784
|
continue;
|
|
10589
10785
|
}
|
|
10590
|
-
const
|
|
10591
|
-
out[spec.key] = spec.num ? Number(
|
|
10786
|
+
const text3 = stringOpt(value2);
|
|
10787
|
+
out[spec.key] = spec.num ? Number(text3) : text3;
|
|
10592
10788
|
}
|
|
10593
10789
|
return out;
|
|
10594
10790
|
}
|
|
@@ -10601,17 +10797,17 @@ function collectEntityFields(entity, parsed, allowClear) {
|
|
|
10601
10797
|
if (entity === "task" && fields.column === "ready") fields.column = "todo";
|
|
10602
10798
|
return fields;
|
|
10603
10799
|
}
|
|
10604
|
-
function statusCol(entity,
|
|
10605
|
-
if (entity === "bug") return `${
|
|
10800
|
+
function statusCol(entity, record11) {
|
|
10801
|
+
if (entity === "bug") return `${record11.status ?? ""}/${record11.severity ?? ""}`;
|
|
10606
10802
|
if (entity === "task") {
|
|
10607
|
-
const state2 =
|
|
10608
|
-
return
|
|
10803
|
+
const state2 = record11.column === "todo" ? "ready" : String(record11.column ?? "");
|
|
10804
|
+
return record11.revision ? `${state2}; r${record11.revision}` : state2;
|
|
10609
10805
|
}
|
|
10610
|
-
return String(
|
|
10806
|
+
return String(record11.status ?? "");
|
|
10611
10807
|
}
|
|
10612
|
-
function referenceMarkup(entity,
|
|
10613
|
-
const label = (
|
|
10614
|
-
return `@[${label}](pm:${entity}/${
|
|
10808
|
+
function referenceMarkup(entity, record11) {
|
|
10809
|
+
const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
|
|
10810
|
+
return `@[${label}](pm:${entity}/${record11.id})`;
|
|
10615
10811
|
}
|
|
10616
10812
|
var STUDIO_SECTION = {
|
|
10617
10813
|
goal: "goals",
|
|
@@ -10619,19 +10815,19 @@ var STUDIO_SECTION = {
|
|
|
10619
10815
|
decision: "decisions",
|
|
10620
10816
|
bug: "bugs"
|
|
10621
10817
|
};
|
|
10622
|
-
function studioRecordUrl(ctx, entity,
|
|
10818
|
+
function studioRecordUrl(ctx, entity, id2) {
|
|
10623
10819
|
return new URL(
|
|
10624
|
-
`/studio/pm/${STUDIO_SECTION[entity]}/${encodeURIComponent(
|
|
10820
|
+
`/studio/pm/${STUDIO_SECTION[entity]}/${encodeURIComponent(id2)}`,
|
|
10625
10821
|
ctx.platformUrl
|
|
10626
10822
|
).href;
|
|
10627
10823
|
}
|
|
10628
|
-
function studioRecordLink(ctx, entity,
|
|
10629
|
-
const label = (
|
|
10630
|
-
return `[${label}](${studioRecordUrl(ctx, entity,
|
|
10824
|
+
function studioRecordLink(ctx, entity, record11) {
|
|
10825
|
+
const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
|
|
10826
|
+
return `[${label}](${studioRecordUrl(ctx, entity, record11.id)})`;
|
|
10631
10827
|
}
|
|
10632
|
-
function printRecord(ctx, entity,
|
|
10828
|
+
function printRecord(ctx, entity, record11) {
|
|
10633
10829
|
ctx.out.log(
|
|
10634
|
-
`${
|
|
10830
|
+
`${record11.id} [${statusCol(entity, record11)}] ${record11.appId} ${studioRecordLink(ctx, entity, record11)}`
|
|
10635
10831
|
);
|
|
10636
10832
|
}
|
|
10637
10833
|
function emit2(ctx, value2, human) {
|
|
@@ -10685,52 +10881,52 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
10685
10881
|
input,
|
|
10686
10882
|
mutationId: writeMutationId2(parsed)
|
|
10687
10883
|
});
|
|
10688
|
-
const
|
|
10689
|
-
emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity,
|
|
10884
|
+
const record11 = { id: res.id, appId, title: String(input.title) };
|
|
10885
|
+
emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity, record11)}`));
|
|
10690
10886
|
}
|
|
10691
|
-
async function pmGet(ctx, entity,
|
|
10692
|
-
const { record:
|
|
10693
|
-
emit2(ctx,
|
|
10887
|
+
async function pmGet(ctx, entity, id2) {
|
|
10888
|
+
const { record: record11 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id2)}`);
|
|
10889
|
+
emit2(ctx, record11, () => printRecord(ctx, entity, record11));
|
|
10694
10890
|
}
|
|
10695
|
-
async function pmReference(ctx, entity,
|
|
10696
|
-
const { record:
|
|
10891
|
+
async function pmReference(ctx, entity, id2) {
|
|
10892
|
+
const { record: record11 } = await pmRequest(
|
|
10697
10893
|
ctx,
|
|
10698
10894
|
"GET",
|
|
10699
|
-
`/${entity}/${encodeURIComponent(
|
|
10895
|
+
`/${entity}/${encodeURIComponent(id2)}`
|
|
10700
10896
|
);
|
|
10701
|
-
const markup = referenceMarkup(entity,
|
|
10702
|
-
emit2(ctx, { kind: `pm:${entity}`, id:
|
|
10897
|
+
const markup = referenceMarkup(entity, record11);
|
|
10898
|
+
emit2(ctx, { kind: `pm:${entity}`, id: record11.id, label: record11.title ?? "", markup }, () => {
|
|
10703
10899
|
ctx.out.log(markup);
|
|
10704
10900
|
});
|
|
10705
10901
|
}
|
|
10706
|
-
async function pmSet(ctx, entity,
|
|
10902
|
+
async function pmSet(ctx, entity, id2, parsed) {
|
|
10707
10903
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
10708
10904
|
if (Object.keys(patch2).length === 0)
|
|
10709
10905
|
throw new Error("pm set needs at least one field flag (e.g. --status doing, --assignee me, --no-assignee)");
|
|
10710
|
-
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(
|
|
10906
|
+
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id2)}`, {
|
|
10711
10907
|
patch: patch2,
|
|
10712
10908
|
mutationId: writeMutationId2(parsed)
|
|
10713
10909
|
});
|
|
10714
10910
|
emit2(ctx, res, () => {
|
|
10715
|
-
if (!res.record) return ctx.out.log(`updated ${entity} ${
|
|
10911
|
+
if (!res.record) return ctx.out.log(`updated ${entity} ${id2}`);
|
|
10716
10912
|
ctx.out.log(`${entity}: ${studioRecordLink(ctx, entity, res.record)} \u2192 ${statusCol(entity, res.record)}`);
|
|
10717
10913
|
});
|
|
10718
10914
|
}
|
|
10719
|
-
async function pmDone(ctx, entity,
|
|
10915
|
+
async function pmDone(ctx, entity, id2, parsed) {
|
|
10720
10916
|
const decisionId = stringOpt(parsed.options.decision);
|
|
10721
10917
|
if (decisionId && entity !== "bug") throw new Error("--decision is only valid when completing a bug");
|
|
10722
10918
|
const patch2 = { ...DONE[entity], ...decisionId ? { decisionId } : {} };
|
|
10723
|
-
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(
|
|
10919
|
+
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id2)}`, {
|
|
10724
10920
|
patch: patch2,
|
|
10725
10921
|
mutationId: writeMutationId2(parsed)
|
|
10726
10922
|
});
|
|
10727
10923
|
emit2(ctx, res, () => {
|
|
10728
|
-
const label = res.record ? studioRecordLink(ctx, entity, res.record) :
|
|
10924
|
+
const label = res.record ? studioRecordLink(ctx, entity, res.record) : id2;
|
|
10729
10925
|
const state2 = res.record ? statusCol(entity, res.record) : "done";
|
|
10730
10926
|
ctx.out.log(`${entity}: ${label} \u2192 ${state2}`);
|
|
10731
10927
|
});
|
|
10732
10928
|
}
|
|
10733
|
-
async function pmTaskLifecycle(ctx,
|
|
10929
|
+
async function pmTaskLifecycle(ctx, id2, action2, parsed) {
|
|
10734
10930
|
const rawRevision = stringOpt(parsed.options["expected-revision"]);
|
|
10735
10931
|
const expectedRevision = Number(rawRevision);
|
|
10736
10932
|
if (!rawRevision || !Number.isSafeInteger(expectedRevision) || expectedRevision < 1) {
|
|
@@ -10740,7 +10936,7 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
|
|
|
10740
10936
|
const res = action2 === "ready" ? await pmRequest(
|
|
10741
10937
|
ctx,
|
|
10742
10938
|
"PATCH",
|
|
10743
|
-
`/task/${encodeURIComponent(
|
|
10939
|
+
`/task/${encodeURIComponent(id2)}`,
|
|
10744
10940
|
{
|
|
10745
10941
|
patch: {
|
|
10746
10942
|
...collectEntityFields("task", parsed, true),
|
|
@@ -10752,12 +10948,12 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
|
|
|
10752
10948
|
) : await pmRequest(
|
|
10753
10949
|
ctx,
|
|
10754
10950
|
"POST",
|
|
10755
|
-
`/task/${encodeURIComponent(
|
|
10951
|
+
`/task/${encodeURIComponent(id2)}/${action2}`,
|
|
10756
10952
|
{ expectedRevision, mutationId }
|
|
10757
10953
|
);
|
|
10758
10954
|
emit2(ctx, res, () => {
|
|
10759
10955
|
const state2 = res.record ? statusCol("task", res.record) : action2;
|
|
10760
|
-
const label = res.record ? studioRecordLink(ctx, "task", res.record) :
|
|
10956
|
+
const label = res.record ? studioRecordLink(ctx, "task", res.record) : id2;
|
|
10761
10957
|
ctx.out.log(`task: ${label} \u2192 ${state2}`);
|
|
10762
10958
|
});
|
|
10763
10959
|
}
|
|
@@ -10786,9 +10982,9 @@ async function pmNext(ctx, parsed) {
|
|
|
10786
10982
|
const result = {
|
|
10787
10983
|
appId,
|
|
10788
10984
|
projectId,
|
|
10789
|
-
openGoals: goals.filter((
|
|
10790
|
-
doing: tasks.filter((
|
|
10791
|
-
ready: tasks.filter((
|
|
10985
|
+
openGoals: goals.filter((record11) => record11.status === "open"),
|
|
10986
|
+
doing: tasks.filter((record11) => record11.column === "doing"),
|
|
10987
|
+
ready: tasks.filter((record11) => record11.column === "todo")
|
|
10792
10988
|
};
|
|
10793
10989
|
emit2(ctx, result, () => {
|
|
10794
10990
|
ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
|
|
@@ -10799,10 +10995,10 @@ async function pmNext(ctx, parsed) {
|
|
|
10799
10995
|
]) {
|
|
10800
10996
|
ctx.out.log(`${label}:`);
|
|
10801
10997
|
if (!records.length) ctx.out.log("- (none)");
|
|
10802
|
-
else for (const
|
|
10998
|
+
else for (const record11 of records) printRecord(
|
|
10803
10999
|
ctx,
|
|
10804
11000
|
label === "open goals" ? "goal" : "task",
|
|
10805
|
-
|
|
11001
|
+
record11
|
|
10806
11002
|
);
|
|
10807
11003
|
}
|
|
10808
11004
|
if (!result.openGoals.length) {
|
|
@@ -10826,9 +11022,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
10826
11022
|
const handoff = {
|
|
10827
11023
|
appId,
|
|
10828
11024
|
projectId,
|
|
10829
|
-
unmetGoals: goals.filter((
|
|
10830
|
-
activeTasks: tasks.filter((
|
|
10831
|
-
openBugs: bugs.filter((
|
|
11025
|
+
unmetGoals: goals.filter((record11) => record11.status !== "met"),
|
|
11026
|
+
activeTasks: tasks.filter((record11) => record11.column !== "done"),
|
|
11027
|
+
openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
|
|
10832
11028
|
};
|
|
10833
11029
|
const result = {
|
|
10834
11030
|
...handoff,
|
|
@@ -10847,45 +11043,45 @@ async function pmHandoff(ctx, parsed) {
|
|
|
10847
11043
|
]) {
|
|
10848
11044
|
ctx.out.log(`${label}:`);
|
|
10849
11045
|
if (!records.length) ctx.out.log("- (none)");
|
|
10850
|
-
else for (const
|
|
11046
|
+
else for (const record11 of records) printRecord(
|
|
10851
11047
|
ctx,
|
|
10852
11048
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
10853
|
-
|
|
11049
|
+
record11
|
|
10854
11050
|
);
|
|
10855
11051
|
}
|
|
10856
11052
|
});
|
|
10857
11053
|
}
|
|
10858
|
-
async function pmRemove(ctx, entity,
|
|
10859
|
-
await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(
|
|
10860
|
-
ctx.out.log(`deleted ${entity} ${
|
|
11054
|
+
async function pmRemove(ctx, entity, id2) {
|
|
11055
|
+
await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
|
|
11056
|
+
ctx.out.log(`deleted ${entity} ${id2}`);
|
|
10861
11057
|
}
|
|
10862
11058
|
|
|
10863
11059
|
// src/pm-links.ts
|
|
10864
|
-
async function pmLink(ctx, entity,
|
|
10865
|
-
const { record:
|
|
11060
|
+
async function pmLink(ctx, entity, id2) {
|
|
11061
|
+
const { record: record11 } = await pmRequest(
|
|
10866
11062
|
ctx,
|
|
10867
11063
|
"GET",
|
|
10868
|
-
`/${entity}/${encodeURIComponent(
|
|
11064
|
+
`/${entity}/${encodeURIComponent(id2)}`
|
|
10869
11065
|
);
|
|
10870
|
-
const url = studioRecordUrl(ctx, entity,
|
|
10871
|
-
const markdown = studioRecordLink(ctx, entity,
|
|
10872
|
-
emit2(ctx, { kind: entity, id:
|
|
11066
|
+
const url = studioRecordUrl(ctx, entity, record11.id);
|
|
11067
|
+
const markdown = studioRecordLink(ctx, entity, record11);
|
|
11068
|
+
emit2(ctx, { kind: entity, id: record11.id, label: record11.title ?? "", url, markdown }, () => {
|
|
10873
11069
|
ctx.out.log(markdown);
|
|
10874
11070
|
});
|
|
10875
11071
|
}
|
|
10876
11072
|
|
|
10877
11073
|
// src/pm-comments.ts
|
|
10878
|
-
async function pmComment(ctx, entity,
|
|
11074
|
+
async function pmComment(ctx, entity, id2, parsed) {
|
|
10879
11075
|
const body = stringOpt(parsed.options.body);
|
|
10880
11076
|
if (!body) throw new Error('pm comment needs --body "..."');
|
|
10881
|
-
await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(
|
|
11077
|
+
await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(id2)}/comments`, {
|
|
10882
11078
|
body,
|
|
10883
11079
|
mutationId: writeMutationId2(parsed)
|
|
10884
11080
|
});
|
|
10885
|
-
ctx.out.log(`commented on ${entity} ${
|
|
11081
|
+
ctx.out.log(`commented on ${entity} ${id2}`);
|
|
10886
11082
|
}
|
|
10887
|
-
async function pmComments(ctx, entity,
|
|
10888
|
-
const { messages } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(
|
|
11083
|
+
async function pmComments(ctx, entity, id2) {
|
|
11084
|
+
const { messages } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id2)}/comments`);
|
|
10889
11085
|
emit2(ctx, messages, () => {
|
|
10890
11086
|
if (messages.length === 0) ctx.out.log("(no comments)");
|
|
10891
11087
|
else for (const message2 of messages) {
|
|
@@ -10903,12 +11099,12 @@ function fieldLine(change) {
|
|
|
10903
11099
|
const before = change.before.length > 60 ? `${change.before.slice(0, 60)}\u2026` : change.before;
|
|
10904
11100
|
return `${change.field} (was: ${before.replace(/\s+/g, " ")})`;
|
|
10905
11101
|
}
|
|
10906
|
-
async function pmHistory(ctx, entity,
|
|
11102
|
+
async function pmHistory(ctx, entity, id2, parsed) {
|
|
10907
11103
|
const limit = numberOpt(parsed.options.limit, "--limit");
|
|
10908
11104
|
const page2 = await pmRequest(
|
|
10909
11105
|
ctx,
|
|
10910
11106
|
"GET",
|
|
10911
|
-
`/${entity}/${encodeURIComponent(
|
|
11107
|
+
`/${entity}/${encodeURIComponent(id2)}/history${limit === void 0 ? "" : `?limit=${limit}`}`
|
|
10912
11108
|
);
|
|
10913
11109
|
emit2(ctx, page2, () => {
|
|
10914
11110
|
if (!page2.entries.length) {
|
|
@@ -10996,16 +11192,16 @@ async function page(ctx, appId, cursor) {
|
|
|
10996
11192
|
}
|
|
10997
11193
|
return data;
|
|
10998
11194
|
}
|
|
10999
|
-
function recordState(
|
|
11000
|
-
if (
|
|
11001
|
-
return String(
|
|
11195
|
+
function recordState(record11) {
|
|
11196
|
+
if (record11.column) return record11.column === "todo" ? "ready" : record11.column;
|
|
11197
|
+
return String(record11.status ?? "");
|
|
11002
11198
|
}
|
|
11003
11199
|
function eventRecord(event) {
|
|
11004
11200
|
return event.payload.payload;
|
|
11005
11201
|
}
|
|
11006
11202
|
function eventLabel(event) {
|
|
11007
|
-
const
|
|
11008
|
-
if (
|
|
11203
|
+
const record11 = eventRecord(event);
|
|
11204
|
+
if (record11) return String(record11.title ?? event.payload.entityId);
|
|
11009
11205
|
const body = event.payload.message?.body?.replace(/\s+/g, " ").trim();
|
|
11010
11206
|
return body || event.payload.entityId;
|
|
11011
11207
|
}
|
|
@@ -11013,10 +11209,10 @@ function report2(ctx, parsed, result) {
|
|
|
11013
11209
|
if (ctx.json) ctx.out.log(JSON.stringify(result, null, 2));
|
|
11014
11210
|
else if (parsed.options.jsonl !== true && result.found) {
|
|
11015
11211
|
for (const event of result.events ?? []) {
|
|
11016
|
-
const
|
|
11017
|
-
const state2 =
|
|
11212
|
+
const record11 = eventRecord(event);
|
|
11213
|
+
const state2 = record11 ? recordState(record11) : "comment";
|
|
11018
11214
|
ctx.out.log(
|
|
11019
|
-
`${event.id} ${event.type} ${state2}${
|
|
11215
|
+
`${event.id} ${event.type} ${state2}${record11?.revision ? `; r${record11.revision}` : ""} ${eventLabel(event)}`
|
|
11020
11216
|
);
|
|
11021
11217
|
}
|
|
11022
11218
|
}
|
|
@@ -11090,8 +11286,8 @@ async function pmWatch(ctx, parsed) {
|
|
|
11090
11286
|
}
|
|
11091
11287
|
firstSuccess = false;
|
|
11092
11288
|
const matching = current.events.filter((event) => {
|
|
11093
|
-
const
|
|
11094
|
-
const state2 =
|
|
11289
|
+
const record11 = eventRecord(event);
|
|
11290
|
+
const state2 = record11 ? recordState(record11).toLowerCase() : "";
|
|
11095
11291
|
return (!entity || event.payload.entityKind === entity) && (!action2 || event.payload.action === action2) && (!wantedState || state2 === wantedState || wantedState === "todo" && state2 === "ready") && (!by || event.actor.id === by) && (!self || event.actor.id !== self);
|
|
11096
11292
|
});
|
|
11097
11293
|
for (const event of matching) {
|
|
@@ -11179,9 +11375,9 @@ async function pmProjectAdd(ctx, parsed) {
|
|
|
11179
11375
|
});
|
|
11180
11376
|
emit2(ctx, result, () => ctx.out.log(`created project: ${result.project.name} (${result.project.id})`));
|
|
11181
11377
|
}
|
|
11182
|
-
async function pmProjectUse(ctx,
|
|
11378
|
+
async function pmProjectUse(ctx, id2) {
|
|
11183
11379
|
if (!ctx.rootDir) throw new Error("pm project use needs a local project directory");
|
|
11184
|
-
const { project } = await pmRequest(ctx, "GET", `/project/${encodeURIComponent(
|
|
11380
|
+
const { project } = await pmRequest(ctx, "GET", `/project/${encodeURIComponent(id2)}`);
|
|
11185
11381
|
if (project.status !== "active") throw new Error(`project ${project.name} is ${project.status}, not active`);
|
|
11186
11382
|
writePmProjectContext(ctx.rootDir, { appId: project.appId, projectId: project.id });
|
|
11187
11383
|
emit2(ctx, project, () => ctx.out.log(`using ${project.appId} / ${project.name} (${project.id}) in this worktree`));
|
|
@@ -11249,9 +11445,9 @@ function allowedOptions(entity, action2) {
|
|
|
11249
11445
|
const entityOptions = action2 === "list" || action2 === "add" || action2 === "set" || action2 === "done" ? ENTITY_OPTIONS[entity][action2] : [];
|
|
11250
11446
|
return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action2], ...entityOptions];
|
|
11251
11447
|
}
|
|
11252
|
-
function requireId2(
|
|
11253
|
-
if (!
|
|
11254
|
-
return
|
|
11448
|
+
function requireId2(id2, action2) {
|
|
11449
|
+
if (!id2) throw new Error(`"pm ... ${action2}" needs an item id`);
|
|
11450
|
+
return id2;
|
|
11255
11451
|
}
|
|
11256
11452
|
async function buildContext2(parsed, deps) {
|
|
11257
11453
|
const context = await resolveOperatorContext(parsed, {
|
|
@@ -11342,34 +11538,34 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
11342
11538
|
throw new Error(`pm ${action2} is only valid for tasks`);
|
|
11343
11539
|
}
|
|
11344
11540
|
const ctx = await buildContext2(parsed, deps);
|
|
11345
|
-
const
|
|
11541
|
+
const id2 = parsed.positionals[3];
|
|
11346
11542
|
switch (action2) {
|
|
11347
11543
|
case "list":
|
|
11348
11544
|
return pmList(ctx, entity, parsed);
|
|
11349
11545
|
case "add":
|
|
11350
11546
|
return pmAdd(ctx, entity, parsed);
|
|
11351
11547
|
case "get":
|
|
11352
|
-
return pmGet(ctx, entity, requireId2(
|
|
11548
|
+
return pmGet(ctx, entity, requireId2(id2, action2));
|
|
11353
11549
|
case "set":
|
|
11354
|
-
return pmSet(ctx, entity, requireId2(
|
|
11550
|
+
return pmSet(ctx, entity, requireId2(id2, action2), parsed);
|
|
11355
11551
|
case "done":
|
|
11356
|
-
return pmDone(ctx, entity, requireId2(
|
|
11552
|
+
return pmDone(ctx, entity, requireId2(id2, action2), parsed);
|
|
11357
11553
|
case "comment":
|
|
11358
|
-
return pmComment(ctx, entity, requireId2(
|
|
11554
|
+
return pmComment(ctx, entity, requireId2(id2, action2), parsed);
|
|
11359
11555
|
case "comments":
|
|
11360
|
-
return pmComments(ctx, entity, requireId2(
|
|
11556
|
+
return pmComments(ctx, entity, requireId2(id2, action2));
|
|
11361
11557
|
case "history":
|
|
11362
|
-
return pmHistory(ctx, entity, requireId2(
|
|
11558
|
+
return pmHistory(ctx, entity, requireId2(id2, action2), parsed);
|
|
11363
11559
|
case "rm":
|
|
11364
|
-
return pmRemove(ctx, entity, requireId2(
|
|
11560
|
+
return pmRemove(ctx, entity, requireId2(id2, action2));
|
|
11365
11561
|
case "link":
|
|
11366
|
-
return pmLink(ctx, entity, requireId2(
|
|
11562
|
+
return pmLink(ctx, entity, requireId2(id2, action2));
|
|
11367
11563
|
case "ref":
|
|
11368
|
-
return pmReference(ctx, entity, requireId2(
|
|
11564
|
+
return pmReference(ctx, entity, requireId2(id2, action2));
|
|
11369
11565
|
case "ready":
|
|
11370
11566
|
case "claim":
|
|
11371
11567
|
case "release":
|
|
11372
|
-
return pmTaskLifecycle(ctx, requireId2(
|
|
11568
|
+
return pmTaskLifecycle(ctx, requireId2(id2, action2), action2, parsed);
|
|
11373
11569
|
}
|
|
11374
11570
|
}
|
|
11375
11571
|
|
|
@@ -11472,17 +11668,17 @@ async function platformStatus(parsed, deps) {
|
|
|
11472
11668
|
}
|
|
11473
11669
|
}
|
|
11474
11670
|
function isPlatformStatus(value2) {
|
|
11475
|
-
if (!
|
|
11476
|
-
if (!
|
|
11477
|
-
if (!
|
|
11671
|
+
if (!record7(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
|
|
11672
|
+
if (!record7(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
|
|
11673
|
+
if (!record7(value2.catalog) || !record7(value2.summary)) return false;
|
|
11478
11674
|
return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
|
|
11479
11675
|
}
|
|
11480
11676
|
function apiMessage(value2) {
|
|
11481
|
-
if (!
|
|
11482
|
-
const error =
|
|
11677
|
+
if (!record7(value2)) return "request failed";
|
|
11678
|
+
const error = record7(value2.error) ? value2.error : value2;
|
|
11483
11679
|
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
11484
11680
|
}
|
|
11485
|
-
function
|
|
11681
|
+
function record7(value2) {
|
|
11486
11682
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
11487
11683
|
}
|
|
11488
11684
|
|
|
@@ -11523,7 +11719,7 @@ function statusVerdict(reads) {
|
|
|
11523
11719
|
severity: "degraded"
|
|
11524
11720
|
});
|
|
11525
11721
|
}
|
|
11526
|
-
const performance =
|
|
11722
|
+
const performance = record8(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
11527
11723
|
if (performance?.status === "unavailable") {
|
|
11528
11724
|
reasons.push({
|
|
11529
11725
|
source: "liveSync",
|
|
@@ -11604,7 +11800,7 @@ function statusVerdict(reads) {
|
|
|
11604
11800
|
reasons
|
|
11605
11801
|
};
|
|
11606
11802
|
}
|
|
11607
|
-
function
|
|
11803
|
+
function record8(value2) {
|
|
11608
11804
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
11609
11805
|
}
|
|
11610
11806
|
function numeric2(value2) {
|
|
@@ -11632,7 +11828,7 @@ function printO11yStatus(status, out) {
|
|
|
11632
11828
|
out.log(
|
|
11633
11829
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
11634
11830
|
);
|
|
11635
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
11831
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record9) : [];
|
|
11636
11832
|
const requests = routes.reduce(
|
|
11637
11833
|
(total, row) => total + numeric3(row.requests),
|
|
11638
11834
|
0
|
|
@@ -11644,39 +11840,39 @@ function printO11yStatus(status, out) {
|
|
|
11644
11840
|
out.log(
|
|
11645
11841
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
11646
11842
|
);
|
|
11647
|
-
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(
|
|
11843
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record9) : [];
|
|
11648
11844
|
out.log(
|
|
11649
11845
|
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
11650
11846
|
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
11651
11847
|
).join(", ") : "none observed"}`
|
|
11652
11848
|
);
|
|
11653
11849
|
out.log(liveSyncLine(status.liveSync));
|
|
11654
|
-
const canaryDurations =
|
|
11850
|
+
const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
11655
11851
|
out.log(
|
|
11656
11852
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
11657
11853
|
);
|
|
11658
|
-
const collectorIngest =
|
|
11659
|
-
const collectorStorage =
|
|
11854
|
+
const collectorIngest = record9(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
11855
|
+
const collectorStorage = record9(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
11660
11856
|
out.log(
|
|
11661
11857
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
11662
11858
|
);
|
|
11663
|
-
const providerMetrics =
|
|
11664
|
-
const providerCapacity =
|
|
11665
|
-
const workerMemory =
|
|
11859
|
+
const providerMetrics = record9(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
11860
|
+
const providerCapacity = record9(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
11861
|
+
const workerMemory = record9(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
11666
11862
|
out.log(
|
|
11667
11863
|
`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`
|
|
11668
11864
|
);
|
|
11669
11865
|
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
11670
11866
|
out.log(line);
|
|
11671
11867
|
}
|
|
11672
|
-
const coverage =
|
|
11673
|
-
const coverageCounts =
|
|
11674
|
-
const coverageBudget =
|
|
11868
|
+
const coverage = record9(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
11869
|
+
const coverageCounts = record9(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
11870
|
+
const coverageBudget = record9(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
11675
11871
|
out.log(
|
|
11676
11872
|
`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`
|
|
11677
11873
|
);
|
|
11678
11874
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
11679
|
-
const providerFreshness =
|
|
11875
|
+
const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
11680
11876
|
out.log(
|
|
11681
11877
|
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
11682
11878
|
);
|
|
@@ -11685,17 +11881,17 @@ function printO11yStatus(status, out) {
|
|
|
11685
11881
|
);
|
|
11686
11882
|
}
|
|
11687
11883
|
function providerCapacityLines(read3) {
|
|
11688
|
-
const resources =
|
|
11689
|
-
const durableObjects =
|
|
11690
|
-
const periodic =
|
|
11691
|
-
const storage =
|
|
11692
|
-
const d1 =
|
|
11693
|
-
const d1Activity =
|
|
11694
|
-
const d1Storage =
|
|
11695
|
-
const d1Latency =
|
|
11696
|
-
const r2 =
|
|
11697
|
-
const r2Operations =
|
|
11698
|
-
const r2Storage =
|
|
11884
|
+
const resources = record9(read3.body.resources) ? read3.body.resources : {};
|
|
11885
|
+
const durableObjects = record9(resources.durableObjects) ? resources.durableObjects : {};
|
|
11886
|
+
const periodic = record9(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
11887
|
+
const storage = record9(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
11888
|
+
const d1 = record9(resources.d1) ? resources.d1 : {};
|
|
11889
|
+
const d1Activity = record9(d1.activity) ? d1.activity : {};
|
|
11890
|
+
const d1Storage = record9(d1.storage) ? d1.storage : {};
|
|
11891
|
+
const d1Latency = record9(d1Activity.latency) ? d1Activity.latency : {};
|
|
11892
|
+
const r2 = record9(resources.r2) ? resources.r2 : {};
|
|
11893
|
+
const r2Operations = record9(r2.operations) ? r2.operations : {};
|
|
11894
|
+
const r2Storage = record9(r2.storage) ? r2.storage : {};
|
|
11699
11895
|
const status = String(
|
|
11700
11896
|
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
11701
11897
|
);
|
|
@@ -11706,11 +11902,11 @@ function providerCapacityLines(read3) {
|
|
|
11706
11902
|
];
|
|
11707
11903
|
}
|
|
11708
11904
|
function liveSyncLine(read3) {
|
|
11709
|
-
const performance =
|
|
11710
|
-
const commitToSend =
|
|
11905
|
+
const performance = record9(read3.body.performance) ? read3.body.performance : {};
|
|
11906
|
+
const commitToSend = record9(performance.commitToSend) ? performance.commitToSend : {};
|
|
11711
11907
|
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`;
|
|
11712
11908
|
}
|
|
11713
|
-
function
|
|
11909
|
+
function record9(value2) {
|
|
11714
11910
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
11715
11911
|
}
|
|
11716
11912
|
function numeric3(value2) {
|
|
@@ -11881,19 +12077,292 @@ function statusMinutes(value2) {
|
|
|
11881
12077
|
}
|
|
11882
12078
|
async function read2(url, headers, doFetch) {
|
|
11883
12079
|
const response2 = await doFetch(url, { headers });
|
|
11884
|
-
const
|
|
12080
|
+
const text3 = await response2.text();
|
|
11885
12081
|
let body = {};
|
|
11886
|
-
if (
|
|
12082
|
+
if (text3) {
|
|
11887
12083
|
try {
|
|
11888
|
-
const value2 = JSON.parse(
|
|
12084
|
+
const value2 = JSON.parse(text3);
|
|
11889
12085
|
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
11890
12086
|
} catch {
|
|
11891
|
-
body = { message:
|
|
12087
|
+
body = { message: text3.slice(0, 300) };
|
|
11892
12088
|
}
|
|
11893
12089
|
}
|
|
11894
12090
|
return { httpStatus: response2.status, body };
|
|
11895
12091
|
}
|
|
11896
12092
|
|
|
12093
|
+
// src/monitoring-config.ts
|
|
12094
|
+
import { createHash as createHash5 } from "crypto";
|
|
12095
|
+
function monitoringWireConfig(cfg, env) {
|
|
12096
|
+
const monitoring = cfg.o11y?.monitoring;
|
|
12097
|
+
if (!monitoring) throw new Error("o11y.monitoring is not configured");
|
|
12098
|
+
if (!cfg.services.includes("o11y")) throw new Error('o11y.monitoring requires "o11y" in services');
|
|
12099
|
+
const authoredLink = cfg.links?.[env];
|
|
12100
|
+
if (!authoredLink) throw new Error(`links.${env} is required for live monitoring`);
|
|
12101
|
+
const baseUrl = new URL(authoredLink).toString();
|
|
12102
|
+
const selectedProbes = (monitoring.probes ?? []).filter((probe) => !probe.envs || probe.envs.includes(env));
|
|
12103
|
+
const probeIds = new Set(selectedProbes.map((probe) => probe.id));
|
|
12104
|
+
const selectedSlos = monitoring.slos.filter(
|
|
12105
|
+
(slo) => slo.indicator.type === "o11y-metric" || slo.indicator.probes.some((id2) => probeIds.has(id2))
|
|
12106
|
+
);
|
|
12107
|
+
if (selectedSlos.length === 0) throw new Error(`o11y.monitoring has no SLOs for env "${env}"`);
|
|
12108
|
+
for (const slo of selectedSlos) {
|
|
12109
|
+
if (slo.indicator.type !== "probe-success") continue;
|
|
12110
|
+
const unavailable = slo.indicator.probes.filter((id2) => !probeIds.has(id2));
|
|
12111
|
+
if (unavailable.length) throw new Error(`SLO "${slo.id}" mixes probes unavailable in env "${env}": ${unavailable.join(", ")}`);
|
|
12112
|
+
}
|
|
12113
|
+
const payload = {
|
|
12114
|
+
environment: env,
|
|
12115
|
+
baseUrl,
|
|
12116
|
+
probes: selectedProbes.map(normalizeProbe),
|
|
12117
|
+
slos: selectedSlos.map(normalizeSlo),
|
|
12118
|
+
...notification(cfg.o11y.monitoring.notifications?.[env])
|
|
12119
|
+
};
|
|
12120
|
+
const revision = `sha256:${createHash5("sha256").update(canonical(payload)).digest("hex")}`;
|
|
12121
|
+
return { revision, ...payload };
|
|
12122
|
+
}
|
|
12123
|
+
function normalizeProbe(probe) {
|
|
12124
|
+
return {
|
|
12125
|
+
id: probe.id,
|
|
12126
|
+
route: probe.route,
|
|
12127
|
+
cadenceMinutes: durationMinutes(probe.every),
|
|
12128
|
+
timeoutMs: probe.timeout ?? 2e4,
|
|
12129
|
+
...probe.ready?.selector ? { readySelector: probe.ready.selector } : {},
|
|
12130
|
+
expect: {
|
|
12131
|
+
status: probe.expect.status,
|
|
12132
|
+
...probe.expect.titleIncludes ? { titleIncludes: probe.expect.titleIncludes } : {},
|
|
12133
|
+
textIncludes: probe.expect.textIncludes ?? [],
|
|
12134
|
+
accessibility: probe.expect.accessibility ?? []
|
|
12135
|
+
},
|
|
12136
|
+
enabled: probe.enabled !== false
|
|
12137
|
+
};
|
|
12138
|
+
}
|
|
12139
|
+
function normalizeSlo(slo) {
|
|
12140
|
+
return {
|
|
12141
|
+
id: slo.id,
|
|
12142
|
+
name: slo.name ?? slo.id,
|
|
12143
|
+
indicator: normalizeIndicator(slo.indicator),
|
|
12144
|
+
target: slo.target,
|
|
12145
|
+
windowMinutes: durationMinutes(slo.window),
|
|
12146
|
+
spike: {
|
|
12147
|
+
badChecks: slo.alerts?.spike?.badChecks ?? 2,
|
|
12148
|
+
withinChecks: slo.alerts?.spike?.withinChecks ?? 3,
|
|
12149
|
+
recoverAfter: slo.alerts?.spike?.recoverAfter ?? 2
|
|
12150
|
+
},
|
|
12151
|
+
trend: {
|
|
12152
|
+
burnRate: slo.alerts?.trend?.burnRate ?? 1,
|
|
12153
|
+
shortMinutes: durationMinutes(slo.alerts?.trend?.shortWindow ?? "6h"),
|
|
12154
|
+
longMinutes: durationMinutes(slo.alerts?.trend?.longWindow ?? "3d"),
|
|
12155
|
+
minBadChecks: slo.alerts?.trend?.minBadChecks ?? 2
|
|
12156
|
+
},
|
|
12157
|
+
enabled: slo.enabled !== false
|
|
12158
|
+
};
|
|
12159
|
+
}
|
|
12160
|
+
function normalizeIndicator(indicator) {
|
|
12161
|
+
if (indicator.type === "probe-success") {
|
|
12162
|
+
return { type: "probe-success", probes: [...new Set(indicator.probes)] };
|
|
12163
|
+
}
|
|
12164
|
+
return {
|
|
12165
|
+
type: "o11y-metric",
|
|
12166
|
+
metric: indicator.metric,
|
|
12167
|
+
comparator: indicator.comparator,
|
|
12168
|
+
threshold: indicator.threshold,
|
|
12169
|
+
cadenceMinutes: durationMinutes(indicator.every),
|
|
12170
|
+
observationWindowMinutes: durationMinutes(indicator.observationWindow),
|
|
12171
|
+
...indicator.route ? { route: indicator.route } : {}
|
|
12172
|
+
};
|
|
12173
|
+
}
|
|
12174
|
+
function notification(policy) {
|
|
12175
|
+
if (!policy) return {};
|
|
12176
|
+
return {
|
|
12177
|
+
notifications: {
|
|
12178
|
+
email: [...new Set(policy.email.map((email) => email.trim().toLowerCase()))],
|
|
12179
|
+
timezone: policy.timezone,
|
|
12180
|
+
daily: policy.daily === void 0 ? "08:00" : policy.daily,
|
|
12181
|
+
weekly: policy.weekly === void 0 ? { day: "monday", at: "08:00" } : policy.weekly
|
|
12182
|
+
}
|
|
12183
|
+
};
|
|
12184
|
+
}
|
|
12185
|
+
function durationMinutes(value2) {
|
|
12186
|
+
const match = /^(\d+)(m|h|d)$/.exec(value2);
|
|
12187
|
+
if (!match) throw new Error(`unsupported duration ${value2}`);
|
|
12188
|
+
const amount = Number(match[1]);
|
|
12189
|
+
return amount * (match[2] === "d" ? 1440 : match[2] === "h" ? 60 : 1);
|
|
12190
|
+
}
|
|
12191
|
+
function canonical(value2) {
|
|
12192
|
+
if (Array.isArray(value2)) return `[${value2.map(canonical).join(",")}]`;
|
|
12193
|
+
if (value2 && typeof value2 === "object") {
|
|
12194
|
+
return `{${Object.entries(value2).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
|
|
12195
|
+
}
|
|
12196
|
+
return JSON.stringify(value2);
|
|
12197
|
+
}
|
|
12198
|
+
|
|
12199
|
+
// src/monitor-command.ts
|
|
12200
|
+
var OPTIONS = [
|
|
12201
|
+
"config",
|
|
12202
|
+
"context",
|
|
12203
|
+
"platform",
|
|
12204
|
+
"token",
|
|
12205
|
+
"email",
|
|
12206
|
+
"json",
|
|
12207
|
+
"app",
|
|
12208
|
+
"env",
|
|
12209
|
+
"open",
|
|
12210
|
+
"yes",
|
|
12211
|
+
"period",
|
|
12212
|
+
"limit",
|
|
12213
|
+
"runs"
|
|
12214
|
+
];
|
|
12215
|
+
async function monitorCommand(parsed, deps = {}) {
|
|
12216
|
+
assertArgs(parsed, OPTIONS, 3);
|
|
12217
|
+
const action2 = parsed.positionals[1] ?? "status";
|
|
12218
|
+
if (!["plan", "apply", "run", "status", "incidents", "report"].includes(action2)) {
|
|
12219
|
+
throw new Error(`unknown monitor action "${action2}". Try "odla-ai monitor status --json".`);
|
|
12220
|
+
}
|
|
12221
|
+
const context = await resolveOperatorContext(parsed, {
|
|
12222
|
+
allowMissingConfig: action2 !== "plan" && action2 !== "apply",
|
|
12223
|
+
requireApp: true
|
|
12224
|
+
});
|
|
12225
|
+
if ((action2 === "plan" || action2 === "apply") && context.config.status !== "loaded") {
|
|
12226
|
+
throw new Error(`monitor ${action2} requires odla.config.mjs`);
|
|
12227
|
+
}
|
|
12228
|
+
const env = context.environment.value ?? context.cfg.envs[0] ?? "prod";
|
|
12229
|
+
const appId = context.app.value;
|
|
12230
|
+
const doFetch = deps.fetch ?? fetch;
|
|
12231
|
+
const out = deps.stdout ?? console;
|
|
12232
|
+
const token = await getDeveloperToken(
|
|
12233
|
+
context.cfg,
|
|
12234
|
+
{
|
|
12235
|
+
configPath: context.cfg.configPath,
|
|
12236
|
+
token: stringOpt(parsed.options.token),
|
|
12237
|
+
email: stringOpt(parsed.options.email),
|
|
12238
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
12239
|
+
openApprovalUrl: deps.openUrl
|
|
12240
|
+
},
|
|
12241
|
+
doFetch,
|
|
12242
|
+
out,
|
|
12243
|
+
action2 === "apply" || action2 === "run" ? { optionalProjectCapabilities: ["app.manage"] } : {}
|
|
12244
|
+
);
|
|
12245
|
+
const base = `${context.cfg.platformUrl}/o11y/${encodeURIComponent(appId)}/monitoring`;
|
|
12246
|
+
const headers = { authorization: `Bearer ${token}`, "content-type": "application/json" };
|
|
12247
|
+
const jsonOutput = parsed.options.json === true;
|
|
12248
|
+
if (action2 === "plan" || action2 === "apply") {
|
|
12249
|
+
const desired = monitoringWireConfig(context.cfg, env);
|
|
12250
|
+
const live = await request2(`${base}?env=${encodeURIComponent(env)}`, { headers }, doFetch);
|
|
12251
|
+
const currentRevision = record10(live.config) ? string(live.config.revision) : null;
|
|
12252
|
+
const changed = currentRevision !== desired.revision;
|
|
12253
|
+
const plan = {
|
|
12254
|
+
schemaVersion: 1,
|
|
12255
|
+
appId,
|
|
12256
|
+
env,
|
|
12257
|
+
currentRevision,
|
|
12258
|
+
desiredRevision: desired.revision,
|
|
12259
|
+
changed,
|
|
12260
|
+
probes: desired.probes.map((probe) => ({ id: probe.id, route: probe.route, cadenceMinutes: probe.cadenceMinutes })),
|
|
12261
|
+
slos: desired.slos.map((slo) => ({ id: slo.id, indicator: slo.indicator, target: slo.target, windowMinutes: slo.windowMinutes })),
|
|
12262
|
+
notifications: desired.notifications ? { recipients: desired.notifications.email.length, timezone: desired.notifications.timezone, daily: desired.notifications.daily, weekly: desired.notifications.weekly } : null
|
|
12263
|
+
};
|
|
12264
|
+
if (action2 === "plan") {
|
|
12265
|
+
emit3(plan, jsonOutput, out, () => {
|
|
12266
|
+
out.log(`monitor plan ${appId}/${env}: ${changed ? "changes pending" : "in sync"}`);
|
|
12267
|
+
out.log(`revision ${currentRevision ?? "not configured"} -> ${desired.revision}`);
|
|
12268
|
+
for (const probe of desired.probes) out.log(`probe ${probe.id} ${probe.route} every ${probe.cadenceMinutes}m`);
|
|
12269
|
+
for (const slo of desired.slos) out.log(`slo ${slo.id} ${slo.indicator.type} ${(slo.target * 100).toFixed(3)}% ${slo.windowMinutes}m`);
|
|
12270
|
+
});
|
|
12271
|
+
return;
|
|
12272
|
+
}
|
|
12273
|
+
if ((env === "prod" || env === "production") && parsed.options.yes !== true) {
|
|
12274
|
+
throw new Error(`refusing to apply live monitoring for "${env}" without --yes; run monitor plan first`);
|
|
12275
|
+
}
|
|
12276
|
+
if (!changed) {
|
|
12277
|
+
emit3({ ...plan, applied: false }, jsonOutput, out, () => out.log(`monitor apply ${appId}/${env}: already in sync`));
|
|
12278
|
+
return;
|
|
12279
|
+
}
|
|
12280
|
+
const applied = await request2(`${base}?env=${encodeURIComponent(env)}`, {
|
|
12281
|
+
method: "PUT",
|
|
12282
|
+
headers,
|
|
12283
|
+
body: JSON.stringify(desired)
|
|
12284
|
+
}, doFetch);
|
|
12285
|
+
emit3({ schemaVersion: 1, appId, env, ...applied }, jsonOutput, out, () => out.log(`monitor apply ${appId}/${env}: ${applied.changed === true ? "applied" : "unchanged"} ${desired.revision}`));
|
|
12286
|
+
return;
|
|
12287
|
+
}
|
|
12288
|
+
if (action2 === "run") {
|
|
12289
|
+
const probeId = parsed.positionals[2];
|
|
12290
|
+
if (!probeId) throw new Error("monitor run requires a probe id");
|
|
12291
|
+
const result2 = await request2(`${base}/probes/${encodeURIComponent(probeId)}/run?env=${encodeURIComponent(env)}`, {
|
|
12292
|
+
method: "POST",
|
|
12293
|
+
headers
|
|
12294
|
+
}, doFetch);
|
|
12295
|
+
emit3(result2, jsonOutput, out, () => {
|
|
12296
|
+
const run = record10(result2.run) ? result2.run : {};
|
|
12297
|
+
out.log(`monitor run ${appId}/${env}/${probeId}: ${string(run.outcome) ?? "unknown"}${run.failure_code ? ` (${String(run.failure_code)})` : ""}`);
|
|
12298
|
+
});
|
|
12299
|
+
return;
|
|
12300
|
+
}
|
|
12301
|
+
let path = action2;
|
|
12302
|
+
if (action2 === "report") {
|
|
12303
|
+
const period = stringOpt(parsed.options.period) ?? "daily";
|
|
12304
|
+
if (period !== "daily" && period !== "weekly") throw new Error("--period must be daily or weekly");
|
|
12305
|
+
path = `report?period=${period}`;
|
|
12306
|
+
} else if (action2 === "incidents") {
|
|
12307
|
+
const params = new URLSearchParams({ limit: String(numberOpt(parsed.options.limit, "--limit") ?? 100) });
|
|
12308
|
+
if (boolOpt(parsed.options.runs) === true) params.set("runs", "true");
|
|
12309
|
+
path = `incidents?${params}`;
|
|
12310
|
+
}
|
|
12311
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
12312
|
+
const result = await request2(`${base}/${path}${separator}env=${encodeURIComponent(env)}`, { headers }, doFetch);
|
|
12313
|
+
emit3(result, jsonOutput, out, () => printRead(action2, appId, env, result, out));
|
|
12314
|
+
}
|
|
12315
|
+
async function request2(url, init, doFetch) {
|
|
12316
|
+
const response2 = await doFetch(url, init);
|
|
12317
|
+
const text3 = await response2.text();
|
|
12318
|
+
let body = {};
|
|
12319
|
+
try {
|
|
12320
|
+
const parsed = text3 ? JSON.parse(text3) : {};
|
|
12321
|
+
body = record10(parsed) ? parsed : { value: parsed };
|
|
12322
|
+
} catch {
|
|
12323
|
+
body = { message: text3.slice(0, 500) };
|
|
12324
|
+
}
|
|
12325
|
+
if (!response2.ok) {
|
|
12326
|
+
const error = record10(body.error) ? body.error : body;
|
|
12327
|
+
throw new Error(string(error.message) ?? string(error.code) ?? `monitor request failed (${response2.status})`);
|
|
12328
|
+
}
|
|
12329
|
+
return body;
|
|
12330
|
+
}
|
|
12331
|
+
function printRead(action2, appId, env, result, out) {
|
|
12332
|
+
if (action2 === "status") {
|
|
12333
|
+
out.log(`monitor status ${appId}/${env}: ${String(result.overall ?? (result.configured === false ? "not configured" : "unknown"))}`);
|
|
12334
|
+
const slos = Array.isArray(result.slos) ? result.slos.filter(record10) : [];
|
|
12335
|
+
for (const slo of slos) out.log(`slo ${String(slo.id)} ${String(slo.state)} ${percent(slo.observed)} observed ${percent(slo.budgetRemaining)} budget remaining`);
|
|
12336
|
+
const incidents = Array.isArray(result.openIncidents) ? result.openIncidents.length : 0;
|
|
12337
|
+
const gaps = Array.isArray(result.monitoringGaps) ? result.monitoringGaps.length : 0;
|
|
12338
|
+
out.log(`open incidents ${incidents}`);
|
|
12339
|
+
out.log(`monitoring gaps ${gaps}`);
|
|
12340
|
+
return;
|
|
12341
|
+
}
|
|
12342
|
+
if (action2 === "incidents") {
|
|
12343
|
+
const incidents = Array.isArray(result.incidents) ? result.incidents.filter(record10) : [];
|
|
12344
|
+
out.log(`monitor incidents ${appId}/${env}: ${incidents.length}`);
|
|
12345
|
+
for (const incident2 of incidents) out.log(`${String(incident2.state)} ${String(incident2.kind)} ${String(incident2.slo_id)} ${new Date(Number(incident2.opened_at)).toISOString()}`);
|
|
12346
|
+
return;
|
|
12347
|
+
}
|
|
12348
|
+
out.log(`monitor report ${appId}/${env}: ${String(result.period)} ${String(result.overall)}`);
|
|
12349
|
+
const probes = Array.isArray(result.probes) ? result.probes.filter(record10) : [];
|
|
12350
|
+
for (const probe of probes) out.log(`probe ${String(probe.id)} ${Number(probe.good)} good ${Number(probe.bad)} bad ${Number(probe.unknown)} unknown`);
|
|
12351
|
+
}
|
|
12352
|
+
function emit3(value2, json, out, human) {
|
|
12353
|
+
if (json) out.log(JSON.stringify(value2, null, 2));
|
|
12354
|
+
else human();
|
|
12355
|
+
}
|
|
12356
|
+
function record10(value2) {
|
|
12357
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
12358
|
+
}
|
|
12359
|
+
function string(value2) {
|
|
12360
|
+
return typeof value2 === "string" ? value2 : null;
|
|
12361
|
+
}
|
|
12362
|
+
function percent(value2) {
|
|
12363
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? `${(value2 * 100).toFixed(2)}%` : "unknown";
|
|
12364
|
+
}
|
|
12365
|
+
|
|
11897
12366
|
// src/provision.ts
|
|
11898
12367
|
import { AppsError as AppsError2, createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor6 } from "@odla-ai/apps";
|
|
11899
12368
|
import { putSecret as putSecret2 } from "@odla-ai/ai";
|
|
@@ -12052,8 +12521,8 @@ function runtimeUrl(cfg, suffix = "") {
|
|
|
12052
12521
|
return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
|
|
12053
12522
|
}
|
|
12054
12523
|
async function safeError(response2) {
|
|
12055
|
-
const
|
|
12056
|
-
return redactSecrets(
|
|
12524
|
+
const text3 = await response2.text();
|
|
12525
|
+
return redactSecrets(text3.slice(0, 1e3));
|
|
12057
12526
|
}
|
|
12058
12527
|
async function finish(doFetch, cfg, token, sessionId, method) {
|
|
12059
12528
|
return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
|
|
@@ -12456,6 +12925,7 @@ var COMMAND_SURFACE = {
|
|
|
12456
12925
|
doctor: {},
|
|
12457
12926
|
help: {},
|
|
12458
12927
|
init: {},
|
|
12928
|
+
monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
|
|
12459
12929
|
o11y: { status: {} },
|
|
12460
12930
|
operations: { get: {}, wait: {} },
|
|
12461
12931
|
platform: {
|
|
@@ -12696,12 +13166,12 @@ async function runbookRemove(ctx, slug) {
|
|
|
12696
13166
|
// src/runbook-import.ts
|
|
12697
13167
|
import { readFileSync as readFileSync10, readdirSync as readdirSync2, statSync } from "fs";
|
|
12698
13168
|
import { basename as basename2, join as join13 } from "path";
|
|
12699
|
-
function parseRunbook(
|
|
12700
|
-
let rest =
|
|
13169
|
+
function parseRunbook(text3, slug) {
|
|
13170
|
+
let rest = text3;
|
|
12701
13171
|
const meta = {};
|
|
12702
|
-
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(
|
|
13172
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text3);
|
|
12703
13173
|
if (fm) {
|
|
12704
|
-
rest =
|
|
13174
|
+
rest = text3.slice(fm[0].length);
|
|
12705
13175
|
for (const line of fm[1].split(/\r?\n/)) {
|
|
12706
13176
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
|
|
12707
13177
|
if (!pair) continue;
|
|
@@ -13052,7 +13522,7 @@ async function runbookImpact(ctx, options, deps = {}) {
|
|
|
13052
13522
|
|
|
13053
13523
|
// src/runbook-lint.ts
|
|
13054
13524
|
function invocationsIn(body) {
|
|
13055
|
-
const re = /(`|npx[^\S\n]+)?(?:@odla-ai\/cli(?:@[\w.-]+)?|odla-ai)((?:[^\S\n]+[a-z][\w-]*)+)/g;
|
|
13525
|
+
const re = /(`|npx[^\S\n]+(?:--yes[^\S\n]+)?)?(?:@odla-ai\/cli(?:@[\w.-]+)?|odla-ai)((?:[^\S\n]+[a-z][\w-]*)+)/g;
|
|
13056
13526
|
const found = [];
|
|
13057
13527
|
for (const match of body.matchAll(re)) {
|
|
13058
13528
|
if (!match[1]) continue;
|
|
@@ -13248,7 +13718,8 @@ var ALLOWED2 = [
|
|
|
13248
13718
|
"base",
|
|
13249
13719
|
"requires",
|
|
13250
13720
|
"platform",
|
|
13251
|
-
"context"
|
|
13721
|
+
"context",
|
|
13722
|
+
"open"
|
|
13252
13723
|
];
|
|
13253
13724
|
function requireSlug(slug, action2) {
|
|
13254
13725
|
if (!slug) throw new Error(`"runbook ${action2}" needs a slug, e.g. "odla-ai runbook ${action2} release"`);
|
|
@@ -13276,6 +13747,7 @@ async function buildContext3(parsed, deps, action2) {
|
|
|
13276
13747
|
};
|
|
13277
13748
|
}
|
|
13278
13749
|
const needsCapability = WRITES2.has(action2) && !dryRun && appId === PLATFORM_SCOPE && !stringOpt(parsed.options.token);
|
|
13750
|
+
const open = parsed.options.open === false ? false : parsed.options.open === true ? true : void 0;
|
|
13279
13751
|
const token = needsCapability ? await getScopedPlatformToken({
|
|
13280
13752
|
platform: cfg.platformUrl,
|
|
13281
13753
|
scope: "platform:runbook:write",
|
|
@@ -13283,6 +13755,7 @@ async function buildContext3(parsed, deps, action2) {
|
|
|
13283
13755
|
label: `odla CLI (runbook ${action2})`,
|
|
13284
13756
|
fetch: doFetch,
|
|
13285
13757
|
stdout: out,
|
|
13758
|
+
open,
|
|
13286
13759
|
openApprovalUrl: deps.openUrl,
|
|
13287
13760
|
// A project, named context, or the global operator context owns the
|
|
13288
13761
|
// exact-scope cache; it never follows an arbitrary shell directory.
|
|
@@ -13294,11 +13767,7 @@ async function buildContext3(parsed, deps, action2) {
|
|
|
13294
13767
|
configPath: cfg.configPath,
|
|
13295
13768
|
token: stringOpt(parsed.options.token),
|
|
13296
13769
|
email: stringOpt(parsed.options.email),
|
|
13297
|
-
|
|
13298
|
-
// CI and SSH). Hardcoding `false` meant a first-time handshake printed
|
|
13299
|
-
// a link and opened nothing — the one moment a browser is the whole
|
|
13300
|
-
// point.
|
|
13301
|
-
open: void 0
|
|
13770
|
+
open
|
|
13302
13771
|
},
|
|
13303
13772
|
doFetch,
|
|
13304
13773
|
out
|
|
@@ -13470,9 +13939,9 @@ function printHostedSecurityIntent(out, intent) {
|
|
|
13470
13939
|
}
|
|
13471
13940
|
function assertHostedSecurityPlanReady(plan) {
|
|
13472
13941
|
const reasons = [];
|
|
13473
|
-
for (const [label,
|
|
13474
|
-
if (!
|
|
13475
|
-
if (!
|
|
13942
|
+
for (const [label, route3] of Object.entries(plan.routes)) {
|
|
13943
|
+
if (!route3.enabled) reasons.push(`${label} is disabled`);
|
|
13944
|
+
if (!route3.credentialReady) reasons.push(`${label} provider credential is unavailable`);
|
|
13476
13945
|
}
|
|
13477
13946
|
if (!plan.independent) reasons.push("discovery and validation are not independently routed");
|
|
13478
13947
|
if (plan.ready && reasons.length === 0) return;
|
|
@@ -13526,20 +13995,20 @@ function enforceHostedReportGate(report4, parsed, out, emitSuccess) {
|
|
|
13526
13995
|
out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report4.coverageStatus}. This is not proof that the application is secure.`);
|
|
13527
13996
|
}
|
|
13528
13997
|
}
|
|
13529
|
-
function printHostedSecurityPlanRoute(out, label,
|
|
13530
|
-
const readiness =
|
|
13531
|
-
|
|
13532
|
-
|
|
13998
|
+
function printHostedSecurityPlanRoute(out, label, route3) {
|
|
13999
|
+
const readiness = route3.enabled && route3.credentialReady ? "ready" : [
|
|
14000
|
+
route3.enabled ? void 0 : "disabled",
|
|
14001
|
+
route3.credentialReady ? void 0 : "credential unavailable"
|
|
13533
14002
|
].filter(Boolean).join(", ");
|
|
13534
|
-
out.log(` ${label}: ${
|
|
13535
|
-
out.log(` bounds: ${
|
|
14003
|
+
out.log(` ${label}: ${route3.provider}/${route3.model} \xB7 policy v${route3.policyVersion} \xB7 ${readiness}`);
|
|
14004
|
+
out.log(` bounds: ${route3.maxCallsPerRun} calls/run \xB7 ${route3.maxInputBytes} input bytes/call \xB7 ${route3.maxOutputTokens} output tokens/call`);
|
|
13536
14005
|
}
|
|
13537
14006
|
function printHostedCoverage(out, job) {
|
|
13538
14007
|
const coverage = job.coverage;
|
|
13539
14008
|
out.log(` coverage: ${job.coverageStatus ?? "pending"}${coverage?.completeCells !== void 0 ? ` ${coverage.completeCells}/${coverage.totalCells ?? "?"}` : ""}${coverage?.shallowCells ? ` shallow=${coverage.shallowCells}` : ""}${coverage?.blockedCells ? ` blocked=${coverage.blockedCells}` : ""}${coverage?.unscheduledCells ? ` unscheduled=${coverage.unscheduledCells}` : ""}${coverage?.budgetExhaustedCells ? ` budget_exhausted=${coverage.budgetExhaustedCells}` : ""}`);
|
|
13540
14009
|
}
|
|
13541
|
-
function routeLabel(
|
|
13542
|
-
return `${
|
|
14010
|
+
function routeLabel(route3) {
|
|
14011
|
+
return `${route3.provider}/${route3.model}${route3.policyVersion ? ` policy v${route3.policyVersion}` : ""}`;
|
|
13543
14012
|
}
|
|
13544
14013
|
var HOSTED_SEVERITIES = ["informational", "low", "medium", "high", "critical"];
|
|
13545
14014
|
function hostedSeverity(value2, flag) {
|
|
@@ -13633,11 +14102,11 @@ function selectEnv(requested, declared, configPath, rootDir) {
|
|
|
13633
14102
|
}
|
|
13634
14103
|
return env;
|
|
13635
14104
|
}
|
|
13636
|
-
async function injectedToken(options,
|
|
13637
|
-
const value2 = options.token ?? await options.getToken?.(Object.freeze({ ...
|
|
14105
|
+
async function injectedToken(options, request3) {
|
|
14106
|
+
const value2 = options.token ?? await options.getToken?.(Object.freeze({ ...request3 }));
|
|
13638
14107
|
if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
|
|
13639
14108
|
throw new Error(
|
|
13640
|
-
|
|
14109
|
+
request3.selfAudit ? "Self-audit requires an injected, scoped platform security token" : "Hosted security requires an injected app developer token or getToken callback"
|
|
13641
14110
|
);
|
|
13642
14111
|
}
|
|
13643
14112
|
return value2;
|
|
@@ -13950,11 +14419,11 @@ async function runLocalSecurityCommand(parsed, dependencies) {
|
|
|
13950
14419
|
sourceDisclosureAck: parsed.options["ack-redacted-source"] === true ? "redacted" : void 0,
|
|
13951
14420
|
fetch: doFetch,
|
|
13952
14421
|
stdout: out,
|
|
13953
|
-
getToken: async (
|
|
13954
|
-
if (
|
|
14422
|
+
getToken: async (request3) => {
|
|
14423
|
+
if (request3.scope === "platform:security:self") {
|
|
13955
14424
|
return getScopedPlatformToken({
|
|
13956
|
-
platform:
|
|
13957
|
-
scope:
|
|
14425
|
+
platform: request3.platform,
|
|
14426
|
+
scope: request3.scope,
|
|
13958
14427
|
email: stringOpt(parsed.options.email),
|
|
13959
14428
|
open,
|
|
13960
14429
|
fetch: doFetch,
|
|
@@ -13963,7 +14432,7 @@ async function runLocalSecurityCommand(parsed, dependencies) {
|
|
|
13963
14432
|
});
|
|
13964
14433
|
}
|
|
13965
14434
|
const cfg = await loadProjectConfig(configPath);
|
|
13966
|
-
if (platformAudience(cfg.platformUrl) !== platformAudience(
|
|
14435
|
+
if (platformAudience(cfg.platformUrl) !== platformAudience(request3.platform)) {
|
|
13967
14436
|
throw new Error("--platform cannot reuse a project developer token from another platform; update odla.config.mjs and authenticate there");
|
|
13968
14437
|
}
|
|
13969
14438
|
return getDeveloperToken(
|
|
@@ -14168,10 +14637,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
14168
14637
|
}
|
|
14169
14638
|
if (command === "bug") {
|
|
14170
14639
|
const action2 = parsed.positionals[1] ?? "list";
|
|
14171
|
-
const
|
|
14640
|
+
const canonical2 = action2 === "report" || action2 === "create" ? "add" : action2;
|
|
14172
14641
|
await pmCommand({
|
|
14173
14642
|
...parsed,
|
|
14174
|
-
positionals: ["pm", "bug",
|
|
14643
|
+
positionals: ["pm", "bug", canonical2, ...parsed.positionals.slice(2)]
|
|
14175
14644
|
}, runtime);
|
|
14176
14645
|
return;
|
|
14177
14646
|
}
|
|
@@ -14183,6 +14652,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
14183
14652
|
await o11yCommand(parsed, runtime);
|
|
14184
14653
|
return;
|
|
14185
14654
|
}
|
|
14655
|
+
if (command === "monitor") {
|
|
14656
|
+
await monitorCommand(parsed, runtime);
|
|
14657
|
+
return;
|
|
14658
|
+
}
|
|
14186
14659
|
if (command === "platform") {
|
|
14187
14660
|
await platformCommand(parsed, runtime);
|
|
14188
14661
|
return;
|
|
@@ -14297,6 +14770,8 @@ export {
|
|
|
14297
14770
|
CODE_BUILD_RECIPES,
|
|
14298
14771
|
codeConnect,
|
|
14299
14772
|
runCodeRuntime,
|
|
14773
|
+
monitoringWireConfig,
|
|
14774
|
+
monitorCommand,
|
|
14300
14775
|
provision,
|
|
14301
14776
|
COMMAND_SURFACE,
|
|
14302
14777
|
acceptedAfter,
|
|
@@ -14315,4 +14790,4 @@ export {
|
|
|
14315
14790
|
isTerminalHostedSecurityStatus,
|
|
14316
14791
|
runCli
|
|
14317
14792
|
};
|
|
14318
|
-
//# sourceMappingURL=chunk-
|
|
14793
|
+
//# sourceMappingURL=chunk-RUEM7ZTA.js.map
|