@oxygen-agent/cli 1.987.20 → 1.1010.1

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 (176) hide show
  1. package/README.md +1 -1
  2. package/dist/admin-primary-providers-render.d.ts +0 -2
  3. package/dist/admin-primary-providers-render.js +1 -1
  4. package/dist/browser-login.js +1 -4
  5. package/dist/column-decision-options.d.ts +20 -0
  6. package/dist/column-decision-options.js +54 -0
  7. package/dist/command-manifest.d.ts +3 -2
  8. package/dist/command-manifest.js +25 -2
  9. package/dist/credentials.d.ts +1 -1
  10. package/dist/functions-commands.js +13 -9
  11. package/dist/help.d.ts +8 -0
  12. package/dist/help.js +46 -0
  13. package/dist/index.js +2751 -242
  14. package/dist/knowledge-mirror.d.ts +2 -2
  15. package/dist/runtime.d.ts +0 -15
  16. package/dist/runtime.js +1 -1
  17. package/dist/search-ai-filter-notice.d.ts +17 -0
  18. package/dist/search-ai-filter-notice.js +38 -0
  19. package/dist/session.d.ts +4 -3
  20. package/dist/skills.d.ts +8 -7
  21. package/dist/skills.js +58 -20
  22. package/dist/transcript.d.ts +2 -1
  23. package/dist/util.d.ts +10 -1
  24. package/dist/util.js +14 -2
  25. package/node_modules/@oxygen/cli-ugc/dist/commands.js +296 -140
  26. package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +9 -0
  27. package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +34 -0
  28. package/node_modules/@oxygen/formula/dist/coerce.d.ts +10 -0
  29. package/node_modules/@oxygen/formula/dist/coerce.js +10 -0
  30. package/node_modules/@oxygen/formula/dist/formula-functions.js +65 -0
  31. package/node_modules/@oxygen/formula/dist/hash.d.ts +19 -0
  32. package/node_modules/@oxygen/formula/dist/hash.js +199 -0
  33. package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +6 -1
  34. package/node_modules/@oxygen/formula/dist/value-cleaners.js +10 -26
  35. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +13 -0
  36. package/node_modules/@oxygen/shared/dist/array-utils.d.ts +5 -0
  37. package/node_modules/@oxygen/shared/dist/array-utils.js +11 -0
  38. package/node_modules/@oxygen/shared/dist/billing.d.ts +99 -22
  39. package/node_modules/@oxygen/shared/dist/billing.js +195 -40
  40. package/node_modules/@oxygen/shared/dist/byok-connect.js +5 -0
  41. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +27 -0
  42. package/node_modules/@oxygen/shared/dist/capability-discovery.js +311 -28
  43. package/node_modules/@oxygen/shared/dist/cli-http-error.d.ts +8 -0
  44. package/node_modules/@oxygen/shared/dist/cli-http-error.js +8 -0
  45. package/node_modules/@oxygen/shared/dist/cli-result.js +1 -0
  46. package/node_modules/@oxygen/shared/dist/column-autofill.d.ts +52 -0
  47. package/node_modules/@oxygen/shared/dist/column-autofill.js +62 -0
  48. package/node_modules/@oxygen/shared/dist/column-decision.d.ts +50 -0
  49. package/node_modules/@oxygen/shared/dist/column-decision.js +228 -0
  50. package/node_modules/@oxygen/shared/dist/column-output-fields.js +2 -6
  51. package/node_modules/@oxygen/shared/dist/company-enrichment-fields.d.ts +113 -0
  52. package/node_modules/@oxygen/shared/dist/company-enrichment-fields.js +548 -0
  53. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +6 -6
  54. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +6 -6
  55. package/node_modules/@oxygen/shared/dist/cutover-freeze.d.ts +26 -0
  56. package/node_modules/@oxygen/shared/dist/cutover-freeze.js +52 -0
  57. package/node_modules/@oxygen/shared/dist/data-suppliers.d.ts +57 -0
  58. package/node_modules/@oxygen/shared/dist/data-suppliers.js +59 -0
  59. package/node_modules/@oxygen/shared/dist/deploy-env.d.ts +74 -0
  60. package/node_modules/@oxygen/shared/dist/deploy-env.js +82 -0
  61. package/node_modules/@oxygen/shared/dist/dnc-rules.d.ts +130 -0
  62. package/node_modules/@oxygen/shared/dist/dnc-rules.js +221 -0
  63. package/node_modules/@oxygen/shared/dist/enrichment-intents.d.ts +107 -0
  64. package/node_modules/@oxygen/shared/dist/enrichment-intents.js +809 -0
  65. package/node_modules/@oxygen/shared/dist/error-message.d.ts +1 -0
  66. package/node_modules/@oxygen/shared/dist/error-message.js +3 -0
  67. package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -3
  68. package/node_modules/@oxygen/shared/dist/external-write-policy.d.ts +33 -0
  69. package/node_modules/@oxygen/shared/dist/external-write-policy.js +68 -0
  70. package/node_modules/@oxygen/shared/dist/format-percent.d.ts +8 -0
  71. package/node_modules/@oxygen/shared/dist/format-percent.js +13 -0
  72. package/node_modules/@oxygen/shared/dist/freemail-domains.d.ts +81 -0
  73. package/node_modules/@oxygen/shared/dist/freemail-domains.js +157 -0
  74. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +1 -0
  75. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +1 -1
  76. package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +60 -4
  77. package/node_modules/@oxygen/shared/dist/hosted-ai.js +125 -10
  78. package/node_modules/@oxygen/shared/dist/index.d.ts +15 -0
  79. package/node_modules/@oxygen/shared/dist/index.js +15 -0
  80. package/node_modules/@oxygen/shared/dist/json-path.js +1 -3
  81. package/node_modules/@oxygen/shared/dist/knowledge-bases.js +1 -3
  82. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +2 -2
  83. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +2 -2
  84. package/node_modules/@oxygen/shared/dist/langfuse.d.ts +44 -1
  85. package/node_modules/@oxygen/shared/dist/langfuse.js +416 -14
  86. package/node_modules/@oxygen/shared/dist/linkedin-countries.d.ts +33 -0
  87. package/node_modules/@oxygen/shared/dist/linkedin-countries.js +361 -0
  88. package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.d.ts +24 -0
  89. package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.js +276 -0
  90. package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.d.ts +2 -0
  91. package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.js +5 -0
  92. package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.d.ts +44 -0
  93. package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.js +116 -0
  94. package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +96 -0
  95. package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +123 -0
  96. package/node_modules/@oxygen/shared/dist/llm-durable-capture.d.ts +24 -0
  97. package/node_modules/@oxygen/shared/dist/llm-durable-capture.js +89 -0
  98. package/node_modules/@oxygen/shared/dist/llm-prompts.d.ts +75 -0
  99. package/node_modules/@oxygen/shared/dist/llm-prompts.js +161 -0
  100. package/node_modules/@oxygen/shared/dist/log-sink-selector.d.ts +39 -0
  101. package/node_modules/@oxygen/shared/dist/log-sink-selector.js +56 -0
  102. package/node_modules/@oxygen/shared/dist/log.d.ts +1 -0
  103. package/node_modules/@oxygen/shared/dist/log.js +6 -1
  104. package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +90 -0
  105. package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.js +130 -0
  106. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +17 -0
  107. package/node_modules/@oxygen/shared/dist/object-storage.js +21 -0
  108. package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +24 -0
  109. package/node_modules/@oxygen/shared/dist/operational-telemetry.js +73 -0
  110. package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +79 -0
  111. package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +366 -0
  112. package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -0
  113. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +23 -22
  114. package/node_modules/@oxygen/shared/dist/plan-limits.js +45 -18
  115. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +48 -41
  116. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +36 -25
  117. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +22 -22
  118. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +40 -34
  119. package/node_modules/@oxygen/shared/dist/product-analytics-environment.js +9 -0
  120. package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +36 -2
  121. package/node_modules/@oxygen/shared/dist/product-analytics-events.js +36 -1
  122. package/node_modules/@oxygen/shared/dist/rate-window.d.ts +5 -0
  123. package/node_modules/@oxygen/shared/dist/rate-window.js +8 -0
  124. package/node_modules/@oxygen/shared/dist/redaction.js +4 -1
  125. package/node_modules/@oxygen/shared/dist/research-output-contract.js +1 -3
  126. package/node_modules/@oxygen/shared/dist/scraper-lane-credential.d.ts +18 -0
  127. package/node_modules/@oxygen/shared/dist/scraper-lane-credential.js +23 -0
  128. package/node_modules/@oxygen/shared/dist/search-vocab.js +4 -5
  129. package/node_modules/@oxygen/shared/dist/select-options.js +6 -1
  130. package/node_modules/@oxygen/shared/dist/sequence-failures.js +1 -5
  131. package/node_modules/@oxygen/shared/dist/sequences.d.ts +23 -0
  132. package/node_modules/@oxygen/shared/dist/sequences.js +115 -5
  133. package/node_modules/@oxygen/shared/dist/signup-lead-payload.d.ts +80 -0
  134. package/node_modules/@oxygen/shared/dist/signup-lead-payload.js +198 -0
  135. package/node_modules/@oxygen/shared/dist/social-capabilities.d.ts +6 -0
  136. package/node_modules/@oxygen/shared/dist/social-capabilities.js +25 -16
  137. package/node_modules/@oxygen/shared/dist/social-post-metrics-core.d.ts +32 -0
  138. package/node_modules/@oxygen/shared/dist/social-post-metrics-core.js +32 -0
  139. package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.d.ts +31 -0
  140. package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.js +103 -0
  141. package/node_modules/@oxygen/shared/dist/social-post-metrics-series.d.ts +96 -0
  142. package/node_modules/@oxygen/shared/dist/social-post-metrics-series.js +213 -0
  143. package/node_modules/@oxygen/shared/dist/social-post-metrics-x.d.ts +13 -0
  144. package/node_modules/@oxygen/shared/dist/social-post-metrics-x.js +78 -0
  145. package/node_modules/@oxygen/shared/dist/social-post-metrics.d.ts +36 -0
  146. package/node_modules/@oxygen/shared/dist/social-post-metrics.js +51 -0
  147. package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +22 -10
  148. package/node_modules/@oxygen/shared/dist/spend-safety.js +15 -21
  149. package/node_modules/@oxygen/shared/dist/sql-rows.d.ts +1 -0
  150. package/node_modules/@oxygen/shared/dist/sql-rows.js +3 -0
  151. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +36 -0
  152. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +184 -0
  153. package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.d.ts +41 -0
  154. package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.js +44 -0
  155. package/node_modules/@oxygen/shared/dist/table-limits.d.ts +3 -0
  156. package/node_modules/@oxygen/shared/dist/table-limits.js +3 -0
  157. package/node_modules/@oxygen/shared/dist/telemetry-export-observer.d.ts +94 -0
  158. package/node_modules/@oxygen/shared/dist/telemetry-export-observer.js +298 -0
  159. package/node_modules/@oxygen/shared/dist/telemetry.d.ts +11 -0
  160. package/node_modules/@oxygen/shared/dist/telemetry.js +28 -2
  161. package/node_modules/@oxygen/shared/dist/type-guards.d.ts +22 -0
  162. package/node_modules/@oxygen/shared/dist/type-guards.js +35 -0
  163. package/node_modules/@oxygen/shared/dist/ugc.d.ts +22 -11
  164. package/node_modules/@oxygen/shared/dist/ugc.js +10 -0
  165. package/node_modules/@oxygen/shared/dist/value-readers.d.ts +21 -0
  166. package/node_modules/@oxygen/shared/dist/value-readers.js +59 -0
  167. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -0
  168. package/node_modules/@oxygen/shared/dist/version.generated.js +2 -0
  169. package/node_modules/@oxygen/shared/dist/version.js +8 -1
  170. package/node_modules/@oxygen/shared/dist/workspace-event-catalog.js +0 -23
  171. package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +29 -0
  172. package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +31 -0
  173. package/node_modules/@oxygen/shared/package.json +59 -0
  174. package/node_modules/@oxygen/workflows/dist/graph/expression.js +2 -5
  175. package/node_modules/@oxygen/workflows/dist/graph/params.js +1 -1
  176. package/package.json +3 -2
