@oxygen-agent/cli 1.1014.619 → 1.1014.771

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.
Files changed (65) hide show
  1. package/README.md +1 -1
  2. package/dist/command-manifest.js +15 -0
  3. package/dist/command-support.d.ts +5 -5
  4. package/dist/command-support.js +7 -7
  5. package/dist/commands/billing.js +12 -6
  6. package/dist/commands/columns.js +2 -2
  7. package/dist/commands/companies.js +1 -1
  8. package/dist/commands/copilot.js +1 -1
  9. package/dist/commands/dashboards.js +1 -1
  10. package/dist/commands/enrich-column.js +2 -2
  11. package/dist/commands/find.js +2 -2
  12. package/dist/commands/mailboxes.js +36 -0
  13. package/dist/commands/recipes.js +1 -1
  14. package/dist/commands/sequences.js +6 -6
  15. package/dist/commands/tables.js +1 -1
  16. package/dist/commands/verify.js +112 -11
  17. package/dist/http-client.js +1 -70
  18. package/dist/index.js +1 -1
  19. package/dist/refactor-internals/binary-download.d.ts +5 -0
  20. package/dist/refactor-internals/binary-download.js +71 -0
  21. package/node_modules/@oxygen/formula/dist/evaluate.js +2 -82
  22. package/node_modules/@oxygen/formula/dist/refactor-internals/novelty-score.d.ts +2 -0
  23. package/node_modules/@oxygen/formula/dist/refactor-internals/novelty-score.js +87 -0
  24. package/node_modules/@oxygen/shared/dist/billing.d.ts +15 -3
  25. package/node_modules/@oxygen/shared/dist/billing.js +19 -7
  26. package/node_modules/@oxygen/shared/dist/cancellation-reasons.d.ts +35 -0
  27. package/node_modules/@oxygen/shared/dist/cancellation-reasons.js +21 -0
  28. package/node_modules/@oxygen/shared/dist/capability-discovery.js +27 -1
  29. package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +6 -6
  30. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +1 -1
  31. package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +1 -0
  32. package/node_modules/@oxygen/shared/dist/contracts/teammates.js +41 -17
  33. package/node_modules/@oxygen/shared/dist/email-verification-batch.d.ts +26 -0
  34. package/node_modules/@oxygen/shared/dist/email-verification-batch.js +55 -0
  35. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +3 -3
  36. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +3 -3
  37. package/node_modules/@oxygen/shared/dist/knowledge-brain-draft.d.ts +70 -0
  38. package/node_modules/@oxygen/shared/dist/knowledge-brain-draft.js +42 -0
  39. package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -1
  40. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
  41. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +3 -1
  42. package/node_modules/@oxygen/shared/dist/product-doctrine.js +1 -1
  43. package/node_modules/@oxygen/shared/dist/sequence-internals/esp-policy.d.ts +225 -0
  44. package/node_modules/@oxygen/shared/dist/sequence-internals/esp-policy.js +578 -0
  45. package/node_modules/@oxygen/shared/dist/sequences.d.ts +1 -226
  46. package/node_modules/@oxygen/shared/dist/sequences.js +2 -576
  47. package/node_modules/@oxygen/shared/dist/teammate-groups.d.ts +103 -24
  48. package/node_modules/@oxygen/shared/dist/teammate-groups.js +125 -32
  49. package/node_modules/@oxygen/shared/dist/teammate-messages.d.ts +1 -1
  50. package/node_modules/@oxygen/shared/dist/teammates.d.ts +28 -2
  51. package/node_modules/@oxygen/shared/dist/teammates.js +32 -2
  52. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +40 -8
  53. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  54. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  55. package/node_modules/@oxygen/workflows/dist/graph/expression.d.ts +1 -16
  56. package/node_modules/@oxygen/workflows/dist/graph/expression.js +4 -224
  57. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +2 -2
  58. package/node_modules/@oxygen/workflows/dist/index.d.ts +1 -63
  59. package/node_modules/@oxygen/workflows/dist/index.js +1 -193
  60. package/node_modules/@oxygen/workflows/dist/refactor-internals/condition-comparison.d.ts +18 -0
  61. package/node_modules/@oxygen/workflows/dist/refactor-internals/condition-comparison.js +222 -0
  62. package/node_modules/@oxygen/workflows/dist/refactor-internals/execution-policy.d.ts +64 -0
  63. package/node_modules/@oxygen/workflows/dist/refactor-internals/execution-policy.js +204 -0
  64. package/node_modules/@oxygen/workflows/package.json +4 -0
  65. package/package.json +1 -1
