@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.
- package/README.md +1 -1
- package/dist/admin-primary-providers-render.d.ts +0 -2
- package/dist/admin-primary-providers-render.js +1 -1
- package/dist/browser-login.js +1 -4
- package/dist/column-decision-options.d.ts +20 -0
- package/dist/column-decision-options.js +54 -0
- package/dist/command-manifest.d.ts +3 -2
- package/dist/command-manifest.js +25 -2
- package/dist/credentials.d.ts +1 -1
- package/dist/functions-commands.js +13 -9
- package/dist/help.d.ts +8 -0
- package/dist/help.js +46 -0
- package/dist/index.js +2751 -242
- package/dist/knowledge-mirror.d.ts +2 -2
- package/dist/runtime.d.ts +0 -15
- package/dist/runtime.js +1 -1
- package/dist/search-ai-filter-notice.d.ts +17 -0
- package/dist/search-ai-filter-notice.js +38 -0
- package/dist/session.d.ts +4 -3
- package/dist/skills.d.ts +8 -7
- package/dist/skills.js +58 -20
- package/dist/transcript.d.ts +2 -1
- package/dist/util.d.ts +10 -1
- package/dist/util.js +14 -2
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +296 -140
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +9 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +34 -0
- package/node_modules/@oxygen/formula/dist/coerce.d.ts +10 -0
- package/node_modules/@oxygen/formula/dist/coerce.js +10 -0
- package/node_modules/@oxygen/formula/dist/formula-functions.js +65 -0
- package/node_modules/@oxygen/formula/dist/hash.d.ts +19 -0
- package/node_modules/@oxygen/formula/dist/hash.js +199 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +6 -1
- package/node_modules/@oxygen/formula/dist/value-cleaners.js +10 -26
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +13 -0
- package/node_modules/@oxygen/shared/dist/array-utils.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/array-utils.js +11 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +99 -22
- package/node_modules/@oxygen/shared/dist/billing.js +195 -40
- package/node_modules/@oxygen/shared/dist/byok-connect.js +5 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +27 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +311 -28
- package/node_modules/@oxygen/shared/dist/cli-http-error.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/cli-http-error.js +8 -0
- package/node_modules/@oxygen/shared/dist/cli-result.js +1 -0
- package/node_modules/@oxygen/shared/dist/column-autofill.d.ts +52 -0
- package/node_modules/@oxygen/shared/dist/column-autofill.js +62 -0
- package/node_modules/@oxygen/shared/dist/column-decision.d.ts +50 -0
- package/node_modules/@oxygen/shared/dist/column-decision.js +228 -0
- package/node_modules/@oxygen/shared/dist/column-output-fields.js +2 -6
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.d.ts +113 -0
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.js +548 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +6 -6
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +6 -6
- package/node_modules/@oxygen/shared/dist/cutover-freeze.d.ts +26 -0
- package/node_modules/@oxygen/shared/dist/cutover-freeze.js +52 -0
- package/node_modules/@oxygen/shared/dist/data-suppliers.d.ts +57 -0
- package/node_modules/@oxygen/shared/dist/data-suppliers.js +59 -0
- package/node_modules/@oxygen/shared/dist/deploy-env.d.ts +74 -0
- package/node_modules/@oxygen/shared/dist/deploy-env.js +82 -0
- package/node_modules/@oxygen/shared/dist/dnc-rules.d.ts +130 -0
- package/node_modules/@oxygen/shared/dist/dnc-rules.js +221 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.d.ts +107 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.js +809 -0
- package/node_modules/@oxygen/shared/dist/error-message.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/error-message.js +3 -0
- package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -3
- package/node_modules/@oxygen/shared/dist/external-write-policy.d.ts +33 -0
- package/node_modules/@oxygen/shared/dist/external-write-policy.js +68 -0
- package/node_modules/@oxygen/shared/dist/format-percent.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/format-percent.js +13 -0
- package/node_modules/@oxygen/shared/dist/freemail-domains.d.ts +81 -0
- package/node_modules/@oxygen/shared/dist/freemail-domains.js +157 -0
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +1 -1
- package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +60 -4
- package/node_modules/@oxygen/shared/dist/hosted-ai.js +125 -10
- package/node_modules/@oxygen/shared/dist/index.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/index.js +15 -0
- package/node_modules/@oxygen/shared/dist/json-path.js +1 -3
- package/node_modules/@oxygen/shared/dist/knowledge-bases.js +1 -3
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +2 -2
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +2 -2
- package/node_modules/@oxygen/shared/dist/langfuse.d.ts +44 -1
- package/node_modules/@oxygen/shared/dist/langfuse.js +416 -14
- package/node_modules/@oxygen/shared/dist/linkedin-countries.d.ts +33 -0
- package/node_modules/@oxygen/shared/dist/linkedin-countries.js +361 -0
- package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.js +276 -0
- package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.js +5 -0
- package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.d.ts +44 -0
- package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.js +116 -0
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +96 -0
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +123 -0
- package/node_modules/@oxygen/shared/dist/llm-durable-capture.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/llm-durable-capture.js +89 -0
- package/node_modules/@oxygen/shared/dist/llm-prompts.d.ts +75 -0
- package/node_modules/@oxygen/shared/dist/llm-prompts.js +161 -0
- package/node_modules/@oxygen/shared/dist/log-sink-selector.d.ts +39 -0
- package/node_modules/@oxygen/shared/dist/log-sink-selector.js +56 -0
- package/node_modules/@oxygen/shared/dist/log.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/log.js +6 -1
- package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +90 -0
- package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.js +130 -0
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/object-storage.js +21 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.js +73 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +79 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +366 -0
- package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +23 -22
- package/node_modules/@oxygen/shared/dist/plan-limits.js +45 -18
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +48 -41
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +36 -25
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +22 -22
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +40 -34
- package/node_modules/@oxygen/shared/dist/product-analytics-environment.js +9 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +36 -2
- package/node_modules/@oxygen/shared/dist/product-analytics-events.js +36 -1
- package/node_modules/@oxygen/shared/dist/rate-window.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/rate-window.js +8 -0
- package/node_modules/@oxygen/shared/dist/redaction.js +4 -1
- package/node_modules/@oxygen/shared/dist/research-output-contract.js +1 -3
- package/node_modules/@oxygen/shared/dist/scraper-lane-credential.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/scraper-lane-credential.js +23 -0
- package/node_modules/@oxygen/shared/dist/search-vocab.js +4 -5
- package/node_modules/@oxygen/shared/dist/select-options.js +6 -1
- package/node_modules/@oxygen/shared/dist/sequence-failures.js +1 -5
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +23 -0
- package/node_modules/@oxygen/shared/dist/sequences.js +115 -5
- package/node_modules/@oxygen/shared/dist/signup-lead-payload.d.ts +80 -0
- package/node_modules/@oxygen/shared/dist/signup-lead-payload.js +198 -0
- package/node_modules/@oxygen/shared/dist/social-capabilities.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/social-capabilities.js +25 -16
- package/node_modules/@oxygen/shared/dist/social-post-metrics-core.d.ts +32 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-core.js +32 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.d.ts +31 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.js +103 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-series.d.ts +96 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-series.js +213 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-x.d.ts +13 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-x.js +78 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics.d.ts +36 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics.js +51 -0
- package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +22 -10
- package/node_modules/@oxygen/shared/dist/spend-safety.js +15 -21
- package/node_modules/@oxygen/shared/dist/sql-rows.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/sql-rows.js +3 -0
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +36 -0
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +184 -0
- package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.d.ts +41 -0
- package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.js +44 -0
- package/node_modules/@oxygen/shared/dist/table-limits.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/table-limits.js +3 -0
- package/node_modules/@oxygen/shared/dist/telemetry-export-observer.d.ts +94 -0
- package/node_modules/@oxygen/shared/dist/telemetry-export-observer.js +298 -0
- package/node_modules/@oxygen/shared/dist/telemetry.d.ts +11 -0
- package/node_modules/@oxygen/shared/dist/telemetry.js +28 -2
- package/node_modules/@oxygen/shared/dist/type-guards.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/type-guards.js +35 -0
- package/node_modules/@oxygen/shared/dist/ugc.d.ts +22 -11
- package/node_modules/@oxygen/shared/dist/ugc.js +10 -0
- package/node_modules/@oxygen/shared/dist/value-readers.d.ts +21 -0
- package/node_modules/@oxygen/shared/dist/value-readers.js +59 -0
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/version.generated.js +2 -0
- package/node_modules/@oxygen/shared/dist/version.js +8 -1
- package/node_modules/@oxygen/shared/dist/workspace-event-catalog.js +0 -23
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +29 -0
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +31 -0
- package/node_modules/@oxygen/shared/package.json +59 -0
- package/node_modules/@oxygen/workflows/dist/graph/expression.js +2 -5
- package/node_modules/@oxygen/workflows/dist/graph/params.js +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workspace do-not-contact RULE CATALOG: which events may put somebody on the
|
|
3
|
+
* DNC list, and what the workspace has decided each one should do.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. Suppression ENFORCEMENT has always been absolute and layered —
|
|
6
|
+
* enrollment, plan and a dispatch-time re-check per channel — and none of that is
|
|
7
|
+
* configurable, on purpose: once an identity is on a ledger it always blocks. What
|
|
8
|
+
* was equally hardcoded, and should not have been, is ENTRY. Six writers decided
|
|
9
|
+
* unilaterally who lands on the list, and one of them was blunt enough to be a
|
|
10
|
+
* product complaint in its own right: ANY inbound LinkedIn message suppressed that
|
|
11
|
+
* person org-wide, forever, so a lead who replied "sure, send me pricing" could
|
|
12
|
+
* never be enrolled in another campaign again.
|
|
13
|
+
*
|
|
14
|
+
* The split this module draws is therefore: the workspace owns WHO GETS ON the
|
|
15
|
+
* list; the platform still owns WHAT A LIST ENTRY DOES. A rule resolves to one of
|
|
16
|
+
* three effects:
|
|
17
|
+
*
|
|
18
|
+
* off — the event is observed and recorded as it always was, but writes no
|
|
19
|
+
* suppression. Nothing else about the event changes.
|
|
20
|
+
* stop_only — end THIS enrollment, do not write the permanent ledger row. The
|
|
21
|
+
* missing middle: "a reply stops the cadence" without "blacklist this
|
|
22
|
+
* human from every future campaign".
|
|
23
|
+
* suppress — stop and write the ledger row. The default for every rule that
|
|
24
|
+
* shipped before this catalog existed, which is what makes the
|
|
25
|
+
* defaults a byte-for-byte reproduction of previous behaviour.
|
|
26
|
+
*
|
|
27
|
+
* DEFAULTS ARE THE OLD BEHAVIOUR. Every pre-existing rule defaults to `suppress`
|
|
28
|
+
* with the exact reason its writer used to hardcode, and every rule introduced with
|
|
29
|
+
* this catalog defaults to `off`. A workspace that never opens the Rules page keeps
|
|
30
|
+
* precisely the product it had, and a policy read that fails resolves to these
|
|
31
|
+
* defaults rather than dropping a suppression on the floor.
|
|
32
|
+
*
|
|
33
|
+
* COMPLIANCE RULES ARE CONFIGURABLE, AND FLAGGED. `email_unsubscribe_click` and
|
|
34
|
+
* `email_hard_bounce` carry `compliance: true`. That flag does not lock them — the
|
|
35
|
+
* product owner's decision (2026-09-19) is that a workspace may set any rule to any
|
|
36
|
+
* allowed effect — it marks the two whose surfaces must say plainly what turning
|
|
37
|
+
* them down means: an explicit unsubscribe request that is not honoured, and a
|
|
38
|
+
* hard-bounced address that keeps being mailed from the shared pool (which is also
|
|
39
|
+
* the numerator of the mailbox auto-pause). The warning is the product's job; the
|
|
40
|
+
* decision is the customer's.
|
|
41
|
+
*/
|
|
42
|
+
/** What a rule does when its event fires. */
|
|
43
|
+
export const DNC_RULE_EFFECTS = ["off", "stop_only", "suppress"];
|
|
44
|
+
export const DNC_RULE_EFFECT_LABELS = {
|
|
45
|
+
off: "Do nothing",
|
|
46
|
+
stop_only: "Stop this enrollment only",
|
|
47
|
+
suppress: "Stop and add to do-not-contact",
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Which ledger a rule writes when it resolves to `suppress`. This decides which
|
|
51
|
+
* reason enum the configured reason is validated against, so a rule can never be
|
|
52
|
+
* saved with a reason its target table's CHECK constraint would reject at write
|
|
53
|
+
* time — the failure has to land on the person changing the setting, not on the
|
|
54
|
+
* inbound webhook hours later.
|
|
55
|
+
*/
|
|
56
|
+
export const DNC_RULE_LEDGERS = ["email", "contact", "phone"];
|
|
57
|
+
const ALL_EFFECTS = DNC_RULE_EFFECTS;
|
|
58
|
+
const OFF_OR_SUPPRESS = ["off", "suppress"];
|
|
59
|
+
/**
|
|
60
|
+
* The writers that existed before this catalog, each pinned to the reason and
|
|
61
|
+
* ledger it used to hardcode. Changing a `defaultEffect` or `defaultReason` here
|
|
62
|
+
* silently changes every workspace that has not overridden that rule, which is why
|
|
63
|
+
* `dnc-rules.test.ts` pins every one of them.
|
|
64
|
+
*
|
|
65
|
+
* `whatsapp_reply` is the LinkedIn rule's twin and was found the same way: an
|
|
66
|
+
* inbound WhatsApp message suppressed the lead org-wide with no way to say
|
|
67
|
+
* otherwise. Its DNC identity is the normalized chat JID rather than a lead
|
|
68
|
+
* provider id, because `whatsapp_message` is excluded from lead resolution — see
|
|
69
|
+
* the note in whatsapp-inbox-service.ts about why it is deliberately NOT the
|
|
70
|
+
* phone ledger.
|
|
71
|
+
*/
|
|
72
|
+
export const DNC_BUILTIN_RULES = [
|
|
73
|
+
{
|
|
74
|
+
key: "email_hard_bounce",
|
|
75
|
+
label: "Email hard bounce",
|
|
76
|
+
description: "A message to this address bounced permanently.",
|
|
77
|
+
ledger: "email",
|
|
78
|
+
defaultEffect: "suppress",
|
|
79
|
+
defaultReason: "hard_bounce",
|
|
80
|
+
allowedEffects: ALL_EFFECTS,
|
|
81
|
+
compliance: true,
|
|
82
|
+
group: "automatic",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: "email_unsubscribe_click",
|
|
86
|
+
label: "One-click unsubscribe",
|
|
87
|
+
description: "The recipient used the unsubscribe link in one of your emails.",
|
|
88
|
+
ledger: "email",
|
|
89
|
+
defaultEffect: "suppress",
|
|
90
|
+
defaultReason: "unsubscribe",
|
|
91
|
+
allowedEffects: ALL_EFFECTS,
|
|
92
|
+
compliance: true,
|
|
93
|
+
group: "automatic",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
key: "email_optout_phrase",
|
|
97
|
+
label: "Opt-out wording in an email reply",
|
|
98
|
+
description: 'The reply contains an explicit opt-out phrase such as "unsubscribe" or "take me off".',
|
|
99
|
+
ledger: "email",
|
|
100
|
+
defaultEffect: "suppress",
|
|
101
|
+
defaultReason: "unsubscribe",
|
|
102
|
+
allowedEffects: ALL_EFFECTS,
|
|
103
|
+
group: "automatic",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
key: "linkedin_reply",
|
|
107
|
+
label: "LinkedIn reply received",
|
|
108
|
+
description: "Any inbound LinkedIn message from a lead, whatever it says.",
|
|
109
|
+
ledger: "contact",
|
|
110
|
+
defaultEffect: "suppress",
|
|
111
|
+
defaultReason: "replied",
|
|
112
|
+
allowedEffects: ALL_EFFECTS,
|
|
113
|
+
group: "automatic",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
key: "whatsapp_reply",
|
|
117
|
+
label: "WhatsApp reply received",
|
|
118
|
+
description: "Any inbound WhatsApp message from a lead, whatever it says.",
|
|
119
|
+
ledger: "contact",
|
|
120
|
+
defaultEffect: "suppress",
|
|
121
|
+
defaultReason: "replied",
|
|
122
|
+
allowedEffects: ALL_EFFECTS,
|
|
123
|
+
group: "automatic",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
key: "call_do_not_call",
|
|
127
|
+
label: 'Call marked "do not call"',
|
|
128
|
+
description: "A rep set the do-not-call disposition on a call task.",
|
|
129
|
+
ledger: "phone",
|
|
130
|
+
defaultEffect: "suppress",
|
|
131
|
+
defaultReason: "do_not_call",
|
|
132
|
+
allowedEffects: OFF_OR_SUPPRESS,
|
|
133
|
+
group: "automatic",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
key: "call_wrong_number",
|
|
137
|
+
label: 'Call marked "wrong number"',
|
|
138
|
+
description: "A rep set the wrong-number disposition on a call task.",
|
|
139
|
+
ledger: "phone",
|
|
140
|
+
defaultEffect: "suppress",
|
|
141
|
+
defaultReason: "wrong_number",
|
|
142
|
+
allowedEffects: OFF_OR_SUPPRESS,
|
|
143
|
+
group: "automatic",
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
/**
|
|
147
|
+
* Reply-classification rules are addressed by label key, so an org's own inbox
|
|
148
|
+
* labels get rules on exactly the same footing as the shipped ones. The prefix
|
|
149
|
+
* keeps that namespace from ever colliding with a builtin key.
|
|
150
|
+
*/
|
|
151
|
+
export const DNC_REPLY_STATUS_RULE_PREFIX = "reply_status:";
|
|
152
|
+
export function replyStatusRuleKey(labelKey) {
|
|
153
|
+
return `${DNC_REPLY_STATUS_RULE_PREFIX}${labelKey}`;
|
|
154
|
+
}
|
|
155
|
+
export function replyStatusLabelKey(ruleKey) {
|
|
156
|
+
if (!ruleKey.startsWith(DNC_REPLY_STATUS_RULE_PREFIX))
|
|
157
|
+
return null;
|
|
158
|
+
const labelKey = ruleKey.slice(DNC_REPLY_STATUS_RULE_PREFIX.length).trim();
|
|
159
|
+
return labelKey.length > 0 ? labelKey : null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Build the rule definition for one inbox label. `ledger: "email"` is not a claim
|
|
163
|
+
* that the rule is email-only — it names which reason enum the reason is validated
|
|
164
|
+
* against. The applier picks the ledger per identity at write time, because the
|
|
165
|
+
* same classified conversation can carry an email address, a LinkedIn person, or
|
|
166
|
+
* both, and a classified LinkedIn DM must be able to suppress the LinkedIn person.
|
|
167
|
+
*/
|
|
168
|
+
export function replyStatusRule(label) {
|
|
169
|
+
const name = label.name?.trim() || label.key;
|
|
170
|
+
return {
|
|
171
|
+
key: replyStatusRuleKey(label.key),
|
|
172
|
+
label: `Reply classified "${name}"`,
|
|
173
|
+
description: `A reply in the inbox was classified as ${name}.`,
|
|
174
|
+
ledger: "email",
|
|
175
|
+
// New behaviour ships off. Classification is a model judgement, so turning one
|
|
176
|
+
// of these on is a deliberate act, never something a workspace wakes up to.
|
|
177
|
+
defaultEffect: "off",
|
|
178
|
+
defaultReason: "unsubscribe",
|
|
179
|
+
allowedEffects: ALL_EFFECTS,
|
|
180
|
+
group: "reply_classification",
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const BUILTIN_BY_KEY = new Map(DNC_BUILTIN_RULES.map((rule) => [rule.key, rule]));
|
|
184
|
+
/** The builtin definition for a key, or null when the key is not a builtin. */
|
|
185
|
+
export function builtinDncRule(key) {
|
|
186
|
+
return BUILTIN_BY_KEY.get(key) ?? null;
|
|
187
|
+
}
|
|
188
|
+
export function isDncRuleEffect(value) {
|
|
189
|
+
return typeof value === "string" && DNC_RULE_EFFECTS.includes(value);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Resolve a stored rule key to its definition. A `reply_status:*` key resolves even
|
|
193
|
+
* when the org has since renamed or archived the label, so an override never
|
|
194
|
+
* becomes unreadable (and therefore unresettable) because a label moved.
|
|
195
|
+
*/
|
|
196
|
+
export function resolveDncRuleDefinition(key, labels) {
|
|
197
|
+
const builtin = builtinDncRule(key);
|
|
198
|
+
if (builtin)
|
|
199
|
+
return builtin;
|
|
200
|
+
const labelKey = replyStatusLabelKey(key);
|
|
201
|
+
if (!labelKey)
|
|
202
|
+
return null;
|
|
203
|
+
const label = labels?.find((candidate) => candidate.key === labelKey);
|
|
204
|
+
return replyStatusRule(label ?? { key: labelKey });
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* The full catalog a surface renders: the six builtins, then one rule per inbox
|
|
208
|
+
* label the workspace can classify a reply as.
|
|
209
|
+
*/
|
|
210
|
+
export function dncRuleCatalog(labels = []) {
|
|
211
|
+
const seen = new Set();
|
|
212
|
+
const replyRules = [];
|
|
213
|
+
for (const label of labels) {
|
|
214
|
+
const key = label.key?.trim();
|
|
215
|
+
if (!key || seen.has(key))
|
|
216
|
+
continue;
|
|
217
|
+
seen.add(key);
|
|
218
|
+
replyRules.push(replyStatusRule(label));
|
|
219
|
+
}
|
|
220
|
+
return [...DNC_BUILTIN_RULES, ...replyRules];
|
|
221
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export declare const ENRICHMENT_INTENT_GROUPS: readonly ["person", "contact", "company", "decision_maker", "signal"];
|
|
2
|
+
export type EnrichmentIntentGroup = (typeof ENRICHMENT_INTENT_GROUPS)[number];
|
|
3
|
+
export declare const ENRICHMENT_INTENT_GROUP_LABELS: Record<EnrichmentIntentGroup, string>;
|
|
4
|
+
export type EnrichmentIntentKind = "profile" | "enrichment" | "verification" | "search" | "signal" | "formula" | "template";
|
|
5
|
+
/**
|
|
6
|
+
* default — a managed provider chain runs it today; the add path is exact.
|
|
7
|
+
* selectable — wired and reachable, but not offered as a one-click default.
|
|
8
|
+
* no_default — declared, but no managed default chain yet (an honest refusal
|
|
9
|
+
* with the reason and, where known, the candidate providers).
|
|
10
|
+
* coming_soon — founder-named for a later slice; projected with no add path.
|
|
11
|
+
* rejected — deliberately not offered; carries the decision id.
|
|
12
|
+
*/
|
|
13
|
+
export type EnrichmentIntentStatus = "default" | "selectable" | "no_default" | "coming_soon" | "rejected";
|
|
14
|
+
export type EnrichmentIntentOutputField = {
|
|
15
|
+
key: string;
|
|
16
|
+
label: string;
|
|
17
|
+
/** `company.*` / `person.*` semantic type (packages/tenant-db/src/model.ts) or null. */
|
|
18
|
+
semanticType: string | null;
|
|
19
|
+
dataType: "text" | "numeric" | "boolean" | "jsonb" | "timestamptz";
|
|
20
|
+
/** Read from another field's payload at 0 credits; never fetched on its own. */
|
|
21
|
+
derived?: true;
|
|
22
|
+
};
|
|
23
|
+
export type EnrichmentIntentSurfaces = {
|
|
24
|
+
/** `columns add --capability <id>` / MCP column capability. */
|
|
25
|
+
capability?: string;
|
|
26
|
+
/** `columns add --preset <id>` / MCP preset. */
|
|
27
|
+
preset?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Company field-catalog keys this row materialises: they point a field at its
|
|
30
|
+
* row and price a waterfall row. Never a preset flag — no preset takes a field
|
|
31
|
+
* list since the founder decision of 2026-09-24.
|
|
32
|
+
*/
|
|
33
|
+
companyFields?: readonly string[];
|
|
34
|
+
/** One-shot CLI verb, e.g. "find email". */
|
|
35
|
+
find?: string;
|
|
36
|
+
/** Managed Functions library id (apps/web/src/lib/managed-functions/catalog.ts). */
|
|
37
|
+
managedFunction?: string;
|
|
38
|
+
/** `columns catalog` template key. */
|
|
39
|
+
template?: string;
|
|
40
|
+
/** tenant-db ENRICHMENT_COLUMN_CATALOG entry id this row projects into. */
|
|
41
|
+
catalogEntry?: string;
|
|
42
|
+
registryFunction?: "enrichment" | "people_search" | "company_search" | "signals";
|
|
43
|
+
/** Registry intent id whose chains materialise this row. */
|
|
44
|
+
registryIntent?: string;
|
|
45
|
+
/** For per-field company rows: the exact `field:<key>` chains of `enrich_company`. */
|
|
46
|
+
registryInputShapes?: readonly string[];
|
|
47
|
+
};
|
|
48
|
+
export type EnrichmentIntentTaxonomyEntry = {
|
|
49
|
+
/** Stable id: registry intent id, `enrich_company:<group>`, `people_search:<intent>`, `signals:<intent>`, or a slug. */
|
|
50
|
+
id: string;
|
|
51
|
+
/** Customer label: the DATA, never a vendor. */
|
|
52
|
+
label: string;
|
|
53
|
+
/** ONE founder-voice line. No vendor names, no cost-structure words. */
|
|
54
|
+
description: string;
|
|
55
|
+
entity: "person" | "company";
|
|
56
|
+
group: EnrichmentIntentGroup;
|
|
57
|
+
kind: EnrichmentIntentKind;
|
|
58
|
+
/** ORDER IS THE PRODUCT DECISION: unique, ascending; pickers sort by it. */
|
|
59
|
+
rank: number;
|
|
60
|
+
/** Customer phrasings that mean this row; lower-case; feeds discovery. */
|
|
61
|
+
aliases: readonly string[];
|
|
62
|
+
/** Identity inputs the customer must hold, in customer words. */
|
|
63
|
+
inputShapes: readonly string[];
|
|
64
|
+
outputFields: readonly EnrichmentIntentOutputField[];
|
|
65
|
+
surfaces: EnrichmentIntentSurfaces;
|
|
66
|
+
status: EnrichmentIntentStatus;
|
|
67
|
+
/** Required for no_default | coming_soon | rejected; customer-readable. */
|
|
68
|
+
statusReason?: string;
|
|
69
|
+
/** Ledger id (docs/gtm-program/decisions.md); required for rejected rows. */
|
|
70
|
+
decision?: string;
|
|
71
|
+
/** Provider ids (metadata only) that document the data, for no_default / coming_soon rows. */
|
|
72
|
+
candidateProviders?: readonly string[];
|
|
73
|
+
};
|
|
74
|
+
export declare const ENRICHMENT_INTENT_TAXONOMY: readonly EnrichmentIntentTaxonomyEntry[];
|
|
75
|
+
/** True when a customer-facing string leaks vendor cost structure (OXYGEN_PRODUCT.md 2026-08-10). */
|
|
76
|
+
export declare function leaksCostStructure(text: string): boolean;
|
|
77
|
+
export declare function listEnrichmentIntents(filter?: {
|
|
78
|
+
group?: EnrichmentIntentGroup;
|
|
79
|
+
status?: EnrichmentIntentStatus | readonly EnrichmentIntentStatus[];
|
|
80
|
+
entity?: "person" | "company";
|
|
81
|
+
}): EnrichmentIntentTaxonomyEntry[];
|
|
82
|
+
export declare function getEnrichmentIntent(id: string): EnrichmentIntentTaxonomyEntry | null;
|
|
83
|
+
/**
|
|
84
|
+
* Best taxonomy row for a customer phrasing: the longest alias contained in the
|
|
85
|
+
* query wins, so "personal email" beats "email" and "decision makers at each
|
|
86
|
+
* account" beats "account". Rejected rows never match. Null when nothing does.
|
|
87
|
+
*/
|
|
88
|
+
/**
|
|
89
|
+
* The pointer an older pricing surface carries back to the catalogue, so a
|
|
90
|
+
* customer who asks "what can I add and what does it cost" on `tools search
|
|
91
|
+
* --for-table` or `find company --list-fields` is handed the exact catalogue
|
|
92
|
+
* command instead of a ceiling. The 2026-09-19 blind acceptance run priced a
|
|
93
|
+
* whole pilot from those two surfaces and never reached `enrichment catalog`,
|
|
94
|
+
* although the skill it read named it: a new place to look is invisible until
|
|
95
|
+
* the place the question is asked points at it.
|
|
96
|
+
*/
|
|
97
|
+
export type EnrichmentCatalogPointer = {
|
|
98
|
+
/** The single data type when the entry maps to exactly one row; null when it fans out to a group. */
|
|
99
|
+
intent: string | null;
|
|
100
|
+
group: EnrichmentIntentGroup;
|
|
101
|
+
exact_command: string;
|
|
102
|
+
};
|
|
103
|
+
/** Catalogue pointer for a column-catalogue entry id such as `preset:company_enrich` or `capability:work_email`. */
|
|
104
|
+
export declare function catalogPointerForEntry(entryId: string): EnrichmentCatalogPointer | null;
|
|
105
|
+
/** Catalogue pointer for one company enrichment field key such as `technologies` or `latest_funding_round`. */
|
|
106
|
+
export declare function catalogPointerForCompanyField(field: string): EnrichmentCatalogPointer | null;
|
|
107
|
+
export declare function findEnrichmentIntentByAlias(query: string): EnrichmentIntentTaxonomyEntry | null;
|