@oxygen-agent/cli 1.832.1 → 1.833.2

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.832.1
37
+ Version: 1.833.2
package/dist/index.js CHANGED
@@ -2835,7 +2835,7 @@ export function createProgram() {
2835
2835
  }));
2836
2836
  program
2837
2837
  .command("support")
2838
- .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.")
2838
+ .description("Open and track Plain support conversations for the active OXYGEN organization. In the app, the support chat is the widget in the bottom-right corner of every page. 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.")
2839
2839
  .addCommand(new Command("file")
2840
2840
  .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.")
2841
2841
  .requiredOption("--subject <subject>", "One-line summary of the problem.")
@@ -14,6 +14,12 @@ export type FormulaScope = {
14
14
  * reference must fail loudly rather than silently evaluate to null.
15
15
  */
16
16
  resolve(identifier: string): unknown;
17
+ /**
18
+ * Resolve a direct identifier argument to COALESCE. Hosts may return null
19
+ * for a recognized-but-absent optional path while keeping typos, unknown
20
+ * roots, and identifiers nested inside larger expressions strict.
21
+ */
22
+ resolveCoalescible?(identifier: string): unknown;
17
23
  /**
18
24
  * Lenient read used only by cross-row functions, which take a column key
19
25
  * rather than an in-scope identifier and treat an absent value as blank.
@@ -6,15 +6,20 @@ export function evaluateFormulaInScope(expression, scope, options = {}) {
6
6
  return evaluateFormulaAst(parseFormulaExpression(expression, options), scope);
7
7
  }
8
8
  export function evaluateFormulaAst(node, scope) {
9
+ return evaluateFormulaAstWithMode(node, scope, "strict");
10
+ }
11
+ function evaluateFormulaAstWithMode(node, scope, resolutionMode) {
9
12
  switch (node.type) { // skipcq: JS-0047
10
13
  case "literal":
11
14
  return node.value;
12
15
  case "identifier":
13
- return scope.resolve(node.name);
16
+ return resolutionMode === "coalescible" && scope.resolveCoalescible
17
+ ? scope.resolveCoalescible(node.name)
18
+ : scope.resolve(node.name);
14
19
  case "unary":
15
- return evaluateUnary(node.operator, evaluateFormulaAst(node.argument, scope));
20
+ return evaluateUnary(node.operator, evaluateFormulaAstWithMode(node.argument, scope, "strict"));
16
21
  case "binary":
17
- return evaluateBinary(node.operator, () => evaluateFormulaAst(node.left, scope), () => evaluateFormulaAst(node.right, scope));
22
+ return evaluateBinary(node.operator, () => evaluateFormulaAstWithMode(node.left, scope, "strict"), () => evaluateFormulaAstWithMode(node.right, scope, "strict"));
18
23
  case "call": {
19
24
  const normalized = node.name.toLowerCase();
20
25
  if (CROSS_ROW_FUNCTIONS.has(normalized)) {
@@ -23,11 +28,14 @@ export function evaluateFormulaAst(node, scope) {
23
28
  const spec = FORMULA_FUNCTION_REGISTRY.get(normalized);
24
29
  if (spec)
25
30
  checkFormulaFunctionArity(spec, node.args.length);
31
+ const evaluateArgument = (arg) => evaluateFormulaAstWithMode(arg, scope, normalized === "coalesce" && arg.type === "identifier"
32
+ ? "coalescible"
33
+ : "strict");
26
34
  if (spec?.lazy && spec.evaluateLazy) {
27
- return spec.evaluateLazy(node.args.map((arg) => () => evaluateFormulaAst(arg, scope)));
35
+ return spec.evaluateLazy(node.args.map((arg) => () => evaluateArgument(arg)));
28
36
  }
29
37
  if (spec?.evaluate) {
30
- return spec.evaluate(node.args.map((arg) => evaluateFormulaAst(arg, scope)));
38
+ return spec.evaluate(node.args.map(evaluateArgument));
31
39
  }
32
40
  throw formulaExpressionError("Unsupported formula function.", {
33
41
  function: node.name,
@@ -1017,11 +1017,19 @@ const SPECS = [
1017
1017
  name: "coalesce",
1018
1018
  category: "null_handling",
1019
1019
  signature: "coalesce(a, b, ...)",
1020
- description: "First non-blank argument, or null when all are blank.",
1020
+ description: "First non-blank argument, or null when all are blank. In Workflow formulas, an absent descendant of a valid trigger, step, or loop binding counts as blank.",
1021
1021
  minArgs: 0,
1022
1022
  maxArgs: null,
1023
+ lazy: true,
1023
1024
  examples: [{ expression: "coalesce(work_email, personal_email)", result: "jo@acme.com" }],
1024
- evaluate: (args) => args.find((arg) => !isBlankFormulaValue(arg)) ?? null,
1025
+ evaluateLazy: (thunks) => {
1026
+ for (const thunk of thunks) {
1027
+ const value = thunk();
1028
+ if (!isBlankFormulaValue(value))
1029
+ return value;
1030
+ }
1031
+ return null;
1032
+ },
1025
1033
  }),
1026
1034
  spec({
1027
1035
  name: "default",
@@ -1,4 +1,4 @@
1
- export declare const OXYGEN_VERSION = "1.832.1";
1
+ export declare const OXYGEN_VERSION = "1.833.2";
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
4
  export declare const SUPPORT_AGENT_REPLY_MINIMUM_CLI_VERSION = "1.747.0";
@@ -1,4 +1,4 @@
1
- export const OXYGEN_VERSION = "1.832.1";
1
+ export const OXYGEN_VERSION = "1.833.2";
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:
@@ -559,6 +559,17 @@ function formulaScopeFor(scope) {
559
559
  const found = lookupFormulaIdentifier(scope, identifier);
560
560
  return found.found ? found.value : undefined;
561
561
  },
562
+ resolveCoalescible(identifier) {
563
+ const found = lookupFormulaIdentifier(scope, identifier);
564
+ if (found.found)
565
+ return found.value;
566
+ if (isCoalescibleWorkflowFormulaPath(scope, identifier))
567
+ return null;
568
+ throw formulaExpressionError("Unknown identifier in workflow formula.", {
569
+ identifier,
570
+ hint: "Reference a workflow input, a node id, or a loop node id.",
571
+ });
572
+ },
562
573
  };
563
574
  }
564
575
  function lookupFormulaIdentifier(scope, identifier) {
@@ -587,6 +598,35 @@ function lookupFormulaIdentifier(scope, identifier) {
587
598
  return { found: true, value: readSegment(loop, "item") };
588
599
  return { found: false };
589
600
  }
601
+ /**
602
+ * COALESCE may absorb an optional value below a real workflow binding, but it
603
+ * must not turn a misspelled root, node id, or structural segment into blank.
604
+ */
605
+ function isCoalescibleWorkflowFormulaPath(scope, identifier) {
606
+ const segments = parseScopePath(identifier);
607
+ if (!segments)
608
+ return false;
609
+ const [root, owner, slot] = segments;
610
+ if (root === "trigger") {
611
+ return segments.length >= 3
612
+ && scope.trigger !== undefined
613
+ && (owner === "input" || owner === "payload");
614
+ }
615
+ if (root === "steps") {
616
+ return segments.length >= 4
617
+ && owner !== undefined
618
+ && slot === "output"
619
+ && Object.prototype.hasOwnProperty.call(scope.steps, owner);
620
+ }
621
+ if (root === "loop") {
622
+ return segments.length >= 4
623
+ && owner !== undefined
624
+ && slot === "item"
625
+ && scope.loop !== undefined
626
+ && Object.prototype.hasOwnProperty.call(scope.loop, owner);
627
+ }
628
+ return false;
629
+ }
590
630
  function isPlainObject(value) {
591
631
  return typeof value === "object" && value !== null && !Array.isArray(value);
592
632
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.832.1",
3
+ "version": "1.833.2",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",