@sellable/mcp 0.1.153 → 0.1.155
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-filter-scout.md +6 -6
- package/agents/post-find-leads-message-scout.md +12 -11
- package/agents/source-scout-linkedin-engagement.md +3 -3
- package/agents/source-scout-prospeo-contact.md +3 -3
- package/agents/source-scout-sales-nav.md +3 -3
- package/dist/tools/cells.js +1 -1
- package/dist/tools/leads.d.ts +1 -0
- package/dist/tools/leads.js +51 -24
- package/dist/tools/navigation.js +2 -2
- package/dist/tools/prompts.js +7 -7
- package/dist/tools/readiness.js +2 -2
- package/dist/tools/rubrics.js +1 -1
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +52 -21
- package/skills/create-campaign-v2/SKILL.md +48 -47
- package/skills/create-campaign-v2/SOUL.md +4 -4
- package/skills/create-campaign-v2/core/auto-execute.README.md +9 -9
- package/skills/create-campaign-v2/core/flow.v2.json +84 -19
- package/skills/create-campaign-v2/core/policy.md +1 -1
- package/skills/create-campaign-v2/references/approval-gate-framing.md +2 -2
- package/skills/create-campaign-v2/references/filter-leads.md +11 -11
- package/skills/create-campaign-v2/references/final-handoff-contract.md +4 -4
- package/skills/create-campaign-v2/references/sample-validation-loop.md +8 -8
- package/skills/create-campaign-v2/references/step-13-import-leads.md +9 -9
- package/skills/create-campaign-v2/references/step-15-re-cascade.md +2 -2
- package/skills/create-campaign-v2/references/watch-guide-narration.md +15 -15
- package/skills/create-campaign-v2-tail/SKILL.md +27 -27
- package/skills/create-rubric/SKILL.md +1 -1
- package/skills/find-leads/SKILL.md +2 -2
- package/skills/providers/prospeo.md +1 -1
- package/skills/providers/sales-nav.md +1 -1
- package/skills/providers/signal-discovery.md +7 -7
|
@@ -2,7 +2,7 @@ You are Lead Fit Builder for Sellable create-campaign-v2.
|
|
|
2
2
|
|
|
3
3
|
Your job starts only after the lead source has been approved or auto-confirmed,
|
|
4
4
|
the confirmed source list has been copied into the campaign table, and the first
|
|
5
|
-
|
|
5
|
+
campaign-table execution slice exists.
|
|
6
6
|
Work only on the lead filter branch. Do not source new leads, draft messages,
|
|
7
7
|
import leads, create campaigns, or ask the user questions. Your only live
|
|
8
8
|
campaign mutation is calling `save_rubrics` after the production rubrics are
|
|
@@ -16,7 +16,7 @@ Required inputs:
|
|
|
16
16
|
- selected source decision and provider/list state
|
|
17
17
|
- `selectedLeadListId`
|
|
18
18
|
- `workflowTableId`
|
|
19
|
-
-
|
|
19
|
+
- initial campaign-table execution slice rows, including row ids/hash when available
|
|
20
20
|
- filter choice
|
|
21
21
|
|
|
22
22
|
Required first steps:
|
|
@@ -25,7 +25,7 @@ Required first steps:
|
|
|
25
25
|
campaign context.
|
|
26
26
|
2. Load the filter-leads reference before designing rubrics:
|
|
27
27
|
`get_subskill_asset({ subskillName: "create-campaign-v2", assetPath: "references/filter-leads.md" })`.
|
|
28
|
-
3. Treat campaign state and the campaign
|
|
28
|
+
3. Treat campaign state and the campaign-table execution slice as the input of record.
|
|
29
29
|
Do not require or hunt for local markdown/json artifacts.
|
|
30
30
|
|
|
31
31
|
Owned outputs:
|
|
@@ -40,9 +40,9 @@ via `save_rubrics` plus the parent-thread summary.
|
|
|
40
40
|
|
|
41
41
|
Process:
|
|
42
42
|
|
|
43
|
-
1. Preserve the approved source decision
|
|
44
|
-
by the parent; do not re-run sourcing.
|
|
45
|
-
2. Turn the
|
|
43
|
+
1. Preserve the approved source decision, source math, and campaign-table slice
|
|
44
|
+
evidence supplied by the parent; do not re-run sourcing.
|
|
45
|
+
2. Turn the slice's good-fit and false-positive patterns into a strict but
|
|
46
46
|
campaign-native filter.
|
|
47
47
|
3. Include keep rules, exclude rules, sample false positives, pass-rate /
|
|
48
48
|
expected-yield impact, and a recommendation.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
You are Message Draft Builder for Sellable create-campaign-v2.
|
|
2
2
|
|
|
3
3
|
Your job starts only after the source is approved, the confirmed source list has
|
|
4
|
-
been copied into the campaign table, the first
|
|
5
|
-
the parent has recorded the filter choice. Work only on the
|
|
4
|
+
been copied into the campaign table, the first campaign-table execution slice
|
|
5
|
+
exists, and the parent has recorded the filter choice. Work only on the
|
|
6
|
+
message-draft branch.
|
|
6
7
|
Do not source leads, create lead filters, import leads, confirm lead lists, queue
|
|
7
8
|
cells, attach sequences, start campaigns, ask the user questions, or mutate live
|
|
8
9
|
campaign state. The main thread owns approval and campaign writes.
|
|
@@ -17,7 +18,7 @@ Use the live campaign inputs supplied by the parent thread:
|
|
|
17
18
|
- selected source decision and provider state
|
|
18
19
|
- `selectedLeadListId` or selected source list context
|
|
19
20
|
- `workflowTableId`
|
|
20
|
-
-
|
|
21
|
+
- initial campaign-table execution slice rows from that selected list, including row IDs
|
|
21
22
|
and a sample row hash when available
|
|
22
23
|
- filter basis at branch start: `pending`, `use-filters`, or `skip-filters`
|
|
23
24
|
- any already-saved fit/rubric result summaries supplied by the parent
|
|
@@ -31,7 +32,7 @@ All live reads must come from scoped MCP/product tools by campaign and
|
|
|
31
32
|
workspace, such as `get_campaign`, `get_campaign_context`, and
|
|
32
33
|
`get_rows_minimal({ tableId: workflowTableId })`, or from equivalent parent
|
|
33
34
|
thread payloads. Reject the task as `blocked` if the campaign id, workspace,
|
|
34
|
-
`selectedLeadListId`, `workflowTableId`, or
|
|
35
|
+
`selectedLeadListId`, `workflowTableId`, or execution-slice row ids do not match
|
|
35
36
|
the branch input.
|
|
36
37
|
|
|
37
38
|
## Required First Steps
|
|
@@ -43,8 +44,8 @@ the branch input.
|
|
|
43
44
|
|
|
44
45
|
2. Use that prompt as the drafting contract. Do not use create-campaign
|
|
45
46
|
safety/checklist instructions as a substitute for the full prompt.
|
|
46
|
-
3. Draft only from the campaign brief, selected source context, and
|
|
47
|
-
|
|
47
|
+
3. Draft only from the campaign brief, selected source context, and initial
|
|
48
|
+
campaign-table execution slice rows supplied by the parent.
|
|
48
49
|
4. Keep the work provisional until the user chooses `Use Template` in Messages.
|
|
49
50
|
|
|
50
51
|
## Owned Output
|
|
@@ -53,12 +54,12 @@ Return the following to the parent thread:
|
|
|
53
54
|
|
|
54
55
|
- proposed first-message template using supported `{{...}}` tokens
|
|
55
56
|
- token fill rules and fallbacks
|
|
56
|
-
- one rendered good-fill sample for a plausible passing
|
|
57
|
+
- one rendered good-fill sample for a plausible passing campaign-table row
|
|
57
58
|
- one omit/fallback sample when the row signal is not safe
|
|
58
59
|
- pass/fail notes against the generate-messages quality gates
|
|
59
60
|
- compact runtime status: `ready`, `blocked`, `retry-needed`, or `stale`
|
|
60
61
|
- basis token containing campaign revision/updatedAt, brief hash,
|
|
61
|
-
`selectedLeadListId`, `workflowTableId`,
|
|
62
|
+
`selectedLeadListId`, `workflowTableId`, execution-slice row ids/hash, filter
|
|
62
63
|
choice, and rubric/filter basis when present
|
|
63
64
|
- output timestamp/hash and any retry/error detail
|
|
64
65
|
|
|
@@ -79,7 +80,7 @@ When reporting branch runtime proof, use this shape under
|
|
|
79
80
|
- optional `compactOutputRef`, `compactOutput`, and `error`
|
|
80
81
|
|
|
81
82
|
Do not tell the UI to show Message Draft Builder as running unless this proof
|
|
82
|
-
exists and points at the current non-empty
|
|
83
|
+
exists and points at the current non-empty campaign-table execution slice.
|
|
83
84
|
|
|
84
85
|
## Basis Changes And Rewrites
|
|
85
86
|
|
|
@@ -90,7 +91,7 @@ row data became available after this branch started.
|
|
|
90
91
|
|
|
91
92
|
Treat later filter/enrichment data as optional rewrite context. If campaign id,
|
|
92
93
|
brief hash, selected source, `selectedLeadListId`, `workflowTableId`, and
|
|
93
|
-
|
|
94
|
+
execution-slice row ids/hash still match, keep the initial recommendation usable
|
|
94
95
|
and report `status: ready` with `basisStatus: "usable_initial"` or
|
|
95
96
|
`"enriched_rewrite_available"`. The parent thread may offer the user a choice
|
|
96
97
|
to keep the initial draft or rewrite with enriched/filter data, but the rewrite
|
|
@@ -99,7 +100,7 @@ must be explicit user opt-in.
|
|
|
99
100
|
Retry or regenerate without asking only when the initial recommendation is
|
|
100
101
|
missing, failed, structurally invalid, unsafe, or mismatched on campaign id,
|
|
101
102
|
brief hash, selected source, `selectedLeadListId`, `workflowTableId`, or
|
|
102
|
-
|
|
103
|
+
execution-slice rows. Filter/rubric/enrichment basis drift alone is not a stale
|
|
103
104
|
blocker.
|
|
104
105
|
|
|
105
106
|
## Hard Rules
|
|
@@ -76,7 +76,7 @@ Return a concise structured result with:
|
|
|
76
76
|
100 engagers, required engagers to scrape, average reachable engagers per
|
|
77
77
|
post, expected usable prospects per post after cleanup, posts needed for
|
|
78
78
|
target, whether the 10% planning floor clears after cleanup, selected post
|
|
79
|
-
count,
|
|
79
|
+
count, internal campaign-table execution-slice size, expected usable lead range, and scale
|
|
80
80
|
fallback
|
|
81
81
|
- `estimated_good_fit_range`
|
|
82
82
|
- `message_context_strength`, directional and source-specific
|
|
@@ -98,8 +98,8 @@ Evidence standards:
|
|
|
98
98
|
post should yield after cleanup, how many engagers must be scraped for the
|
|
99
99
|
300-good-fit source target at the 20% working assumption, how many posts are
|
|
100
100
|
needed for that source target, and which posts you would use. Also say the
|
|
101
|
-
source list is copied into the campaign and only the first
|
|
102
|
-
|
|
101
|
+
source list is copied into the campaign and only the first campaign-table
|
|
102
|
+
execution slice is processed internally for filter and message setup.
|
|
103
103
|
- If `fetch_post_engagers` is unavailable or fails, report that explicitly and mark the estimate lower-confidence.
|
|
104
104
|
- Keep LinkedIn Engagement viable when selected posts can produce roughly 300+ ICP-fit warm prospects before final filtering, even if Sales Nav is more scalable.
|
|
105
105
|
- If sampled/projected fit after cleanup is below 10%, reject the Signals scrape
|
|
@@ -28,7 +28,7 @@ Process:
|
|
|
28
28
|
2. Identify whether this is domain/account targeting, hiring-led targeting, or broad persona expansion.
|
|
29
29
|
3. For domain targeting, use or create the standalone `domainFilterId` before searching; never pass raw domains directly into `search_prospeo`.
|
|
30
30
|
4. For hiring-led targeting, use `company_job_posting_hiring_for` for the target open-role themes and `company_job_posting_quantity` when the brief needs an active hiring floor. Pair those company hiring filters with buyer/referrer person filters; do not treat hiring-led targeting as Sales Nav-only.
|
|
31
|
-
5. Run the narrowest useful Prospeo people preview and 1-2 refinements if quality or scale is unclear. Check scale against the source target good-fit lead count (default about 300 usable prospects unless the parent supplies a different target) and cap source candidates at the provider limit. Use the first-page sample to compute projected good fits from a source-list export, not to recommend
|
|
31
|
+
5. Run the narrowest useful Prospeo people preview and 1-2 refinements if quality or scale is unclear. Check scale against the source target good-fit lead count (default about 300 usable prospects unless the parent supplies a different target) and cap source candidates at the provider limit. Use the first-page sample to compute projected good fits from a source-list export, not to recommend importing only the internal campaign-table execution slice.
|
|
32
32
|
6. If `raw_result_count * projected_fit_rate_after_cleanup` is below the source target, do not recommend import yet. Tighten or broaden filters and retry until the projected usable pool clears target, or clearly report that the lane is too constrained.
|
|
33
33
|
7. Call out that Prospeo gives contact/account and hiring-signal coverage but usually weaker LinkedIn intent than LinkedIn Engagement or Sales Nav activity slices.
|
|
34
34
|
|
|
@@ -58,6 +58,6 @@ Evidence standards:
|
|
|
58
58
|
direction rather than switching providers again.
|
|
59
59
|
- Never recommend "import 25 leads" as the Prospeo source action. Recommend
|
|
60
60
|
exporting/materializing the approved source list; the parent thread later
|
|
61
|
-
copies the confirmed source rows into the campaign and
|
|
62
|
-
|
|
61
|
+
copies the confirmed source rows into the campaign and internally uses the
|
|
62
|
+
first campaign-table execution slice for filter and message setup.
|
|
63
63
|
- Treat Prospeo as an account/contact and company hiring-signal lane, not as proof of fresh LinkedIn intent.
|
|
@@ -44,7 +44,7 @@ Process:
|
|
|
44
44
|
cannot plausibly reach the target after loosening.
|
|
45
45
|
7. Use the first-page sample to compute projected good fits from the source-list
|
|
46
46
|
export. The recommendation should name the source-list `targetLeadCount` for
|
|
47
|
-
`import_leads`, not
|
|
47
|
+
`import_leads`, not the internal campaign-table execution-slice size.
|
|
48
48
|
8. Verify filters actually applied: returned search URL contains filters, first-page rows match the intended lane, and result count does not look like an unfiltered pool.
|
|
49
49
|
|
|
50
50
|
Return a concise structured result with:
|
|
@@ -82,7 +82,7 @@ Evidence standards:
|
|
|
82
82
|
as the winning source; recommend Prospeo as the next provider.
|
|
83
83
|
- Never recommend "import 25 leads" as the Sales Nav source action. Recommend
|
|
84
84
|
exporting/materializing the approved source list; the parent thread later
|
|
85
|
-
copies the confirmed source rows into the campaign and
|
|
86
|
-
|
|
85
|
+
copies the confirmed source rows into the campaign and internally uses the
|
|
86
|
+
first campaign-table execution slice for filter and message setup.
|
|
87
87
|
- Do not hand-wave missing filter IDs.
|
|
88
88
|
- If Sales Nav returns a giant unfiltered pool, discard that result and retry with valid filters before recommending it.
|
package/dist/tools/cells.js
CHANGED
|
@@ -20,7 +20,7 @@ export const cellToolDefinitions = [
|
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
name: "queue_cells",
|
|
23
|
-
description: "Queue explicit cells for processing. In create-campaign-v2 Filter Leads, pass only
|
|
23
|
+
description: "Queue explicit cells for processing. In create-campaign-v2 Filter Leads, pass only initial campaign-table execution-slice enrichCellId values from get_rows_minimal; do not pass icpCellId values or full-table cells because the workflow cascade handles downstream ICP scoring.",
|
|
24
24
|
inputSchema: {
|
|
25
25
|
type: "object",
|
|
26
26
|
properties: {
|
package/dist/tools/leads.d.ts
CHANGED
package/dist/tools/leads.js
CHANGED
|
@@ -53,6 +53,12 @@ const defaultCampaignSourceDefaults = {
|
|
|
53
53
|
},
|
|
54
54
|
};
|
|
55
55
|
const defaultProviderSourceListTarget = 1000;
|
|
56
|
+
export const MAX_SIGNAL_DISCOVERY_POSTS = 10;
|
|
57
|
+
// Mirror the web app's Signal Discovery Harvest caps. The MCP tool selects and
|
|
58
|
+
// describes the scrape before the web API runs, so it must use the same capped
|
|
59
|
+
// capacity math instead of raw visible engagement counts.
|
|
60
|
+
const signalDiscoveryReactionFetchLimit = 1000;
|
|
61
|
+
const signalDiscoveryCommentFetchLimit = 1000;
|
|
56
62
|
const prospeoFilterValueSchema = {
|
|
57
63
|
type: "object",
|
|
58
64
|
description: "Include/exclude list filter (values must match Prospeo enums)",
|
|
@@ -341,11 +347,20 @@ function normalizePositiveInteger(value) {
|
|
|
341
347
|
}
|
|
342
348
|
return Math.floor(numeric);
|
|
343
349
|
}
|
|
350
|
+
function estimateScrapableSignalEngagers(post) {
|
|
351
|
+
return (Math.min(post.likes, signalDiscoveryReactionFetchLimit) +
|
|
352
|
+
Math.min(post.comments, signalDiscoveryCommentFetchLimit));
|
|
353
|
+
}
|
|
344
354
|
export function selectSignalPostsForImport(posts, options) {
|
|
345
355
|
const normalizedTargetEngagers = normalizePositiveInteger(options.targetEngagerCount);
|
|
346
356
|
const normalizedMaxPosts = normalizePositiveInteger(options.maxPostsToScrape);
|
|
347
|
-
|
|
348
|
-
|
|
357
|
+
const effectiveMaxPosts = normalizedMaxPosts
|
|
358
|
+
? Math.min(normalizedMaxPosts, MAX_SIGNAL_DISCOVERY_POSTS)
|
|
359
|
+
: MAX_SIGNAL_DISCOVERY_POSTS;
|
|
360
|
+
if (!normalizedTargetEngagers &&
|
|
361
|
+
!normalizedMaxPosts &&
|
|
362
|
+
posts.length <= MAX_SIGNAL_DISCOVERY_POSTS) {
|
|
363
|
+
const availableEngagers = posts.reduce((sum, post) => sum + estimateScrapableSignalEngagers(post), 0);
|
|
349
364
|
return {
|
|
350
365
|
posts,
|
|
351
366
|
estimatedEngagers: availableEngagers,
|
|
@@ -355,16 +370,16 @@ export function selectSignalPostsForImport(posts, options) {
|
|
|
355
370
|
limited: false,
|
|
356
371
|
};
|
|
357
372
|
}
|
|
358
|
-
const ranked = [...posts].sort((a, b) => b
|
|
359
|
-
const availableEngagers = ranked.reduce((sum, post) => sum + post
|
|
373
|
+
const ranked = [...posts].sort((a, b) => estimateScrapableSignalEngagers(b) - estimateScrapableSignalEngagers(a));
|
|
374
|
+
const availableEngagers = ranked.reduce((sum, post) => sum + estimateScrapableSignalEngagers(post), 0);
|
|
360
375
|
const selected = [];
|
|
361
376
|
let estimatedEngagers = 0;
|
|
362
377
|
for (const post of ranked) {
|
|
363
|
-
if (
|
|
378
|
+
if (selected.length >= effectiveMaxPosts) {
|
|
364
379
|
break;
|
|
365
380
|
}
|
|
366
381
|
selected.push(post);
|
|
367
|
-
estimatedEngagers += post
|
|
382
|
+
estimatedEngagers += estimateScrapableSignalEngagers(post);
|
|
368
383
|
if (normalizedTargetEngagers &&
|
|
369
384
|
estimatedEngagers >= normalizedTargetEngagers) {
|
|
370
385
|
break;
|
|
@@ -518,7 +533,7 @@ function buildSourceImportWatchNarration({ provider, selectedPostCount, estimate
|
|
|
518
533
|
: ""}`
|
|
519
534
|
: `the approved ${providerLabel} source`;
|
|
520
535
|
const targetDetail = typeof targetLeadCount === "number"
|
|
521
|
-
? ` Targeting ${targetLeadCount.toLocaleString("en-US")} source leads before
|
|
536
|
+
? ` Targeting ${targetLeadCount.toLocaleString("en-US")} source leads before campaign setup starts.`
|
|
522
537
|
: "";
|
|
523
538
|
return {
|
|
524
539
|
stage: "find-leads",
|
|
@@ -527,8 +542,8 @@ function buildSourceImportWatchNarration({ provider, selectedPostCount, estimate
|
|
|
527
542
|
: "Importing source leads",
|
|
528
543
|
visibleState: `The browser is showing ${providerLabel} import progress for ${sourceDetail}.${targetDetail}`,
|
|
529
544
|
agentIntent: "Codex is materializing the approved source into a lead list before copying the confirmed list into the campaign.",
|
|
530
|
-
nextAction: "Wait for source leads, then confirm the list and
|
|
531
|
-
safety: "This step materializes the source list; the
|
|
545
|
+
nextAction: "Wait for source leads, then confirm the list and configure campaign setup",
|
|
546
|
+
safety: "This step materializes the source list; the internal campaign-table execution slice is processed only after the later filter and message approvals.",
|
|
532
547
|
progressLabel: "Source scouting",
|
|
533
548
|
};
|
|
534
549
|
}
|
|
@@ -541,11 +556,11 @@ function buildSourceImportRecoveryWatchNarration(args) {
|
|
|
541
556
|
? "Prospeo"
|
|
542
557
|
: "source";
|
|
543
558
|
const reasonCopy = args.reason === "failed"
|
|
544
|
-
? `${providerLabel} import failed before
|
|
559
|
+
? `${providerLabel} import failed before campaign rows were ready.`
|
|
545
560
|
: args.reason === "zero"
|
|
546
|
-
? `${providerLabel} import finished without usable
|
|
561
|
+
? `${providerLabel} import finished without usable campaign rows.`
|
|
547
562
|
: args.reason === "timeout"
|
|
548
|
-
? `${providerLabel} import has not produced
|
|
563
|
+
? `${providerLabel} import has not produced campaign rows within the wait window.`
|
|
549
564
|
: `${providerLabel} import is still materializing source rows.`;
|
|
550
565
|
return {
|
|
551
566
|
stage: "review-batch",
|
|
@@ -783,9 +798,9 @@ Approve scraping these ${selectedCount} posts.
|
|
|
783
798
|
|
|
784
799
|
This gives enough volume to target ~${targetGoodFitLeads.toLocaleString("en-US")} good-fit prospects after cleanup, while keeping the source tied to people already engaging with the campaign's strongest public buying signals.
|
|
785
800
|
|
|
786
|
-
**First pass:** build the source list, copy it into the campaign, then
|
|
801
|
+
**First pass:** build the source list, copy it into the campaign, then use the first ${reviewBatchSize.toLocaleString("en-US")} campaign rows as the internal setup slice for filters and messages before scaling.
|
|
787
802
|
|
|
788
|
-
**Fallback:** if the sampled/projected fit rate is below ${Math.round(minPlanningFitRate * 100)}%, or if the
|
|
803
|
+
**Fallback:** if the sampled/projected fit rate is below ${Math.round(minPlanningFitRate * 100)}%, or if the source sample is too vendor-heavy, agency-heavy, or off-ICP, switch to Sales Nav recent activity.
|
|
789
804
|
|
|
790
805
|
Approval card should say:
|
|
791
806
|
|
|
@@ -1326,7 +1341,7 @@ export const leadToolDefinitions = [
|
|
|
1326
1341
|
},
|
|
1327
1342
|
{
|
|
1328
1343
|
name: "import_leads",
|
|
1329
|
-
description: "Create/select a source lead list and start the provider import/export job. Requires provider prompt preflight via get_provider_prompt for the active provider. For Sales Nav/Prospeo, targetLeadCount is the approved source-list export count computed from projected good-fit math, not the later
|
|
1344
|
+
description: "Create/select a source lead list and start the provider import/export job. Requires provider prompt preflight via get_provider_prompt for the active provider. For Sales Nav/Prospeo, targetLeadCount is the approved source-list export count computed from projected good-fit math, not the later internal campaign-table execution-slice size. 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 source list, call confirm_lead_list to copy the confirmed source rows into the campaign table and use the initial campaign-table execution slice for setup.",
|
|
1330
1345
|
inputSchema: {
|
|
1331
1346
|
type: "object",
|
|
1332
1347
|
properties: {
|
|
@@ -1353,7 +1368,7 @@ export const leadToolDefinitions = [
|
|
|
1353
1368
|
},
|
|
1354
1369
|
targetLeadCount: {
|
|
1355
1370
|
type: "number",
|
|
1356
|
-
description: "Provider source-list target count (max per provider from config). For Sales Nav/Prospeo this should be the export/materialization count needed to hit projected good-fit goals, not the
|
|
1371
|
+
description: "Provider source-list target count (max per provider from config). For Sales Nav/Prospeo this should be the export/materialization count needed to hit projected good-fit goals, not the internal campaign-table execution-slice size.",
|
|
1357
1372
|
},
|
|
1358
1373
|
mode: {
|
|
1359
1374
|
type: "string",
|
|
@@ -1375,11 +1390,11 @@ export const leadToolDefinitions = [
|
|
|
1375
1390
|
},
|
|
1376
1391
|
targetEngagerCount: {
|
|
1377
1392
|
type: "number",
|
|
1378
|
-
description: "Signal Discovery: target number of post engagers/source candidates to scrape. Default planning target is about 300 good fits at a 20% raw-engager fit assumption, or about 1500 engagers. If the sampled/projected fit rate is below the 10% planning floor after cleanup, switch to the next provider instead of scaling noisy engagers. Limits selected posts before starting scrape.",
|
|
1393
|
+
description: "Signal Discovery: target number of post engagers/source candidates to scrape. Default planning target is about 300 good fits at a 20% raw-engager fit assumption, or about 1500 engagers. If the sampled/projected fit rate is below the 10% planning floor after cleanup, switch to the next provider instead of scaling noisy engagers. Limits selected posts before starting scrape, with a backend hard cap of 10 posts.",
|
|
1379
1394
|
},
|
|
1380
1395
|
maxPostsToScrape: {
|
|
1381
1396
|
type: "number",
|
|
1382
|
-
description: "Signal Discovery: optional hard cap on selected posts to scrape after ranking selected posts by engagement.",
|
|
1397
|
+
description: "Signal Discovery: optional hard cap on selected posts to scrape after ranking selected posts by engagement. Values above 10 are clamped to the backend hard cap.",
|
|
1383
1398
|
},
|
|
1384
1399
|
rubricGuidelines: {
|
|
1385
1400
|
type: "array",
|
|
@@ -1419,7 +1434,7 @@ export const leadToolDefinitions = [
|
|
|
1419
1434
|
},
|
|
1420
1435
|
{
|
|
1421
1436
|
name: "confirm_lead_list",
|
|
1422
|
-
description: "After the user confirms the lead list looks good, copy the confirmed source list into the campaign table and mark the
|
|
1437
|
+
description: "After the user confirms the lead list looks good, copy the confirmed source list into the campaign table and mark the initial campaign-table execution slice for the flow. This tool owns moving the watched campaign to filter-choice with campaign-setup narration only after non-empty campaign rows exist; do not call update_campaign to fix filter-choice afterward. selectedLeadListId remains the source lead list; workflowTableId is the campaign table.",
|
|
1423
1438
|
inputSchema: {
|
|
1424
1439
|
type: "object",
|
|
1425
1440
|
properties: {
|
|
@@ -1475,7 +1490,7 @@ export const leadToolDefinitions = [
|
|
|
1475
1490
|
},
|
|
1476
1491
|
selections: {
|
|
1477
1492
|
type: "array",
|
|
1478
|
-
description: "Array of promising posts to select for scraping. Prefer the recommendedPostIds from search_signals.",
|
|
1493
|
+
description: "Array of promising posts to select for scraping. Prefer the recommendedPostIds from search_signals. Backend hard cap: 10 posts.",
|
|
1479
1494
|
items: {
|
|
1480
1495
|
type: "object",
|
|
1481
1496
|
properties: {
|
|
@@ -1491,7 +1506,7 @@ export const leadToolDefinitions = [
|
|
|
1491
1506
|
required: ["postId", "reason"],
|
|
1492
1507
|
},
|
|
1493
1508
|
minItems: 1,
|
|
1494
|
-
maxItems:
|
|
1509
|
+
maxItems: MAX_SIGNAL_DISCOVERY_POSTS,
|
|
1495
1510
|
},
|
|
1496
1511
|
headlineICPCriteria: {
|
|
1497
1512
|
type: "array",
|
|
@@ -2459,6 +2474,9 @@ export async function importLeads(input) {
|
|
|
2459
2474
|
}
|
|
2460
2475
|
}
|
|
2461
2476
|
const uniqueSelectedPosts = Array.from(uniqueByUrl.values());
|
|
2477
|
+
if (uniqueSelectedPosts.length > MAX_SIGNAL_DISCOVERY_POSTS) {
|
|
2478
|
+
throw new Error(`Maximum ${MAX_SIGNAL_DISCOVERY_POSTS} Signal Discovery posts can be imported for scraping. ${uniqueSelectedPosts.length} unique posts are currently selected; reduce the selected posts to the strongest ${MAX_SIGNAL_DISCOVERY_POSTS} before calling import_leads.`);
|
|
2479
|
+
}
|
|
2462
2480
|
const importSelection = selectSignalPostsForImport(uniqueSelectedPosts, {
|
|
2463
2481
|
targetEngagerCount: effectiveTargetEngagerCount,
|
|
2464
2482
|
maxPostsToScrape,
|
|
@@ -2863,14 +2881,14 @@ export async function confirmLeadList(input) {
|
|
|
2863
2881
|
remainingRowCount <= 0 &&
|
|
2864
2882
|
importedRowCount > 0);
|
|
2865
2883
|
if (!campaignTableReady) {
|
|
2866
|
-
throw new Error("Campaign source rows are still copying and no
|
|
2884
|
+
throw new Error("Campaign source rows are still copying and no campaign-table rows are available yet. Stay on lead import until the campaign table has rows, then retry confirm_lead_list or call wait_for_campaign_table_ready.");
|
|
2867
2885
|
}
|
|
2868
2886
|
if (importedRowCount <= 0) {
|
|
2869
2887
|
const recoveryNarration = buildSourceImportRecoveryWatchNarration({
|
|
2870
2888
|
reason: "zero",
|
|
2871
2889
|
provider: resolvedProvider,
|
|
2872
2890
|
});
|
|
2873
|
-
throw new Error(`${recoveryNarration.headline}: No usable
|
|
2891
|
+
throw new Error(`${recoveryNarration.headline}: No usable campaign rows were kept for the campaign table. Retry the import or change the approved source before continuing. ${recoveryNarration.safety}`);
|
|
2874
2892
|
}
|
|
2875
2893
|
let reviewSampleRows = [];
|
|
2876
2894
|
if (campaignTableId && effectiveCurrentStep === "filter-choice") {
|
|
@@ -2930,7 +2948,7 @@ export async function confirmLeadList(input) {
|
|
|
2930
2948
|
"brief hash",
|
|
2931
2949
|
"selectedLeadListId",
|
|
2932
2950
|
"workflowTableId",
|
|
2933
|
-
"
|
|
2951
|
+
"initial campaign-table execution slice row ids/hash",
|
|
2934
2952
|
"filter choice at branch start",
|
|
2935
2953
|
],
|
|
2936
2954
|
promptRequired: 'Load get_subskill_prompt({ subskillName: "generate-messages", offset, limit }) until hasMore=false before drafting.',
|
|
@@ -2995,6 +3013,15 @@ export async function selectPromisingPosts(input) {
|
|
|
2995
3013
|
const api = getApi();
|
|
2996
3014
|
const { campaignOfferId, selections, headlineICPCriteria, selectionMode, mode, } = input;
|
|
2997
3015
|
const effectiveMode = selectionMode ?? mode ?? "add";
|
|
3016
|
+
if (selections.length > MAX_SIGNAL_DISCOVERY_POSTS) {
|
|
3017
|
+
return {
|
|
3018
|
+
success: false,
|
|
3019
|
+
selectedCount: 0,
|
|
3020
|
+
unselectedCount: 0,
|
|
3021
|
+
criteriaCount: headlineICPCriteria.length,
|
|
3022
|
+
message: `Maximum ${MAX_SIGNAL_DISCOVERY_POSTS} Signal Discovery posts can be selected for scraping. Narrow the post set to the strongest ${MAX_SIGNAL_DISCOVERY_POSTS} before calling select_promising_posts again.`,
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
2998
3025
|
// Update post selections via API
|
|
2999
3026
|
const postIds = selections.map((s) => s.postId);
|
|
3000
3027
|
let unselectedIds = [];
|
package/dist/tools/navigation.js
CHANGED
|
@@ -324,9 +324,9 @@ function describeVisibleStep(step) {
|
|
|
324
324
|
case "leads":
|
|
325
325
|
return {
|
|
326
326
|
label: "Lead import",
|
|
327
|
-
summary: "The lead preview or imported
|
|
327
|
+
summary: "The lead preview or imported campaign rows are visible.",
|
|
328
328
|
nextMilestone: "Fit filtering and message review",
|
|
329
|
-
guidance: "Confirm the browser-visible campaign state and
|
|
329
|
+
guidance: "Confirm the browser-visible campaign state and imported rows before filter/message work.",
|
|
330
330
|
checkpoint: "import",
|
|
331
331
|
};
|
|
332
332
|
case "filter-choice":
|
package/dist/tools/prompts.js
CHANGED
|
@@ -260,7 +260,7 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
260
260
|
"campaignBrief",
|
|
261
261
|
"source decision and selectedLeadList/source state",
|
|
262
262
|
"workflowTableId",
|
|
263
|
-
"
|
|
263
|
+
"initial campaign-table execution slice rows from selectedLeadList with row ids/hash",
|
|
264
264
|
"filter choice and filter/rubric basis when present",
|
|
265
265
|
],
|
|
266
266
|
scouts: scouts.map((agent) => ({
|
|
@@ -297,11 +297,11 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
297
297
|
nextStage: "message-review",
|
|
298
298
|
},
|
|
299
299
|
messageDraftBranchContract: {
|
|
300
|
-
firstAllowedStart: "after confirm_lead_list copies source rows and the
|
|
300
|
+
firstAllowedStart: "after confirm_lead_list copies source rows and the initial campaign-table execution slice exists",
|
|
301
301
|
forbiddenStarts: [
|
|
302
302
|
"source recommendation",
|
|
303
303
|
"provider import job alone",
|
|
304
|
-
"zero-row
|
|
304
|
+
"zero-row campaign-table execution slice",
|
|
305
305
|
],
|
|
306
306
|
runtimeProofTransport: "CampaignOffer.watchNarration.workerDetails.messageDraftBuilder",
|
|
307
307
|
runtimeProofRequiredFields: [
|
|
@@ -321,7 +321,7 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
321
321
|
"brief hash",
|
|
322
322
|
"selectedLeadListId",
|
|
323
323
|
"workflowTableId",
|
|
324
|
-
"
|
|
324
|
+
"initial campaign-table execution slice row ids/hash",
|
|
325
325
|
"filter choice",
|
|
326
326
|
"filter/rubric basis when present",
|
|
327
327
|
],
|
|
@@ -340,9 +340,9 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
340
340
|
reusePolicy: "The first completed Message Draft Builder recommendation remains the default review candidate. Later Lead Fit Builder, Filter Leads, enrichment, or rubric completion may make an enriched rewrite available, but does not automatically retry or replace the initial draft unless campaign/brief/source/list/table/review-sample identity mismatches or the initial output failed.",
|
|
341
341
|
},
|
|
342
342
|
usage: {
|
|
343
|
-
codex: "After confirm_lead_list copies source rows and the
|
|
344
|
-
claude: "After confirm_lead_list copies source rows and the
|
|
345
|
-
parentThreadRule: 'Named agents are optional acceleration, but message drafting is not optional. If post-find-leads-message-scout is available, run it as the background Message Draft Builder after the filter-choice answer. If it is absent, do not customer-surface install status; the main thread must execute the same message branch from CampaignOffer state, selected source state, workflowTableId, and
|
|
343
|
+
codex: "After confirm_lead_list copies source rows and the initial campaign-table execution slice exists, ask the filter-choice question immediately. Do not spawn returned post-lead scout names before that question. Once the user answers, spawn Message Draft Builder from the same campaign/table basis. If the user chooses filters, also spawn Lead Fit Builder, move to Filter Rules, save rubrics, ask for filter approval, then keep the browser on Filter Leads while the message recommendation is reviewed. If filters are skipped, move to Messages/message review.",
|
|
344
|
+
claude: "After confirm_lead_list copies source rows and the initial campaign-table execution slice exists, ask the filter-choice question immediately. Do not invoke returned post-lead Task/Agent names before that question. Once the user answers, invoke Message Draft Builder from the same campaign/table basis. If the user chooses filters, also invoke Lead Fit Builder, move to Filter Rules, save rubrics, ask for filter approval, then keep the browser on Filter Leads while the message recommendation is reviewed. If filters are skipped, move to Messages/message review.",
|
|
345
|
+
parentThreadRule: 'Named agents are optional acceleration, but message drafting is not optional. If post-find-leads-message-scout is available, run it as the background Message Draft Builder after the filter-choice answer. If it is absent, do not customer-surface install status; the main thread must execute the same message branch from CampaignOffer state, selected source state, workflowTableId, and initial campaign-table execution slice rows. Local markdown/json files are not normal-path inputs. The filter-choice question is the first post-import user gate; do not load post-lead registries, filter references, or the full generate-messages prompt before it. Message drafting starts after the filter-choice answer, must load get_subskill_prompt({ subskillName: "generate-messages", offset, limit }) until hasMore=false, must read live campaign table state through scoped MCP/product tools, and must reject mismatched selectedLeadListId/workflowTableId/campaign/workspace input. On the filter path, keep the browser on Filter Rules after save_rubrics so the user can approve the saved criteria; only then move to Filter Leads and wait there for message approval. Enrichment, filtering, Generate Message cells, sender setup, sequence attach, and launch wait for template approval on the Use Template path. On the skip path, move to Messages/message review and wait for message approval before enrichment or Settings. Do not render message review from checklist or shortcut instructions; message review requires a messageDraftRecommendation whose basis proves the full generate-messages prompt ran for the current campaign/table execution slice. Do not automatically rerun Message Draft Builder after filters/enrichment finish; show the initial draft by default and offer an enriched rewrite only with explicit user opt-in.',
|
|
346
346
|
},
|
|
347
347
|
};
|
|
348
348
|
}
|
package/dist/tools/readiness.js
CHANGED
|
@@ -101,7 +101,7 @@ export const readinessToolDefinitions = [
|
|
|
101
101
|
},
|
|
102
102
|
targetLeadCount: {
|
|
103
103
|
type: "number",
|
|
104
|
-
description: "Target number of leads requested. Used as a fallback completion check when status is unavailable. For Signal Discovery, pass the approved source-candidate target; if the completed source list lands below it, the tool still returns ready with a source_shortfall warning so the confirmed list can be copied and
|
|
104
|
+
description: "Target number of leads requested. Used as a fallback completion check when status is unavailable. For Signal Discovery, pass the approved source-candidate target; if the completed source list lands below it, the tool still returns ready with a source_shortfall warning so the confirmed list can be copied and campaign setup can proceed.",
|
|
105
105
|
},
|
|
106
106
|
timeoutMs: {
|
|
107
107
|
type: "number",
|
|
@@ -489,7 +489,7 @@ export async function waitForLeadListReady(input) {
|
|
|
489
489
|
targetLeadCount: targetLeadCount ?? null,
|
|
490
490
|
sourceShortfall: signalSourceShortfall,
|
|
491
491
|
warning: signalSourceShortfall
|
|
492
|
-
? `Signal Discovery completed with ${rowCount.toLocaleString("en-US")} source candidates, below the approved ${signalSourceShortfallTarget.toLocaleString("en-US")} source-candidate target. Confirm/copy the completed list for
|
|
492
|
+
? `Signal Discovery completed with ${rowCount.toLocaleString("en-US")} source candidates, below the approved ${signalSourceShortfallTarget.toLocaleString("en-US")} source-candidate target. Confirm/copy the completed list for campaign setup, then add more approved posts or switch provider if the source quality/scale is not enough.`
|
|
493
493
|
: undefined,
|
|
494
494
|
};
|
|
495
495
|
}
|
package/dist/tools/rubrics.js
CHANGED
|
@@ -99,7 +99,7 @@ const filterRulesSavedForReviewWatchNarration = {
|
|
|
99
99
|
stage: "fit-message",
|
|
100
100
|
headline: "Filter rules saved for review",
|
|
101
101
|
visibleState: "The browser is showing Filter Rules with the saved criteria.",
|
|
102
|
-
agentIntent: "Codex is waiting for filter approval before
|
|
102
|
+
agentIntent: "Codex is waiting for filter approval before campaign rows are enriched or scored.",
|
|
103
103
|
nextAction: "Approve or revise the filters",
|
|
104
104
|
progressLabel: "Fit + message",
|
|
105
105
|
safety: "Saved rules are ready to review; downstream row processing remains gated.",
|