@@ -3,7 +3,7 @@
3
3
  // scheduler, the linter, and the visual editor's preview pane.
4
4
  //
5
5
  // This module must stay BROWSER-SAFE. Its only imports are @oxygen/formula (itself
6
- // dependency-free and node-free) and ./types.js — never ../index.js, which pulls
6
+ // host-free and node-free), condition comparison, and ./types.js — never ../index.js, which pulls
7
7
  // node:vm and node:crypto.
8
8
  //
9
9
  // Two invariants hold everywhere below:
@@ -14,6 +14,8 @@
14
14
  // only, and the segments that reach JavaScript object internals
15
15
  // (__proto__ / constructor / prototype) are rejected outright.
16
16
  import { analyzeFormulaDependencies, evaluateFormulaInScope, formulaExpressionError, isRecord, } from "@oxygen/formula";
17
+ import { compareWorkflowConditionValues } from "../refactor-internals/condition-comparison.js";
18
+ export { compileConditionPattern } from "../refactor-internals/condition-comparison.js";
17
19
  /** A deterministic authoring/data error; retrying the same revision cannot repair it. */
18
20
  export class WorkflowValueCoercionError extends Error {
19
21
  coercion;
@@ -656,229 +658,7 @@ export function evaluateCondition(condition, scope) {
656
658
  }
657
659
  const left = resolveValueRef(condition.left, scope);
658
660
  const right = condition.right === undefined ? undefined : resolveValueRef(condition.right, scope);
659
- return compareValues(left, condition.op, right);
660
- }
661
- function compareValues(left, op, right) {
662
- switch (op) {
663
- case "eq":
664
- return valuesEqual(left, right);
665
- case "neq":
666
- return !valuesEqual(left, right);
667
- case "gt":
668
- return orderingOf(left, right) > 0;
669
- case "gte":
670
- return orderingOf(left, right) >= 0;
671
- case "lt":
672
- return orderingOf(left, right) < 0;
673
- case "lte":
674
- return orderingOf(left, right) <= 0;
675
- case "contains":
676
- return containsValue(left, right);
677
- case "not_contains":
678
- return !containsValue(left, right);
679
- case "starts_with":
680
- return textOf(left).startsWith(textOf(right));
681
- case "ends_with":
682
- return textOf(left).endsWith(textOf(right));
683
- case "is_empty":
684
- return isEmptyValue(left);
685
- case "is_not_empty":
686
- return !isEmptyValue(left);
687
- case "in":
688
- return containsValue(right, left);
689
- case "not_in":
690
- return !containsValue(right, left);
691
- case "matches":
692
- return matchesPattern(left, right);
693
- }
694
- }
695
- /**
696
- * Numeric when both sides read as numbers, otherwise strict identity with
697
- * null/undefined treated as one blank. Deliberately NOT the formula language's
698
- * `formulaValuesEqual`, which throws on non-numeric input: a condition gates
699
- * routing, and a run must not fail because a provider returned an object where
700
- * the author expected a string.
701
- */
702
- function valuesEqual(left, right) {
703
- if (left === right)
704
- return true;
705
- if (left === null || left === undefined)
706
- return right === null || right === undefined;
707
- if (right === null || right === undefined)
708
- return false;
709
- const leftNumber = numberOf(left);
710
- const rightNumber = numberOf(right);
711
- if (leftNumber !== null && rightNumber !== null)
712
- return leftNumber === rightNumber;
713
- if (Array.isArray(left) && Array.isArray(right)) {
714
- return left.length === right.length
715
- && left.every((entry, index) => valuesEqual(entry, right[index]));
716
- }
717
- return false;
718
- }
719
- /**
720
- * Ordering for the relational operators. Incomparable operands sort as 0, so
721
- * `gt`/`lt` are false and `gte`/`lte` are true — the same shape JavaScript gives
722
- * for equal values, and never an exception.
723
- */
724
- function orderingOf(left, right) {
725
- const leftNumber = numberOf(left);
726
- const rightNumber = numberOf(right);
727
- if (leftNumber !== null && rightNumber !== null) {
728
- if (leftNumber === rightNumber)
729
- return 0;
730
- return leftNumber < rightNumber ? -1 : 1;
731
- }
732
- if (typeof left === "string" && typeof right === "string") {
733
- return left.localeCompare(right);
734
- }
735
- return 0;
736
- }
737
- function numberOf(value) {
738
- if (typeof value === "number")
739
- return Number.isFinite(value) ? value : null;
740
- if (typeof value === "string" && value.trim()) {
741
- const parsed = Number(value);
742
- return Number.isFinite(parsed) ? parsed : null;
743
- }
744
- return null;
745
- }
746
- /** Membership for arrays, case-sensitive substring for text. */
747
- function containsValue(haystack, needle) {
748
- if (Array.isArray(haystack))
749
- return haystack.some((entry) => valuesEqual(entry, needle));
750
- if (haystack === null || haystack === undefined)
751
- return false;
752
- if (needle === null || needle === undefined)
753
- return false;
754
- return textOf(haystack).includes(textOf(needle));
755
- }
756
- function isEmptyValue(value) {
757
- if (value === null || value === undefined)
758
- return true;
759
- if (typeof value === "string")
760
- return value.trim().length === 0;
761
- if (Array.isArray(value))
762
- return value.length === 0;
763
- // 0 and false are values an author chose, not emptiness.
764
- if (typeof value === "object")
765
- return Object.keys(value).length === 0;
766
- return false;
767
- }
768
- function textOf(value) {
769
- if (value === null || value === undefined)
770
- return "";
771
- if (typeof value === "string")
772
- return value;
773
- if (typeof value === "number" || typeof value === "boolean")
774
- return String(value);
775
- try {
776
- return JSON.stringify(value) ?? "";
777
- }
778
- catch {
779
- return "";
780
- }
781
- }
782
- // ---------------------------------------------------------------------------
783
- // `matches`
784
- // ---------------------------------------------------------------------------
785
- const MAX_CONDITION_PATTERN_LENGTH = 200;
786
- /**
787
- * Compile a `matches` pattern, or return null when it is unsafe or invalid.
788
- *
789
- * Choice: conservative STATIC REJECTION rather than a linear (Thompson NFA)
790
- * matcher. A linear engine would be several hundred lines of new, security-load-
791
- * bearing code in a module the browser also bundles, whereas the patterns real
792
- * GTM authors write (domain suffixes, job-title keywords, ID formats) never need
793
- * the constructs rejected here. Rejected: sources over 200 characters,
794
- * lookaround, backreferences, and a quantified group whose body itself contains
795
- * an unbounded quantifier — the `(a+)+` family that drives catastrophic
796
- * backtracking. Everything else compiles as a normal RegExp.
797
- *
798
- * The linter calls this at authoring time and reports `invalid_condition`, so a
799
- * rejected pattern is a lint error the author sees, not a silent run-time false.
800
- */
801
- export function compileConditionPattern(pattern) {
802
- if (!isSafeRegExpSource(pattern))
803
- return null;
804
- try {
805
- return new RegExp(pattern);
806
- }
807
- catch {
808
- return null;
809
- }
810
- }
811
- function isSafeRegExpSource(source) {
812
- if (!source || source.length > MAX_CONDITION_PATTERN_LENGTH)
813
- return false;
814
- if (/\(\?<?[=!]/.test(source))
815
- return false; // lookahead / lookbehind
816
- if (/\\[1-9]/.test(source))
817
- return false; // backreference
818
- return !hasNestedQuantifier(source);
819
- }
820
- function isUnboundedQuantifier(char) {
821
- // `?` alone is bounded and cannot blow up; `*`, `+` and `{n,m}` can.
822
- return char === "*" || char === "+" || char === "{";
823
- }
824
- function hasNestedQuantifier(source) {
825
- // One frame per open group, plus a base frame for the top level. A frame is
826
- // "quantified" once anything inside it repeats.
827
- const stack = [{ quantified: false }];
828
- let inCharacterClass = false;
829
- let index = 0;
830
- while (index < source.length) {
831
- const char = source[index] ?? "";
832
- if (char === "\\") {
833
- index += 2;
834
- continue;
835
- }
836
- if (inCharacterClass) {
837
- if (char === "]")
838
- inCharacterClass = false;
839
- index += 1;
840
- continue;
841
- }
842
- if (char === "[") {
843
- inCharacterClass = true;
844
- index += 1;
845
- continue;
846
- }
847
- if (char === "(") {
848
- stack.push({ quantified: false });
849
- index += 1;
850
- continue;
851
- }
852
- if (char === ")") {
853
- index += 1;
854
- // Unbalanced ")" — leave it to RegExp construction to reject.
855
- if (stack.length === 1)
856
- continue;
857
- const frame = stack.pop();
858
- const repeated = isUnboundedQuantifier(source[index] ?? "");
859
- if (repeated && frame?.quantified === true)
860
- return true;
861
- const parent = stack[stack.length - 1];
862
- if (parent && (repeated || frame?.quantified === true))
863
- parent.quantified = true;
864
- continue;
865
- }
866
- if (isUnboundedQuantifier(char)) {
867
- const top = stack[stack.length - 1];
868
- if (top)
869
- top.quantified = true;
870
- }
871
- index += 1;
872
- }
873
- return false;
874
- }
875
- function matchesPattern(value, pattern) {
876
- if (typeof pattern !== "string")
877
- return false;
878
- const regex = compileConditionPattern(pattern);
879
- if (!regex)
880
- return false;
881
- return regex.test(textOf(value));
661
+ return compareWorkflowConditionValues(left, condition.op, right);
882
662
  }
883
663
  // ---------------------------------------------------------------------------
884
664
  // Static analysis
@@ -1,3 +1,5 @@
1
+ import type { WorkflowCompareOp } from "../refactor-internals/condition-comparison.js";
2
+ export type { WorkflowCompareOp } from "../refactor-internals/condition-comparison.js";
1
3
  import type { JsonSchema, RecipeManifest, WorkflowMode, WorkflowStatus, WorkflowStepEffect, WorkflowTriggerManifest } from "../index.js";
2
4
  export declare const WORKFLOW_GRAPH_MANIFEST_VERSION = 2;
3
5
  export declare const WORKFLOW_GRAPH_COMPILER_VERSION = "oxygen-workflows-v2";
@@ -220,7 +222,6 @@ export type WorkflowCodeV1 = {
220
222
  max_output_bytes: number;
221
223
  };
222
224
  };
