@oxygen-agent/cli 1.739.0 → 1.750.4

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 CHANGED
@@ -34,4 +34,4 @@ oxygen update
34
34
 
35
35
  For product documentation, visit https://oxygen-agent.com/docs. For support, visit https://oxygen-agent.com.
36
36
 
37
- Version: 1.739.0
37
+ Version: 1.750.4
@@ -32,7 +32,7 @@ const MUTATING_VERBS = new Set([
32
32
  "billing-link", "billing-unlink",
33
33
  "bind", "buy", "call", "cancel", "chat-action", "claim", "clear",
34
34
  "comment", "configure", "connect", "create", "decide", "delete", "delist", "disable",
35
- "disconnect", "dispatch", "draft", "duplicate", "edit", "emit", "enable", "enroll",
35
+ "disconnect", "dispatch", "done", "draft", "duplicate", "edit", "emit", "enable", "enroll",
36
36
  "file", "forward", "grant", "harvest", "history", "import", "insert", "interrupt", "invite",
37
37
  "label-add", "label-remove", "launch", "log", "login", "logout", "mark-all-read", "mark-read", "materialize", "merge",
38
38
  "migrate",
@@ -54,6 +54,10 @@ const ZERO_CREDIT_APPROVAL_COMMANDS = new Set([
54
54
  // Credential replacement is destructive but does not execute the graph,
55
55
  // call a provider, or spend Oxygen credits.
56
56
  "workflows webhooks rotate",
57
+ // These flags describe a hypothetical autonomous grant to the shared
58
+ // readiness validator. Lint remains read-only and can never create the
59
+ // grant, call a node/provider, or spend the candidate ceiling.
60
+ "workflows lint",
57
61
  ]);
58
62
  // Exact noun-style reads whose leaf happens to look like a mutating verb.
59
63
  // `workflows run <id>` gets an existing Workflow run; `workflows call` creates
@@ -64,6 +68,7 @@ const READ_ONLY_COMMANDS = new Set(["workflows run"]);
64
68
  // calling the command without its confirmation flag writes anything.
65
69
  const PREVIEW_BY_DEFAULT_COMMANDS = new Set([
66
70
  "mailboxes delete",
71
+ "support admin done",
67
72
  "support admin reply",
68
73
  "workflows webhooks rotate",
69
74
  ]);
package/dist/index.js CHANGED
@@ -2655,9 +2655,9 @@ export function createProgram() {
2655
2655
  }));
2656
2656
  program
2657
2657
  .command("support")
2658
- .description("Open and track Plain support conversations for the active OXYGEN organization. Filing is a zero-credit write to the canonical Plain queue.")
2658
+ .description("Open and track Plain support conversations for the active OXYGEN organization. In the app, use Settings → Support → Open support chat. Retry is the only recovery control; it returns after each failed connection attempt and never creates a Thread. CLI filing is a zero-credit write to the canonical Plain queue. Guide: https://oxygen-agent.com/docs/surfaces/support.")
2659
2659
  .addCommand(new Command("file")
2660
- .description("Create a real, zero-credit Plain support Thread immediately. There is no preview: review the exact subject, body, category, and severity before running it. Use when you're stuck on an OXYGEN operation.")
2660
+ .description("Create a real, zero-credit Plain support Thread immediately. This is a separate filing action, not the in-app Retry, and can create a second Thread: check `support list --status open` first and reply to the existing Thread for the same issue. There is no preview: review the exact subject, body, category, and severity before running it. Use when you're stuck on an OXYGEN operation.")
2661
2661
  .requiredOption("--subject <subject>", "One-line summary of the problem.")
2662
2662
  .option("--body <body>", "What you were doing, what happened, and what you tried.")
2663
2663
  .option("--severity <severity>", "low | normal | high. Defaults to normal.")
@@ -2801,19 +2801,54 @@ export function createProgram() {
2801
2801
  });
2802
2802
  }))
