@oxygen-agent/cli 1.1014.136 → 1.1014.619

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 (293) hide show
  1. package/README.md +1 -1
  2. package/dist/admin-primary-providers-render.js +27 -19
  3. package/dist/cli-output.d.ts +12 -0
  4. package/dist/cli-output.js +395 -0
  5. package/dist/cli-paths.d.ts +1 -0
  6. package/dist/cli-paths.js +3 -0
  7. package/dist/cli-profile-context.d.ts +3 -0
  8. package/dist/cli-profile-context.js +13 -0
  9. package/dist/command-manifest.d.ts +15 -0
  10. package/dist/command-manifest.js +23 -1
  11. package/dist/command-support.d.ts +438 -0
  12. package/dist/command-support.js +1610 -0
  13. package/dist/commands/action-column.d.ts +3 -0
  14. package/dist/commands/action-column.js +43 -0
  15. package/dist/commands/activation.d.ts +3 -0
  16. package/dist/commands/activation.js +24 -0
  17. package/dist/commands/admin.d.ts +3 -0
  18. package/dist/commands/admin.js +347 -0
  19. package/dist/commands/ads.d.ts +3 -0
  20. package/dist/commands/ads.js +62 -0
  21. package/dist/commands/agent.d.ts +3 -0
  22. package/dist/commands/agent.js +274 -0
  23. package/dist/commands/api-keys.d.ts +3 -0
  24. package/dist/commands/api-keys.js +77 -0
  25. package/dist/commands/approvals.d.ts +3 -0
  26. package/dist/commands/approvals.js +518 -0
  27. package/dist/commands/auth.d.ts +3 -0
  28. package/dist/commands/auth.js +230 -0
  29. package/dist/commands/billing.d.ts +3 -0
  30. package/dist/commands/billing.js +357 -0
  31. package/dist/commands/blueprints.d.ts +3 -0
  32. package/dist/commands/blueprints.js +312 -0
  33. package/dist/commands/budget.d.ts +3 -0
  34. package/dist/commands/budget.js +55 -0
  35. package/dist/commands/callables.d.ts +3 -0
  36. package/dist/commands/callables.js +111 -0
  37. package/dist/commands/capabilities.d.ts +3 -0
  38. package/dist/commands/capabilities.js +44 -0
  39. package/dist/commands/cells.d.ts +3 -0
  40. package/dist/commands/cells.js +94 -0
  41. package/dist/commands/columns.d.ts +3 -0
  42. package/dist/commands/columns.js +1628 -0
  43. package/dist/commands/commands.d.ts +3 -0
  44. package/dist/commands/commands.js +45 -0
  45. package/dist/commands/comments.d.ts +3 -0
  46. package/dist/commands/comments.js +188 -0
  47. package/dist/commands/companies.d.ts +3 -0
  48. package/dist/commands/companies.js +312 -0
  49. package/dist/commands/connections.d.ts +3 -0
  50. package/dist/commands/connections.js +39 -0
  51. package/dist/commands/context.d.ts +3 -0
  52. package/dist/commands/context.js +170 -0
  53. package/dist/commands/copilot.d.ts +3 -0
  54. package/dist/commands/copilot.js +565 -0
  55. package/dist/commands/crm.d.ts +3 -0
  56. package/dist/commands/crm.js +1672 -0
  57. package/dist/commands/custom-integrations.d.ts +3 -0
  58. package/dist/commands/custom-integrations.js +140 -0
  59. package/dist/commands/dashboards.d.ts +3 -0
  60. package/dist/commands/dashboards.js +240 -0
  61. package/dist/commands/db.d.ts +3 -0
  62. package/dist/commands/db.js +190 -0
  63. package/dist/commands/deliverability.d.ts +3 -0
  64. package/dist/commands/deliverability.js +90 -0
  65. package/dist/commands/directory.d.ts +3 -0
  66. package/dist/commands/directory.js +101 -0
  67. package/dist/commands/domains.d.ts +3 -0
  68. package/dist/commands/domains.js +604 -0
  69. package/dist/commands/egress.d.ts +3 -0
  70. package/dist/commands/egress.js +130 -0
  71. package/dist/commands/email.d.ts +3 -0
  72. package/dist/commands/email.js +49 -0
  73. package/dist/commands/engagement.d.ts +3 -0
  74. package/dist/commands/engagement.js +189 -0
  75. package/dist/commands/enrich-column.d.ts +3 -0
  76. package/dist/commands/enrich-column.js +149 -0
  77. package/dist/commands/enrichment.d.ts +3 -0
  78. package/dist/commands/enrichment.js +49 -0
  79. package/dist/commands/feedback.d.ts +3 -0
  80. package/dist/commands/feedback.js +96 -0
  81. package/dist/commands/feeds.d.ts +3 -0
  82. package/dist/commands/feeds.js +486 -0
  83. package/dist/commands/find.d.ts +3 -0
  84. package/dist/commands/find.js +159 -0
  85. package/dist/commands/followers.d.ts +3 -0
  86. package/dist/commands/followers.js +39 -0
  87. package/dist/commands/formulas.d.ts +3 -0
  88. package/dist/commands/formulas.js +43 -0
  89. package/dist/commands/home.d.ts +3 -0
  90. package/dist/commands/home.js +22 -0
  91. package/dist/commands/inbox.d.ts +3 -0
  92. package/dist/commands/inbox.js +779 -0
  93. package/dist/commands/integrations.d.ts +3 -0
  94. package/dist/commands/integrations.js +290 -0
  95. package/dist/commands/knowledge.d.ts +3 -0
  96. package/dist/commands/knowledge.js +874 -0
  97. package/dist/commands/lead-sourcing.d.ts +3 -0
  98. package/dist/commands/lead-sourcing.js +52 -0
  99. package/dist/commands/limits.d.ts +3 -0
  100. package/dist/commands/limits.js +54 -0
  101. package/dist/commands/linkedin.d.ts +3 -0
  102. package/dist/commands/linkedin.js +275 -0
  103. package/dist/commands/login.d.ts +3 -0
  104. package/dist/commands/login.js +347 -0
  105. package/dist/commands/logout.d.ts +3 -0
  106. package/dist/commands/logout.js +12 -0
  107. package/dist/commands/mailboxes.d.ts +3 -0
  108. package/dist/commands/mailboxes.js +1290 -0
  109. package/dist/commands/managed-inboxes.d.ts +3 -0
  110. package/dist/commands/managed-inboxes.js +361 -0
  111. package/dist/commands/messages.d.ts +3 -0
  112. package/dist/commands/messages.js +78 -0
  113. package/dist/commands/notetaker.d.ts +3 -0
  114. package/dist/commands/notetaker.js +79 -0
  115. package/dist/commands/observability.d.ts +3 -0
  116. package/dist/commands/observability.js +125 -0
  117. package/dist/commands/orgs.d.ts +3 -0
  118. package/dist/commands/orgs.js +237 -0
  119. package/dist/commands/people.d.ts +3 -0
  120. package/dist/commands/people.js +353 -0
  121. package/dist/commands/posts.d.ts +3 -0
  122. package/dist/commands/posts.js +139 -0
  123. package/dist/commands/profiles.d.ts +3 -0
  124. package/dist/commands/profiles.js +250 -0
  125. package/dist/commands/projects.d.ts +3 -0
  126. package/dist/commands/projects.js +94 -0
  127. package/dist/commands/prompts.d.ts +3 -0
  128. package/dist/commands/prompts.js +84 -0
  129. package/dist/commands/publishing.d.ts +3 -0
  130. package/dist/commands/publishing.js +1419 -0
  131. package/dist/commands/recipes.d.ts +3 -0
  132. package/dist/commands/recipes.js +120 -0
  133. package/dist/commands/reviews.d.ts +3 -0
  134. package/dist/commands/reviews.js +75 -0
  135. package/dist/commands/rows.d.ts +3 -0
  136. package/dist/commands/rows.js +27 -0
  137. package/dist/commands/runs.d.ts +3 -0
  138. package/dist/commands/runs.js +37 -0
  139. package/dist/commands/search.d.ts +3 -0
  140. package/dist/commands/search.js +182 -0
  141. package/dist/commands/senders.d.ts +3 -0
  142. package/dist/commands/senders.js +393 -0
  143. package/dist/commands/sequences.d.ts +3 -0
  144. package/dist/commands/sequences.js +2322 -0
  145. package/dist/commands/session.d.ts +3 -0
  146. package/dist/commands/session.js +109 -0
  147. package/dist/commands/signals.d.ts +3 -0
  148. package/dist/commands/signals.js +289 -0
  149. package/dist/commands/signup-leads.d.ts +3 -0
  150. package/dist/commands/signup-leads.js +46 -0
  151. package/dist/commands/skills.d.ts +3 -0
  152. package/dist/commands/skills.js +50 -0
  153. package/dist/commands/sourcing.d.ts +3 -0
  154. package/dist/commands/sourcing.js +27 -0
  155. package/dist/commands/status.d.ts +3 -0
  156. package/dist/commands/status.js +51 -0
  157. package/dist/commands/supabase.d.ts +3 -0
  158. package/dist/commands/supabase.js +128 -0
  159. package/dist/commands/support.d.ts +3 -0
  160. package/dist/commands/support.js +651 -0
  161. package/dist/commands/suppressions.d.ts +3 -0
  162. package/dist/commands/suppressions.js +577 -0
  163. package/dist/commands/table-ingestions.d.ts +3 -0
  164. package/dist/commands/table-ingestions.js +145 -0
  165. package/dist/commands/table-runs.d.ts +3 -0
  166. package/dist/commands/table-runs.js +277 -0
  167. package/dist/commands/tables.d.ts +3 -0
  168. package/dist/commands/tables.js +3505 -0
  169. package/dist/commands/tags.d.ts +3 -0
  170. package/dist/commands/tags.js +147 -0
  171. package/dist/commands/tools.d.ts +3 -0
  172. package/dist/commands/tools.js +246 -0
  173. package/dist/commands/update.d.ts +3 -0
  174. package/dist/commands/update.js +106 -0
  175. package/dist/commands/verify.d.ts +3 -0
  176. package/dist/commands/verify.js +27 -0
  177. package/dist/commands/viewers.d.ts +3 -0
  178. package/dist/commands/viewers.js +72 -0
  179. package/dist/commands/voice.d.ts +3 -0
  180. package/dist/commands/voice.js +351 -0
  181. package/dist/commands/whatsapp.d.ts +3 -0
  182. package/dist/commands/whatsapp.js +226 -0
  183. package/dist/commands/whoami.d.ts +3 -0
  184. package/dist/commands/whoami.js +110 -0
  185. package/dist/commands/worker.d.ts +3 -0
  186. package/dist/commands/worker.js +60 -0
  187. package/dist/commands/workflows.d.ts +3 -0
  188. package/dist/commands/workflows.js +1752 -0
  189. package/dist/commands/workspace.d.ts +3 -0
  190. package/dist/commands/workspace.js +94 -0
  191. package/dist/compact-list-flags.d.ts +7 -0
  192. package/dist/compact-list-flags.js +12 -0
  193. package/dist/crm-support-flags.js +14 -5
  194. package/dist/customer-operation-commands.js +8 -6
  195. package/dist/diagnostic-uploader.d.ts +3 -1
  196. package/dist/diagnostic-uploader.js +29 -8
  197. package/dist/help.d.ts +5 -0
  198. package/dist/help.js +22 -0
  199. package/dist/index.d.ts +98 -1
  200. package/dist/index.js +331 -30719
  201. package/dist/local-module-loader.d.ts +4 -0
  202. package/dist/local-module-loader.js +156 -0
  203. package/dist/visual-commands.js +9 -5
  204. package/node_modules/@oxygen/cli-ugc/dist/commands.js +2 -2
  205. package/node_modules/@oxygen/shared/dist/agent-session-schedule.d.ts +36 -0
  206. package/node_modules/@oxygen/shared/dist/agent-session-schedule.js +69 -0
  207. package/node_modules/@oxygen/shared/dist/agent-sessions.d.ts +14 -5
  208. package/node_modules/@oxygen/shared/dist/agent-sessions.js +14 -3
  209. package/node_modules/@oxygen/shared/dist/capability-discovery.js +175 -14
  210. package/node_modules/@oxygen/shared/dist/compact-list-result.d.ts +6 -0
  211. package/node_modules/@oxygen/shared/dist/compact-list-result.js +27 -0
  212. package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.d.ts +12 -1
  213. package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.js +5 -2
  214. package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +4 -2
  215. package/node_modules/@oxygen/shared/dist/contracts/compact-list-reads.d.ts +57 -0
  216. package/node_modules/@oxygen/shared/dist/contracts/compact-list-reads.js +23 -0
  217. package/node_modules/@oxygen/shared/dist/contracts/crm-support.d.ts +52 -0
  218. package/node_modules/@oxygen/shared/dist/contracts/crm-support.js +43 -2
  219. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +20 -16
  220. package/node_modules/@oxygen/shared/dist/contracts/knowledge-content-reads.js +1 -1
  221. package/node_modules/@oxygen/shared/dist/contracts/knowledge-proposal-reads.d.ts +49 -0
  222. package/node_modules/@oxygen/shared/dist/contracts/knowledge-proposal-reads.js +14 -0
  223. package/node_modules/@oxygen/shared/dist/contracts/knowledge-sync.js +1 -1
  224. package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +60 -7
  225. package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +9 -1
  226. package/node_modules/@oxygen/shared/dist/contracts/operations.js +13 -5
  227. package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.js +1 -1
  228. package/node_modules/@oxygen/shared/dist/contracts/teammates.d.ts +16 -0
  229. package/node_modules/@oxygen/shared/dist/contracts/teammates.js +115 -0
  230. package/node_modules/@oxygen/shared/dist/cost-estimate-view.js +21 -3
  231. package/node_modules/@oxygen/shared/dist/cost-estimate.d.ts +31 -1
  232. package/node_modules/@oxygen/shared/dist/cost-estimate.js +56 -15
  233. package/node_modules/@oxygen/shared/dist/diagnostic-credentials.d.ts +2 -0
  234. package/node_modules/@oxygen/shared/dist/diagnostic-credentials.js +2 -0
  235. package/node_modules/@oxygen/shared/dist/diagnostic-lock.js +71 -44
  236. package/node_modules/@oxygen/shared/dist/diagnostic-spool.d.ts +18 -0
  237. package/node_modules/@oxygen/shared/dist/diagnostic-spool.js +348 -0
  238. package/node_modules/@oxygen/shared/dist/diagnostic-storage-failure.d.ts +10 -0
  239. package/node_modules/@oxygen/shared/dist/diagnostic-storage-failure.js +42 -0
  240. package/node_modules/@oxygen/shared/dist/diagnostic-storage.d.ts +1 -1
  241. package/node_modules/@oxygen/shared/dist/diagnostic-storage.js +59 -213
  242. package/node_modules/@oxygen/shared/dist/diagnostics.js +3 -2
  243. package/node_modules/@oxygen/shared/dist/email-dsn.d.ts +16 -5
  244. package/node_modules/@oxygen/shared/dist/email-dsn.js +97 -1
  245. package/node_modules/@oxygen/shared/dist/emailguard-placement-probe.js +5 -0
  246. package/node_modules/@oxygen/shared/dist/enrichment-intents.js +4 -5
  247. package/node_modules/@oxygen/shared/dist/file-import.d.ts +8 -0
  248. package/node_modules/@oxygen/shared/dist/file-import.js +21 -2
  249. package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +30 -0
  250. package/node_modules/@oxygen/shared/dist/hosted-ai.js +20 -0
  251. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +6 -2
  252. package/node_modules/@oxygen/shared/dist/knowledge-seed-content.d.ts +17 -7
  253. package/node_modules/@oxygen/shared/dist/knowledge-seed-content.js +81 -17
  254. package/node_modules/@oxygen/shared/dist/langfuse.js +8 -4
  255. package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +1 -1
  256. package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +6 -3
  257. package/node_modules/@oxygen/shared/dist/mailbox-import.js +9 -9
  258. package/node_modules/@oxygen/shared/dist/mcp-server-info.d.ts +8 -0
  259. package/node_modules/@oxygen/shared/dist/mcp-server-info.js +10 -0
  260. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +10 -0
  261. package/node_modules/@oxygen/shared/dist/object-storage.js +14 -0
  262. package/node_modules/@oxygen/shared/dist/plan-limits-view.d.ts +5 -0
  263. package/node_modules/@oxygen/shared/dist/plan-limits-view.js +17 -1
  264. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
  265. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
  266. package/node_modules/@oxygen/shared/dist/provider-selection.d.ts +15 -0
  267. package/node_modules/@oxygen/shared/dist/provider-selection.js +3 -0
  268. package/node_modules/@oxygen/shared/dist/repricing.d.ts +1 -1
  269. package/node_modules/@oxygen/shared/dist/repricing.js +2 -2
  270. package/node_modules/@oxygen/shared/dist/schedule-label.d.ts +6 -0
  271. package/node_modules/@oxygen/shared/dist/schedule-label.js +12 -0
  272. package/node_modules/@oxygen/shared/dist/sequence-failures.d.ts +1 -0
  273. package/node_modules/@oxygen/shared/dist/sequence-failures.js +20 -6
  274. package/node_modules/@oxygen/shared/dist/teammate-groups.d.ts +55 -0
  275. package/node_modules/@oxygen/shared/dist/teammate-groups.js +74 -0
  276. package/node_modules/@oxygen/shared/dist/teammate-messages.d.ts +52 -0
  277. package/node_modules/@oxygen/shared/dist/teammate-messages.js +88 -0
  278. package/node_modules/@oxygen/shared/dist/teammate-notes.d.ts +58 -0
  279. package/node_modules/@oxygen/shared/dist/teammate-notes.js +73 -0
  280. package/node_modules/@oxygen/shared/dist/teammate-routines.d.ts +95 -0
  281. package/node_modules/@oxygen/shared/dist/teammate-routines.js +266 -0
  282. package/node_modules/@oxygen/shared/dist/teammate-tasks.d.ts +56 -0
  283. package/node_modules/@oxygen/shared/dist/teammate-tasks.js +114 -0
  284. package/node_modules/@oxygen/shared/dist/teammates.d.ts +78 -0
  285. package/node_modules/@oxygen/shared/dist/teammates.js +140 -0
  286. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +120 -0
  287. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  288. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  289. package/node_modules/@oxygen/shared/dist/visual-render.d.ts +9 -1
  290. package/node_modules/@oxygen/shared/dist/visual-render.js +12 -5
  291. package/node_modules/@oxygen/shared/dist/warmup-metrics.d.ts +5 -0
  292. package/node_modules/@oxygen/shared/dist/warmup-metrics.js +44 -0
  293. package/package.json +1 -1