@@ -0,0 +1 @@
1
+ export declare function errorMessage(error: unknown): string;
@@ -0,0 +1,3 @@
1
+ export function errorMessage(error) {
2
+ return error instanceof Error ? error.message : String(error);
3
+ }
@@ -20,6 +20,7 @@
20
20
  // idempotent, never throws, and never widens a message.
21
21
  import { redactSecretsInString } from "./redaction.js";
22
22
  import { isRetryableConcurrencyError, redactSqlParameters } from "./sql-error.js";
23
+ import { isRecord } from "./type-guards.js";
23
24
  // Whole fields whose NAME marks them secret. Unchanged from the operation-event
24
25
  // writer this was extracted from; substring credentials (Bearer/sk-/DB URLs) are
25
26
  // scrubbed separately by redactSecretsInString.
@@ -306,6 +307,3 @@ export function summarizeProviderBody(body) {
306
307
  function truncate(value, maxLength) {
307
308
  return value.length > maxLength ? `${value.slice(0, maxLength - 3)}...` : value;
308
309
  }
309
- function isRecord(value) {
310
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
311
- }
@@ -0,0 +1,33 @@
1
+ export type ExternalWritePolicy = "allow" | "deny";
2
+ /** The error code every refusal carries, on every surface. */
3
+ export declare const EXTERNAL_WRITES_DENIED_CODE = "external_writes_denied";
4
+ export declare const EXTERNAL_WRITES_ENV_VAR = "OXYGEN_EXTERNAL_WRITES";
5
+ type PolicyEnv = {
6
+ [key: string]: string | undefined;
7
+ };
8
+ /**
9
+ * `deny` when `OXYGEN_EXTERNAL_WRITES` is exactly `deny` (trimmed,
10
+ * case-insensitive), `allow` for anything else including unset.
11
+ *
12
+ * Read per call rather than memoized at module load: the value is read on paths
13
+ * a test needs to drive both ways, and a process-static snapshot would make the
14
+ * denial untestable in exactly the suite that has to prove it.
15
+ */
16
+ export declare function resolveExternalWritePolicy(env?: PolicyEnv): ExternalWritePolicy;
17
+ /** Convenience predicate for callers that only branch. */
18
+ export declare function externalWritesDenied(env?: PolicyEnv): boolean;
19
+ /**
20
+ * Refuse `action` when this environment denies external writes.
21
+ *
22
+ * Throws `OxygenError` with code `external_writes_denied` so the refusal travels
23
+ * the CLI/MCP envelope, the run record and the worker's job failure the same way
24
+ * every other refusal does — a shadow that swallows the denial into a generic
25
+ * failure teaches its operator nothing.
26
+ *
27
+ * Logged at `warn`, not `error`: a refusal here is the control WORKING, and the
28
+ * shadow's soak is expected to produce a stream of them. The message names both
29
+ * the action and the variable, because the person reading it in Axiom is usually
30
+ * asking "why did nothing send" and the answer is a configuration they set.
31
+ */
32
+ export declare function assertExternalWritesAllowed(action: string, env?: PolicyEnv): void;
33
+ export {};
@@ -0,0 +1,68 @@
1
+ // The environment-level external-write denial, and the one place that reads
2
+ // `OXYGEN_EXTERNAL_WRITES`.
3
+ //
4
+ // WHY THIS EXISTS AND WHY IT IS NOT A CLASSIFIER. A production *shadow* holds a
5
+ // restored copy of production: real mailbox OAuth tokens, real provider
6
+ // credentials, real customer records — while managed production is still live
7
+ // and doing the same work. The restore script scrubs those columns, but a scrub
8
+ // is data hygiene, not a guarantee: one un-nulled column is one real send, to a
9
+ // real prospect, twice. So the shadow also fails closed at the platform
10
+ // boundary (`.agents/skills/oxygen-self-host-cutover`, Part 1 Control 3).
11
+ //
12
+ // THE ORDERING IS THE SAFETY PROPERTY, and it is the same lesson
13
+ // `EXTERNAL_REACH_CAPABILITY_NAMES` (packages/copilot/src/policy.ts) was written
14
+ // to record: `capabilityLooksPaid` read ARGUMENT NAMES, so a write-capable tool
15
+ // declaring `max_credits` classified `paid` and could never reach the
16
+ // `external_write` branch — which made a real outbound launch waivable by an
17
+ // approval-free spend budget on a declared ceiling of one credit. A denial that
18
+ // runs anywhere except FIRST inherits exactly that defect. Every caller here
19
+ // asks this before it classifies, before it evaluates an allowance, an approval
20
+ // or a policy rule, and before it constructs a provider client.
21
+ //
22
+ // UNSET MEANS ALLOW, deliberately and permanently. Managed production must be
23
+ // byte-identical to the day before this module landed; only a shadow sets the
24
+ // variable. `deny` is the only recognised denial value — a typo (`denied`,
25
+ // `true`, `1`) allows, which is the fail-OPEN direction and is the right one
26
+ // here: a variable nobody set must never take production off the air, and the
27
+ // shadow's operator proves the denial by ATTEMPTING a send and observing the
28
+ // refusal rather than by trusting the configuration (same skill, "Proving it,
29
+ // rather than believing it").
30
+ import { OxygenError } from "./cli-result.js";
31
+ import { log } from "./log.js";
32
+ /** The error code every refusal carries, on every surface. */
33
+ export const EXTERNAL_WRITES_DENIED_CODE = "external_writes_denied";
34
+ export const EXTERNAL_WRITES_ENV_VAR = "OXYGEN_EXTERNAL_WRITES";
35
+ /**
36
+ * `deny` when `OXYGEN_EXTERNAL_WRITES` is exactly `deny` (trimmed,
37
+ * case-insensitive), `allow` for anything else including unset.
38
+ *
39
+ * Read per call rather than memoized at module load: the value is read on paths
40
+ * a test needs to drive both ways, and a process-static snapshot would make the
41
+ * denial untestable in exactly the suite that has to prove it.
42
+ */
43
+ export function resolveExternalWritePolicy(env = process.env) {
44
+ return env[EXTERNAL_WRITES_ENV_VAR]?.trim().toLowerCase() === "deny" ? "deny" : "allow";
45
+ }
46
+ /** Convenience predicate for callers that only branch. */
47
+ export function externalWritesDenied(env = process.env) {
48
+ return resolveExternalWritePolicy(env) === "deny";
49
+ }
50
+ /**
51
+ * Refuse `action` when this environment denies external writes.
52
+ *
53
+ * Throws `OxygenError` with code `external_writes_denied` so the refusal travels
54
+ * the CLI/MCP envelope, the run record and the worker's job failure the same way
55
+ * every other refusal does — a shadow that swallows the denial into a generic
56
+ * failure teaches its operator nothing.
57
+ *
58
+ * Logged at `warn`, not `error`: a refusal here is the control WORKING, and the
59
+ * shadow's soak is expected to produce a stream of them. The message names both
60
+ * the action and the variable, because the person reading it in Axiom is usually
61
+ * asking "why did nothing send" and the answer is a configuration they set.
62
+ */
63
+ export function assertExternalWritesAllowed(action, env = process.env) {
64
+ if (resolveExternalWritePolicy(env) !== "deny")
65
+ return;
66
+ log("warn", "external_writes.denied", { action });
67
+ throw new OxygenError(EXTERNAL_WRITES_DENIED_CODE, `External writes are denied in this environment (${EXTERNAL_WRITES_ENV_VAR}=deny), so "${action}" cannot run.`, { details: { action, env_var: EXTERNAL_WRITES_ENV_VAR } });
68
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Canonical percentage formatting shared by the web app and MCP surfaces.
3
+ */
4
+ /**
5
+ * Format a 0..1 ratio as an en-US percentage with at most one decimal; `null`
6
+ * renders as an em dash. The locale is pinned so web and MCP cannot drift.
7
+ */
8
+ export declare function formatPercent(value: number | null): string;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Canonical percentage formatting shared by the web app and MCP surfaces.
3
+ */
4
+ /**
5
+ * Format a 0..1 ratio as an en-US percentage with at most one decimal; `null`
6
+ * renders as an em dash. The locale is pinned so web and MCP cannot drift.
7
+ */
8
+ export function formatPercent(value) {
9
+ return value === null ? "—" : new Intl.NumberFormat("en-US", {
10
+ style: "percent",
11
+ maximumFractionDigits: 1,
12
+ }).format(value);
13
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Consumer / freemail mail hosts — the ONE list, in two deliberate tiers.
3
+ *
4
+ * EIGHT call sites used to carry their own hand-typed copy of this concept — 101, 34, 34,
5
+ * 25, 22, 18, 13 and 12 entries — none importing another, none covered by a test
6
+ * asserting they agreed, and two of them byte-identical forks that nothing kept in sync.
7
+ * The same address resolved differently depending on which copy a code path happened to
8
+ * reach. This module is the single source of truth; `packages/shared` is the only leaf
9
+ * package all eight can reach without inverting a dependency edge.
10
+ *
11
+ * WHY TWO TIERS, and why they must not be collapsed into one:
12
+ *
13
+ * The callers are not asking the same question, and the cost of a wrong answer is
14
+ * asymmetric in opposite directions.
15
+ *
16
+ * - `FREEMAIL_DOMAINS` (core) answers "may I treat this domain as a shared EMPLOYER?"
17
+ * Two consumers ride on that answer, and they fail in OPPOSITE directions, which is
18
+ * why this tier is conservative rather than generous:
19
+ *
20
+ * 1. The company-scope reply stop (`settings.stop_on_reply_scope = "company_domain"`)
21
+ * terminalizes an enrollment's colleagues by matching domain. A host wrongly IN
22
+ * this tier is harmless here; a host wrongly OUT of it stops strangers who merely
23
+ * share a mail host — real, customer-visible damage.
24
+ * 2. Company derivation from a person's email domain
25
+ * (`deriveCompanyDomainFromEmail`, `packages/tenant-db/src/table-link-plan.ts`)
26
+ * drops freemail before offering a domain as a company join key. Here the
27
+ * polarity REVERSES: a host wrongly OUT of this tier makes OXYGEN mint a company
28
+ * record for a mail provider, so every @<host> person collapses onto one fake
29
+ * account. Where that derivation is wired into record creation, removing a host
30
+ * from this tier silently starts creating accounts for it.
31
+ *
32
+ * So a host must be in this tier to be safe for BOTH, and the safe direction for an
33
+ * edit is to ADD, never to remove or relocate to the broad tier. Extend it only with
34
+ * hosts that are unambiguously consumer mailboxes — and note the ORDER caveat in
35
+ * `packages/tenant-db/src/freemail-domains.ts`, because a core-tier edit also changes
36
+ * a formula string already persisted in every tenant's column definitions.
37
+ *
38
+ * - `PERSONAL_EMAIL_DOMAINS` (broad) answers "is this domain a poor stand-in for a
39
+ * COMPANY identity?" — used to skip fetching a workspace icon, to reject a personal
40
+ * host typed into the marketing hero form, to avoid picking a personal address as a
41
+ * person's work email, and to avoid treating a meeting participant's mail host as their
42
+ * employer. Here a false positive costs nothing (we simply decline to guess a company)
43
+ * while a miss produces a junk company/icon. So this tier is the superset, including
44
+ * regional and privacy-focused providers.
45
+ *
46
+ * The broad tier is a strict superset of the core tier by construction below, so a host
47
+ * can never be "freemail but not personal" — exactly the incoherence the forks had
48
+ * developed (the old core list carried `t-online.de`; several broad lists did not).
49
+ *
50
+ * The broad tier is the union of all eight former lists. Most of its bulk comes from the
51
+ * old `packages/formula` copy, which was by far the best maintained and is the only one
52
+ * that covered country-specific variants (`yahoo.co.uk`, `hotmail.fr`, `orange.fr`,
53
+ * `wp.pl`, `uol.com.br` …) and ISP mailboxes (`comcast.net`, `btinternet.com`). Every
54
+ * other caller was silently missing those.
55
+ */
56
+ /** Tier 1 — see the module note. Small on purpose. */
57
+ export declare const FREEMAIL_DOMAINS: ReadonlySet<string>;
58
+ /** Tier 2 — a strict superset of {@link FREEMAIL_DOMAINS}. */
59
+ export declare const PERSONAL_EMAIL_DOMAINS: ReadonlySet<string>;
60
+ /**
61
+ * Reduce an address or a bare domain to a comparable mail host: lowercased, trimmed,
62
+ * everything before the last '@' dropped, and a trailing FQDN dot removed. Returns null
63
+ * for junk/empty/nullish input so callers can distinguish "not a host" from "not in the
64
+ * set". Previously each fork normalized differently — one did full address handling, two
65
+ * only lowercased a bare domain — so the same input could resolve differently depending
66
+ * on which copy you happened to call.
67
+ */
68
+ export declare function normalizeMailHost(value: string | null | undefined): string | null;
69
+ /**
70
+ * True when the host is a tier-1 consumer mailbox, i.e. it NEVER identifies a shared
71
+ * employer. Accepts a bare domain or a full address. Unknown input returns false: an
72
+ * unrecognized domain is treated as a company domain, which keeps the company-scope
73
+ * stop's safe default identical to lead-scope.
74
+ */
75
+ export declare function isFreemailDomain(domain: string | null | undefined): boolean;
76
+ /**
77
+ * True when the host is a personal mailbox of any kind (tier 2). Use this for "should I
78
+ * infer a company from this domain?"; use {@link isFreemailDomain} when a false positive
79
+ * would wrongly group unrelated people as colleagues.
80
+ */
81
+ export declare function isPersonalEmailDomain(domain: string | null | undefined): boolean;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Consumer / freemail mail hosts — the ONE list, in two deliberate tiers.
3
+ *
4
+ * EIGHT call sites used to carry their own hand-typed copy of this concept — 101, 34, 34,
5
+ * 25, 22, 18, 13 and 12 entries — none importing another, none covered by a test
6
+ * asserting they agreed, and two of them byte-identical forks that nothing kept in sync.
7
+ * The same address resolved differently depending on which copy a code path happened to
8
+ * reach. This module is the single source of truth; `packages/shared` is the only leaf
9
+ * package all eight can reach without inverting a dependency edge.
10
+ *
11
+ * WHY TWO TIERS, and why they must not be collapsed into one:
12
+ *
13
+ * The callers are not asking the same question, and the cost of a wrong answer is
14
+ * asymmetric in opposite directions.
15
+ *
16
+ * - `FREEMAIL_DOMAINS` (core) answers "may I treat this domain as a shared EMPLOYER?"
17
+ * Two consumers ride on that answer, and they fail in OPPOSITE directions, which is
18
+ * why this tier is conservative rather than generous:
19
+ *
20
+ * 1. The company-scope reply stop (`settings.stop_on_reply_scope = "company_domain"`)
21
+ * terminalizes an enrollment's colleagues by matching domain. A host wrongly IN
22
+ * this tier is harmless here; a host wrongly OUT of it stops strangers who merely
23
+ * share a mail host — real, customer-visible damage.
24
+ * 2. Company derivation from a person's email domain
25
+ * (`deriveCompanyDomainFromEmail`, `packages/tenant-db/src/table-link-plan.ts`)
26
+ * drops freemail before offering a domain as a company join key. Here the
27
+ * polarity REVERSES: a host wrongly OUT of this tier makes OXYGEN mint a company
28
+ * record for a mail provider, so every @<host> person collapses onto one fake
29
+ * account. Where that derivation is wired into record creation, removing a host
30
+ * from this tier silently starts creating accounts for it.
31
+ *
32
+ * So a host must be in this tier to be safe for BOTH, and the safe direction for an
33
+ * edit is to ADD, never to remove or relocate to the broad tier. Extend it only with
34
+ * hosts that are unambiguously consumer mailboxes — and note the ORDER caveat in
35
+ * `packages/tenant-db/src/freemail-domains.ts`, because a core-tier edit also changes
36
+ * a formula string already persisted in every tenant's column definitions.
37
+ *
38
+ * - `PERSONAL_EMAIL_DOMAINS` (broad) answers "is this domain a poor stand-in for a
39
+ * COMPANY identity?" — used to skip fetching a workspace icon, to reject a personal
40
+ * host typed into the marketing hero form, to avoid picking a personal address as a
41
+ * person's work email, and to avoid treating a meeting participant's mail host as their
42
+ * employer. Here a false positive costs nothing (we simply decline to guess a company)
43
+ * while a miss produces a junk company/icon. So this tier is the superset, including
44
+ * regional and privacy-focused providers.
45
+ *
46
+ * The broad tier is a strict superset of the core tier by construction below, so a host
47
+ * can never be "freemail but not personal" — exactly the incoherence the forks had
48
+ * developed (the old core list carried `t-online.de`; several broad lists did not).
49
+ *
50
+ * The broad tier is the union of all eight former lists. Most of its bulk comes from the
51
+ * old `packages/formula` copy, which was by far the best maintained and is the only one
52
+ * that covered country-specific variants (`yahoo.co.uk`, `hotmail.fr`, `orange.fr`,
53
+ * `wp.pl`, `uol.com.br` …) and ISP mailboxes (`comcast.net`, `btinternet.com`). Every
54
+ * other caller was silently missing those.
55
+ */
56
+ // Tier 1. Unambiguous global consumer mailboxes. Load-bearing for the company-scope
57
+ // reply stop — read the note above before adding anything here.
58
+ const CORE_FREEMAIL_DOMAINS = [
59
+ "gmail.com",
60
+ "googlemail.com",
61
+ "outlook.com",
62
+ "hotmail.com",
63
+ "live.com",
64
+ "msn.com",
65
+ "yahoo.com",
66
+ "ymail.com",
67
+ "icloud.com",
68
+ "me.com",
69
+ "aol.com",
70
+ "gmx.com",
71
+ "gmx.net",
72
+ "gmx.de",
73
+ "web.de",
74
+ "proton.me",
75
+ "protonmail.com",
76
+ "mail.com",
77
+ "zoho.com",
78
+ "yandex.com",
79
+ "yandex.ru",
80
+ "t-online.de",
81
+ ];
82
+ // Tier 2 additions: country variants of the global providers, regional hosts (qq.com,
83
+ // naver.com, wp.pl, libero.it, uol.com.br), ISP mailboxes (comcast.net, btinternet.com,
84
+ // telenet.be), privacy hosts (pm.me, tutanota.com, duck.com) and prosumer ones
85
+ // (fastmail, hey.com, mac.com). Confident enough to refuse to call any of them a
86
+ // company; NOT confident enough to let them drive a reply-stop decision — an ISP or
87
+ // country-TLD host is exactly where a "shared employer" guess goes wrong.
88
+ // Sorted, because this list is long enough that grouping by theme would rot.
89
+ const ADDITIONAL_PERSONAL_EMAIL_DOMAINS = [
90
+ "126.com", "163.com", "att.net", "bellsouth.net",
91
+ "bigpond.com", "bluewin.ch", "blueyonder.co.uk", "bol.com.br",
92
+ "btinternet.com", "charter.net", "comcast.net", "cox.net",
93
+ "daum.net", "duck.com", "earthlink.net", "fastmail.com",
94
+ "fastmail.fm", "free.fr", "freenet.de", "gmx.at",
95
+ "gmx.ch", "hanmail.net", "hey.com", "home.nl",
96
+ "hotmail.co.uk", "hotmail.de", "hotmail.es", "hotmail.fr",
97
+ "hotmail.it", "interia.pl", "kpnmail.nl", "laposte.net",
98
+ "libero.it", "live.co.uk", "live.de", "live.fr",
99
+ "mac.com", "mail.ru", "naver.com", "o2.pl",
100
+ "onet.pl", "optusnet.com.au", "orange.fr", "outlook.co.uk",
101
+ "outlook.de", "outlook.es", "outlook.fr", "pm.me",
102
+ "qq.com", "rediffmail.com", "rocketmail.com", "rogers.com",
103
+ "sbcglobal.net", "seznam.cz", "sfr.fr", "shaw.ca",
104
+ "sina.com", "sky.com", "skynet.be", "sympatico.ca",
105
+ "talktalk.net", "telenet.be", "telus.net", "terra.com.br",
106
+ "tuta.io", "tutanota.com", "uol.com.br", "verizon.net",
107
+ "virgilio.it", "virginmedia.com", "wanadoo.fr", "wp.pl",
108
+ "yahoo.ca", "yahoo.co.jp", "yahoo.co.uk", "yahoo.com.au",
109
+ "yahoo.de", "yahoo.es", "yahoo.fr", "yahoo.it",
110
+ "ziggo.nl",
111
+ ];
112
+ /** Tier 1 — see the module note. Small on purpose. */
113
+ export const FREEMAIL_DOMAINS = new Set(CORE_FREEMAIL_DOMAINS);
114
+ /** Tier 2 — a strict superset of {@link FREEMAIL_DOMAINS}. */
115
+ export const PERSONAL_EMAIL_DOMAINS = new Set([
116
+ ...CORE_FREEMAIL_DOMAINS,
117
+ ...ADDITIONAL_PERSONAL_EMAIL_DOMAINS,
118
+ ]);
119
+ /**
120
+ * Reduce an address or a bare domain to a comparable mail host: lowercased, trimmed,
121
+ * everything before the last '@' dropped, and a trailing FQDN dot removed. Returns null
122
+ * for junk/empty/nullish input so callers can distinguish "not a host" from "not in the
123
+ * set". Previously each fork normalized differently — one did full address handling, two
124
+ * only lowercased a bare domain — so the same input could resolve differently depending
125
+ * on which copy you happened to call.
126
+ */
127
+ export function normalizeMailHost(value) {
128
+ if (typeof value !== "string")
129
+ return null;
130
+ let host = value.trim().toLowerCase();
131
+ if (!host)
132
+ return null;
133
+ const at = host.lastIndexOf("@");
134
+ if (at >= 0)
135
+ host = host.slice(at + 1);
136
+ host = host.replace(/\.+$/, "");
137
+ return host || null;
138
+ }
139
+ /**
140
+ * True when the host is a tier-1 consumer mailbox, i.e. it NEVER identifies a shared
141
+ * employer. Accepts a bare domain or a full address. Unknown input returns false: an
142
+ * unrecognized domain is treated as a company domain, which keeps the company-scope
143
+ * stop's safe default identical to lead-scope.
144
+ */
145
+ export function isFreemailDomain(domain) {
146
+ const host = normalizeMailHost(domain);
147
+ return host !== null && FREEMAIL_DOMAINS.has(host);
148
+ }
149
+ /**
150
+ * True when the host is a personal mailbox of any kind (tier 2). Use this for "should I
151
+ * infer a company from this domain?"; use {@link isFreemailDomain} when a false positive
152
+ * would wrongly group unrelated people as colleagues.
153
+ */
154
+ export function isPersonalEmailDomain(domain) {
155
+ const host = normalizeMailHost(domain);
156
+ return host !== null && PERSONAL_EMAIL_DOMAINS.has(host);
157
+ }
@@ -423,3 +423,4 @@ export declare function normalizeFutureSignupLifecycleProjectionEnvelope(value:
423
423
  export declare function normalizeFutureSignupLifecycleProjectionV2Envelope(value: unknown): NormalizedFutureSignupLifecycleProjectionV2Envelope;
424
424
  export declare function normalizeFutureSignupLifecycleProjectionDestinationApplyOptions(value: unknown): NormalizedFutureSignupLifecycleProjectionDestinationApplyOptions;
425
425
  export declare function normalizeFutureSignupLifecycleProjectionV2DestinationApplyOptions(value: unknown): NormalizedFutureSignupLifecycleProjectionV2DestinationApplyOptions;
426
+ export declare function codeUnitCompare(left: string, right: string): number;
@@ -1336,7 +1336,7 @@ function canonicalize(value) {
1336
1336
  }
1337
1337
  return value;
1338
1338
  }
1339
- function codeUnitCompare(left, right) {
1339
+ export function codeUnitCompare(left, right) {
1340
1340
  return left < right ? -1 : left > right ? 1 : 0;
1341
1341
  }
1342
1342
  function invalid(path, rule) {
@@ -22,6 +22,17 @@
22
22
  */
23
23
  export type HostedAiLevel = "low" | "medium" | "high";
24
24
  export type HostedAiUseCase = "ai_column" | "copilot" | "agent";
25
+ /**
26
+ * The Copilot's own level set. `auto` is the only tier a customer can land on
27
+ * since 2026-09-21 ("Oxygen Auto"); low/medium/high survive because sessions
28
+ * created before that release carry them in `copilot_sessions.reasoning_level`
29
+ * and must keep resolving to the model they were priced against.
30
+ *
31
+ * Deliberately NOT folded into `HostedAiLevel`: that type is shared with
32
+ * `AiReasoningLevel` and with the ai_column/agent registries, neither of which
33
+ * has an `auto` entry, so widening it would force two fictional rows.
34
+ */
35
+ export type CopilotAiLevel = HostedAiLevel | "auto";
25
36
  export type HostedAiModelSpec = {
26
37
  /** OpenRouter model id, e.g. "deepseek/deepseek-v4-flash". */
27
38
  model: string;
@@ -44,7 +55,11 @@ export type HostedAiModelSpec = {
44
55
  /** Upper bound on completion tokens requested for this tier. */
45
56
  maxOutputTokens: number;
46
57
  };
47
- export declare const HOSTED_AI_MODEL_REGISTRY: Record<HostedAiUseCase, Record<HostedAiLevel, HostedAiModelSpec>>;
58
+ export declare const HOSTED_AI_MODEL_REGISTRY: {
59
+ ai_column: Record<HostedAiLevel, HostedAiModelSpec>;
60
+ copilot: Record<CopilotAiLevel, HostedAiModelSpec>;
61
+ agent: Record<HostedAiLevel, HostedAiModelSpec>;
62
+ };
48
63
  /**
49
64
  * What a MANAGED AI column's reasoning tier is called in front of a customer.
50
65
  *
@@ -60,8 +75,49 @@ export declare const HOSTED_AI_MODEL_REGISTRY: Record<HostedAiUseCase, Record<Ho
60
75
  * their own key, so the real model id is the correct thing to show.
61
76
  */
62
77
  export declare const MANAGED_AI_TIER_LABELS: Record<HostedAiLevel, string>;
63
- /** The reasoning tier the hosted copilot runs at by default. */
64
- export declare const COPILOT_DEFAULT_LEVEL: HostedAiLevel;
78
+ /**
79
+ * What the Copilot calls its tiers.
80
+ *
81
+ * Separate from MANAGED_AI_TIER_LABELS rather than folded into it because the
82
+ * Copilot's level set is not the AI column's: only the Copilot has `auto`, and
83
+ * only the Copilot has stopped offering a choice. AI columns and Agents still
84
+ * sell Fast/Balanced/Max and still mean it.
85
+ *
86
+ * Every current session reads "Oxygen Auto". The other three exist so a session
87
+ * created before 2026-09-21 still renders as the thing its owner picked, rather
88
+ * than being relabelled under them.
89
+ */
90
+ export declare const COPILOT_TIER_LABELS: Record<CopilotAiLevel, string>;
91
+ /**
92
+ * The tier every new Copilot session gets. There is no longer a picker: "Oxygen
93
+ * Auto" is the whole customer-facing choice (Philipp, 2026-09-21).
94
+ *
95
+ * It was `high` until then, which is why 83% of production sessions ran the most
96
+ * expensive tier -- 110 of 132 sessions across 69 of 81 tenants -- while the
97
+ * composer's own dropdown told the user that medium was "The default for most
98
+ * GTM work". Nobody chose that; we defaulted them into it.
99
+ */
100
+ export declare const COPILOT_DEFAULT_LEVEL: CopilotAiLevel;
101
+ /**
102
+ * The tier the Copilot's ERRANDS run at: sub-agent children, compaction
103
+ * summaries, and auto-titles.
104
+ *
105
+ * This is the only place a model may differ from the session's own, and it is
106
+ * safe for one reason: each of those builds a FRESH context rather than
107
+ * continuing the main transcript -- a child constructs its own Agent and
108
+ * messages (`packages/agent-runtime/src/subagents.ts`), and the summarizer sends
109
+ * a two-message prompt with no tools
110
+ * (`packages/agent-runtime/src/strands-runtime.ts` summarizeForCompaction). A
111
+ * cold context has no prompt cache to lose and no transcript to corrupt, so the
112
+ * switch costs nothing. Switching the main thread would cost both.
113
+ *
114
+ * It matters because delegation is habitual and currently doubles the price of a
115
+ * turn: measured over 30 days to 2026-09-21, the 26% of turns that fired
116
+ * `subagent_run` produced 45% of all Copilot credit burn (1,805 credits/turn
117
+ * against 786), because every child inherited the parent's model through the
118
+ * parent's own `callModel`.
119
+ */
120
+ export declare const COPILOT_ERRAND_LEVEL: CopilotAiLevel;
65
121
  export declare const AGENT_DEFAULT_LEVEL: HostedAiLevel;
66
122
  /**
67
123
  * Resolve the model spec for a hosted-AI use case + reasoning level.
@@ -87,7 +143,7 @@ export declare const AGENT_DEFAULT_LEVEL: HostedAiLevel;
87
143
  export declare function findHostedAiModelSpec(model: string, preferredUseCase?: HostedAiUseCase): HostedAiModelSpec | null;
88
144
  export declare function resolveHostedAiModel(input: {
89
145
  useCase: HostedAiUseCase;
90
- level: HostedAiLevel;
146
+ level: CopilotAiLevel;
91
147
  env?: Record<string, string | undefined>;
92
148
  }): HostedAiModelSpec;
93
149
  /**