@odla-ai/cli 0.46.11 → 0.48.0

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/dist/index.cjs CHANGED
@@ -1385,6 +1385,7 @@ var COMMAND_SURFACE = {
1385
1385
  watch: {}
1386
1386
  },
1387
1387
  provision: {},
1388
+ promotion: { plan: {}, inspect: {}, status: {}, apply: {}, rollback: {}, next: {}, record: {}, cancel: {} },
1388
1389
  runbook: {
1389
1390
  ask: {},
1390
1391
  search: {},
@@ -3733,9 +3734,9 @@ function canonicalValue(value2) {
3733
3734
  }
3734
3735
  if (Array.isArray(value2)) return value2.map(canonicalValue);
3735
3736
  if (value2 && typeof value2 === "object") {
3736
- const record11 = value2;
3737
+ const record12 = value2;
3737
3738
  return Object.fromEntries(
3738
- Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, canonicalValue(record11[key])])
3739
+ Object.keys(record12).filter((key) => record12[key] !== void 0).sort().map((key) => [key, canonicalValue(record12[key])])
3739
3740
  );
3740
3741
  }
3741
3742
  throw new TypeError("canonical JSON rejects unsupported values");
@@ -6119,10 +6120,10 @@ var import_node_fs16 = require("fs");
6119
6120
  var import_node_os5 = require("os");
6120
6121
  var import_node_path16 = require("path");
6121
6122
 
6122
- // ../harness/dist/chunk-RXNHCGWE.js
6123
+ // ../harness/dist/chunk-U324RQ4N.js
6123
6124
  var HARNESS_PROTOCOL_VERSION = 1;
6124
6125
 
6125
- // ../harness/dist/chunk-CR6RE3A2.js
6126
+ // ../harness/dist/chunk-5LRYJKUI.js
6126
6127
  var import_child_process = require("child_process");
6127
6128
  var import_fs = require("fs");
6128
6129
  var import_promises2 = require("fs/promises");
@@ -6290,8 +6291,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
6290
6291
  const maxFiles = options.maxFiles ?? 2e4;
6291
6292
  const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
6292
6293
  const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
6293
- const entries = inventory.flatMap((record11) => {
6294
- const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record11);
6294
+ const entries = inventory.flatMap((record12) => {
6295
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record12);
6295
6296
  return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
6296
6297
  });
6297
6298
  if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
@@ -6495,7 +6496,165 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
6495
6496
  }
6496
6497
  }
6497
6498
 
6498
- // ../harness/dist/chunk-J7XU5QB7.js
6499
+ // ../harness/dist/chunk-FAN2R3GW.js
6500
+ var text3 = (value2, maximum) => {
6501
+ if (typeof value2 !== "string") return void 0;
6502
+ const bounded = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
6503
+ return bounded ? bounded.slice(0, maximum) : void 0;
6504
+ };
6505
+ var integer = (value2) => Number.isSafeInteger(value2) && Number(value2) >= 0 ? Number(value2) : void 0;
6506
+ var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
6507
+ var excerpt = (value2, tail = false) => {
6508
+ if (typeof value2 !== "string") return void 0;
6509
+ const safe = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
6510
+ const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
6511
+ if (!source) return void 0;
6512
+ const lines = source.split("\n").filter((line2) => line2.trim()).map((line2) => line2.slice(0, 240));
6513
+ const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
6514
+ return text3(selected.join("\n"), 2400);
6515
+ };
6516
+ var paths = (value2) => {
6517
+ if (!Array.isArray(value2)) return void 0;
6518
+ const items = value2.flatMap((item) => {
6519
+ const path = text3(item, 1024);
6520
+ return path ? [path] : [];
6521
+ }).slice(0, 12);
6522
+ return items.length ? items : void 0;
6523
+ };
6524
+ function patchStats(value2) {
6525
+ if (typeof value2 !== "string") return {};
6526
+ let additions = 0;
6527
+ let deletions = 0;
6528
+ for (const line2 of value2.slice(0, 262144).split("\n")) {
6529
+ if (line2.startsWith("+++") || line2.startsWith("---")) continue;
6530
+ if (line2.startsWith("+")) additions += 1;
6531
+ else if (line2.startsWith("-")) deletions += 1;
6532
+ }
6533
+ return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
6534
+ }
6535
+ function searchResults(value2) {
6536
+ if (typeof value2 !== "string") return void 0;
6537
+ const results = value2.split("\n").flatMap((line2) => {
6538
+ const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line2);
6539
+ if (!match) return [];
6540
+ const lineNumber = Number(match[2]);
6541
+ const itemText = text3(match[3], 240);
6542
+ if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
6543
+ return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
6544
+ }).slice(0, 5);
6545
+ return results.length ? results : void 0;
6546
+ }
6547
+ function codeToolRequestPresentation(request3) {
6548
+ const input = request3.input;
6549
+ if (request3.tool === "sandbox.read") {
6550
+ const path = text3(input.path, 1024);
6551
+ if (!path) return void 0;
6552
+ const startLine = integer(input.startLine);
6553
+ const endLine = integer(input.endLine);
6554
+ return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
6555
+ }
6556
+ if (request3.tool === "sandbox.list") {
6557
+ const scope = text3(input.prefix, 1024);
6558
+ return { kind: "list", ...scope ? { scope } : {} };
6559
+ }
6560
+ if (request3.tool === "sandbox.search" || request3.tool === "sandbox.overview" || request3.tool === "sandbox.where_is" || request3.tool === "sandbox.who_imports" || request3.tool === "sandbox.who_touches") {
6561
+ const query = text3(input.query, 512);
6562
+ const scope = request3.tool === "sandbox.search" ? text3(input.prefix, 1024) : void 0;
6563
+ if (request3.tool === "sandbox.search" && !query) return void 0;
6564
+ return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
6565
+ }
6566
+ if (request3.tool === "sandbox.apply_patch") {
6567
+ return { kind: "patch", ...patchStats(input.patch) };
6568
+ }
6569
+ const recipeId = text3(input.recipeId, 120);
6570
+ return recipeId ? { kind: "recipe", recipeId } : void 0;
6571
+ }
6572
+ function codeToolResultPresentation(request3, response2) {
6573
+ const started = codeToolRequestPresentation(request3);
6574
+ if (!started || !response2.ok) return started;
6575
+ const details = record5(response2.details);
6576
+ if (started.kind === "read") {
6577
+ return {
6578
+ ...started,
6579
+ ...integer(details?.startLine) ? { startLine: integer(details?.startLine) } : {},
6580
+ ...integer(details?.endLine) ? { endLine: integer(details?.endLine) } : {},
6581
+ ...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
6582
+ };
6583
+ }
6584
+ if (started.kind === "list") {
6585
+ const listed = response2.content.split("\n").filter((line2) => line2 && !line2.startsWith("\u2026") && !line2.startsWith("Workspace ")).map((line2) => text3(line2, 1024)).filter((line2) => Boolean(line2)).slice(0, 8);
6586
+ return {
6587
+ ...started,
6588
+ ...integer(details?.count) !== void 0 ? { count: integer(details?.count) } : {},
6589
+ ...listed.length ? { paths: listed } : {}
6590
+ };
6591
+ }
6592
+ if (started.kind === "query") {
6593
+ const results = request3.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
6594
+ const resultExcerpt = request3.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
6595
+ return {
6596
+ ...started,
6597
+ ...integer(details?.count) !== void 0 ? { count: integer(details?.count) } : {},
6598
+ ...results ? { results } : {},
6599
+ ...resultExcerpt ? { excerpt: resultExcerpt } : {}
6600
+ };
6601
+ }
6602
+ if (started.kind === "patch") {
6603
+ return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
6604
+ }
6605
+ const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
6606
+ return {
6607
+ ...started,
6608
+ ...integer(details?.exitCode) !== void 0 ? { exitCode: integer(details?.exitCode) } : {},
6609
+ ...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
6610
+ ...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
6611
+ ...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
6612
+ };
6613
+ }
6614
+ var MAX_FAILURE_REASON = 240;
6615
+ function toolFailureReason(response2) {
6616
+ if (response2.ok) return void 0;
6617
+ const supplied = response2.details?.failureReason;
6618
+ const reason = typeof supplied === "string" && supplied || DEFAULT_FAILURE_REASON[response2.content] || response2.content || "tool request failed; inspect the tool input and workspace state";
6619
+ return reason.slice(0, MAX_FAILURE_REASON);
6620
+ }
6621
+ var DEFAULT_FAILURE_REASON = {
6622
+ "tool denied by CaMeL policy": "tool denied by CaMeL policy",
6623
+ "review sessions are read-only": "review session is read-only; workspace changes are not permitted"
6624
+ };
6625
+ function observedBroker(input) {
6626
+ const now = input.now ?? Date.now;
6627
+ return {
6628
+ execute: async (context, request3) => {
6629
+ const startedAt = now();
6630
+ const operationId = input.operationIdFor(request3.requestId);
6631
+ const startedPresentation = codeToolRequestPresentation(request3);
6632
+ await input.emit({
6633
+ type: "tool",
6634
+ phase: "started",
6635
+ tool: request3.tool,
6636
+ operationId,
6637
+ ...startedPresentation ? { presentation: startedPresentation } : {}
6638
+ });
6639
+ const response2 = await input.broker.execute(context, request3);
6640
+ const completedPresentation = codeToolResultPresentation(request3, response2);
6641
+ const failureReason = toolFailureReason(response2);
6642
+ await input.emit({
6643
+ type: "tool",
6644
+ phase: "completed",
6645
+ tool: request3.tool,
6646
+ ok: response2.ok,
6647
+ durationMs: now() - startedAt,
6648
+ operationId,
6649
+ ...failureReason ? { failureReason } : {},
6650
+ ...completedPresentation ? { presentation: completedPresentation } : {}
6651
+ });
6652
+ return response2;
6653
+ }
6654
+ };
6655
+ }
6656
+
6657
+ // ../harness/dist/chunk-Q4NL5XO3.js
6499
6658
  var import_crypto = require("crypto");
6500
6659
  var import_promises5 = require("fs/promises");
6501
6660
  var import_path5 = require("path");
@@ -6539,8 +6698,8 @@ function normalize(value2) {
6539
6698
  if (Array.isArray(value2)) return value2.map(normalize);
6540
6699
  if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
6541
6700
  if (typeof value2 === "object") {
6542
- const record11 = value2;
6543
- return Object.fromEntries(Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, normalize(record11[key])]));
6701
+ const record12 = value2;
6702
+ return Object.fromEntries(Object.keys(record12).filter((key) => record12[key] !== void 0).sort().map((key) => [key, normalize(record12[key])]));
6544
6703
  }
