@oxygen-agent/cli 1.1011.855 → 1.1011.1091
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/admin-lead-magnet-commands.d.ts +7 -0
- package/dist/admin-lead-magnet-commands.js +128 -0
- package/dist/command-manifest.js +29 -0
- package/dist/crm-support-flags.d.ts +1 -0
- package/dist/crm-support-flags.js +5 -1
- package/dist/customer-operation-commands.js +30 -1
- package/dist/functions-commands.js +1 -1
- package/dist/help.js +10 -2
- package/dist/http-client.d.ts +4 -0
- package/dist/http-client.js +21 -10
- package/dist/index.js +444 -208
- package/dist/sender-limits-flags.d.ts +7 -0
- package/dist/sender-limits-flags.js +13 -0
- package/dist/workspace-utility-flags.d.ts +7 -0
- package/dist/workspace-utility-flags.js +45 -0
- package/node_modules/@oxygen/shared/dist/attempt-provenance.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/attempt-provenance.js +83 -0
- package/node_modules/@oxygen/shared/dist/billing-anchors.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/billing-anchors.js +7 -1
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +279 -13
- package/node_modules/@oxygen/shared/dist/cli-result.js +6 -0
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +21 -9
- package/node_modules/@oxygen/shared/dist/contracts/crm-attributes.d.ts +7 -1
- package/node_modules/@oxygen/shared/dist/contracts/crm-attributes.js +5 -1
- package/node_modules/@oxygen/shared/dist/contracts/crm-support.d.ts +14 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-support.js +4 -0
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +13 -11
- package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +11 -1
- package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.js +1 -0
- package/node_modules/@oxygen/shared/dist/contracts/schema.js +10 -6
- package/node_modules/@oxygen/shared/dist/contracts/spintax.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/contracts/spintax.js +11 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-row-removal.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-row-removal.js +21 -0
- package/node_modules/@oxygen/shared/dist/contracts/workspace-utilities.d.ts +162 -0
- package/node_modules/@oxygen/shared/dist/contracts/workspace-utilities.js +46 -0
- package/node_modules/@oxygen/shared/dist/credit-topup-review.d.ts +11 -0
- package/node_modules/@oxygen/shared/dist/credit-topup-review.js +3 -0
- package/node_modules/@oxygen/shared/dist/emailguard-placement-probe.d.ts +70 -0
- package/node_modules/@oxygen/shared/dist/emailguard-placement-probe.js +161 -0
- package/node_modules/@oxygen/shared/dist/future-signup-events.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/future-signup-events.js +4 -0
- package/node_modules/@oxygen/shared/dist/knowledge-seed-content.js +2 -2
- package/node_modules/@oxygen/shared/dist/linkedin-quota-denial.d.ts +48 -0
- package/node_modules/@oxygen/shared/dist/linkedin-quota-denial.js +94 -0
- package/node_modules/@oxygen/shared/dist/partner-coupon-bonus.d.ts +45 -0
- package/node_modules/@oxygen/shared/dist/partner-coupon-bonus.js +57 -0
- package/node_modules/@oxygen/shared/dist/provider-balance-signal.js +3 -2
- package/node_modules/@oxygen/shared/dist/table-filter-operators.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/table-filter-operators.js +50 -0
- package/node_modules/@oxygen/shared/dist/telemetry.js +4 -0
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- 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/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/shared/dist/whatsapp-sender-limits.d.ts +42 -0
- package/node_modules/@oxygen/shared/dist/whatsapp-sender-limits.js +68 -0
- package/node_modules/@oxygen/shared/dist/workflow-trigger-metadata.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/workflow-trigger-metadata.js +33 -0
- package/node_modules/@oxygen/shared/package.json +5 -5
- package/node_modules/@oxygen/workflows/dist/index.d.ts +1 -0
- package/node_modules/@oxygen/workflows/dist/index.js +1 -0
- package/node_modules/@oxygen/workflows/dist/recipe-timestamp.d.ts +22 -0
- package/node_modules/@oxygen/workflows/dist/recipe-timestamp.js +72 -0
- package/node_modules/@oxygen/workflows/dist/usage-estimate.js +9 -5
- package/package.json +1 -1
- package/node_modules/@oxygen/shared/dist/vercel-sandbox-fetch.d.ts +0 -9
- package/node_modules/@oxygen/shared/dist/vercel-sandbox-fetch.js +0 -33
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Operators a filter-tree leaf (`tables query --filter-tree-json`, MCP
|
|
2
|
+
// `filterTree`, saved views) accepts. The SQL builder in
|
|
3
|
+
// packages/tenant-db/src/filters.ts, the CLI help and the MCP schema all read
|
|
4
|
+
// this one list, so the operators a caller is told about are exactly the ones
|
|
5
|
+
// the server accepts.
|
|
6
|
+
export const FILTER_TREE_OPERATORS = [
|
|
7
|
+
"is",
|
|
8
|
+
"is_not",
|
|
9
|
+
"contains",
|
|
10
|
+
"not_contains",
|
|
11
|
+
"starts_with",
|
|
12
|
+
"ends_with",
|
|
13
|
+
"eq",
|
|
14
|
+
"neq",
|
|
15
|
+
"gt",
|
|
16
|
+
"gte",
|
|
17
|
+
"lt",
|
|
18
|
+
"lte",
|
|
19
|
+
"before",
|
|
20
|
+
"after",
|
|
21
|
+
"on_or_before",
|
|
22
|
+
"on_or_after",
|
|
23
|
+
"is_true",
|
|
24
|
+
"is_false",
|
|
25
|
+
"is_any_of",
|
|
26
|
+
"is_none_of",
|
|
27
|
+
"is_empty",
|
|
28
|
+
"is_not_empty",
|
|
29
|
+
];
|
|
30
|
+
// is_true/is_false compare without a cast, so only a boolean value accepts them.
|
|
31
|
+
// Every other operator casts the stored value and works on any column.
|
|
32
|
+
const BOOLEAN_ONLY_OPERATORS = new Set(["is_true", "is_false"]);
|
|
33
|
+
/** The filter-tree operators a column whose SQL value has `dataType` accepts. */
|
|
34
|
+
export function filterTreeOperatorsFor(dataType) {
|
|
35
|
+
if (dataType === "boolean")
|
|
36
|
+
return [...FILTER_TREE_OPERATORS];
|
|
37
|
+
return FILTER_TREE_OPERATORS.filter((operator) => !BOOLEAN_ONLY_OPERATORS.has(operator));
|
|
38
|
+
}
|
|
39
|
+
// The flat `--filter-json` dialect spells some operators differently. Callers
|
|
40
|
+
// moving between the two dialects send these names to a filter tree.
|
|
41
|
+
const FLAT_FILTER_OPERATOR_EQUIVALENTS = {
|
|
42
|
+
is_null: "is_empty",
|
|
43
|
+
is_not_null: "is_not_empty",
|
|
44
|
+
in: "is_any_of",
|
|
45
|
+
not_in: "is_none_of",
|
|
46
|
+
};
|
|
47
|
+
/** The filter-tree operator a rejected operator name most likely meant, if any. */
|
|
48
|
+
export function filterTreeOperatorSuggestion(operator) {
|
|
49
|
+
return FLAT_FILTER_OPERATOR_EQUIVALENTS[operator.trim().toLowerCase()] ?? null;
|
|
50
|
+
}
|
|
@@ -239,6 +239,10 @@ const METRIC_SAFE_ATTRIBUTE_KEYS = new Set([
|
|
|
239
239
|
"queue.lane",
|
|
240
240
|
"queue.role",
|
|
241
241
|
"queue.state",
|
|
242
|
+
// oxygen.worker.queue.uncertain: the store path that quarantined a row
|
|
243
|
+
// (EXECUTION_UNCERTAIN_SOURCES in @oxygen/work-queue, or "unknown"). Never the
|
|
244
|
+
// kind's resource or effect key.
|
|
245
|
+
"queue.uncertain_source",
|
|
242
246
|
"scope",
|
|
243
247
|
"rail",
|
|
244
248
|
"overall_status",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const OXYGEN_VERSION: string;
|
|
2
|
-
export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.
|
|
2
|
+
export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.917.5";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const OXYGEN_BUILD_VERSION = "1.1011.
|
|
1
|
+
export declare const OXYGEN_BUILD_VERSION = "1.1011.1091";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// GENERATED by scripts/ci/version-stamp.mjs from VERSION + git history. Do not edit; do not commit.
|
|
2
|
-
export const OXYGEN_BUILD_VERSION = "1.1011.
|
|
2
|
+
export const OXYGEN_BUILD_VERSION = "1.1011.1091";
|
|
@@ -49,4 +49,4 @@ export const OXYGEN_VERSION = OXYGEN_BUILD_VERSION;
|
|
|
49
49
|
// Since 2026-09-28 the release-preparation step advances this literal to the
|
|
50
50
|
// highest CLI version published on npm for at least 30 days. It never lowers
|
|
51
51
|
// the floor. The source change lands on dev before the next production release.
|
|
52
|
-
export const OXYGEN_MINIMUM_CLI_VERSION = "1.
|
|
52
|
+
export const OXYGEN_MINIMUM_CLI_VERSION = "1.917.5";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WhatsApp sender-limit overlay: the keys a WhatsApp account accepts and
|
|
3
|
+
* their defaults and hard maximums. One owner for the quota enforcer
|
|
4
|
+
* (packages/integrations/src/whatsapp-quota.ts), the limits route, the MCP and
|
|
5
|
+
* CLI surfaces and the web form (OXP-11.3.19): the MCP tool once advertised a
|
|
6
|
+
* chats_reads_per_day key the route silently dropped and maxima (400 reads,
|
|
7
|
+
* 7200 s spacing, 3600 s jitter) the quota never allowed.
|
|
8
|
+
*
|
|
9
|
+
* Everything stays deliberately small: WhatsApp Web bans on volume.
|
|
10
|
+
*/
|
|
11
|
+
export declare const WHATSAPP_SENDER_LIMIT_KEYS: readonly ["messages_per_day", "likes_per_day", "messages_reads_per_day", "relations_reads_per_day", "total_actions_per_day", "total_reads_per_day", "min_action_spacing_seconds", "action_spacing_jitter_seconds"];
|
|
12
|
+
export type WhatsAppSenderLimitKey = (typeof WHATSAPP_SENDER_LIMIT_KEYS)[number];
|
|
13
|
+
export declare const WHATSAPP_SENDER_LIMIT_DEFAULTS: {
|
|
14
|
+
readonly messages_per_day: 20;
|
|
15
|
+
readonly likes_per_day: 20;
|
|
16
|
+
readonly messages_reads_per_day: 200;
|
|
17
|
+
readonly relations_reads_per_day: 40;
|
|
18
|
+
readonly total_actions_per_day: 40;
|
|
19
|
+
readonly total_reads_per_day: 240;
|
|
20
|
+
readonly min_action_spacing_seconds: 600;
|
|
21
|
+
readonly action_spacing_jitter_seconds: 300;
|
|
22
|
+
};
|
|
23
|
+
export declare const WHATSAPP_SENDER_LIMIT_MAXIMUMS: {
|
|
24
|
+
readonly messages_per_day: 40;
|
|
25
|
+
readonly likes_per_day: 40;
|
|
26
|
+
readonly messages_reads_per_day: 600;
|
|
27
|
+
readonly relations_reads_per_day: 100;
|
|
28
|
+
readonly total_actions_per_day: 80;
|
|
29
|
+
readonly total_reads_per_day: 700;
|
|
30
|
+
readonly min_action_spacing_seconds: 3600;
|
|
31
|
+
readonly action_spacing_jitter_seconds: 1800;
|
|
32
|
+
};
|
|
33
|
+
/** What each WhatsApp limit counts, for MCP and CLI help. */
|
|
34
|
+
export declare const WHATSAPP_SENDER_LIMIT_NOTES: Record<WhatsAppSenderLimitKey, string>;
|
|
35
|
+
/**
|
|
36
|
+
* The WhatsApp limits an account runs at: each stored override, floored and
|
|
37
|
+
* capped at its WhatsApp maximum, else the WhatsApp default. Read from the
|
|
38
|
+
* overrides alone: a sender's resolved `limits` start from the LinkedIn
|
|
39
|
+
* defaults (300 s spacing, 150 actions, 1000 reads a day), which a WhatsApp
|
|
40
|
+
* number must never inherit (OXP-11.3.21).
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveWhatsAppSenderLimits(overrides: Partial<Record<WhatsAppSenderLimitKey, unknown>> | null | undefined): Record<WhatsAppSenderLimitKey, number>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WhatsApp sender-limit overlay: the keys a WhatsApp account accepts and
|
|
3
|
+
* their defaults and hard maximums. One owner for the quota enforcer
|
|
4
|
+
* (packages/integrations/src/whatsapp-quota.ts), the limits route, the MCP and
|
|
5
|
+
* CLI surfaces and the web form (OXP-11.3.19): the MCP tool once advertised a
|
|
6
|
+
* chats_reads_per_day key the route silently dropped and maxima (400 reads,
|
|
7
|
+
* 7200 s spacing, 3600 s jitter) the quota never allowed.
|
|
8
|
+
*
|
|
9
|
+
* Everything stays deliberately small: WhatsApp Web bans on volume.
|
|
10
|
+
*/
|
|
11
|
+
export const WHATSAPP_SENDER_LIMIT_KEYS = [
|
|
12
|
+
"messages_per_day",
|
|
13
|
+
"likes_per_day",
|
|
14
|
+
"messages_reads_per_day",
|
|
15
|
+
"relations_reads_per_day",
|
|
16
|
+
"total_actions_per_day",
|
|
17
|
+
"total_reads_per_day",
|
|
18
|
+
"min_action_spacing_seconds",
|
|
19
|
+
"action_spacing_jitter_seconds",
|
|
20
|
+
];
|
|
21
|
+
export const WHATSAPP_SENDER_LIMIT_DEFAULTS = {
|
|
22
|
+
messages_per_day: 20,
|
|
23
|
+
likes_per_day: 20,
|
|
24
|
+
messages_reads_per_day: 200,
|
|
25
|
+
relations_reads_per_day: 40,
|
|
26
|
+
total_actions_per_day: 40,
|
|
27
|
+
total_reads_per_day: 240,
|
|
28
|
+
min_action_spacing_seconds: 600,
|
|
29
|
+
action_spacing_jitter_seconds: 300,
|
|
30
|
+
};
|
|
31
|
+
export const WHATSAPP_SENDER_LIMIT_MAXIMUMS = {
|
|
32
|
+
messages_per_day: 40,
|
|
33
|
+
likes_per_day: 40,
|
|
34
|
+
messages_reads_per_day: 600,
|
|
35
|
+
relations_reads_per_day: 100,
|
|
36
|
+
total_actions_per_day: 80,
|
|
37
|
+
total_reads_per_day: 700,
|
|
38
|
+
min_action_spacing_seconds: 3600,
|
|
39
|
+
action_spacing_jitter_seconds: 1800,
|
|
40
|
+
};
|
|
41
|
+
/** What each WhatsApp limit counts, for MCP and CLI help. */
|
|
42
|
+
export const WHATSAPP_SENDER_LIMIT_NOTES = {
|
|
43
|
+
messages_per_day: "Daily WhatsApp messages across direct warm traffic and opted-in Sequence actions; the warm-up ceiling (5, 10, 15, 20 by account age) wins when lower.",
|
|
44
|
+
likes_per_day: "Daily reactions.",
|
|
45
|
+
messages_reads_per_day: "Chat and message-history reads.",
|
|
46
|
+
relations_reads_per_day: "Chat-list reads.",
|
|
47
|
+
total_actions_per_day: "All sends: messages, reactions and edits.",
|
|
48
|
+
total_reads_per_day: "All reads.",
|
|
49
|
+
min_action_spacing_seconds: "Minimum seconds between WhatsApp actions.",
|
|
50
|
+
action_spacing_jitter_seconds: "Random seconds added on top of the spacing floor.",
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The WhatsApp limits an account runs at: each stored override, floored and
|
|
54
|
+
* capped at its WhatsApp maximum, else the WhatsApp default. Read from the
|
|
55
|
+
* overrides alone: a sender's resolved `limits` start from the LinkedIn
|
|
56
|
+
* defaults (300 s spacing, 150 actions, 1000 reads a day), which a WhatsApp
|
|
57
|
+
* number must never inherit (OXP-11.3.21).
|
|
58
|
+
*/
|
|
59
|
+
export function resolveWhatsAppSenderLimits(overrides) {
|
|
60
|
+
const limits = { ...WHATSAPP_SENDER_LIMIT_DEFAULTS };
|
|
61
|
+
for (const key of WHATSAPP_SENDER_LIMIT_KEYS) {
|
|
62
|
+
const value = overrides?.[key];
|
|
63
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
64
|
+
limits[key] = Math.min(Math.floor(value), WHATSAPP_SENDER_LIMIT_MAXIMUMS[key]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return limits;
|
|
68
|
+
}
|
|
@@ -1,2 +1,17 @@
|
|
|
1
1
|
export declare const WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEYS: readonly ["consecutive_failure_count", "consecutive_capacity_failure_count", "last_failure_code", "last_failure_at", "auto_paused_at", "auto_pause_reason"];
|
|
2
2
|
export declare function clearWorkflowTriggerAutoPauseMetadata(metadata: Record<string, unknown>): Record<string, unknown>;
|
|
3
|
+
export type WorkflowAutomationPauseCode = "automation_actions_exceeded" | "insufficient_credits";
|
|
4
|
+
export type TriggerAutomationPause = {
|
|
5
|
+
reason: string;
|
|
6
|
+
reasonCode?: WorkflowAutomationPauseCode | null;
|
|
7
|
+
blockedAt: string | null;
|
|
8
|
+
resumesAt: string | null;
|
|
9
|
+
};
|
|
10
|
+
/** Decode typed refusals and the two controlled messages written before codes were stored. */
|
|
11
|
+
export declare function readWorkflowAutomationPauseCode(code: unknown, reason: unknown): WorkflowAutomationPauseCode | null;
|
|
12
|
+
/** Customer copy never echoes arbitrary persisted error text or invents a credit refill date. */
|
|
13
|
+
export declare function workflowAutomationPauseMessage(pause: {
|
|
14
|
+
reason?: unknown;
|
|
15
|
+
reasonCode?: unknown;
|
|
16
|
+
resumesAt?: unknown;
|
|
17
|
+
}): string;
|
|
@@ -13,3 +13,36 @@ const WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEY_SET = new Set(WORKFLOW_TRIGGER_AU
|
|
|
13
13
|
export function clearWorkflowTriggerAutoPauseMetadata(metadata) {
|
|
14
14
|
return Object.fromEntries(Object.entries(metadata).filter(([key]) => !WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEY_SET.has(key)));
|
|
15
15
|
}
|
|
16
|
+
/** Decode typed refusals and the two controlled messages written before codes were stored. */
|
|
17
|
+
export function readWorkflowAutomationPauseCode(code, reason) {
|
|
18
|
+
// An older scheduler can replace the reason without replacing a newer
|
|
19
|
+
// worker's code. Only a controlled legacy reason may override that code.
|
|
20
|
+
const text = typeof reason === "string" ? reason.trim() : "";
|
|
21
|
+
if (text === "automation_actions_exceeded" || text === "This workspace has reached its monthly automation action limit.") {
|
|
22
|
+
return "automation_actions_exceeded";
|
|
23
|
+
}
|
|
24
|
+
if (text === "insufficient_credits" || text.startsWith("Not enough credits for this automation work (")) {
|
|
25
|
+
return "insufficient_credits";
|
|
26
|
+
}
|
|
27
|
+
if (code === "automation_actions_exceeded" || code === "insufficient_credits")
|
|
28
|
+
return code;
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
/** Customer copy never echoes arbitrary persisted error text or invents a credit refill date. */
|
|
32
|
+
export function workflowAutomationPauseMessage(pause) {
|
|
33
|
+
const code = readWorkflowAutomationPauseCode(pause.reasonCode, pause.reason);
|
|
34
|
+
if (code === "insufficient_credits") {
|
|
35
|
+
return "Not enough credits for the next scheduled run. Add credits before the next scheduled attempt.";
|
|
36
|
+
}
|
|
37
|
+
if (code === "automation_actions_exceeded") {
|
|
38
|
+
const resume = typeof pause.resumesAt === "string" ? new Date(pause.resumesAt) : null;
|
|
39
|
+
if (resume && !Number.isNaN(resume.getTime())) {
|
|
40
|
+
const day = new Intl.DateTimeFormat("en-US", {
|
|
41
|
+
month: "short", day: "numeric", year: "numeric", timeZone: "UTC",
|
|
42
|
+
}).format(resume);
|
|
43
|
+
return `The monthly automation-action allowance is exhausted. Cron runs resume on ${day} when the window resets.`;
|
|
44
|
+
}
|
|
45
|
+
return "The monthly automation-action allowance is exhausted. Check billing before the next scheduled attempt.";
|
|
46
|
+
}
|
|
47
|
+
return "Automation admission was refused. Check billing before the next scheduled attempt.";
|
|
48
|
+
}
|
|
@@ -11,11 +11,6 @@
|
|
|
11
11
|
"import": "./dist/knowledge-vault-markdown.js",
|
|
12
12
|
"default": "./dist/knowledge-vault-markdown.js"
|
|
13
13
|
},
|
|
14
|
-
"./vercel-sandbox-fetch": {
|
|
15
|
-
"types": "./dist/vercel-sandbox-fetch.d.ts",
|
|
16
|
-
"import": "./dist/vercel-sandbox-fetch.js",
|
|
17
|
-
"default": "./dist/vercel-sandbox-fetch.js"
|
|
18
|
-
},
|
|
19
14
|
".": {
|
|
20
15
|
"types": "./dist/index.d.ts",
|
|
21
16
|
"import": "./dist/index.js",
|
|
@@ -291,6 +286,11 @@
|
|
|
291
286
|
"types": "./dist/provider-balance-signal.d.ts",
|
|
292
287
|
"import": "./dist/provider-balance-signal.js"
|
|
293
288
|
},
|
|
289
|
+
"./attempt-provenance": {
|
|
290
|
+
"types": "./dist/attempt-provenance.d.ts",
|
|
291
|
+
"import": "./dist/attempt-provenance.js",
|
|
292
|
+
"default": "./dist/attempt-provenance.js"
|
|
293
|
+
},
|
|
294
294
|
"./copilot-skills.generated": null
|
|
295
295
|
},
|
|
296
296
|
"dependencies": {}
|
|
@@ -6,6 +6,7 @@ export type { CanonicalWorkflowToolEffect } from "./tool-effects.js";
|
|
|
6
6
|
export * from "./graph/index.js";
|
|
7
7
|
export * from "./graph/lint.js";
|
|
8
8
|
export * from "./portable.js";
|
|
9
|
+
export { RECIPE_TIMESTAMP_MILLIS_SOURCE } from "./recipe-timestamp.js";
|
|
9
10
|
export declare const WORKFLOW_MANIFEST_VERSION = 1;
|
|
10
11
|
export declare const WORKFLOW_COMPILER_VERSION = "oxygen-workflows-v1";
|
|
11
12
|
export declare const DURABLE_RECIPE_COMPILER_VERSION = "oxygen-recipes-v2";
|
|
@@ -13,6 +13,7 @@ export * from "./event-dispatch.js";
|
|
|
13
13
|
export * from "./graph/index.js";
|
|
14
14
|
export * from "./graph/lint.js";
|
|
15
15
|
export * from "./portable.js";
|
|
16
|
+
export { RECIPE_TIMESTAMP_MILLIS_SOURCE } from "./recipe-timestamp.js";
|
|
16
17
|
export const WORKFLOW_MANIFEST_VERSION = 1;
|
|
17
18
|
export const WORKFLOW_COMPILER_VERSION = "oxygen-workflows-v1";
|
|
18
19
|
export const DURABLE_RECIPE_COMPILER_VERSION = "oxygen-recipes-v2";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recipe-source helpers that emitted template bundles embed verbatim.
|
|
3
|
+
*
|
|
4
|
+
* The durable recipe runtime removes `Date` from the sandbox's globals
|
|
5
|
+
* (`installRecipeGlobalGuards` in apps/worker/src/recipe-sandbox-runtime.ts)
|
|
6
|
+
* so a replayed run cannot read a different clock. A bundle that calls
|
|
7
|
+
* `Date.parse` therefore throws "Cannot read properties of undefined (reading
|
|
8
|
+
* 'parse')" on every live run: both HubSpot sequencer projection templates
|
|
9
|
+
* failed that way from the day they were armed. Timestamps inside a bundle are
|
|
10
|
+
* parsed here by arithmetic instead, and `ctx.now()` stays the only clock.
|
|
11
|
+
*
|
|
12
|
+
* `readTimestampMillis` accepts positive epoch milliseconds (number or digit
|
|
13
|
+
* string) and ISO-8601 dates / date-times. A date-time without a zone is read
|
|
14
|
+
* as UTC, which is what `Date.parse` gives in the UTC sandbox. Anything else
|
|
15
|
+
* returns null.
|
|
16
|
+
*
|
|
17
|
+
* It lives here rather than in apps/web because the worker's sandbox runner
|
|
18
|
+
* embeds the same source: a bundle compiled before this parser existed is
|
|
19
|
+
* frozen in its installed revision and still calls `Date.parse`, so the runner
|
|
20
|
+
* answers that one call with this parser instead of the host clock.
|
|
21
|
+
*/
|
|
22
|
+
export declare const RECIPE_TIMESTAMP_MILLIS_SOURCE: string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recipe-source helpers that emitted template bundles embed verbatim.
|
|
3
|
+
*
|
|
4
|
+
* The durable recipe runtime removes `Date` from the sandbox's globals
|
|
5
|
+
* (`installRecipeGlobalGuards` in apps/worker/src/recipe-sandbox-runtime.ts)
|
|
6
|
+
* so a replayed run cannot read a different clock. A bundle that calls
|
|
7
|
+
* `Date.parse` therefore throws "Cannot read properties of undefined (reading
|
|
8
|
+
* 'parse')" on every live run: both HubSpot sequencer projection templates
|
|
9
|
+
* failed that way from the day they were armed. Timestamps inside a bundle are
|
|
10
|
+
* parsed here by arithmetic instead, and `ctx.now()` stays the only clock.
|
|
11
|
+
*
|
|
12
|
+
* `readTimestampMillis` accepts positive epoch milliseconds (number or digit
|
|
13
|
+
* string) and ISO-8601 dates / date-times. A date-time without a zone is read
|
|
14
|
+
* as UTC, which is what `Date.parse` gives in the UTC sandbox. Anything else
|
|
15
|
+
* returns null.
|
|
16
|
+
*
|
|
17
|
+
* It lives here rather than in apps/web because the worker's sandbox runner
|
|
18
|
+
* embeds the same source: a bundle compiled before this parser existed is
|
|
19
|
+
* frozen in its installed revision and still calls `Date.parse`, so the runner
|
|
20
|
+
* answers that one call with this parser instead of the host clock.
|
|
21
|
+
*/
|
|
22
|
+
export const RECIPE_TIMESTAMP_MILLIS_SOURCE = String.raw `
|
|
23
|
+
const RECIPE_ISO_TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})(?:[Tt ](\d{2}):(\d{2})(?::(\d{2})(?:[.,](\d+))?)?(?:\s*([Zz]|[+-]\d{2}(?::?\d{2})?))?)?$/;
|
|
24
|
+
|
|
25
|
+
function recipeDaysInMonth(year, month) {
|
|
26
|
+
if (month === 2) return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0 ? 29 : 28;
|
|
27
|
+
return month === 4 || month === 6 || month === 9 || month === 11 ? 30 : 31;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseIsoTimestampMillis(text) {
|
|
31
|
+
const match = RECIPE_ISO_TIMESTAMP.exec(text);
|
|
32
|
+
if (!match) return null;
|
|
33
|
+
const year = Number(match[1]);
|
|
34
|
+
const month = Number(match[2]);
|
|
35
|
+
const day = Number(match[3]);
|
|
36
|
+
const hour = match[4] === undefined ? 0 : Number(match[4]);
|
|
37
|
+
const minute = match[5] === undefined ? 0 : Number(match[5]);
|
|
38
|
+
const second = match[6] === undefined ? 0 : Number(match[6]);
|
|
39
|
+
const millis = match[7] === undefined ? 0 : Number((match[7] + "00").slice(0, 3));
|
|
40
|
+
if (month < 1 || month > 12 || day < 1 || day > recipeDaysInMonth(year, month)) return null;
|
|
41
|
+
if (hour > 23 || minute > 59 || second > 59) return null;
|
|
42
|
+
let offsetMinutes = 0;
|
|
43
|
+
const zone = match[8];
|
|
44
|
+
if (zone && zone !== "Z" && zone !== "z") {
|
|
45
|
+
const digits = zone.slice(1).replace(":", "");
|
|
46
|
+
const zoneHours = Number(digits.slice(0, 2));
|
|
47
|
+
const zoneMinutes = digits.length > 2 ? Number(digits.slice(2)) : 0;
|
|
48
|
+
if (zoneHours > 23 || zoneMinutes > 59) return null;
|
|
49
|
+
offsetMinutes = (zone[0] === "-" ? -1 : 1) * (zoneHours * 60 + zoneMinutes);
|
|
50
|
+
}
|
|
51
|
+
// Days since 1970-01-01 in the proleptic Gregorian calendar
|
|
52
|
+
// (Howard Hinnant's days_from_civil).
|
|
53
|
+
const shiftedYear = month <= 2 ? year - 1 : year;
|
|
54
|
+
const era = Math.floor(shiftedYear / 400);
|
|
55
|
+
const yearOfEra = shiftedYear - era * 400;
|
|
56
|
+
const dayOfYear = Math.floor((153 * (month + (month > 2 ? -3 : 9)) + 2) / 5) + day - 1;
|
|
57
|
+
const dayOfEra = yearOfEra * 365 + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100) + dayOfYear;
|
|
58
|
+
const days = era * 146097 + dayOfEra - 719468;
|
|
59
|
+
return ((days * 24 + hour) * 60 + minute - offsetMinutes) * 60000 + second * 1000 + millis;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function readTimestampMillis(value) {
|
|
63
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.trunc(value);
|
|
64
|
+
const candidate = typeof value === "string" ? value.trim() : "";
|
|
65
|
+
if (!candidate) return null;
|
|
66
|
+
if (/^\d+$/.test(candidate)) {
|
|
67
|
+
const numeric = Number(candidate);
|
|
68
|
+
return Number.isFinite(numeric) && numeric > 0 ? Math.trunc(numeric) : null;
|
|
69
|
+
}
|
|
70
|
+
return parseIsoTimestampMillis(candidate);
|
|
71
|
+
}
|
|
72
|
+
`.trim();
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Static automation usage estimation shared by the web workflow page and the
|
|
2
|
-
// worker scheduler's admission gate. The unit is the workflow STEP: every
|
|
3
|
-
// live
|
|
4
|
-
// inside a run are free (decision 6.2),
|
|
2
|
+
// worker scheduler's admission gate. The unit is the workflow STEP: every
|
|
3
|
+
// billable node a live graph executes bills one (repricing 2026-09, decision
|
|
4
|
+
// 6.1). Control nodes and rows written inside a run are free (decision 6.2),
|
|
5
|
+
// so a row-writing step costs one step
|
|
5
6
|
// however many rows it writes. The per-run count derived from a manifest is a
|
|
6
7
|
// FLOOR, never a projection: recipe code emits one step per checkpoint (loops
|
|
7
8
|
// multiply) and a retried attempt bills again. Surfaces present these numbers as
|
|
@@ -167,7 +168,8 @@ function estimateWorkflowGraphActionsFloor(graph) {
|
|
|
167
168
|
}
|
|
168
169
|
// Nested caps multiply, and two large ones overflow Number's exact range;
|
|
169
170
|
// clamping keeps every downstream product (runs x floor) a finite number.
|
|
170
|
-
|
|
171
|
+
// Free control-only graphs have no step charge, including in previews.
|
|
172
|
+
return Math.min(floor, Number.MAX_SAFE_INTEGER);
|
|
171
173
|
}
|
|
172
174
|
export function estimateWorkflowAutomationUsage(manifest, cron) {
|
|
173
175
|
if (!isRecord(manifest))
|
|
@@ -320,7 +322,9 @@ export function assessCronCadenceViability(input) {
|
|
|
320
322
|
return null;
|
|
321
323
|
const actionsPerRunFloor = estimateWorkflowRunAutomationActionsFloor(input.manifest);
|
|
322
324
|
const floorActionsPer30Days = scheduledRunsPer30Days * actionsPerRunFloor;
|
|
323
|
-
const runsAllowedPer30Days =
|
|
325
|
+
const runsAllowedPer30Days = actionsPerRunFloor === 0
|
|
326
|
+
? MINUTES_PER_30_DAYS
|
|
327
|
+
: Math.floor(includedActions / actionsPerRunFloor);
|
|
324
328
|
return {
|
|
325
329
|
scheduledRunsPer30Days,
|
|
326
330
|
actionsPerRunFloor,
|
package/package.json
CHANGED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/** The Sandbox SDK retries transport failures and 429/5xx responses even for
|
|
2
|
-
* POSTs. Visual execution cannot replay an ambiguously accepted mutation.
|
|
3
|
-
* The SDK recognizes AbortError as non-retryable; outcomeUnknown deliberately
|
|
4
|
-
* distinguishes this transport fence from ordinary caller cancellation. */
|
|
5
|
-
export declare class SandboxMutationOutcomeUnknownError extends Error {
|
|
6
|
-
readonly outcomeUnknown = true;
|
|
7
|
-
constructor(options?: ErrorOptions);
|
|
8
|
-
}
|
|
9
|
-
export declare function createSandboxNonReplayFetch(rawFetch?: typeof globalThis.fetch): typeof globalThis.fetch;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/** The Sandbox SDK retries transport failures and 429/5xx responses even for
|
|
2
|
-
* POSTs. Visual execution cannot replay an ambiguously accepted mutation.
|
|
3
|
-
* The SDK recognizes AbortError as non-retryable; outcomeUnknown deliberately
|
|
4
|
-
* distinguishes this transport fence from ordinary caller cancellation. */
|
|
5
|
-
export class SandboxMutationOutcomeUnknownError extends Error {
|
|
6
|
-
outcomeUnknown = true;
|
|
7
|
-
constructor(options) {
|
|
8
|
-
super("Sandbox mutation acceptance requires reconciliation.", options);
|
|
9
|
-
this.name = "AbortError";
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
export function createSandboxNonReplayFetch(rawFetch = globalThis.fetch) {
|
|
13
|
-
return async (input, init) => {
|
|
14
|
-
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
15
|
-
if (["GET", "HEAD", "OPTIONS"].includes(method))
|
|
16
|
-
return rawFetch(input, init);
|
|
17
|
-
try {
|
|
18
|
-
// Redirects must not resubmit a mutation either. No request URL, body or
|
|
19
|
-
// authentication header is copied into the error or telemetry.
|
|
20
|
-
const response = await rawFetch(input, { ...init, redirect: "error" });
|
|
21
|
-
if (response.status === 429 || response.status >= 500) {
|
|
22
|
-
void response.body?.cancel().catch(() => { });
|
|
23
|
-
throw new SandboxMutationOutcomeUnknownError();
|
|
24
|
-
}
|
|
25
|
-
return response;
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
if (error instanceof SandboxMutationOutcomeUnknownError)
|
|
29
|
-
throw error;
|
|
30
|
-
throw new SandboxMutationOutcomeUnknownError({ cause: error });
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}
|