@oxygen-agent/cli 1.1010.650 → 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 (114) 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 +15 -1
  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/inbox-needs-reply-notice.d.ts +12 -0
  10. package/dist/inbox-needs-reply-notice.js +51 -0
  11. package/dist/index.js +756 -177
  12. package/dist/run-wait.d.ts +3 -1
  13. package/dist/run-wait.js +19 -5
  14. package/dist/skills.js +48 -22
  15. package/dist/streamed-file-import.d.ts +58 -0
  16. package/dist/streamed-file-import.js +115 -0
  17. package/dist/update.d.ts +29 -0
  18. package/dist/update.js +62 -16
  19. package/dist/workflow-plan-limit-notices.d.ts +8 -0
  20. package/dist/workflow-plan-limit-notices.js +28 -0
  21. package/node_modules/@oxygen/cli-ugc/dist/commands.js +3 -3
  22. package/node_modules/@oxygen/shared/dist/billing-anchors.d.ts +50 -2
  23. package/node_modules/@oxygen/shared/dist/billing-anchors.js +94 -2
  24. package/node_modules/@oxygen/shared/dist/billing.d.ts +247 -37
  25. package/node_modules/@oxygen/shared/dist/billing.js +418 -45
  26. package/node_modules/@oxygen/shared/dist/capability-discovery.js +66 -6
  27. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +6 -6
  28. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +6 -6
  29. package/node_modules/@oxygen/shared/dist/cost-estimate-view.d.ts +50 -0
  30. package/node_modules/@oxygen/shared/dist/cost-estimate-view.js +90 -0
  31. package/node_modules/@oxygen/shared/dist/cost-estimate.d.ts +167 -0
  32. package/node_modules/@oxygen/shared/dist/cost-estimate.js +361 -0
  33. package/node_modules/@oxygen/shared/dist/credit-gate.d.ts +26 -0
  34. package/node_modules/@oxygen/shared/dist/credit-gate.js +65 -0
  35. package/node_modules/@oxygen/shared/dist/email-deliverability-policy.d.ts +51 -0
  36. package/node_modules/@oxygen/shared/dist/email-deliverability-policy.js +101 -0
  37. package/node_modules/@oxygen/shared/dist/email-hard-bounce.d.ts +27 -0
  38. package/node_modules/@oxygen/shared/dist/email-hard-bounce.js +27 -0
  39. package/node_modules/@oxygen/shared/dist/error-redaction.d.ts +1 -1
  40. package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -1
  41. package/node_modules/@oxygen/shared/dist/feature-gates.d.ts +10 -1
  42. package/node_modules/@oxygen/shared/dist/feature-gates.js +12 -1
  43. package/node_modules/@oxygen/shared/dist/file-import.d.ts +13 -1
  44. package/node_modules/@oxygen/shared/dist/file-import.js +33 -6
  45. package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +73 -3
  46. package/node_modules/@oxygen/shared/dist/hosted-ai.js +246 -24
  47. package/node_modules/@oxygen/shared/dist/import-limits.d.ts +25 -1
  48. package/node_modules/@oxygen/shared/dist/import-limits.js +35 -2
  49. package/node_modules/@oxygen/shared/dist/index.d.ts +4 -23
  50. package/node_modules/@oxygen/shared/dist/index.js +4 -43
  51. package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +114 -0
  52. package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +150 -0
  53. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +9 -0
  54. package/node_modules/@oxygen/shared/dist/object-storage.js +17 -0
  55. package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +41 -0
  56. package/node_modules/@oxygen/shared/dist/operational-telemetry.js +55 -0
  57. package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +19 -2
  58. package/node_modules/@oxygen/shared/dist/plan-band.d.ts +234 -0
  59. package/node_modules/@oxygen/shared/dist/plan-band.js +312 -0
  60. package/node_modules/@oxygen/shared/dist/plan-capabilities.d.ts +77 -7
  61. package/node_modules/@oxygen/shared/dist/plan-capabilities.js +87 -7
  62. package/node_modules/@oxygen/shared/dist/plan-limits-view.d.ts +219 -0
  63. package/node_modules/@oxygen/shared/dist/plan-limits-view.js +330 -0
  64. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +335 -126
  65. package/node_modules/@oxygen/shared/dist/plan-limits.js +277 -86
  66. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +158 -49
  67. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +139 -41
  68. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +42 -23
  69. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +56 -37
  70. package/node_modules/@oxygen/shared/dist/process-resource.d.ts +4 -0
  71. package/node_modules/@oxygen/shared/dist/process-resource.js +25 -0
  72. package/node_modules/@oxygen/shared/dist/provider-http-error.d.ts +10 -0
  73. package/node_modules/@oxygen/shared/dist/provider-http-error.js +27 -0
  74. package/node_modules/@oxygen/shared/dist/repricing.d.ts +257 -0
  75. package/node_modules/@oxygen/shared/dist/repricing.js +721 -0
  76. package/node_modules/@oxygen/shared/dist/semver.d.ts +21 -0
  77. package/node_modules/@oxygen/shared/dist/semver.js +41 -0
  78. package/node_modules/@oxygen/shared/dist/sending-limits.d.ts +30 -0
  79. package/node_modules/@oxygen/shared/dist/sending-limits.js +43 -0
  80. package/node_modules/@oxygen/shared/dist/sending-seats.d.ts +18 -15
  81. package/node_modules/@oxygen/shared/dist/sending-seats.js +22 -17
  82. package/node_modules/@oxygen/shared/dist/sequence-failures.js +4 -1
  83. package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +57 -8
  84. package/node_modules/@oxygen/shared/dist/spend-safety.js +64 -11
  85. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +33 -1
  86. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +71 -1
  87. package/node_modules/@oxygen/shared/dist/table-capacity.d.ts +68 -10
  88. package/node_modules/@oxygen/shared/dist/table-capacity.js +85 -4
  89. package/node_modules/@oxygen/shared/dist/telemetry-export-observer.d.ts +6 -0
  90. package/node_modules/@oxygen/shared/dist/telemetry-export-observer.js +13 -5
  91. package/node_modules/@oxygen/shared/dist/telemetry-resource.d.ts +40 -0
  92. package/node_modules/@oxygen/shared/dist/telemetry-resource.js +35 -0
  93. package/node_modules/@oxygen/shared/dist/telemetry.d.ts +9 -0
  94. package/node_modules/@oxygen/shared/dist/telemetry.js +41 -2
  95. package/node_modules/@oxygen/shared/dist/trace-context.d.ts +29 -0
  96. package/node_modules/@oxygen/shared/dist/trace-context.js +88 -0
  97. package/node_modules/@oxygen/shared/dist/ugc.d.ts +15 -0
  98. package/node_modules/@oxygen/shared/dist/ugc.js +29 -0
  99. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -3
  100. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  101. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  102. package/node_modules/@oxygen/shared/dist/version.js +14 -27
  103. package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +5 -0
  104. package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +5 -0
  105. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +3 -3
  106. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +15 -1
  107. package/node_modules/@oxygen/workflows/dist/graph/types.js +15 -1
  108. package/node_modules/@oxygen/workflows/dist/index.d.ts +45 -0
  109. package/node_modules/@oxygen/workflows/dist/index.js +152 -2
  110. package/node_modules/@oxygen/workflows/dist/usage-estimate.d.ts +10 -1
  111. package/node_modules/@oxygen/workflows/dist/usage-estimate.js +33 -29
  112. package/package.json +1 -1
  113. package/node_modules/@oxygen/shared/dist/email-warmup-readiness.d.ts +0 -64
  114. package/node_modules/@oxygen/shared/dist/email-warmup-readiness.js +0 -90
