@oxygen-agent/cli 1.1014.1343 → 1.1014.1420
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/command-families/tables/register-reads.js +1 -1
- package/dist/command-support.js +4 -2
- package/dist/commands/blueprints.js +10 -6
- package/dist/commands/crm.js +5 -2
- package/dist/commands/dashboards.js +4 -2
- package/dist/commands/db.js +1 -1
- package/dist/commands/directory.js +7 -4
- package/dist/commands/inbox.js +5 -2
- package/dist/commands/prompts.js +4 -2
- package/dist/commands/publishing.js +5 -2
- package/dist/commands/sequences.js +13 -4
- package/dist/commands/support.js +30 -19
- package/dist/customer-operation-commands.js +0 -42
- package/dist/http-client.js +4 -1
- package/dist/workspace-placement-commands.js +1 -1
- package/node_modules/@oxygen/shared/dist/capability-args.d.ts +19 -0
- package/node_modules/@oxygen/shared/dist/capability-args.js +73 -0
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +2 -1
- package/node_modules/@oxygen/shared/dist/contracts/operations.js +3 -6
- package/node_modules/@oxygen/shared/dist/contracts/table-query-raw.d.ts +5 -1
- package/node_modules/@oxygen/shared/dist/contracts/table-query-raw.js +25 -2
- package/node_modules/@oxygen/shared/dist/contracts/teammates.js +1 -1
- package/node_modules/@oxygen/shared/dist/enrollment-origin.d.ts +3 -1
- package/node_modules/@oxygen/shared/dist/enrollment-origin.js +2 -0
- package/node_modules/@oxygen/shared/dist/guided-activation.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-company-context.d.ts +9 -2
- package/node_modules/@oxygen/shared/dist/guided-trial-billing.d.ts +10 -2
- package/node_modules/@oxygen/shared/dist/guided-trial-billing.js +2 -5
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +1 -0
- package/node_modules/@oxygen/shared/dist/onboarding-plays.d.ts +68 -0
- package/node_modules/@oxygen/shared/dist/onboarding-plays.js +114 -0
- package/node_modules/@oxygen/shared/dist/onboarding-research.d.ts +37 -0
- package/node_modules/@oxygen/shared/dist/onboarding-research.js +57 -0
- package/node_modules/@oxygen/shared/dist/placement-copy.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/placement-copy.js +16 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.js +8 -0
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +30 -2
- package/node_modules/@oxygen/shared/dist/sequences.js +67 -7
- package/node_modules/@oxygen/shared/dist/teammates.d.ts +3 -1
- package/node_modules/@oxygen/shared/dist/teammates.js +4 -2
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +19 -40
- 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/workspace-placement.js +4 -0
- package/package.json +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/guided-company-files.d.ts +0 -30
- package/node_modules/@oxygen/shared/dist/contracts/guided-company-files.js +0 -53
- package/node_modules/@oxygen/shared/dist/contracts/guided-first-sourcing.d.ts +0 -120
- package/node_modules/@oxygen/shared/dist/contracts/guided-first-sourcing.js +0 -48
- package/node_modules/@oxygen/shared/dist/contracts/guided-onboarding-journey.d.ts +0 -267
- package/node_modules/@oxygen/shared/dist/contracts/guided-onboarding-journey.js +0 -63
- package/node_modules/@oxygen/shared/dist/guided-company-files.d.ts +0 -13
- package/node_modules/@oxygen/shared/dist/guided-company-files.js +0 -14
- package/node_modules/@oxygen/shared/dist/guided-first-sourcing.d.ts +0 -116
- package/node_modules/@oxygen/shared/dist/guided-first-sourcing.js +0 -11
- package/node_modules/@oxygen/shared/dist/guided-onboarding-answers.d.ts +0 -4
- package/node_modules/@oxygen/shared/dist/guided-onboarding-answers.js +0 -42
- package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.d.ts +0 -147
- package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.fixture.d.ts +0 -12
- package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.fixture.js +0 -83
- package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.js +0 -44
- package/node_modules/@oxygen/shared/dist/guided-onboarding-prospects.d.ts +0 -7
- package/node_modules/@oxygen/shared/dist/guided-onboarding-prospects.js +0 -13
- package/node_modules/@oxygen/shared/dist/guided-onboarding-recommendations.d.ts +0 -54
- package/node_modules/@oxygen/shared/dist/guided-onboarding-recommendations.js +0 -100
- package/node_modules/@oxygen/shared/dist/guided-onboarding-signals.d.ts +0 -4
- package/node_modules/@oxygen/shared/dist/guided-onboarding-signals.js +0 -34
|
@@ -12,9 +12,6 @@ import { BROWSER_OPERATIONS } from "./browser-operations.js";
|
|
|
12
12
|
import { TABLE_FEED_UPDATE_OPERATION } from "./table-feed-update.js";
|
|
13
13
|
import { GEO_OPERATIONS } from "./geo.js";
|
|
14
14
|
import { CUSTOMER_OPERATIONS } from "./customer-operations.js";
|
|
15
|
-
import { GUIDED_ONBOARDING_JOURNEY_OPERATIONS } from "./guided-onboarding-journey.js";
|
|
16
|
-
import { GUIDED_FIRST_SOURCING_OPERATIONS } from "./guided-first-sourcing.js";
|
|
17
|
-
import { GUIDED_COMPANY_FILE_UPLOAD_OPERATION } from "./guided-company-files.js";
|
|
18
15
|
import { GUIDED_ACTIVATION_OPERATIONS } from "./guided-activation.js";
|
|
19
16
|
import { AGENT_SESSION_OPERATIONS } from "./agent-sessions.js";
|
|
20
17
|
import { REPLY_OXYGENT_OPERATIONS } from "./reply-oxygent.js";
|
|
@@ -63,6 +60,6 @@ export function operationFormData(spec, body) {
|
|
|
63
60
|
}
|
|
64
61
|
return form;
|
|
65
62
|
}
|
|
66
|
-
export const OPERATION_SPECS = [SENDER_LIMITS_SET_OPERATION, CONNECTOR_UPDATE_OPERATION, CONNECTOR_EVENTS_REVOKE_OPERATION, WORKSPACE_PAGES_GET_OPERATION, WORKSPACE_PAGES_UPDATE_OPERATION, TABLE_FEED_UPDATE_OPERATION, ...CUSTOMER_OPERATIONS, ...GEO_OPERATIONS, ...BROWSER_OPERATIONS, ...AGENT_SESSION_OPERATIONS, ...GUIDED_ACTIVATION_OPERATIONS, ...
|
|
67
|
-
export const CLI_OPERATIONS = [...CUSTOMER_OPERATIONS, ...GEO_OPERATIONS, ...BROWSER_OPERATIONS, ...AGENT_SESSION_OPERATIONS, ...GUIDED_ACTIVATION_OPERATIONS, ...
|
|
68
|
-
export const MCP_OPERATIONS = [...CUSTOMER_OPERATIONS, ...GEO_OPERATIONS, ...BROWSER_OPERATIONS, ...AGENT_SESSION_OPERATIONS, ...GUIDED_ACTIVATION_OPERATIONS, ...
|
|
63
|
+
export const OPERATION_SPECS = [SENDER_LIMITS_SET_OPERATION, CONNECTOR_UPDATE_OPERATION, CONNECTOR_EVENTS_REVOKE_OPERATION, WORKSPACE_PAGES_GET_OPERATION, WORKSPACE_PAGES_UPDATE_OPERATION, TABLE_FEED_UPDATE_OPERATION, ...CUSTOMER_OPERATIONS, ...GEO_OPERATIONS, ...BROWSER_OPERATIONS, ...AGENT_SESSION_OPERATIONS, ...GUIDED_ACTIVATION_OPERATIONS, ...TEAMMATE_OPERATIONS, ...REPLY_OXYGENT_OPERATIONS, ...WORKSPACE_SKILL_OPERATIONS];
|
|
64
|
+
export const CLI_OPERATIONS = [...CUSTOMER_OPERATIONS, ...GEO_OPERATIONS, ...BROWSER_OPERATIONS, ...AGENT_SESSION_OPERATIONS, ...GUIDED_ACTIVATION_OPERATIONS, ...TEAMMATE_OPERATIONS, ...REPLY_OXYGENT_OPERATIONS, ...WORKSPACE_SKILL_OPERATIONS].filter((spec) => spec.generated?.cli !== false);
|
|
65
|
+
export const MCP_OPERATIONS = [...CUSTOMER_OPERATIONS, ...GEO_OPERATIONS, ...BROWSER_OPERATIONS, ...AGENT_SESSION_OPERATIONS, ...GUIDED_ACTIVATION_OPERATIONS, ...TEAMMATE_OPERATIONS, ...REPLY_OXYGENT_OPERATIONS, ...WORKSPACE_SKILL_OPERATIONS].filter((spec) => spec.generated?.mcp !== false);
|
|
@@ -13,4 +13,8 @@ export declare const TABLE_QUERY_CLI_DEFAULT_MAX_CHARS = 12000;
|
|
|
13
13
|
export declare function tableQueryRawResultFits(data: Record<string, unknown>, maxChars: number | undefined): boolean;
|
|
14
14
|
/** What a page shortened to fit its result window says to do next (CLI, MCP and API alike). */
|
|
15
15
|
export declare const TABLE_QUERY_RESULT_WINDOW_NEXT_STEP: string;
|
|
16
|
-
|
|
16
|
+
/** The field whose value is longest in a row (system fields excluded): the one to drop or inspect. */
|
|
17
|
+
export declare function tableRowWidestField(row: unknown): string | undefined;
|
|
18
|
+
/** Leads a refusal's next step with the exact field to drop or inspect, when the row is known. */
|
|
19
|
+
export declare function tableQueryWidestFieldStep(widestField: string | undefined): string;
|
|
20
|
+
export declare function tableQueryRawRecovery(table: string, row?: string, firstRow?: unknown): Record<string, unknown>;
|
|
@@ -12,10 +12,33 @@ export function tableQueryRawResultFits(data, maxChars) {
|
|
|
12
12
|
/** What a page shortened to fit its result window says to do next (CLI, MCP and API alike). */
|
|
13
13
|
export const TABLE_QUERY_RESULT_WINDOW_NEXT_STEP = "This page holds fewer rows than the limit so it fits the result window; every value is complete. "
|
|
14
14
|
+ "Continue with nextCursor (same table, filters, sorts and fields), or select fewer fields for more rows per page.";
|
|
15
|
-
|
|
15
|
+
/** The field whose value is longest in a row (system fields excluded): the one to drop or inspect. */
|
|
16
|
+
export function tableRowWidestField(row) {
|
|
17
|
+
if (!row || typeof row !== "object" || Array.isArray(row))
|
|
18
|
+
return undefined;
|
|
19
|
+
let widest;
|
|
20
|
+
let widestLength = 0;
|
|
21
|
+
for (const [key, value] of Object.entries(row)) {
|
|
22
|
+
if (key.startsWith("_") || value === undefined)
|
|
23
|
+
continue;
|
|
24
|
+
const length = JSON.stringify(value).length;
|
|
25
|
+
if (length > widestLength) {
|
|
26
|
+
widest = key;
|
|
27
|
+
widestLength = length;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return widest;
|
|
31
|
+
}
|
|
32
|
+
/** Leads a refusal's next step with the exact field to drop or inspect, when the row is known. */
|
|
33
|
+
export function tableQueryWidestFieldStep(widestField) {
|
|
34
|
+
return widestField ? `Field \`${widestField}\` is the largest value in that row: drop it from fields or inspect it. ` : "";
|
|
35
|
+
}
|
|
36
|
+
export function tableQueryRawRecovery(table, row, firstRow) {
|
|
37
|
+
const widestField = tableRowWidestField(firstRow);
|
|
16
38
|
return {
|
|
17
39
|
table,
|
|
18
40
|
...(row ? { row } : {}),
|
|
19
|
-
|
|
41
|
+
...(widestField ? { widest_field: widestField } : {}),
|
|
42
|
+
next_step: tableQueryWidestFieldStep(widestField) + "Reduce fields/limit. Inspect a large cell with `tables rows inspect --table <table> --row <row> --field <field> --projection value --path /row/<field>/<key>`; select a JSON Pointer and continue with value_cursor. MCP: hydrate oxygen_table_row_inspect with oxygen_capabilities_schema, then dispatch it through oxygen_capabilities_read. CLI: --full restores legacy reads; --output <path> keeps full JSON outside context.",
|
|
20
43
|
};
|
|
21
44
|
}
|
|
@@ -5,7 +5,7 @@ import { NEW_TEAMMATE_NAME, TEAMMATE_AVATAR_COLORS, TEAMMATE_AVATAR_KEYS, TEAMMA
|
|
|
5
5
|
import { arr, bool, enumOf, int, obj, str } from "./schema.js";
|
|
6
6
|
/**
|
|
7
7
|
* AI teammates (ADR 0032), branded Oxygents: Agents with one ongoing chat,
|
|
8
|
-
* shared by default or Personal, the workspace's first one ("
|
|
8
|
+
* shared by default or Personal, the workspace's first one ("Oxygent" until named) and
|
|
9
9
|
* any others added, none with a role (Tim, 2026-10-13). Every member but a client
|
|
10
10
|
* login has them where the workspace does (launched 2026-10-17).
|
|
11
11
|
* The people-facing names say Oxygent (`oxygen oxygents`, `oxygen_oxygents_*`,
|
|
@@ -16,7 +16,9 @@ export type EnrollmentOriginKind = typeof ENROLLMENT_ORIGIN_KINDS[number];
|
|
|
16
16
|
export declare const ENROLLMENT_ACTOR_TYPES: readonly ["user", "agent", "workflow", "api_key", "system"];
|
|
17
17
|
export type EnrollmentActorType = typeof ENROLLMENT_ACTOR_TYPES[number];
|
|
18
18
|
/** The surface the enroll call came through. */
|
|
19
|
-
export declare const ENROLLMENT_GATEWAYS: readonly ["cli", "mcp", "web", "copilot", "agent", "workflow", "engagement_watch"
|
|
19
|
+
export declare const ENROLLMENT_GATEWAYS: readonly ["cli", "mcp", "web", "copilot", "agent", "workflow", "engagement_watch",
|
|
20
|
+
/** The scheduled sync that enrolls new rows of a live campaign's bound table. */
|
|
21
|
+
"table_sync"];
|
|
20
22
|
export type EnrollmentGateway = typeof ENROLLMENT_GATEWAYS[number];
|
|
21
23
|
export declare const ENROLLMENT_REF_TYPES: readonly ["workflow_run", "agent_run", "copilot_session", "engagement_watch", "guided_activation"];
|
|
22
24
|
export type EnrollmentRefType = typeof ENROLLMENT_REF_TYPES[number];
|
|
@@ -47,11 +47,13 @@ export type GuidedActivation = {
|
|
|
47
47
|
resources: GuidedActivationResources;
|
|
48
48
|
outcomes: Partial<Record<"first_leads" | "sequence" | "launch" | GuidedActivationPurpose, "proposed" | "armed" | "launched" | "confirmed_sent" | "paused">>;
|
|
49
49
|
reviews: Partial<Record<GuidedActivationReview["purpose"], GuidedActivationReview>>;
|
|
50
|
+
/** `finished_at`: the person chose a play (or closed onboarding), so the chat is an ordinary Oxygent chat again. */
|
|
50
51
|
onboarding_start: {
|
|
51
52
|
state: "claimed" | "posted";
|
|
52
53
|
chat_id: string;
|
|
53
54
|
turn_id?: string;
|
|
54
55
|
event_seq?: number;
|
|
56
|
+
finished_at?: string;
|
|
55
57
|
} | null;
|
|
56
58
|
last_fault: {
|
|
57
59
|
code: string;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { GuidedOnboardingAnswers } from "./guided-onboarding-journey.js";
|
|
2
1
|
/** One native-supported input budget for web/CLI/MCP and D research preparation. */
|
|
3
2
|
export declare const GUIDED_ONBOARDING_COMPANY_CONTEXT_LIMITS: {
|
|
4
3
|
readonly descriptionChars: 6000;
|
|
@@ -7,6 +6,14 @@ export declare const GUIDED_ONBOARDING_COMPANY_CONTEXT_LIMITS: {
|
|
|
7
6
|
readonly nameChars: 160;
|
|
8
7
|
readonly urlChars: 2000;
|
|
9
8
|
};
|
|
9
|
+
/** Company context saved on a guided activation; the knowledge bootstrap still reads it. */
|
|
10
|
+
export type GuidedOnboardingCompanyContext = {
|
|
11
|
+
website: string | null;
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
context_links: string[];
|
|
15
|
+
file_ids: string[];
|
|
16
|
+
};
|
|
10
17
|
export declare const GUIDED_ONBOARDING_COMPANY_CONTEXT_CONTRACT: import("./contracts/schema.js").ContractObject<{
|
|
11
18
|
readonly website: {
|
|
12
19
|
oneOf: [import("./contracts/schema.js").StringProperty, import("./contracts/schema.js").NullProperty];
|
|
@@ -19,4 +26,4 @@ export declare const GUIDED_ONBOARDING_COMPANY_CONTEXT_CONTRACT: import("./contr
|
|
|
19
26
|
}, "context_links" | "description" | "file_ids" | "name" | "website">;
|
|
20
27
|
/** Input-only rule; existing evidence links retain their independent navigation policy. */
|
|
21
28
|
export declare function normalizeGuidedOnboardingContextLink(value: string): string;
|
|
22
|
-
export declare function normalizeGuidedOnboardingCompanyContext(input: unknown):
|
|
29
|
+
export declare function normalizeGuidedOnboardingCompanyContext(input: unknown): GuidedOnboardingCompanyContext;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
/** DEV onboarding offer.
|
|
1
|
+
/** Retired DEV onboarding trial offer. New checkouts no longer create it; the
|
|
2
|
+
* marker still identifies subscriptions that were started under it. */
|
|
2
3
|
export declare const GUIDED_TRIAL_OFFER: {
|
|
3
4
|
readonly key: "guided_onboarding_v2";
|
|
4
5
|
readonly plan: "oxygen_49";
|
|
@@ -10,7 +11,6 @@ export declare const GUIDED_TRIAL_OFFER: {
|
|
|
10
11
|
readonly card_required: true;
|
|
11
12
|
readonly early_charge_allowed: false;
|
|
12
13
|
};
|
|
13
|
-
export declare function guidedTrialAcquisitionEnabled(env?: Record<string, string | undefined>): boolean;
|
|
14
14
|
export declare function isGuidedTrialSubscription(metadata: Record<string, unknown> | null | undefined): boolean;
|
|
15
15
|
export type GuidedTrialBilling = {
|
|
16
16
|
offer: typeof GUIDED_TRIAL_OFFER;
|
|
@@ -24,3 +24,11 @@ export type GuidedTrialBilling = {
|
|
|
24
24
|
spendable_credits: number;
|
|
25
25
|
funded_work_paused: boolean;
|
|
26
26
|
};
|
|
27
|
+
/** Read projection from native billing: a paid plan or a retained guided trial. */
|
|
28
|
+
export type GuidedOnboardingEligibility = {
|
|
29
|
+
state: "unverified" | "checkout_required" | "checkout_pending" | "eligible" | "unavailable";
|
|
30
|
+
basis: "paid" | "trial" | null;
|
|
31
|
+
subscription_id: string | null;
|
|
32
|
+
observed_at: string | null;
|
|
33
|
+
trial_ends_at: string | null;
|
|
34
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** Retired DEV onboarding trial offer. New checkouts no longer create it; the
|
|
2
|
+
* marker still identifies subscriptions that were started under it. */
|
|
3
3
|
export const GUIDED_TRIAL_OFFER = {
|
|
4
4
|
key: "guided_onboarding_v2",
|
|
5
5
|
plan: "oxygen_49",
|
|
@@ -11,9 +11,6 @@ export const GUIDED_TRIAL_OFFER = {
|
|
|
11
11
|
card_required: true,
|
|
12
12
|
early_charge_allowed: false,
|
|
13
13
|
};
|
|
14
|
-
export function guidedTrialAcquisitionEnabled(env = process.env) {
|
|
15
|
-
return guidedActivationEnabled(env);
|
|
16
|
-
}
|
|
17
14
|
export function isGuidedTrialSubscription(metadata) {
|
|
18
15
|
return metadata?.oxygen_trial_offer === GUIDED_TRIAL_OFFER.key;
|
|
19
16
|
}
|
|
@@ -256,6 +256,12 @@ export type KnowledgeBootstrapConsent = {
|
|
|
256
256
|
source: string | null;
|
|
257
257
|
/** Clerk user who accepted, when the surface knows. Informational. */
|
|
258
258
|
granted_by_clerk_user_id: string | null;
|
|
259
|
+
/**
|
|
260
|
+
* `agent`: agent-led onboarding (Philipp, 2026-10-19). The Oxygent researches the
|
|
261
|
+
* company in the chat with its sub-agents, so the automatic background pass and
|
|
262
|
+
* the brain draft do not run; a manual request still does.
|
|
263
|
+
*/
|
|
264
|
+
research?: "agent";
|
|
259
265
|
};
|
|
260
266
|
/**
|
|
261
267
|
* Read consent out of an organization's control-DB metadata blob, FAIL-CLOSED.
|
|
@@ -243,6 +243,7 @@ function readGrant(raw) {
|
|
|
243
243
|
...(creatorLinkedinUrl ? { creator_linkedin_url: creatorLinkedinUrl } : {}),
|
|
244
244
|
source: typeof record.source === "string" && record.source.length > 0 ? record.source : null,
|
|
245
245
|
granted_by_clerk_user_id: grantedBy,
|
|
246
|
+
...(record.research === "agent" ? { research: "agent" } : {}),
|
|
246
247
|
},
|
|
247
248
|
};
|
|
248
249
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export declare const ONBOARDING_PROPOSE_PLAYS_TOOL_NAME = "propose_plays";
|
|
2
|
+
export declare const ONBOARDING_PLAY_LIMITS: {
|
|
3
|
+
readonly minPlays: 2;
|
|
4
|
+
readonly maxPlays: 4;
|
|
5
|
+
readonly titleChars: 80;
|
|
6
|
+
readonly lineChars: 240;
|
|
7
|
+
readonly slugChars: 120;
|
|
8
|
+
};
|
|
9
|
+
export type OnboardingPlay = {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
/** Who it targets: segment and buyer. */
|
|
13
|
+
audience: string;
|
|
14
|
+
/** The signal or trigger it acts on. */
|
|
15
|
+
trigger: string | null;
|
|
16
|
+
/** The angle: what the message offers them. */
|
|
17
|
+
angle: string;
|
|
18
|
+
/** Why it fits this company, from the research. */
|
|
19
|
+
why: string | null;
|
|
20
|
+
/** What running it does first. */
|
|
21
|
+
firstStep: string;
|
|
22
|
+
/** The play's Knowledge page, when one was written. */
|
|
23
|
+
page: string | null;
|
|
24
|
+
};
|
|
25
|
+
export type OnboardingPlayProposal = {
|
|
26
|
+
id: string;
|
|
27
|
+
plays: OnboardingPlay[];
|
|
28
|
+
};
|
|
29
|
+
/** The proposal a chat is waiting on, as every surface reads it. */
|
|
30
|
+
export type PendingOnboardingPlayProposal = OnboardingPlayProposal & {
|
|
31
|
+
turnId: string;
|
|
32
|
+
};
|
|
33
|
+
/** The tool's input schema, as the model reads it. */
|
|
34
|
+
export declare const ONBOARDING_PROPOSE_PLAYS_INPUT_SCHEMA: Record<string, unknown>;
|
|
35
|
+
/** Validate the plays the model proposed, minting ids. */
|
|
36
|
+
export declare function normalizeOnboardingPlayProposal(args: Record<string, unknown>, mintId: () => string): {
|
|
37
|
+
ok: true;
|
|
38
|
+
proposal: OnboardingPlayProposal;
|
|
39
|
+
} | {
|
|
40
|
+
ok: false;
|
|
41
|
+
message: string;
|
|
42
|
+
};
|
|
43
|
+
/** The proposal as the turn's reply: what the CLI and MCP print, and what the next turn reads back. */
|
|
44
|
+
export declare function renderOnboardingPlayProposalText(proposal: OnboardingPlayProposal): string;
|
|
45
|
+
/** Read a stored proposal back, refusing anything malformed. */
|
|
46
|
+
export declare function readOnboardingPlayProposal(value: unknown): OnboardingPlayProposal | null;
|
|
47
|
+
/** The message a chosen play sends, as the person's own. */
|
|
48
|
+
export declare function onboardingPlayMessage(play: Pick<OnboardingPlay, "title" | "firstStep" | "page">): string;
|
|
49
|
+
type ProjectionTurn = {
|
|
50
|
+
id: string;
|
|
51
|
+
status: string;
|
|
52
|
+
turnIndex: number;
|
|
53
|
+
};
|
|
54
|
+
type ProjectionEvent = {
|
|
55
|
+
turnId: string | null;
|
|
56
|
+
kind: string;
|
|
57
|
+
payload: Record<string, unknown>;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* The plays the chat is waiting on, or null. Like a question, only the newest
|
|
61
|
+
* turn can be waiting, and only when it completed by proposing: choosing one
|
|
62
|
+
* sends a new message, which is the end of the wait.
|
|
63
|
+
*/
|
|
64
|
+
export declare function projectPendingOnboardingPlayProposal(input: {
|
|
65
|
+
turns: readonly ProjectionTurn[];
|
|
66
|
+
events: readonly ProjectionEvent[];
|
|
67
|
+
}): PendingOnboardingPlayProposal | null;
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Agent-led onboarding (Philipp, 2026-10-19): the onboarding turn ends with a
|
|
2
|
+
// few proposed outbound plays as cards. Choosing one has the Oxygent run it in
|
|
3
|
+
// the chat, as an ordinary message the person sent; typing steers instead.
|
|
4
|
+
//
|
|
5
|
+
// One owner for the contract, because four readers depend on it: the tool that
|
|
6
|
+
// validates the model's proposal, the session read that projects the pending
|
|
7
|
+
// one, the card that shows it and the route that runs the chosen play.
|
|
8
|
+
export const ONBOARDING_PROPOSE_PLAYS_TOOL_NAME = "propose_plays";
|
|
9
|
+
export const ONBOARDING_PLAY_LIMITS = {
|
|
10
|
+
minPlays: 2,
|
|
11
|
+
maxPlays: 4,
|
|
12
|
+
titleChars: 80,
|
|
13
|
+
lineChars: 240,
|
|
14
|
+
slugChars: 120,
|
|
15
|
+
};
|
|
16
|
+
const line = (description) => ({ type: "string", minLength: 1, maxLength: ONBOARDING_PLAY_LIMITS.lineChars, description });
|
|
17
|
+
/** The tool's input schema, as the model reads it. */
|
|
18
|
+
export const ONBOARDING_PROPOSE_PLAYS_INPUT_SCHEMA = {
|
|
19
|
+
type: "object",
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
properties: {
|
|
22
|
+
plays: {
|
|
23
|
+
type: "array",
|
|
24
|
+
minItems: ONBOARDING_PLAY_LIMITS.minPlays,
|
|
25
|
+
maxItems: ONBOARDING_PLAY_LIMITS.maxPlays,
|
|
26
|
+
items: {
|
|
27
|
+
type: "object",
|
|
28
|
+
additionalProperties: false,
|
|
29
|
+
properties: {
|
|
30
|
+
title: { type: "string", minLength: 1, maxLength: ONBOARDING_PLAY_LIMITS.titleChars, description: "Short name, e.g. \"New VP Sales at Series A SaaS\"." },
|
|
31
|
+
audience: line("Who: the segment and the buyer persona."),
|
|
32
|
+
trigger: line("The signal or event that makes now the moment, if any."),
|
|
33
|
+
angle: line("What the outreach offers them, in one line."),
|
|
34
|
+
why: line("Why this fits this company, from the research."),
|
|
35
|
+
first_step: line("What running it does first, e.g. \"Find 50 matching accounts and their VP Sales\"."),
|
|
36
|
+
page: { type: "string", maxLength: ONBOARDING_PLAY_LIMITS.slugChars, description: "The play's Knowledge page slug, if you wrote one." },
|
|
37
|
+
},
|
|
38
|
+
required: ["title", "audience", "angle", "first_step"],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: ["plays"],
|
|
43
|
+
};
|
|
44
|
+
const clean = (value, max) => typeof value === "string" ? value.replace(/\s+/g, " ").trim().slice(0, max) : "";
|
|
45
|
+
/** Validate the plays the model proposed, minting ids. */
|
|
46
|
+
export function normalizeOnboardingPlayProposal(args, mintId) {
|
|
47
|
+
const raw = Array.isArray(args.plays) ? args.plays : [];
|
|
48
|
+
const plays = [];
|
|
49
|
+
for (const entry of raw) {
|
|
50
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
51
|
+
continue;
|
|
52
|
+
const record = entry;
|
|
53
|
+
const title = clean(record.title, ONBOARDING_PLAY_LIMITS.titleChars);
|
|
54
|
+
const audience = clean(record.audience, ONBOARDING_PLAY_LIMITS.lineChars);
|
|
55
|
+
const angle = clean(record.angle, ONBOARDING_PLAY_LIMITS.lineChars);
|
|
56
|
+
const firstStep = clean(record.first_step ?? record.firstStep, ONBOARDING_PLAY_LIMITS.lineChars);
|
|
57
|
+
if (!title || !audience || !angle || !firstStep)
|
|
58
|
+
continue;
|
|
59
|
+
const trigger = clean(record.trigger, ONBOARDING_PLAY_LIMITS.lineChars);
|
|
60
|
+
const why = clean(record.why, ONBOARDING_PLAY_LIMITS.lineChars);
|
|
61
|
+
const page = clean(record.page, ONBOARDING_PLAY_LIMITS.slugChars);
|
|
62
|
+
plays.push({ id: typeof record.id === "string" && record.id ? record.id : mintId(), title, audience, trigger: trigger || null,
|
|
63
|
+
angle, why: why || null, firstStep, page: /^[a-z0-9][a-z0-9-]*$/.test(page) ? page : null });
|
|
64
|
+
}
|
|
65
|
+
if (plays.length < ONBOARDING_PLAY_LIMITS.minPlays || plays.length > ONBOARDING_PLAY_LIMITS.maxPlays) {
|
|
66
|
+
return { ok: false, message: `Propose ${ONBOARDING_PLAY_LIMITS.minPlays} to ${ONBOARDING_PLAY_LIMITS.maxPlays} plays, each with a title, audience, angle and first step.` };
|
|
67
|
+
}
|
|
68
|
+
return { ok: true, proposal: { id: typeof args.id === "string" && args.id ? args.id : mintId(), plays } };
|
|
69
|
+
}
|
|
70
|
+
/** The proposal as the turn's reply: what the CLI and MCP print, and what the next turn reads back. */
|
|
71
|
+
export function renderOnboardingPlayProposalText(proposal) {
|
|
72
|
+
return [
|
|
73
|
+
"Plays I'd run first:",
|
|
74
|
+
...proposal.plays.map((play, index) => `${index + 1}. **${play.title}**: ${play.audience}. ${play.angle}`),
|
|
75
|
+
"Pick one to start it, or tell me what to change.",
|
|
76
|
+
].join("\n");
|
|
77
|
+
}
|
|
78
|
+
/** Read a stored proposal back, refusing anything malformed. */
|
|
79
|
+
export function readOnboardingPlayProposal(value) {
|
|
80
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
81
|
+
return null;
|
|
82
|
+
const record = value;
|
|
83
|
+
if (typeof record.id !== "string" || !record.id)
|
|
84
|
+
return null;
|
|
85
|
+
const normalized = normalizeOnboardingPlayProposal(record, () => "");
|
|
86
|
+
return normalized.ok && normalized.proposal.plays.every((play) => play.id) ? normalized.proposal : null;
|
|
87
|
+
}
|
|
88
|
+
/** The message a chosen play sends, as the person's own. */
|
|
89
|
+
export function onboardingPlayMessage(play) {
|
|
90
|
+
return `Run the play "${play.title}". Start with: ${play.firstStep}${play.page ? ` (see [[${play.page}]])` : ""}`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The plays the chat is waiting on, or null. Like a question, only the newest
|
|
94
|
+
* turn can be waiting, and only when it completed by proposing: choosing one
|
|
95
|
+
* sends a new message, which is the end of the wait.
|
|
96
|
+
*/
|
|
97
|
+
export function projectPendingOnboardingPlayProposal(input) {
|
|
98
|
+
const newest = input.turns.reduce((latest, turn) => (latest === null || turn.turnIndex > latest.turnIndex ? turn : latest), null);
|
|
99
|
+
if (!newest || newest.status !== "completed")
|
|
100
|
+
return null;
|
|
101
|
+
for (let index = input.events.length - 1; index >= 0; index -= 1) {
|
|
102
|
+
const event = input.events[index];
|
|
103
|
+
if (!event || event.turnId !== newest.id || event.kind !== "tool_call_finished")
|
|
104
|
+
continue;
|
|
105
|
+
if (event.payload.tool !== ONBOARDING_PROPOSE_PLAYS_TOOL_NAME)
|
|
106
|
+
continue;
|
|
107
|
+
const result = event.payload.result;
|
|
108
|
+
if (!result || typeof result !== "object" || result.ok === false)
|
|
109
|
+
continue;
|
|
110
|
+
const proposal = readOnboardingPlayProposal(result.proposal);
|
|
111
|
+
return proposal ? { ...proposal, turnId: newest.id } : null;
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-led onboarding's research (Philipp, 2026-10-19; OXYGEN_PRODUCT.md
|
|
3
|
+
* "Agent-led onboarding"): the first "Start onboarding" turn of a guided
|
|
4
|
+
* activation is OXYGEN-funded, and its research sub-agents may search the web,
|
|
5
|
+
* read the company's site and LinkedIn, find its people and write draft
|
|
6
|
+
* Knowledge pages in parallel, without an approval card per call. These bounds
|
|
7
|
+
* are what replaces the card. Shared by the worker (the sub-agents' grant), the
|
|
8
|
+
* tool route (who pays) and the credit rail (the funding policy).
|
|
9
|
+
*/
|
|
10
|
+
export declare const ONBOARDING_RESEARCH_PURPOSE = "onboarding_research";
|
|
11
|
+
/** The research sub-agent the onboarding turn delegates to. */
|
|
12
|
+
export declare const ONBOARDING_RESEARCH_SUBAGENT = "onboarding_researcher";
|
|
13
|
+
/** Provider credits OXYGEN pays for one workspace's onboarding research, over its lifetime. */
|
|
14
|
+
export declare const ONBOARDING_RESEARCH_MAX_CREDITS = 300;
|
|
15
|
+
/** Ceiling on any one research call; a call that names a higher one is lowered to it. */
|
|
16
|
+
export declare const ONBOARDING_RESEARCH_MAX_CREDITS_PER_CALL = 15;
|
|
17
|
+
/** The onboarding turn's own and its sub-agents' inference, credited back once the turn settles. */
|
|
18
|
+
export declare const ONBOARDING_RESEARCH_INFERENCE_MAX_CREDITS = 150;
|
|
19
|
+
/**
|
|
20
|
+
* Eight areas at once (offer, ICP, plays, signals, competitors, company
|
|
21
|
+
* LinkedIn, people, the signing-up person), with room for one follow-up pass.
|
|
22
|
+
*/
|
|
23
|
+
export declare const ONBOARDING_RESEARCH_SUBAGENT_LIMITS: {
|
|
24
|
+
readonly maxRuns: 12;
|
|
25
|
+
readonly maxParallelTasks: 8;
|
|
26
|
+
readonly maxInferencesPerRun: 8;
|
|
27
|
+
readonly maxCapabilityCallsPerRun: 24;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Read-only provider tools the research may call through `oxygen_tools_run_live`.
|
|
31
|
+
* Nothing here writes outside OXYGEN; the costly ones (competitor search at 61
|
|
32
|
+
* credits, large exa result sets) stay out or are capped per call.
|
|
33
|
+
*/
|
|
34
|
+
export declare const ONBOARDING_RESEARCH_TOOL_IDS: readonly ["serper.search", "exa.search", "exa.contents", "exa.find_similar", "firecrawl.map", "firecrawl.scrape", "web.page_fetch", "scraper.linkedin_company", "scraper.linkedin_profile", "scraper.linkedin_profile_search", "scraper.linkedin_job_search", "blitzapi.employee_search", "blitzapi.person_profile_enrich", "blitzapi.company_profile_enrich", "predictleads.company_news_events", "predictleads.company_job_openings", "predictleads.company_similar_companies"];
|
|
35
|
+
export declare function onboardingResearchToolAllowed(toolId: unknown): boolean;
|
|
36
|
+
/** Knowledge page types a research sub-agent may not write: canonical voice and positioning stay proposal-gated. */
|
|
37
|
+
export declare const ONBOARDING_RESEARCH_DENIED_PAGE_TYPES: readonly ["voice", "brand", "positioning", "message_playbook", "playbook"];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-led onboarding's research (Philipp, 2026-10-19; OXYGEN_PRODUCT.md
|
|
3
|
+
* "Agent-led onboarding"): the first "Start onboarding" turn of a guided
|
|
4
|
+
* activation is OXYGEN-funded, and its research sub-agents may search the web,
|
|
5
|
+
* read the company's site and LinkedIn, find its people and write draft
|
|
6
|
+
* Knowledge pages in parallel, without an approval card per call. These bounds
|
|
7
|
+
* are what replaces the card. Shared by the worker (the sub-agents' grant), the
|
|
8
|
+
* tool route (who pays) and the credit rail (the funding policy).
|
|
9
|
+
*/
|
|
10
|
+
export const ONBOARDING_RESEARCH_PURPOSE = "onboarding_research";
|
|
11
|
+
/** The research sub-agent the onboarding turn delegates to. */
|
|
12
|
+
export const ONBOARDING_RESEARCH_SUBAGENT = "onboarding_researcher";
|
|
13
|
+
/** Provider credits OXYGEN pays for one workspace's onboarding research, over its lifetime. */
|
|
14
|
+
export const ONBOARDING_RESEARCH_MAX_CREDITS = 300;
|
|
15
|
+
/** Ceiling on any one research call; a call that names a higher one is lowered to it. */
|
|
16
|
+
export const ONBOARDING_RESEARCH_MAX_CREDITS_PER_CALL = 15;
|
|
17
|
+
/** The onboarding turn's own and its sub-agents' inference, credited back once the turn settles. */
|
|
18
|
+
export const ONBOARDING_RESEARCH_INFERENCE_MAX_CREDITS = 150;
|
|
19
|
+
/**
|
|
20
|
+
* Eight areas at once (offer, ICP, plays, signals, competitors, company
|
|
21
|
+
* LinkedIn, people, the signing-up person), with room for one follow-up pass.
|
|
22
|
+
*/
|
|
23
|
+
export const ONBOARDING_RESEARCH_SUBAGENT_LIMITS = {
|
|
24
|
+
maxRuns: 12,
|
|
25
|
+
maxParallelTasks: 8,
|
|
26
|
+
maxInferencesPerRun: 8,
|
|
27
|
+
maxCapabilityCallsPerRun: 24,
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Read-only provider tools the research may call through `oxygen_tools_run_live`.
|
|
31
|
+
* Nothing here writes outside OXYGEN; the costly ones (competitor search at 61
|
|
32
|
+
* credits, large exa result sets) stay out or are capped per call.
|
|
33
|
+
*/
|
|
34
|
+
export const ONBOARDING_RESEARCH_TOOL_IDS = [
|
|
35
|
+
"serper.search",
|
|
36
|
+
"exa.search",
|
|
37
|
+
"exa.contents",
|
|
38
|
+
"exa.find_similar",
|
|
39
|
+
"firecrawl.map",
|
|
40
|
+
"firecrawl.scrape",
|
|
41
|
+
"web.page_fetch",
|
|
42
|
+
"scraper.linkedin_company",
|
|
43
|
+
"scraper.linkedin_profile",
|
|
44
|
+
"scraper.linkedin_profile_search",
|
|
45
|
+
"scraper.linkedin_job_search",
|
|
46
|
+
"blitzapi.employee_search",
|
|
47
|
+
"blitzapi.person_profile_enrich",
|
|
48
|
+
"blitzapi.company_profile_enrich",
|
|
49
|
+
"predictleads.company_news_events",
|
|
50
|
+
"predictleads.company_job_openings",
|
|
51
|
+
"predictleads.company_similar_companies",
|
|
52
|
+
];
|
|
53
|
+
export function onboardingResearchToolAllowed(toolId) {
|
|
54
|
+
return typeof toolId === "string" && ONBOARDING_RESEARCH_TOOL_IDS.includes(toolId);
|
|
55
|
+
}
|
|
56
|
+
/** Knowledge page types a research sub-agent may not write: canonical voice and positioning stay proposal-gated. */
|
|
57
|
+
export const ONBOARDING_RESEARCH_DENIED_PAGE_TYPES = ["voice", "brand", "positioning", "message_playbook", "playbook"];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Browser-safe copy policy shared by placement forms and native request owners. */
|
|
2
|
+
export declare const PLACEMENT_COPY_GUIDANCE = "Use the outreach email you would send to a prospect. Import a Sequence step or write realistic copy; do not use delivery-check text or no-reply notices.";
|
|
3
|
+
export declare const PLACEMENT_SAMPLE_COPY: {
|
|
4
|
+
subject: string;
|
|
5
|
+
body_text: string;
|
|
6
|
+
};
|
|
7
|
+
/** Reject obvious diagnostic placeholders, without scoring or rewriting outreach. */
|
|
8
|
+
export declare function placementCopyIssue(subject: string, bodyText: string, bodyHtml?: string): string | null;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Browser-safe copy policy shared by placement forms and native request owners. */
|
|
2
|
+
export const PLACEMENT_COPY_GUIDANCE = "Use the outreach email you would send to a prospect. Import a Sequence step or write realistic copy; do not use delivery-check text or no-reply notices.";
|
|
3
|
+
export const PLACEMENT_SAMPLE_COPY = {
|
|
4
|
+
subject: "A question about your outbound workflow",
|
|
5
|
+
body_text: "Hi,\n\nHow does your team move from a list of target accounts to personalized outreach today?\n\nWe help teams keep research, enrichment and follow-ups in one workspace so reps spend less time moving data between tools.\n\nWould it be useful to compare that with your current workflow?",
|
|
6
|
+
};
|
|
7
|
+
/** Reject obvious diagnostic placeholders, without scoring or rewriting outreach. */
|
|
8
|
+
export function placementCopyIssue(subject, bodyText, bodyHtml) {
|
|
9
|
+
if (!subject.trim() || !bodyText.trim())
|
|
10
|
+
return PLACEMENT_COPY_GUIDANCE;
|
|
11
|
+
const diagnosticSubject = /^(?:(?:oxygen|emailguard|sendkit|dev|workspace|internal|plain|html)\s+)*(?:(?:inbox[ -]placement|inbox|deliverability|delivery|placement|spam|primary)(?:\s+(?:test|check|probe))|test\s+(?:email|message))(?:\s|[:—–-]|$)/i;
|
|
12
|
+
const diagnosticBody = /\b(?:this\s+(?:(?:email|message)\s+)?is\s+(?:an?\s+|the\s+)?(?:(?:oxygen|dev|workspace)\s+)*(?:inbox[ -]placement|inbox|deliverability|delivery|placement)\s+(?:test|check|probe)|no\s+(?:reply|response)\s+(?:is\s+)?(?:needed|required)|do\s+not\s+reply)\b/i;
|
|
13
|
+
const htmlText = bodyHtml?.replace(/<[^<>]*>/g, " ").replace(/ | /gi, " ") ?? "";
|
|
14
|
+
return diagnosticSubject.test(subject.trim()) || diagnosticBody.test(bodyText) || diagnosticBody.test(htmlText)
|
|
15
|
+
? PLACEMENT_COPY_GUIDANCE : null;
|
|
16
|
+
}
|
|
@@ -25,6 +25,14 @@ export declare const PRODUCT_EVENTS: {
|
|
|
25
25
|
readonly ONBOARDING_RESET: "onboarding_reset";
|
|
26
26
|
readonly ACTIVATION_PATH_SELECTED: "activation_path_selected";
|
|
27
27
|
readonly AGENT_CONNECTED: "agent_connected";
|
|
28
|
+
/**
|
|
29
|
+
* Browser-side, on onboarding's Install OXYGEN step (`/setup/agent`): what a
|
|
30
|
+
* founder did there before connecting or leaving. `action` is agent_selected,
|
|
31
|
+
* endpoint_copied, prompt_copied, connected, open_agent, open_oxygen,
|
|
32
|
+
* copilot_instead or skipped; `agent` is claude, chatgpt or cli;
|
|
33
|
+
* `seconds_on_step` is a number. A handful per signup, never per poll.
|
|
34
|
+
*/
|
|
35
|
+
readonly AGENT_SETUP_ACTION: "agent_setup_action";
|
|
28
36
|
readonly FIRST_RECORD_CREATED: "first_record_created";
|
|
29
37
|
/**
|
|
30
38
|
* The prescribed first play (inbound-led outbound), walked on ANY surface.
|
|
@@ -25,6 +25,14 @@ export const PRODUCT_EVENTS = {
|
|
|
25
25
|
ONBOARDING_RESET: "onboarding_reset",
|
|
26
26
|
ACTIVATION_PATH_SELECTED: "activation_path_selected",
|
|
27
27
|
AGENT_CONNECTED: "agent_connected",
|
|
28
|
+
/**
|
|
29
|
+
* Browser-side, on onboarding's Install OXYGEN step (`/setup/agent`): what a
|
|
30
|
+
* founder did there before connecting or leaving. `action` is agent_selected,
|
|
31
|
+
* endpoint_copied, prompt_copied, connected, open_agent, open_oxygen,
|
|
32
|
+
* copilot_instead or skipped; `agent` is claude, chatgpt or cli;
|
|
33
|
+
* `seconds_on_step` is a number. A handful per signup, never per poll.
|
|
34
|
+
*/
|
|
35
|
+
AGENT_SETUP_ACTION: "agent_setup_action",
|
|
28
36
|
FIRST_RECORD_CREATED: "first_record_created",
|
|
29
37
|
/**
|
|
30
38
|
* The prescribed first play (inbound-led outbound), walked on ANY surface.
|
|
@@ -701,13 +701,40 @@ export type SequenceStopStep = {
|
|
|
701
701
|
* emailsUnsubscribed → { signal: "email_unsubscribed" }
|
|
702
702
|
* aircallDone → out of scope (no Aircall integration)
|
|
703
703
|
* Negate any leaf with present:false (e.g. "no email on file" = { has_column: "email", present: false }).
|
|
704
|
+
*
|
|
705
|
+
* A CONTACT leaf `{ has_contact: "email" | "linkedin" | "phone", present? }` asks
|
|
706
|
+
* whether the step that channel needs could reach the lead, resolved exactly as
|
|
707
|
+
* dispatch resolves it: email and phone through the sequence's mapped column with
|
|
708
|
+
* the built-in fallbacks, LinkedIn through the profile captured at enrollment. It
|
|
709
|
+
* is what "If has email / LinkedIn / phone" in the builder writes, so one campaign
|
|
710
|
+
* can route every lead instead of splitting the audience by data.
|
|
711
|
+
*/
|
|
712
|
+
export declare const SEQUENCE_CONTACT_CHANNELS: readonly ["email", "linkedin", "phone"];
|
|
713
|
+
export type SequenceContactChannel = (typeof SEQUENCE_CONTACT_CHANNELS)[number];
|
|
714
|
+
/** What a lead can be reached by, as dispatch resolves it; a contact leaf reads this. */
|
|
715
|
+
export type SequenceLeadContacts = Record<SequenceContactChannel, boolean>;
|
|
716
|
+
/**
|
|
717
|
+
* Resolve a lead's reachable contacts the way dispatch does: the mapped email or
|
|
718
|
+
* phone column first, then the built-in keys, and LinkedIn from the profile the
|
|
719
|
+
* enrollment captured (a row value alone never makes a LinkedIn step sendable).
|
|
704
720
|
*/
|
|
721
|
+
export declare function sequenceLeadContacts(input: {
|
|
722
|
+
rowValues: Record<string, unknown> | null | undefined;
|
|
723
|
+
settings?: {
|
|
724
|
+
email_column_key?: string | null;
|
|
725
|
+
phone_column_key?: string | null;
|
|
726
|
+
} | null;
|
|
727
|
+
linkedin: boolean;
|
|
728
|
+
}): SequenceLeadContacts;
|
|
705
729
|
export type SequenceSignalCondition = {
|
|
706
730
|
signal: SequenceSignal;
|
|
707
731
|
present?: boolean;
|
|
708
732
|
} | {
|
|
709
733
|
has_column: string;
|
|
710
734
|
present?: boolean;
|
|
735
|
+
} | {
|
|
736
|
+
has_contact: SequenceContactChannel;
|
|
737
|
+
present?: boolean;
|
|
711
738
|
} | {
|
|
712
739
|
all: SequenceSignalCondition[];
|
|
713
740
|
} | {
|
|
@@ -959,9 +986,10 @@ export declare function sequenceTemplateVariables(definition: SequenceDefinition
|
|
|
959
986
|
* `rowValues` is optional and backward-compatible: when omitted (undefined), a data
|
|
960
987
|
* leaf ({ has_column }) evaluates as ABSENT — present:false matches, present:true
|
|
961
988
|
* does not — so an existing signal-only caller is unaffected. Signal leaves ignore
|
|
962
|
-
* rowValues entirely.
|
|
989
|
+
* rowValues entirely. A contact leaf ({ has_contact }) reads `contacts`; without
|
|
990
|
+
* it, the contact counts as absent.
|
|
963
991
|
*/
|
|
964
|
-
export declare function evaluateSequenceCondition(condition: SequenceSignalCondition, firedSignals: Iterable<string>, rowValues?: Record<string, unknown> | null): boolean;
|
|
992
|
+
export declare function evaluateSequenceCondition(condition: SequenceSignalCondition, firedSignals: Iterable<string>, rowValues?: Record<string, unknown> | null, contacts?: SequenceLeadContacts): boolean;
|
|
965
993
|
/** Variant label for an index: 0 → "a" (the base step), 1 → "b", … */
|
|
966
994
|
export declare function sequenceVariantLabel(index: number): string;
|
|
967
995
|
type StepVariantContent = Record<string, string>;
|