@oxygen-agent/cli 1.1010.721 → 1.1010.905

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 (100) hide show
  1. package/README.md +1 -1
  2. package/dist/auto-update.d.ts +129 -0
  3. package/dist/auto-update.js +392 -0
  4. package/dist/command-manifest.js +14 -0
  5. package/dist/credentials.d.ts +2 -0
  6. package/dist/credentials.js +6 -3
  7. package/dist/functions-commands.js +1 -1
  8. package/dist/http-client.js +28 -4
  9. package/dist/index.js +583 -145
  10. package/dist/run-wait.d.ts +3 -1
  11. package/dist/run-wait.js +19 -5
  12. package/dist/streamed-file-import.d.ts +58 -0
  13. package/dist/streamed-file-import.js +115 -0
  14. package/dist/update.d.ts +29 -0
  15. package/dist/update.js +62 -16
  16. package/dist/workflow-plan-limit-notices.d.ts +8 -0
  17. package/dist/workflow-plan-limit-notices.js +28 -0
  18. package/node_modules/@oxygen/cli-ugc/dist/commands.js +3 -3
  19. package/node_modules/@oxygen/shared/dist/billing-anchors.d.ts +17 -0
  20. package/node_modules/@oxygen/shared/dist/billing-anchors.js +27 -0
  21. package/node_modules/@oxygen/shared/dist/billing.d.ts +191 -35
  22. package/node_modules/@oxygen/shared/dist/billing.js +333 -42
  23. package/node_modules/@oxygen/shared/dist/capability-discovery.js +55 -5
  24. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +2 -2
  25. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +2 -2
  26. package/node_modules/@oxygen/shared/dist/cost-estimate-view.d.ts +50 -0
  27. package/node_modules/@oxygen/shared/dist/cost-estimate-view.js +90 -0
  28. package/node_modules/@oxygen/shared/dist/cost-estimate.d.ts +167 -0
  29. package/node_modules/@oxygen/shared/dist/cost-estimate.js +361 -0
  30. package/node_modules/@oxygen/shared/dist/credit-gate.d.ts +26 -0
  31. package/node_modules/@oxygen/shared/dist/credit-gate.js +65 -0
  32. package/node_modules/@oxygen/shared/dist/email-deliverability-policy.d.ts +51 -0
  33. package/node_modules/@oxygen/shared/dist/email-deliverability-policy.js +101 -0
  34. package/node_modules/@oxygen/shared/dist/email-hard-bounce.d.ts +3 -1
  35. package/node_modules/@oxygen/shared/dist/email-hard-bounce.js +3 -3
  36. package/node_modules/@oxygen/shared/dist/error-redaction.d.ts +1 -1
  37. package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -1
  38. package/node_modules/@oxygen/shared/dist/feature-gates.d.ts +4 -0
  39. package/node_modules/@oxygen/shared/dist/feature-gates.js +5 -0
  40. package/node_modules/@oxygen/shared/dist/file-import.d.ts +13 -1
  41. package/node_modules/@oxygen/shared/dist/file-import.js +33 -6
  42. package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +73 -3
  43. package/node_modules/@oxygen/shared/dist/hosted-ai.js +246 -24
  44. package/node_modules/@oxygen/shared/dist/import-limits.d.ts +25 -1
  45. package/node_modules/@oxygen/shared/dist/import-limits.js +35 -2
  46. package/node_modules/@oxygen/shared/dist/index.d.ts +2 -22
  47. package/node_modules/@oxygen/shared/dist/index.js +2 -42
  48. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +9 -0
  49. package/node_modules/@oxygen/shared/dist/object-storage.js +17 -0
  50. package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +41 -0
  51. package/node_modules/@oxygen/shared/dist/operational-telemetry.js +55 -0
  52. package/node_modules/@oxygen/shared/dist/plan-band.d.ts +117 -1
  53. package/node_modules/@oxygen/shared/dist/plan-band.js +175 -10
  54. package/node_modules/@oxygen/shared/dist/plan-capabilities.d.ts +77 -7
  55. package/node_modules/@oxygen/shared/dist/plan-capabilities.js +87 -7
  56. package/node_modules/@oxygen/shared/dist/plan-limits-view.d.ts +219 -0
  57. package/node_modules/@oxygen/shared/dist/plan-limits-view.js +330 -0
  58. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +204 -6
  59. package/node_modules/@oxygen/shared/dist/plan-limits.js +197 -15
  60. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +80 -36
  61. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +80 -31
  62. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +38 -20
  63. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +47 -34
  64. package/node_modules/@oxygen/shared/dist/provider-http-error.d.ts +10 -0
  65. package/node_modules/@oxygen/shared/dist/provider-http-error.js +27 -0
  66. package/node_modules/@oxygen/shared/dist/repricing.d.ts +127 -0
  67. package/node_modules/@oxygen/shared/dist/repricing.js +407 -6
  68. package/node_modules/@oxygen/shared/dist/semver.d.ts +21 -0
  69. package/node_modules/@oxygen/shared/dist/semver.js +41 -0
  70. package/node_modules/@oxygen/shared/dist/sending-limits.d.ts +5 -7
  71. package/node_modules/@oxygen/shared/dist/sending-limits.js +10 -16
  72. package/node_modules/@oxygen/shared/dist/sending-seats.d.ts +18 -15
  73. package/node_modules/@oxygen/shared/dist/sending-seats.js +22 -17
  74. package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +57 -8
  75. package/node_modules/@oxygen/shared/dist/spend-safety.js +64 -11
  76. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +15 -7
  77. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +18 -5
  78. package/node_modules/@oxygen/shared/dist/table-capacity.d.ts +34 -5
  79. package/node_modules/@oxygen/shared/dist/table-capacity.js +25 -8
  80. package/node_modules/@oxygen/shared/dist/telemetry-export-observer.d.ts +6 -0
  81. package/node_modules/@oxygen/shared/dist/telemetry-export-observer.js +13 -5
  82. package/node_modules/@oxygen/shared/dist/telemetry-resource.d.ts +40 -0
  83. package/node_modules/@oxygen/shared/dist/telemetry-resource.js +35 -0
  84. package/node_modules/@oxygen/shared/dist/telemetry.js +5 -0
  85. package/node_modules/@oxygen/shared/dist/ugc.d.ts +15 -0
  86. package/node_modules/@oxygen/shared/dist/ugc.js +29 -0
  87. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -3
  88. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  89. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  90. package/node_modules/@oxygen/shared/dist/version.js +14 -27
  91. package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +5 -0
  92. package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +5 -0
  93. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +3 -3
  94. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +15 -1
  95. package/node_modules/@oxygen/workflows/dist/graph/types.js +15 -1
  96. package/node_modules/@oxygen/workflows/dist/index.d.ts +45 -0
  97. package/node_modules/@oxygen/workflows/dist/index.js +152 -2
  98. package/node_modules/@oxygen/workflows/dist/usage-estimate.d.ts +10 -1
  99. package/node_modules/@oxygen/workflows/dist/usage-estimate.js +33 -29
  100. package/package.json +1 -1