@@ -151,14 +151,18 @@ export type KnowledgeBootstrapMarker = {
151
151
  completed_at: string | null;
152
152
  /** `ox_runs.runs` id (source_type `system`) carrying the observable run. */
153
153
  run_id: string | null;
154
- /** Managed credits actually spent, across every phase. Never exceeds the cap. */
154
+ /** Managed accounting total across every phase. When effect_outcome is unknown,
155
+ * this is a conservative bound rather than measured spend. Never exceeds the cap. */
155
156
  credits_used: number;
156
157
  /**
157
158
  * `unknown` when the pass stopped with no record of which provider calls it made: a
158
159
  * legacy attempt closed as `interrupted_without_checkpoint`. `credits_used` is then
159
- * not a measurement, and nothing about what was read or spent may be claimed.
160
+ * not a measurement. A customer read can also qualify saved research whose
161
+ * current checkpoint retains an unconfirmed paid lookup.
160
162
  */
161
163
  effect_outcome?: "unknown";
164
+ /** Read projection only: saved research remains available despite an unconfirmed lookup. */
165
+ research_available?: boolean;
162
166
  /**
163
167
  * The workspace domain researched (from organizations.iconDomain). Null (stored
164
168
  * as "", the key the execution and onboarding checks compare on) for a company
@@ -2,14 +2,18 @@ import type { KnowledgePageType, KnowledgePageStatus } from "./knowledge-constan
2
2
  /** Monotonic scaffold cursor stored in knowledge_state.scaffold_version. Bump to ship a
3
3
  * new wave of starter pages (tag them with a higher minScaffoldVersion).
4
4
  * v1: the 9-page core. v2: twelve GTM stubs (voice/brand/messaging/objections, three
5
- * motion playbooks + qualification, and the personas/customers/research/metrics hubs). */
5
+ * motion playbooks + qualification, and the personas/customers/research/metrics hubs).
6
+ * On 2026-10-12 both waves were retired for the two-page set below and the cursor stayed
7
+ * at 2: it is stored per workspace and never moves backwards, so a workspace already at
8
+ * 2 keeps what it has, one at 1 receives nothing more, and a fresh one seeds the two
9
+ * pages and records 2. The next wave is minScaffoldVersion 3. */
6
10
  export declare const KNOWLEDGE_SCAFFOLD_VERSION = 2;