2803
2803
  .addCommand(new Command("reply")
2804
- .description("Preview or send a public reply on the Thread's native Plain channel (human staff only; sent as Oxygen Support).")
2804
+ .description("Preview or send a guarded public reply on the Thread's native Plain channel as Oxygen Support (staff only). CLI delivery is agent-only; named humans use Plain Inbox or Plain MCP.")
2805
2805
  .argument("<ticketId>", "Plain Thread ID (th_...).")
2806
2806
  .requiredOption("--body <body>", "Customer-visible reply body.")
2807
+ .requiredOption("--agent", "Required safety declaration: reply as the authenticated Oxygen Support machine user after it owns the Thread.")
2807
2808
  .option("--confirm-ref <ref>", "Send only when this exactly matches the previewed Plain ref (for example T-10). Omit to preview without sending.")
2809
+ .option("--confirm-message <messageId>", "Agent send only: confirm the latest customer message ID returned by preview.")
2810
+ .option("--confirm-token <token>", "Agent send only: use the exact server-authenticated preview token bound to the Thread, latest message, reply body, and actor mode; never calculate it locally.")
2808
2811
  .option("--json", "Print a JSON envelope.")
2809
2812
  .action(async (ticketId, options) => {
2810
2813
  await handleAsyncAction("support admin reply", options, () => requestOxygen(`/api/cli/admin/support/tickets/${encodeURIComponent(ticketId)}/messages`, {
2811
2814
  method: "POST",
2812
2815
  body: {
2813
2816
  body: readOption(options.body),
2817
+ as_agent: true,
2814
2818
  ...(readOption(options.confirmRef)
2815
2819
  ? { confirm_ref: readOption(options.confirmRef) }
2816
2820
  : {}),
2821
+ ...(readOption(options.confirmMessage)
2822
+ ? { confirm_message_id: readOption(options.confirmMessage) }
2823
+ : {}),
2824
+ ...(readOption(options.confirmToken)
2825
+ ? { confirm_token: readOption(options.confirmToken) }
2826
+ : {}),
2827
+ },
2828
+ }));
2829
+ }))
2830
+ .addCommand(new Command("done")
2831
+ .description("Preview or mark a Plain Thread Done after the exact verified agent reply; new customer activity reopens it to Todo (staff only). CLI completion is agent-only.")
2832
+ .argument("<ticketId>", "Plain Thread ID (th_...).")
2833
+ .requiredOption("--agent", "Required safety declaration: mark Done as the authenticated Oxygen Support machine user while it still owns the Thread.")
2834
+ .requiredOption("--reply-token <token>", "Use the exact agent_done_token returned by the preceding live `support admin reply --agent`; previews and errors never mint or reveal one.")
2835
+ .option("--confirm-ref <ref>", "Complete only when this exactly matches the previewed Plain ref.")
2836
+ .option("--confirm-message <messageId>", "Complete only when this exactly matches the latest verified staff reply ID from preview.")
2837
+ .option("--json", "Print a JSON envelope.")
2838
+ .action(async (ticketId, options) => {
2839
+ await handleAsyncAction("support admin done", options, () => requestOxygen(`/api/cli/admin/support/tickets/${encodeURIComponent(ticketId)}/resolve`, {
2840
+ method: "POST",
2841
+ body: {
2842
+ as_agent: true,
2843
+ ...(readOption(options.replyToken)
2844
+ ? { agent_reply_token: readOption(options.replyToken) }
2845
+ : {}),
2846
+ ...(readOption(options.confirmRef)
2847
+ ? { confirm_ref: readOption(options.confirmRef) }
2848
+ : {}),
2849
+ ...(readOption(options.confirmMessage)
2850
+ ? { confirm_message_id: readOption(options.confirmMessage) }
2851
+ : {}),
2817
2852
  },
2818
2853
  }));
2819
2854
  }))