@@ -1,5 +1,7 @@
1
1
  import { resolveBasePricingPlan } from "./billing.js";
2
+ import { OxygenError } from "./cli-result.js";
2
3
  import { WORKSPACE_TABLE_CAPACITY } from "./table-capacity.js";
4
+ import { findRepricedValue, repricedValue } from "./repricing.js";
3
5
  export const LIMITS_TIER_ORDER = [
4
6
  "free",
5
7
  "starter",
@@ -11,10 +13,12 @@ const HOUR = 60 * 60;
11
13
  const DAY = 24 * HOUR;
12
14
  const MIB = 1024 * 1024;
13
15
  /**
14
- * One platform-wide Table import ceiling. A file can fill an empty physical
15
- * Table exactly to its retained-row capacity on every plan; imports into a
16
- * non-empty Table remain subject to that Table's remaining capacity and the
17
- * workspace-wide capacity envelope.
16
+ * The per-rung Table import ceiling this matrix reports: today's envelope. The
17
+ * ceiling ENFORCED on an import is the workspace's plan-band rows-per-Table
18
+ * limit (`resolveWorkspaceTableCapacity`, PROPOSED P-60: a file can fill an
19
+ * empty Table exactly), which the web import paths resolve per organization.
20
+ * Imports into a non-empty Table remain subject to that Table's remaining
21
+ * capacity and the workspace-wide envelope.
18
22
  */
19
23
  export const TABLE_IMPORT_ROW_LIMIT = WORKSPACE_TABLE_CAPACITY.tableRowLimit;
20
24
  // The platform request-body ceiling belongs to the same question as the import
@@ -29,42 +33,77 @@ export { VERCEL_REQUEST_BODY_LIMIT_BYTES } from "./import-limits.js";
29
33
  * in the CLI alone — an older server would still 413 at the old number.
30
34
  */
31
35
  export const MAX_CLI_JSON_BODY_BYTES = 2_000_000;
36
+ /**
37
+ * Derived plan-scaled limits (repricing 2026-09 decision L6, spec § 4.7):
38
+ * import file size, columns per action run or standing auto-run and the signals
39
+ * feed. Each only rises against the pre-repricing ladder, so no paid customer
40
+ * loses a limit and nothing here waits for the effective-date switch. Rows per
41
+ * write request stay 5,000 free / 50,000 paid: the 2 MB request body
42
+ * (`MAX_CLI_JSON_BODY_BYTES`) binds first.
43
+ *
44
+ * `importFileBytes` is ratified (decision F.8, Philipp 2026-09-27): free 500 MiB,
45
+ * $49–$99 1 GiB, $199–$499 2 GiB, $999–$1,999 5 GiB. It is live on every plan
46
+ * and no longer waits for the per-Table capacity test: rows per Table still cap
47
+ * what one import can load, so a bigger file never buys more rows. 5 GiB is also
48
+ * the largest single PUT an S3-compatible store accepts
49
+ * (`IMPORT_OBJECT_SINGLE_PUT_MAX_BYTES`), which is the one upload a staged
50
+ * import makes.
51
+ */
52
+ export const DERIVED_PLAN_LIMIT_TARGETS = {
53
+ free: { importFileBytes: 500 * MIB, maxActionsPerRun: 10, signals: { maxEvents: 100, maxWindowDays: 30 } },
54
+ starter: { importFileBytes: 1_024 * MIB, maxActionsPerRun: 25, signals: { maxEvents: 200, maxWindowDays: 90 } },
55
+ pro: { importFileBytes: 2_048 * MIB, maxActionsPerRun: 25, signals: { maxEvents: 500, maxWindowDays: 180 } },
56
+ team: { importFileBytes: 5_120 * MIB, maxActionsPerRun: 50, signals: { maxEvents: 1_000, maxWindowDays: 365 } },
57
+ scale: { importFileBytes: 5_120 * MIB, maxActionsPerRun: 50, signals: { maxEvents: 1_000, maxWindowDays: 365 } },
58
+ };
59
+ /** The import file size a rung enforces (see `DERIVED_PLAN_LIMIT_TARGETS`). */
60
+ export function importMaxFileBytesForTier(tier) {
61
+ return DERIVED_PLAN_LIMIT_TARGETS[tier].importFileBytes;
62
+ }
32
63
  /**
33
64
  * The per-rung limit matrix. `ai_live` deliberately equals `tool_live` at every
34
65
  * rung — one "live actions" mental model; the per-call cost asymmetry between
35
66
  * AI and tool calls is priced in credits, not throttled here.
67
+ *
68
+ * API rates follow the 2026-09 repricing (decision L2, ratified 2026-09-26):
69
+ * `cli.global` is the per-organization and per-key request total per minute,
70
+ * 1,200 / 3,000 / 6,000 / 12,000 / 30,000 from free to scale. The per-operation
71
+ * buckets below are PROPOSED (P-52, repricing spec § 4.2): each pre-repricing
72
+ * bucket multiplied by its rung's organization factor, 2x on free and 5x on
73
+ * every paid rung, and never above the rung's per-key total. `tenantSchema` and
74
+ * the flat `tenant_admin` guard in the web rate limiter are DDL abuse guards and
75
+ * stay unscaled.
36
76
  */
37
77
  export const PLAN_LIMITS = {
38
78
  free: {
39
79
  cli: {
40
- global: { keyRequestsPerMinute: 120, orgRequestsPerMinute: 600 },
41
- tenantRead: { requestsPerMinute: 60, orgUnits: { limit: 500_000, windowSeconds: HOUR } },
42
- tenantWrite: { requestsPerMinute: 30, orgUnits: { limit: 100_000, windowSeconds: HOUR } },
43
- bulkImport: { requestsPerMinute: 10, orgRequests: { limit: 100, windowSeconds: HOUR } },
44
- aiLive: { requestsPerMinute: 20, orgRequests: { limit: 100, windowSeconds: HOUR } },
45
- toolLive: { requestsPerMinute: 20, orgRequests: { limit: 100, windowSeconds: HOUR } },
46
- toolDryRun: { requestsPerMinute: 60, orgRequests: { limit: 500, windowSeconds: HOUR } },
80
+ global: { keyRequestsPerMinute: 1_200, orgRequestsPerMinute: 1_200 },
81
+ tenantRead: { requestsPerMinute: 120, orgUnits: { limit: 1_000_000, windowSeconds: HOUR } },
82
+ tenantWrite: { requestsPerMinute: 60, orgUnits: { limit: 200_000, windowSeconds: HOUR } },
83
+ bulkImport: { requestsPerMinute: 20, orgRequests: { limit: 200, windowSeconds: HOUR } },
84
+ aiLive: { requestsPerMinute: 40, orgRequests: { limit: 200, windowSeconds: HOUR } },
85
+ toolLive: { requestsPerMinute: 40, orgRequests: { limit: 200, windowSeconds: HOUR } },
86
+ toolDryRun: { requestsPerMinute: 120, orgRequests: { limit: 1_000, windowSeconds: HOUR } },
47
87
  tenantSchema: { requestsPerMinute: 20, orgRequests: { limit: 100, windowSeconds: HOUR } },
48
88
  maxWriteRowsPerRequest: 5_000,
49
89
  },
50
90
  web: {
51
- tenantRead: { requestsPerMinute: 120, orgUnits: { limit: 1_000_000, windowSeconds: HOUR } },
52
- tenantWrite: { requestsPerMinute: 60, orgUnits: { limit: 500_000, windowSeconds: HOUR } },
53
- columnRun: { requestsPerMinute: 30, orgUnits: { limit: 2_000, windowSeconds: HOUR } },
54
- import: { requestsPerMinute: 10, orgRequests: { limit: 100, windowSeconds: HOUR } },
91
+ tenantRead: { requestsPerMinute: 240, orgUnits: { limit: 2_000_000, windowSeconds: HOUR } },
92
+ tenantWrite: { requestsPerMinute: 120, orgUnits: { limit: 1_000_000, windowSeconds: HOUR } },
93
+ columnRun: { requestsPerMinute: 60, orgUnits: { limit: 4_000, windowSeconds: HOUR } },
94
+ import: { requestsPerMinute: 20, orgRequests: { limit: 200, windowSeconds: HOUR } },
55
95
  },
56
96
  import: {
57
97
  maxRowsPerFile: TABLE_IMPORT_ROW_LIMIT,
58
- maxFileBytes: 100 * MIB,
98
+ maxFileBytes: importMaxFileBytesForTier("free"),
59
99
  maxDirectCsvBodyBytes: 10_000_000,
60
100
  },
61
- // Sized against the free tier's actual funding (OXP-10.2.4, 2026-08-17):
62
- // 5,000 credits at signup plus 5,000 on CLI/MCP activation, one time each,
63
- // with no monthly allowance to refill them. The previous ceilings were
64
- // written when `free` meant a churned org that could spend nothing, so they
65
- // were harmless at any value; now a single Agent run capped at 10,000 could
66
- // consume the ENTIRE lifetime grant, and one capped at 5,000 could consume
67
- // the whole signup grant before the customer saw anything work.
101
+ // Sized against the free tier's actual funding (OXP-10.2.4, 2026-08-17): one
102
+ // signup grant of FREE_SIGNUP_GRANT_CREDITS (1,000) with no monthly allowance
103
+ // to refill it. The previous ceilings were written when `free` meant a
104
+ // churned org that could spend nothing, so they were harmless at any value;
105
+ // an Agent run capped at the old 1,000 (today's unit) could consume the
106
+ // ENTIRE lifetime grant before the customer saw anything work.
68
107
  //
69
108
  // These are runaway guards, not the billing meter — a workspace that tops up
70
109
  // is bounded by its balance, not by these. The intent is only that no single
@@ -75,36 +114,40 @@ export const PLAN_LIMITS = {
75
114
  maxUpstreamCostUsd: 10,
76
115
  maxUpstreamCostUsdByok: 10,
77
116
  },
78
- // Left at 5,000/1,500 deliberately. The signup grant is exactly one default
79
- // Copilot session, and that is the point: a founder who picks the in-product
80
- // path over the CLI must be able to complete one real session on the base
81
- // grant alone. Lowering this would re-create the dead-on-arrival Copilot the
82
- // split grant exists to prevent.
117
+ // Left at 500/150 deliberately (5,000/1,500 before the 2026-09-19 unit
118
+ // change). A founder who picks the in-product path over the CLI must be able
119
+ // to complete one real Copilot session on the 1,000-credit signup grant
120
+ // alone. Lowering this would re-create a dead-on-arrival Copilot.
83
121
  copilot: { maxSessionBudgetCredits: 500, defaultPerTurnCreditCeiling: 150 },
84
- signals: { maxEvents: 100, maxWindowDays: 30 },
85
- tableActions: { maxActionsPerRun: 10 },
122
+ signals: DERIVED_PLAN_LIMIT_TARGETS.free.signals,
123
+ tableActions: { maxActionsPerRun: DERIVED_PLAN_LIMIT_TARGETS.free.maxActionsPerRun },
124
+ // Decision L4.2 (ratified 2026-09-26): a free workspace schedules at most
125
+ // every 15 minutes, paid plans every minute. Dark until the repricing switch
126
+ // (FREE_CRON_MIN_INTERVAL_REPRICING); a faster schedule already armed is
127
+ // slowed to this interval at fire time, never deleted (proposal P-47).
128
+ workflows: { cronMinIntervalMinutes: 15 },
86
129
  },
87
130
  starter: {
88
131
  cli: {
89
- global: { keyRequestsPerMinute: 240, orgRequestsPerMinute: 600 },
90
- tenantRead: { requestsPerMinute: 120, orgUnits: { limit: 25_000_000, windowSeconds: DAY } },
91
- tenantWrite: { requestsPerMinute: 120, orgUnits: { limit: 5_000_000, windowSeconds: DAY } },
92
- bulkImport: { requestsPerMinute: 30, orgRequests: { limit: 500, windowSeconds: HOUR } },
93
- aiLive: { requestsPerMinute: 60, orgRequests: { limit: 5_000, windowSeconds: HOUR } },
94
- toolLive: { requestsPerMinute: 60, orgRequests: { limit: 5_000, windowSeconds: HOUR } },
95
- toolDryRun: { requestsPerMinute: 120, orgRequests: { limit: 2_000, windowSeconds: HOUR } },
132
+ global: { keyRequestsPerMinute: 3_000, orgRequestsPerMinute: 3_000 },
133
+ tenantRead: { requestsPerMinute: 600, orgUnits: { limit: 125_000_000, windowSeconds: DAY } },
134
+ tenantWrite: { requestsPerMinute: 600, orgUnits: { limit: 25_000_000, windowSeconds: DAY } },
135
+ bulkImport: { requestsPerMinute: 150, orgRequests: { limit: 2_500, windowSeconds: HOUR } },
136
+ aiLive: { requestsPerMinute: 300, orgRequests: { limit: 25_000, windowSeconds: HOUR } },
137
+ toolLive: { requestsPerMinute: 300, orgRequests: { limit: 25_000, windowSeconds: HOUR } },
138
+ toolDryRun: { requestsPerMinute: 600, orgRequests: { limit: 10_000, windowSeconds: HOUR } },
96
139
  tenantSchema: { requestsPerMinute: 60, orgRequests: { limit: 600, windowSeconds: HOUR } },
97
140
  maxWriteRowsPerRequest: 50_000,
98
141
  },
99
142
  web: {
100
- tenantRead: { requestsPerMinute: 300, orgUnits: { limit: 20_000_000, windowSeconds: DAY } },
101
- tenantWrite: { requestsPerMinute: 180, orgUnits: { limit: 10_000_000, windowSeconds: DAY } },
102
- columnRun: { requestsPerMinute: 120, orgUnits: { limit: 500_000, windowSeconds: DAY } },
103
- import: { requestsPerMinute: 30, orgRequests: { limit: 500, windowSeconds: HOUR } },
143
+ tenantRead: { requestsPerMinute: 1_500, orgUnits: { limit: 100_000_000, windowSeconds: DAY } },
144
+ tenantWrite: { requestsPerMinute: 900, orgUnits: { limit: 50_000_000, windowSeconds: DAY } },
145
+ columnRun: { requestsPerMinute: 600, orgUnits: { limit: 2_500_000, windowSeconds: DAY } },
146
+ import: { requestsPerMinute: 150, orgRequests: { limit: 2_500, windowSeconds: HOUR } },
104
147
  },
105
148
  import: {
106
149
  maxRowsPerFile: TABLE_IMPORT_ROW_LIMIT,
107
- maxFileBytes: 250 * MIB,
150
+ maxFileBytes: importMaxFileBytesForTier("starter"),
108
151
  maxDirectCsvBodyBytes: 10_000_000,
109
152
  },
110
153
  agents: {
@@ -114,30 +157,31 @@ export const PLAN_LIMITS = {
114
157
  maxUpstreamCostUsdByok: 250,
115
158
  },
116
159
  copilot: { maxSessionBudgetCredits: 500, defaultPerTurnCreditCeiling: 150 },
117
- signals: { maxEvents: 200, maxWindowDays: 90 },
118
- tableActions: { maxActionsPerRun: 25 },
160
+ signals: DERIVED_PLAN_LIMIT_TARGETS.starter.signals,
161
+ tableActions: { maxActionsPerRun: DERIVED_PLAN_LIMIT_TARGETS.starter.maxActionsPerRun },
162
+ workflows: { cronMinIntervalMinutes: 1 },
119
163
  },
120
164
  pro: {
121
165
  cli: {
122
- global: { keyRequestsPerMinute: 240, orgRequestsPerMinute: 1_200 },
123
- tenantRead: { requestsPerMinute: 240, orgUnits: { limit: 50_000_000, windowSeconds: DAY } },
124
- tenantWrite: { requestsPerMinute: 240, orgUnits: { limit: 10_000_000, windowSeconds: DAY } },
125
- bulkImport: { requestsPerMinute: 60, orgRequests: { limit: 1_000, windowSeconds: HOUR } },
126
- aiLive: { requestsPerMinute: 120, orgRequests: { limit: 12_000, windowSeconds: HOUR } },
127
- toolLive: { requestsPerMinute: 120, orgRequests: { limit: 12_000, windowSeconds: HOUR } },
128
- toolDryRun: { requestsPerMinute: 120, orgRequests: { limit: 2_000, windowSeconds: HOUR } },
166
+ global: { keyRequestsPerMinute: 6_000, orgRequestsPerMinute: 6_000 },
167
+ tenantRead: { requestsPerMinute: 1_200, orgUnits: { limit: 250_000_000, windowSeconds: DAY } },
168
+ tenantWrite: { requestsPerMinute: 1_200, orgUnits: { limit: 50_000_000, windowSeconds: DAY } },
169
+ bulkImport: { requestsPerMinute: 300, orgRequests: { limit: 5_000, windowSeconds: HOUR } },
170
+ aiLive: { requestsPerMinute: 600, orgRequests: { limit: 60_000, windowSeconds: HOUR } },
171
+ toolLive: { requestsPerMinute: 600, orgRequests: { limit: 60_000, windowSeconds: HOUR } },
172
+ toolDryRun: { requestsPerMinute: 600, orgRequests: { limit: 10_000, windowSeconds: HOUR } },
129
173
  tenantSchema: { requestsPerMinute: 60, orgRequests: { limit: 600, windowSeconds: HOUR } },
130
174
  maxWriteRowsPerRequest: 50_000,
131
175
  },
132
176
  web: {
133
- tenantRead: { requestsPerMinute: 600, orgUnits: { limit: 40_000_000, windowSeconds: DAY } },
134
- tenantWrite: { requestsPerMinute: 360, orgUnits: { limit: 20_000_000, windowSeconds: DAY } },
135
- columnRun: { requestsPerMinute: 240, orgUnits: { limit: 1_000_000, windowSeconds: DAY } },
136
- import: { requestsPerMinute: 60, orgRequests: { limit: 1_000, windowSeconds: HOUR } },
177
+ tenantRead: { requestsPerMinute: 3_000, orgUnits: { limit: 200_000_000, windowSeconds: DAY } },
178
+ tenantWrite: { requestsPerMinute: 1_800, orgUnits: { limit: 100_000_000, windowSeconds: DAY } },
179
+ columnRun: { requestsPerMinute: 1_200, orgUnits: { limit: 5_000_000, windowSeconds: DAY } },
180
+ import: { requestsPerMinute: 300, orgRequests: { limit: 5_000, windowSeconds: HOUR } },
137
181
  },
138
182
  import: {
139
183
  maxRowsPerFile: TABLE_IMPORT_ROW_LIMIT,
140
- maxFileBytes: 250 * MIB,
184
+ maxFileBytes: importMaxFileBytesForTier("pro"),
141
185
  maxDirectCsvBodyBytes: 10_000_000,
142
186
  },
143
187
  agents: {
@@ -147,30 +191,31 @@ export const PLAN_LIMITS = {
147
191
  maxUpstreamCostUsdByok: 250,
148
192
  },
149
193
  copilot: { maxSessionBudgetCredits: 1_000, defaultPerTurnCreditCeiling: 250 },
150
- signals: { maxEvents: 200, maxWindowDays: 180 },
151
- tableActions: { maxActionsPerRun: 25 },
194
+ signals: DERIVED_PLAN_LIMIT_TARGETS.pro.signals,
195
+ tableActions: { maxActionsPerRun: DERIVED_PLAN_LIMIT_TARGETS.pro.maxActionsPerRun },
196
+ workflows: { cronMinIntervalMinutes: 1 },
152
197
  },
153
198
  team: {
154
199
  cli: {
155
- global: { keyRequestsPerMinute: 240, orgRequestsPerMinute: 2_400 },
156
- tenantRead: { requestsPerMinute: 480, orgUnits: { limit: 100_000_000, windowSeconds: DAY } },
157
- tenantWrite: { requestsPerMinute: 480, orgUnits: { limit: 25_000_000, windowSeconds: DAY } },
158
- bulkImport: { requestsPerMinute: 120, orgRequests: { limit: 2_000, windowSeconds: HOUR } },
159
- aiLive: { requestsPerMinute: 240, orgRequests: { limit: 30_000, windowSeconds: HOUR } },
160
- toolLive: { requestsPerMinute: 240, orgRequests: { limit: 30_000, windowSeconds: HOUR } },
161
- toolDryRun: { requestsPerMinute: 120, orgRequests: { limit: 2_000, windowSeconds: HOUR } },
200
+ global: { keyRequestsPerMinute: 12_000, orgRequestsPerMinute: 12_000 },
201
+ tenantRead: { requestsPerMinute: 2_400, orgUnits: { limit: 500_000_000, windowSeconds: DAY } },
202
+ tenantWrite: { requestsPerMinute: 2_400, orgUnits: { limit: 125_000_000, windowSeconds: DAY } },
203
+ bulkImport: { requestsPerMinute: 600, orgRequests: { limit: 10_000, windowSeconds: HOUR } },
204
+ aiLive: { requestsPerMinute: 1_200, orgRequests: { limit: 150_000, windowSeconds: HOUR } },
205
+ toolLive: { requestsPerMinute: 1_200, orgRequests: { limit: 150_000, windowSeconds: HOUR } },
206
+ toolDryRun: { requestsPerMinute: 600, orgRequests: { limit: 10_000, windowSeconds: HOUR } },
162
207
  tenantSchema: { requestsPerMinute: 60, orgRequests: { limit: 600, windowSeconds: HOUR } },
163
208
  maxWriteRowsPerRequest: 50_000,
164
209
  },
165
210
  web: {
166
- tenantRead: { requestsPerMinute: 1_200, orgUnits: { limit: 100_000_000, windowSeconds: DAY } },
167
- tenantWrite: { requestsPerMinute: 720, orgUnits: { limit: 50_000_000, windowSeconds: DAY } },
168
- columnRun: { requestsPerMinute: 480, orgUnits: { limit: 2_500_000, windowSeconds: DAY } },
169
- import: { requestsPerMinute: 120, orgRequests: { limit: 2_000, windowSeconds: HOUR } },
211
+ tenantRead: { requestsPerMinute: 6_000, orgUnits: { limit: 500_000_000, windowSeconds: DAY } },
212
+ tenantWrite: { requestsPerMinute: 3_600, orgUnits: { limit: 250_000_000, windowSeconds: DAY } },
213
+ columnRun: { requestsPerMinute: 2_400, orgUnits: { limit: 12_500_000, windowSeconds: DAY } },
214
+ import: { requestsPerMinute: 600, orgRequests: { limit: 10_000, windowSeconds: HOUR } },
170
215
  },
171
216
  import: {
172
217
  maxRowsPerFile: TABLE_IMPORT_ROW_LIMIT,
173
- maxFileBytes: 250 * MIB,
218
+ maxFileBytes: importMaxFileBytesForTier("team"),
174
219
  maxDirectCsvBodyBytes: 10_000_000,
175
220
  },
176
221
  agents: {
@@ -180,30 +225,31 @@ export const PLAN_LIMITS = {
180
225
  maxUpstreamCostUsdByok: 250,
181
226
  },
182
227
  copilot: { maxSessionBudgetCredits: 2_000, defaultPerTurnCreditCeiling: 500 },
183
- signals: { maxEvents: 200, maxWindowDays: 365 },
184
- tableActions: { maxActionsPerRun: 25 },
228
+ signals: DERIVED_PLAN_LIMIT_TARGETS.team.signals,
229
+ tableActions: { maxActionsPerRun: DERIVED_PLAN_LIMIT_TARGETS.team.maxActionsPerRun },
230
+ workflows: { cronMinIntervalMinutes: 1 },
185
231
  },
186
232
  scale: {
187
233
  cli: {
188
- global: { keyRequestsPerMinute: 240, orgRequestsPerMinute: 6_000 },
189
- tenantRead: { requestsPerMinute: 1_000, orgUnits: { limit: 500_000_000, windowSeconds: DAY } },
190
- tenantWrite: { requestsPerMinute: 1_000, orgUnits: { limit: 100_000_000, windowSeconds: DAY } },
191
- bulkImport: { requestsPerMinute: 240, orgRequests: { limit: 5_000, windowSeconds: HOUR } },
192
- aiLive: { requestsPerMinute: 600, orgRequests: { limit: 100_000, windowSeconds: HOUR } },
193
- toolLive: { requestsPerMinute: 600, orgRequests: { limit: 100_000, windowSeconds: HOUR } },
194
- toolDryRun: { requestsPerMinute: 120, orgRequests: { limit: 2_000, windowSeconds: HOUR } },
234
+ global: { keyRequestsPerMinute: 30_000, orgRequestsPerMinute: 30_000 },
235
+ tenantRead: { requestsPerMinute: 5_000, orgUnits: { limit: 2_500_000_000, windowSeconds: DAY } },
236
+ tenantWrite: { requestsPerMinute: 5_000, orgUnits: { limit: 500_000_000, windowSeconds: DAY } },
237
+ bulkImport: { requestsPerMinute: 1_200, orgRequests: { limit: 25_000, windowSeconds: HOUR } },
238
+ aiLive: { requestsPerMinute: 3_000, orgRequests: { limit: 500_000, windowSeconds: HOUR } },
239
+ toolLive: { requestsPerMinute: 3_000, orgRequests: { limit: 500_000, windowSeconds: HOUR } },
240
+ toolDryRun: { requestsPerMinute: 600, orgRequests: { limit: 10_000, windowSeconds: HOUR } },
195
241
  tenantSchema: { requestsPerMinute: 60, orgRequests: { limit: 600, windowSeconds: HOUR } },
196
242
  maxWriteRowsPerRequest: 50_000,
197
243
  },
198
244
  web: {
199
- tenantRead: { requestsPerMinute: 2_000, orgUnits: { limit: 400_000_000, windowSeconds: DAY } },
200
- tenantWrite: { requestsPerMinute: 1_500, orgUnits: { limit: 200_000_000, windowSeconds: DAY } },
201
- columnRun: { requestsPerMinute: 1_000, orgUnits: { limit: 10_000_000, windowSeconds: DAY } },
202
- import: { requestsPerMinute: 240, orgRequests: { limit: 5_000, windowSeconds: HOUR } },
245
+ tenantRead: { requestsPerMinute: 10_000, orgUnits: { limit: 2_000_000_000, windowSeconds: DAY } },
246
+ tenantWrite: { requestsPerMinute: 7_500, orgUnits: { limit: 1_000_000_000, windowSeconds: DAY } },
247
+ columnRun: { requestsPerMinute: 5_000, orgUnits: { limit: 50_000_000, windowSeconds: DAY } },
248
+ import: { requestsPerMinute: 1_200, orgRequests: { limit: 25_000, windowSeconds: HOUR } },
203
249
  },
204
250
  import: {
205
251
  maxRowsPerFile: TABLE_IMPORT_ROW_LIMIT,
206
- maxFileBytes: 250 * MIB,
252
+ maxFileBytes: importMaxFileBytesForTier("scale"),
207
253
  maxDirectCsvBodyBytes: 10_000_000,
208
254
  },
209
255
  agents: {
@@ -213,10 +259,32 @@ export const PLAN_LIMITS = {
213
259
  maxUpstreamCostUsdByok: 250,
214
260
  },
215
261
  copilot: { maxSessionBudgetCredits: 5_000, defaultPerTurnCreditCeiling: 1_000 },
216
- signals: { maxEvents: 200, maxWindowDays: 365 },
217
- tableActions: { maxActionsPerRun: 25 },
262
+ signals: DERIVED_PLAN_LIMIT_TARGETS.scale.signals,
263
+ tableActions: { maxActionsPerRun: DERIVED_PLAN_LIMIT_TARGETS.scale.maxActionsPerRun },
264
+ workflows: { cronMinIntervalMinutes: 1 },
218
265
  },
219
266
  };
267
+ /**
268
+ * The fastest cron cadence, in minutes, a rung may schedule right now. The free
269
+ * rung's cut from every minute to every 15 (decision L4.2) is a tightening, so
270
+ * it resolves through the 2026-09 repricing switch: every minute until the
271
+ * effective instant, `PLAN_LIMITS.free.workflows.cronMinIntervalMinutes` from it.
272
+ */
273
+ export function resolveCronMinIntervalMinutes(tier, options = {}) {
274
+ const scheduled = findRepricedValue(`workflows.${tier}.cron_min_interval_minutes`);
275
+ if (scheduled && typeof scheduled.before === "number" && typeof scheduled.after === "number") {
276
+ return repricedValue({ before: scheduled.before, after: scheduled.after }, options);
277
+ }
278
+ return PLAN_LIMITS[tier].workflows.cronMinIntervalMinutes;
279
+ }
280
+ /**
281
+ * The largest minimum cron interval any rung enforces right now. A schedule
282
+ * whose fires are never closer than this fits every plan, so a caller can skip
283
+ * the plan lookup for it.
284
+ */
285
+ export function highestCronMinIntervalMinutes(options = {}) {
286
+ return Math.max(...LIMITS_TIER_ORDER.map((tier) => resolveCronMinIntervalMinutes(tier, options)));
287
+ }
220
288
  /**
221
289
  * Which enforcement rung a rung of the six-rung Oxygen plan lands on.
222
290
  *
@@ -260,3 +328,126 @@ export function limitsTierForPlanTier(tier) {
260
328
  export function planLimitsForTier(tier) {
261
329
  return PLAN_LIMITS[tier];
262
330
  }
331
+ /**
332
+ * The default credit ceilings of one Agent run in force on a rung right now.
333
+ *
334
+ * `PLAN_LIMITS[tier].agents` holds the values each rung read before the 2026-09
335
+ * repricing. The team and scale rungs drop to 2,500 total / 1,250 inference
336
+ * (decision L5.4, inference half PROPOSED P-56), and a cut waits for the
337
+ * effective-date switch (`agents.<tier>.*` in `REPRICING_2026_09_SCHEDULE`).
338
+ * Every Agent budget check reads this, never the table, so the cut lands
339
+ * everywhere at the same instant.
340
+ */
341
+ export function resolvePlanAgentCreditLimits(tier, options = {}) {
342
+ const agents = PLAN_LIMITS[tier].agents;
343
+ const resolve = (field, fallback) => {
344
+ const scheduled = findRepricedValue(`agents.${tier}.${field}`);
345
+ return scheduled && typeof scheduled.before === "number" && typeof scheduled.after === "number"
346
+ ? repricedValue({ before: scheduled.before, after: scheduled.after }, options)
347
+ : fallback;
348
+ };
349
+ return {
350
+ maxTotalCredits: resolve("max_total_credits", agents.maxTotalCredits),
351
+ maxInferenceCredits: resolve("max_inference_credits", agents.maxInferenceCredits),
352
+ };
353
+ }
354
+ /**
355
+ * The platform's hard backstop on one Agent run's approved credit ceiling
356
+ * (decision F.4, ratified 2026-09-27).
357
+ *
358
+ * The plan's Agent run ceiling is a DEFAULT: a run or trigger whose approved
359
+ * `max_credits` fits under it needs only the usual approval. Going above it
360
+ * takes a second, explicit per-run (or per-trigger) approval, and even that
361
+ * approval stops here. 25,000 credits ($250) is the largest standing
362
+ * per-delivery default any plan has carried (the pre-2026-09 scale trigger
363
+ * default), so an approved Agent run never outspends one delivery of the
364
+ * largest plan's own automation. The other backstops stay in force whatever is
365
+ * approved: the plan's upstream-USD clamp, the iteration, tool-call and
366
+ * active-minute bounds, the balance check on every reservation and the org
367
+ * daily spend guard.
368
+ */
369
+ export const AGENT_RUN_APPROVED_CEILING_MAX_CREDITS = 25_000;
370
+ /**
371
+ * The default Agent run ceilings for an org: its plan rung's, unless an
372
+ * enterprise contract names its own default (decision F.4). The contract value
373
+ * replaces the rung's total and model calls take half of it, as on every paid
374
+ * rung. A contract value is clamped to the platform backstop; an unusable one
375
+ * (absent, non-numeric, not positive) is ignored.
376
+ */
377
+ export function resolveAgentRunDefaultCeilings(input) {
378
+ const contract = input.enterpriseDefaultCredits;
379
+ if (typeof contract === "number" && Number.isFinite(contract) && contract >= 1) {
380
+ const total = Math.min(Math.floor(contract), AGENT_RUN_APPROVED_CEILING_MAX_CREDITS);
381
+ return {
382
+ maxTotalCredits: total,
383
+ maxInferenceCredits: Math.max(1, Math.floor(total / 2)),
384
+ source: "enterprise_contract",
385
+ };
386
+ }
387
+ return { ...resolvePlanAgentCreditLimits(input.tier, input.options), source: "plan" };
388
+ }
389
+ /** `details.approval_kind` on the refusal below, so every surface can render its own approval card. */
390
+ export const AGENT_CEILING_ABOVE_DEFAULT_APPROVAL_KIND = "agent_ceiling_above_default";
391
+ /**
392
+ * Check one run's (or one trigger's per-delivery) approved ceiling against the
393
+ * org's default before anything is created.
394
+ *
395
+ * - At or under the default: nothing more to approve.
396
+ * - Above it: refused with `approval_required` unless the caller also passed the
397
+ * explicit above-default approval. The details carry the numbers and the flag
398
+ * each surface re-sends, so the refusal is the approval card.
399
+ * - Above the platform backstop: refused outright; no approval raises it.
400
+ *
401
+ * Returns whether the approved ceiling is above the default; the caller persists
402
+ * that as the run's or trigger's above-default approval.
403
+ */
404
+ export function assertAgentRunCeilingApproval(input) {
405
+ const defaultMax = input.defaults.maxTotalCredits;
406
+ const subject = input.scope === "trigger" ? "each delivery of this trigger" : "this run";
407
+ if (input.maxCredits > AGENT_RUN_APPROVED_CEILING_MAX_CREDITS) {
408
+ throw new OxygenError("invalid_request", `max_credits ${input.maxCredits} is above the platform maximum of ${AGENT_RUN_APPROVED_CEILING_MAX_CREDITS} credits for ${subject}.`, {
409
+ exitCode: 2,
410
+ details: {
411
+ requested_max_credits: input.maxCredits,
412
+ default_max_credits: defaultMax,
413
+ platform_max_credits: AGENT_RUN_APPROVED_CEILING_MAX_CREDITS,
414
+ },
415
+ });
416
+ }
417
+ if (input.maxCredits <= defaultMax)
418
+ return { aboveDefault: false };
419
+ if (input.approveAboveDefault)
420
+ return { aboveDefault: true };
421
+ throw new OxygenError("approval_required", `max_credits ${input.maxCredits} is above your default Agent run ceiling of ${defaultMax} credits. Approve the higher ceiling for ${subject} with approve_above_default=true, or lower max_credits to ${defaultMax} or less.`, {
422
+ exitCode: 7,
423
+ details: {
424
+ approval_kind: AGENT_CEILING_ABOVE_DEFAULT_APPROVAL_KIND,
425
+ scope: input.scope,
426
+ requested_max_credits: input.maxCredits,
427
+ default_max_credits: defaultMax,
428
+ default_source: input.defaults.source,
429
+ platform_max_credits: AGENT_RUN_APPROVED_CEILING_MAX_CREDITS,
430
+ next_step: `Re-send with approve_above_default=true (CLI: --approve-above-default) to approve up to ${input.maxCredits} credits for ${subject}.`,
431
+ },
432
+ });
433
+ }
434
+ /**
435
+ * The ceilings one run is held to, for the worker and every budget check that
436
+ * reads a run's authority. A run with a recorded above-default approval is
437
+ * bounded by its own approved `max_credits` (applied by the caller as a user
438
+ * limit) under the platform backstop. Every other run is bounded by the default,
439
+ * so a ceiling approved without the second approval — including every one
440
+ * approved before this rule — still binds at the default exactly as before.
441
+ */
442
+ export function agentRunEnforcedCeilings(input) {
443
+ if (!input.aboveDefaultApproved) {
444
+ return {
445
+ maxTotalCredits: input.defaults.maxTotalCredits,
446
+ maxInferenceCredits: input.defaults.maxInferenceCredits,
447
+ };
448
+ }
449
+ return {
450
+ maxTotalCredits: AGENT_RUN_APPROVED_CEILING_MAX_CREDITS,
451
+ maxInferenceCredits: AGENT_RUN_APPROVED_CEILING_MAX_CREDITS,
452
+ };
453
+ }