@oxygen-agent/cli 1.1014.1224 → 1.1014.1341
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/browser-login.js +4 -1
- package/dist/cli-output.js +14 -0
- package/dist/cli-values.js +1 -1
- package/dist/command-manifest.js +4 -1
- package/dist/commands/billing.js +2 -2
- package/dist/commands/crm.js +12 -4
- package/dist/commands/db.js +30 -0
- package/dist/commands/domains.js +1 -1
- package/dist/commands/feeds.js +29 -1
- package/dist/commands/knowledge.js +24 -1
- package/dist/commands/sequences.js +44 -8
- package/dist/deliverability-evidence-commands.js +6 -6
- package/dist/functions-commands.js +1 -1
- package/dist/help.js +1 -1
- package/dist/local-custom-http-column.js +1 -1
- package/dist/runtime.js +1 -1
- package/dist/session.js +1 -1
- package/dist/transcript.js +1 -1
- package/dist/util.d.ts +1 -1
- package/dist/util.js +1 -1
- package/dist/visual-commands.js +1 -1
- package/dist/visual-render-wait.js +1 -1
- package/node_modules/@oxygen/shared/dist/agent-sessions.d.ts +34 -173
- package/node_modules/@oxygen/shared/dist/agent-sessions.js +134 -18
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +1 -0
- package/node_modules/@oxygen/shared/dist/conversation-lineage.d.ts +131 -0
- package/node_modules/@oxygen/shared/dist/conversation-lineage.js +72 -0
- package/node_modules/@oxygen/shared/dist/copilot-errors.d.ts +3 -3
- package/node_modules/@oxygen/shared/dist/copilot-errors.js +6 -3
- package/node_modules/@oxygen/shared/dist/crm-record-brief.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/crm-record-brief.js +3 -1
- package/node_modules/@oxygen/shared/dist/deliverability-surface.d.ts +4 -2
- package/node_modules/@oxygen/shared/dist/deliverability-surface.js +4 -2
- package/node_modules/@oxygen/shared/dist/dev-eval-context.d.ts +20 -0
- package/node_modules/@oxygen/shared/dist/enrollment-origin.d.ts +65 -0
- package/node_modules/@oxygen/shared/dist/enrollment-origin.js +92 -0
- package/node_modules/@oxygen/shared/dist/first-month-offer.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/first-month-offer.js +13 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-recommendations.d.ts +54 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-recommendations.js +100 -0
- package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/hosted-ai.js +9 -0
- package/node_modules/@oxygen/shared/dist/hubspot-engagements.d.ts +26 -0
- package/node_modules/@oxygen/shared/dist/hubspot-engagements.js +107 -0
- package/node_modules/@oxygen/shared/dist/integration-secrets.d.ts +19 -0
- package/node_modules/@oxygen/shared/dist/integration-secrets.js +77 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +15 -4
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +15 -4
- package/node_modules/@oxygen/shared/dist/knowledge-brain-draft.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/knowledge-brain-draft.js +2 -0
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/network-mining-approval.d.ts +90 -0
- package/node_modules/@oxygen/shared/dist/network-mining-approval.js +225 -0
- package/node_modules/@oxygen/shared/dist/network-mining-signals.d.ts +25 -0
- package/node_modules/@oxygen/shared/dist/network-mining-signals.js +34 -0
- package/node_modules/@oxygen/shared/dist/reply-oxygent.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/reply-oxygent.js +6 -0
- package/node_modules/@oxygen/shared/dist/research-findings.d.ts +11 -2
- package/node_modules/@oxygen/shared/dist/research-findings.js +16 -3
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +25 -4
- package/node_modules/@oxygen/shared/dist/sequences.js +40 -10
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +18 -0
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/shared/dist/workspace-event-catalog.js +21 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GuidedActivationRoutine } from "./guided-activation.js";
|
|
2
|
+
import { type WorkspaceEventCatalogEntry } from "./workspace-event-catalog.js";
|
|
2
3
|
import { type AgentSessionSchedule } from "./agent-session-schedule.js";
|
|
3
4
|
export type AgentSessionStatus = "draft" | "active" | "paused" | "budget_wait" | "completed" | "archived";
|
|
4
5
|
export type AgentSessionResources = {
|
|
@@ -63,8 +64,10 @@ export declare const AGENT_SESSION_ADAPTERS: Readonly<Record<string, {
|
|
|
63
64
|
fixed?: string;
|
|
64
65
|
array?: boolean;
|
|
65
66
|
}>>;
|
|
66
|
-
|
|
67
|
-
export declare function
|
|
67
|
+
/** The adapters this deployment serves: the reviewed set, plus the Network Mining reads where it is open. */
|
|
68
|
+
export declare function agentSessionAdapters(env?: Record<string, string | undefined>): Readonly<Record<string, (typeof AGENT_SESSION_ADAPTERS)[string]>>;
|
|
69
|
+
export declare function isAgentSessionCapability(name: string, env?: Record<string, string | undefined>): boolean;
|
|
70
|
+
export declare function agentSessionCapabilityDecision(config: AgentSessionConfig, name: string, args: Record<string, unknown>, env?: Record<string, string | undefined>): "allow" | "ask" | "deny";
|
|
68
71
|
export declare const EMPTY_AGENT_SESSION_RESOURCES: AgentSessionResources;
|
|
69
72
|
export declare const AGENT_SESSION_EVENT_RESOURCES: {
|
|
70
73
|
readonly sequence_id: "sequences";
|
|
@@ -74,8 +77,25 @@ export declare const AGENT_SESSION_EVENT_RESOURCES: {
|
|
|
74
77
|
readonly row_id: "records";
|
|
75
78
|
readonly record_id: "records";
|
|
76
79
|
};
|
|
77
|
-
export declare const AGENT_SESSION_EVENTS:
|
|
78
|
-
|
|
80
|
+
export declare const AGENT_SESSION_EVENTS: WorkspaceEventCatalogEntry[];
|
|
81
|
+
/**
|
|
82
|
+
* The events a session may wake on here. Where Network Mining is open, a CRM timeline activity can too, through the
|
|
83
|
+
* records it links (S12); elsewhere this is AGENT_SESSION_EVENTS.
|
|
84
|
+
*/
|
|
85
|
+
export declare function agentSessionEvents(env?: Record<string, string | undefined>): readonly WorkspaceEventCatalogEntry[];
|
|
86
|
+
export declare function agentSessionEventIsScoped(config: AgentSessionConfig, payload: Record<string, unknown>, env?: Record<string, string | undefined>): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Focus learning (Network Mining S12, DEV-only): an unattended session over the warmest people in the focus rank. It
|
|
89
|
+
* wakes when one of them shows inbound activity and once a day, reads their brief (which carries the lineage of
|
|
90
|
+
* their enrollments), and may only append cited research findings and notes to them: every other write, and any
|
|
91
|
+
* write that would need a person's card, is refused. Its scope is the focus top-K when it was created, at most
|
|
92
|
+
* FOCUS_LEARNING_MAX_RECORDS people; it is created as a draft and runs only after a person activates it.
|
|
93
|
+
*/
|
|
94
|
+
export declare const FOCUS_LEARNING_TEMPLATE_ID = "focus-learning";
|
|
95
|
+
export declare const FOCUS_LEARNING_DEFAULT_RECORDS = 10;
|
|
96
|
+
export declare const FOCUS_LEARNING_MAX_RECORDS = 25;
|
|
97
|
+
export declare const FOCUS_LEARNING_MAX_PER_RUN_CREDITS = 25;
|
|
98
|
+
export declare const FOCUS_LEARNING_MAX_DAILY_CREDITS = 100;
|
|
79
99
|
export declare const AGENT_SESSION_TEMPLATES: readonly [{
|
|
80
100
|
readonly id: "campaign-monitor";
|
|
81
101
|
readonly title: "Campaign monitor";
|
|
@@ -133,173 +153,14 @@ export declare const AGENT_SESSION_TEMPLATES: readonly [{
|
|
|
133
153
|
readonly actions: readonly ["oxygen_crm_notes_add"];
|
|
134
154
|
readonly events: readonly [];
|
|
135
155
|
}];
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
readonly goal: "Watch this native campaign. Compare sends, replies and meetings with earlier reviews; explain changes, propose improvements and schedule the next review.";
|
|
140
|
-
readonly resources: readonly ["sequences"];
|
|
141
|
-
readonly actions: readonly ["oxygen_sequences_set_status"];
|
|
142
|
-
readonly events: readonly ["sequencer.contact_activity.any"];
|
|
143
|
-
} | {
|
|
144
|
-
readonly id: "lead-replenishment";
|
|
145
|
-
readonly title: "Lead replenishment";
|
|
146
|
-
readonly goal: "Maintain a qualified lead pool in the selected Table for this native campaign. Research gaps, deduplicate and enroll only qualified leads within the approved scope.";
|
|
147
|
-
readonly resources: readonly ["tables", "sequences"];
|
|
148
|
-
readonly actions: readonly ["oxygen_rows_upsert", "oxygen_sequences_enroll"];
|
|
149
|
-
readonly events: readonly [];
|
|
150
|
-
} | {
|
|
151
|
-
readonly id: "reply-handling";
|
|
152
|
-
readonly title: "Reply handling";
|
|
153
|
-
readonly goal: "Review the selected conversations, remember prior responses, qualify intent and draft replies. Send only when explicitly authorized; hand off uncertain or sensitive replies.";
|
|
154
|
-
readonly resources: readonly ["conversations"];
|
|
155
|
-
readonly actions: readonly ["oxygen_inbox_set_status"];
|
|
156
|
-
readonly events: readonly ["crm.email_reply_classified"];
|
|
157
|
-
} | {
|
|
158
|
-
readonly id: "deliverability";
|
|
159
|
-
readonly title: "Deliverability";
|
|
160
|
-
readonly goal: "Watch the selected mailboxes for sending health changes. Explain evidence and recommend or apply only approved cap and status adjustments.";
|
|
161
|
-
readonly resources: readonly ["mailboxes"];
|
|
162
|
-
readonly actions: readonly ["oxygen_mailboxes_set_cap", "oxygen_mailboxes_set_status"];
|
|
163
|
-
readonly events: readonly [];
|
|
164
|
-
} | {
|
|
165
|
-
readonly id: "account-research";
|
|
166
|
-
readonly title: "Account research";
|
|
167
|
-
readonly goal: "Maintain cited account research for the selected records and knowledge pages. Track changes and recommend next actions; keep facts separate from hypotheses.";
|
|
168
|
-
readonly resources: readonly ["records", "knowledge"];
|
|
169
|
-
readonly actions: readonly ["oxygen_crm_notes_add", "oxygen_knowledge_page_upsert"];
|
|
170
|
-
readonly events: readonly [];
|
|
171
|
-
} | {
|
|
172
|
-
readonly id: "inbound-qualification";
|
|
173
|
-
readonly title: "Inbound qualification";
|
|
174
|
-
readonly goal: "Qualify the selected inbound conversations and records against workspace ICP and product knowledge. Record evidence and recommendations; hand off high intent to a person.";
|
|
175
|
-
readonly resources: readonly ["conversations", "records"];
|
|
176
|
-
readonly actions: readonly ["oxygen_inbox_set_status", "oxygen_crm_notes_add"];
|
|
177
|
-
readonly events: readonly ["crm.email_reply_classified"];
|
|
178
|
-
} | {
|
|
179
|
-
readonly id: "crm-hygiene";
|
|
180
|
-
readonly title: "CRM hygiene";
|
|
181
|
-
readonly goal: "Review the selected CRM objects for duplicates and missing or inconsistent fields. Propose repairs with evidence, and apply only explicitly authorized changes.";
|
|
182
|
-
readonly resources: readonly ["objects"];
|
|
183
|
-
readonly actions: readonly ["oxygen_crm_merge"];
|
|
184
|
-
readonly events: readonly [];
|
|
185
|
-
} | {
|
|
186
|
-
readonly id: "pipeline-review";
|
|
187
|
-
readonly title: "Pipeline review";
|
|
188
|
-
readonly goal: "Review the selected pipeline and lifecycle stages. Remember earlier reviews, identify stalled opportunities and recommend the next action with evidence.";
|
|
189
|
-
readonly resources: readonly ["objects", "records"];
|
|
190
|
-
readonly actions: readonly ["oxygen_crm_notes_add"];
|
|
191
|
-
readonly events: readonly [];
|
|
192
|
-
} | {
|
|
193
|
-
id: "campaign-monitor";
|
|
194
|
-
title: "Campaign monitor";
|
|
195
|
-
resources: readonly ["sequences"];
|
|
196
|
-
actions: readonly ["oxygen_sequences_set_status"];
|
|
197
|
-
events: readonly ["sequencer.contact_activity.any"];
|
|
198
|
-
goal: string;
|
|
199
|
-
} | {
|
|
200
|
-
id: "lead-replenishment";
|
|
201
|
-
title: "Lead replenishment";
|
|
202
|
-
resources: readonly ["tables", "sequences"];
|
|
203
|
-
actions: readonly ["oxygen_rows_upsert", "oxygen_sequences_enroll"];
|
|
204
|
-
events: readonly [];
|
|
205
|
-
goal: string;
|
|
206
|
-
} | {
|
|
207
|
-
id: "reply-handling";
|
|
208
|
-
title: "Reply handling";
|
|
209
|
-
resources: readonly ["conversations"];
|
|
210
|
-
actions: readonly ["oxygen_inbox_set_status"];
|
|
211
|
-
events: readonly ["crm.email_reply_classified"];
|
|
212
|
-
goal: string;
|
|
213
|
-
} | {
|
|
214
|
-
id: "deliverability";
|
|
215
|
-
title: "Deliverability";
|
|
216
|
-
resources: readonly ["mailboxes"];
|
|
217
|
-
actions: readonly ["oxygen_mailboxes_set_cap", "oxygen_mailboxes_set_status"];
|
|
218
|
-
events: readonly [];
|
|
219
|
-
goal: string;
|
|
220
|
-
} | {
|
|
221
|
-
id: "account-research";
|
|
222
|
-
title: "Account research";
|
|
223
|
-
resources: readonly ["records", "knowledge"];
|
|
224
|
-
actions: readonly ["oxygen_crm_notes_add", "oxygen_knowledge_page_upsert"];
|
|
225
|
-
events: readonly [];
|
|
226
|
-
goal: string;
|
|
227
|
-
} | {
|
|
228
|
-
id: "inbound-qualification";
|
|
229
|
-
title: "Inbound qualification";
|
|
230
|
-
resources: readonly ["conversations", "records"];
|
|
231
|
-
actions: readonly ["oxygen_inbox_set_status", "oxygen_crm_notes_add"];
|
|
232
|
-
events: readonly ["crm.email_reply_classified"];
|
|
233
|
-
goal: string;
|
|
234
|
-
} | {
|
|
235
|
-
id: "crm-hygiene";
|
|
236
|
-
title: "CRM hygiene";
|
|
237
|
-
resources: readonly ["objects"];
|
|
238
|
-
actions: readonly ["oxygen_crm_merge"];
|
|
239
|
-
events: readonly [];
|
|
240
|
-
goal: string;
|
|
241
|
-
} | {
|
|
242
|
-
id: "pipeline-review";
|
|
243
|
-
title: "Pipeline review";
|
|
244
|
-
resources: readonly ["objects", "records"];
|
|
245
|
-
actions: readonly ["oxygen_crm_notes_add"];
|
|
246
|
-
events: readonly [];
|
|
156
|
+
type AgentSessionTemplate = {
|
|
157
|
+
id: string;
|
|
158
|
+
title: string;
|
|
247
159
|
goal: string;
|
|
248
|
-
|
|
249
|
-
readonly
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}, {
|
|
256
|
-
readonly id: "lead-replenishment";
|
|
257
|
-
readonly title: "Lead replenishment";
|
|
258
|
-
readonly goal: "Maintain a qualified lead pool in the selected Table for this native campaign. Research gaps, deduplicate and enroll only qualified leads within the approved scope.";
|
|
259
|
-
readonly resources: readonly ["tables", "sequences"];
|
|
260
|
-
readonly actions: readonly ["oxygen_rows_upsert", "oxygen_sequences_enroll"];
|
|
261
|
-
readonly events: readonly [];
|
|
262
|
-
}, {
|
|
263
|
-
readonly id: "reply-handling";
|
|
264
|
-
readonly title: "Reply handling";
|
|
265
|
-
readonly goal: "Review the selected conversations, remember prior responses, qualify intent and draft replies. Send only when explicitly authorized; hand off uncertain or sensitive replies.";
|
|
266
|
-
readonly resources: readonly ["conversations"];
|
|
267
|
-
readonly actions: readonly ["oxygen_inbox_set_status"];
|
|
268
|
-
readonly events: readonly ["crm.email_reply_classified"];
|
|
269
|
-
}, {
|
|
270
|
-
readonly id: "deliverability";
|
|
271
|
-
readonly title: "Deliverability";
|
|
272
|
-
readonly goal: "Watch the selected mailboxes for sending health changes. Explain evidence and recommend or apply only approved cap and status adjustments.";
|
|
273
|
-
readonly resources: readonly ["mailboxes"];
|
|
274
|
-
readonly actions: readonly ["oxygen_mailboxes_set_cap", "oxygen_mailboxes_set_status"];
|
|
275
|
-
readonly events: readonly [];
|
|
276
|
-
}, {
|
|
277
|
-
readonly id: "account-research";
|
|
278
|
-
readonly title: "Account research";
|
|
279
|
-
readonly goal: "Maintain cited account research for the selected records and knowledge pages. Track changes and recommend next actions; keep facts separate from hypotheses.";
|
|
280
|
-
readonly resources: readonly ["records", "knowledge"];
|
|
281
|
-
readonly actions: readonly ["oxygen_crm_notes_add", "oxygen_knowledge_page_upsert"];
|
|
282
|
-
readonly events: readonly [];
|
|
283
|
-
}, {
|
|
284
|
-
readonly id: "inbound-qualification";
|
|
285
|
-
readonly title: "Inbound qualification";
|
|
286
|
-
readonly goal: "Qualify the selected inbound conversations and records against workspace ICP and product knowledge. Record evidence and recommendations; hand off high intent to a person.";
|
|
287
|
-
readonly resources: readonly ["conversations", "records"];
|
|
288
|
-
readonly actions: readonly ["oxygen_inbox_set_status", "oxygen_crm_notes_add"];
|
|
289
|
-
readonly events: readonly ["crm.email_reply_classified"];
|
|
290
|
-
}, {
|
|
291
|
-
readonly id: "crm-hygiene";
|
|
292
|
-
readonly title: "CRM hygiene";
|
|
293
|
-
readonly goal: "Review the selected CRM objects for duplicates and missing or inconsistent fields. Propose repairs with evidence, and apply only explicitly authorized changes.";
|
|
294
|
-
readonly resources: readonly ["objects"];
|
|
295
|
-
readonly actions: readonly ["oxygen_crm_merge"];
|
|
296
|
-
readonly events: readonly [];
|
|
297
|
-
}, {
|
|
298
|
-
readonly id: "pipeline-review";
|
|
299
|
-
readonly title: "Pipeline review";
|
|
300
|
-
readonly goal: "Review the selected pipeline and lifecycle stages. Remember earlier reviews, identify stalled opportunities and recommend the next action with evidence.";
|
|
301
|
-
readonly resources: readonly ["objects", "records"];
|
|
302
|
-
readonly actions: readonly ["oxygen_crm_notes_add"];
|
|
303
|
-
readonly events: readonly [];
|
|
304
|
-
}];
|
|
305
|
-
export declare function validateAgentSessionConfig(config: AgentSessionConfig, requireResources?: boolean): void;
|
|
160
|
+
resources: readonly string[];
|
|
161
|
+
actions: readonly string[];
|
|
162
|
+
events: readonly string[];
|
|
163
|
+
};
|
|
164
|
+
export declare function agentSessionTemplates(env?: Record<string, string | undefined>): readonly AgentSessionTemplate[];
|
|
165
|
+
export declare function validateAgentSessionConfig(config: AgentSessionConfig, requireResources?: boolean, env?: Record<string, string | undefined>): void;
|
|
166
|
+
export {};
|
|
@@ -5,7 +5,8 @@ import { validateContract } from "./contracts/schema.js";
|
|
|
5
5
|
import { GUIDED_ACTIVATION_SCAN_POLICY_CONTRACT } from "./contracts/guided-activation.js";
|
|
6
6
|
import { isStaffEmail } from "./staff.js";
|
|
7
7
|
import { OxygenError } from "./cli-result.js";
|
|
8
|
-
import {
|
|
8
|
+
import { RESEARCH_FINDING_ACTIVITY_TYPE } from "./research-findings.js";
|
|
9
|
+
import { servedWorkspaceEventCatalog, WORKSPACE_EVENT_CATALOG } from "./workspace-event-catalog.js";
|
|
9
10
|
import { agentSessionScheduleProblem } from "./agent-session-schedule.js";
|
|
10
11
|
/** The routine marker of a session's settings, or null for a general session. */
|
|
11
12
|
export function agentSessionTeammateRoutine(config) {
|
|
@@ -43,6 +44,9 @@ export const AGENT_SESSION_ADAPTERS = {
|
|
|
43
44
|
oxygen_sequences_analytics: { resource: "sequences", field: "sequence", read: true },
|
|
44
45
|
oxygen_sequences_contacts: { resource: "sequences", field: "sequence", read: true },
|
|
45
46
|
oxygen_sequences_enrollments: { resource: "sequences", field: "sequence", read: true },
|
|
47
|
+
oxygen_sequences_events: { resource: "sequences", field: "sequence", read: true },
|
|
48
|
+
// Email messages of one campaign; one record's messages are the gated `record` scope below.
|
|
49
|
+
oxygen_messages_query: { resource: "sequences", field: "sequence_id", read: true },
|
|
46
50
|
oxygen_sequences_enroll: { resource: "sequences", field: "sequence", read: false },
|
|
47
51
|
oxygen_sequences_set_status: { resource: "sequences", field: "sequence", read: false },
|
|
48
52
|
oxygen_tables_describe: { resource: "tables", field: "table", read: true },
|
|
@@ -72,17 +76,41 @@ export const AGENT_SESSION_ADAPTERS = {
|
|
|
72
76
|
oxygen_knowledge_page_upsert: { resource: "knowledge", field: "page.slug", read: false },
|
|
73
77
|
oxygen_find_company: { resource: "domains", field: "domain", read: false },
|
|
74
78
|
};
|
|
79
|
+
/**
|
|
80
|
+
* Reviewed adapters served only where Network Mining is: the brief exists only there, and an activity log from a
|
|
81
|
+
* session may only append a cited research finding (S12).
|
|
82
|
+
*/
|
|
83
|
+
const NETWORK_MINING_SESSION_ADAPTERS = {
|
|
84
|
+
oxygen_crm_record_brief: { resource: "records", field: "row_id", read: true },
|
|
85
|
+
oxygen_crm_activity_log: { resource: "records", field: "row_id", read: false },
|
|
86
|
+
};
|
|
87
|
+
const defaultEnv = () => (typeof process === "undefined" ? {} : process.env);
|
|
88
|
+
/** The adapters this deployment serves: the reviewed set, plus the Network Mining reads where it is open. */
|
|
89
|
+
export function agentSessionAdapters(env = defaultEnv()) {
|
|
90
|
+
return networkMiningEnabled(env) ? { ...AGENT_SESSION_ADAPTERS, ...NETWORK_MINING_SESSION_ADAPTERS } : AGENT_SESSION_ADAPTERS;
|
|
91
|
+
}
|
|
75
92
|
const CONTEXT_READS = new Set([
|
|
76
93
|
"oxygen_capabilities_search", "oxygen_capabilities_schema", "oxygen_skills_list", "oxygen_skills_get",
|
|
77
94
|
"oxygen_knowledge_index", "oxygen_knowledge_search", "oxygen_workspace_get", "oxygen_enrichment_catalog",
|
|
78
95
|
]);
|
|
79
|
-
export function isAgentSessionCapability(name) { return CONTEXT_READS.has(name) || Boolean(
|
|
80
|
-
export function agentSessionCapabilityDecision(config, name, args) {
|
|
96
|
+
export function isAgentSessionCapability(name, env = defaultEnv()) { return CONTEXT_READS.has(name) || Boolean(agentSessionAdapters(env)[name]); }
|
|
97
|
+
export function agentSessionCapabilityDecision(config, name, args, env = defaultEnv()) {
|
|
98
|
+
const decision = sessionCapabilityDecision(config, name, args, env);
|
|
99
|
+
// Focus learning runs unattended on its own wakes: it never raises a card, so what it may not do it is refused.
|
|
100
|
+
return decision === "ask" && config.template === FOCUS_LEARNING_TEMPLATE_ID ? "deny" : decision;
|
|
101
|
+
}
|
|
102
|
+
function sessionCapabilityDecision(config, name, args, env) {
|
|
81
103
|
if (CONTEXT_READS.has(name))
|
|
82
104
|
return "allow";
|
|
83
|
-
const adapter =
|
|
105
|
+
const adapter = agentSessionAdapters(env)[name];
|
|
84
106
|
if (!adapter)
|
|
85
107
|
return "deny";
|
|
108
|
+
// One record's messages (Network Mining): scoped by the record, never the whole inbox.
|
|
109
|
+
if (name === "oxygen_messages_query" && args.record !== undefined) {
|
|
110
|
+
if (!networkMiningEnabled(env) || typeof args.record !== "string" || !args.record || args.record.includes(","))
|
|
111
|
+
return "deny";
|
|
112
|
+
return config.resources.records.includes(args.record) ? "allow" : "ask";
|
|
113
|
+
}
|
|
86
114
|
// No alternate aggregate scope may widen a resource-specific read.
|
|
87
115
|
if (["campaigns", "senders", "account", "crm_object", "ignore_sender_bindings"].some(key => args[key] !== undefined && args[key] !== false))
|
|
88
116
|
return "deny";
|
|
@@ -96,6 +124,9 @@ export function agentSessionCapabilityDecision(config, name, args) {
|
|
|
96
124
|
return "deny";
|
|
97
125
|
if (name === "oxygen_knowledge_page_get" && args.id !== undefined)
|
|
98
126
|
return "deny";
|
|
127
|
+
// A session appends findings, never other timeline facts (calls, meetings, stage changes) about a record.
|
|
128
|
+
if (name === "oxygen_crm_activity_log" && args.activity_type !== RESEARCH_FINDING_ACTIVITY_TYPE)
|
|
129
|
+
return "deny";
|
|
99
130
|
const ref = adapter.fixed ?? adapter.field.split(".").reduce((value, key) => value && typeof value === "object" ? value[key] : undefined, args);
|
|
100
131
|
if (adapter.array) {
|
|
101
132
|
if (!Array.isArray(ref) || !ref.length || ref.some(value => typeof value !== "string" || value.includes(",")))
|
|
@@ -118,9 +149,59 @@ export const AGENT_SESSION_EVENT_RESOURCES = {
|
|
|
118
149
|
mailbox_id: "mailboxes", row_id: "records", record_id: "records",
|
|
119
150
|
};
|
|
120
151
|
export const AGENT_SESSION_EVENTS = WORKSPACE_EVENT_CATALOG.filter(event => Object.keys(AGENT_SESSION_EVENT_RESOURCES).some(key => event.payloadFields.includes(key)));
|
|
121
|
-
|
|
122
|
-
|
|
152
|
+
/** A CRM activity names its records in `links[].row_id`, not a top-level field. */
|
|
153
|
+
const LINKED_RECORDS_FIELD = "links";
|
|
154
|
+
/**
|
|
155
|
+
* The events a session may wake on here. Where Network Mining is open, a CRM timeline activity can too, through the
|
|
156
|
+
* records it links (S12); elsewhere this is AGENT_SESSION_EVENTS.
|
|
157
|
+
*/
|
|
158
|
+
export function agentSessionEvents(env = defaultEnv()) {
|
|
159
|
+
if (!networkMiningEnabled(env))
|
|
160
|
+
return AGENT_SESSION_EVENTS;
|
|
161
|
+
return [...AGENT_SESSION_EVENTS, ...servedWorkspaceEventCatalog(env).filter(event => event.payloadFields.includes(LINKED_RECORDS_FIELD) && !AGENT_SESSION_EVENTS.includes(event))];
|
|
123
162
|
}
|
|
163
|
+
function linkedRecordIds(payload) {
|
|
164
|
+
const links = payload[LINKED_RECORDS_FIELD];
|
|
165
|
+
return Array.isArray(links)
|
|
166
|
+
? links.flatMap(link => link && typeof link === "object" && typeof link.row_id === "string" ? [link.row_id] : [])
|
|
167
|
+
: [];
|
|
168
|
+
}
|
|
169
|
+
export function agentSessionEventIsScoped(config, payload, env = defaultEnv()) {
|
|
170
|
+
return Object.entries(AGENT_SESSION_EVENT_RESOURCES).some(([key, resource]) => typeof payload[key] === "string" && config.resources[resource].includes(payload[key]))
|
|
171
|
+
|| (networkMiningEnabled(env) && linkedRecordIds(payload).some(id => config.resources.records.includes(id)));
|
|
172
|
+
}
|
|
173
|
+
/** Whether an event can match a session's selected resources at all. */
|
|
174
|
+
function eventHasSelectedResource(event, resources) {
|
|
175
|
+
return Object.entries(AGENT_SESSION_EVENT_RESOURCES).some(([key, resource]) => event.payloadFields.includes(key) && resources[resource].length > 0)
|
|
176
|
+
|| (event.payloadFields.includes(LINKED_RECORDS_FIELD) && resources.records.length > 0);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Focus learning (Network Mining S12, DEV-only): an unattended session over the warmest people in the focus rank. It
|
|
180
|
+
* wakes when one of them shows inbound activity and once a day, reads their brief (which carries the lineage of
|
|
181
|
+
* their enrollments), and may only append cited research findings and notes to them: every other write, and any
|
|
182
|
+
* write that would need a person's card, is refused. Its scope is the focus top-K when it was created, at most
|
|
183
|
+
* FOCUS_LEARNING_MAX_RECORDS people; it is created as a draft and runs only after a person activates it.
|
|
184
|
+
*/
|
|
185
|
+
export const FOCUS_LEARNING_TEMPLATE_ID = "focus-learning";
|
|
186
|
+
export const FOCUS_LEARNING_DEFAULT_RECORDS = 10;
|
|
187
|
+
export const FOCUS_LEARNING_MAX_RECORDS = 25;
|
|
188
|
+
export const FOCUS_LEARNING_MAX_PER_RUN_CREDITS = 25;
|
|
189
|
+
export const FOCUS_LEARNING_MAX_DAILY_CREDITS = 100;
|
|
190
|
+
const DAY_MINUTES = 1440;
|
|
191
|
+
const FOCUS_LEARNING_ACTIONS = ["oxygen_crm_activity_log", "oxygen_crm_notes_add"];
|
|
192
|
+
// Inbound activity only: a finding or note the session writes itself never wakes it again.
|
|
193
|
+
const FOCUS_LEARNING_EVENTS = [
|
|
194
|
+
"website_visit", "profile_view", "post_reaction", "new_follower", "connection_accepted",
|
|
195
|
+
"email_reply", "linkedin_message", "meeting_booked", "post_comment", "company_follow",
|
|
196
|
+
].map(kind => `crm.activity_created.${kind}`);
|
|
197
|
+
const FOCUS_LEARNING_TEMPLATE = {
|
|
198
|
+
id: FOCUS_LEARNING_TEMPLATE_ID,
|
|
199
|
+
title: "Focus learning",
|
|
200
|
+
goal: "Keep learning about the selected people, the warmest in the focus rank. On each wake, read the brief of every person a new event names (oxygen_crm_record_brief: their signals, conversations, enrollments and where they came from), or of each selected person on the daily wake. Append a research finding (oxygen_crm_activity_log with activity_type research_finding and mode live) only for a claim you can cite to this person's own message, activity or a source URL you read, with its confidence; add a note (oxygen_crm_notes_add) for what a seller should do next and why. Never change fields, merge, enroll or send, and write nothing you cannot cite.",
|
|
201
|
+
resources: ["records"],
|
|
202
|
+
actions: [...FOCUS_LEARNING_ACTIONS],
|
|
203
|
+
events: FOCUS_LEARNING_EVENTS,
|
|
204
|
+
};
|
|
124
205
|
export const AGENT_SESSION_TEMPLATES = [
|
|
125
206
|
{ id: "campaign-monitor", title: "Campaign monitor", goal: "Watch this native campaign. Compare sends, replies and meetings with earlier reviews; explain changes, propose improvements and schedule the next review.", resources: ["sequences"], actions: ["oxygen_sequences_set_status"], events: ["sequencer.contact_activity.any"] },
|
|
126
207
|
{ id: "lead-replenishment", title: "Lead replenishment", goal: "Maintain a qualified lead pool in the selected Table for this native campaign. Research gaps, deduplicate and enroll only qualified leads within the approved scope.", resources: ["tables", "sequences"], actions: ["oxygen_rows_upsert", "oxygen_sequences_enroll"], events: [] },
|
|
@@ -137,18 +218,49 @@ export const AGENT_SESSION_TEMPLATES = [
|
|
|
137
218
|
*/
|
|
138
219
|
const BRIEF_FIRST_TEMPLATES = new Set(["account-research", "inbound-qualification"]);
|
|
139
220
|
const BRIEF_FIRST_SENTENCE = "Start each record with its brief (oxygen_crm_record_brief): one free read of its fields, signals, conversations, team connections, notes, tasks and company.";
|
|
221
|
+
const LINEAGE_FIRST_TEMPLATES = new Set(["reply-handling", "inbound-qualification"]);
|
|
222
|
+
const LINEAGE_FIRST_SENTENCE = "Read each conversation with oxygen_inbox_get: its lineage says which sequence, step and variant the reply answered, how fast it came, from which sender and where the lead came from.";
|
|
140
223
|
export function agentSessionTemplates(env = typeof process === "undefined" ? {} : process.env) {
|
|
141
224
|
if (!networkMiningEnabled(env))
|
|
142
225
|
return AGENT_SESSION_TEMPLATES;
|
|
143
|
-
return AGENT_SESSION_TEMPLATES.map((template) =>
|
|
144
|
-
|
|
145
|
-
|
|
226
|
+
return [...AGENT_SESSION_TEMPLATES.map((template) => {
|
|
227
|
+
const lead = [
|
|
228
|
+
...(LINEAGE_FIRST_TEMPLATES.has(template.id) ? [LINEAGE_FIRST_SENTENCE] : []),
|
|
229
|
+
...(BRIEF_FIRST_TEMPLATES.has(template.id) ? [BRIEF_FIRST_SENTENCE] : []),
|
|
230
|
+
];
|
|
231
|
+
return lead.length > 0 ? { ...template, goal: `${lead.join(" ")} ${template.goal}` } : template;
|
|
232
|
+
}), {
|
|
233
|
+
...FOCUS_LEARNING_TEMPLATE,
|
|
234
|
+
events: FOCUS_LEARNING_TEMPLATE.events.filter(id => agentSessionEvents(env).some(event => event.id === id)),
|
|
235
|
+
}];
|
|
146
236
|
}
|
|
147
|
-
|
|
237
|
+
function focusLearningProblem(config) {
|
|
238
|
+
const { per_run_credits: perRun, daily_credits: daily } = config.budget;
|
|
239
|
+
if (perRun > FOCUS_LEARNING_MAX_PER_RUN_CREDITS || daily > FOCUS_LEARNING_MAX_DAILY_CREDITS) {
|
|
240
|
+
return `Focus learning runs within ${FOCUS_LEARNING_MAX_PER_RUN_CREDITS} credits a run and ${FOCUS_LEARNING_MAX_DAILY_CREDITS} in any 24 hours.`;
|
|
241
|
+
}
|
|
242
|
+
if (config.actions.some(name => !FOCUS_LEARNING_ACTIONS.includes(name)))
|
|
243
|
+
return "Focus learning may only append research findings and notes.";
|
|
244
|
+
if (config.events.some(id => !FOCUS_LEARNING_EVENTS.includes(id)))
|
|
245
|
+
return "Focus learning wakes only on inbound activity of its people.";
|
|
246
|
+
if (Object.entries(config.resources).some(([key, values]) => key !== "records" && values.length > 0))
|
|
247
|
+
return "Focus learning is scoped to people only.";
|
|
248
|
+
if (config.resources.records.length > FOCUS_LEARNING_MAX_RECORDS)
|
|
249
|
+
return `Focus learning covers at most ${FOCUS_LEARNING_MAX_RECORDS} people.`;
|
|
250
|
+
const schedule = config.schedule;
|
|
251
|
+
if (schedule.kind === "cron" || schedule.min_interval_minutes < DAY_MINUTES || schedule.interval_minutes < DAY_MINUTES) {
|
|
252
|
+
return "Focus learning wakes on its people's activity and at most once a day otherwise: use an interval of at least 1,440 minutes.";
|
|
253
|
+
}
|
|
254
|
+
if (config.teammate_routine || config.guided_activation)
|
|
255
|
+
return "Focus learning is its own session, not a routine.";
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
export function validateAgentSessionConfig(config, requireResources = true, env = defaultEnv()) {
|
|
148
259
|
const fail = (message) => { throw new OxygenError("invalid_session_config", message); };
|
|
149
260
|
if (!config.title?.trim() || !config.goal?.trim())
|
|
150
261
|
fail("A title and goal are required.");
|
|
151
|
-
|
|
262
|
+
const templates = agentSessionTemplates(env);
|
|
263
|
+
if (config.template !== "custom" && !templates.some(t => t.id === config.template))
|
|
152
264
|
fail("Unknown session template.");
|
|
153
265
|
const { per_run_credits: perRun, daily_credits: daily, lifetime_credits: lifetime } = config.budget;
|
|
154
266
|
if (!Number.isFinite(perRun) || perRun <= 0 || perRun > 25000 || !Number.isFinite(daily) || daily < perRun)
|
|
@@ -183,20 +295,24 @@ export function validateAgentSessionConfig(config, requireResources = true) {
|
|
|
183
295
|
}
|
|
184
296
|
if (config.ends_at && !Number.isFinite(Date.parse(config.ends_at)))
|
|
185
297
|
fail("Use an ISO date for the end time.");
|
|
186
|
-
|
|
298
|
+
const adapters = agentSessionAdapters(env), events = agentSessionEvents(env);
|
|
299
|
+
if (config.actions.some(name => !adapters[name] || adapters[name].read))
|
|
187
300
|
fail("Choose a reviewed native action.");
|
|
188
|
-
if (config.events.some(id => !
|
|
301
|
+
if (config.events.some(id => !events.some(event => event.id === id)))
|
|
189
302
|
fail("Choose an event that can match the session's selected resources.");
|
|
190
303
|
for (const values of Object.values(config.resources)) {
|
|
191
304
|
if (!Array.isArray(values) || values.some(ref => typeof ref !== "string" || !ref.trim() || ref.includes(",") || ref === "*"))
|
|
192
305
|
fail("Resources require exact native IDs or slugs.");
|
|
193
306
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
307
|
+
if (config.template === FOCUS_LEARNING_TEMPLATE_ID) {
|
|
308
|
+
const problem = focusLearningProblem(config);
|
|
309
|
+
if (problem)
|
|
310
|
+
fail(problem);
|
|
311
|
+
}
|
|
312
|
+
const template = templates.find(t => t.id === config.template);
|
|
313
|
+
if (requireResources && config.events.some(id => !eventHasSelectedResource(events.find(entry => entry.id === id), config.resources))) {
|
|
199
314
|
fail("Select a matching resource for each event before activating.");
|
|
315
|
+
}
|
|
200
316
|
if (requireResources && template?.resources.some(key => !config.resources[key]?.length))
|
|
201
317
|
fail("Select the template's required resources before activating.");
|
|
202
318
|
}
|
|
@@ -1053,7 +1053,7 @@ function recordDossierReadRecommendation(query) {
|
|
|
1053
1053
|
const RECORD_DOSSIER_FIRST_COMMANDS = ["crm search", "crm get", "crm activity timeline"];
|
|
1054
1054
|
const RECORD_BRIEF_TOOL = "oxygen_crm_record_brief";
|
|
1055
1055
|
const RECORD_BRIEF_COMMAND = "crm brief";
|
|
1056
|
-
const RECORD_BRIEF_EXECUTION_PREFIX = `Start with ${RECORD_BRIEF_COMMAND} (${RECORD_BRIEF_TOOL}): one free read of everything stored about the record,
|
|
1056
|
+
const RECORD_BRIEF_EXECUTION_PREFIX = `Start with ${RECORD_BRIEF_COMMAND} (${RECORD_BRIEF_TOOL}): one free read of everything stored about the record. It takes the row id, an identity or the exact name, so no search comes first, and each cut section names the read for its rest; use the reads below only for those. `;
|
|
1057
1057
|
/**
|
|
1058
1058
|
* Put the Network Mining record brief first on a record-dossier route where the
|
|
1059
1059
|
* host serves it, and take it off where it does not. The shared router decides
|
|
@@ -135,6 +135,7 @@ export const CUSTOMER_OPERATIONS = [
|
|
|
135
135
|
operation("tools.check_access", "/api/cli/tools/check-access", "POST", "tools check-access", "oxygen_tools_check_access", "enrichment", "Read tool access for this workspace; verify_credential explicitly opts into a provider credential probe.", { tool_id: text(), connection_id: text(), credential_mode: enumOf(["managed", "user_api_key"]), input: json, verify_credential: bool() }, ["tool_id"], { ...cliOnly, mutates: false }),
|
|
136
136
|
operation("linkedin.connections.import_status", "/api/cli/linkedin/connections/import", "GET", "linkedin connections import-status", "oxygen_linkedin_connections_import_status", "sequences", "Read the durable LinkedIn connections import status and target Table.", { account: text() }),
|
|
137
137
|
operation("linkedin.followers.import_status", "/api/cli/linkedin/followers/import", "GET", "linkedin followers import-status", "oxygen_linkedin_followers_import_status", "sequences", "Read the durable LinkedIn followers import status and target Table.", { account: text() }),
|
|
138
|
+
operation("inbox.import", "/api/cli/inbox/import", "POST", "inbox import", "oxygen_inbox_import", "inbox", "Import your own Instantly or Smartlead cold-email history into Messages (record timelines, messages query; not the Unibox). Runs on your connection; each read has the own-key fee. Never stops sequences or changes DNC. Resumable; dry_run reads one page.", { provider: enumOf(["instantly", "smartlead"]), connection_id: text(), campaign_id: text(), since: text("ISO timestamp; older mail is skipped."), import_id: text("Continue this import."), max_pages: int({ minimum: 0, maximum: 10 }), dry_run: bool() }, ["provider"], { spends: true, annotations: { openWorldHint: true } }),
|
|
138
139
|
operation("linkedin.inbox.backfill_status", "/api/cli/linkedin/inbox/backfill", "GET", "linkedin inbox backfill-status", "oxygen_linkedin_inbox_backfill_status", "inbox", "Read the durable LinkedIn inbox backfill status.", { account: text() }),
|
|
139
140
|
operation("linkedin.viewers.list", "/api/cli/linkedin/viewers", "GET", "linkedin viewers list", "oxygen_profile_viewers", "publishing", "Read named LinkedIn profile viewers, respecting the connected account's privacy ceiling.", { account: text(), start: int({ minimum: 0 }), include_raw: bool() }, [], cliOnly),
|
|
140
141
|
operation("signals.search.filters", "/api/cli/signals/search/preview", "GET", "signals search filters", "oxygen_signals_search_preview", "signals", "Read the LinkedIn post-search filter catalog and credential status; runs no search.", { source: enumOf(["linkedin_posts"]) }, [], cliOnly),
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
export declare const CONVERSATION_LINEAGE_SCHEMA_VERSION = 1;
|
|
2
|
+
export declare const CONVERSATION_LINEAGE_MAX_CHARS = 2500;
|
|
3
|
+
export declare const LINEAGE_DIGEST_MAX_CHARS = 600;
|
|
4
|
+
export type LineageCoverage = "complete" | "partial" | "unknown" | "empty";
|
|
5
|
+
/**
|
|
6
|
+
* Where the lead came from, as the enrollment recorded it (migration 0541): the
|
|
7
|
+
* kind of choice, the surface and actor type it came through, the table and the
|
|
8
|
+
* run behind it, and the focus decile at enroll time where Network Mining took
|
|
9
|
+
* one. An enrollment from before 0541 holds none and reads `unknown`.
|
|
10
|
+
*/
|
|
11
|
+
export type LineageOrigin = {
|
|
12
|
+
kind: "unknown";
|
|
13
|
+
} | {
|
|
14
|
+
kind: string;
|
|
15
|
+
gateway: string;
|
|
16
|
+
enrolled_by: string;
|
|
17
|
+
table_id: string | null;
|
|
18
|
+
ref: {
|
|
19
|
+
type: string;
|
|
20
|
+
id: string;
|
|
21
|
+
} | null;
|
|
22
|
+
focus?: {
|
|
23
|
+
in_focus: boolean;
|
|
24
|
+
rank: number | null;
|
|
25
|
+
decile: number | null;
|
|
26
|
+
} | null;
|
|
27
|
+
};
|
|
28
|
+
export type LineageEnrollment = {
|
|
29
|
+
enrollment_id: string;
|
|
30
|
+
sequence: {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string | null;
|
|
33
|
+
};
|
|
34
|
+
status: string | null;
|
|
35
|
+
enrolled_at: string | null;
|
|
36
|
+
/** How this conversation was tied to the enrollment. */
|
|
37
|
+
link: "stored" | "chat_id" | "lead_provider_id";
|
|
38
|
+
origin: LineageOrigin;
|
|
39
|
+
touches_before_reply: {
|
|
40
|
+
total: number;
|
|
41
|
+
by_kind: Record<string, number>;
|
|
42
|
+
channels: string[];
|
|
43
|
+
} | null;
|
|
44
|
+
reply: {
|
|
45
|
+
occurred_at: string;
|
|
46
|
+
channel: string;
|
|
47
|
+
answered: {
|
|
48
|
+
action_id: string;
|
|
49
|
+
step_index: number | null;
|
|
50
|
+
variant_id: string | null;
|
|
51
|
+
action_kind: string | null;
|
|
52
|
+
sent_at: string | null;
|
|
53
|
+
} | null;
|
|
54
|
+
latency_seconds: number | null;
|
|
55
|
+
time_to_first_reply_seconds: number | null;
|
|
56
|
+
attribution: "thread" | "latest_send_before_reply" | "unattributed";
|
|
57
|
+
source: string;
|
|
58
|
+
} | null;
|
|
59
|
+
sender: {
|
|
60
|
+
account_id: string | null;
|
|
61
|
+
mailbox_id: string | null;
|
|
62
|
+
label: string | null;
|
|
63
|
+
} | null;
|
|
64
|
+
/** Which reviewed copy produced the send. `null` until sends record it (LN3). */
|
|
65
|
+
copy: null;
|
|
66
|
+
};
|
|
67
|
+
export type ConversationLineage = {
|
|
68
|
+
schema_version: typeof CONVERSATION_LINEAGE_SCHEMA_VERSION;
|
|
69
|
+
conversation: {
|
|
70
|
+
id: string;
|
|
71
|
+
channel: string;
|
|
72
|
+
started_by: "us" | "them" | null;
|
|
73
|
+
first_outbound_at: string | null;
|
|
74
|
+
first_inbound_at: string | null;
|
|
75
|
+
sender: {
|
|
76
|
+
account_id: string | null;
|
|
77
|
+
mailbox_id: string | null;
|
|
78
|
+
label: string | null;
|
|
79
|
+
sender_profile_id: string | null;
|
|
80
|
+
};
|
|
81
|
+
other_senders: {
|
|
82
|
+
account_id: string;
|
|
83
|
+
label: string | null;
|
|
84
|
+
}[];
|
|
85
|
+
};
|
|
86
|
+
record: {
|
|
87
|
+
object: string;
|
|
88
|
+
row_id: string;
|
|
89
|
+
tier: "crm" | "audience";
|
|
90
|
+
match: "identity";
|
|
91
|
+
matched_on: string;
|
|
92
|
+
} | {
|
|
93
|
+
match: "none";
|
|
94
|
+
};
|
|
95
|
+
enrollments: LineageEnrollment[];
|
|
96
|
+
turns: {
|
|
97
|
+
inbound_count: number;
|
|
98
|
+
latest_inbound_at: string | null;
|
|
99
|
+
response_gaps: {
|
|
100
|
+
inbound_message_id: string;
|
|
101
|
+
after_message_id: string;
|
|
102
|
+
seconds: number;
|
|
103
|
+
}[];
|
|
104
|
+
};
|
|
105
|
+
/** Newest first; the first entry is the current status. `message_id` is the newest inbound when it changed. */
|
|
106
|
+
classification_history: {
|
|
107
|
+
status: string;
|
|
108
|
+
source: string;
|
|
109
|
+
at: string | null;
|
|
110
|
+
message_id: string | null;
|
|
111
|
+
confidence: number | null;
|
|
112
|
+
}[];
|
|
113
|
+
other_conversations: {
|
|
114
|
+
id: string;
|
|
115
|
+
channel: string;
|
|
116
|
+
last_at: string | null;
|
|
117
|
+
status: string | null;
|
|
118
|
+
}[];
|
|
119
|
+
coverage: Record<"enrollments" | "record" | "origin" | "copy" | "classification_history" | "other_conversations", {
|
|
120
|
+
state: LineageCoverage;
|
|
121
|
+
reason?: string;
|
|
122
|
+
}>;
|
|
123
|
+
};
|
|
124
|
+
/** "a one-off send via cli, focus decile 2"; `unknown` when the enrollment recorded none. */
|
|
125
|
+
export declare function originText(origin: LineageOrigin): string;
|
|
126
|
+
/**
|
|
127
|
+
* One short, plain-text line set an agent reads beside the thread (≤600 chars):
|
|
128
|
+
* which sequence, step and variant the reply answered, how fast, from which
|
|
129
|
+
* sender, where the lead came from, and what is unknown.
|
|
130
|
+
*/
|
|
131
|
+
export declare function renderLineageDigest(lineage: ConversationLineage | null | undefined): string | null;
|