7
11
  export type KnowledgeScaffoldPage = {
8
12
  /** Immutable slug; must be valid kebab grammar and NOT a reserved slug. */
9
13
  slug: string;
10
14
  type: KnowledgePageType;
11
15
  title: string;
12
- /** Nav pages ship active; content stubs ship draft so they never ground AI copy. */
16
+ /** Seeded pages ship draft so they never ground AI copy; only the retired nav pages shipped active. */
13
17
  status: Extract<KnowledgePageStatus, "active" | "draft">;
14
18
  tags: string[];
15
19
  summary: string;
@@ -24,11 +28,17 @@ export type KnowledgeScaffoldPage = {
24
28
  researchable?: boolean;
25
29
  };
26
30
  /**
27
- * The 21-page starter pack. Two active nav pages first (start-here + the conventions
28
- * schema page), then nineteen draft stub hubs (tag `seed`) — seven from wave 1 and
29
- * twelve from wave 2. `positioning` seeds as a NON-canonical draft so it writes freely;
30
- * pinning canonical positioning stays a separate human-approved act. Wave 2 adds ZERO
31
- * active pages, per the binding rule at the top of this file.
31
+ * The 21-page starter set workspaces were seeded with until 2026-10-12, verbatim: two
32
+ * active nav pages (start-here + the conventions schema page), then nineteen draft stub
33
+ * hubs (tag `seed`) — seven from wave 1 and twelve from wave 2. No longer seeded.
34
+ * Workspaces created before then still hold every copy nobody edited, so readers of
35
+ * stored pages (the ChatGPT profile's sanitizer) are tested against these exact bodies,
36
+ * and any cleanup of untouched copies must compare against them rather than guess.
37
+ */
38
+ export declare const RETIRED_KNOWLEDGE_SEED_PAGES: readonly KnowledgeScaffoldPage[];
39
+ /**
40
+ * The starter set: the ICP and Offers documents, each a draft stub (tag `seed`) linked to
41
+ * the other, so a fresh workspace's graph is two connected files instead of a scaffold.
32
42
  */
33
43
  export declare const KNOWLEDGE_SEED_PAGES: readonly KnowledgeScaffoldPage[];
34
44
  /**
@@ -1,8 +1,10 @@
1
- // Knowledge Graph starter pack (the Karpathy LLM-wiki scaffold). A fresh workspace
2
- // self-seeds this page set at tenant provisioning / first onboarding / `oxygen knowledge
3
- // seed`, so the wiki arrives alive: two active nav pages ([[start-here]] + the
4
- // [[conventions]] schema page) and nineteen draft stub hubs that show the shape of a
5
- // healthy second brain without grounding any AI copy until a human fills them.
1
+ // Knowledge Graph starter set. A fresh workspace self-seeds these pages at tenant
2
+ // provisioning / first onboarding / `oxygen knowledge seed`: the ICP and Offers documents,
3
+ // as draft stubs. They are the two the onboarding conversation fills first (offer → ICP)
4
+ // and the two every GTM motion reads. Nothing else is seeded (Tim, 2026-10-12): the
5
+ // earlier 21-page set — a start-here guide, a conventions schema page and nineteen hub
6
+ // stubs — read as bloat and confused new users. Agents and users create any other page
7
+ // when there is something real to put in it.
6
8
  //
7
9
  // The seeder (tenant-db `ensureKnowledgeScaffold`) is purely additive and version-gated:
8
10
  // it creates a page only when its slug is free (never overwriting a user's edits) and
@@ -11,9 +13,8 @@
11
13
  // never rewritten by the scaffold. Bodies are the single source of truth for the starter
12
14
  // content — keep them dense and wikilinked.
13
15
  //
14
- // BINDING RULE — a scaffold wave adds ZERO active pages. Every content page ships
15
- // `status: "draft"` with `seed` in its tags; only the two nav pages are active, and they
16
- // are written full rather than as stubs. `retrieveWorkspaceKnowledge` selects on
16
+ // BINDING RULE — the scaffold adds ZERO active pages. Every seeded page ships
17
+ // `status: "draft"` with `seed` in its tags. `retrieveWorkspaceKnowledge` selects on
17
18
  // `status = 'active'` with MIN_BODY_CHARS = 80, so an ACTIVE stub is grounding material:
18
19
  // "Stub — fill me. ## Firmographics —" would be injected verbatim into a customer's cold
19
20
  // email as company knowledge. Promoting a stub to active is the user's act, never the
@@ -22,12 +23,15 @@
22
23
  // NEVER SEED `outreach-learnings`, `won-lost-learnings`, or `meeting-learnings`. tenant-db
23
24
  // `ensureLearningsPage` get-or-creates them as `active` on the first reply, deal, or
24
25
  // meeting and returns an existing row UNTOUCHED — seeding them as draft stubs would pin
25
- // them draft forever and silently break the learning loop's read half. They stay dashed
26
- // unresolved links in [[campaign-learnings]] until the loop creates them for real.
26
+ // them draft forever and silently break the learning loop's read half.
27
27
  /** Monotonic scaffold cursor stored in knowledge_state.scaffold_version. Bump to ship a
28
28
  * new wave of starter pages (tag them with a higher minScaffoldVersion).
29
29
  * v1: the 9-page core. v2: twelve GTM stubs (voice/brand/messaging/objections, three
30
- * motion playbooks + qualification, and the personas/customers/research/metrics hubs). */
30
+ * motion playbooks + qualification, and the personas/customers/research/metrics hubs).
31
+ * On 2026-10-12 both waves were retired for the two-page set below and the cursor stayed
32
+ * at 2: it is stored per workspace and never moves backwards, so a workspace already at
33
+ * 2 keeps what it has, one at 1 receives nothing more, and a fresh one seeds the two
34
+ * pages and records 2. The next wave is minScaffoldVersion 3. */
31
35
  // KNOWN LIMITATION of a create-only seeder, accepted deliberately: on a workspace
