@sellable/mcp 0.1.134 → 0.1.136
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/agents/post-find-leads-message-scout.md +31 -1
- package/agents/registry.json +6 -8
- package/agents/source-scout-linkedin-engagement.md +6 -3
- package/agents/source-scout-prospeo-contact.md +4 -2
- package/agents/source-scout-sales-nav.md +4 -2
- package/dist/tools/campaigns.d.ts +6 -0
- package/dist/tools/campaigns.js +16 -11
- package/dist/tools/leads.d.ts +14 -0
- package/dist/tools/leads.js +160 -15
- package/dist/tools/processing.js +1 -1
- package/dist/tools/prompts.d.ts +10 -0
- package/dist/tools/prompts.js +46 -2
- package/dist/tools/sequencer.d.ts +1 -0
- package/dist/tools/sequencer.js +1 -1
- package/package.json +1 -1
- package/skills/create-campaign-v2/SKILL.md +41 -10
- package/skills/create-campaign-v2/core/flow.v2.json +148 -462
- package/skills/create-campaign-v2/references/watch-guide-narration.md +12 -10
- package/skills/create-campaign-v2/references/watch-link-handoff.md +3 -1
- package/skills/find-leads/SKILL.md +5 -2
- package/skills/generate-messages/SKILL.md +25 -0
- package/skills/providers/prospeo.md +5 -4
- package/skills/providers/sales-nav.md +1 -2
- package/skills/providers/signal-discovery.md +1 -1
|
@@ -11,11 +11,14 @@ campaign state. The main thread owns approval and campaign writes.
|
|
|
11
11
|
Use the live campaign inputs supplied by the parent thread:
|
|
12
12
|
|
|
13
13
|
- `campaignId`
|
|
14
|
+
- campaign revision or `campaignUpdatedAt`
|
|
14
15
|
- `campaignBrief` / campaign brief content model
|
|
15
16
|
- selected source decision and provider state
|
|
16
17
|
- `selectedLeadListId` or selected source list context
|
|
17
18
|
- `workflowTableId`
|
|
18
|
-
- imported review-batch rows from that selected list
|
|
19
|
+
- imported review-batch rows from that selected list, including row IDs and a
|
|
20
|
+
review-batch row hash when available
|
|
21
|
+
- filter basis at branch start: `pending`, `use-filters`, or `skip-filters`
|
|
19
22
|
- any already-saved fit/rubric result summaries supplied by the parent
|
|
20
23
|
|
|
21
24
|
Do not require or hunt for `brief.md`, `lead-review.md`, or `lead-sample.json`.
|
|
@@ -23,6 +26,13 @@ Those files are optional debug context only when the parent explicitly provides
|
|
|
23
26
|
them. Never inspect the product database directly, never run `psql`, and never
|
|
24
27
|
read stale local markdown files to reconstruct campaign state.
|
|
25
28
|
|
|
29
|
+
All live reads must come from scoped MCP/product tools by campaign and
|
|
30
|
+
workspace, such as `get_campaign`, `get_campaign_context`, and
|
|
31
|
+
`get_rows_minimal({ tableId: workflowTableId })`, or from equivalent parent
|
|
32
|
+
thread payloads. Reject the task as `blocked` if the campaign id, workspace,
|
|
33
|
+
`selectedLeadListId`, `workflowTableId`, or review-batch row ids do not match
|
|
34
|
+
the branch input.
|
|
35
|
+
|
|
26
36
|
## Required First Steps
|
|
27
37
|
|
|
28
38
|
1. Load the full long-form generate-messages prompt:
|
|
@@ -45,11 +55,31 @@ Return the following to the parent thread:
|
|
|
45
55
|
- one rendered good-fill sample for a plausible passing review-batch row
|
|
46
56
|
- one omit/fallback sample when the row signal is not safe
|
|
47
57
|
- pass/fail notes against the generate-messages quality gates
|
|
58
|
+
- compact runtime status: `ready`, `blocked`, `retry-needed`, or `stale`
|
|
59
|
+
- basis token containing campaign revision/updatedAt, brief hash,
|
|
60
|
+
`selectedLeadListId`, `workflowTableId`, review-batch row ids/hash, filter
|
|
61
|
+
choice, and rubric/filter basis when present
|
|
62
|
+
- output timestamp/hash and any retry/error detail
|
|
48
63
|
|
|
49
64
|
Write `message-validation.md`, `message-prep.md`, or
|
|
50
65
|
`message-candidate-drafts.md` only when the parent explicitly asks for debug
|
|
51
66
|
artifacts. Normal live campaign runs can return the same content directly.
|
|
52
67
|
|
|
68
|
+
When reporting branch runtime proof, use this shape under
|
|
69
|
+
`watchNarration.workerDetails.messageDraftBuilder`:
|
|
70
|
+
|
|
71
|
+
- `statusSource`: `branch` or `parent-thread-fallback`
|
|
72
|
+
- `status`: `branch-running`, `fallback-active`, `spawn-failed`,
|
|
73
|
+
`fallback-superseded`, `branch-superseded`, `ready`, `blocked`,
|
|
74
|
+
`retry-needed`, or `stale`
|
|
75
|
+
- `runId` or `fallbackId`
|
|
76
|
+
- `startedAt` and `updatedAt`
|
|
77
|
+
- `basisToken` and `basis`
|
|
78
|
+
- optional `compactOutputRef`, `compactOutput`, and `error`
|
|
79
|
+
|
|
80
|
+
Do not tell the UI to show Message Draft Builder as running unless this proof
|
|
81
|
+
exists and points at the current non-empty bounded review batch.
|
|
82
|
+
|
|
53
83
|
## Hard Rules
|
|
54
84
|
|
|
55
85
|
- Do not call product Generate Message cells. This worker drafts the template
|
package/agents/registry.json
CHANGED
|
@@ -225,10 +225,10 @@
|
|
|
225
225
|
],
|
|
226
226
|
"ownership": "proof inventory, token strategy, angle drafting, skeptical-prospect review, and selected winner only",
|
|
227
227
|
"codex": {
|
|
228
|
-
"description": "Message Draft Builder for campaign-backed template proposals
|
|
228
|
+
"description": "Message Draft Builder for campaign-backed template proposals after confirm_lead_list imports a non-empty bounded review batch.",
|
|
229
229
|
"model": "gpt-5.5",
|
|
230
230
|
"modelReasoningEffort": "high",
|
|
231
|
-
"sandboxMode": "
|
|
231
|
+
"sandboxMode": "read-only",
|
|
232
232
|
"nicknameCandidates": [
|
|
233
233
|
"Message Draft Builder",
|
|
234
234
|
"Draft Builder",
|
|
@@ -236,19 +236,17 @@
|
|
|
236
236
|
]
|
|
237
237
|
},
|
|
238
238
|
"claude": {
|
|
239
|
-
"description": "Use proactively as Message Draft Builder after
|
|
239
|
+
"description": "Use proactively as Message Draft Builder after confirm_lead_list imports a non-empty bounded review batch; load the full generate-messages prompt and draft only from scoped campaign/tool state.",
|
|
240
240
|
"model": "inherit",
|
|
241
241
|
"background": true,
|
|
242
242
|
"maxTurns": 10,
|
|
243
243
|
"color": "magenta",
|
|
244
244
|
"tools": [
|
|
245
245
|
"Read",
|
|
246
|
-
"Write",
|
|
247
|
-
"Edit",
|
|
248
|
-
"Grep",
|
|
249
|
-
"Glob",
|
|
250
246
|
"mcp__sellable__get_subskill_prompt",
|
|
251
|
-
"
|
|
247
|
+
"mcp__sellable__get_campaign",
|
|
248
|
+
"mcp__sellable__get_campaign_context",
|
|
249
|
+
"mcp__sellable__get_rows_minimal"
|
|
252
250
|
]
|
|
253
251
|
}
|
|
254
252
|
}
|
|
@@ -8,9 +8,10 @@ Required first step:
|
|
|
8
8
|
draft `campaignOfferId`, call `get_provider_prompt({ provider:
|
|
9
9
|
"signal-discovery", campaignOfferId, confirmed: true })` and include that same
|
|
10
10
|
`campaignOfferId` plus `currentStep: "signal-discovery"` in `search_signals`
|
|
11
|
-
so the
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
so the owning search route can show the source lane with current find-leads
|
|
12
|
+
narration and user options. Treat that as a campaign-attached persisted
|
|
13
|
+
search; do not run a post-mint search without the campaign ID. If no campaign
|
|
14
|
+
ID is supplied, run campaignless preview mode.
|
|
14
15
|
|
|
15
16
|
Use the inherited Sellable MCP tools when available:
|
|
16
17
|
|
|
@@ -44,6 +45,8 @@ sales Claude`; do not treat broad anchor-only lanes like `Claude Code`, `MCP`,
|
|
|
44
45
|
campaignOfferId, selectionMode: "replace", selections, headlineICPCriteria,
|
|
45
46
|
currentStep: "signal-discovery" })` before sampling so the watched Signal
|
|
46
47
|
Discovery table shows the promoted posts and the exact posts being tested.
|
|
48
|
+
Do not move the campaign to `confirm-lead-list`; `import_leads` owns that
|
|
49
|
+
visible transition after source approval.
|
|
47
50
|
6. Fetch or sample engagers for promoted posts and score rough ICP fit from
|
|
48
51
|
visible headline/display-name cues only. Do not enrich people during
|
|
49
52
|
viability estimation.
|
|
@@ -8,10 +8,12 @@ Required first step:
|
|
|
8
8
|
draft `campaignOfferId`, call `get_provider_prompt({ provider: "prospeo",
|
|
9
9
|
campaignOfferId, confirmed: true })` and include that same `campaignOfferId`
|
|
10
10
|
plus `currentStep: "prospeo"` in `search_prospeo` so the user can watch source
|
|
11
|
-
work in the campaign UI
|
|
12
|
-
preview mode. Treat post-mint
|
|
11
|
+
work in the campaign UI with source-lane narration owned by the search route.
|
|
12
|
+
If no campaign ID is supplied, run campaignless preview mode. Treat post-mint
|
|
13
13
|
searches with `campaignOfferId` as campaign-attached persisted search tabs;
|
|
14
14
|
do not run a live campaign search without the campaign ID.
|
|
15
|
+
Do not move the campaign to `confirm-lead-list`; `import_leads` owns that
|
|
16
|
+
visible transition after source approval.
|
|
15
17
|
|
|
16
18
|
Use the inherited Sellable MCP tools when available:
|
|
17
19
|
|
|
@@ -8,10 +8,12 @@ Required first step:
|
|
|
8
8
|
draft `campaignOfferId`, call `get_provider_prompt({ provider: "sales-nav",
|
|
9
9
|
campaignOfferId, confirmed: true })` and include that same `campaignOfferId` in
|
|
10
10
|
`search_sales_nav` with `currentStep: "sales-nav"` so the user can watch
|
|
11
|
-
source work in the campaign UI
|
|
12
|
-
campaignless preview mode. Treat post-mint
|
|
11
|
+
source work in the campaign UI with source-lane narration owned by the search
|
|
12
|
+
route. If no campaign ID is supplied, run campaignless preview mode. Treat post-mint
|
|
13
13
|
searches with `campaignOfferId` as campaign-attached persisted search tabs;
|
|
14
14
|
do not run a live campaign search without the campaign ID.
|
|
15
|
+
Do not move the campaign to `confirm-lead-list`; `import_leads` owns that
|
|
16
|
+
visible transition after source approval.
|
|
15
17
|
|
|
16
18
|
Use the inherited Sellable MCP tools when available:
|
|
17
19
|
|
|
@@ -568,6 +568,12 @@ export interface CreateCampaignResult {
|
|
|
568
568
|
watchUrl: string;
|
|
569
569
|
resumed?: boolean;
|
|
570
570
|
warning?: string;
|
|
571
|
+
approvalGate: {
|
|
572
|
+
kind: "brief-review";
|
|
573
|
+
mustShowBeforeQuestion: string[];
|
|
574
|
+
questionToolBlockedUntilShown: true;
|
|
575
|
+
};
|
|
576
|
+
nextStep: string;
|
|
571
577
|
}
|
|
572
578
|
export declare function createCampaign(input: CreateCampaignInput): Promise<CreateCampaignResult>;
|
|
573
579
|
export interface UpdateCampaignResult {
|
package/dist/tools/campaigns.js
CHANGED
|
@@ -658,6 +658,20 @@ function deriveOfferValue(name, briefContent) {
|
|
|
658
658
|
return heading.slice(0, 120);
|
|
659
659
|
return lines[0].slice(0, 120);
|
|
660
660
|
}
|
|
661
|
+
function buildBriefApprovalGate(watchUrl) {
|
|
662
|
+
return {
|
|
663
|
+
approvalGate: {
|
|
664
|
+
kind: "brief-review",
|
|
665
|
+
mustShowBeforeQuestion: [
|
|
666
|
+
"the full campaignBrief markdown the user is approving",
|
|
667
|
+
`Watch link: ${watchUrl}`,
|
|
668
|
+
],
|
|
669
|
+
questionToolBlockedUntilShown: true,
|
|
670
|
+
},
|
|
671
|
+
nextStep: "Before asking for approval, render the full campaign brief in normal chat text and then print " +
|
|
672
|
+
`Watch link: ${watchUrl}. Do not call AskUserQuestion or request_user_input until the user has seen both the brief content and this watch link.`,
|
|
673
|
+
};
|
|
674
|
+
}
|
|
661
675
|
export async function createCampaign(input) {
|
|
662
676
|
const api = getApi();
|
|
663
677
|
const config = getConfig();
|
|
@@ -675,6 +689,7 @@ export async function createCampaign(input) {
|
|
|
675
689
|
createdAt: existing.createdAt,
|
|
676
690
|
currentStep: existing.currentStep ?? null,
|
|
677
691
|
watchUrl,
|
|
692
|
+
...buildBriefApprovalGate(watchUrl),
|
|
678
693
|
resumed: true,
|
|
679
694
|
warning: hasCreateFields
|
|
680
695
|
? "campaignId provided; resume mode ignores create fields."
|
|
@@ -790,6 +805,7 @@ export async function createCampaign(input) {
|
|
|
790
805
|
createdAt: result.createdAt,
|
|
791
806
|
currentStep: result.currentStep,
|
|
792
807
|
watchUrl,
|
|
808
|
+
...buildBriefApprovalGate(watchUrl),
|
|
793
809
|
};
|
|
794
810
|
}
|
|
795
811
|
export async function updateCampaign(campaignId, input) {
|
|
@@ -827,17 +843,6 @@ export async function updateCampaign(campaignId, input) {
|
|
|
827
843
|
}
|
|
828
844
|
}
|
|
829
845
|
}
|
|
830
|
-
// Hotfix: when rubrics are saved with enableICPFilters, ensure the UI step
|
|
831
|
-
// is set to "filter-rules" so the frontend can render the filter state.
|
|
832
|
-
// Without this, the MCP caller can save rubrics + enableICPFilters on the
|
|
833
|
-
// backend while the UI currentStep points somewhere else (e.g. "messages"),
|
|
834
|
-
// causing the UI to be out of sync with the backend filter state.
|
|
835
|
-
if (updates.enableICPFilters === true &&
|
|
836
|
-
Array.isArray(updates.rubric) &&
|
|
837
|
-
updates.rubric.length > 0 &&
|
|
838
|
-
!updates.currentStep) {
|
|
839
|
-
updates.currentStep = "filter-rules";
|
|
840
|
-
}
|
|
841
846
|
let result = null;
|
|
842
847
|
let senderRouteResult = null;
|
|
843
848
|
const shouldPreserveInteractionModePut = typeof interactionMode === "string" &&
|
package/dist/tools/leads.d.ts
CHANGED
|
@@ -2540,6 +2540,20 @@ export declare function confirmLeadList(input: ConfirmLeadListInput): Promise<{
|
|
|
2540
2540
|
remainingRowCount?: number | null;
|
|
2541
2541
|
rowCount?: number | null;
|
|
2542
2542
|
};
|
|
2543
|
+
messageDraftBuilder: {
|
|
2544
|
+
firstAllowedStartPoint: string;
|
|
2545
|
+
startAllowed: boolean;
|
|
2546
|
+
requiredBeforeRunningCopy: string;
|
|
2547
|
+
requiredLiveInputs: {
|
|
2548
|
+
campaignOfferId: string;
|
|
2549
|
+
selectedLeadListId: string;
|
|
2550
|
+
workflowTableId: string | null;
|
|
2551
|
+
reviewBatchRowIds: string[];
|
|
2552
|
+
reviewBatchRowCount: number;
|
|
2553
|
+
};
|
|
2554
|
+
branchBasisFields: string[];
|
|
2555
|
+
promptRequired: string;
|
|
2556
|
+
};
|
|
2543
2557
|
boundedReviewBatch: {
|
|
2544
2558
|
requestedTargetLeadCount: number;
|
|
2545
2559
|
importedRowCount: number;
|
package/dist/tools/leads.js
CHANGED
|
@@ -429,6 +429,81 @@ function summarizeSignalSearchResponse(response) {
|
|
|
429
429
|
],
|
|
430
430
|
};
|
|
431
431
|
}
|
|
432
|
+
function buildSourceImportWatchNarration({ provider, selectedPostCount, estimatedEngagers, targetLeadCount, }) {
|
|
433
|
+
const providerLabel = provider === "signal-discovery"
|
|
434
|
+
? "Signal Leads"
|
|
435
|
+
: provider === "sales-nav"
|
|
436
|
+
? "Sales Nav"
|
|
437
|
+
: provider === "prospeo"
|
|
438
|
+
? "Prospeo"
|
|
439
|
+
: "Apollo";
|
|
440
|
+
const sourceDetail = provider === "signal-discovery"
|
|
441
|
+
? `${selectedPostCount ?? "selected"} approved post${selectedPostCount === 1 ? "" : "s"}${typeof estimatedEngagers === "number"
|
|
442
|
+
? ` with about ${estimatedEngagers.toLocaleString("en-US")} engagers`
|
|
443
|
+
: ""}`
|
|
444
|
+
: `the approved ${providerLabel} source`;
|
|
445
|
+
const targetDetail = typeof targetLeadCount === "number"
|
|
446
|
+
? ` Targeting ${targetLeadCount.toLocaleString("en-US")} source leads before the bounded review batch is cloned.`
|
|
447
|
+
: "";
|
|
448
|
+
return {
|
|
449
|
+
stage: "review-batch",
|
|
450
|
+
headline: provider === "signal-discovery"
|
|
451
|
+
? "Scraping source leads from posts"
|
|
452
|
+
: "Importing source leads",
|
|
453
|
+
visibleState: `The browser is showing ${providerLabel} import progress for ${sourceDetail}.${targetDetail}`,
|
|
454
|
+
agentIntent: "Codex is materializing the approved source into a lead list before cloning only the bounded review batch into the campaign.",
|
|
455
|
+
nextAction: "Wait for source leads, then import the 15-row review batch",
|
|
456
|
+
safety: "No enrichment, sequence, or sending starts during source import.",
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function buildSourceImportRecoveryWatchNarration(args) {
|
|
460
|
+
const providerLabel = args.provider === "signal-discovery"
|
|
461
|
+
? "Signal Leads"
|
|
462
|
+
: args.provider === "sales-nav"
|
|
463
|
+
? "Sales Nav"
|
|
464
|
+
: args.provider === "prospeo"
|
|
465
|
+
? "Prospeo"
|
|
466
|
+
: "source";
|
|
467
|
+
const reasonCopy = args.reason === "failed"
|
|
468
|
+
? `${providerLabel} import failed before review rows were ready.`
|
|
469
|
+
: args.reason === "zero"
|
|
470
|
+
? `${providerLabel} import finished without usable review rows.`
|
|
471
|
+
: args.reason === "timeout"
|
|
472
|
+
? `${providerLabel} import has not produced review rows within the wait window.`
|
|
473
|
+
: `${providerLabel} import is still materializing source rows.`;
|
|
474
|
+
return {
|
|
475
|
+
stage: "review-batch",
|
|
476
|
+
headline: args.reason === "failed" || args.reason === "zero"
|
|
477
|
+
? "Source import needs attention"
|
|
478
|
+
: "Source import still running",
|
|
479
|
+
visibleState: `${reasonCopy} The browser should stay on the lead import/review-batch screen.`,
|
|
480
|
+
agentIntent: "Codex is holding the campaign before filter-choice until a non-empty bounded review batch exists.",
|
|
481
|
+
nextAction: args.reason === "failed" || args.reason === "zero"
|
|
482
|
+
? "Retry the import or change the approved source"
|
|
483
|
+
: "Wait again, retry readiness, or change the source",
|
|
484
|
+
safety: "No enrichment, filtering, message cells, sequence, or sending has started.",
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function buildFilterChoiceWatchNarration({ sourceLeadCount, reviewRowCount, }) {
|
|
488
|
+
const sourceCopy = typeof sourceLeadCount === "number" && sourceLeadCount > 0
|
|
489
|
+
? ` from ${sourceLeadCount.toLocaleString("en-US")} source candidate${sourceLeadCount === 1 ? "" : "s"}`
|
|
490
|
+
: "";
|
|
491
|
+
const reviewCopy = typeof reviewRowCount === "number" && reviewRowCount > 0
|
|
492
|
+
? `${reviewRowCount.toLocaleString("en-US")} review lead${reviewRowCount === 1 ? " is" : "s are"} in the campaign table${sourceCopy}.`
|
|
493
|
+
: `The bounded review batch is in the campaign table${sourceCopy}.`;
|
|
494
|
+
return {
|
|
495
|
+
stage: "fit-message",
|
|
496
|
+
headline: "I recommend adding filters",
|
|
497
|
+
visibleState: `${reviewCopy} The browser is showing the filter-choice screen and sample rows.`,
|
|
498
|
+
agentIntent: "Codex is pausing on this sample because a mixed review list should be filtered before message review. Skip filters only if the visible rows already look clean.",
|
|
499
|
+
nextAction: "Choose filters or skip",
|
|
500
|
+
safety: "No enrichment, filtering, Generate Message cells, sequence, or sending starts from this choice.",
|
|
501
|
+
workerStatuses: {
|
|
502
|
+
leadFitBuilder: "idle",
|
|
503
|
+
messageDraftBuilder: "idle",
|
|
504
|
+
},
|
|
505
|
+
};
|
|
506
|
+
}
|
|
432
507
|
function normalizeImportProvider(provider) {
|
|
433
508
|
if (provider === "apollo-ai" || provider === "apollo")
|
|
434
509
|
return "apollo";
|
|
@@ -960,7 +1035,7 @@ export const leadToolDefinitions = [
|
|
|
960
1035
|
},
|
|
961
1036
|
{
|
|
962
1037
|
name: "import_leads",
|
|
963
|
-
description: "Create/select a lead list and start the provider import job. Requires provider prompt preflight via get_provider_prompt for the active provider.
|
|
1038
|
+
description: "Create/select a lead list and start the provider import job. Requires provider prompt preflight via get_provider_prompt for the active provider. On success, this tool owns moving the watched campaign to confirm-lead-list with import/progress narration after a leadListId/jobId exists; do not call update_campaign to fix that step. After the user confirms the list, call confirm_lead_list.",
|
|
964
1039
|
inputSchema: {
|
|
965
1040
|
type: "object",
|
|
966
1041
|
properties: {
|
|
@@ -1053,7 +1128,7 @@ export const leadToolDefinitions = [
|
|
|
1053
1128
|
},
|
|
1054
1129
|
{
|
|
1055
1130
|
name: "confirm_lead_list",
|
|
1056
|
-
description: "After the user confirms the lead list looks good, import
|
|
1131
|
+
description: "After the user confirms the lead list looks good, import the bounded review batch into the campaign table. This tool owns moving the watched campaign to filter-choice with sample-assessment narration only after non-empty review rows exist; do not call update_campaign to fix filter-choice afterward. selectedLeadListId remains the source lead list; workflowTableId is the campaign table.",
|
|
1057
1132
|
inputSchema: {
|
|
1058
1133
|
type: "object",
|
|
1059
1134
|
properties: {
|
|
@@ -1067,7 +1142,7 @@ export const leadToolDefinitions = [
|
|
|
1067
1142
|
},
|
|
1068
1143
|
currentStep: {
|
|
1069
1144
|
type: ["string", "null"],
|
|
1070
|
-
description: 'Headless workflow step ID. If omitted, defaults to "
|
|
1145
|
+
description: 'Headless workflow step ID. If omitted, defaults to "filter-choice" after review rows exist. Pass null to skip auto-advance.',
|
|
1071
1146
|
},
|
|
1072
1147
|
campaignName: {
|
|
1073
1148
|
type: "string",
|
|
@@ -2061,6 +2136,17 @@ export async function importLeads(input) {
|
|
|
2061
2136
|
await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
|
|
2062
2137
|
selectedLeadListId: result.tableId,
|
|
2063
2138
|
...(shouldSetCurrentStep ? { currentStep: effectiveCurrentStep } : {}),
|
|
2139
|
+
...(shouldSetCurrentStep
|
|
2140
|
+
? {
|
|
2141
|
+
watchNarration: buildSourceImportWatchNarration({
|
|
2142
|
+
provider: "signal-discovery",
|
|
2143
|
+
selectedPostCount: postsToScrape.length,
|
|
2144
|
+
estimatedEngagers: result.estimatedEngagers,
|
|
2145
|
+
targetLeadCount: normalizePositiveInteger(targetEngagerCount) ??
|
|
2146
|
+
result.estimatedEngagers,
|
|
2147
|
+
}),
|
|
2148
|
+
}
|
|
2149
|
+
: {}),
|
|
2064
2150
|
});
|
|
2065
2151
|
return {
|
|
2066
2152
|
provider: "signal-discovery",
|
|
@@ -2075,7 +2161,7 @@ export async function importLeads(input) {
|
|
|
2075
2161
|
targetLeadCount: cappedTargetLeadCount ?? null,
|
|
2076
2162
|
message: `Started scraping ${postsToScrape.length} posts (~${result.estimatedEngagers} engagers). Leads will appear as scraping completes.${importSelection.limited
|
|
2077
2163
|
? ` Limited from ${uniqueSelectedPosts.length} selected posts by the approved source-capacity scrape plan.`
|
|
2078
|
-
: ""}
|
|
2164
|
+
: ""} The watched campaign has been moved to confirm-lead-list with import progress copy; do not call update_campaign to fix that step.`,
|
|
2079
2165
|
};
|
|
2080
2166
|
}
|
|
2081
2167
|
// === SALES NAV / PROSPEO FLOW ===
|
|
@@ -2172,6 +2258,14 @@ export async function importLeads(input) {
|
|
|
2172
2258
|
await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
|
|
2173
2259
|
selectedLeadListId: leadListId,
|
|
2174
2260
|
...(shouldSetCurrentStep ? { currentStep: effectiveCurrentStep } : {}),
|
|
2261
|
+
...(shouldSetCurrentStep
|
|
2262
|
+
? {
|
|
2263
|
+
watchNarration: buildSourceImportWatchNarration({
|
|
2264
|
+
provider,
|
|
2265
|
+
targetLeadCount: cappedTargetLeadCount ?? null,
|
|
2266
|
+
}),
|
|
2267
|
+
}
|
|
2268
|
+
: {}),
|
|
2175
2269
|
});
|
|
2176
2270
|
return {
|
|
2177
2271
|
provider,
|
|
@@ -2180,7 +2274,7 @@ export async function importLeads(input) {
|
|
|
2180
2274
|
jobResult,
|
|
2181
2275
|
jobId,
|
|
2182
2276
|
targetLeadCount: cappedTargetLeadCount ?? null,
|
|
2183
|
-
message: "Import started. Review the lead list as it fills; once it looks good,
|
|
2277
|
+
message: "Import started and the watched campaign has been moved to confirm-lead-list with import progress copy. Review the lead list as it fills; once it looks good, call confirm_lead_list. Do not call update_campaign to fix the import step.",
|
|
2184
2278
|
};
|
|
2185
2279
|
}
|
|
2186
2280
|
export async function cancelLeadImport(input) {
|
|
@@ -2332,12 +2426,24 @@ export async function confirmLeadList(input) {
|
|
|
2332
2426
|
}
|
|
2333
2427
|
if (!readiness.ready) {
|
|
2334
2428
|
if (readiness.reason === "missing_job_id") {
|
|
2335
|
-
|
|
2429
|
+
const recoveryNarration = buildSourceImportRecoveryWatchNarration({
|
|
2430
|
+
reason: "pending",
|
|
2431
|
+
provider: resolvedProvider,
|
|
2432
|
+
});
|
|
2433
|
+
throw new Error(`${recoveryNarration.headline}: Import job ID is missing. Keep the campaign at confirm-lead-list; provide the jobId, retry readiness, cancel the import, or re-run-source before confirming. ${recoveryNarration.safety}`);
|
|
2336
2434
|
}
|
|
2337
2435
|
if (readiness.reason === "import_failed") {
|
|
2338
|
-
|
|
2436
|
+
const recoveryNarration = buildSourceImportRecoveryWatchNarration({
|
|
2437
|
+
reason: "failed",
|
|
2438
|
+
provider: resolvedProvider,
|
|
2439
|
+
});
|
|
2440
|
+
throw new Error(`${recoveryNarration.headline}: Import failed. Keep the campaign at confirm-lead-list; retry the provider import, cancel it, or re-run-source before confirming. ${recoveryNarration.safety}`);
|
|
2339
2441
|
}
|
|
2340
|
-
|
|
2442
|
+
const recoveryNarration = buildSourceImportRecoveryWatchNarration({
|
|
2443
|
+
reason: "timeout",
|
|
2444
|
+
provider: resolvedProvider,
|
|
2445
|
+
});
|
|
2446
|
+
throw new Error(`${recoveryNarration.headline}: Import still in progress. Keep the campaign at confirm-lead-list; retry readiness, cancel the import, or re-run-source before launching post-import scouts. ${recoveryNarration.safety}`);
|
|
2341
2447
|
}
|
|
2342
2448
|
const isTerminalAccessError = (error) => error instanceof SellableApiError && [401, 403, 404].includes(error.status);
|
|
2343
2449
|
const formatTerminalAccessError = (error) => {
|
|
@@ -2353,7 +2459,7 @@ export async function confirmLeadList(input) {
|
|
|
2353
2459
|
campaignName,
|
|
2354
2460
|
keepInSync,
|
|
2355
2461
|
...(typeof targetLeadCount === "number" ? { targetLeadCount } : {}),
|
|
2356
|
-
|
|
2462
|
+
currentStep: null,
|
|
2357
2463
|
})
|
|
2358
2464
|
.catch((error) => {
|
|
2359
2465
|
if (isTerminalAccessError(error)) {
|
|
@@ -2375,9 +2481,15 @@ export async function confirmLeadList(input) {
|
|
|
2375
2481
|
? importedRowIds.length
|
|
2376
2482
|
: typeof importResult.rowCount === "number"
|
|
2377
2483
|
? importResult.rowCount
|
|
2378
|
-
: typeof importResult.leadsImported === "number"
|
|
2379
|
-
|
|
2380
|
-
|
|
2484
|
+
: typeof importResult.leadsImported === "number" &&
|
|
2485
|
+
typeof importResult.leadsSkipped === "number"
|
|
2486
|
+
? importResult.leadsImported + importResult.leadsSkipped
|
|
2487
|
+
: typeof importResult.leadsImported === "number"
|
|
2488
|
+
? importResult.leadsImported
|
|
2489
|
+
: 0;
|
|
2490
|
+
const keptReviewRowCount = requestedTargetLeadCount !== null
|
|
2491
|
+
? Math.min(importedRowCount, requestedTargetLeadCount)
|
|
2492
|
+
: importedRowCount;
|
|
2381
2493
|
const remainingRowCount = typeof importResult.remainingRowCount === "number"
|
|
2382
2494
|
? importResult.remainingRowCount
|
|
2383
2495
|
: 0;
|
|
@@ -2392,7 +2504,11 @@ export async function confirmLeadList(input) {
|
|
|
2392
2504
|
throw new Error("Campaign review rows are still importing. Stay on lead import until the bounded campaign table is ready, then retry confirm_lead_list or wait_for_campaign_table_ready.");
|
|
2393
2505
|
}
|
|
2394
2506
|
if (importedRowCount <= 0) {
|
|
2395
|
-
|
|
2507
|
+
const recoveryNarration = buildSourceImportRecoveryWatchNarration({
|
|
2508
|
+
reason: "zero",
|
|
2509
|
+
provider: resolvedProvider,
|
|
2510
|
+
});
|
|
2511
|
+
throw new Error(`${recoveryNarration.headline}: No usable review rows were kept for the campaign table. Retry the import or change the approved source before continuing. ${recoveryNarration.safety}`);
|
|
2396
2512
|
}
|
|
2397
2513
|
if (campaignTableId && overflowRowIds.length > 0) {
|
|
2398
2514
|
const deleteBatchSize = 25;
|
|
@@ -2413,12 +2529,41 @@ export async function confirmLeadList(input) {
|
|
|
2413
2529
|
if (shouldSetCurrentStep) {
|
|
2414
2530
|
await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
|
|
2415
2531
|
currentStep: effectiveCurrentStep,
|
|
2532
|
+
...(effectiveCurrentStep === "filter-choice"
|
|
2533
|
+
? {
|
|
2534
|
+
watchNarration: buildFilterChoiceWatchNarration({
|
|
2535
|
+
sourceLeadCount: leadListRowCount,
|
|
2536
|
+
reviewRowCount: keptReviewRowCount,
|
|
2537
|
+
}),
|
|
2538
|
+
}
|
|
2539
|
+
: {}),
|
|
2416
2540
|
});
|
|
2417
2541
|
}
|
|
2418
2542
|
return {
|
|
2419
2543
|
sourceLeadListId: resolvedLeadListId,
|
|
2420
2544
|
campaignTableId: campaignTableId ?? null,
|
|
2421
2545
|
importResult,
|
|
2546
|
+
messageDraftBuilder: {
|
|
2547
|
+
firstAllowedStartPoint: "confirm_lead_list",
|
|
2548
|
+
startAllowed: true,
|
|
2549
|
+
requiredBeforeRunningCopy: "Persist watchNarration.workerDetails.messageDraftBuilder with runId or fallbackId, statusSource, status, startedAt, updatedAt, and current basis before showing Message Draft Builder as In Progress.",
|
|
2550
|
+
requiredLiveInputs: {
|
|
2551
|
+
campaignOfferId,
|
|
2552
|
+
selectedLeadListId: resolvedLeadListId,
|
|
2553
|
+
workflowTableId: campaignTableId ?? null,
|
|
2554
|
+
reviewBatchRowIds: importedRowIds.slice(0, keptReviewRowCount),
|
|
2555
|
+
reviewBatchRowCount: keptReviewRowCount,
|
|
2556
|
+
},
|
|
2557
|
+
branchBasisFields: [
|
|
2558
|
+
"campaign revision or updatedAt",
|
|
2559
|
+
"brief hash",
|
|
2560
|
+
"selectedLeadListId",
|
|
2561
|
+
"workflowTableId",
|
|
2562
|
+
"bounded review-batch row ids/hash",
|
|
2563
|
+
"filter choice at branch start",
|
|
2564
|
+
],
|
|
2565
|
+
promptRequired: 'Load get_subskill_prompt({ subskillName: "generate-messages", offset, limit }) until hasMore=false before drafting.',
|
|
2566
|
+
},
|
|
2422
2567
|
boundedReviewBatch: requestedTargetLeadCount !== null
|
|
2423
2568
|
? {
|
|
2424
2569
|
requestedTargetLeadCount,
|
|
@@ -2431,8 +2576,8 @@ export async function confirmLeadList(input) {
|
|
|
2431
2576
|
: undefined,
|
|
2432
2577
|
message: requestedTargetLeadCount !== null &&
|
|
2433
2578
|
leadListRowCount > requestedTargetLeadCount
|
|
2434
|
-
? `I found ${leadListRowCount} source candidates and imported the first ${Math.min(importedRowCount, requestedTargetLeadCount)} into the campaign review table.`
|
|
2435
|
-
: "Lead list imported into the campaign review table.
|
|
2579
|
+
? `I found ${leadListRowCount} source candidates and imported the first ${Math.min(importedRowCount, requestedTargetLeadCount)} into the campaign review table. The watched campaign is now on filter-choice with sample assessment copy; announce Message Draft Builder background start only if that branch has actually started.`
|
|
2580
|
+
: "Lead list imported into the campaign review table. The watched campaign is now on filter-choice with sample assessment copy; announce Message Draft Builder background start only if that branch has actually started.",
|
|
2436
2581
|
};
|
|
2437
2582
|
}
|
|
2438
2583
|
export function getProviderPrompt(input) {
|
package/dist/tools/processing.js
CHANGED
|
@@ -17,7 +17,7 @@ export const processingToolDefinitions = [
|
|
|
17
17
|
},
|
|
18
18
|
currentStep: {
|
|
19
19
|
type: ["string", "null"],
|
|
20
|
-
description: "Headless workflow step ID",
|
|
20
|
+
description: "Headless workflow step ID only when paired with the visible watch narration expected by the campaign-offers API; do not use as a separate step-only fixup after rubric, sender, sequence, or start work.",
|
|
21
21
|
},
|
|
22
22
|
enableICPFilters: {
|
|
23
23
|
type: "boolean",
|
package/dist/tools/prompts.d.ts
CHANGED
|
@@ -127,10 +127,20 @@ export interface PostFindLeadsScoutRegistryResponse {
|
|
|
127
127
|
afterAllComplete: boolean;
|
|
128
128
|
requiredArtifacts?: string[];
|
|
129
129
|
requiredState?: string[];
|
|
130
|
+
noFilterRequiredState?: string[];
|
|
131
|
+
skipFilterRule?: string;
|
|
130
132
|
optionalDebugArtifacts?: string[];
|
|
131
133
|
show: string[];
|
|
132
134
|
nextStage: string;
|
|
133
135
|
};
|
|
136
|
+
messageDraftBranchContract?: {
|
|
137
|
+
firstAllowedStart: string;
|
|
138
|
+
forbiddenStarts: string[];
|
|
139
|
+
runtimeProofTransport: string;
|
|
140
|
+
runtimeProofRequiredFields: string[];
|
|
141
|
+
basisFields: string[];
|
|
142
|
+
compactOutputFields: string[];
|
|
143
|
+
};
|
|
134
144
|
usage: {
|
|
135
145
|
codex: string;
|
|
136
146
|
claude: string;
|
package/dist/tools/prompts.js
CHANGED
|
@@ -256,10 +256,12 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
256
256
|
trigger: "find_leads_source_approved",
|
|
257
257
|
requiredInputs: [
|
|
258
258
|
"campaignId",
|
|
259
|
+
"campaign revision or campaignUpdatedAt",
|
|
259
260
|
"campaignBrief",
|
|
260
261
|
"source decision and selectedLeadList/source state",
|
|
261
262
|
"workflowTableId",
|
|
262
|
-
"imported review-batch rows from selectedLeadList",
|
|
263
|
+
"imported review-batch rows from selectedLeadList with row ids/hash",
|
|
264
|
+
"filter choice and filter/rubric basis when present",
|
|
263
265
|
],
|
|
264
266
|
optionalDebugArtifacts: ["brief.md", "lead-review.md", "lead-sample.json"],
|
|
265
267
|
scouts: scouts.map((agent) => ({
|
|
@@ -290,14 +292,56 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
290
292
|
joinGate: {
|
|
291
293
|
afterAllComplete: true,
|
|
292
294
|
requiredState: ["leadScoringRubrics", "messageDraftRecommendation"],
|
|
295
|
+
noFilterRequiredState: ["messageDraftRecommendation"],
|
|
296
|
+
skipFilterRule: "leadScoringRubrics may be absent only when the user explicitly skips filters with enableICPFilters=false; messageDraftRecommendation is still required before template review.",
|
|
293
297
|
optionalDebugArtifacts: ["lead-filter.md", "message-validation.md"],
|
|
294
298
|
show: ["readable_filters_with_reasons", "sample_message_for_approval"],
|
|
295
299
|
nextStage: "message-review",
|
|
296
300
|
},
|
|
301
|
+
messageDraftBranchContract: {
|
|
302
|
+
firstAllowedStart: "after confirm_lead_list imports a non-empty bounded review batch and get_rows_minimal confirms rows for workflowTableId",
|
|
303
|
+
forbiddenStarts: [
|
|
304
|
+
"source recommendation",
|
|
305
|
+
"provider import job alone",
|
|
306
|
+
"zero-row review batch",
|
|
307
|
+
],
|
|
308
|
+
runtimeProofTransport: "CampaignOffer.watchNarration.workerDetails.messageDraftBuilder",
|
|
309
|
+
runtimeProofRequiredFields: [
|
|
310
|
+
"runId or fallbackId",
|
|
311
|
+
"statusSource",
|
|
312
|
+
"status",
|
|
313
|
+
"startedAt",
|
|
314
|
+
"updatedAt",
|
|
315
|
+
"basisToken",
|
|
316
|
+
"basis.selectedLeadListId",
|
|
317
|
+
"basis.workflowTableId",
|
|
318
|
+
"basis.reviewBatchRowHash or basis.reviewBatchRowIds",
|
|
319
|
+
],
|
|
320
|
+
basisFields: [
|
|
321
|
+
"campaign revision or updatedAt",
|
|
322
|
+
"brief hash",
|
|
323
|
+
"selectedLeadListId",
|
|
324
|
+
"workflowTableId",
|
|
325
|
+
"bounded review-batch row ids/hash",
|
|
326
|
+
"filter choice",
|
|
327
|
+
"filter/rubric basis when present",
|
|
328
|
+
],
|
|
329
|
+
compactOutputFields: [
|
|
330
|
+
"templateRecommendation",
|
|
331
|
+
"tokenFillRules",
|
|
332
|
+
"renderedSample",
|
|
333
|
+
"concerns",
|
|
334
|
+
"status",
|
|
335
|
+
"basisToken",
|
|
336
|
+
"outputAt",
|
|
337
|
+
"outputHash",
|
|
338
|
+
"error or retry detail",
|
|
339
|
+
],
|
|
340
|
+
},
|
|
297
341
|
usage: {
|
|
298
342
|
codex: "After the user approves or auto-confirms the lead source, spawn both returned scout `name` values in one assistant turn only when the current Codex host exposes those custom agents.",
|
|
299
343
|
claude: "After lead source approval, invoke both returned Task/Agent subagents in one assistant message only when the current Claude session lists those agents, so filter-leads and message generation run concurrently.",
|
|
300
|
-
parentThreadRule: "Named agents are optional acceleration. If they are absent, do not customer-surface install status; the main thread still orchestrates filter and message branches from CampaignOffer state, selected source state, workflowTableId, and imported review-batch rows. Debug markdown/json artifacts are optional only. The message branch must load the full generate-messages prompt and may use the create-campaign-v2 message-review safety gate as a supplemental approval check. Join before message review.",
|
|
344
|
+
parentThreadRule: "Named agents are optional acceleration. If they are absent, do not customer-surface install status; the main thread still orchestrates filter and message branches from CampaignOffer state, selected source state, workflowTableId, and imported review-batch rows. Debug markdown/json artifacts are optional only. The message branch must load the full generate-messages prompt, must read live campaign/review-batch state through scoped MCP/product tools, must reject mismatched selectedLeadListId/workflowTableId/campaign/workspace input, and may use the create-campaign-v2 message-review safety gate as a supplemental approval check. Join before message review.",
|
|
301
345
|
},
|
|
302
346
|
};
|
|
303
347
|
}
|