@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
@@ -0,0 +1,222 @@
1
+ export function compareWorkflowConditionValues(left, op, right) {
2
+ switch (op) {
3
+ case "eq":
4
+ return valuesEqual(left, right);
5
+ case "neq":
6
+ return !valuesEqual(left, right);
7
+ case "gt":
8
+ return orderingOf(left, right) > 0;
9
+ case "gte":
10
+ return orderingOf(left, right) >= 0;
11
+ case "lt":
12
+ return orderingOf(left, right) < 0;
13
+ case "lte":
14
+ return orderingOf(left, right) <= 0;
15
+ case "contains":
16
+ return containsValue(left, right);
17
+ case "not_contains":
18
+ return !containsValue(left, right);
19
+ case "starts_with":
20
+ return textOf(left).startsWith(textOf(right));
21
+ case "ends_with":
22
+ return textOf(left).endsWith(textOf(right));
23
+ case "is_empty":
24
+ return isEmptyValue(left);
25
+ case "is_not_empty":
26
+ return !isEmptyValue(left);
27
+ case "in":
28
+ return containsValue(right, left);
29
+ case "not_in":
30
+ return !containsValue(right, left);
31
+ case "matches":
32
+ return matchesPattern(left, right);
33
+ }
34
+ }
35
+ /**
36
+ * Numeric when both sides read as numbers, otherwise strict identity with
37
+ * null/undefined treated as one blank. Deliberately NOT the formula language's
38
+ * `formulaValuesEqual`, which throws on non-numeric input: a condition gates
39
+ * routing, and a run must not fail because a provider returned an object where
40
+ * the author expected a string.
41
+ */
42
+ function valuesEqual(left, right) {
43
+ if (left === right)
44
+ return true;
45
+ if (left === null || left === undefined)
46
+ return right === null || right === undefined;
47
+ if (right === null || right === undefined)
48
+ return false;
49
+ const leftNumber = numberOf(left);
50
+ const rightNumber = numberOf(right);
51
+ if (leftNumber !== null && rightNumber !== null)
52
+ return leftNumber === rightNumber;
53
+ if (Array.isArray(left) && Array.isArray(right)) {
54
+ return left.length === right.length
55
+ && left.every((entry, index) => valuesEqual(entry, right[index]));
56
+ }
57
+ return false;
58
+ }
59
+ /**
60
+ * Ordering for the relational operators. Incomparable operands sort as 0, so
61
+ * `gt`/`lt` are false and `gte`/`lte` are true — the same shape JavaScript gives
62
+ * for equal values, and never an exception.
63
+ */
64
+ function orderingOf(left, right) {
65
+ const leftNumber = numberOf(left);
66
+ const rightNumber = numberOf(right);
67
+ if (leftNumber !== null && rightNumber !== null) {
68
+ if (leftNumber === rightNumber)
69
+ return 0;
70
+ return leftNumber < rightNumber ? -1 : 1;
71
+ }
72
+ if (typeof left === "string" && typeof right === "string") {
73
+ return left.localeCompare(right);
74
+ }
75
+ return 0;
76
+ }
77
+ function numberOf(value) {
78
+ if (typeof value === "number")
79
+ return Number.isFinite(value) ? value : null;
80
+ if (typeof value === "string" && value.trim()) {
81
+ const parsed = Number(value);
82
+ return Number.isFinite(parsed) ? parsed : null;
83
+ }
84
+ return null;
85
+ }
86
+ /** Membership for arrays, case-sensitive substring for text. */
87
+ function containsValue(haystack, needle) {
88
+ if (Array.isArray(haystack))
89
+ return haystack.some((entry) => valuesEqual(entry, needle));
90
+ if (haystack === null || haystack === undefined)
91
+ return false;
92
+ if (needle === null || needle === undefined)
93
+ return false;
94
+ return textOf(haystack).includes(textOf(needle));
95
+ }
96
+ function isEmptyValue(value) {
97
+ if (value === null || value === undefined)
98
+ return true;
99
+ if (typeof value === "string")
100
+ return value.trim().length === 0;
101
+ if (Array.isArray(value))
102
+ return value.length === 0;
103
+ // 0 and false are values an author chose, not emptiness.
104
+ if (typeof value === "object")
105
+ return Object.keys(value).length === 0;
106
+ return false;
107
+ }
108
+ function textOf(value) {
109
+ if (value === null || value === undefined)
110
+ return "";
111
+ if (typeof value === "string")
112
+ return value;
113
+ if (typeof value === "number" || typeof value === "boolean")
114
+ return String(value);
115
+ try {
116
+ return JSON.stringify(value) ?? "";
117
+ }
118
+ catch {
119
+ return "";
120
+ }
121
+ }
122
+ // ---------------------------------------------------------------------------
123
+ // `matches`
124
+ // ---------------------------------------------------------------------------
125
+ const MAX_CONDITION_PATTERN_LENGTH = 200;
126
+ /**
127
+ * Compile a `matches` pattern, or return null when it is unsafe or invalid.
128
+ *
129
+ * Choice: conservative STATIC REJECTION rather than a linear (Thompson NFA)
130
+ * matcher. A linear engine would be several hundred lines of new, security-load-
131
+ * bearing code in a module the browser also bundles, whereas the patterns real
132
+ * GTM authors write (domain suffixes, job-title keywords, ID formats) never need
133
+ * the constructs rejected here. Rejected: sources over 200 characters,
134
+ * lookaround, backreferences, and a quantified group whose body itself contains
135
+ * an unbounded quantifier — the `(a+)+` family that drives catastrophic
136
+ * backtracking. Everything else compiles as a normal RegExp.
137
+ *
138
+ * The linter calls this at authoring time and reports `invalid_condition`, so a
139
+ * rejected pattern is a lint error the author sees, not a silent run-time false.
140
+ */
141
+ export function compileConditionPattern(pattern) {
142
+ if (!isSafeRegExpSource(pattern))
143
+ return null;
144
+ try {
145
+ return new RegExp(pattern);
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ }
151
+ function isSafeRegExpSource(source) {
152
+ if (!source || source.length > MAX_CONDITION_PATTERN_LENGTH)
153
+ return false;
154
+ if (/\(\?<?[=!]/.test(source))
155
+ return false; // lookahead / lookbehind
156
+ if (/\\[1-9]/.test(source))
157
+ return false; // backreference
158
+ return !hasNestedQuantifier(source);
159
+ }
160
+ function isUnboundedQuantifier(char) {
161
+ // `?` alone is bounded and cannot blow up; `*`, `+` and `{n,m}` can.
162
+ return char === "*" || char === "+" || char === "{";
163
+ }
164
+ function hasNestedQuantifier(source) {
165
+ // One frame per open group, plus a base frame for the top level. A frame is
166
+ // "quantified" once anything inside it repeats.
167
+ const stack = [{ quantified: false }];
168
+ let inCharacterClass = false;
169
+ let index = 0;
170
+ while (index < source.length) {
171
+ const char = source[index] ?? "";
172
+ if (char === "\\") {
173
+ index += 2;
174
+ continue;
175
+ }
176
+ if (inCharacterClass) {
177
+ if (char === "]")
178
+ inCharacterClass = false;
179
+ index += 1;
180
+ continue;
181
+ }
182
+ if (char === "[") {
183
+ inCharacterClass = true;
184
+ index += 1;
185
+ continue;
186
+ }
187
+ if (char === "(") {
188
+ stack.push({ quantified: false });
189
+ index += 1;
190
+ continue;
191
+ }
192
+ if (char === ")") {
193
+ index += 1;
194
+ // Unbalanced ")" — leave it to RegExp construction to reject.
195
+ if (stack.length === 1)
196
+ continue;
197
+ const frame = stack.pop();
198
+ const repeated = isUnboundedQuantifier(source[index] ?? "");
199
+ if (repeated && frame?.quantified === true)
200
+ return true;
201
+ const parent = stack[stack.length - 1];
202
+ if (parent && (repeated || frame?.quantified === true))
203
+ parent.quantified = true;
204
+ continue;
205
+ }
206
+ if (isUnboundedQuantifier(char)) {
207
+ const top = stack[stack.length - 1];
208
+ if (top)
209
+ top.quantified = true;
210
+ }
211
+ index += 1;
212
+ }
213
+ return false;
214
+ }
215
+ function matchesPattern(value, pattern) {
216
+ if (typeof pattern !== "string")
217
+ return false;
218
+ const regex = compileConditionPattern(pattern);
219
+ if (!regex)
220
+ return false;
221
+ return regex.test(textOf(value));
222
+ }
@@ -0,0 +1,64 @@
1
+ export declare const WORKFLOW_MAX_CREDITS_EXCEEDED_ERROR_CODE = "max_credits_exceeded";
2
+ export declare function readWorkflowRunMaxCredits(metadata: Record<string, unknown> | null | undefined): number | null;
3
+ export declare const WORKFLOW_STANDING_AUTHORIZATION_METADATA_KEY = "standing_authorization";
4
+ export declare const WORKFLOW_STANDING_AUTHORIZATION_POLICY_METADATA_KEY = "standing_authorization_policy";
5
+ export declare const WORKFLOW_STANDING_AUTHORIZATION_POLICY_REVISION_BOUND_V1 = "revision_bound_v1";
6
+ export declare const WORKFLOW_STANDING_AUTHORIZATION_REQUIRED_ERROR_CODE = "workflow_standing_authorization_required";
7
+ 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.";
8
+ type WorkflowStandingAuthorizationBase = {
9
+ approved: true;
10
+ revisionId: string;
11
+ authorizedByActorId: string;
12
+ authorizedAt: string;
13
+ };
14
+ export type WorkflowStandingAuthorization = WorkflowStandingAuthorizationBase & ({
15
+ capPolicy: "explicit";
16
+ maxCredits: number;
17
+ } | {
18
+ capPolicy: "plan_default";
19
+ maxCredits: null;
20
+ });
21
+ export declare function isAutonomousWorkflowTriggerType(value: unknown): value is "cron" | "webhook" | "event";
22
+ /**
23
+ * Whether this manifest can cross into a tool at runtime. Hosted workflows are
24
+ * explicit in `steps`; oxygen-workflows-v2 graphs are explicit in `nodes`;
25
+ * durable recipes declare the complete allowlist in `tools_used`. Keeping this
26
+ * predicate structural means legacy revisions can be checked before execution
27
+ * without trusting trigger metadata.
28
+ *
29
+ * The v2 branch is load-bearing, not cosmetic: this predicate is the ONLY input
30
+ * to workflowRequiresStandingAuthorization, so a graph it does not recognise arms
31
+ * a cron / webhook / event trigger with no standing authorization and therefore
32
+ * no per-delivery credit ceiling. A `workflow` (sub-graph) node counts as well —
33
+ * a child run can invoke tools of its own, and the gate must fail closed.
34
+ */
35
+ export declare function workflowManifestCanInvokeTool(manifest: unknown): boolean;
36
+ /** The single apply/enable/runtime gate for standing autonomous authority. */
37
+ export declare function workflowRequiresStandingAuthorization(manifest: unknown, triggerType?: unknown): boolean;
38
+ /**
39
+ * Runtime enforcement marker for triggers created or explicitly re-armed after
40
+ * revision-bound standing authority shipped. Legacy active triggers have no
41
+ * marker and stay runnable during the default-ceiling compatibility rollout;
42
+ * every new/re-armed tool trigger carries this marker and fails closed if its
43
+ * matching authorization is missing or stale.
44
+ */
45
+ export declare function workflowStandingAuthorizationPolicyIsEnforced(metadata: Record<string, unknown> | null | undefined, triggerDefaultCapsMode?: unknown): boolean;
46
+ /**
47
+ * Read the standing authority for one autonomous trigger revision. A null
48
+ * result is fail-closed for policy-marked triggers. Legacy active rows
49
+ * are distinguished by the separate policy marker so they can continue under
50
+ * the fire-time default-ceiling compatibility rollout.
51
+ */
52
+ export declare function readWorkflowStandingAuthorization(metadata: Record<string, unknown> | null | undefined, revisionId: string): WorkflowStandingAuthorization | null;
53
+ export declare function readWorkflowRunPreApprovedLabels(metadata: Record<string, unknown> | null | undefined): string[];
54
+ export declare function readManagedToolRunCredits(output: unknown): number;
55
+ export type WorkflowSpendCapDecision = {
56
+ allowed: boolean;
57
+ projectedCredits: number;
58
+ };
59
+ export declare function evaluateWorkflowRunSpendCap(input: {
60
+ maxCredits: number;
61
+ creditsUsed: number;
62
+ estimatedCredits?: number | null;
63
+ }): WorkflowSpendCapDecision;
64
+ export {};
@@ -0,0 +1,204 @@
1
+ // Pure run authority and spend policy. Hosts supply trusted persisted metadata;
2
+ // the compiler and worker keep their own VM, database and execution ownership.
3
+ // Deliberately local: importing the formula aggregate for this predicate loads
4
+ // its evaluator and function catalog into callers that only need authority.
5
+ // Match the existing loose record contract, including class instances.
6
+ function isRecord(value) {
7
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8
+ }
9
+ function isNonEmptyString(value) {
10
+ return typeof value === "string" && value.trim().length > 0;
11
+ }
12
+ // Live `workflows call` requests carry an approved max_credits spend cap in
13
+ // the run's metadata (R-E.22(c)). These pure helpers are the single source of
14
+ // truth for reading that cap and deciding whether the next paid tool step may
15
+ // run; the worker's step loop and the durable-recipe runtime wrap the refusal
16
+ // in an OxygenError with this code so the stop is visible as run/step state.
17
+ export const WORKFLOW_MAX_CREDITS_EXCEEDED_ERROR_CODE = "max_credits_exceeded";
18
+ // Matches the soft-ceiling epsilon used by table action runs so float drift
19
+ // never refuses a run that is exactly at its cap.
20
+ const WORKFLOW_SPEND_CAP_EPSILON = 0.000001;
21
+ export function readWorkflowRunMaxCredits(metadata) {
22
+ if (!isRecord(metadata))
23
+ return null;
24
+ const raw = metadata.max_credits ?? metadata.maxCredits;
25
+ const parsed = typeof raw === "number"
26
+ ? raw
27
+ : typeof raw === "string" && raw.trim()
28
+ ? Number(raw)
29
+ : Number.NaN;
30
+ if (!Number.isFinite(parsed) || parsed <= 0)
31
+ return null;
32
+ return parsed;
33
+ }
34
+ export const WORKFLOW_STANDING_AUTHORIZATION_METADATA_KEY = "standing_authorization";
35
+ export const WORKFLOW_STANDING_AUTHORIZATION_POLICY_METADATA_KEY = "standing_authorization_policy";
36
+ export const WORKFLOW_STANDING_AUTHORIZATION_POLICY_REVISION_BOUND_V1 = "revision_bound_v1";
37
+ export const WORKFLOW_STANDING_AUTHORIZATION_REQUIRED_ERROR_CODE = "workflow_standing_authorization_required";
38
+ 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.";
39
+ export function isAutonomousWorkflowTriggerType(value) {
40
+ return value === "cron" || value === "webhook" || value === "event";
41
+ }
42
+ /**
43
+ * Whether this manifest can cross into a tool at runtime. Hosted workflows are
44
+ * explicit in `steps`; oxygen-workflows-v2 graphs are explicit in `nodes`;
45
+ * durable recipes declare the complete allowlist in `tools_used`. Keeping this
46
+ * predicate structural means legacy revisions can be checked before execution
47
+ * without trusting trigger metadata.
48
+ *
49
+ * The v2 branch is load-bearing, not cosmetic: this predicate is the ONLY input
50
+ * to workflowRequiresStandingAuthorization, so a graph it does not recognise arms
51
+ * a cron / webhook / event trigger with no standing authorization and therefore
52
+ * no per-delivery credit ceiling. A `workflow` (sub-graph) node counts as well —
53
+ * a child run can invoke tools of its own, and the gate must fail closed.
54
+ */
55
+ export function workflowManifestCanInvokeTool(manifest) {
56
+ if (!isRecord(manifest))
57
+ return false;
58
+ // EVERY shape is checked and the results OR'd — deliberately not a chain of
59
+ // early returns dispatching on which field is present.
60
+ //
61
+ // The chain was a bypass: `tools_used` was tested first and returned from that
62
+ // branch, so a v2 graph manifest carrying an extra `tools_used: []` never
63
+ // reached the `nodes` check and reported "cannot invoke a tool". That is this
64
+ // predicate's ONLY input, so apply then skipped the standing-authorization gate
65
+ // for a cron-triggered graph full of tool nodes, left the trigger unmarked so
66
+ // the fire-time check was fooled too, and resolved a delivery cap of null — no
67
+ // ceiling at all. The graph linter accepts the extra key, so nothing else
68
+ // objected either.
69
+ //
70
+ // A manifest that carries a tool node CAN invoke a tool no matter what other
71
+ // fields it also carries, so the honest form is a union that fails closed.
72
+ const viaRecipe = Array.isArray(manifest.tools_used)
73
+ && manifest.tools_used.some((toolId) => isNonEmptyString(toolId));
74
+ const viaGraph = Array.isArray(manifest.nodes)
75
+ && manifest.nodes.some((node) => isRecord(node) && (node.kind === "tool"
76
+ || (isRecord(node.recipe)
77
+ && Array.isArray(node.recipe.tools_used)
78
+ && node.recipe.tools_used.some((toolId) => isNonEmptyString(toolId)))));
79
+ const viaSteps = Array.isArray(manifest.steps)
80
+ && manifest.steps.some((step) => isRecord(step) && step.kind === "tool");
81
+ return viaRecipe || viaGraph || viaSteps;
82
+ }
83
+ /** The single apply/enable/runtime gate for standing autonomous authority. */
84
+ export function workflowRequiresStandingAuthorization(manifest, triggerType) {
85
+ const manifestTriggerType = isRecord(manifest)
86
+ && isRecord(manifest.trigger)
87
+ ? manifest.trigger.type
88
+ : "api";
89
+ return isAutonomousWorkflowTriggerType(triggerType ?? manifestTriggerType)
90
+ && workflowManifestCanInvokeTool(manifest);
91
+ }
92
+ /**
93
+ * Runtime enforcement marker for triggers created or explicitly re-armed after
94
+ * revision-bound standing authority shipped. Legacy active triggers have no
95
+ * marker and stay runnable during the default-ceiling compatibility rollout;
96
+ * every new/re-armed tool trigger carries this marker and fails closed if its
97
+ * matching authorization is missing or stale.
98
+ */
99
+ export function workflowStandingAuthorizationPolicyIsEnforced(metadata, triggerDefaultCapsMode) {
100
+ return triggerDefaultCapsMode === "enforce"
101
+ || (isRecord(metadata)
102
+ && metadata[WORKFLOW_STANDING_AUTHORIZATION_POLICY_METADATA_KEY]
103
+ === WORKFLOW_STANDING_AUTHORIZATION_POLICY_REVISION_BOUND_V1);
104
+ }
105
+ /**
106
+ * Read the standing authority for one autonomous trigger revision. A null
107
+ * result is fail-closed for policy-marked triggers. Legacy active rows
108
+ * are distinguished by the separate policy marker so they can continue under
109
+ * the fire-time default-ceiling compatibility rollout.
110
+ */
111
+ export function readWorkflowStandingAuthorization(metadata, revisionId) {
112
+ if (!isRecord(metadata))
113
+ return null;
114
+ const raw = metadata[WORKFLOW_STANDING_AUTHORIZATION_METADATA_KEY];
115
+ if (!isRecord(raw) || raw.approved !== true)
116
+ return null;
117
+ const maxCredits = typeof raw.max_credits === "number"
118
+ ? raw.max_credits
119
+ : Number(raw.max_credits);
120
+ const capPolicy = raw.cap_policy === "plan_default"
121
+ ? "plan_default"
122
+ : raw.cap_policy === "explicit" || raw.cap_policy === undefined
123
+ ? "explicit"
124
+ : null;
125
+ const authorizedAt = typeof raw.authorized_at === "string" ? raw.authorized_at.trim() : "";
126
+ const authorizedByActorId = typeof raw.authorized_by_actor_id === "string"
127
+ ? raw.authorized_by_actor_id.trim()
128
+ : "";
129
+ if (!capPolicy
130
+ || (capPolicy === "explicit" && (!Number.isFinite(maxCredits) || maxCredits <= 0))
131
+ || raw.revision_id !== revisionId
132
+ || !authorizedByActorId
133
+ || !authorizedAt
134
+ || Number.isNaN(Date.parse(authorizedAt))) {
135
+ return null;
136
+ }
137
+ const base = {
138
+ approved: true,
139
+ revisionId,
140
+ authorizedByActorId,
141
+ authorizedAt,
142
+ };
143
+ return capPolicy === "explicit"
144
+ ? { ...base, capPolicy, maxCredits }
145
+ : { ...base, capPolicy, maxCredits: null };
146
+ }
147
+ // Approval labels the run's enqueue route already collected SYNCHRONOUS human
148
+ // approval for (a CLI/MCP `approved=true` gate before the run was created), and
149
+ // recorded in run metadata as `preapproved_labels`. A live ctx.approvals.require()
150
+ // call whose `label` is in this set is honored immediately by both recipe
151
+ // executors instead of creating and parking on a durable decision row. Every
152
+ // other label goes through the same host-mediated durable gate under both local
153
+ // and sandbox executors, and the max_credits spend cap is
154
+ // enforced separately per paid tool step, so a pre-approved label never widens
155
+ // spend. Only trusted server code (the enqueue route) sets this — it is read
156
+ // from run metadata, never from user-suppliable workflow input.
157
+ export function readWorkflowRunPreApprovedLabels(metadata) {
158
+ if (!isRecord(metadata))
159
+ return [];
160
+ const raw = metadata.preapproved_labels ?? metadata.preApprovedLabels;
161
+ if (!Array.isArray(raw))
162
+ return [];
163
+ const labels = raw
164
+ .filter((entry) => typeof entry === "string")
165
+ .map((entry) => entry.trim())
166
+ .filter((entry) => entry.length > 0);
167
+ return Array.from(new Set(labels));
168
+ }
169
+ // Completed tool runs attach their captured OXYGEN charge in `meta.billing`.
170
+ // Managed provider credits and own-key platform fees both count toward an
171
+ // approved workflow ceiling; user OAuth calls carry neither charge.
172
+ export function readManagedToolRunCredits(output) {
173
+ if (!isRecord(output))
174
+ return 0;
175
+ const meta = output.meta;
176
+ if (!isRecord(meta))
177
+ return 0;
178
+ const billing = meta.billing;
179
+ if (!isRecord(billing))
180
+ return 0;
181
+ const credits = billing.credential_mode === "managed"
182
+ ? billing.managed_credit_estimate
183
+ : billing.credential_mode === "byok"
184
+ ? billing.byok_platform_fee_credits
185
+ : null;
186
+ if (typeof credits !== "number" || !Number.isFinite(credits) || credits <= 0)
187
+ return 0;
188
+ return credits;
189
+ }
190
+ // `estimatedCredits` is null when the next tool has no managed-credit price
191
+ // (free, BYOK, or unknown catalog entry): such a step is only refused when
192
+ // the cap is already breached, never pre-emptively.
193
+ export function evaluateWorkflowRunSpendCap(input) {
194
+ const estimated = typeof input.estimatedCredits === "number"
195
+ && Number.isFinite(input.estimatedCredits)
196
+ && input.estimatedCredits > 0
197
+ ? input.estimatedCredits
198
+ : 0;
199
+ const projectedCredits = input.creditsUsed + estimated;
200
+ return {
201
+ allowed: projectedCredits <= input.maxCredits + WORKFLOW_SPEND_CAP_EPSILON,
202
+ projectedCredits,
203
+ };
204
+ }
@@ -13,6 +13,10 @@
13
13
  "./graph": {
14
14
  "types": "./dist/graph/index.d.ts",
15
15
  "import": "./dist/graph/index.js"
16
+ },
17
+ "./policy": {
18
+ "types": "./dist/refactor-internals/execution-policy.d.ts",
19
+ "import": "./dist/refactor-internals/execution-policy.js"
16
20
  }
17
21
  },
18
22
  "dependencies": {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.1014.619",
3
+ "version": "1.1014.771",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",