32
36
  // UPGRADED from an earlier wave, the new pages get no inbound links, because the hub that
33
37
  // would link them (start-here) already exists and the scaffold never rewrites an existing
@@ -472,13 +476,14 @@ This page holds definitions and targets — argued over once, then reused. The v
472
476
  Moving a target is a call: record it in [[decisions]] with its reversal condition. Why it moved is evidence: that goes to [[campaign-learnings]].
473
477
  `;
474
478
  /**
475
- * The 21-page starter pack. Two active nav pages first (start-here + the conventions
476
- * schema page), then nineteen draft stub hubs (tag `seed`) — seven from wave 1 and
477
- * twelve from wave 2. `positioning` seeds as a NON-canonical draft so it writes freely;
478
- * pinning canonical positioning stays a separate human-approved act. Wave 2 adds ZERO
479
- * active pages, per the binding rule at the top of this file.
479
+ * The 21-page starter set workspaces were seeded with until 2026-10-12, verbatim: two
480
+ * active nav pages (start-here + the conventions schema page), then nineteen draft stub
481
+ * hubs (tag `seed`) — seven from wave 1 and twelve from wave 2. No longer seeded.
482
+ * Workspaces created before then still hold every copy nobody edited, so readers of
483
+ * stored pages (the ChatGPT profile's sanitizer) are tested against these exact bodies,
484
+ * and any cleanup of untouched copies must compare against them rather than guess.
480
485
  */
481
- export const KNOWLEDGE_SEED_PAGES = [
486
+ export const RETIRED_KNOWLEDGE_SEED_PAGES = [
482
487
  {
483
488
  slug: "start-here",
484
489
  type: "topic",
@@ -698,6 +703,65 @@ export const KNOWLEDGE_SEED_PAGES = [
698
703
  minScaffoldVersion: 2,
699
704
  },
700
705
  ];
706
+ const STARTER_ICP_BODY = `# ICP — ideal customer profile
707
+
708
+ > **Stub — fill me.** Replace this with real, cited content, then set \`status: active\`. No approval needed; a draft never grounds AI copy.
709
+
710
+ ## Firmographics
711
+ - Company type / size / stage:
712
+ - Industry / vertical:
713
+ - Geography:
714
+
715
+ ## Buyers
716
+ - Who signs, who champions, who uses it day to day:
717
+
718
+ ## Triggers (why now)
719
+ - Signals that a fit account is in-market:
720
+
721
+ ## Disqualifiers (hard no)
722
+ - Who we should NOT pursue and why:
723
+
724
+ ## Basis
725
+ - Cite the deals, calls, or market data this is drawn from. What we sell them is in [[offers]].
726
+ `;
727
+ const STARTER_OFFERS_BODY = `# Offers
728
+
729
+ > **Stub — fill me.** Replace this with real, cited content, then set \`status: active\`. No approval needed; a draft never grounds AI copy.
730
+
731
+ ## Offers
732
+ For each: name, who it's for ([[icp]]), price/packaging, the outcome it delivers, and qualifying criteria.
733
+
734
+ - Offer 1 —
735
+ - Offer 2 —
736
+ `;
737
+ /**
738
+ * The starter set: the ICP and Offers documents, each a draft stub (tag `seed`) linked to
739
+ * the other, so a fresh workspace's graph is two connected files instead of a scaffold.
740
+ */
741
+ export const KNOWLEDGE_SEED_PAGES = [
742
+ {
743
+ slug: "icp",
744
+ type: "strategy",
745
+ title: "ICP — ideal customer profile",
746
+ status: "draft",
747
+ tags: ["seed", "icp"],
748
+ summary: "Stub: the ideal customer profile — firmographics, buyers, triggers, and disqualifiers that define who we sell to. Fill with cited content and set active.",
749
+ body: STARTER_ICP_BODY,
750
+ minScaffoldVersion: 1,
751
+ researchable: true,
752
+ },
753
+ {
754
+ slug: "offers",
755
+ type: "strategy",
756
+ title: "Offers",
757
+ status: "draft",
758
+ tags: ["seed", "offers"],
759
+ summary: "Stub: what we sell — packages, pricing, and the qualifying criteria per offer. Fill with cited content and set active.",
760
+ body: STARTER_OFFERS_BODY,
761
+ minScaffoldVersion: 1,
762
+ researchable: true,
763
+ },
764
+ ];
701
765
  /**
702
766
  * Seed slugs a domain-grounded research pass could genuinely draft from the company's
703
767
  * PUBLIC web presence — what they sell, who they say they sell to, how they sound, who
@@ -287,6 +287,7 @@ function createOtelEmitter(env, warn, summary) {
287
287
  let status;
288
288
  let failureKind = "transport_failed";
289
289
  let attempts = 0;
290
+ let deliveryPhase = "request";
290
291
  for (let attempt = 0; attempt < 3; attempt++) {
291
292
  const remaining = LANGFUSE_EXPORT_BUDGET_MS - (Date.now() - startedAt);
292
293
  if (remaining <= 0)
@@ -295,6 +296,7 @@ function createOtelEmitter(env, warn, summary) {
295
296
  status = undefined;
296
297
  const signal = AbortSignal.timeout(Math.min(LANGFUSE_EXPORT_TIMEOUT_SECONDS * 1000, remaining));
297
298
  let response;
299
+ deliveryPhase = "request";
298
300
  try {
299
301
  try {
300
302
  response = await fetch(exportUrl, { method: "POST", headers: exportHeaders, body, signal, redirect: "error" });
@@ -307,6 +309,7 @@ function createOtelEmitter(env, warn, summary) {
307
309
  await response.body?.cancel().catch(() => { });
308
310
  throw new Error("http_rejected");
309
311
  }
312
+ deliveryPhase = "acknowledgement";
310
313
  const chunks = [];
311
314
  let bytes = 0;
312
315
  const reader = response.body?.getReader();
@@ -358,11 +361,12 @@ function createOtelEmitter(env, warn, summary) {
358
361
  catch (error) {
359
362
  failureKind = error instanceof Error ? error.message : "transport_failed";
360
363
  const retryable = ["deadline_exceeded", "network_failed", "response_read_failed"].includes(failureKind)
361
- || (failureKind === "http_rejected" && [429, 502, 503, 504].includes(status ?? 0));
364
+ // Langfuse surfaces temporary S3 upload refusals as HTTP 500.
365
+ || (failureKind === "http_rejected" && [429, 500, 502, 503, 504].includes(status ?? 0));
362
366
  if (!retryable || attempt === 2) {
363
367
  // Carry fixed classifications and numbers only, never an SDK or
364
368
  // receiver error that could quote the request/response payload.
365
- throw Object.assign(new Error(failureKind), { attempts: attempt + 1, status });
369
+ throw Object.assign(new Error(failureKind), { attempts: attempt + 1, status, deliveryPhase, elapsedMs: Date.now() - startedAt });
366
370
  }
367
371
  const pause = 100 * (attempt + 1);
368
372
  if (LANGFUSE_EXPORT_BUDGET_MS - (Date.now() - startedAt) <= pause)
@@ -371,7 +375,7 @@ function createOtelEmitter(env, warn, summary) {
371
375
  await new Promise((resolve) => setTimeout(resolve, pause));
372
376
  }
373
377
  }
374
- throw Object.assign(new Error(failureKind), { attempts, status });
378
+ throw Object.assign(new Error(failureKind), { attempts, status, deliveryPhase, elapsedMs: Date.now() - startedAt });
375
379
  };
376
380
  const privateExporter = {
377
381
  export: (spans, done) => {
@@ -391,7 +395,7 @@ function createOtelEmitter(env, warn, summary) {
391
395
  summary.maybeReport();
392
396
  const details = error;
393
397
  warn(error, { stage: "export", observation_count: spans.length, failure_kind: failureKind,
394
- attempts: details.attempts, http_status: details.status });
398
+ attempts: details.attempts, http_status: details.status, delivery_phase: details.deliveryPhase, elapsed_ms: details.elapsedMs });
395
399
  });
396
400
  pendingExports.add(pending);
397
401
  // Even an unexpected SDK callback failure must not create a rejected
@@ -1,7 +1,7 @@
1
1
  export declare const MAILBOX_EGRESS_MODE_ENV = "OXYGEN_MAILBOX_EGRESS_MODE";
2
2
  export type MailboxEgressMode = "direct" | "proxy";
3
3
  /** `proxy` only when explicitly selected; anything else keeps today's transport. */
