@sellable/mcp 0.1.140 → 0.1.142
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 +5 -4
- package/dist/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/server.js +29 -59
- package/dist/tools/auth.js +5 -5
- package/dist/tools/leads.d.ts +47 -5
- package/dist/tools/leads.js +93 -16
- package/dist/tools/navigation.js +6 -1
- package/dist/tools/registry.d.ts +4186 -0
- package/dist/tools/registry.js +59 -0
- package/dist/tools/rubrics.js +15 -14
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +34 -23
- package/skills/create-campaign-v2/SKILL.md +25 -15
- package/skills/create-campaign-v2/SOUL.md +19 -19
- package/skills/create-campaign-v2/core/flow.v2.json +65 -11
- package/skills/create-campaign-v2/references/watch-guide-narration.md +3 -1
- package/skills/find-leads/SKILL.md +3 -3
- package/skills/interview/SKILL.md +53 -0
- package/skills/interview/references/voice-capture-method.md +12 -1
- package/skills/research/config.json +0 -9
package/README.md
CHANGED
|
@@ -21,7 +21,9 @@ There are two public Sellable entrypoints shared across hosts:
|
|
|
21
21
|
- `sellable:create-campaign`
|
|
22
22
|
- `sellable:interview`
|
|
23
23
|
|
|
24
|
-
The create-campaign public wrapper
|
|
24
|
+
The create-campaign public wrapper at
|
|
25
|
+
`mcp/sellable/skills/create-campaign/SKILL.md` handles auth/bootstrap and loads
|
|
26
|
+
the approval-gated workflow from:
|
|
25
27
|
|
|
26
28
|
- `mcp/sellable/skills/create-campaign-v2/SKILL.md`
|
|
27
29
|
|
|
@@ -30,9 +32,7 @@ from:
|
|
|
30
32
|
|
|
31
33
|
- `mcp/sellable/skills/interview/SKILL.md`
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
internal legacy prompt for compatibility only. Do not advertise it as a public
|
|
35
|
-
command.
|
|
35
|
+
Keep `create-campaign-v2` internal. Do not advertise it as a public command.
|
|
36
36
|
|
|
37
37
|
### 1. One-Command Agent Install
|
|
38
38
|
|
|
@@ -68,6 +68,7 @@ startup/auth update check and tells the agent to run
|
|
|
68
68
|
Publish from the repo root with:
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
|
+
npm run validate:sellable-skills
|
|
71
72
|
npm run mcp:publish
|
|
72
73
|
```
|
|
73
74
|
|
package/dist/index-dev.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/server.js
CHANGED
|
@@ -2,35 +2,36 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
4
|
import { getSkillByName, listSkills } from "./skills.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { bootstrapCreateCampaign
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { handleStartCliLogin, handleWaitForCliLogin,
|
|
11
|
-
import {
|
|
12
|
-
import { addToCommentCampaign, addToConnectionCampaign, addToInmailCampaign,
|
|
13
|
-
import { bootstrapEngage, bootstrapEngageMulti,
|
|
14
|
-
import {
|
|
15
|
-
import { copySenderConfigTool,
|
|
16
|
-
import {
|
|
17
|
-
import { bulkEnrichWithProspeo,
|
|
18
|
-
import {
|
|
19
|
-
import { cancelLeadImport, confirmLeadList, getProviderPrompt, importLeads,
|
|
20
|
-
import { fetchCompany, fetchCompanyPosts, fetchLinkedInPosts, fetchLinkedInProfile, fetchPostEngagers, getLinkedInProfile, getUserPosts,
|
|
21
|
-
import { getCampaignNavigationState
|
|
22
|
-
import { addOnDemandLeads, createOnDemandCampaign, createOnDemandTable, initOnDemandSequence,
|
|
23
|
-
import {
|
|
24
|
-
import { completeSenderResearch, getMessagePrompt, getPostFindLeadsScoutRegistry, getSourceScoutRegistry, getSubskillAsset, getSubskillPrompt, listSubskillPrompts,
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
5
|
+
import { getAuthStatus } from "./tools/auth.js";
|
|
6
|
+
import { handleAddColumn, handleCommitBlueprint, } from "./tools/blueprint-commit.js";
|
|
7
|
+
import { bootstrapCreateCampaign } from "./tools/bootstrap.js";
|
|
8
|
+
import { createCampaign, getCampaign, getCampaignMessagesPreview, getCampaigns, pauseCampaign, startCampaign, updateCampaign, updateCampaignBrief, } from "./tools/campaigns.js";
|
|
9
|
+
import { queueCells, updateCell } from "./tools/cells.js";
|
|
10
|
+
import { handleStartCliLogin, handleWaitForCliLogin, } from "./tools/cli-login.js";
|
|
11
|
+
import { getCampaignContext, hydrateCampaignContextFromCampaign, markCampaignContextDirty, } from "./tools/context.js";
|
|
12
|
+
import { addToCommentCampaign, addToConnectionCampaign, addToInmailCampaign, getEngagedPosts, getOrCreateDirectCampaignTable, pauseDirectCampaign, startDirectCampaign, } from "./tools/direct-campaigns.js";
|
|
13
|
+
import { bootstrapEngage, bootstrapEngageMulti, } from "./tools/engage-bootstrap.js";
|
|
14
|
+
import { searchEngagementPosts } from "./tools/engage-discovery.js";
|
|
15
|
+
import { copySenderConfigTool, getEngageMemoryTool, migrateFlatConfigsTool, recordEngageProvenSearchTool, setEngageStyleGuideTool, upsertEngageTrackedPersonTool, } from "./tools/engage-memory.js";
|
|
16
|
+
import { getEngageStateTool, setEngageStateTool, } from "./tools/engage-state.js";
|
|
17
|
+
import { bulkEnrichWithProspeo, enrichWithProspeo, getProspeoCredits, } from "./tools/enrichment.js";
|
|
18
|
+
import { getCampaignFramework } from "./tools/framework.js";
|
|
19
|
+
import { cancelLeadImport, confirmLeadList, getProviderPrompt, importLeads, loadCsvDomains, loadCsvLinkedinLeads, lookupSalesNavFilter, saveDomainFilters, searchApollo, searchProspeo, searchSalesNav, searchSignals, selectPromisingPosts, setHeadlineICPCriteria, } from "./tools/leads.js";
|
|
20
|
+
import { fetchCompany, fetchCompanyPosts, fetchLinkedInPosts, fetchLinkedInProfile, fetchPostEngagers, getLinkedInProfile, getUserPosts, } from "./tools/linkedin.js";
|
|
21
|
+
import { getCampaignNavigationState } from "./tools/navigation.js";
|
|
22
|
+
import { addOnDemandLeads, createOnDemandCampaign, createOnDemandTable, initOnDemandSequence, pauseOnDemandCampaign, startOnDemandCampaign, } from "./tools/one-off.js";
|
|
23
|
+
import { upsertRubric } from "./tools/processing.js";
|
|
24
|
+
import { completeSenderResearch, getMessagePrompt, getPostFindLeadsScoutRegistry, getSourceScoutRegistry, getSubskillAsset, getSubskillPrompt, listSubskillPrompts, searchSubskillPrompts, } from "./tools/prompts.js";
|
|
25
|
+
import { waitForCampaignTableReady, waitForLeadListReady, } from "./tools/readiness.js";
|
|
26
|
+
import { allTools } from "./tools/registry.js";
|
|
27
|
+
import { getRows, getTableRows, getTableRowsMinimal } from "./tools/rows.js";
|
|
28
|
+
import { addRubricItem, checkRubric, deleteRubricItem, draftRubrics, saveRubrics, selectNecessaryRubrics, updateRubricItem, waitForRubricResults, } from "./tools/rubrics.js";
|
|
29
|
+
import { getSender, listSenders } from "./tools/senders.js";
|
|
30
|
+
import { attachRecommendedSequence, attachSequence, createWorkflowTable, } from "./tools/sequencer.js";
|
|
31
|
+
import { listTables } from "./tools/tables.js";
|
|
32
|
+
import { handleVerifyTableRow } from "./tools/verify-row.js";
|
|
32
33
|
import { sanitizeWatchUrlsForMcpResult } from "./tools/watch-url-security.js";
|
|
33
|
-
import { addTeammate, createWorkspace, getActiveWorkspace, listWorkspaces, setActiveWorkspace,
|
|
34
|
+
import { addTeammate, createWorkspace, getActiveWorkspace, listWorkspaces, setActiveWorkspace, } from "./tools/workspaces.js";
|
|
34
35
|
import { checkForUpdates, logUpdateNotice } from "./update-check.js";
|
|
35
36
|
const server = new Server({
|
|
36
37
|
name: "sellable-mcp",
|
|
@@ -41,37 +42,6 @@ const server = new Server({
|
|
|
41
42
|
prompts: {},
|
|
42
43
|
},
|
|
43
44
|
});
|
|
44
|
-
const allTools = [
|
|
45
|
-
...campaignToolDefinitions,
|
|
46
|
-
...authToolDefinitions,
|
|
47
|
-
startCliLoginToolDef,
|
|
48
|
-
waitForCliLoginToolDef,
|
|
49
|
-
...bootstrapToolDefinitions,
|
|
50
|
-
...engageBootstrapToolDefinitions,
|
|
51
|
-
...engageDiscoveryToolDefinitions,
|
|
52
|
-
...workspaceToolDefinitions,
|
|
53
|
-
...frameworkToolDefinitions,
|
|
54
|
-
...contextToolDefinitions,
|
|
55
|
-
...navigationToolDefinitions,
|
|
56
|
-
...leadToolDefinitions,
|
|
57
|
-
...enrichmentToolDefinitions,
|
|
58
|
-
...processingToolDefinitions,
|
|
59
|
-
...rubricToolDefinitions,
|
|
60
|
-
...readinessToolDefinitions,
|
|
61
|
-
...rowToolDefinitions,
|
|
62
|
-
...cellToolDefinitions,
|
|
63
|
-
...promptToolDefinitions,
|
|
64
|
-
...linkedinToolDefinitions,
|
|
65
|
-
...onDemandToolDefinitions,
|
|
66
|
-
...directCampaignToolDefinitions,
|
|
67
|
-
...senderToolDefinitions,
|
|
68
|
-
...engageStateToolDefinitions,
|
|
69
|
-
...engageMemoryToolDefinitions,
|
|
70
|
-
...sequencerToolDefinitions,
|
|
71
|
-
...tableToolDefinitions,
|
|
72
|
-
...blueprintCommitToolDefinitions,
|
|
73
|
-
...verifyRowToolDefinitions,
|
|
74
|
-
];
|
|
75
45
|
function parseOptionalNumber(value) {
|
|
76
46
|
if (typeof value === "number" && Number.isFinite(value))
|
|
77
47
|
return value;
|
package/dist/tools/auth.js
CHANGED
|
@@ -22,7 +22,7 @@ function appendUpdateNotice(notice, update) {
|
|
|
22
22
|
export const authToolDefinitions = [
|
|
23
23
|
{
|
|
24
24
|
name: "get_auth_status",
|
|
25
|
-
description: "Verify Sellable authentication early. Call this before any campaign work. Also performs a cached Sellable package update check. Returns ok=false with clear guidance when the token is missing, revoked, or the active workspace is not set.
|
|
25
|
+
description: "Verify Sellable authentication early. Call this before any campaign work. Also performs a cached Sellable package update check. Returns ok=false with clear guidance when the token is missing, revoked, or the active workspace is not set. When auth succeeds, use _userNotice when workspace context is useful; it is written in customer-facing product language and may include update guidance. CRITICAL: When auth FAILS with a missing/invalid token (error.type is 'config' or 'auth'), the response includes an `agentInstruction` field with the verbatim FTUX magic-link signup flow. Follow agentInstruction VERBATIM. Do NOT show the user error.guidance — that's a fallback for non-interactive contexts only.",
|
|
26
26
|
inputSchema: {
|
|
27
27
|
type: "object",
|
|
28
28
|
properties: {},
|
|
@@ -92,8 +92,8 @@ export async function getAuthStatus() {
|
|
|
92
92
|
const envLabel = config.activeEnvName || null;
|
|
93
93
|
const wsLabel = workspaceName || activeWorkspaceId;
|
|
94
94
|
const notice = envLabel
|
|
95
|
-
? `
|
|
96
|
-
: `
|
|
95
|
+
? `I’m building this in ${wsLabel} (${envLabel}).`
|
|
96
|
+
: `I’m building this in ${wsLabel}.`;
|
|
97
97
|
return {
|
|
98
98
|
ok: true,
|
|
99
99
|
configPath,
|
|
@@ -138,8 +138,8 @@ export async function getAuthStatus() {
|
|
|
138
138
|
"5) Call `mcp__sellable__wait_for_cli_login({ sessionId })` using the sessionId returned by start_cli_login. " +
|
|
139
139
|
"6) If the result is `error.type === 'tool_timeout_guard'`, IMMEDIATELY re-call wait_for_cli_login with the SAME sessionId — do not narrate, do not call start_cli_login again. Loop until you get a different result. " +
|
|
140
140
|
"7) On `ok: true`, the user is signed in and `~/.sellable/config.json` has been written. Branch on `isReturningUser`: " +
|
|
141
|
-
"if true, say `You're in — {activeWorkspaceName} workspace, ready to roll.\\n\\nNow — paste the LinkedIn profile
|
|
142
|
-
"if false, say `You're set up — your {activeWorkspaceName} workspace is ready.\\n\\nNow — paste the LinkedIn profile
|
|
141
|
+
"if true, say `You're in — {activeWorkspaceName} workspace, ready to roll.\\n\\nNow — paste the LinkedIn profile or company website for the client/company this campaign is for. I’ll use that to resolve the campaign identity before we pick the target, offer, proof, and lead source.\\n\\ne.g. https://example.com or https://www.linkedin.com/in/client-handle`; " +
|
|
142
|
+
"if false, say `You're set up — your {activeWorkspaceName} workspace is ready.\\n\\nNow — paste the LinkedIn profile or company website for the client/company this campaign is for. I’ll use that to resolve the campaign identity before we pick the target, offer, proof, and lead source.\\n\\ne.g. https://example.com or https://www.linkedin.com/in/client-handle`";
|
|
143
143
|
if (error instanceof SellableApiError && error.isAuthError) {
|
|
144
144
|
return {
|
|
145
145
|
...base,
|
package/dist/tools/leads.d.ts
CHANGED
|
@@ -128,8 +128,6 @@ export type ImportLeadsInput = {
|
|
|
128
128
|
maxPostsToScrape?: number;
|
|
129
129
|
rubricGuidelines?: string[];
|
|
130
130
|
};
|
|
131
|
-
type ImportLeadProvider = NonNullable<ImportLeadsInput["provider"]>;
|
|
132
|
-
type Phase126ImportProvider = Exclude<ImportLeadProvider, "apollo">;
|
|
133
131
|
export type CancelLeadImportInput = {
|
|
134
132
|
campaignOfferId: string;
|
|
135
133
|
tableId: string;
|
|
@@ -2349,6 +2347,45 @@ export declare function searchSignals(input: SignalSearchInput): Promise<{
|
|
|
2349
2347
|
notes: string[];
|
|
2350
2348
|
}>;
|
|
2351
2349
|
export declare function importLeads(input: ImportLeadsInput): Promise<{
|
|
2350
|
+
provider: string;
|
|
2351
|
+
leadListId: string;
|
|
2352
|
+
existingLeadListId: string;
|
|
2353
|
+
reusedExistingSourceList: boolean;
|
|
2354
|
+
message: string;
|
|
2355
|
+
suggestedToolCalls: ({
|
|
2356
|
+
tool: string;
|
|
2357
|
+
args: {
|
|
2358
|
+
campaignOfferId: string;
|
|
2359
|
+
leadListId: string;
|
|
2360
|
+
provider: string;
|
|
2361
|
+
sourceLeadListId?: undefined;
|
|
2362
|
+
confirmed?: undefined;
|
|
2363
|
+
};
|
|
2364
|
+
} | {
|
|
2365
|
+
tool: string;
|
|
2366
|
+
args: {
|
|
2367
|
+
campaignOfferId: string;
|
|
2368
|
+
sourceLeadListId: string;
|
|
2369
|
+
confirmed: boolean;
|
|
2370
|
+
leadListId?: undefined;
|
|
2371
|
+
provider?: undefined;
|
|
2372
|
+
};
|
|
2373
|
+
})[];
|
|
2374
|
+
error?: undefined;
|
|
2375
|
+
needsModeSelection?: undefined;
|
|
2376
|
+
modeOptions?: undefined;
|
|
2377
|
+
jobId?: undefined;
|
|
2378
|
+
estimatedEngagers?: undefined;
|
|
2379
|
+
selectedPostCount?: undefined;
|
|
2380
|
+
availableSelectedPostCount?: undefined;
|
|
2381
|
+
targetEngagerCount?: undefined;
|
|
2382
|
+
maxPostsToScrape?: undefined;
|
|
2383
|
+
limitedSelectedPosts?: undefined;
|
|
2384
|
+
targetLeadCount?: undefined;
|
|
2385
|
+
existingCount?: undefined;
|
|
2386
|
+
createdLeadList?: undefined;
|
|
2387
|
+
jobResult?: undefined;
|
|
2388
|
+
} | {
|
|
2352
2389
|
error: string;
|
|
2353
2390
|
message: string;
|
|
2354
2391
|
existingLeadListId: string;
|
|
@@ -2373,6 +2410,7 @@ export declare function importLeads(input: ImportLeadsInput): Promise<{
|
|
|
2373
2410
|
}[];
|
|
2374
2411
|
provider?: undefined;
|
|
2375
2412
|
leadListId?: undefined;
|
|
2413
|
+
reusedExistingSourceList?: undefined;
|
|
2376
2414
|
needsModeSelection?: undefined;
|
|
2377
2415
|
modeOptions?: undefined;
|
|
2378
2416
|
jobId?: undefined;
|
|
@@ -2387,7 +2425,7 @@ export declare function importLeads(input: ImportLeadsInput): Promise<{
|
|
|
2387
2425
|
createdLeadList?: undefined;
|
|
2388
2426
|
jobResult?: undefined;
|
|
2389
2427
|
} | {
|
|
2390
|
-
provider:
|
|
2428
|
+
provider: "signal-discovery" | "sales-nav" | "prospeo";
|
|
2391
2429
|
leadListId: string;
|
|
2392
2430
|
existingLeadListId: string;
|
|
2393
2431
|
needsModeSelection: boolean;
|
|
@@ -2412,6 +2450,7 @@ export declare function importLeads(input: ImportLeadsInput): Promise<{
|
|
|
2412
2450
|
rubricGuidelines?: string[];
|
|
2413
2451
|
};
|
|
2414
2452
|
}[];
|
|
2453
|
+
reusedExistingSourceList?: undefined;
|
|
2415
2454
|
error?: undefined;
|
|
2416
2455
|
jobId?: undefined;
|
|
2417
2456
|
estimatedEngagers?: undefined;
|
|
@@ -2436,9 +2475,10 @@ export declare function importLeads(input: ImportLeadsInput): Promise<{
|
|
|
2436
2475
|
limitedSelectedPosts: boolean;
|
|
2437
2476
|
targetLeadCount: number | null;
|
|
2438
2477
|
message: string;
|
|
2439
|
-
error?: undefined;
|
|
2440
2478
|
existingLeadListId?: undefined;
|
|
2479
|
+
reusedExistingSourceList?: undefined;
|
|
2441
2480
|
suggestedToolCalls?: undefined;
|
|
2481
|
+
error?: undefined;
|
|
2442
2482
|
needsModeSelection?: undefined;
|
|
2443
2483
|
modeOptions?: undefined;
|
|
2444
2484
|
existingCount?: undefined;
|
|
@@ -2471,6 +2511,7 @@ export declare function importLeads(input: ImportLeadsInput): Promise<{
|
|
|
2471
2511
|
rubricGuidelines?: string[];
|
|
2472
2512
|
};
|
|
2473
2513
|
}[];
|
|
2514
|
+
reusedExistingSourceList?: undefined;
|
|
2474
2515
|
error?: undefined;
|
|
2475
2516
|
jobId?: undefined;
|
|
2476
2517
|
estimatedEngagers?: undefined;
|
|
@@ -2490,9 +2531,10 @@ export declare function importLeads(input: ImportLeadsInput): Promise<{
|
|
|
2490
2531
|
jobId: string | undefined;
|
|
2491
2532
|
targetLeadCount: number | null;
|
|
2492
2533
|
message: string;
|
|
2493
|
-
error?: undefined;
|
|
2494
2534
|
existingLeadListId?: undefined;
|
|
2535
|
+
reusedExistingSourceList?: undefined;
|
|
2495
2536
|
suggestedToolCalls?: undefined;
|
|
2537
|
+
error?: undefined;
|
|
2496
2538
|
needsModeSelection?: undefined;
|
|
2497
2539
|
modeOptions?: undefined;
|
|
2498
2540
|
estimatedEngagers?: undefined;
|
package/dist/tools/leads.js
CHANGED
|
@@ -504,6 +504,39 @@ function buildFilterChoiceWatchNarration({ sourceLeadCount, reviewRowCount, }) {
|
|
|
504
504
|
},
|
|
505
505
|
};
|
|
506
506
|
}
|
|
507
|
+
function buildSignalDiscoverySearchWatchNarration() {
|
|
508
|
+
return {
|
|
509
|
+
stage: "find-leads",
|
|
510
|
+
headline: "Searching Signal Discovery",
|
|
511
|
+
visibleState: "The browser is showing Signal Discovery while LinkedIn post searches run.",
|
|
512
|
+
agentIntent: "Codex is searching the approved post themes and will ask before scraping selected engagers into a source list.",
|
|
513
|
+
nextAction: "Review selected posts before scraping",
|
|
514
|
+
safety: "Search only stores post candidates; it does not import leads, enrich, generate messages, create a sequence, or send.",
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function buildSignalDiscoveryResultsWatchNarration(postsReturned) {
|
|
518
|
+
const postCopy = typeof postsReturned === "number"
|
|
519
|
+
? `${postsReturned.toLocaleString("en-US")} post${postsReturned === 1 ? "" : "s"} matched the approved search themes and are visible in Signal Discovery.`
|
|
520
|
+
: "Signal Discovery results are visible for review.";
|
|
521
|
+
return {
|
|
522
|
+
stage: "find-leads",
|
|
523
|
+
headline: "Review Signal Discovery posts",
|
|
524
|
+
visibleState: postCopy,
|
|
525
|
+
agentIntent: "Codex is promoting the strongest posts, then it will ask before scraping the selected engager pool.",
|
|
526
|
+
nextAction: "Approve selected posts before scraping",
|
|
527
|
+
safety: "Post results do not import leads, enrich, generate messages, create a sequence, or send.",
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function buildSelectedPostApprovalWatchNarration(selectedPostCount) {
|
|
531
|
+
return {
|
|
532
|
+
stage: "find-leads",
|
|
533
|
+
headline: "Approve selected-post scrape",
|
|
534
|
+
visibleState: `${selectedPostCount.toLocaleString("en-US")} LinkedIn post${selectedPostCount === 1 ? "" : "s"} selected in Signal Discovery.`,
|
|
535
|
+
agentIntent: "Codex is asking before scraping this selected engager pool into a source list and cloning only the bounded review batch into the campaign.",
|
|
536
|
+
nextAction: `Scrape ${selectedPostCount.toLocaleString("en-US")} selected post${selectedPostCount === 1 ? "" : "s"} for the bounded review batch`,
|
|
537
|
+
safety: "Post selection does not import leads, enrich, generate messages, create a sequence, or send.",
|
|
538
|
+
};
|
|
539
|
+
}
|
|
507
540
|
function normalizeImportProvider(provider) {
|
|
508
541
|
if (provider === "apollo-ai" || provider === "apollo")
|
|
509
542
|
return "apollo";
|
|
@@ -1951,9 +1984,20 @@ export async function searchSignals(input) {
|
|
|
1951
1984
|
});
|
|
1952
1985
|
const api = getApi();
|
|
1953
1986
|
const { campaignOfferId, headlineICPCriteria, rubricGuidelines } = input;
|
|
1987
|
+
const searchCurrentStep = input.currentStep === null
|
|
1988
|
+
? undefined
|
|
1989
|
+
: (input.currentStep ?? "signal-discovery");
|
|
1954
1990
|
const effectiveHeadlineICPCriteria = headlineICPCriteria && headlineICPCriteria.length > 0
|
|
1955
1991
|
? headlineICPCriteria
|
|
1956
1992
|
: rubricGuidelines;
|
|
1993
|
+
if (campaignOfferId) {
|
|
1994
|
+
await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
|
|
1995
|
+
leadSourceType: "new",
|
|
1996
|
+
leadSourceProvider: "signal-discovery",
|
|
1997
|
+
...(searchCurrentStep ? { currentStep: searchCurrentStep } : {}),
|
|
1998
|
+
watchNarration: buildSignalDiscoverySearchWatchNarration(),
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
1957
2001
|
// Persist criteria early so user take-over has filters ready.
|
|
1958
2002
|
if (campaignOfferId && effectiveHeadlineICPCriteria?.length) {
|
|
1959
2003
|
await api.patch(`/api/v3/campaigns/${campaignOfferId}/signal-discovery/posts`, {
|
|
@@ -1964,7 +2008,14 @@ export async function searchSignals(input) {
|
|
|
1964
2008
|
});
|
|
1965
2009
|
}
|
|
1966
2010
|
const response = await api.post(`/api/v1/signal-discovery/search-signals`, input);
|
|
1967
|
-
|
|
2011
|
+
const summary = summarizeSignalSearchResponse(response);
|
|
2012
|
+
if (campaignOfferId) {
|
|
2013
|
+
await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
|
|
2014
|
+
...(searchCurrentStep ? { currentStep: searchCurrentStep } : {}),
|
|
2015
|
+
watchNarration: buildSignalDiscoveryResultsWatchNarration(summary.postsReturned),
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
return summary;
|
|
1968
2019
|
}
|
|
1969
2020
|
export async function importLeads(input) {
|
|
1970
2021
|
const api = getApi();
|
|
@@ -2009,6 +2060,35 @@ export async function importLeads(input) {
|
|
|
2009
2060
|
const shouldSetCurrentStep = typeof effectiveCurrentStep === "string" && effectiveCurrentStep.length > 0;
|
|
2010
2061
|
const normalizedMode = mode === "add" || mode === "replace" ? mode : undefined;
|
|
2011
2062
|
assertPhase126ImportProvider(provider, campaignOfferId);
|
|
2063
|
+
if (provider === "signal-discovery" &&
|
|
2064
|
+
!sourceLeadListId &&
|
|
2065
|
+
campaignSelectedLeadListId) {
|
|
2066
|
+
return {
|
|
2067
|
+
provider: "signal-discovery",
|
|
2068
|
+
leadListId: campaignSelectedLeadListId,
|
|
2069
|
+
existingLeadListId: campaignSelectedLeadListId,
|
|
2070
|
+
reusedExistingSourceList: true,
|
|
2071
|
+
message: "A Signal Discovery source list already exists for the approved selected-post scrape. Reuse it and continue with wait_for_lead_list_ready or confirm_lead_list; do not ask add/replace and do not rescrape unless the user approves a different selected-post scrape.",
|
|
2072
|
+
suggestedToolCalls: [
|
|
2073
|
+
{
|
|
2074
|
+
tool: "wait_for_lead_list_ready",
|
|
2075
|
+
args: {
|
|
2076
|
+
campaignOfferId,
|
|
2077
|
+
leadListId: campaignSelectedLeadListId,
|
|
2078
|
+
provider: "signal-discovery",
|
|
2079
|
+
},
|
|
2080
|
+
},
|
|
2081
|
+
{
|
|
2082
|
+
tool: "confirm_lead_list",
|
|
2083
|
+
args: {
|
|
2084
|
+
campaignOfferId,
|
|
2085
|
+
sourceLeadListId: campaignSelectedLeadListId,
|
|
2086
|
+
confirmed: true,
|
|
2087
|
+
},
|
|
2088
|
+
},
|
|
2089
|
+
],
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2012
2092
|
// If no sourceLeadListId but campaign has one selected, handle mode selection
|
|
2013
2093
|
if (!sourceLeadListId && campaignSelectedLeadListId) {
|
|
2014
2094
|
// If mode is provided but sourceLeadListId is missing, remind LLM to pass it
|
|
@@ -2614,7 +2694,7 @@ export function getProviderPrompt(input) {
|
|
|
2614
2694
|
}
|
|
2615
2695
|
export async function selectPromisingPosts(input) {
|
|
2616
2696
|
const api = getApi();
|
|
2617
|
-
const { campaignOfferId, selections, headlineICPCriteria,
|
|
2697
|
+
const { campaignOfferId, selections, headlineICPCriteria, selectionMode, mode, } = input;
|
|
2618
2698
|
const effectiveMode = selectionMode ?? mode ?? "add";
|
|
2619
2699
|
// Update post selections via API
|
|
2620
2700
|
const postIds = selections.map((s) => s.postId);
|
|
@@ -2642,18 +2722,16 @@ export async function selectPromisingPosts(input) {
|
|
|
2642
2722
|
message: "No Signal Discovery posts were selected for this campaign. Do not import yet. Re-run a campaign-scoped search_signals call, use recommendedPostIds from the current campaign search state, or ask the user to promote posts in the UI before retrying select_promising_posts.",
|
|
2643
2723
|
};
|
|
2644
2724
|
}
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
});
|
|
2650
|
-
}
|
|
2725
|
+
await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
|
|
2726
|
+
currentStep: "signal-discovery",
|
|
2727
|
+
watchNarration: buildSelectedPostApprovalWatchNarration(selectionResult.selectedCount),
|
|
2728
|
+
});
|
|
2651
2729
|
return {
|
|
2652
2730
|
success: true,
|
|
2653
2731
|
selectedCount: selectionResult.selectedCount,
|
|
2654
2732
|
unselectedCount: selectionResult.unselectedCount,
|
|
2655
2733
|
criteriaCount: selectionResult.criteriaCount,
|
|
2656
|
-
message: `Selected ${selectionResult.selectedCount} posts with ${selectionResult.criteriaCount} ICP criteria (persisted). Criteria are already saved to the campaign.
|
|
2734
|
+
message: `Selected ${selectionResult.selectedCount} posts with ${selectionResult.criteriaCount} ICP criteria (persisted). Criteria are already saved to the campaign. Ask the user to approve scraping these selected posts before calling import_leads; the earlier source-plan approval only authorized search/scouting.`,
|
|
2657
2735
|
};
|
|
2658
2736
|
}
|
|
2659
2737
|
export async function setHeadlineICPCriteria(input) {
|
|
@@ -2666,15 +2744,14 @@ export async function setHeadlineICPCriteria(input) {
|
|
|
2666
2744
|
headlineICPCriteria,
|
|
2667
2745
|
rubricGuidelines: headlineICPCriteria,
|
|
2668
2746
|
});
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
}
|
|
2747
|
+
const criteriaCurrentStep = currentStep === null ? undefined : (currentStep ?? "signal-discovery");
|
|
2748
|
+
await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
|
|
2749
|
+
...(criteriaCurrentStep ? { currentStep: criteriaCurrentStep } : {}),
|
|
2750
|
+
watchNarration: buildSignalDiscoveryResultsWatchNarration(),
|
|
2751
|
+
});
|
|
2675
2752
|
return {
|
|
2676
2753
|
success: true,
|
|
2677
2754
|
criteriaCount: headlineICPCriteria.length,
|
|
2678
|
-
message: `Prepared ${headlineICPCriteria.length} ICP criteria (persisted).
|
|
2755
|
+
message: `Prepared ${headlineICPCriteria.length} ICP criteria (persisted). Select posts and ask for selected-post scrape approval before calling import_leads with provider: "signal-discovery".`,
|
|
2679
2756
|
};
|
|
2680
2757
|
}
|
package/dist/tools/navigation.js
CHANGED
|
@@ -537,6 +537,9 @@ export function computeCampaignNavigationStateFromCampaign(campaign, options) {
|
|
|
537
537
|
computedStep = isRunningCampaign(campaign) ? "running" : "send";
|
|
538
538
|
}
|
|
539
539
|
const stepForHeadless = blockedAt ?? computedStep;
|
|
540
|
+
const waitingOnMessageTemplateAfterFilters = blockedAt === "messages" &&
|
|
541
|
+
campaign.enableICPFilters === true &&
|
|
542
|
+
hasActiveRubrics(campaign);
|
|
540
543
|
const expectedHeadlessStep = stepForHeadless === "campaign-created"
|
|
541
544
|
? "create-offer"
|
|
542
545
|
: stepForHeadless === "provider-search"
|
|
@@ -544,7 +547,9 @@ export function computeCampaignNavigationStateFromCampaign(campaign, options) {
|
|
|
544
547
|
: stepForHeadless === "filter-rules"
|
|
545
548
|
? "create-icp-rubric"
|
|
546
549
|
: stepForHeadless === "messages"
|
|
547
|
-
?
|
|
550
|
+
? waitingOnMessageTemplateAfterFilters
|
|
551
|
+
? "apply-icp-rubric"
|
|
552
|
+
: "messages"
|
|
548
553
|
: stepForHeadless === "settings"
|
|
549
554
|
? "settings"
|
|
550
555
|
: stepForHeadless === "sequence"
|