6545
6704
  throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
6546
6705
  }
@@ -6832,7 +6991,7 @@ function validateSnapshot(snapshot, limits) {
6832
6991
  }
6833
6992
  }
6834
6993
 
6835
- // ../harness/dist/chunk-J7XU5QB7.js
6994
+ // ../harness/dist/chunk-Q4NL5XO3.js
6836
6995
  var import_child_process4 = require("child_process");
6837
6996
  var import_promises6 = require("fs/promises");
6838
6997
  var import_path6 = require("path");
@@ -7136,7 +7295,7 @@ function looksLikeDestination(value2) {
7136
7295
  return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text4) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text4);
7137
7296
  }
7138
7297
 
7139
- // ../harness/dist/chunk-J7XU5QB7.js
7298
+ // ../harness/dist/chunk-Q4NL5XO3.js
7140
7299
  var import_promises10 = require("fs/promises");
7141
7300
  var import_promises11 = require("fs/promises");
7142
7301
  var import_path10 = require("path");
@@ -7404,7 +7563,7 @@ async function buildCodeGraph(input) {
7404
7563
  return builder.build();
7405
7564
  }
7406
7565
 
7407
- // ../harness/dist/chunk-J7XU5QB7.js
7566
+ // ../harness/dist/chunk-Q4NL5XO3.js
7408
7567
  var import_crypto4 = require("crypto");