4
- export declare function readMailboxEgressMode(env?: NodeJS.ProcessEnv): MailboxEgressMode;
4
+ export declare function readMailboxEgressMode(env?: Readonly<Record<string, string | undefined>>): MailboxEgressMode;
5
5
  export type DedicatedEgressInventoryRow = {
6
6
  id: string;
7
7
  organizationId: string | null;
@@ -44,7 +44,7 @@ export type MailboxImportValidationSummary = {
44
44
  };
45
45
  export type MailboxOAuthProviderReviewPlan = {
46
46
  mailboxes: number;
47
- authorization_mode: "individual" | "tenant";
47
+ authorization_mode: "individual";
48
48
  batch_size: number;
49
49
  batches: number;
50
50
  batch_sizes: number[];
@@ -56,6 +56,7 @@ export type MailboxOAuthReviewPlan = {
56
56
  existing_grants_checked: false;
57
57
  /** Backward-compatible individual OAuth review size. */
58
58
  batch_size: number;
59
+ /** Address limit of the agent-only Microsoft tenant connection; no planned batch uses it. */
59
60
  tenant_batch_size: number;
60
61
  total_batches: number;
61
62
  providers: {
@@ -93,8 +94,10 @@ export declare function summarizeMailboxImportValidation(mailboxes: readonly Nor
93
94
  * authorization. This is safe during local file validation: it uses only
94
95
  * normalized provider counts, performs no directory discovery, and does not
95
96
  * claim to know whether a workspace already holds a usable grant for an address.
96
- * Multi-mailbox Microsoft imports recommend one tenant-admin batch; a single
97
- * Microsoft mailbox and all Google mailboxes retain individual OAuth.
97
+ * Google and Microsoft plan the same individual sign-in batches: the app connects
98
+ * both one way (decision 2026-10-14). The one-administrator Microsoft tenant
99
+ * connection is an explicit agent option (`connect-oauth --vendor oxygen
100
+ * --authorization-mode tenant`, up to `tenant_batch_size` addresses), never planned.
98
101
  */
99
102
  export declare function planMailboxOAuthReviews(mailboxes: readonly Pick<NormalizedMailboxImportRow, "provider">[]): MailboxOAuthReviewPlan;
100
103
  export declare function normalizeMailboxImportVendor(raw: string | null | undefined, from: string | null | undefined): string | null;
@@ -233,18 +233,17 @@ export function summarizeMailboxImportValidation(mailboxes, input) {
233
233
  * authorization. This is safe during local file validation: it uses only
234
234
  * normalized provider counts, performs no directory discovery, and does not
235
235
  * claim to know whether a workspace already holds a usable grant for an address.
236
- * Multi-mailbox Microsoft imports recommend one tenant-admin batch; a single
237
- * Microsoft mailbox and all Google mailboxes retain individual OAuth.
236
+ * Google and Microsoft plan the same individual sign-in batches: the app connects
237
+ * both one way (decision 2026-10-14). The one-administrator Microsoft tenant
238
+ * connection is an explicit agent option (`connect-oauth --vendor oxygen
239
+ * --authorization-mode tenant`, up to `tenant_batch_size` addresses), never planned.
238
240
  */
239
241
  export function planMailboxOAuthReviews(mailboxes) {
240
242
  const counts = { google: 0, microsoft: 0 };
241
243
  for (const mailbox of mailboxes)
242
244
  counts[mailbox.provider] += 1;
243
- const google = planProviderOAuthReviews(counts.google, MAILBOX_OAUTH_REVIEW_BATCH_SIZE, "individual");
244
- const microsoftMode = counts.microsoft > 1 ? "tenant" : "individual";
245
- const microsoft = planProviderOAuthReviews(counts.microsoft, microsoftMode === "tenant"
246
- ? MAILBOX_IMPORT_ROW_LIMIT
247
- : MAILBOX_OAUTH_REVIEW_BATCH_SIZE, microsoftMode);
245
+ const google = planProviderOAuthReviews(counts.google);
246
+ const microsoft = planProviderOAuthReviews(counts.microsoft);
248
247
  return {
249
248
  authorization: "exact_account_oauth",
250
249
  scope: "validated_addresses",
@@ -256,7 +255,8 @@ export function planMailboxOAuthReviews(mailboxes) {
256
255
  providers: { google, microsoft },
257
256
  };
258
257
  }
259
- function planProviderOAuthReviews(mailboxes, batchSize, authorizationMode) {
258
+ function planProviderOAuthReviews(mailboxes) {
259
+ const batchSize = MAILBOX_OAUTH_REVIEW_BATCH_SIZE;
260
260
  const fullBatches = Math.floor(mailboxes / batchSize);
261
261
  const remainder = mailboxes % batchSize;
262
262
  const batchSizes = Array.from({ length: fullBatches }, () => batchSize);
@@ -264,7 +264,7 @@ function planProviderOAuthReviews(mailboxes, batchSize, authorizationMode) {
264
264
  batchSizes.push(remainder);
265
265
  return {
266
266
  mailboxes,
267
- authorization_mode: authorizationMode,
267
+ authorization_mode: "individual",
268
268
  batch_size: batchSize,
269
269
  batches: batchSizes.length,
270
270
  batch_sizes: batchSizes,
@@ -0,0 +1,8 @@
1
+ export declare const OXYGEN_MCP_SERVER_NAME = "oxygen";
2
+ export declare const OXYGEN_MCP_SERVER_TITLE = "Oxygen GTM";
3
+ export declare const MCP_SERVER_INFO_META_KEY = "io.modelcontextprotocol/serverInfo";
4
+ export declare const OXYGEN_MCP_SERVER_INFO: {
5
+ name: string;
6
+ version: string;
7
+ title: string;
8
+ };
@@ -0,0 +1,10 @@
1
+ import { OXYGEN_VERSION } from "./version.js";
2
+ // Shared by the serving MCP transport and native result-window projections.
3
+ export const OXYGEN_MCP_SERVER_NAME = "oxygen";
4
+ export const OXYGEN_MCP_SERVER_TITLE = "Oxygen GTM";
5
+ export const MCP_SERVER_INFO_META_KEY = "io.modelcontextprotocol/serverInfo";
6
+ export const OXYGEN_MCP_SERVER_INFO = {
7
+ name: OXYGEN_MCP_SERVER_NAME,
8
+ version: OXYGEN_VERSION,
9
+ title: OXYGEN_MCP_SERVER_TITLE,
10
+ };
@@ -231,3 +231,13 @@ export declare function deleteInboxAvatarObject(input: {
231
231
  export declare function deleteImportObject(input: {
232
232
  storageKey: string;
233
233
  }): Promise<void>;
234
+ /**
235
+ * Delete a file a person attached in a Copilot chat, when the chat itself is
236
+ * deleted (an Oxygent deleted for good, ADR 0032). The organization's own
237
+ * prefix is required, for the reason `deleteInboxAvatarObject` gives: one bucket
238
+ * holds every tenant's uploads, and these keys come back out of durable state.
239
+ */
240
+ export declare function deleteCopilotAttachmentObject(input: {
241
+ storageKey: string;
242
+ organizationId: string;
243
+ }): Promise<void>;
@@ -464,6 +464,20 @@ export async function deleteImportObject(input) {
464
464
  const { client, config } = resolveClient();
465
465
  await client.send(new DeleteObjectCommand({ Bucket: config.bucket, Key: input.storageKey }));
466
466
  }
467
+ /**
468
+ * Delete a file a person attached in a Copilot chat, when the chat itself is
469
+ * deleted (an Oxygent deleted for good, ADR 0032). The organization's own
470
+ * prefix is required, for the reason `deleteInboxAvatarObject` gives: one bucket
471
+ * holds every tenant's uploads, and these keys come back out of durable state.
472
+ */
473
+ export async function deleteCopilotAttachmentObject(input) {
474
+ assertAppStorageWriteAllowed();
475
+ if (!isCopilotAttachmentObjectKeyForOrganization(input.storageKey, input.organizationId)) {
476
+ throw new OxygenError("invalid_object_key", "Refusing to delete an object outside this organization's Copilot attachments.", { details: { prefix: input.storageKey.split("/")[0] ?? "" }, exitCode: 1 });
477
+ }
478
+ const { client, config } = resolveClient();
479
+ await client.send(new DeleteObjectCommand({ Bucket: config.bucket, Key: input.storageKey }));
480
+ }
467
481
  function sanitizeFileName(fileName) {
468
482
  if (!fileName)
469
483
  return "";
@@ -70,6 +70,9 @@ export type PlanLimitsWorkflowLimits = {
70
70
  run_attempt_timeout_minutes?: number;
71
71
  };
72
72
  export type PlanLimitsStorageCapacity = {
73
+ status?: string;
74
+ effective_at?: string | null;
75
+ target?: PlanLimitsStorageTarget | null;
73
76
  platform_guard?: {
74
77
  table_row_limit?: number;
75
78
  workspace_row_limit?: number;
@@ -179,6 +182,8 @@ export declare function planLimitsLadder(payload: PlanLimitsEnvelope): PlanLimit
179
182
  * named, so a full one is identifiable (decision F.2).
180
183
  */
181
184
  export declare function planLimitsStorageUsageLine(storage: PlanLimitsStorageCapacity | undefined): string | null;
185
+ /** Pending capacity never supplies current headroom or an inferred date. */
186
+ export declare function planLimitsStoragePendingLine(storage: PlanLimitsStorageCapacity | undefined): string | null;
182
187
  /** Status of the largest active Table, from the same flags CLI and MCP read. */
183
188
  export declare function planLimitsTableStatus(storage: PlanLimitsStorageCapacity | undefined): "full" | "nearly full" | null;
184
189
  /** Sending limits are the same on every plan: one row per channel. */
@@ -180,7 +180,7 @@ export function planLimitsLadder(payload) {
180
180
  notes.push(`"→" marks a limit that changes on ${payload.pricing_schedule.effective_at.slice(0, 10)}.`);
181
181
  }
182
182
  if (bands.some(awaitingCapacityTest)) {
183
- notes.push(`"${AWAITING_TEST_MARK}" rises once the rows-per-table capacity test at that plan size passes.`);
183
+ notes.push(`"${AWAITING_TEST_MARK}" is planned capacity, not in force. It requires a capacity test at that plan size; no activation date is set.`);
184
184
  }
185
185
  return {
186
186
  bands: bands.map((row) => ({ band: row.band ?? "", label: row.label ?? row.band ?? "", current: row.current === true })),
@@ -207,8 +207,24 @@ export function planLimitsStorageUsageLine(storage) {
207
207
  `workspace ${formatLimitCount(usage.workspace_rows)} / ${formatLimitCount(guard.workspace_row_limit)} rows`,
208
208
  `database ${formatLimitBytes(usage.workspace_database_bytes)} / ${formatLimitBytes(guard.workspace_database_limit_bytes)}`,
209
209
  ];
210
+ if (typeof usage.workspace_rows === "number" && typeof guard.workspace_row_limit === "number") {
211
+ parts.push(`${formatLimitCount(Math.max(0, guard.workspace_row_limit - usage.workspace_rows))} workspace rows remaining`);
212
+ }
210
213
  return `Your storage: ${parts.join(" · ")}`;
211
214
  }
215
+ /** Pending capacity never supplies current headroom or an inferred date. */
216
+ export function planLimitsStoragePendingLine(storage) {
217
+ const target = storage?.target;
218
+ if (!target)
219
+ return null;
220
+ if (storage.status === "awaiting_capacity_test" && typeof target.table_row_limit === "number") {
221
+ return `A rise to ${formatLimitCount(target.table_row_limit)} rows per Table is planned but not in force. It requires a capacity test at this plan size; no activation date is set.`;
222
+ }
223
+ if (storage.status === "scheduled" && storage.effective_at) {
224
+ return `From ${storage.effective_at.slice(0, 10)}: ${formatLimitCount(target.table_row_limit)} rows per Table, ${formatLimitCount(target.workspace_row_limit)} in the workspace and ${formatLimitBytes(target.workspace_database_limit_bytes)} of Table data.`;
225
+ }
226
+ return null;
227
+ }
212
228
  /** Status of the largest active Table, from the same flags CLI and MCP read. */
213
229
  export function planLimitsTableStatus(storage) {
214
230
  if (storage?.usage?.table_limit_reached === true)
@@ -219,7 +219,7 @@ export declare const SENDING_DOMAIN_CREDITS_PER_YEAR_TYPICAL = 1250;
219
219
  * inverts its own margin: at $1.00/inbox-month the EIGHTH test in a cycle was
220
220
  * already sold at a loss. Two included tests cost $0.265 against $1.00 of
221
221
  * revenue at today's price, and everything past them bills
222
- * PLACEMENT_TEST_OVERAGE_CREDITS. Orgs on their own EmailGuard key (BYOK) pay 0.
222
+ * PLACEMENT_TEST_OVERAGE_CREDITS. Own-key tests pay the separate OXYGEN platform fee, without consuming this allowance.
223
223
  *
224
224
  * Monitoring is 100 credits per inbox-month (founder, 2026-10-07; the 2026-09
225
225
  * repricing had set 135). The extra test is the RATIFIED 66 (slice S30), whose rise
@@ -206,7 +206,7 @@ export const SENDING_DOMAIN_CREDITS_PER_YEAR_TYPICAL = SNAPSHOT_SENDING_DOMAIN_C
206
206
  * inverts its own margin: at $1.00/inbox-month the EIGHTH test in a cycle was
207
207
  * already sold at a loss. Two included tests cost $0.265 against $1.00 of
208
208
  * revenue at today's price, and everything past them bills
209
- * PLACEMENT_TEST_OVERAGE_CREDITS. Orgs on their own EmailGuard key (BYOK) pay 0.
209
+ * PLACEMENT_TEST_OVERAGE_CREDITS. Own-key tests pay the separate OXYGEN platform fee, without consuming this allowance.
210
210
  *
211
211
  * Monitoring is 100 credits per inbox-month (founder, 2026-10-07; the 2026-09
212
212
  * repricing had set 135). The extra test is the RATIFIED 66 (slice S30), whose rise
@@ -0,0 +1,15 @@
1
+ /** Staff inventory metadata; configuration is not a health or entitlement verdict. */
2
+ export type ProductProviderRail = {
3
+ provider: string;
4
+ supplier: string;
5
+ uses: string[];
6
+ selection: "default" | "conditional" | "legacy";
7
+ condition: string;
8
+ source: string;
9
+ required_env: string[];
10
+ /** At least one alternative is required, in addition to every required_env. */
11
+ required_env_any?: string[];
12
+ key_configured: boolean;
13
+ economics: string;
14
+ };
15
+ export declare function formatProviderEnvRequirements(required: readonly string[], alternatives?: readonly string[]): string;
@@ -0,0 +1,3 @@
1
+ export function formatProviderEnvRequirements(required, alternatives = []) {
2
+ return [...required, ...(alternatives.length ? [`(${alternatives.join(" or ")})`] : [])].join(" + ");
3
+ }
@@ -123,7 +123,7 @@ export declare function deliverabilitySeatCredits(): number;
123
123
  /**
124
124
  * S30 (decision 3.6; flat literal, proposal P-29): one managed placement test
125
125
  * past an inbox's included allowance, or on an inbox with no monitoring
126
- * subscription, 16.6 -> 66 credits. Tests on your own EmailGuard key stay 0.
126
+ * subscription, 16.6 -> 66 credits. Own-key calls pay the separate platform fee.
127
127
  */
128
128
  export declare const PLACEMENT_TEST_REPRICING: RepricedValue<16.6 | 66>;
129
129
  /** One managed placement test beyond the included allowance, in credits, at the price in force. */
@@ -300,7 +300,7 @@ export function deliverabilitySeatCredits() {
300
300
  /**
301
301
  * S30 (decision 3.6; flat literal, proposal P-29): one managed placement test
302
302
  * past an inbox's included allowance, or on an inbox with no monitoring
303
- * subscription, 16.6 -> 66 credits. Tests on your own EmailGuard key stay 0.
303
+ * subscription, 16.6 -> 66 credits. Own-key calls pay the separate platform fee.
304
304
  */
305
305
  export const PLACEMENT_TEST_REPRICING = defineRepricedValue({
306
306
  key: "overage.placement_test",
@@ -308,7 +308,7 @@ export const PLACEMENT_TEST_REPRICING = defineRepricedValue({
308
308
  slice: "S30",
309
309
  decision: "3.6",
310
310
  unit: "credits_per_test",
311
- description: "One managed inbox-placement test beyond an inbox's included tests. Tests on your own EmailGuard key cost 0.",
311
+ description: "One managed inbox-placement test beyond an inbox's included tests. Own-key calls pay the separate OXYGEN platform fee.",
312
312
  before: 16.6,
313
313
  after: SNAPSHOT_PLACEMENT_TEST_CREDITS,
314
314
  });
@@ -1 +1,7 @@
1
+ /**
2
+ * Whether `cron` is five fields in the grammar above (`*`, `N`, `N-M`, `/step`,
3
+ * comma lists). A syntax check only: whether a schedule ever fires, and how
4
+ * often, is the Workflows scheduler's to answer (`nextCronRunAfter`).
5
+ */
6
+ export declare function isCronSyntax(cron: string): boolean;
1
7
  export declare function humanizeCronSchedule(cron: string): string | null;
@@ -86,6 +86,18 @@ function dayNames(days) {
86
86
  .map((day) => DAY_NAMES[day])
87
87
  .join(", ");
88
88
  }
89
+ /**
90
+ * Whether `cron` is five fields in the grammar above (`*`, `N`, `N-M`, `/step`,
91
+ * comma lists). A syntax check only: whether a schedule ever fires, and how
92
+ * often, is the Workflows scheduler's to answer (`nextCronRunAfter`).
93
+ */
94
+ export function isCronSyntax(cron) {
95
+ const fields = cron.trim().split(/\s+/);
96
+ if (fields.length !== 5)
97
+ return false;
98
+ const ranges = [MINUTE_RANGE, HOUR_RANGE, DAY_OF_MONTH_RANGE, MONTH_RANGE, DAY_OF_WEEK_RANGE];
99
+ return fields.every((field, index) => expandCronField(field, ranges[index]) !== null);
100
+ }
89
101
  export function humanizeCronSchedule(cron) {
90
102
  const fields = cron.trim().split(/\s+/);
91
103
  if (fields.length !== 5)
@@ -1,5 +1,6 @@
1
1
  export declare const SEQUENCE_FAILURE_SCHEMA_VERSION = 1;
2
2
  export declare const SEQUENCE_FAILURE_RETRY_AFTER_MAX_MS: number;
3
+ export declare const WHATSAPP_UNCERTAIN_DELIVERY_MESSAGE = "WhatsApp delivery is uncertain. Automatic resending is blocked.";
3
4
  export declare const SEQUENCE_FAILURE_CLASSES: readonly ["capacity", "recipient", "authorization_scope", "funding", "validation", "provider", "internal", "effect_unknown", "unknown"];
4
5
  export type SequenceFailureClass = (typeof SEQUENCE_FAILURE_CLASSES)[number];
5
6
  export type SequenceFailureEffectOutcome = "not_applied" | "applied" | "unknown";
@@ -3,6 +3,7 @@ import { isProviderFundingErrorCode, isProviderRateLimitErrorCode, } from "./pro
3
3
  import { asRecordOrNull as asRecord } from "./type-guards.js";
4
4
  export const SEQUENCE_FAILURE_SCHEMA_VERSION = 1;
5
5
  export const SEQUENCE_FAILURE_RETRY_AFTER_MAX_MS = 24 * 60 * 60 * 1000;
6
+ export const WHATSAPP_UNCERTAIN_DELIVERY_MESSAGE = "WhatsApp delivery is uncertain. Automatic resending is blocked.";
6
7
  export const SEQUENCE_FAILURE_CLASSES = [
7
8
  "capacity",
8
9
  "recipient",
@@ -122,15 +123,28 @@ export function readSequenceFailure(error, overrides = {}) {
122
123
  "provider_status",
123
124
  "providerStatus",
124
125
  ]));
125
- const retryable = overrides.retryable ?? firstBoolean(records, ["retryable"]);
126
- const effectOutcome = overrides.effectOutcome ?? readEffectOutcome(records);
126
+ let retryable = overrides.retryable ?? firstBoolean(records, ["retryable"]);
127
+ let effectOutcome = overrides.effectOutcome ?? readEffectOutcome(records);
127
128
  const retryAfterMs = normalizeRetryAfterMs(overrides.retryAfterMs ?? readRetryAfterMs(records));
128
- const hardBounce = overrides.hardBounce ??
129
+ let hardBounce = overrides.hardBounce ??
129
130
  firstBoolean(records, ["hard_bounce", "hardBounce"]);
131
+ // Older WhatsApp clients treated every provider/* response as proof that no
132
+ // message was sent, including gateway failures after dispatch. Neither the
133
+ // HTTP response nor that stale v1 envelope makes another write safe.
134
+ const uncertainWhatsAppGateway = rawCode === "whatsapp_conversation_unavailable"
135
+ && httpStatus !== null && httpStatus >= 500 && effectOutcome !== "applied";
136
+ const fault = uncertainWhatsAppGateway
137
+ ? { code: "provider_write_ambiguous", message: WHATSAPP_UNCERTAIN_DELIVERY_MESSAGE }
138
+ : redacted;
139
+ if (uncertainWhatsAppGateway) {
140
+ effectOutcome = "unknown";
141
+ retryable = false;
142
+ hardBounce = false;
143
+ }
130
144
  const existingClass = firstString(records, ["failure_class"]);
131
145
  const existingVersion = firstNumber(records, ["failure_schema_version"]);
132
146
  const classified = classifySequenceFailure({
133
- code: redacted.code,
147
+ code: fault.code,
134
148
  providerSubtype,
135
149
  provider,
136
150
  httpStatus,
@@ -153,8 +167,8 @@ export function readSequenceFailure(error, overrides = {}) {
153
167
  return {
154
168
  failure_schema_version: SEQUENCE_FAILURE_SCHEMA_VERSION,
155
169
  failure_class: failureClass,
156
- code: redacted.code,
157
- message: redacted.message,
170
+ code: fault.code,
171
+ message: fault.message,
158
172
  ...(provider ? { provider } : {}),
159
173
  ...(operation ? { operation } : {}),
160
174
  ...(providerSubtype ? { provider_subtype: providerSubtype } : {}),