@@ -2865,17 +2900,6 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
2865
2900
  `)
2866
2901
  .action(async (options) => {
2867
2902
  await handleAsyncAction("support admin events", options, () => requestOxygen(withSupportEventsQuery("/api/cli/admin/support/events", options)));
2868
- }), { hidden: true })
2869
- .addCommand(new Command("resolve")
2870
- .description("Retired legacy write; always returns the Plain-cutover error (staff only).")
2871
- .argument("<ticketId>", "Ticket UUID.")
2872
- .requiredOption("--resolution <text>", "Resolution message sent to the opener.")
2873
- .option("--json", "Print a JSON envelope.")
2874
- .action(async (ticketId, options) => {
2875
- await handleAsyncAction("support admin resolve", options, () => requestOxygen(`/api/cli/admin/support/tickets/${encodeURIComponent(ticketId)}/resolve`, {
2876
- method: "POST",
2877
- body: { resolution: readOption(options.resolution) },
2878
- }));
2879
2903
  }), { hidden: true })
2880
2904
  .addCommand(new Command("workflow")
2881
2905
  .description("Retired legacy write; Plain notes and downstream engineering work own triage (staff only).")
@@ -14233,12 +14257,14 @@ Fastest editable graph:
14233
14257
  1. oxygen workflows events list --search "<outcome>" --kind builtin --json
14234
14258
  2. oxygen workflows init --id my-workflow
14235
14259
  3. oxygen workflows schema --subject graph --json
14236
- 4. oxygen workflows lint --file my-workflow.workflow.json --json
14237
- 5. oxygen workflows apply --file my-workflow.workflow.json --json
14260
+ 4. oxygen workflows lint --file my-workflow.workflow.json --phase draft --json
14261
+ 5. oxygen workflows apply --file my-workflow.workflow.json --draft --json
14262
+ 6. oxygen workflows lint --file my-workflow.workflow.json --phase publish --json
14238
14263
 
14239
- Applying saves the definition only: it costs 0 credits, calls no graph nodes,
14240
- and creates no run. A disabled workflow stays disabled. Calling or enabling it
14241
- is the separate execution/authorization step.
14264
+ Draft apply saves an inert editable revision: it costs 0 credits, calls no graph
14265
+ nodes, creates no run, and publishes nothing. After publish lint is clean, apply
14266
+ without --draft publishes the revision; calling or enabling it remains a separate
14267
+ execution/authorization step.
14242
14268
 
14243
14269
  Run completion:
14244
14270
  Calls enqueue asynchronously. Follow the returned run_id with:
@@ -14335,15 +14361,24 @@ Run completion:
14335
14361
  await handleAsyncAction("workflows init", options, async () => scaffoldWorkflowProject(options));
14336
14362
  }))
14337
14363
  .addCommand(new Command("lint")
14338
- .description("Compile and lint a workflow file without saving it.")
14364
+ .description("Compile and validate an authored workflow file against the same draft/publish readiness contract as the web editor and apply. Saves nothing, runs nothing, calls no provider, and spends no credits. Portable exports use `workflows import --preflight` because their destination connection bindings are part of validation.")
14339
14365
  .requiredOption("--file <path>", "Workflow module or manifest JSON file.")
14366
+ .addOption(new Option("--phase <phase>", "Validation phase: publish (default) or draft.").choices(["draft", "publish"]).default("publish"))
14367
+ .option("--approved", "Validate that revision-bound autonomous authority is present; grants nothing because lint is read-only.")
14368
+ .option("--max-credits <n>", "Candidate positive per-delivery ceiling; omit to validate the plan default.")
14340
14369
  .option("--json", "Print a JSON envelope.")
14341
14370
  .action(async (options) => {
14342
14371
  await handleAsyncAction("workflows lint", options, async () => {
14343
14372
  const manifest = await compileWorkflowFile(options.file);
14373
+ const maxCredits = readPositiveNumber(options.maxCredits);
14344
14374
  return requestOxygen("/api/cli/workflows/lint", {
14345
14375
  method: "POST",
14346
- body: { manifest },
14376
+ body: {
14377
+ manifest,
14378
+ phase: options.phase ?? "publish",
14379
+ ...(options.approved ? { approved: true } : {}),
14380
+ ...(maxCredits !== undefined ? { max_credits: maxCredits } : {}),
14381
+ },
14347
14382
  });
14348
14383
  });
14349
14384
  }))
@@ -14538,9 +14573,9 @@ Run completion:
14538
14573
  },
14539
14574
  }))))
14540
14575
  .addCommand(new Command("replay")
14541
- .description("Create a new dry run from one authenticated delivery's exact original revision and payload. Never resends the webhook; uses 0 credits, no paid provider calls, and no external writes. Internal reads and outbound HTTP GETs can still execute.")
14542
- .argument("<delivery_id>", "Delivery UUID from `oxygen workflows webhooks deliveries`.")
14543
- .option("--request-key <key>", "Stable key for safely retrying the same replay request.")
14576
+ .description("Create a durable, inspectable run in dry_run mode from one verified outcome=ran delivery's exact original revision and payload. Never resends the webhook or adds an inbound delivery-history row; uses 0 credits, no paid provider calls, and no external writes. Internal reads and outbound HTTP GETs can still execute.")
14577
+ .argument("<delivery_id>", "Verified outcome=ran delivery UUID from `oxygen workflows webhooks deliveries`.")
14578
+ .option("--request-key <key>", "Idempotency key. Reusing the same delivery/key returns the existing replay run; it never creates a second run.")
14544
14579
  .option("--json", "Print a JSON envelope.")
14545
14580
  .action((deliveryId, options) => handleAsyncAction("workflows webhooks replay", options, () => requestOxygen("/api/cli/workflows/webhooks/deliveries/replay", {
14546
14581
  method: "POST",
@@ -14550,7 +14585,7 @@ Run completion:
14550
14585
  },
14551
14586
  }))))
14552
14587
  .addCommand(new Command("deliveries")
14553
- .description("List workflow webhook deliveries: what arrived, whether it verified, and the run it started.")
14588
+ .description("List inbound workflow webhook deliveries: what arrived, whether it verified, and the run it started. Replays never append here; only a verified outcome=ran delivery can be replayed.")
14554
14589
  .argument("[trigger_id]", "Optional webhook trigger name, such as lead-created.")
14555
14590
  .option("--workflow-id <id>", "Filter to deliveries that started a run of this workflow.")
14556
14591
  .option("--outcome <outcome>", "Filter by rejected (sender authentication failed), blocked (authenticated, no run), or ran.")
@@ -14563,7 +14598,7 @@ Run completion:
14563
14598
  limit: options.limit,
14564
14599
  }))))))
14565
14600
  .addCommand(new Command("revisions")
14566
- .description("Published version history for one workflow, newest first — every save cuts a revision, and this is how you find the one to go back to.")
14601
+ .description("Saved revision history for one workflow, newest first — includes unpublished drafts and published versions; use --include-manifest to inspect either.")
14567
14602
  .argument("[workflow]", "Workflow id, slug, or name.")
14568
14603
  .option("--workflow <workflow>", "Workflow id, slug, or name.")
14569
14604
  .option("--limit <n>", "Maximum revisions to return. Defaults to 50.")
@@ -14760,6 +14795,7 @@ Full trigger schema: oxygen workflows schema --subject trigger --json
14760
14795
  .argument("<run_id>", "Workflow run UUID returned as data.run.id by workflows call/tail; not the separate provenance run id.")
14761
14796
  .option("--include-bundle", "Include durable recipe bundles in JSON output.")
14762
14797
  .option("--json", "Print a JSON envelope.")
14798
+ .addHelpText("after", "\nWebhook replay: for a webhook-origin run, find its verified delivery with `oxygen workflows webhooks deliveries --workflow-id <workflow-uuid> --outcome ran`, then use `oxygen workflows webhooks replay <delivery-id>`. The replay response and run metadata retain the source delivery/run lineage.\n")
14763
14799
  .action(async (runId, options) => {
14764
14800
  await handleAsyncAction("workflows run", options, async () => prepareWorkflowCliOutput(await requestOxygen("/api/cli/workflows/run", {
14765
14801
  method: "POST",
@@ -15182,9 +15218,10 @@ function scaffoldGraphProject(options) {
15182
15218
  files_written: filesWritten,
15183
15219
  files_skipped: filesSkipped,
15184
15220
  next_steps: [
15185
- `Edit ${workflowFileName}, then validate it: oxygen workflows lint --file ./${workflowFileName}`,
15186
- `Install it disabled: oxygen workflows apply --file ./${workflowFileName}`,
15187
- `Open it: oxygen workflows get ${workflowId} --json`,
15221
+ `Edit ${workflowFileName}, then validate the draft: oxygen workflows lint --file ./${workflowFileName} --phase draft --json`,
15222
+ `Save it without publishing: oxygen workflows apply --file ./${workflowFileName} --draft --json`,
15223
+ `Inspect the saved draft: oxygen workflows revisions ${workflowId} --include-manifest --json`,
15224
+ `Before publishing, validate readiness: oxygen workflows lint --file ./${workflowFileName} --phase publish --json`,
15188
15225
  ],
15189
15226
  };
15190
15227
  }
@@ -15356,8 +15393,21 @@ async function loadWorkflowManifestFromFile(filePath) {
15356
15393
  ? parsed.manifest
15357
15394
  : null;
15358
15395
  if (!manifest) {
15359
- throw new OxygenError("invalid_workflow_manifest", "Workflow JSON must be a manifest or { manifest } object.", {
15360
- details: { file: filePath },
15396
+ const portable = parsed.kind === "oxygen-workflow-definition"
15397
+ && parsed.definition_version === 1
15398
+ && isRecord(parsed.graph);
15399
+ throw new OxygenError("invalid_workflow_manifest", portable
15400
+ ? `Detected a portable workflow export. Validate it without saving or running anything: oxygen workflows import --file ${filePath} --preflight --json. Workflows lint accepts authored manifests, not import envelopes.`
15401
+ : "Workflow JSON must be a manifest or { manifest } object.", {
15402
+ details: {
15403
+ file: filePath,
15404
+ ...(portable
15405
+ ? {
15406
+ detected_format: "oxygen-workflow-definition",
15407
+ next_command: `oxygen workflows import --file ${filePath} --preflight --json`,
15408
+ }
15409
+ : {}),
15410
+ },
15361
15411
  exitCode: 1,
15362
15412
  });
15363
15413
  }
@@ -1,4 +1,4 @@
1
- export { MANAGED_INBOX_MINIMUM_CLI_VERSION, OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION, } from "./version.js";
1
+ export { MANAGED_INBOX_MINIMUM_CLI_VERSION, OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION, SUPPORT_AGENT_REPLY_MINIMUM_CLI_VERSION, } from "./version.js";
2
2
  export { WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEYS, clearWorkflowTriggerAutoPauseMetadata, } from "./workflow-trigger-metadata.js";
3
3
  export { WORKFLOW_STATUS_CHANGE_METADATA_KEY, type WorkflowStatusChange, type WorkflowStatusChangeActor, type WorkflowStatusChangeSource, describeWorkflowStatusChange, formatWorkflowStatusChangeTimestamp, parseWorkflowStatusChange, readWorkflowStatusChange, } from "./workflow-status-change.js";
4
4
  export * from "./billing.js";
@@ -1,4 +1,4 @@
1
- export { MANAGED_INBOX_MINIMUM_CLI_VERSION, OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION, } from "./version.js";
1
+ export { MANAGED_INBOX_MINIMUM_CLI_VERSION, OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION, SUPPORT_AGENT_REPLY_MINIMUM_CLI_VERSION, } from "./version.js";
2
2
  export { WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEYS, clearWorkflowTriggerAutoPauseMetadata, } from "./workflow-trigger-metadata.js";
3
3
  export { WORKFLOW_STATUS_CHANGE_METADATA_KEY, describeWorkflowStatusChange, formatWorkflowStatusChangeTimestamp, parseWorkflowStatusChange, readWorkflowStatusChange, } from "./workflow-status-change.js";
4
4
  export * from "./billing.js";
@@ -6,7 +6,9 @@ import { getCapabilityRouteMatch, inferCapabilityRoute, } from "./capability-dis
6
6
  // connection routes.
7
7
  export function inferUserCapabilityRoute(query) {
8
8
  const route = inferCapabilityRoute(query);
9
- if (route?.card.id !== "tables" || !isSimpleProviderConnectionIntent(query)) {
9
+ const providerConnectionIntent = isSimpleProviderConnectionIntent(query);
10
+ if (!providerConnectionIntent
11
+ || (route?.card.id !== "tables" && route?.card.id !== "workspace-access")) {
10
12
  return route;
11
13
  }
12
14
  return getCapabilityRouteMatch("connected-integrations") ?? route;
@@ -19,5 +21,9 @@ function isSimpleProviderConnectionIntent(query) {
19
21
  if (/\b(integration|provider|oauth|byok|api key|connected account)\b/.test(normalized)) {
20
22
  return /\b(connect|reconnect|disconnect)\b/.test(normalized);
21
23
  }
22
- return /\b(?:connect|reconnect|disconnect)\s+(?:(?:my|our|the)\s+)?(?!(?:this|that|them|us|me)\b)[a-z0-9][a-z0-9.]*\s*$/.test(normalized);
24
+ const match = normalized.match(/\b(?:connect|reconnect|disconnect)\s+(?:(?:my|our|the)\s+)?([a-z0-9][a-z0-9.]*)(?:\s+(account|workspace))?\s*$/);
25
+ if (!match)
26
+ return false;
27
+ return !new Set(["this", "that", "them", "us", "me", "account", "workspace"])
28
+ .has(match[1] ?? "");
23
29
  }
@@ -1,3 +1,4 @@
1
- export declare const OXYGEN_VERSION = "1.739.0";
1
+ export declare const OXYGEN_VERSION = "1.750.4";
2
2
  export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.181.0";
3
3
  export declare const MANAGED_INBOX_MINIMUM_CLI_VERSION = "1.326.2";
4
+ export declare const SUPPORT_AGENT_REPLY_MINIMUM_CLI_VERSION = "1.747.0";
@@ -1,4 +1,4 @@
1
- export const OXYGEN_VERSION = "1.739.0";
1
+ export const OXYGEN_VERSION = "1.750.4";
2
2
  // The GLOBAL CLI compatibility floor: the oldest CLI allowed to call any
3
3
  // operational route. Raising it hard-rejects every older CLI from the entire
4
4
  // product, so it obeys one law, enforced by scripts/ci/cli-min-version-gate.mjs:
@@ -39,3 +39,11 @@ export const OXYGEN_MINIMUM_CLI_VERSION = "1.181.0";
39
39
  // so it can never exceed npm's newest CLI. (1.298.0 was the prior credits→Stripe
40
40
  // contract move.) Do NOT raise the global OXYGEN_MINIMUM_CLI_VERSION for this.
41
41
  export const MANAGED_INBOX_MINIMUM_CLI_VERSION = "1.326.2";
42
+ // Per-surface floor for guarded Plain agent reply + Done. CLI 1.747.0 is the
43
+ // first client that declares --agent, binds the latest customer message and
44
+ // body-authenticated preview token, carries the live agent_done_token into the
45
+ // separate Done confirmation, and never falls through to the retired resolve
46
+ // shape. This floor MUST remain dormant in production until 1.747.0 has been
47
+ // published by the preceding release per OXY-4091; the live feature gate is the
48
+ // rollout boundary that preserves that two-phase ordering.
49
+ export const SUPPORT_AGENT_REPLY_MINIMUM_CLI_VERSION = "1.747.0";
@@ -1,4 +1,4 @@
1
- import type { WorkflowCompareOp, WorkflowCondition, WorkflowValueRef } from "./types.js";
1
+ import type { WorkflowCompareOp, WorkflowCondition, WorkflowValueCoercion, WorkflowValueRef } from "./types.js";
2
2
  /**
3
3
  * The data a v2 run exposes to its value refs.
4
4
  *
@@ -22,6 +22,20 @@ export type WorkflowRunScope = {
22
22
  contextProfile?: unknown;
23
23
  contextAssets?: Record<string, unknown>;
24
24
  };
25
+ /** A deterministic authoring/data error; retrying the same revision cannot repair it. */
26
+ export declare class WorkflowValueCoercionError extends Error {
27
+ readonly coercion: WorkflowValueCoercion;
28
+ readonly code = "workflow_value_coercion_failed";
29
+ constructor(coercion: WorkflowValueCoercion);
30
+ }
31
+ /** A direct mapping named data the run does not carry; sending undefined is never intentional. */
32
+ export declare class WorkflowValueRefUnresolvedError extends Error {
33
+ readonly path: string;
34
+ readonly code = "workflow_value_ref_unresolved";
35
+ constructor(path: string);
36
+ }
37
+ /** Apply one explicit scalar conversion without lossy truthiness or truncation. */
38
+ export declare function coerceWorkflowValue(value: unknown, coercion: WorkflowValueCoercion): string | number | boolean;
25
39
  /** The roots a `ref` path may start from. Anything else is not a scope path. */
26
40
  export declare const SCOPE_PATH_ROOTS: ReadonlySet<string>;
27
41
  export declare const WORKFLOW_COMPARE_OPS: ReadonlySet<WorkflowCompareOp>;
@@ -40,6 +54,8 @@ export declare const UNARY_COMPARE_OPS: ReadonlySet<WorkflowCompareOp>;
40
54
  * typo surfaces in lint rather than as a silently empty value at run time.
41
55
  */
42
56
  export declare function parseScopePath(path: string): string[] | null;
57
+ /** Canonical path formatter: dots for simple keys, JSON brackets for every other key. */
58
+ export declare function formatScopePath(segments: readonly string[]): string | null;
43
59
  /** Resolve a dotted (+ `[n]`) scope path. Missing or malformed → undefined. */
44
60
  export declare function resolvePath(path: string, scope: WorkflowRunScope): unknown;
45
61
  /**
@@ -49,6 +65,16 @@ export declare function resolvePath(path: string, scope: WorkflowRunScope): unkn
49
65
  */
50
66
  export declare function renderTemplate(template: string, scope: WorkflowRunScope): string;
51
67
  export declare function resolveValueRef(ref: WorkflowValueRef, scope: WorkflowRunScope): unknown;
68
+ /**
69
+ * Resolve a binding that is about to cross an execution boundary.
70
+ *
71
+ * Conditions and optional control-flow refs deliberately keep total lookup
72
+ * semantics: a missing path is `undefined`, so `is_empty` remains useful. Tool
73
+ * requests are different — silently handing `undefined` to a provider makes a
74
+ * stale mapping look like a provider bug. Call this stricter adapter at that
75
+ * boundary so the failure names the mapped path before any provider executes.
76
+ */
77
+ export declare function resolveRequiredValueRef(ref: WorkflowValueRef, scope: WorkflowRunScope): unknown;
52
78
  export declare function evaluateCondition(condition: WorkflowCondition, scope: WorkflowRunScope): boolean;
53
79
  /**
54
80
  * Compile a `matches` pattern, or return null when it is unsafe or invalid.
@@ -76,3 +102,5 @@ export declare function compileConditionPattern(pattern: string): RegExp | null;
76
102
  * with the manifest's declared node ids.
77
103
  */
78
104
  export declare function referencedNodeIds(ref: WorkflowValueRef | WorkflowCondition): string[];
105
+ /** Exact rooted scope paths a ref/condition can read, excluding ambiguous bare formula names. */
106
+ export declare function referencedScopePaths(ref: WorkflowValueRef | WorkflowCondition): string[];