223
- export type WorkflowCompareOp = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "contains" | "not_contains" | "starts_with" | "ends_with" | "is_empty" | "is_not_empty" | "in" | "not_in" | "matches";
224
225
  /**
225
226
  * A structured (never source-code) predicate, so the editor can render it and the
226
227
  * runtime can evaluate it without a sandbox.
@@ -489,4 +490,3 @@ export declare function isTriggerNode(node: WorkflowGraphNode): node is Workflow
489
490
  export declare function isToolNode(node: WorkflowGraphNode): node is WorkflowGraphNodeOfKind<"tool">;
490
491
  export declare function isSwitchNode(node: WorkflowGraphNode): node is WorkflowGraphNodeOfKind<"switch">;
491
492
  export declare function isLoopNode(node: WorkflowGraphNode): node is WorkflowGraphNodeOfKind<"loop">;
492
- export {};
@@ -7,6 +7,7 @@ export * from "./graph/index.js";
7
7
  export * from "./graph/lint.js";
8
8
  export * from "./portable.js";
9
9
  export { RECIPE_TIMESTAMP_MILLIS_SOURCE } from "./recipe-timestamp.js";
10
+ export * from "./refactor-internals/execution-policy.js";
10
11
  export declare const WORKFLOW_MANIFEST_VERSION = 1;
11
12
  export declare const WORKFLOW_COMPILER_VERSION = "oxygen-workflows-v1";
12
13
  export declare const DURABLE_RECIPE_COMPILER_VERSION = "oxygen-recipes-v2";
@@ -1048,66 +1049,3 @@ value: unknown, path: string, add: (path: string, code: string, message: string)
1048
1049
  export declare function validatePureFunctionSource(source: string, path: string, add: (path: string, code: string, message: string) => void): void;
1049
1050
  export declare function isWorkflowToolRejected(toolId: string): boolean;
1050
1051
  export declare function computeWorkflowPlanHash(input: Record<string, unknown>): string;
1051
- export declare const WORKFLOW_MAX_CREDITS_EXCEEDED_ERROR_CODE = "max_credits_exceeded";
1052
- export declare function readWorkflowRunMaxCredits(metadata: Record<string, unknown> | null | undefined): number | null;
1053
- export declare const WORKFLOW_STANDING_AUTHORIZATION_METADATA_KEY = "standing_authorization";
1054
- export declare const WORKFLOW_STANDING_AUTHORIZATION_POLICY_METADATA_KEY = "standing_authorization_policy";
1055
- export declare const WORKFLOW_STANDING_AUTHORIZATION_POLICY_REVISION_BOUND_V1 = "revision_bound_v1";
1056
- export declare const WORKFLOW_STANDING_AUTHORIZATION_REQUIRED_ERROR_CODE = "workflow_standing_authorization_required";
1057
- export declare const WORKFLOW_STANDING_AUTHORIZATION_REQUIRED_MESSAGE = "This autonomous workflow can call tools and needs standing authorization for its current revision. Re-enable it with approved=true; pass a positive max_credits per delivery or omit it to use the plan default.";
1058
- type WorkflowStandingAuthorizationBase = {
1059
- approved: true;
1060
- revisionId: string;
1061
- authorizedByActorId: string;
1062
- authorizedAt: string;
1063
- };
1064
- export type WorkflowStandingAuthorization = WorkflowStandingAuthorizationBase & ({
1065
- capPolicy: "explicit";
1066
- maxCredits: number;
1067
- } | {
1068
- capPolicy: "plan_default";
1069
- maxCredits: null;
1070
- });
1071
- export declare function isAutonomousWorkflowTriggerType(value: unknown): value is "cron" | "webhook" | "event";
1072
- /**
1073
- * Whether this manifest can cross into a tool at runtime. Hosted workflows are
1074
- * explicit in `steps`; oxygen-workflows-v2 graphs are explicit in `nodes`;
1075
- * durable recipes declare the complete allowlist in `tools_used`. Keeping this
1076
- * predicate structural means legacy revisions can be checked before execution
1077
- * without trusting trigger metadata.
1078
- *
1079
- * The v2 branch is load-bearing, not cosmetic: this predicate is the ONLY input
1080
- * to workflowRequiresStandingAuthorization, so a graph it does not recognise arms
1081
- * a cron / webhook / event trigger with no standing authorization and therefore
1082
- * no per-delivery credit ceiling. A `workflow` (sub-graph) node counts as well —
1083
- * a child run can invoke tools of its own, and the gate must fail closed.
1084
- */
1085
- export declare function workflowManifestCanInvokeTool(manifest: unknown): boolean;
1086
- /** The single apply/enable/runtime gate for standing autonomous authority. */
1087
- export declare function workflowRequiresStandingAuthorization(manifest: unknown, triggerType?: unknown): boolean;
1088
- /**
1089
- * Runtime enforcement marker for triggers created or explicitly re-armed after
1090
- * revision-bound standing authority shipped. Legacy active triggers have no
1091
- * marker and stay runnable during the default-ceiling compatibility rollout;
1092
- * every new/re-armed tool trigger carries this marker and fails closed if its
1093
- * matching authorization is missing or stale.
1094
- */
1095
- export declare function workflowStandingAuthorizationPolicyIsEnforced(metadata: Record<string, unknown> | null | undefined, triggerDefaultCapsMode?: unknown): boolean;
1096
- /**
1097
- * Read the standing authority for one autonomous trigger revision. A null
1098
- * result is fail-closed for policy-marked triggers. Legacy active rows
1099
- * are distinguished by the separate policy marker so they can continue under
1100
- * the fire-time default-ceiling compatibility rollout.
1101
- */
1102
- export declare function readWorkflowStandingAuthorization(metadata: Record<string, unknown> | null | undefined, revisionId: string): WorkflowStandingAuthorization | null;
1103
- export declare function readWorkflowRunPreApprovedLabels(metadata: Record<string, unknown> | null | undefined): string[];
1104
- export declare function readManagedToolRunCredits(output: unknown): number;
1105
- export type WorkflowSpendCapDecision = {
1106
- allowed: boolean;
1107
- projectedCredits: number;
1108
- };
1109
- export declare function evaluateWorkflowRunSpendCap(input: {
1110
- maxCredits: number;
1111
- creditsUsed: number;
1112
- estimatedCredits?: number | null;
1113
- }): WorkflowSpendCapDecision;
@@ -14,6 +14,7 @@ export * from "./graph/index.js";
14
14
  export * from "./graph/lint.js";