@@ -46,6 +46,12 @@ export type TelemetryExportObserverOptions = {
46
46
  windowMs?: number | undefined;
47
47
  now?: (() => number) | undefined;
48
48
  };
49
+ /**
50
+ * Copies the optional reader-facing exporter methods (FORWARDED_METHODS) from
51
+ * `inner` onto `wrapper`, bound to `inner`. Every exporter wrapper in this
52
+ * package uses it, so a method the SDK starts probing is added in one place.
53
+ */
54
+ export declare function forwardExporterMethods(inner: object, wrapper: Record<string, unknown>): void;
49
55
  /** Origin + path only: never query strings, fragments or URL credentials. */
50
56
  export declare function telemetryEndpointForLog(endpoint: string): string;
51
57
  /**
@@ -12,6 +12,18 @@ const FORWARDED_METHODS = [
12
12
  "selectAggregationTemporality",
13
13
  "selectAggregation",
14
14
  ];
15
+ /**
16
+ * Copies the optional reader-facing exporter methods (FORWARDED_METHODS) from
17
+ * `inner` onto `wrapper`, bound to `inner`. Every exporter wrapper in this
18
+ * package uses it, so a method the SDK starts probing is added in one place.
19
+ */
20
+ export function forwardExporterMethods(inner, wrapper) {
21
+ for (const method of FORWARDED_METHODS) {
22
+ const fn = inner[method];
23
+ if (typeof fn === "function")
24
+ wrapper[method] = fn.bind(inner);
25
+ }
26
+ }
15
27
  /** Origin + path only: never query strings, fragments or URL credentials. */
16
28
  export function telemetryEndpointForLog(endpoint) {
17
29
  try {
@@ -187,11 +199,7 @@ export function observeTelemetryExporter(inner, options) {
187
199
  return inner.shutdown();
188
200
  },
189
201
  };
190
- for (const method of FORWARDED_METHODS) {
191
- const fn = inner[method];
192
- if (typeof fn === "function")
193
- observed[method] = fn.bind(inner);
194
- }
202
+ forwardExporterMethods(inner, observed);
195
203
  return observed;
196
204
  }
197
205
  const DROPPED_SPANS_MESSAGE = /^Dropped (\d+) spans because maxQueueSize reached$/;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Which OTel resource each telemetry destination of this process receives.
