@oxygen-agent/cli 1.1014.620 → 1.1014.773
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 +1 -1
- package/dist/command-manifest.js +15 -0
- package/dist/command-support.d.ts +5 -5
- package/dist/command-support.js +7 -7
- package/dist/commands/billing.js +12 -6
- package/dist/commands/columns.js +2 -2
- package/dist/commands/companies.js +1 -1
- package/dist/commands/copilot.js +1 -1
- package/dist/commands/dashboards.js +1 -1
- package/dist/commands/enrich-column.js +2 -2
- package/dist/commands/find.js +2 -2
- package/dist/commands/mailboxes.js +36 -0
- package/dist/commands/recipes.js +1 -1
- package/dist/commands/sequences.js +6 -6
- package/dist/commands/tables.js +1 -1
- package/dist/commands/verify.js +112 -11
- package/dist/http-client.js +1 -70
- package/dist/index.js +1 -1
- package/dist/refactor-internals/binary-download.d.ts +5 -0
- package/dist/refactor-internals/binary-download.js +71 -0
- package/node_modules/@oxygen/formula/dist/evaluate.js +2 -82
- package/node_modules/@oxygen/formula/dist/refactor-internals/novelty-score.d.ts +2 -0
- package/node_modules/@oxygen/formula/dist/refactor-internals/novelty-score.js +87 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +15 -3
- package/node_modules/@oxygen/shared/dist/billing.js +19 -7
- package/node_modules/@oxygen/shared/dist/cancellation-reasons.d.ts +35 -0
- package/node_modules/@oxygen/shared/dist/cancellation-reasons.js +21 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +27 -1
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +6 -6
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +1 -0
- package/node_modules/@oxygen/shared/dist/contracts/teammates.js +41 -17
- package/node_modules/@oxygen/shared/dist/email-verification-batch.d.ts +26 -0
- package/node_modules/@oxygen/shared/dist/email-verification-batch.js +55 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +3 -3
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +3 -3
- package/node_modules/@oxygen/shared/dist/knowledge-brain-draft.d.ts +70 -0
- package/node_modules/@oxygen/shared/dist/knowledge-brain-draft.js +42 -0
- package/node_modules/@oxygen/shared/dist/linkedin-url.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/linkedin-url.js +10 -0
- package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -1
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +3 -1
- package/node_modules/@oxygen/shared/dist/product-doctrine.js +1 -1
- package/node_modules/@oxygen/shared/dist/sequence-internals/esp-policy.d.ts +225 -0
- package/node_modules/@oxygen/shared/dist/sequence-internals/esp-policy.js +578 -0
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +1 -226
- package/node_modules/@oxygen/shared/dist/sequences.js +2 -576
- package/node_modules/@oxygen/shared/dist/teammate-groups.d.ts +103 -24
- package/node_modules/@oxygen/shared/dist/teammate-groups.js +125 -32
- package/node_modules/@oxygen/shared/dist/teammate-messages.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/teammates.d.ts +28 -2
- package/node_modules/@oxygen/shared/dist/teammates.js +32 -2
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +40 -8
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/workflows/dist/graph/expression.d.ts +1 -16
- package/node_modules/@oxygen/workflows/dist/graph/expression.js +4 -224
- package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +2 -2
- package/node_modules/@oxygen/workflows/dist/index.d.ts +1 -63
- package/node_modules/@oxygen/workflows/dist/index.js +1 -193
- package/node_modules/@oxygen/workflows/dist/refactor-internals/condition-comparison.d.ts +18 -0
- package/node_modules/@oxygen/workflows/dist/refactor-internals/condition-comparison.js +222 -0
- package/node_modules/@oxygen/workflows/dist/refactor-internals/execution-policy.d.ts +64 -0
- package/node_modules/@oxygen/workflows/dist/refactor-internals/execution-policy.js +204 -0
- package/node_modules/@oxygen/workflows/package.json +4 -0
- 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
|
-
//
|
|
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
|
|
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;
|