7409
7568
  async function digestStagedWorkspace(root, limits) {
7410
7569
  const files = [];
@@ -7463,7 +7622,7 @@ function createCodeRuntimeControlClient(options) {
7463
7622
  }
7464
7623
  const value2 = await response2.json().catch(() => null);
7465
7624
  if (!response2.ok) {
7466
- const problem = record5(record5(value2)?.error);
7625
+ const problem = record6(record6(value2)?.error);
7467
7626
  throw new CodeRuntimeControlError(
7468
7627
  typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
7469
7628
  response2.status,
@@ -7484,12 +7643,12 @@ function createCodeRuntimeControlClient(options) {
7484
7643
  await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
7485
7644
  ),
7486
7645
  infer: async (sessionId, inference) => {
7487
- const value2 = record5(await call4(
7646
+ const value2 = record6(await call4(
7488
7647
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
7489
7648
  inference,
7490
7649
  modelRequestTimeoutMs
7491
7650
  ));
7492
- if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
7651
+ if (!value2 || value2.requestId !== inference.requestId || !record6(value2.response) || !record6(value2.receipt)) {
7493
7652
  throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
7494
7653
  }
7495
7654
  return value2;
@@ -7669,12 +7828,12 @@ function validateHeartbeat(version, capabilities) {
7669
7828
  }
7670
7829
  }
7671
7830
  function parseSnapshot(value2) {
7672
- const root = record5(value2);
7673
- const host = record5(root?.host);
7831
+ const root = record6(value2);
7832
+ const host = record6(root?.host);
7674
7833
  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");
7675
7834
  const bindingIds = /* @__PURE__ */ new Set();
7676
7835
  const bindings = root.bindings.map((item) => {
7677
- const binding = record5(item);
7836
+ const binding = record6(item);
7678
7837
  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)) {
7679
7838
  throw invalid("binding");
7680
7839
  }
@@ -7684,10 +7843,10 @@ function parseSnapshot(value2) {
7684
7843
  const commandIds = /* @__PURE__ */ new Set();
7685
7844
  const commandSequences = /* @__PURE__ */ new Set();
7686
7845
  const commands = root.commands.map((item) => {
7687
- const command = record5(item);
7846
+ const command = record6(item);
7688
7847
  const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
7689
7848
  const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
7690
- 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");
7849
+ 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)) || !record6(command.payload) || !Number.isSafeInteger(command.createdAt)) throw invalid("command");
7691
7850
  commandIds.add(command.commandId);
7692
7851
  commandSequences.add(sequenceKey);
7693
7852
  return command;
@@ -7696,10 +7855,10 @@ function parseSnapshot(value2) {
7696
7855
  }
7697
7856
  async function parseSource(value2) {
7698
7857
  const repositoryLimits = { maximumFiles: 1e5, maximumBytes: 80 * 1024 * 1024 };
7699
- const snapshot = record5(record5(value2)?.snapshot);
7858
+ const snapshot = record6(record6(value2)?.snapshot);
7700
7859
  if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
7701
7860
  const files = snapshot.files.map((value22) => {
7702
- const file = record5(value22);
7861
+ const file = record6(value22);
7703
7862
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
7704
7863
  return { path: file.path, content: file.content };
7705
7864
  });
@@ -7708,11 +7867,11 @@ async function parseSource(value2) {
7708
7867
  const aliases = /* @__PURE__ */ new Set();
7709
7868
  const references = [];
7710
7869
  for (const item of referencesValue) {
7711
- const reference = record5(item);
7870
+ const reference = record6(item);
7712
7871
  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");
7713
7872
  aliases.add(reference.alias);
7714
7873
  const referenceFiles = reference.files.map((entry) => {
7715
- const file = record5(entry);
7874
+ const file = record6(entry);
7716
7875
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
7717
7876
  return { path: file.path, content: file.content };
7718
7877
  });
@@ -7727,31 +7886,31 @@ async function parseSource(value2) {
7727
7886
  return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
7728
7887
  }
7729
7888
  function parseReview(value2) {
7730
- const review = record5(record5(value2)?.review);
7731
- 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");
7889
+ const review = record6(record6(value2)?.review);
7890
+ 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 || !Number.isSafeInteger(review.score) || Number(review.score) < 0 || Number(review.score) > 100 || typeof review.summary !== "string" || !Array.isArray(review.findings) || review.findings.length > 12) throw invalid("review");
7732
7891
  return review;
7733
7892
  }
7734
7893
  function parseCandidate(value2) {
7735
- const candidate = record5(record5(value2)?.candidate);
7894
+ const candidate = record6(record6(value2)?.candidate);
7736
7895
  if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
7737
7896
  throw invalid("candidate");
7738
7897
  }
7739
7898
  return { candidateId: candidate.candidateId, status: candidate.status };
7740
7899
  }
7741
7900
  function parseCollaborationSkills(value2) {
7742
- const items = record5(value2)?.skills;
7901
+ const items = record6(value2)?.skills;
7743
7902
  if (!Array.isArray(items) || items.length > 16) throw invalid("collaboration skills");
7744
7903
  const skillNames2 = /* @__PURE__ */ new Set();
7745
7904
  const toolNames = /* @__PURE__ */ new Set();
7746
7905
  return items.map((item) => {
7747
- const skill = record5(item);
7906
+ const skill = record6(item);
7748
7907
  if (!skill || !validManifestName(skill.name) || skillNames2.has(skill.name) || skill.instructions !== void 0 && (typeof skill.instructions !== "string" || utf8Bytes(skill.instructions) > 32e3) || !Array.isArray(skill.tools) || !skill.tools.length || skill.tools.length > 128) {
7749
7908
  throw invalid("collaboration skill");
7750
7909
  }
7751
7910
  skillNames2.add(skill.name);
7752
7911
  const tools = skill.tools.map((candidate) => {
7753
- const tool = record5(candidate);
7754
- const inputSchema = record5(tool?.inputSchema);
7912
+ const tool = record6(candidate);
7913
+ const inputSchema = record6(tool?.inputSchema);
7755
7914
  if (!tool || !validManifestName(tool.name) || toolNames.has(tool.name) || typeof tool.description !== "string" || utf8Bytes(tool.description) > 8e3 || !inputSchema || jsonBytes(inputSchema) > 64e3 || tool.concurrency !== void 0 && tool.concurrency !== "parallel") {
7756
7915
  throw invalid("collaboration tool");
7757
7916
  }
@@ -7776,12 +7935,12 @@ function parseCollaborationSkills(value2) {
7776
7935
  }
7777
7936
  function validateCollaborationToolRequest(value2) {
7778
7937
  validCommandId(value2.commandId);
7779
- if (typeof value2.toolCallId !== "string" || value2.toolCallId.length > 256 || !/^[^\s\u0000-\u001f\u007f]+$/.test(value2.toolCallId) || !validManifestName(value2.skill) || !validManifestName(value2.tool) || !record5(value2.input) || jsonBytes(value2.input) > 128e3) {
7938
+ if (typeof value2.toolCallId !== "string" || value2.toolCallId.length > 256 || !/^[^\s\u0000-\u001f\u007f]+$/.test(value2.toolCallId) || !validManifestName(value2.skill) || !validManifestName(value2.tool) || !record6(value2.input) || jsonBytes(value2.input) > 128e3) {
7780
7939
  throw new TypeError("invalid Code collaboration tool request");
7781
7940
  }
7782
7941
  }
7783
7942
  function parseCollaborationToolOutput(value2) {
7784
- const output = record5(record5(value2)?.output);
7943
+ const output = record6(record6(value2)?.output);
7785
7944
  if (!output || output.isError !== void 0 && typeof output.isError !== "boolean") {
7786
7945
  throw invalid("collaboration tool");
7787
7946
  }
@@ -7790,7 +7949,7 @@ function parseCollaborationToolOutput(value2) {
7790
7949
  return { content: output.content, ...output.isError === true ? { isError: true } : {} };
7791
7950
  }
7792
7951
  if (!Array.isArray(output.content) || output.content.length > 64 || jsonBytes(output.content) > 1e6 || !output.content.every((block2) => {
7793
- const item = record5(block2);
7952
+ const item = record6(block2);
7794
7953
  return item && ["text", "image", "audio", "document", "tool_use", "tool_result", "thinking"].includes(String(item.type));
7795
7954
  })) throw invalid("collaboration tool");
7796
7955
  return {
@@ -7823,17 +7982,48 @@ function jsonBytes(value2) {
7823
7982
  return Number.POSITIVE_INFINITY;
7824
7983
  }
7825
7984
  }
7826
- var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
7985
+ var record6 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
7827
7986
  var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
7828
7987
  var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
7829
7988
  var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
7830
7989
  var PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
7831
7990
  var FORBIDDEN = /^(?:GIT binary patch|Binary files |rename (?:from|to) |copy (?:from|to) |similarity index |old mode |new mode |deleted file mode 160000|new file mode 160000)/m;
7832
7991
  function stripPatchEnvelope(patch2) {
7833
- if (!/^\*\*\* (?:Begin|End) Patch\s*$/m.test(patch2)) return patch2;
7992
+ if (!/^\*\*\* (?:Begin|End) Patch\s*$/m.test(patch2)) return applyPatchDialectToDiff(patch2);
7834
7993
  const kept = patch2.split("\n").filter((line2) => !/^\*\*\* (?:Begin|End) Patch\s*$/.test(line2));
7835
7994
  const stripped = kept.join("\n");
7836
- return /^diff --git /m.test(stripped) ? stripped : patch2;
7995
+ if (/^diff --git /m.test(stripped)) return stripped;
7996
+ const translated = applyPatchDialectToDiff(stripped);
7997
+ return translated === stripped ? patch2 : translated;
7998
+ }
7999
+ function applyPatchDialectToDiff(patch2) {
8000
+ if (!/^\*\*\* (?:Update|Add|Delete) File: /m.test(patch2)) return patch2;
8001
+ const out = [];
8002
+ let open = false;
8003
+ for (const line2 of patch2.split("\n")) {
8004
+ const file = /^\*\*\* (Update|Add|Delete) File: (.+?)\s*$/.exec(line2);
8005
+ if (file) {
8006
+ const [, verb, raw] = file;
8007
+ const path = raw.trim();
8008
+ if (!PATH.test(path)) return patch2;
8009
+ out.push(`diff --git a/${path} b/${path}`);
8010
+ if (verb === "Add") out.push("new file mode 100644", "--- /dev/null", `+++ b/${path}`);
8011
+ else if (verb === "Delete") out.push(`--- a/${path}`, "+++ /dev/null");
8012
+ else out.push(`--- a/${path}`, `+++ b/${path}`);
8013
+ open = true;
8014
+ continue;
8015
+ }
8016
+ if (/^\*\*\* /.test(line2)) continue;
8017
+ if (!open) continue;
8018
+ if (/^@@/.test(line2)) {
8019
+ out.push("@@ -1 +1 @@");
8020
+ continue;
8021
+ }
8022
+ out.push(line2);
8023
+ }
8024
+ if (!open) return patch2;
8025
+ return `${out.join("\n").replace(/\n+$/, "")}
8026
+ `;
7837
8027
  }
7838
8028
  function validateCodePatch(rawPatch, maxBytes) {
7839
8029
  const patch2 = stripPatchEnvelope(rawPatch);
@@ -7886,17 +8076,20 @@ function resolveCodePath(workspaceDir, path) {
7886
8076
  if (target !== root && !target.startsWith(`${root}${import_path6.sep}`)) throw new TypeError("path escapes the staged workspace");
7887
8077
  return target;
7888
8078
  }
8079
+ function hasContextFreeHunk(patch2) {
8080
+ const bodies = patch2.split(/^@@.*$/m).slice(1);
8081
+ return bodies.some((body) => !body.split("\n").some((line2) => line2.startsWith(" ") && line2.trim().length > 0));
8082
+ }
7889
8083
  function describePatchFailure(patch2, detail) {
7890
8084
  const hunks = patch2.split("\n").filter((line2) => line2.startsWith("@@"));
7891
- const bodies = patch2.split(/^@@.*$/m).slice(1);
7892
- const contextless = bodies.some((body) => !body.split("\n").some((line2) => line2.startsWith(" ") && line2.trim().length > 0));
7893
- const hint = hunks.length > 0 && contextless ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
8085
+ const hint = hunks.length > 0 && hasContextFreeHunk(patch2) ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
7894
8086
  return `patch did not apply: ${detail}${hint}`;
7895
8087
  }
7896
8088
  async function applyCodePatch(workspaceDir, rawPatch, paths2) {
7897
8089
  const patch2 = stripPatchEnvelope(rawPatch);
7898
- await gitApply(workspaceDir, patch2, true);
7899
- await gitApply(workspaceDir, patch2, false);
8090
+ const zero = hasContextFreeHunk(patch2);
8091
+ await gitApply(workspaceDir, patch2, true, zero);
8092
+ await gitApply(workspaceDir, patch2, false, zero);
7900
8093
  for (const path of paths2) {
7901
8094
  try {
7902
8095
  const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
@@ -7908,9 +8101,16 @@ async function applyCodePatch(workspaceDir, rawPatch, paths2) {
7908
8101
  }
7909
8102
  }
7910
8103
  }
7911
- function gitApply(cwd, patch2, check) {
8104
+ function gitApply(cwd, patch2, check, unidiffZero = false) {
7912
8105
  return new Promise((accept, reject) => {
7913
- const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
8106
+ const args = [
8107
+ "apply",
8108
+ "--recount",
8109
+ ...unidiffZero ? ["--unidiff-zero"] : [],
8110
+ "--whitespace=nowarn",
8111
+ ...check ? ["--check"] : [],
8112
+ "-"
8113
+ ];
7914
8114
  const child = (0, import_child_process4.spawn)("git", args, {
7915
8115
  cwd,
7916
8116
  shell: false,
@@ -7919,8 +8119,8 @@ function gitApply(cwd, patch2, check) {
7919
8119
  });
7920
8120
  let stderr2 = "";
7921
8121
  child.stderr.setEncoding("utf8");
7922
- child.stderr.on("data", (text22) => {
7923
- if (stderr2.length < 4e3) stderr2 += text22.slice(0, 4e3);
8122
+ child.stderr.on("data", (text4) => {
8123
+ if (stderr2.length < 4e3) stderr2 += text4.slice(0, 4e3);
7924
8124
  });
7925
8125
  child.once("error", reject);
7926
8126
  child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr2.trim().slice(0, 500)))));
@@ -8166,7 +8366,7 @@ function validate2(input) {
8166
8366
  maximumFiles: policy.maximumFiles ?? 2e4,
8167
8367
  maximumBytes: policy.maximumBytes ?? 512 * 1024 * 1024
8168
8368
  };
8169
- if ([...result.testPathPrefixes, ...result.testPathSuffixes].some((rule) => !RULE.test(rule)) || !integer(result.maximumChangedTests, 0, 1e4) || !integer(result.maximumPatchBytes, 1, 4 * 1024 * 1024) || !integer(result.maximumFiles, 1, 1e5) || !integer(result.maximumBytes, 1, 2 * 1024 * 1024 * 1024)) {
8369
+ if ([...result.testPathPrefixes, ...result.testPathSuffixes].some((rule) => !RULE.test(rule)) || !integer2(result.maximumChangedTests, 0, 1e4) || !integer2(result.maximumPatchBytes, 1, 4 * 1024 * 1024) || !integer2(result.maximumFiles, 1, 1e5) || !integer2(result.maximumBytes, 1, 2 * 1024 * 1024 * 1024)) {
8170
8370
  throw new TypeError("clean verification policy exceeds its bounds");
8171
8371
  }
8172
8372
  return result;
@@ -8217,7 +8417,7 @@ function hashFile(path) {
8217
8417
  });
8218
8418
  }
8219
8419
  function checkedResult(result, maximumOutputBytes) {
8220
- if (!integer(result.exitCode, 0, 255) || !integer(result.durationMs, 0, 30 * 6e4) || typeof result.stdout !== "string" || typeof result.stderr !== "string" || typeof result.outputLimitExceeded !== "boolean" || typeof result.timedOut !== "boolean") {
8420
+ if (!integer2(result.exitCode, 0, 255) || !integer2(result.durationMs, 0, 30 * 6e4) || typeof result.stdout !== "string" || typeof result.stderr !== "string" || typeof result.outputLimitExceeded !== "boolean" || typeof result.timedOut !== "boolean") {
8221
8421
  throw new TypeError("recipe executor returned an invalid result");
8222
8422
  }
8223
8423
  const bytes = Buffer.byteLength(result.stdout) + Buffer.byteLength(result.stderr);
@@ -8267,7 +8467,7 @@ function digestJson(value2) {
8267
8467
  function digestBytes(value2) {
8268
8468
  return `sha256:${(0, import_crypto3.createHash)("sha256").update(value2).digest("hex")}`;
8269
8469
  }
8270
- function integer(value2, minimum, maximum) {
8470
+ function integer2(value2, minimum, maximum) {
8271
8471
  return Number.isSafeInteger(value2) && value2 >= minimum && value2 <= maximum;
8272
8472
  }
8273
8473
  async function prepareRuntimeCheckpoint(input) {
@@ -8294,9 +8494,9 @@ async function prepareRuntimeCheckpoint(input) {
8294
8494
  if (evidence.receipt.outcome === "passed") {
8295
8495
  verification = evidence.receipt;
8296
8496
  review = await input.review(patch2, verification);
8297
- note = review.verdict === "approved" ? "Clean verification and independent review passed" : "Clean verification passed; independent review rejected the candidate";
8497
+ note = describeReview(review);
8298
8498
  } else {
8299
- note = `Clean verification failed: ${evidence.receipt.recipes.filter((recipe2) => recipe2.status !== "passed").map((recipe2) => `${recipe2.recipeId}=${recipe2.status}`).join(", ")}`;
8499
+ note = describeGateFailure(evidence);
8300
8500
  }
8301
8501
  } catch (cause) {
8302
8502
  note = `Candidate verification or review failed closed: ${message(cause)}`;
@@ -8321,6 +8521,23 @@ async function prepareRuntimeCheckpoint(input) {
8321
8521
  });
8322
8522
  return { checkpoint, verification, review, note };
8323
8523
  }
8524
+ function describeReview(review) {
8525
+ const findings = review.findings.map((finding) => ` - [${finding.severity}] ${finding.detail}`).join("\n");
8526
+ const head = review.verdict === "approved" ? `Clean verification and independent review passed (score ${review.score}/100).` : `Clean verification passed; independent review REJECTED this candidate (score ${review.score}/100). Address the findings and checkpoint again.`;
8527
+ return [head, review.summary, findings].filter(Boolean).join("\n").slice(0, 4e3);
8528
+ }
8529
+ function describeGateFailure(evidence) {
8530
+ const failed = evidence.receipt.recipes.filter((recipe2) => recipe2.status !== "passed");
8531
+ const detail = failed.map((recipe2) => {
8532
+ const log = evidence.logs.find((entry) => entry.recipeId === recipe2.recipeId);
8533
+ const output = `${log?.stdout ?? ""}
8534
+ ${log?.stderr ?? ""}`.trim();
8535
+ return `${recipe2.recipeId}=${recipe2.status}${output ? `
8536
+ ${output.slice(0, 1500)}` : ""}`;
8537
+ }).join("\n\n");
8538
+ return `Clean verification failed. Fix this and checkpoint again:
8539
+ ${detail}`.slice(0, 4e3);
8540
+ }
8324
8541
  var message = (value2) => (value2 instanceof Error ? value2.message : String(value2)).slice(0, 500);
8325
8542
  var CodeRuntimeCheckpointManager = class {
8326
8543
  constructor(options) {
@@ -9339,8 +9556,14 @@ function optionalInteger(value2) {
9339
9556
  if (!Number.isSafeInteger(value2) || value2 < 1) throw new TypeError("line bounds must be positive integers");
9340
9557
  return value2;
9341
9558
  }
9559
+ var DEFAULT_FAILURE_REASON2 = {
9560
+ "tool denied by CaMeL policy": "tool denied by CaMeL policy",
9561
+ "review sessions are read-only": "review session is read-only; workspace changes are not permitted"
9562
+ };
9342
9563
  function response(request3, ok, content2, details) {
9343
- return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
9564
+ if (ok) return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
9565
+ const failureReason = typeof details?.failureReason === "string" && details.failureReason || DEFAULT_FAILURE_REASON2[content2] || content2 || "tool request failed; inspect the tool input and workspace state";
9566
+ return { requestId: request3.requestId, ok, content: content2, details: { ...details, failureReason } };
9344
9567
  }
9345
9568
  var cache = /* @__PURE__ */ new Map();
9346
9569
  function workspaceGraphs(workspaceDir, paths2) {
@@ -9916,120 +10139,6 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
9916
10139
  }
9917
10140
  var digestRuntimeValue = (value2) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value2).digest("hex")}`;
9918
10141
  var runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
9919
- var text3 = (value2, maximum) => {
9920
- if (typeof value2 !== "string") return void 0;
9921
- const bounded = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
9922
- return bounded ? bounded.slice(0, maximum) : void 0;
9923
- };
9924
- var integer2 = (value2) => Number.isSafeInteger(value2) && Number(value2) >= 0 ? Number(value2) : void 0;
9925
- var record32 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
9926
- var excerpt = (value2, tail = false) => {
9927
- if (typeof value2 !== "string") return void 0;
9928
- const safe = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
9929
- const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
9930
- if (!source) return void 0;
9931
- const lines = source.split("\n").filter((line2) => line2.trim()).map((line2) => line2.slice(0, 240));
9932
- const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
9933
- return text3(selected.join("\n"), 2400);
9934
- };
9935
- var paths = (value2) => {
9936
- if (!Array.isArray(value2)) return void 0;
9937
- const items = value2.flatMap((item) => {
9938
- const path = text3(item, 1024);
9939
- return path ? [path] : [];
9940
- }).slice(0, 12);
9941
- return items.length ? items : void 0;
9942
- };
9943
- function patchStats(value2) {
9944
- if (typeof value2 !== "string") return {};
9945
- let additions = 0;
9946
- let deletions = 0;
9947
- for (const line2 of value2.slice(0, 262144).split("\n")) {
9948
- if (line2.startsWith("+++") || line2.startsWith("---")) continue;
9949
- if (line2.startsWith("+")) additions += 1;
9950
- else if (line2.startsWith("-")) deletions += 1;
9951
- }
9952
- return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
9953
- }
9954
- function searchResults(value2) {
9955
- if (typeof value2 !== "string") return void 0;
9956
- const results = value2.split("\n").flatMap((line2) => {
9957
- const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line2);
9958
- if (!match) return [];
9959
- const lineNumber = Number(match[2]);
9960
- const itemText = text3(match[3], 240);
9961
- if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
9962
- return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
9963
- }).slice(0, 5);
9964
- return results.length ? results : void 0;
9965
- }
9966
- function codeToolRequestPresentation(request3) {
9967
- const input = request3.input;
9968
- if (request3.tool === "sandbox.read") {
9969
- const path = text3(input.path, 1024);
9970
- if (!path) return void 0;
9971
- const startLine = integer2(input.startLine);
9972
- const endLine = integer2(input.endLine);
9973
- return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
9974
- }
9975
- if (request3.tool === "sandbox.list") {
9976
- const scope = text3(input.prefix, 1024);
9977
- return { kind: "list", ...scope ? { scope } : {} };
9978
- }
9979
- if (request3.tool === "sandbox.search" || request3.tool === "sandbox.overview" || request3.tool === "sandbox.where_is" || request3.tool === "sandbox.who_imports" || request3.tool === "sandbox.who_touches") {
9980
- const query = text3(input.query, 512);
9981
- const scope = request3.tool === "sandbox.search" ? text3(input.prefix, 1024) : void 0;
9982
- if (request3.tool === "sandbox.search" && !query) return void 0;
9983
- return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
9984
- }
9985
- if (request3.tool === "sandbox.apply_patch") {
9986
- return { kind: "patch", ...patchStats(input.patch) };
9987
- }
9988
- const recipeId = text3(input.recipeId, 120);
9989
- return recipeId ? { kind: "recipe", recipeId } : void 0;
9990
- }
9991
- function codeToolResultPresentation(request3, response2) {
9992
- const started = codeToolRequestPresentation(request3);
9993
- if (!started || !response2.ok) return started;
9994
- const details = record32(response2.details);
9995
- if (started.kind === "read") {
9996
- return {
9997
- ...started,
9998
- ...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
9999
- ...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
10000
- ...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
10001
- };
10002
- }
10003
- if (started.kind === "list") {
10004
- const listed = response2.content.split("\n").filter((line2) => line2 && !line2.startsWith("\u2026") && !line2.startsWith("Workspace ")).map((line2) => text3(line2, 1024)).filter((line2) => Boolean(line2)).slice(0, 8);
10005
- return {
10006
- ...started,
10007
- ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
10008
- ...listed.length ? { paths: listed } : {}
10009
- };
10010
- }
10011
- if (started.kind === "query") {
10012
- const results = request3.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
10013
- const resultExcerpt = request3.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
10014
- return {
10015
- ...started,
10016
- ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
10017
- ...results ? { results } : {},
10018
- ...resultExcerpt ? { excerpt: resultExcerpt } : {}
10019
- };
10020
- }
10021
- if (started.kind === "patch") {
10022
- return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
10023
- }
10024
- const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
10025
- return {
10026
- ...started,
10027
- ...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
10028
- ...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
10029
- ...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
10030
- ...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
10031
- };
10032
- }
10033
10142
  function codeRuntimeAcknowledgementGate(signal) {
10034
10143
  let settle;
10035
10144
  let settled = false;
@@ -10047,6 +10156,55 @@ function codeRuntimeAcknowledgementGate(signal) {
10047
10156
  else signal.addEventListener("abort", onAbort, { once: true });
10048
10157
  return { ready, release };
10049
10158
  }
10159
+ function observeCodeRuntimeSessionSkills(command, skills, emit4) {
10160
+ return skills.map((skill) => ({
10161
+ ...skill,
10162
+ tools: skill.tools.map((tool) => {
10163
+ if (!tool.handler) return tool;
10164
+ const handler = tool.handler;
10165
+ return {
10166
+ ...tool,
10167
+ handler: async (input, context) => {
10168
+ const startedAt = Date.now();
10169
+ const operationId = digestRuntimeValue(
10170
+ `${command.commandId}:${skill.name}:${tool.name}:${context.toolCallId ?? "missing"}`
10171
+ );
10172
+ await emit4({
10173
+ type: "collaboration",
10174
+ phase: "started",
10175
+ skill: skill.name,
10176
+ tool: tool.name,
10177
+ operationId
10178
+ }).catch(() => void 0);
10179
+ try {
10180
+ const output = await handler(input, context);
10181
+ await emit4({
10182
+ type: "collaboration",
10183
+ phase: "completed",
10184
+ skill: skill.name,
10185
+ tool: tool.name,
10186
+ operationId,
10187
+ ok: output.isError !== true,
10188
+ durationMs: Date.now() - startedAt
10189
+ }).catch(() => void 0);
10190
+ return output;
10191
+ } catch (cause) {
10192
+ await emit4({
10193
+ type: "collaboration",
10194
+ phase: "completed",
10195
+ skill: skill.name,
10196
+ tool: tool.name,
10197
+ operationId,
10198
+ ok: false,
10199
+ durationMs: Date.now() - startedAt
10200
+ }).catch(() => void 0);
10201
+ throw cause;
10202
+ }
10203
+ }
10204
+ };
10205
+ })
10206
+ }));
10207
+ }
10050
10208
  var TheseusRuntimeEngine = class {
10051
10209
  constructor(options) {
10052
10210
  this.options = options;
@@ -10243,7 +10401,7 @@ var TheseusRuntimeEngine = class {
10243
10401
  event: (event) => this.#event(command, event, active.conversationRefs)
10244
10402
  });
10245
10403
  await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
10246
- const extraSkills = await sessionSkillsFor(this.options, command);
10404
+ const extraSkills = observeCodeRuntimeSessionSkills(command, await sessionSkillsFor(this.options, command), (event) => this.#event(command, event, active.conversationRefs));
10247
10405
  const result = await this.#attempt({
10248
10406
  inference,
10249
10407
  broker,
@@ -10282,36 +10440,11 @@ var TheseusRuntimeEngine = class {
10282
10440
  }
10283
10441
  /** Report every brokered effect as it starts and finishes. */
10284
10442
  #observed(command, active, broker) {
10285
- return {
10286
- execute: async (context, request3) => {
10287
- const startedAt = Date.now();
10288
- const operationId = digestRuntimeValue(`${command.commandId}:${request3.requestId}`);
10289
- const startedPresentation = codeToolRequestPresentation(request3);
10290
- await this.#event(
10291
- command,
10292
- {
10293
- type: "tool",
10294
- phase: "started",
10295
- tool: request3.tool,
10296
- operationId,
10297
- ...startedPresentation ? { presentation: startedPresentation } : {}
10298
- },
10299
- active.conversationRefs
10300
- ).catch(() => void 0);
10301
- const response2 = await broker.execute(context, request3);
10302
- const completedPresentation = codeToolResultPresentation(request3, response2);
10303
- await this.#event(command, {
10304
- type: "tool",
10305
- phase: "completed",
10306
- tool: request3.tool,
10307
- ok: response2.ok,
10308
- durationMs: Date.now() - startedAt,
10309
- operationId,
10310
- ...completedPresentation ? { presentation: completedPresentation } : {}
10311
- }, active.conversationRefs).catch(() => void 0);
10312
- return response2;
10313
- }
10314
- };
10443
+ return observedBroker({
10444
+ broker,
10445
+ operationIdFor: (requestId) => digestRuntimeValue(`${command.commandId}:${requestId}`),
10446
+ emit: (event) => this.#event(command, event, active.conversationRefs).catch(() => void 0)
10447
+ });
10315
10448
  }
10316
10449
  async #checkpoint(command) {
10317
10450
  const active = this.#active.get(command.sessionId);
@@ -10621,6 +10754,19 @@ function digestText(value2) {
10621
10754
  // src/code-runtime-config.ts
10622
10755
  var CODE_NODE_IMAGE = "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd";
10623
10756
  var CODE_BUILD_RECIPES = Object.freeze([{
10757
+ id: "odla-code-gates",
10758
+ image: CODE_NODE_IMAGE,
10759
+ // Selects the repository's fast, no-capability gates from the same manifest
10760
+ // CI and `npm run preflight` read, so a new gate is picked up without a
10761
+ // second list to keep in sync. The container has no shell: this is argv.
10762
+ command: ["node", "scripts/recipe-gates.mjs"],
10763
+ timeoutMs: 18e4,
10764
+ maxOutputBytes: 1024 * 1024,
10765
+ cpus: 1,
10766
+ // check:secrets holds ~2,500 source files in memory at once.
10767
+ memory: "1g",
10768
+ pids: 128
10769
+ }, {
10624
10770
  id: "odla-code-contracts",
10625
10771
  image: CODE_NODE_IMAGE,
10626
10772
  command: [
@@ -10800,20 +10946,20 @@ async function runCodeRuntime(input) {
10800
10946
  }
10801
10947
  }
10802
10948
  function parseConnection(value2, appId, appEnv) {
10803
- const root = record6(value2);
10804
- const host = record6(root?.host);
10805
- const offer = record6(root?.offer);
10806
- const binding = record6(root?.binding);
10949
+ const root = record7(value2);
10950
+ const host = record7(root?.host);
10951
+ const offer = record7(root?.offer);
10952
+ const binding = record7(root?.binding);
10807
10953
  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)) {
10808
10954
  throw new Error("connect Code host returned an invalid response");
10809
10955
  }
10810
10956
  return root;
10811
10957
  }
10812
10958
  function apiFailure(action2, status, value2) {
10813
- const message2 = record6(record6(value2)?.error)?.message;
10959
+ const message2 = record7(record7(value2)?.error)?.message;
10814
10960
  return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
10815
10961
  }
10816
- function record6(value2) {
10962
+ function record7(value2) {
10817
10963
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
10818
10964
  }
10819
10965
 
@@ -11319,6 +11465,14 @@ Usage:
11319
11465
  odla-ai ai models [--config odla.config.mjs] [--env dev] [--provider <id>] [--json]
11320
11466
  odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
11321
11467
  odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
11468
+ odla-ai promotion plan --file <promotion-plan.json> [--env dev] [--json]
11469
+ odla-ai promotion inspect <plan-id> [--env dev] [--json]
11470
+ odla-ai promotion status <plan-id> [--env dev] [--json]
11471
+ odla-ai promotion apply <plan-id> --approval <approval-id> [--idempotency-key <key>] [--env dev] [--json]
11472
+ odla-ai promotion rollback <plan-id> --approval <approval-id> [--idempotency-key <key>] [--env dev] [--json]
11473
+ odla-ai promotion next <plan-id> <operation-id> [--env dev] [--json]
11474
+ odla-ai promotion record <plan-id> <operation-id> <step-id> --file <step-receipt.json> [--env dev] [--json]
11475
+ odla-ai promotion cancel <plan-id> <operation-id> [--env dev] [--json]
11322
11476
  odla-ai operations get <operation-id> [--json]
11323
11477
  odla-ai operations wait <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
11324
11478
  odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
@@ -11485,6 +11639,11 @@ Commands:
11485
11639
  init Create a generic odla.config.mjs plus starter schema/rules files.
11486
11640
  doctor Validate and summarize the project config without network calls.
11487
11641
  config Diff Registry intent, freeze a CAS-bound plan, and conditionally apply its safe actions.
11642
+ promotion Plan and inspect exact selective-release bundles without target
11643
+ mutation, then queue and resume a human-approved apply or rollback
11644
+ from an enrolled device. Each target step is leased and receipt-
11645
+ journaled; approval remains bound to the immutable plan digest,
11646
+ target set, action, and expiry, so every mismatch fails closed.
11488
11647
  operations Inspect or wait on one exact, durable config-operation receipt.
11489
11648
  calendar Inspect, connect, or disconnect the live Google booking connection.
11490
11649
  app Archive (suspend, data retained), restore, export, import, or
@@ -12360,17 +12519,17 @@ function collectEntityFields(entity, parsed, allowClear) {
12360
12519
  if (entity === "task" && fields.column === "ready") fields.column = "todo";
12361
12520
  return fields;
12362
12521
  }
12363
- function statusCol(entity, record11) {
12364
- if (entity === "bug") return `${record11.status ?? ""}/${record11.severity ?? ""}`;
12522
+ function statusCol(entity, record12) {
12523
+ if (entity === "bug") return `${record12.status ?? ""}/${record12.severity ?? ""}`;
12365
12524
  if (entity === "task") {
12366
- const state2 = record11.column === "todo" ? "ready" : String(record11.column ?? "");
12367
- return record11.revision ? `${state2}; r${record11.revision}` : state2;
12525
+ const state2 = record12.column === "todo" ? "ready" : String(record12.column ?? "");
12526
+ return record12.revision ? `${state2}; r${record12.revision}` : state2;
12368
12527
  }
12369
- return String(record11.status ?? "");
12528
+ return String(record12.status ?? "");
12370
12529
  }
12371
- function referenceMarkup(entity, record11) {
12372
- const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
12373
- return `@[${label}](pm:${entity}/${record11.id})`;
12530
+ function referenceMarkup(entity, record12) {
12531
+ const label = (record12.title?.trim() || `${entity} ${record12.id}`).replaceAll("]", ")");
12532
+ return `@[${label}](pm:${entity}/${record12.id})`;
12374
12533
  }
12375
12534
  var STUDIO_SECTION = {
12376
12535
  goal: "goals",
@@ -12384,13 +12543,13 @@ function studioRecordUrl(ctx, entity, id2) {
12384
12543
  ctx.platformUrl
12385
12544
  ).href;
12386
12545
  }
12387
- function studioRecordLink(ctx, entity, record11) {
12388
- const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
12389
- return `[${label}](${studioRecordUrl(ctx, entity, record11.id)})`;
12546
+ function studioRecordLink(ctx, entity, record12) {
12547
+ const label = (record12.title?.trim() || `${entity} ${record12.id}`).replaceAll("]", ")");
12548
+ return `[${label}](${studioRecordUrl(ctx, entity, record12.id)})`;
12390
12549
  }
12391
- function printRecord(ctx, entity, record11) {
12550
+ function printRecord(ctx, entity, record12) {
12392
12551
  ctx.out.log(
12393
- `${record11.id} [${statusCol(entity, record11)}] ${record11.appId} ${studioRecordLink(ctx, entity, record11)}`
12552
+ `${record12.id} [${statusCol(entity, record12)}] ${record12.appId} ${studioRecordLink(ctx, entity, record12)}`
12394
12553
  );
12395
12554
  }
12396
12555
  function emit2(ctx, value2, human) {
@@ -12444,8 +12603,8 @@ async function pmAdd(ctx, entity, parsed) {
12444
12603
  input,
12445
12604
  mutationId: writeMutationId2(parsed)
12446
12605
  });
12447
- const record11 = { id: res.id, appId, title: String(input.title) };
12448
- emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity, record11)}`));
12606
+ const record12 = { id: res.id, appId, title: String(input.title) };
12607
+ emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity, record12)}`));
12449
12608
  }
12450
12609
  var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
12451
12610
  var UUID_PREFIX = /^[0-9a-f-]{8,35}$/i;
@@ -12465,7 +12624,7 @@ async function resolvePmId(ctx, entity, supplied) {
12465
12624
  "GET",
12466
12625
  `/${entity}?limit=${limit}&offset=${offset}`
12467
12626
  );
12468
- matches.push(...page2.records.map((record11) => record11.id).filter((id2) => id2.toLowerCase().startsWith(prefix)));
12627
+ matches.push(...page2.records.map((record12) => record12.id).filter((id2) => id2.toLowerCase().startsWith(prefix)));
12469
12628
  offset += page2.records.length;
12470
12629
  if (matches.length > 1 || page2.records.length === 0 || offset >= page2.total) break;
12471
12630
  }
@@ -12479,17 +12638,17 @@ async function resolvePmId(ctx, entity, supplied) {
12479
12638
  }
12480
12639
  async function pmGet(ctx, entity, id2) {
12481
12640
  const resolved = await resolvePmId(ctx, entity, id2);
12482
- const { record: record11 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(resolved)}`);
12483
- emit2(ctx, record11, () => printRecord(ctx, entity, record11));
12641
+ const { record: record12 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(resolved)}`);
12642
+ emit2(ctx, record12, () => printRecord(ctx, entity, record12));
12484
12643
  }
12485
12644
  async function pmReference(ctx, entity, id2) {
12486
- const { record: record11 } = await pmRequest(
12645
+ const { record: record12 } = await pmRequest(
12487
12646
  ctx,
12488
12647
  "GET",
12489
12648
  `/${entity}/${encodeURIComponent(id2)}`
12490
12649
  );
12491
- const markup = referenceMarkup(entity, record11);
12492
- emit2(ctx, { kind: `pm:${entity}`, id: record11.id, label: record11.title ?? "", markup }, () => {
12650
+ const markup = referenceMarkup(entity, record12);
12651
+ emit2(ctx, { kind: `pm:${entity}`, id: record12.id, label: record12.title ?? "", markup }, () => {
12493
12652
  ctx.out.log(markup);
12494
12653
  });
12495
12654
  }
@@ -12574,16 +12733,16 @@ var PER_ENTITY = {
12574
12733
  decision: ["status", "body", "supersedesId"],
12575
12734
  bug: ["status", "severity", "description", "goalId", "assigneeId", "decisionId"]
12576
12735
  };
12577
- function leanRecord(entity, record11) {
12736
+ function leanRecord(entity, record12) {
12578
12737
  const out = {};
12579
12738
  for (const key of [...COMMON, ...PER_ENTITY[entity]]) {
12580
- const value2 = record11[key];
12739
+ const value2 = record12[key];
12581
12740
  if (value2 !== void 0 && value2 !== null) out[key] = value2;
12582
12741
  }
12583
12742
  return out;
12584
12743
  }
12585
12744
  function leanRecords(entity, records, verbose) {
12586
- return verbose ? records : records.map((record11) => leanRecord(entity, record11));
12745
+ return verbose ? records : records.map((record12) => leanRecord(entity, record12));
12587
12746
  }
12588
12747
 
12589
12748
  // src/pm-intake.ts
@@ -12616,16 +12775,18 @@ async function pmNext(ctx, parsed) {
12616
12775
  // One request for both live columns; the split below is over open work only.
12617
12776
  listFiltered(ctx, "task", appId, projectId, { column: `${READY_COLUMN},doing` })
12618
12777
  ]);
12619
- const ready = tasks.filter((record11) => record11.column === READY_COLUMN);
12620
- const doing = tasks.filter((record11) => record11.column === "doing");
12778
+ const ready = tasks.filter((record12) => record12.column === READY_COLUMN);
12779
+ const doing = tasks.filter((record12) => record12.column === "doing");
12621
12780
  const guidance = !goals.length ? "discuss alignment with the user before creating or claiming project work" : !ready.length ? 'refine a linked Backlog task and mark it Ready with "pm task ready <id>"' : `claim the top Ready task in one call: odla-ai pm start --app ${appId}`;
12781
+ const monitor = `odla-ai pm watch --app ${appId} --jsonl`;
12622
12782
  const result = {
12623
12783
  appId,
12624
12784
  projectId,
12625
12785
  openGoals: leanRecords("goal", goals, verbose),
12626
12786
  doing: leanRecords("task", doing, verbose),
12627
12787
  ready: leanRecords("task", ready, verbose),
12628
- next: guidance
12788
+ next: guidance,
12789
+ monitor
12629
12790
  };
12630
12791
  emit2(ctx, result, () => {
12631
12792
  ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
@@ -12636,9 +12797,10 @@ async function pmNext(ctx, parsed) {
12636
12797
  ]) {
12637
12798
  ctx.out.log(`${label}:`);
12638
12799
  if (!records.length) ctx.out.log("- (none)");
12639
- else for (const record11 of records) printRecord(ctx, entity, record11);
12800
+ else for (const record12 of records) printRecord(ctx, entity, record12);
12640
12801
  }
12641
12802
  ctx.out.log(`next: ${guidance}`);
12803
+ ctx.out.log(`monitor: ${monitor}`);
12642
12804
  });
12643
12805
  }
12644
12806
  async function pmHandoff(ctx, parsed) {
@@ -12650,17 +12812,20 @@ async function pmHandoff(ctx, parsed) {
12650
12812
  listFiltered(ctx, "bug", appId, projectId, { status: OPEN_BUG_STATUSES })
12651
12813
  ]);
12652
12814
  const clean4 = !goals.length && !tasks.length && !bugs.length;
12815
+ const monitor = `odla-ai pm watch --app ${appId} --jsonl`;
12653
12816
  const result = {
12654
12817
  appId,
12655
12818
  projectId,
12656
12819
  unmetGoals: leanRecords("goal", goals, verbose),
12657
12820
  activeTasks: leanRecords("task", tasks, verbose),
12658
12821
  openBugs: leanRecords("bug", bugs, verbose),
12659
- clean: clean4
12822
+ clean: clean4,
12823
+ monitor
12660
12824
  };
12661
12825
  emit2(ctx, result, () => {
12662
12826
  if (clean4) {
12663
12827
  ctx.out.log(`${appId}: no unresolved PM work`);
12828
+ ctx.out.log(`monitor: ${monitor}`);
12664
12829
  return;
12665
12830
  }
12666
12831
  ctx.out.log(`${appId}: authoritative PM handoff`);
@@ -12671,8 +12836,9 @@ async function pmHandoff(ctx, parsed) {
12671
12836
  ]) {
12672
12837
  ctx.out.log(`${label}:`);
12673
12838
  if (!records.length) ctx.out.log("- (none)");
12674
- else for (const record11 of records) printRecord(ctx, entity, record11);
12839
+ else for (const record12 of records) printRecord(ctx, entity, record12);
12675
12840
  }
12841
+ ctx.out.log(`monitor: ${monitor}`);
12676
12842
  });
12677
12843
  }
12678
12844
 
@@ -12745,14 +12911,14 @@ async function pmStart(ctx, parsed) {
12745
12911
 
12746
12912
  // src/pm-links.ts
12747
12913
  async function pmLink(ctx, entity, id2) {
12748
- const { record: record11 } = await pmRequest(
12914
+ const { record: record12 } = await pmRequest(
12749
12915
  ctx,
12750
12916
  "GET",
12751
12917
  `/${entity}/${encodeURIComponent(id2)}`
12752
12918
  );
12753
- const url = studioRecordUrl(ctx, entity, record11.id);
12754
- const markdown = studioRecordLink(ctx, entity, record11);
12755
- emit2(ctx, { kind: entity, id: record11.id, label: record11.title ?? "", url, markdown }, () => {
12919
+ const url = studioRecordUrl(ctx, entity, record12.id);
12920
+ const markdown = studioRecordLink(ctx, entity, record12);
12921
+ emit2(ctx, { kind: entity, id: record12.id, label: record12.title ?? "", url, markdown }, () => {
12756
12922
  ctx.out.log(markdown);
12757
12923
  });
12758
12924
  }
@@ -12879,16 +13045,16 @@ async function page(ctx, appId, cursor) {
12879
13045
  }
12880
13046
  return data;
12881
13047
  }
12882
- function recordState(record11) {
12883
- if (record11.column) return record11.column === "todo" ? "ready" : record11.column;
12884
- return String(record11.status ?? "");
13048
+ function recordState(record12) {
13049
+ if (record12.column) return record12.column === "todo" ? "ready" : record12.column;
13050
+ return String(record12.status ?? "");
12885
13051
  }
12886
13052
  function eventRecord(event) {
12887
13053
  return event.payload.payload;
12888
13054
  }
12889
13055
  function eventLabel(event) {
12890
- const record11 = eventRecord(event);
12891
- if (record11) return String(record11.title ?? event.payload.entityId);
13056
+ const record12 = eventRecord(event);
13057
+ if (record12) return String(record12.title ?? event.payload.entityId);
12892
13058
  const body = event.payload.message?.body?.replace(/\s+/g, " ").trim();
12893
13059
  return body || event.payload.entityId;
12894
13060
  }
@@ -12896,10 +13062,10 @@ function report3(ctx, parsed, result) {
12896
13062
  if (ctx.json) ctx.out.log(JSON.stringify(result, null, 2));
12897
13063
  else if (parsed.options.jsonl !== true && result.found) {
12898
13064
  for (const event of result.events ?? []) {
12899
- const record11 = eventRecord(event);
12900
- const state2 = record11 ? recordState(record11) : "comment";
13065
+ const record12 = eventRecord(event);
13066
+ const state2 = record12 ? recordState(record12) : "comment";
12901
13067
  ctx.out.log(
12902
- `${event.id} ${event.type} ${state2}${record11?.revision ? `; r${record11.revision}` : ""} ${eventLabel(event)}`
13068
+ `${event.id} ${event.type} ${state2}${record12?.revision ? `; r${record12.revision}` : ""} ${eventLabel(event)}`
12903
13069
  );
12904
13070
  }
12905
13071
  }
@@ -12973,8 +13139,8 @@ async function pmWatch(ctx, parsed) {
12973
13139
  }
12974
13140
  firstSuccess = false;
12975
13141
  const matching = current.events.filter((event) => {
12976
- const record11 = eventRecord(event);
12977
- const state2 = record11 ? recordState(record11).toLowerCase() : "";
13142
+ const record12 = eventRecord(event);
13143
+ const state2 = record12 ? recordState(record12).toLowerCase() : "";
12978
13144
  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);
12979
13145
  });
12980
13146
  for (const event of matching) {
@@ -13405,17 +13571,17 @@ async function platformStatus(parsed, deps) {
13405
13571
  }
13406
13572
  }
13407
13573
  function isPlatformStatus(value2) {
13408
- if (!record7(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
13409
- if (!record7(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
13410
- if (!record7(value2.catalog) || !record7(value2.summary)) return false;
13574
+ if (!record8(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
13575
+ if (!record8(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
13576
+ if (!record8(value2.catalog) || !record8(value2.summary)) return false;
13411
13577
  return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
13412
13578
  }
13413
13579
  function apiMessage(value2) {
13414
- if (!record7(value2)) return "request failed";
13415
- const error = record7(value2.error) ? value2.error : value2;
13580
+ if (!record8(value2)) return "request failed";
13581
+ const error = record8(value2.error) ? value2.error : value2;
13416
13582
  return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
13417
13583
  }
13418
- function record7(value2) {
13584
+ function record8(value2) {
13419
13585
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
13420
13586
  }
13421
13587
 
@@ -13456,7 +13622,7 @@ function statusVerdict(reads) {
13456
13622
  severity: "degraded"
13457
13623
  });
13458
13624
  }
13459
- const performance = record8(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
13625
+ const performance = record9(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
13460
13626
  if (performance?.status === "unavailable") {
13461
13627
  reasons.push({
13462
13628
  source: "liveSync",
@@ -13537,7 +13703,7 @@ function statusVerdict(reads) {
13537
13703
  reasons
13538
13704
  };
13539
13705
  }
13540
- function record8(value2) {
13706
+ function record9(value2) {
13541
13707
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
13542
13708
  }
13543
13709
  function numeric2(value2) {
@@ -13565,7 +13731,7 @@ function printO11yStatus(status, out) {
13565
13731
  out.log(
13566
13732
  `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
13567
13733
  );
13568
- const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record9) : [];
13734
+ const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record10) : [];
13569
13735
  const requests = routes.reduce(
13570
13736
  (total, row) => total + numeric3(row.requests),
13571
13737
  0
@@ -13577,39 +13743,39 @@ function printO11yStatus(status, out) {
13577
13743
  out.log(
13578
13744
  `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
13579
13745
  );
13580
- const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record9) : [];
13746
+ const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record10) : [];
13581
13747
  out.log(
13582
13748
  `application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
13583
13749
  (row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
13584
13750
  ).join(", ") : "none observed"}`
13585
13751
  );
13586
13752
  out.log(liveSyncLine(status.liveSync));
13587
- const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
13753
+ const canaryDurations = record10(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
13588
13754
  out.log(
13589
13755
  `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
13590
13756
  );
13591
- const collectorIngest = record9(status.collector.body.ingest) ? status.collector.body.ingest : {};
13592
- const collectorStorage = record9(collectorIngest.storage) ? collectorIngest.storage : {};
13757
+ const collectorIngest = record10(status.collector.body.ingest) ? status.collector.body.ingest : {};
13758
+ const collectorStorage = record10(collectorIngest.storage) ? collectorIngest.storage : {};
13593
13759
  out.log(
13594
13760
  `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
13595
13761
  );
13596
- const providerMetrics = record9(status.provider.body.metrics) ? status.provider.body.metrics : {};
13597
- const providerCapacity = record9(status.provider.body.capacity) ? status.provider.body.capacity : {};
13598
- const workerMemory = record9(providerCapacity.memory) ? providerCapacity.memory : {};
13762
+ const providerMetrics = record10(status.provider.body.metrics) ? status.provider.body.metrics : {};
13763
+ const providerCapacity = record10(status.provider.body.capacity) ? status.provider.body.capacity : {};
13764
+ const workerMemory = record10(providerCapacity.memory) ? providerCapacity.memory : {};
13599
13765
  out.log(
13600
13766
  `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`
13601
13767
  );
13602
13768
  for (const line2 of providerCapacityLines(status.providerCapacity)) {
13603
13769
  out.log(line2);
13604
13770
  }
13605
- const coverage = record9(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
13606
- const coverageCounts = record9(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
13607
- const coverageBudget = record9(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
13771
+ const coverage = record10(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
13772
+ const coverageCounts = record10(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
13773
+ const coverageBudget = record10(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
13608
13774
  out.log(
13609
13775
  `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`
13610
13776
  );
13611
13777
  const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
13612
- const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
13778
+ const providerFreshness = record10(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
13613
13779
  out.log(
13614
13780
  `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
13615
13781
  );
@@ -13618,17 +13784,17 @@ function printO11yStatus(status, out) {
13618
13784
  );
13619
13785
  }
13620
13786
  function providerCapacityLines(read3) {
13621
- const resources = record9(read3.body.resources) ? read3.body.resources : {};
13622
- const durableObjects = record9(resources.durableObjects) ? resources.durableObjects : {};
13623
- const periodic = record9(durableObjects.periodic) ? durableObjects.periodic : {};
13624
- const storage = record9(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
13625
- const d1 = record9(resources.d1) ? resources.d1 : {};
13626
- const d1Activity = record9(d1.activity) ? d1.activity : {};
13627
- const d1Storage = record9(d1.storage) ? d1.storage : {};
13628
- const d1Latency = record9(d1Activity.latency) ? d1Activity.latency : {};
13629
- const r2 = record9(resources.r2) ? resources.r2 : {};
13630
- const r2Operations = record9(r2.operations) ? r2.operations : {};
13631
- const r2Storage = record9(r2.storage) ? r2.storage : {};
13787
+ const resources = record10(read3.body.resources) ? read3.body.resources : {};
13788
+ const durableObjects = record10(resources.durableObjects) ? resources.durableObjects : {};
13789
+ const periodic = record10(durableObjects.periodic) ? durableObjects.periodic : {};
13790
+ const storage = record10(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
13791
+ const d1 = record10(resources.d1) ? resources.d1 : {};
13792
+ const d1Activity = record10(d1.activity) ? d1.activity : {};
13793
+ const d1Storage = record10(d1.storage) ? d1.storage : {};
13794
+ const d1Latency = record10(d1Activity.latency) ? d1Activity.latency : {};
13795
+ const r2 = record10(resources.r2) ? resources.r2 : {};
13796
+ const r2Operations = record10(r2.operations) ? r2.operations : {};
13797
+ const r2Storage = record10(r2.storage) ? r2.storage : {};
13632
13798
  const status = String(
13633
13799
  read3.body.status ?? read3.body.error ?? "unavailable"
13634
13800
  );
@@ -13639,11 +13805,11 @@ function providerCapacityLines(read3) {
13639
13805
  ];
13640
13806
  }
13641
13807
  function liveSyncLine(read3) {
13642
- const performance = record9(read3.body.performance) ? read3.body.performance : {};
13643
- const commitToSend = record9(performance.commitToSend) ? performance.commitToSend : {};
13808
+ const performance = record10(read3.body.performance) ? read3.body.performance : {};
13809
+ const commitToSend = record10(performance.commitToSend) ? performance.commitToSend : {};
13644
13810
  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`;
13645
13811
  }
13646
- function record9(value2) {
13812
+ function record10(value2) {
13647
13813
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
13648
13814
  }
13649
13815
  function numeric3(value2) {
@@ -13981,7 +14147,7 @@ async function monitorCommand(parsed, deps = {}) {
13981
14147
  if (action2 === "plan" || action2 === "apply") {
13982
14148
  const desired = monitoringWireConfig(context.cfg, env);
13983
14149
  const live = await request2(`${base}?env=${encodeURIComponent(env)}`, { headers }, doFetch);
13984
- const currentRevision = record10(live.config) ? string(live.config.revision) : null;
14150
+ const currentRevision = record11(live.config) ? string(live.config.revision) : null;
13985
14151
  const changed = currentRevision !== desired.revision;
13986
14152
  const plan = {
13987
14153
  schemaVersion: 1,
@@ -14026,7 +14192,7 @@ async function monitorCommand(parsed, deps = {}) {
14026
14192
  headers
14027
14193
  }, doFetch);
14028
14194
  emit3(result2, jsonOutput, out, () => {
14029
- const run = record10(result2.run) ? result2.run : {};
14195
+ const run = record11(result2.run) ? result2.run : {};
14030
14196
  out.log(`monitor run ${appId}/${env}/${probeId}: ${string(run.outcome) ?? "unknown"}${run.failure_code ? ` (${String(run.failure_code)})` : ""}`);
14031
14197
  });
14032
14198
  return;
@@ -14051,12 +14217,12 @@ async function request2(url, init, doFetch) {
14051
14217
  let body = {};
14052
14218
  try {
14053
14219
  const parsed = text4 ? JSON.parse(text4) : {};
14054
- body = record10(parsed) ? parsed : { value: parsed };
14220
+ body = record11(parsed) ? parsed : { value: parsed };
14055
14221
  } catch {
14056
14222
  body = { message: text4.slice(0, 500) };
14057
14223
  }
14058
14224
  if (!response2.ok) {
14059
- const error = record10(body.error) ? body.error : body;
14225
+ const error = record11(body.error) ? body.error : body;
14060
14226
  throw new Error(string(error.message) ?? string(error.code) ?? `monitor request failed (${response2.status})`);
14061
14227
  }
14062
14228
  return body;
@@ -14064,7 +14230,7 @@ async function request2(url, init, doFetch) {
14064
14230
  function printRead(action2, appId, env, result, out) {
14065
14231
  if (action2 === "status") {
14066
14232
  out.log(`monitor status ${appId}/${env}: ${String(result.overall ?? (result.configured === false ? "not configured" : "unknown"))}`);
14067
- const slos = Array.isArray(result.slos) ? result.slos.filter(record10) : [];
14233
+ const slos = Array.isArray(result.slos) ? result.slos.filter(record11) : [];
14068
14234
  for (const slo of slos) out.log(`slo ${String(slo.id)} ${String(slo.state)} ${percent(slo.observed)} observed ${percent(slo.budgetRemaining)} budget remaining`);
14069
14235
  const incidents = Array.isArray(result.openIncidents) ? result.openIncidents.length : 0;
14070
14236
  const gaps = Array.isArray(result.monitoringGaps) ? result.monitoringGaps.length : 0;
@@ -14073,20 +14239,20 @@ function printRead(action2, appId, env, result, out) {
14073
14239
  return;
14074
14240
  }
14075
14241
  if (action2 === "incidents") {
14076
- const incidents = Array.isArray(result.incidents) ? result.incidents.filter(record10) : [];
14242
+ const incidents = Array.isArray(result.incidents) ? result.incidents.filter(record11) : [];
14077
14243
  out.log(`monitor incidents ${appId}/${env}: ${incidents.length}`);
14078
14244
  for (const incident2 of incidents) out.log(`${String(incident2.state)} ${String(incident2.kind)} ${String(incident2.slo_id)} ${new Date(Number(incident2.opened_at)).toISOString()}`);
14079
14245
  return;
14080
14246
  }
14081
14247
  out.log(`monitor report ${appId}/${env}: ${String(result.period)} ${String(result.overall)}`);
14082
- const probes = Array.isArray(result.probes) ? result.probes.filter(record10) : [];
14248
+ const probes = Array.isArray(result.probes) ? result.probes.filter(record11) : [];
14083
14249
  for (const probe of probes) out.log(`probe ${String(probe.id)} ${Number(probe.good)} good ${Number(probe.bad)} bad ${Number(probe.unknown)} unknown`);
14084
14250
  }
14085
14251
  function emit3(value2, json, out, human) {
14086
14252
  if (json) out.log(JSON.stringify(value2, null, 2));
14087
14253
  else human();
14088
14254
  }
14089
- function record10(value2) {
14255
+ function record11(value2) {
14090
14256
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
14091
14257
  }
14092
14258
  function string(value2) {
@@ -14577,6 +14743,122 @@ async function provision(options) {
14577
14743
  }
14578
14744
  }
14579
14745
 
14746
+ // src/promotion-command.ts
14747
+ var import_promises12 = require("fs/promises");
14748
+ var import_apps14 = require("@odla-ai/apps");
14749
+
14750
+ // src/security-command-context.ts
14751
+ async function hostedSecurityContext(parsed, dependencies) {
14752
+ const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
14753
+ const cfg = await loadProjectConfig(configPath);
14754
+ const env = stringOpt(parsed.options.env) ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
14755
+ if (!env || !cfg.envs.includes(env)) {
14756
+ throw new Error(`env "${env ?? ""}" is not declared in ${configPath}`);
14757
+ }
14758
+ const platform = platformAudience(stringOpt(parsed.options.platform) ?? cfg.platformUrl);
14759
+ if (platformAudience(cfg.platformUrl) !== platform) {
14760
+ throw new Error("--platform cannot reuse a project developer token from another platform; update odla.config.mjs and authenticate there");
14761
+ }
14762
+ const doFetch = dependencies.fetch ?? fetch;
14763
+ const stdout = dependencies.stdout ?? console;
14764
+ const open = parsed.options.open === false ? false : parsed.options.open === true ? true : void 0;
14765
+ const token = await getDeveloperToken(
14766
+ cfg,
14767
+ { configPath, email: stringOpt(parsed.options.email), open, openApprovalUrl: dependencies.openUrl },
14768
+ doFetch,
14769
+ stdout,
14770
+ { optionalProjectCapabilities: ["app.manage"] }
14771
+ );
14772
+ return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
14773
+ }
14774
+ function requiredSecurityPositional(parsed, index, label) {
14775
+ const value2 = parsed.positionals[index];
14776
+ if (!value2) throw new Error(`${label} is required`);
14777
+ return value2;
14778
+ }
14779
+ function securityProfile(value2) {
14780
+ if (value2 === void 0) return void 0;
14781
+ if (value2 === "odla" || value2 === "cloudflare-app" || value2 === "generic") return value2;
14782
+ throw new Error("--profile must be odla, cloudflare-app, or generic");
14783
+ }
14784
+
14785
+ // src/promotion-command.ts
14786
+ var render = (value2, json, out) => {
14787
+ if (json) out.log(JSON.stringify(value2));
14788
+ else out.log(JSON.stringify(value2, null, 2));
14789
+ };
14790
+ async function promotionCommand(parsed, dependencies) {
14791
+ const action2 = parsed.positionals[1];
14792
+ if (!action2 || !["plan", "inspect", "status", "apply", "rollback", "next", "record", "cancel"].includes(action2)) rejectWord(["promotion"], action2);
14793
+ assertArgs(parsed, ["config", "env", "platform", "email", "open", "json", "file", "approval", "idempotency-key"], 5);
14794
+ const context = await hostedSecurityContext(parsed, dependencies);
14795
+ const client = (0, import_apps14.createAppsClient)({
14796
+ token: context.token,
14797
+ fetcher: { fetch: (request3, init) => {
14798
+ const internal = new URL(typeof request3 === "string" ? request3 : request3.url);
14799
+ return context.fetch(`${context.platform}${internal.pathname}${internal.search}`, init);
14800
+ } }
14801
+ });
14802
+ const json = parsed.options.json === true;
14803
+ if (action2 === "plan") {
14804
+ if (parsed.positionals[2]) throw new Error("promotion plan takes --file, not a positional plan id");
14805
+ const file = stringOpt(parsed.options.file);
14806
+ if (!file) throw new Error("--file <promotion-plan.json> is required");
14807
+ const source = JSON.parse(await (0, import_promises12.readFile)(file, "utf8"));
14808
+ const plan2 = await client.createPromotionReleasePlan(context.appId, source);
14809
+ render(plan2, json, context.stdout);
14810
+ return;
14811
+ }
14812
+ const planId = requiredSecurityPositional(parsed, 2, "plan id");
14813
+ if (action2 === "inspect") {
14814
+ if (parsed.positionals[3]) throw new Error("promotion inspect takes only a plan id");
14815
+ const plan2 = await client.getPromotionReleasePlan(context.appId, planId);
14816
+ if (!plan2) throw new Error(`promotion release ${planId} was not found`);
14817
+ render(plan2, json, context.stdout);
14818
+ return;
14819
+ }
14820
+ if (action2 === "status") {
14821
+ if (parsed.positionals[3]) throw new Error("promotion status takes only a plan id");
14822
+ const status = await client.getPromotionReleaseStatus(context.appId, planId);
14823
+ if (!status) throw new Error(`promotion release ${planId} was not found`);
14824
+ render(status, json, context.stdout);
14825
+ return;
14826
+ }
14827
+ if (action2 === "next" || action2 === "cancel" || action2 === "record") {
14828
+ const operationId = requiredSecurityPositional(parsed, 3, "operation id");
14829
+ if (action2 === "next") {
14830
+ if (parsed.positionals[4]) throw new Error("promotion next takes only plan and operation ids");
14831
+ render(await client.claimPromotionReleaseWork(context.appId, planId, operationId), json, context.stdout);
14832
+ return;
14833
+ }
14834
+ if (action2 === "cancel") {
14835
+ if (parsed.positionals[4]) throw new Error("promotion cancel takes only plan and operation ids");
14836
+ render(await client.cancelPromotionReleaseOperation(context.appId, planId, operationId), json, context.stdout);
14837
+ return;
14838
+ }
14839
+ const stepId = requiredSecurityPositional(parsed, 4, "step id");
14840
+ const file = stringOpt(parsed.options.file);
14841
+ if (!file) throw new Error("--file <step-receipt.json> is required");
14842
+ const receipt = JSON.parse(await (0, import_promises12.readFile)(file, "utf8"));
14843
+ render(await client.recordPromotionReleaseWork(context.appId, planId, operationId, stepId, receipt), json, context.stdout);
14844
+ return;
14845
+ }
14846
+ const approvalId = stringOpt(parsed.options.approval);
14847
+ if (parsed.positionals[3]) throw new Error(`promotion ${action2} takes only a plan id`);
14848
+ if (!approvalId) throw new Error(`--approval <approval-id> is required for promotion ${action2}`);
14849
+ const plan = await client.getPromotionReleasePlan(context.appId, planId);
14850
+ if (!plan) throw new Error(`promotion release ${planId} was not found`);
14851
+ if (plan.state !== "reviewable") throw new Error(`promotion release is ${plan.state}: ${plan.blockers.join("; ")}`);
14852
+ const operationAction = action2;
14853
+ const operation = await client.requestPromotionReleaseOperation(context.appId, planId, operationAction, {
14854
+ approvalId,
14855
+ planDigest: plan.planDigest,
14856
+ targetSetDigest: plan.targetSetDigest,
14857
+ idempotencyKey: stringOpt(parsed.options["idempotency-key"]) ?? `${operationAction}:${planId}`
14858
+ });
14859
+ render(operation, json, context.stdout);
14860
+ }
14861
+
14580
14862
  // src/record.ts
14581
14863
  var import_node_fs18 = require("fs");
14582
14864
  var import_node_process18 = __toESM(require("process"), 1);
@@ -14596,12 +14878,12 @@ function recordInvocation(parsed) {
14596
14878
  }
14597
14879
 
14598
14880
  // src/advisory-output.ts
14599
- var import_apps14 = require("@odla-ai/apps");
14881
+ var import_apps15 = require("@odla-ai/apps");
14600
14882
  function advisoryCollectingFetch(inner, sink) {
14601
14883
  return (async (input, init) => {
14602
14884
  const response2 = await inner(input, init);
14603
14885
  try {
14604
- sink.push(...(0, import_apps14.parseAdvisories)(response2));
14886
+ sink.push(...(0, import_apps15.parseAdvisories)(response2));
14605
14887
  } catch {
14606
14888
  }
14607
14889
  return response2;
@@ -14621,7 +14903,7 @@ function renderAdvisories(out, advisories, env = process.env) {
14621
14903
  const key = `${advisory.code}:${advisory.message}`;
14622
14904
  if (seen.has(key)) continue;
14623
14905
  seen.add(key);
14624
- out.error((0, import_apps14.formatAdvisory)(advisory));
14906
+ out.error((0, import_apps15.formatAdvisory)(advisory));
14625
14907
  }
14626
14908
  }
14627
14909
 
@@ -14908,7 +15190,7 @@ async function bySlug(ctx, slug) {
14908
15190
  "GET",
14909
15191
  `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
14910
15192
  );
14911
- const filtered = page2.records.find((record11) => record11.slug === slug);
15193
+ const filtered = page2.records.find((record12) => record12.slug === slug);
14912
15194
  if (filtered) return filtered;
14913
15195
  const limit = 100;
14914
15196
  for (let offset = 0; ; offset += limit) {
@@ -14917,7 +15199,7 @@ async function bySlug(ctx, slug) {
14917
15199
  "GET",
14918
15200
  `/runbook?app=${encodeURIComponent(ctx.appId)}&limit=${limit}&offset=${offset}`
14919
15201
  );
14920
- const found = fallback.records.find((record11) => record11.slug === slug);
15202
+ const found = fallback.records.find((record12) => record12.slug === slug);
14921
15203
  if (found) return found;
14922
15204
  if (!fallback.records.length || offset + fallback.records.length >= fallback.total) break;
14923
15205
  }
@@ -15717,41 +15999,6 @@ async function runbookCommand(parsed, deps = {}) {
15717
15999
  }
15718
16000
  }
15719
16001
 
15720
- // src/security-command-context.ts
15721
- async function hostedSecurityContext(parsed, dependencies) {
15722
- const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
15723
- const cfg = await loadProjectConfig(configPath);
15724
- const env = stringOpt(parsed.options.env) ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
15725
- if (!env || !cfg.envs.includes(env)) {
15726
- throw new Error(`env "${env ?? ""}" is not declared in ${configPath}`);
15727
- }
15728
- const platform = platformAudience(stringOpt(parsed.options.platform) ?? cfg.platformUrl);
15729
- if (platformAudience(cfg.platformUrl) !== platform) {
15730
- throw new Error("--platform cannot reuse a project developer token from another platform; update odla.config.mjs and authenticate there");
15731
- }
15732
- const doFetch = dependencies.fetch ?? fetch;
15733
- const stdout = dependencies.stdout ?? console;
15734
- const open = parsed.options.open === false ? false : parsed.options.open === true ? true : void 0;
15735
- const token = await getDeveloperToken(
15736
- cfg,
15737
- { configPath, email: stringOpt(parsed.options.email), open, openApprovalUrl: dependencies.openUrl },
15738
- doFetch,
15739
- stdout,
15740
- { optionalProjectCapabilities: ["app.manage"] }
15741
- );
15742
- return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
15743
- }
15744
- function requiredSecurityPositional(parsed, index, label) {
15745
- const value2 = parsed.positionals[index];
15746
- if (!value2) throw new Error(`${label} is required`);
15747
- return value2;
15748
- }
15749
- function securityProfile(value2) {
15750
- if (value2 === void 0) return void 0;
15751
- if (value2 === "odla" || value2 === "cloudflare-app" || value2 === "generic") return value2;
15752
- throw new Error("--profile must be odla, cloudflare-app, or generic");
15753
- }
15754
-
15755
16002
  // src/security-command-output.ts
15756
16003
  function printHostedSecurityPlan(out, plan, appId) {
15757
16004
  out.log(`Hosted security plan for ${appId}/${plan.env}: ${plan.ready ? "ready" : "not ready"}`);
@@ -16574,6 +16821,10 @@ async function dispatchCli(argv, dependencies) {
16574
16821
  await securityCommand(parsed, runtime);
16575
16822
  return;
16576
16823
  }
16824
+ if (command === "promotion") {
16825
+ await promotionCommand(parsed, runtime);
16826
+ return;
16827
+ }
16577
16828
  if (command === "brand") {
16578
16829
  await brandCommand(parsed, runtime);
16579
16830
  return;