3
+ *
4
+ * A process has ONE TracerProvider / MeterProvider and therefore one resource,
5
+ * but up to two destinations: Axiom (the managed-era store, whose dashboards and
6
+ * monitors filter on `deployment.environment == 'production'`) and the opt-in
7
+ * OTLP collector (DEV1 SigNoz, whose production receiver keeps only
8
+ * `deployment.environment == 'prod'` rows and whose dev receiver only `dev`).
9
+ *
10
+ * Production is the one tier where the two labels disagree, so production is the
11
+ * one tier that splits: the SDK resource stays exactly what a production process
12
+ * without a collector exports (the Axiom label, resource auto-detection on), and
13
+ * the collector's exporters swap in `collectorAttributes` — declared attributes
14
+ * only, never detected ones, because Node's process detector copies argv
15
+ * verbatim. Every other tier (dev, shadow, managed) keeps its single shared
16
+ * resource exactly as before.
17
+ */
18
+ export type TelemetryResourcePlan = {
19
+ /** The SDK resource's `deployment.environment`: what Axiom receives. */
20
+ deploymentEnvironment: string;
21
+ /** The SDK resource attributes. */
22
+ resourceAttributes: Record<string, string>;
23
+ /** False when the collector shares the SDK resource, so nothing is detected into it. */
24
+ autoDetectResources: boolean;
25
+ /**
26
+ * Set only when the collector must see a different resource than Axiom: the
27
+ * declared attributes plus the process identity, labelled with the tier.
28
+ */
29
+ collectorAttributes: Record<string, string> | null;
30
+ };
31
+ export type TelemetryResourcePlanInput = {
32
+ /** Every resource attribute except `deployment.environment`. */
33
+ declared: Record<string, string>;
34
+ /** The surface's managed fallback chain, used when OXYGEN_DEPLOY_ENV is unset. */
35
+ managedEnvironment: string;
36
+ /** True when OXYGEN_OTLP_ENDPOINT is configured for this process. */
37
+ collectorConfigured: boolean;
38
+ env?: NodeJS.ProcessEnv;
39
+ };
40
+ export declare function planTelemetryResource({ declared, managedEnvironment, collectorConfigured, env, }: TelemetryResourcePlanInput): TelemetryResourcePlan;
@@ -0,0 +1,35 @@
1
+ import { deployEnvTelemetryEnvironment, resolveDeployEnv } from "./deploy-env.js";
2
+ import { processResourceIdentity } from "./process-resource.js";
3
+ export function planTelemetryResource({ declared, managedEnvironment, collectorConfigured, env = process.env, }) {
4
+ // The label a process without a collector has always exported: `production`
5
+ // on selfhost-prod, `development` on shadow/dev, the managed chain otherwise.
6
+ const axiomEnvironment = deployEnvTelemetryEnvironment(env) ?? managedEnvironment;
7
+ const withEnvironment = (environment, attributes) => ({
8
+ ...attributes,
9
+ "deployment.environment": environment,
10
+ });
11
+ if (!collectorConfigured) {
12
+ return {
13
+ deploymentEnvironment: axiomEnvironment,
14
+ resourceAttributes: withEnvironment(axiomEnvironment, declared),
15
+ autoDetectResources: true,
16
+ collectorAttributes: null,
17
+ };
18
+ }
19
+ const collectorEnvironment = env.OXYGEN_DEPLOY_ENV?.trim() ? resolveDeployEnv(env).tier : axiomEnvironment;
20
+ const collectorAttributes = withEnvironment(collectorEnvironment, { ...processResourceIdentity(), ...declared });
21
+ if (collectorEnvironment === "prod") {
22
+ return {
23
+ deploymentEnvironment: axiomEnvironment,
24
+ resourceAttributes: withEnvironment(axiomEnvironment, declared),
25
+ autoDetectResources: true,
26
+ collectorAttributes,
27
+ };
28
+ }
29
+ return {
30
+ deploymentEnvironment: collectorEnvironment,
31
+ resourceAttributes: collectorAttributes,
32
+ autoDetectResources: false,
33
+ collectorAttributes: null,
34
+ };
35
+ }
@@ -226,6 +226,11 @@ const METRIC_SAFE_ATTRIBUTE_KEYS = new Set([
226
226
  "worker.cycle.slow",
227
227
  "table_action.lane",
228
228
  "tenant.ticket_reason",
229
+ // oxygen.worker.tenant_queue_wait_ms: the ticket's narrow scope
230
+ // (copilot | publishing | none) and the tenant's plan band (the seven bands
231
+ // plus "unresolved"), which the worker-share target is read by. Closed enums.
232
+ "tenant.wake_kind",
233
+ "tenant.plan_band",
229
234
  "queue.stalled",
230
235
  // BullMQ execution queue dimensions (oxygen.worker.queue.*). `queue.lane` is
231
236
  // the closed EXECUTION_LANES enum, `queue.role` the lane or "relay",
@@ -150,3 +150,18 @@ export type UgcVoiceImport = {
150
150
  };
151
151
  export declare const UGC_MONTHLY_CREATOR_CREDITS = 10000;
152
152
  export declare const UGC_SPONSORED_LINKEDIN_SEATS = 1;
153
+ /**
154
+ * One label for a sponsor's row (`view=sponsorship`) on every surface, the web
155
+ * Creators tab and the MCP widget alike: a card sponsorship waiting on its
156
+ * checkout reads differently from one waiting on the provider, and a stopped
157
+ * renewal says so while its paid period runs.
158
+ */
159
+ export declare function ugcSponsorshipStatusLabel(row: {
160
+ status: string;
161
+ checkout_url?: string | null;
162
+ cancellation_pending?: boolean;
163
+ }): string;
164
+ /** What confirming a credit-sponsorship quote does for one creator (`participants[].state`). */
165
+ export declare const UGC_SPONSORSHIP_PLAN_LABELS: Readonly<Record<string, string>>;
166
+ /** When a new credit sponsorship's reservation starts (`participants[].reservation_starts`). */
167
+ export declare const UGC_RESERVATION_START_LABELS: Readonly<Record<string, string>>;
@@ -10,3 +10,32 @@ export function ugcPostingTarget(program) {
10
10
  }
11
11
  export const UGC_MONTHLY_CREATOR_CREDITS = 10_000;
12
12
  export const UGC_SPONSORED_LINKEDIN_SEATS = 1;
13
+ /**
14
+ * One label for a sponsor's row (`view=sponsorship`) on every surface, the web
15
+ * Creators tab and the MCP widget alike: a card sponsorship waiting on its
16
+ * checkout reads differently from one waiting on the provider, and a stopped
17
+ * renewal says so while its paid period runs.
18
+ */
19
+ export function ugcSponsorshipStatusLabel(row) {
20
+ if (row.cancellation_pending)
21
+ return "Renewal stopping";
22
+ if (row.status === "active")
23
+ return "Sponsored";
24
+ if (row.status === "pending")
25
+ return row.checkout_url ? "Checkout open" : "Pending";
26
+ if (row.status === "cancelled")
27
+ return "Renewal stopped";
28
+ return "Not sponsored";
29
+ }
30
+ /** What confirming a credit-sponsorship quote does for one creator (`participants[].state`). */
31
+ export const UGC_SPONSORSHIP_PLAN_LABELS = {
32
+ new: "Starts",
33
+ already_active: "Already sponsored",
34
+ stripe_period_running: "Moves to credits when the card period ends",
35
+ checkout_open: "Card checkout still open; cancel it first",
36
+ };
37
+ /** When a new credit sponsorship's reservation starts (`participants[].reservation_starts`). */
38
+ export const UGC_RESERVATION_START_LABELS = {
39
+ now: "Now",
40
+ when_linkedin_connects: "When they connect LinkedIn",
41
+ };
@@ -1,4 +1,2 @@
1
1
  export declare const OXYGEN_VERSION: string;
2
- export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.181.0";
3
- export declare const MANAGED_INBOX_MINIMUM_CLI_VERSION = "1.326.2";
4
- export declare const SUPPORT_AGENT_REPLY_MINIMUM_CLI_VERSION = "1.747.0";
2
+ export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.906.0";
@@ -1 +1 @@
1
- export declare const OXYGEN_BUILD_VERSION = "1.1010.721";
1
+ export declare const OXYGEN_BUILD_VERSION = "1.1010.905";
@@ -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.1010.721";
2
+ export const OXYGEN_BUILD_VERSION = "1.1010.905";
@@ -26,7 +26,8 @@ export const OXYGEN_VERSION = OXYGEN_BUILD_VERSION;
26
26
  // So floors LAG one release: publish the CLI in release N, raise the floor to it in
27
27
  // release N+1. And a contract change that breaks ONE surface belongs on that
28
28
  // surface — pass `minimumCliVersion` to requireCliIdentity/requireCliOrWebIdentity
29
- // (see MANAGED_INBOX_MINIMUM_CLI_VERSION). A per-route floor fails one command
29
+ // (declare it here as <SURFACE>_MINIMUM_CLI_VERSION, above the global floor; the
30
+ // release gate checks it too). A per-route floor fails one command
30
31
  // safely; a global floor fails the whole product. OXY-4091.
31
32
  //
32
33
  // 1.328.1: paid table action runs and background columns run require
@@ -34,29 +35,15 @@ export const OXYGEN_VERSION = OXYGEN_BUILD_VERSION;
34
35
  // 1.328.1: LinkedIn → Sequencer rename moved the CLI/API/MCP surface
35
36
  // (oxygen sequences|inbox|senders, /api/cli/{sequences,inbox,senders}) and
36
37
  // removed the old /api/cli/linkedin/* routes — older CLIs would 404.
37
- export const OXYGEN_MINIMUM_CLI_VERSION = "1.181.0";
38
- // Per-surface floor for the whitelabel/managed-inbox purchase path, enforced by
39
- // BOTH paid inbox routes: /api/cli/managed-inboxes/subscribe and
40
- // /api/cli/managed-inboxes/[domain]/mailboxes. (The comment used to say subscribe
41
- // only; the expansion route has enforced it too since it shipped, and three
42
- // separate reads of this file have since reasoned from the wrong half.)
43
- //
44
- // 1.326.2: Pricing Model 2.0 (2026-07-14) moved whitelabel inbox subscribe from the
45
- // USD Stripe rail back to Oxygen credits: the preview now returns
46
- // monthly_credits/one_off_credits and the approved execute RESERVES credits instead
47
- // of creating a Stripe subscription. An older CLI mis-renders the price/shape of a
48
- // PAID order (and would misread the reserved-not-charged response), so it is refused
49
- // on THIS route only. The floor lags one release per OXY-4091: the 1.326.2 CLI
50
- // publishes from the 1.326.x prod deploy, and this floor rides the 1.330.0 release —
51
- // so it can never exceed npm's newest CLI. (1.298.0 was the prior credits→Stripe
52
- // contract move.) Do NOT raise the global OXYGEN_MINIMUM_CLI_VERSION for this.
53
- export const MANAGED_INBOX_MINIMUM_CLI_VERSION = "1.326.2";
54
- // Per-surface floor for guarded Plain agent reply + Done. CLI 1.747.0 is the
55
- // first client that declares --agent, binds the latest customer-visible
56
- // conversation-head message and
57
- // body-authenticated preview token, carries the live agent_done_token into the
58
- // separate Done confirmation, and never falls through to the retired resolve
59
- // shape. This floor MUST remain dormant in production until 1.747.0 has been
60
- // published by the preceding release per OXY-4091; the live feature gate is the
61
- // rollout boundary that preserves that two-phase ordering.
62
- export const SUPPORT_AGENT_REPLY_MINIMUM_CLI_VERSION = "1.747.0";
38
+ // 1.906.0 (2026-09-27): the floor had sat at 1.181.0, below both of those
39
+ // breaks, so CLIs that could no longer run paid actions or reach the Sequencer
40
+ // were still admitted. 1.906.0 has been the npm `latest` since 2026-08-29, far
41
+ // behind today's latest, so the lag rule holds. Measured before the raise
42
+ // (PostHog `cli_command_run`, PROD, 14 days): 9 of 87 active organizations
43
+ // were newest on an older CLI; each gets `cli_update_required` and one
44
+ // `oxygen update` away from working. CLIs that ship packages/cli/src/auto-update.ts
45
+ // update themselves on that rejection and re-run the command, so later raises
46
+ // stop costing a manual step — the lag rule still applies unchanged. The raise
47
+ // also retired both per-route floors, managed inboxes (1.326.2) and the guarded
48
+ // support agent reply (1.747.0): below the global floor they could refuse no one.
49
+ export const OXYGEN_MINIMUM_CLI_VERSION = "1.906.0";
@@ -1,5 +1,10 @@
1
1
  export declare const WORKSPACE_VISUAL_SOURCE_MAX_BYTES = 1500000;
2
2
  export declare const WORKSPACE_VISUAL_BINARY_MAX_BYTES: number;
3
+ /**
4
+ * Retained workspace files (CRM record attachments). Table-import staging is not
5
+ * a workspace file and never reads these two ceilings: it stages under
6
+ * `imports/` through `presignImportUpload` up to the plan's import file limit.
7
+ */
3
8
  export declare const WORKSPACE_OPAQUE_FILE_MAX_BYTES: number;
4
9
  /** Platform abuse guard shared by every plan, not a storage entitlement. */
5
10
  export declare const WORKSPACE_RETAINED_FILES_MAX_BYTES: number;
@@ -6,6 +6,11 @@ import { OxygenError } from "./cli-result.js";
6
6
  import { resolveObjectStorageClient } from "./object-storage.js";
7
7
  export const WORKSPACE_VISUAL_SOURCE_MAX_BYTES = 1_500_000;
8
8
  export const WORKSPACE_VISUAL_BINARY_MAX_BYTES = 50 * 1024 * 1024;
9
+ /**
10
+ * Retained workspace files (CRM record attachments). Table-import staging is not
11
+ * a workspace file and never reads these two ceilings: it stages under
12
+ * `imports/` through `presignImportUpload` up to the plan's import file limit.
13
+ */
9
14
  export const WORKSPACE_OPAQUE_FILE_MAX_BYTES = 100 * 1024 * 1024;
10
15
  /** Platform abuse guard shared by every plan, not a storage entitlement. */
11
16
  export const WORKSPACE_RETAINED_FILES_MAX_BYTES = 1024 * 1024 * 1024;
@@ -121,7 +121,7 @@ export declare const workflowCodeV1Schema: {
121
121
  readonly timeout_ms: {
122
122
  readonly type: "integer";
123
123
  readonly minimum: 10;
124
- readonly maximum: 10000;
124
+ readonly maximum: 30000;
125
125
  readonly default: 1000;
126
126
  };
127
127
  readonly max_input_bytes: {
@@ -779,7 +779,7 @@ export declare const workflowGraphManifestSchema: {
779
779
  readonly timeout_ms: {
780
780
  readonly type: "integer";
781
781
  readonly minimum: 10;
782
- readonly maximum: 10000;
782
+ readonly maximum: 30000;
783
783
  readonly default: 1000;
784
784
  };
785
785
  readonly max_input_bytes: {
@@ -1569,7 +1569,7 @@ export declare const portableWorkflowDefinitionSchema: {
1569
1569
  readonly timeout_ms: {
1570
1570
  readonly type: "integer";
1571
1571
  readonly minimum: 10;
1572
- readonly maximum: 10000;
1572
+ readonly maximum: 30000;
1573
1573
  readonly default: 1000;
1574
1574
  };
1575
1575
  readonly max_input_bytes: {
@@ -162,8 +162,22 @@ export declare const WORKFLOW_CODE_V1_RUNTIME = "oxygen-js-v1";
162
162
  export declare const WORKFLOW_CODE_V1_MAX_SOURCE_BYTES: number;
163
163
  export declare const WORKFLOW_CODE_V1_MAX_INPUTS = 100;
164
164
  export declare const WORKFLOW_CODE_V1_MIN_TIMEOUT_MS = 10;
165
- export declare const WORKFLOW_CODE_V1_MAX_TIMEOUT_MS = 10000;
165
+ /**
166
+ * The longest a typed Code node may declare (`limits.timeout_ms`): 30 s on every
167
+ * plan (repricing 2026-09, decision L4.3, up from 10 s). It applies to this
168
+ * sandboxed microVM runtime only; the legacy synchronous `run_source` path keeps
169
+ * its own lower ceiling (`PURE_WORKFLOW_FUNCTION_MAX_TIMEOUT_MS`, proposal P-49).
170
+ */
171
+ export declare const WORKFLOW_CODE_V1_MAX_TIMEOUT_MS = 30000;
166
172
  export declare const WORKFLOW_CODE_V1_DEFAULT_TIMEOUT_MS = 1000;
173
+ /**
174
+ * The wall-clock budget of one workflow run attempt on every plan: 30 minutes
175
+ * (repricing 2026-09, decision L4.3, up from 10). An attempt that reaches it is
176
+ * stopped and resumed from its durable checkpoints, not failed. The worker's
177
+ * default recipe timeout reads this; a deployment may still pin a lower value
178
+ * with `OXYGEN_WORKER_RECIPE_TIMEOUT_MS`.
179
+ */
180
+ export declare const WORKFLOW_RUN_ATTEMPT_TIMEOUT_MS: number;
167
181
  export declare const WORKFLOW_CODE_V1_MIN_INPUT_BYTES = 1000;
168
182
  export declare const WORKFLOW_CODE_V1_MAX_INPUT_BYTES = 2000000;
169
183
  export declare const WORKFLOW_CODE_V1_DEFAULT_INPUT_BYTES = 250000;
@@ -90,8 +90,22 @@ export const WORKFLOW_CODE_V1_RUNTIME = "oxygen-js-v1";
90
90
  export const WORKFLOW_CODE_V1_MAX_SOURCE_BYTES = 64 * 1_024;
91
91
  export const WORKFLOW_CODE_V1_MAX_INPUTS = 100;
92
92
  export const WORKFLOW_CODE_V1_MIN_TIMEOUT_MS = 10;
93
- export const WORKFLOW_CODE_V1_MAX_TIMEOUT_MS = 10_000;
93
+ /**
94
+ * The longest a typed Code node may declare (`limits.timeout_ms`): 30 s on every
95
+ * plan (repricing 2026-09, decision L4.3, up from 10 s). It applies to this
96
+ * sandboxed microVM runtime only; the legacy synchronous `run_source` path keeps
97
+ * its own lower ceiling (`PURE_WORKFLOW_FUNCTION_MAX_TIMEOUT_MS`, proposal P-49).
98
+ */
99
+ export const WORKFLOW_CODE_V1_MAX_TIMEOUT_MS = 30_000;
94
100
  export const WORKFLOW_CODE_V1_DEFAULT_TIMEOUT_MS = 1_000;
101
+ /**
102
+ * The wall-clock budget of one workflow run attempt on every plan: 30 minutes
103
+ * (repricing 2026-09, decision L4.3, up from 10). An attempt that reaches it is
104
+ * stopped and resumed from its durable checkpoints, not failed. The worker's
105
+ * default recipe timeout reads this; a deployment may still pin a lower value
106
+ * with `OXYGEN_WORKER_RECIPE_TIMEOUT_MS`.
107
+ */
108
+ export const WORKFLOW_RUN_ATTEMPT_TIMEOUT_MS = 30 * 60 * 1000;
95
109
  export const WORKFLOW_CODE_V1_MIN_INPUT_BYTES = 1_000;
96
110
  export const WORKFLOW_CODE_V1_MAX_INPUT_BYTES = 2_000_000;
97
111
  export const WORKFLOW_CODE_V1_DEFAULT_INPUT_BYTES = 250_000;
@@ -341,6 +341,43 @@ export declare function nextCronRunAfter(input: {
341
341
  timezone?: string | null;
342
342
  after?: Date;
343
343
  }): Date;
344
+ /**
345
+ * The first slot of `cron` strictly after `after` that is also at least
346
+ * `minIntervalMinutes` after `lastFireAt`. This is how a plan's minimum cron
347
+ * interval (repricing 2026-09, decision L4.2) is enforced at fire time: a
348
+ * schedule that fires closer together than the plan allows keeps running, only
349
+ * slower, and is never deleted (proposal P-47). With no last fire or a minimum
350
+ * of one minute or less it is exactly `nextCronRunAfter`.
351
+ */
352
+ export declare function nextCronRunAfterMinInterval(input: {
353
+ cron: string;
354
+ timezone?: string | null;
355
+ after: Date;
356
+ lastFireAt?: Date | null;
357
+ minIntervalMinutes?: number | null;
358
+ }): Date;
359
+ /**
360
+ * The smallest gap in minutes between two consecutive fires of `cron`, which is
361
+ * what a plan's minimum cron interval is checked against (repricing 2026-09,
362
+ * decision L4.2). It is the smallest gap, never the average: `0,5 9 * * *`
363
+ * fires once a day on average but twice five minutes apart, so its gap is 5.
364
+ *
365
+ * Gaps are measured in real time in `timezone`. A daylight-saving change can
366
+ * bring two fires closer than their wall-clock times suggest (01:55 and 03:05
367
+ * are ten minutes apart on the night the clock skips from 02:00 to 03:00, and
368
+ * a repeated hour can fire 01:45 and then 01:00 again), so the fires around
369
+ * each change in the coming year are simulated as well.
370
+ *
371
+ * Returns null when the schedule never fires twice within the scan (for
372
+ * example `0 9 29 2 *` fires once every few years and has no meaningful gap
373
+ * for a minimum to bind). Throws on an unparseable expression, like
374
+ * `nextCronRunAfter`.
375
+ */
376
+ export declare function minCronGapMinutes(input: {
377
+ cron: string;
378
+ timezone?: string | null;
379
+ now?: Date;
380
+ }): number | null;
344
381
  export declare function transformStep(input: {
345
382
  id: string;
346
383
  description?: string;
@@ -449,6 +486,14 @@ export declare function assertRecipeBundleSafe(bundle: string): void;
449
486
  export declare function lintRecipeBundleSafety(bundle: string, path?: string): WorkflowLintIssue[];
450
487
  /** Comment/string-aware author feedback for the stricter oxygen-js-v1 runtime. */
451
488
  export declare function lintWorkflowCodeSourceSafety(source: string, path?: string): WorkflowLintIssue[];
489
+ /**
490
+ * The ceiling of the legacy synchronous `node:vm` path below. It stays at 10 s
491
+ * (and its callers pass 1 s) when the typed Code node rose to 30 s (repricing
492
+ * 2026-09, decision L4.3; proposal P-49): this path runs on the worker's own
493
+ * event loop, so a long synchronous script would stall every other job and its
494
+ * queue lock renewal. The typed Code node runs in a separate microVM instead.
495
+ */
496
+ export declare const PURE_WORKFLOW_FUNCTION_MAX_TIMEOUT_MS = 10000;
452
497
  export declare function runPureWorkflowFunction(input: {
453
498
  source: string;
454
499
  context: Record<string, unknown>;
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import * as vm from "node:vm"; // skipcq: JS-C1003
3
- import { isWorkflowGraphManifest, WORKFLOW_CODE_V1_DEFAULT_OUTPUT_BYTES, WORKFLOW_CODE_V1_DEFAULT_TIMEOUT_MS, WORKFLOW_CODE_V1_MAX_OUTPUT_BYTES, WORKFLOW_CODE_V1_MAX_TIMEOUT_MS, WORKFLOW_CODE_V1_MIN_OUTPUT_BYTES, WORKFLOW_CODE_V1_MIN_TIMEOUT_MS, } from "./graph/types.js";
3
+ import { isWorkflowGraphManifest, WORKFLOW_CODE_V1_DEFAULT_OUTPUT_BYTES, WORKFLOW_CODE_V1_DEFAULT_TIMEOUT_MS, WORKFLOW_CODE_V1_MAX_OUTPUT_BYTES, WORKFLOW_CODE_V1_MIN_OUTPUT_BYTES, WORKFLOW_CODE_V1_MIN_TIMEOUT_MS, } from "./graph/types.js";
4
4
  import { lintWorkflowGraphManifest } from "./graph/lint.js";
5
5
  export * from "./usage-estimate.js";
6
6
  // Pure event-dispatch semantics, shared by the apps/web and apps/worker event
@@ -162,6 +162,148 @@ export function nextCronRunAfter(input) {
162
162
  }
163
163
  throw new Error(`Cron expression has no matching run time in the next ${MAX_CRON_LOOKAHEAD_DAYS} days.`);
164
164
  }
165
+ /**
166
+ * The first slot of `cron` strictly after `after` that is also at least
167
+ * `minIntervalMinutes` after `lastFireAt`. This is how a plan's minimum cron
168
+ * interval (repricing 2026-09, decision L4.2) is enforced at fire time: a
169
+ * schedule that fires closer together than the plan allows keeps running, only
170
+ * slower, and is never deleted (proposal P-47). With no last fire or a minimum
171
+ * of one minute or less it is exactly `nextCronRunAfter`.
172
+ */
173
+ export function nextCronRunAfterMinInterval(input) {
174
+ const minIntervalMs = Math.max(0, input.minIntervalMinutes ?? 0) * 60_000;
175
+ let after = input.after;
176
+ if (input.lastFireAt && minIntervalMs > 60_000) {
177
+ // One millisecond before the earliest allowed instant: nextCronRunAfter
178
+ // returns the first whole-minute slot strictly after it, so a slot exactly
179
+ // `minIntervalMinutes` after the last fire is allowed.
180
+ const earliestAllowed = input.lastFireAt.getTime() + minIntervalMs - 1;
181
+ if (earliestAllowed > after.getTime())
182
+ after = new Date(earliestAllowed);
183
+ }
184
+ return nextCronRunAfter({ cron: input.cron, timezone: input.timezone ?? null, after });
185
+ }
186
+ // How many calendar days minCronGapMinutes scans for two matching days in a
187
+ // row: two leap cycles, so a Feb 29 or a day-of-month/weekday coincidence is
188
+ // always seen.
189
+ const CRON_GAP_DAY_SCAN = 366 * 8 + 2;
190
+ // Real-time simulation around a daylight-saving change covers the day it falls
191
+ // in plus this many whole days either side (so the fire before a long gap that
192
+ // straddles the change is inside the window), and stops after this many fires.
193
+ const CRON_GAP_DST_WINDOW_DAYS = 1;
194
+ const CRON_GAP_DST_MAX_FIRES = 5_000;
195
+ /**
196
+ * The smallest gap in minutes between two consecutive fires of `cron`, which is
197
+ * what a plan's minimum cron interval is checked against (repricing 2026-09,
198
+ * decision L4.2). It is the smallest gap, never the average: `0,5 9 * * *`
199
+ * fires once a day on average but twice five minutes apart, so its gap is 5.
200
+ *
201
+ * Gaps are measured in real time in `timezone`. A daylight-saving change can
202
+ * bring two fires closer than their wall-clock times suggest (01:55 and 03:05
203
+ * are ten minutes apart on the night the clock skips from 02:00 to 03:00, and
204
+ * a repeated hour can fire 01:45 and then 01:00 again), so the fires around
205
+ * each change in the coming year are simulated as well.
206
+ *
207
+ * Returns null when the schedule never fires twice within the scan (for
208
+ * example `0 9 29 2 *` fires once every few years and has no meaningful gap
209
+ * for a minimum to bind). Throws on an unparseable expression, like
210
+ * `nextCronRunAfter`.
211
+ */
212
+ export function minCronGapMinutes(input) {
213
+ const schedule = parseCronSchedule(input.cron);
214
+ const timezone = input.timezone?.trim() || DEFAULT_WORKFLOW_CRON_TIMEZONE;
215
+ const fireMinutes = [...schedule.hours]
216
+ .flatMap((hour) => [...schedule.minutes].map((minute) => hour * 60 + minute))
217
+ .sort((a, b) => a - b);
218
+ let gap = Number.POSITIVE_INFINITY;
219
+ for (let index = 1; index < fireMinutes.length; index += 1) {
220
+ gap = Math.min(gap, (fireMinutes[index] ?? 0) - (fireMinutes[index - 1] ?? 0));
221
+ }
222
+ const dayGap = smallestMatchingDayGap(schedule);
223
+ const firstFire = fireMinutes[0];
224
+ const lastFire = fireMinutes[fireMinutes.length - 1];
225
+ if (dayGap !== null && firstFire !== undefined && lastFire !== undefined) {
226
+ gap = Math.min(gap, dayGap * 24 * 60 + firstFire - lastFire);
227
+ }
228
+ // A one-minute gap is the floor of the cron grammar; daylight saving cannot
229
+ // bring two fires closer than that.
230
+ if (gap > 1) {
231
+ const dstGap = smallestGapAroundDaylightSavingChanges(input.cron, timezone, input.now ?? new Date());
232
+ if (dstGap !== null)
233
+ gap = Math.min(gap, dstGap);
234
+ }
235
+ return Number.isFinite(gap) ? gap : null;
236
+ }
237
+ /** Fewest days between two calendar days the schedule's date fields both match. */
238
+ function smallestMatchingDayGap(schedule) {
239
+ // Calendar arithmetic only (no timezone): day-of-month, month and weekday
240
+ // matching is the same on every clock. A fixed leap-year start covers every
241
+ // weekday/day-of-month combination within the scan.
242
+ const start = Date.UTC(2024, 0, 1);
243
+ let previousMatch = null;
244
+ let smallest = null;
245
+ for (let day = 0; day < CRON_GAP_DAY_SCAN; day += 1) {
246
+ const date = new Date(start + day * 24 * 60 * 60 * 1000);
247
+ const matches = matchesCronDate(schedule, {
248
+ minute: 0,
249
+ hour: 0,
250
+ dayOfMonth: date.getUTCDate(),
251
+ month: date.getUTCMonth() + 1,
252
+ dayOfWeek: date.getUTCDay(),
253
+ });
254
+ if (!matches)
255
+ continue;
256
+ if (previousMatch !== null) {
257
+ const gap = day - previousMatch;
258
+ smallest = smallest === null ? gap : Math.min(smallest, gap);
259
+ if (smallest === 1)
260
+ return 1;
261
+ }
262
+ previousMatch = day;
263
+ }
264
+ return smallest;
265
+ }
266
+ /**
267
+ * The smallest real-time gap between fires in the days around each
268
+ * daylight-saving change in the year after `now`; null when the zone has no
269
+ * change in that year or no two fires fall in any window.
270
+ */
271
+ function smallestGapAroundDaylightSavingChanges(cron, timezone, now) {
272
+ const dayMs = 24 * 60 * 60 * 1000;
273
+ const start = Math.floor(now.getTime() / dayMs) * dayMs;
274
+ let smallest = null;
275
+ let previousOffset = zonedOffsetMinutes(new Date(start), timezone);
276
+ for (let day = 1; day <= 367; day += 1) {
277
+ const offset = zonedOffsetMinutes(new Date(start + day * dayMs), timezone);
278
+ if (offset === previousOffset)
279
+ continue;
280
+ previousOffset = offset;
281
+ const windowStart = new Date(start + (day - 1 - CRON_GAP_DST_WINDOW_DAYS) * dayMs);
282
+ const windowEnd = start + (day + CRON_GAP_DST_WINDOW_DAYS) * dayMs;
283
+ let fire = nextCronRunAfter({ cron, timezone, after: windowStart });
284
+ for (let fires = 0; fires < CRON_GAP_DST_MAX_FIRES && fire.getTime() <= windowEnd; fires += 1) {
285
+ const next = nextCronRunAfter({ cron, timezone, after: fire });
286
+ if (next.getTime() > windowEnd)
287
+ break;
288
+ const gap = Math.round((next.getTime() - fire.getTime()) / 60_000);
289
+ smallest = smallest === null ? gap : Math.min(smallest, gap);
290
+ fire = next;
291
+ }
292
+ }
293
+ return smallest;
294
+ }
295
+ /** Minutes the zone's wall clock is ahead of UTC at `date`. */
296
+ function zonedOffsetMinutes(date, timezone) {
297
+ const formatter = getZonedFormatter(timezone);
298
+ const parts = new Map();
299
+ for (const part of formatter.formatToParts(date)) {
300
+ if (part.type !== "literal")
301
+ parts.set(part.type, part.value);
302
+ }
303
+ const wallClock = Date.UTC(Number(parts.get("year")), Number(parts.get("month")) - 1, Number(parts.get("day")), Number(parts.get("hour")), Number(parts.get("minute")));
304
+ const instant = Math.floor(date.getTime() / 60_000) * 60_000;
305
+ return Math.round((wallClock - instant) / 60_000);
306
+ }
165
307
  export function transformStep(input) {
166
308
  return {
167
309
  __oxygen_workflow_step: true,
@@ -983,8 +1125,16 @@ export function lintWorkflowCodeSourceSafety(source, path = "$.source") {
983
1125
  }
984
1126
  return issues;
985
1127
  }
1128
+ /**
1129
+ * The ceiling of the legacy synchronous `node:vm` path below. It stays at 10 s
1130
+ * (and its callers pass 1 s) when the typed Code node rose to 30 s (repricing
1131
+ * 2026-09, decision L4.3; proposal P-49): this path runs on the worker's own
1132
+ * event loop, so a long synchronous script would stall every other job and its
1133
+ * queue lock renewal. The typed Code node runs in a separate microVM instead.
1134
+ */
1135
+ export const PURE_WORKFLOW_FUNCTION_MAX_TIMEOUT_MS = 10_000;
986
1136
  export async function runPureWorkflowFunction(input) {
987
- const timeoutMs = Math.max(WORKFLOW_CODE_V1_MIN_TIMEOUT_MS, Math.min(input.timeoutMs ?? WORKFLOW_CODE_V1_DEFAULT_TIMEOUT_MS, WORKFLOW_CODE_V1_MAX_TIMEOUT_MS));
1137
+ const timeoutMs = Math.max(WORKFLOW_CODE_V1_MIN_TIMEOUT_MS, Math.min(input.timeoutMs ?? WORKFLOW_CODE_V1_DEFAULT_TIMEOUT_MS, PURE_WORKFLOW_FUNCTION_MAX_TIMEOUT_MS));
988
1138
  const maxOutputBytes = Math.max(WORKFLOW_CODE_V1_MIN_OUTPUT_BYTES, Math.min(input.maxOutputBytes ?? WORKFLOW_CODE_V1_DEFAULT_OUTPUT_BYTES, WORKFLOW_CODE_V1_MAX_OUTPUT_BYTES));
989
1139
  const issues = [];
990
1140
  validatePureFunctionSource(input.source, "$.source", (path, code, message) => {
@@ -3,7 +3,12 @@ export type WorkflowAutomationUsageEstimate = {
3
3
  scheduledRunsPer30Days: number | null;
4
4
  scheduledActionsPer30DaysFloor: number | null;
5
5
  dynamicRuntimeActions: boolean;
6
- perRowFanout: boolean;
6
+ /**
7
+ * Always false: row writes stopped billing per row (repricing 2026-09,
8
+ * decision 6.2). Kept for one release so older CLIs and pages that read it
9
+ * still parse; remove after that release.
10
+ */
11
+ perRowFanout: false;
7
12
  };
8
13
  export declare function estimateWorkflowRunAutomationActionsFloor(manifest: unknown): number;
9
14
  export declare function estimateWorkflowAutomationUsage(manifest: unknown, cron?: string | null): WorkflowAutomationUsageEstimate | null;
@@ -16,6 +21,8 @@ export type CronCadenceAssessment = {
16
21
  floorShareOfIncluded: number;
17
22
  basis: "plan_action_quota" | "plan_monthly_credits";
18
23
  includedCredits: number | null;
24
+ spendableCredits: number | null;
25
+ allowanceCredits: number | null;
19
26
  floorCreditsPer30Days: number | null;
20
27
  minimumViableIntervalMinutes: number | null;
21
28
  verdict: "fits" | "impossible";
@@ -27,6 +34,8 @@ export declare function assessCronCadenceViability(input: {
27
34
  overageEnabled: boolean;
28
35
  includedCredits?: number | null;
29
36
  creditsPerAction?: number | null;
37
+ /** Credits the workspace can spend right now (its available balance). */
38
+ spendableCredits?: number | null;
30
39
  }): CronCadenceAssessment | null;
31
40
  export type CronAggressiveness = {
32
41
  level: "aggressive" | "very_aggressive";