15
15
  export * from "./portable.js";
16
16
  export { RECIPE_TIMESTAMP_MILLIS_SOURCE } from "./recipe-timestamp.js";
17
+ export * from "./refactor-internals/execution-policy.js";
17
18
  export const WORKFLOW_MANIFEST_VERSION = 1;
18
19
  export const WORKFLOW_COMPILER_VERSION = "oxygen-workflows-v1";
19
20
  export const DURABLE_RECIPE_COMPILER_VERSION = "oxygen-recipes-v2";
@@ -2025,199 +2026,6 @@ function isNonEmptyString(value) {
2025
2026
  // (OXYGEN_INTERNAL_WORKFLOW_TOOL_IDS). Both the worker's runtime registry and the
2026
2027
  // web's lint allowlist derive from it, so re-declaring it here would recreate
2027
2028
  // exactly the drift that broke crm-sync.
2028
- // Live `workflows call` requests carry an approved max_credits spend cap in
2029
- // the run's metadata (R-E.22(c)). These pure helpers are the single source of
2030
- // truth for reading that cap and deciding whether the next paid tool step may
2031
- // run; the worker's step loop and the durable-recipe runtime wrap the refusal
2032
- // in an OxygenError with this code so the stop is visible as run/step state.
2033
- export const WORKFLOW_MAX_CREDITS_EXCEEDED_ERROR_CODE = "max_credits_exceeded";
2034
- // Matches the soft-ceiling epsilon used by table action runs so float drift
2035
- // never refuses a run that is exactly at its cap.
2036
- const WORKFLOW_SPEND_CAP_EPSILON = 0.000001;
2037
- export function readWorkflowRunMaxCredits(metadata) {
2038
- if (!isRecord(metadata))
2039
- return null;
2040
- const raw = metadata.max_credits ?? metadata.maxCredits;
2041
- const parsed = typeof raw === "number"
2042
- ? raw
2043
- : typeof raw === "string" && raw.trim()
2044
- ? Number(raw)
2045
- : Number.NaN;
2046
- if (!Number.isFinite(parsed) || parsed <= 0)
2047
- return null;
2048
- return parsed;
2049
- }
2050
- export const WORKFLOW_STANDING_AUTHORIZATION_METADATA_KEY = "standing_authorization";
2051
- export const WORKFLOW_STANDING_AUTHORIZATION_POLICY_METADATA_KEY = "standing_authorization_policy";
2052
- export const WORKFLOW_STANDING_AUTHORIZATION_POLICY_REVISION_BOUND_V1 = "revision_bound_v1";
2053
- export const WORKFLOW_STANDING_AUTHORIZATION_REQUIRED_ERROR_CODE = "workflow_standing_authorization_required";
2054
- export const WORKFLOW_STANDING_AUTHORIZATION_REQUIRED_MESSAGE = "This autonomous workflow can call tools and needs standing authorization for its current revision. Re-enable it with approved=true; pass a positive max_credits per delivery or omit it to use the plan default.";
2055
- export function isAutonomousWorkflowTriggerType(value) {
2056
- return value === "cron" || value === "webhook" || value === "event";
2057
- }
2058
- /**
2059
- * Whether this manifest can cross into a tool at runtime. Hosted workflows are
2060
- * explicit in `steps`; oxygen-workflows-v2 graphs are explicit in `nodes`;
2061
- * durable recipes declare the complete allowlist in `tools_used`. Keeping this
2062
- * predicate structural means legacy revisions can be checked before execution
2063
- * without trusting trigger metadata.
2064
- *
2065
- * The v2 branch is load-bearing, not cosmetic: this predicate is the ONLY input
2066
- * to workflowRequiresStandingAuthorization, so a graph it does not recognise arms
2067
- * a cron / webhook / event trigger with no standing authorization and therefore
2068
- * no per-delivery credit ceiling. A `workflow` (sub-graph) node counts as well —
2069
- * a child run can invoke tools of its own, and the gate must fail closed.
2070
- */
2071
- export function workflowManifestCanInvokeTool(manifest) {
2072
- if (!isRecord(manifest))
2073
- return false;
2074
- // EVERY shape is checked and the results OR'd — deliberately not a chain of
2075
- // early returns dispatching on which field is present.
2076
- //
2077
- // The chain was a bypass: `tools_used` was tested first and returned from that
2078
- // branch, so a v2 graph manifest carrying an extra `tools_used: []` never
2079
- // reached the `nodes` check and reported "cannot invoke a tool". That is this
2080
- // predicate's ONLY input, so apply then skipped the standing-authorization gate
2081
- // for a cron-triggered graph full of tool nodes, left the trigger unmarked so
2082
- // the fire-time check was fooled too, and resolved a delivery cap of null — no
2083
- // ceiling at all. The graph linter accepts the extra key, so nothing else
2084
- // objected either.
2085
- //
2086
- // A manifest that carries a tool node CAN invoke a tool no matter what other
2087
- // fields it also carries, so the honest form is a union that fails closed.
2088
- const viaRecipe = Array.isArray(manifest.tools_used)
2089
- && manifest.tools_used.some((toolId) => isNonEmptyString(toolId));
2090
- const viaGraph = Array.isArray(manifest.nodes)
2091
- && manifest.nodes.some((node) => isRecord(node) && (node.kind === "tool"
2092
- || (isRecord(node.recipe)
2093
- && Array.isArray(node.recipe.tools_used)
2094
- && node.recipe.tools_used.some((toolId) => isNonEmptyString(toolId)))));
2095
- const viaSteps = Array.isArray(manifest.steps)
2096
- && manifest.steps.some((step) => isRecord(step) && step.kind === "tool");
2097
- return viaRecipe || viaGraph || viaSteps;
2098
- }
2099
- /** The single apply/enable/runtime gate for standing autonomous authority. */
2100
- export function workflowRequiresStandingAuthorization(manifest, triggerType) {
2101
- const manifestTriggerType = isRecord(manifest)
2102
- && isRecord(manifest.trigger)
2103
- ? manifest.trigger.type
2104
- : "api";
2105
- return isAutonomousWorkflowTriggerType(triggerType ?? manifestTriggerType)
2106
- && workflowManifestCanInvokeTool(manifest);
2107
- }
2108
- /**
2109
- * Runtime enforcement marker for triggers created or explicitly re-armed after
2110
- * revision-bound standing authority shipped. Legacy active triggers have no
2111
- * marker and stay runnable during the default-ceiling compatibility rollout;
2112
- * every new/re-armed tool trigger carries this marker and fails closed if its
2113
- * matching authorization is missing or stale.
2114
- */
2115
- export function workflowStandingAuthorizationPolicyIsEnforced(metadata, triggerDefaultCapsMode) {
2116
- return triggerDefaultCapsMode === "enforce"
2117
- || (isRecord(metadata)
2118
- && metadata[WORKFLOW_STANDING_AUTHORIZATION_POLICY_METADATA_KEY]
2119
- === WORKFLOW_STANDING_AUTHORIZATION_POLICY_REVISION_BOUND_V1);
2120
- }
2121
- /**
2122
- * Read the standing authority for one autonomous trigger revision. A null
2123
- * result is fail-closed for policy-marked triggers. Legacy active rows
2124
- * are distinguished by the separate policy marker so they can continue under
2125
- * the fire-time default-ceiling compatibility rollout.
2126
- */
2127
- export function readWorkflowStandingAuthorization(metadata, revisionId) {
2128
- if (!isRecord(metadata))
2129
- return null;
2130
- const raw = metadata[WORKFLOW_STANDING_AUTHORIZATION_METADATA_KEY];
2131
- if (!isRecord(raw) || raw.approved !== true)
2132
- return null;
2133
- const maxCredits = typeof raw.max_credits === "number"
2134
- ? raw.max_credits
2135
- : Number(raw.max_credits);
2136
- const capPolicy = raw.cap_policy === "plan_default"
2137
- ? "plan_default"
2138
- : raw.cap_policy === "explicit" || raw.cap_policy === undefined
2139
- ? "explicit"
2140
- : null;
2141
- const authorizedAt = typeof raw.authorized_at === "string" ? raw.authorized_at.trim() : "";
2142
- const authorizedByActorId = typeof raw.authorized_by_actor_id === "string"
2143
- ? raw.authorized_by_actor_id.trim()
2144
- : "";
2145
- if (!capPolicy
2146
- || (capPolicy === "explicit" && (!Number.isFinite(maxCredits) || maxCredits <= 0))
2147
- || raw.revision_id !== revisionId
2148
- || !authorizedByActorId
2149
- || !authorizedAt
2150
- || Number.isNaN(Date.parse(authorizedAt))) {
2151
- return null;
2152
- }
2153
- const base = {
2154
- approved: true,
2155
- revisionId,
2156
- authorizedByActorId,
2157
- authorizedAt,
2158
- };
2159
- return capPolicy === "explicit"
2160
- ? { ...base, capPolicy, maxCredits }
2161
- : { ...base, capPolicy, maxCredits: null };
2162
- }
2163
- // Approval labels the run's enqueue route already collected SYNCHRONOUS human
2164
- // approval for (a CLI/MCP `approved=true` gate before the run was created), and
2165
- // recorded in run metadata as `preapproved_labels`. A live ctx.approvals.require()
2166
- // call whose `label` is in this set is honored immediately by both recipe
2167
- // executors instead of creating and parking on a durable decision row. Every
2168
- // other label goes through the same host-mediated durable gate under both local
2169
- // and sandbox executors, and the max_credits spend cap is
2170
- // enforced separately per paid tool step, so a pre-approved label never widens
2171
- // spend. Only trusted server code (the enqueue route) sets this — it is read
2172
- // from run metadata, never from user-suppliable workflow input.
2173
- export function readWorkflowRunPreApprovedLabels(metadata) {
2174
- if (!isRecord(metadata))
2175
- return [];
2176
- const raw = metadata.preapproved_labels ?? metadata.preApprovedLabels;
2177
- if (!Array.isArray(raw))
2178
- return [];
2179
- const labels = raw
2180
- .filter((entry) => typeof entry === "string")
2181
- .map((entry) => entry.trim())
2182
- .filter((entry) => entry.length > 0);
2183
- return Array.from(new Set(labels));
2184
- }
2185
- // Completed tool runs attach their captured OXYGEN charge in `meta.billing`.
2186
- // Managed provider credits and own-key platform fees both count toward an
2187
- // approved workflow ceiling; user OAuth calls carry neither charge.
2188
- export function readManagedToolRunCredits(output) {
2189
- if (!isRecord(output))
2190
- return 0;
2191
- const meta = output.meta;
2192
- if (!isRecord(meta))
2193
- return 0;
2194
- const billing = meta.billing;
2195
- if (!isRecord(billing))
2196
- return 0;
2197
- const credits = billing.credential_mode === "managed"
2198
- ? billing.managed_credit_estimate
2199
- : billing.credential_mode === "byok"
2200
- ? billing.byok_platform_fee_credits
2201
- : null;
2202
- if (typeof credits !== "number" || !Number.isFinite(credits) || credits <= 0)
2203
- return 0;
2204
- return credits;
2205
- }
2206
- // `estimatedCredits` is null when the next tool has no managed-credit price
2207
- // (free, BYOK, or unknown catalog entry): such a step is only refused when
2208
- // the cap is already breached, never pre-emptively.
2209
- export function evaluateWorkflowRunSpendCap(input) {
2210
- const estimated = typeof input.estimatedCredits === "number"
2211
- && Number.isFinite(input.estimatedCredits)
2212
- && input.estimatedCredits > 0
2213
- ? input.estimatedCredits
2214
- : 0;
2215
- const projectedCredits = input.creditsUsed + estimated;
2216
- return {
2217
- allowed: projectedCredits <= input.maxCredits + WORKFLOW_SPEND_CAP_EPSILON,
2218
- projectedCredits,
2219
- };
2220
- }
2221
2029
  function escapeRegExp(value) {
2222
2030
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2223
2031
  }
@@ -0,0 +1,18 @@
1
+ export type WorkflowCompareOp = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "contains" | "not_contains" | "starts_with" | "ends_with" | "is_empty" | "is_not_empty" | "in" | "not_in" | "matches";
2
+ export declare function compareWorkflowConditionValues(left: unknown, op: WorkflowCompareOp, right: unknown): boolean;
3
+ /**
4
+ * Compile a `matches` pattern, or return null when it is unsafe or invalid.
5
+ *
6
+ * Choice: conservative STATIC REJECTION rather than a linear (Thompson NFA)
7
+ * matcher. A linear engine would be several hundred lines of new, security-load-
8
+ * bearing code in a module the browser also bundles, whereas the patterns real
9
+ * GTM authors write (domain suffixes, job-title keywords, ID formats) never need
10
+ * the constructs rejected here. Rejected: sources over 200 characters,
11
+ * lookaround, backreferences, and a quantified group whose body itself contains
12
+ * an unbounded quantifier — the `(a+)+` family that drives catastrophic
13
+ * backtracking. Everything else compiles as a normal RegExp.
14
+ *
15
+ * The linter calls this at authoring time and reports `invalid_condition`, so a
16
+ * rejected pattern is a lint error the author sees, not a silent run-time false.
17
+ */
18
+ export declare function compileConditionPattern(pattern: string): RegExp | null;