@sellable/mcp 0.1.104 → 0.1.106
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/source-scout-linkedin-engagement.md +4 -4
- package/agents/source-scout-prospeo-contact.md +4 -3
- package/agents/source-scout-sales-nav.md +3 -2
- package/dist/tools/leads.js +12 -3
- package/dist/tools/rubrics.d.ts +45 -0
- package/dist/tools/rubrics.js +60 -1
- package/package.json +1 -1
- package/skills/create-campaign-v2/SKILL.md +12 -0
- package/skills/create-campaign-v2/core/flow.v2.json +36 -3
- package/skills/create-campaign-v2-tail/SKILL.md +16 -2
- package/skills/providers/signal-discovery.md +8 -0
|
@@ -7,10 +7,10 @@ Required first step:
|
|
|
7
7
|
- Load the canonical provider prompt before searching. If the parent supplies a
|
|
8
8
|
draft `campaignOfferId`, call `get_provider_prompt({ provider:
|
|
9
9
|
"signal-discovery", campaignOfferId, confirmed: true })` and include that same
|
|
10
|
-
`campaignOfferId`
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
campaignless preview mode.
|
|
10
|
+
`campaignOfferId` plus `currentStep: "signal-discovery"` in `search_signals`
|
|
11
|
+
so the user can watch source work in the campaign UI. Treat that as a
|
|
12
|
+
campaign-attached persisted search; do not run a post-mint search without the
|
|
13
|
+
campaign ID. If no campaign ID is supplied, run campaignless preview mode.
|
|
14
14
|
|
|
15
15
|
Use the inherited Sellable MCP tools when available:
|
|
16
16
|
|
|
@@ -6,9 +6,10 @@ Required first step:
|
|
|
6
6
|
|
|
7
7
|
- Load the canonical provider prompt before searching. If the parent supplies a
|
|
8
8
|
draft `campaignOfferId`, call `get_provider_prompt({ provider: "prospeo",
|
|
9
|
-
campaignOfferId, confirmed: true })` and include that same `campaignOfferId`
|
|
10
|
-
`search_prospeo` so the user can watch source
|
|
11
|
-
campaign ID is supplied, run campaignless
|
|
9
|
+
campaignOfferId, confirmed: true })` and include that same `campaignOfferId`
|
|
10
|
+
plus `currentStep: "prospeo"` in `search_prospeo` so the user can watch source
|
|
11
|
+
work in the campaign UI. If no campaign ID is supplied, run campaignless
|
|
12
|
+
preview mode. Treat post-mint
|
|
12
13
|
searches with `campaignOfferId` as campaign-attached persisted search tabs;
|
|
13
14
|
do not run a live campaign search without the campaign ID.
|
|
14
15
|
|
|
@@ -7,8 +7,9 @@ Required first step:
|
|
|
7
7
|
- Load the canonical provider prompt before searching. If the parent supplies a
|
|
8
8
|
draft `campaignOfferId`, call `get_provider_prompt({ provider: "sales-nav",
|
|
9
9
|
campaignOfferId, confirmed: true })` and include that same `campaignOfferId` in
|
|
10
|
-
`search_sales_nav` so the user can watch
|
|
11
|
-
campaign ID is supplied, run
|
|
10
|
+
`search_sales_nav` with `currentStep: "sales-nav"` so the user can watch
|
|
11
|
+
source work in the campaign UI. If no campaign ID is supplied, run
|
|
12
|
+
campaignless preview mode. Treat post-mint
|
|
12
13
|
searches with `campaignOfferId` as campaign-attached persisted search tabs;
|
|
13
14
|
do not run a live campaign search without the campaign ID.
|
|
14
15
|
|
package/dist/tools/leads.js
CHANGED
|
@@ -594,7 +594,7 @@ export const leadToolDefinitions = [
|
|
|
594
594
|
},
|
|
595
595
|
{
|
|
596
596
|
name: "search_sales_nav",
|
|
597
|
-
description: 'Search LinkedIn Sales Navigator. Requires get_provider_prompt({ provider: "sales-nav" }) first. Returns normalized results with pagination. `campaignOfferId` is optional for directional preview runs; include it only when the search should be associated with a campaign.',
|
|
597
|
+
description: 'Search LinkedIn Sales Navigator. Requires get_provider_prompt({ provider: "sales-nav" }) first. Returns normalized results with pagination. `campaignOfferId` is optional for directional preview runs; include it only when the search should be associated with a campaign. Post-mint create-campaign-v2 watch runs MUST pass campaignOfferId and currentStep: "sales-nav" so the search appears in the watched campaign UI. Omitting campaignOfferId post-mint orphans the search from the UI.',
|
|
598
598
|
inputSchema: {
|
|
599
599
|
type: "object",
|
|
600
600
|
properties: {
|
|
@@ -739,7 +739,7 @@ export const leadToolDefinitions = [
|
|
|
739
739
|
},
|
|
740
740
|
{
|
|
741
741
|
name: "search_prospeo",
|
|
742
|
-
description: 'Search Prospeo for people using filters and optional domainFilterId. Requires get_provider_prompt({ provider: "prospeo" }) first. When targeting known accounts, call load_csv_domains for CSV-on-disk workflows or save_domain_filters for pasted/raw domain lists, then pass domainFilterId. Raw domain inputs and company-name targeting are NOT supported in this MCP tool. Strategy: start with 2-3 high-signal filters (title/seniority + industry or domainFilterId + headcount), then tighten one filter at a time. For security, AppSec, SOC, RevOps, Demand Gen, and similar function-specific lanes, do not widen with bare seniority labels like "Head" or "Director" alone; pair them with explicit function-title keywords and inspect the sample for off-function `Head of X` leakage. Prefer person location over company HQ unless HQ is explicitly needed. `campaignOfferId` routing rule: OMIT campaignOfferId ONLY in pre-mint Phase 84 `find leads` discovery mode (validating ICP before the commit gate). In every other context — post-mint lead additions, operator-driven searches on a live campaign, any search where the intent is to persist results to a specific campaign — you MUST pass campaignOfferId so the search shows up in that campaign\'s Contact Search panel. Omitting campaignOfferId post-mint orphans the search from the UI. Returns normalized results with pagination.',
|
|
742
|
+
description: 'Search Prospeo for people using filters and optional domainFilterId. Requires get_provider_prompt({ provider: "prospeo" }) first. When targeting known accounts, call load_csv_domains for CSV-on-disk workflows or save_domain_filters for pasted/raw domain lists, then pass domainFilterId. Raw domain inputs and company-name targeting are NOT supported in this MCP tool. Strategy: start with 2-3 high-signal filters (title/seniority + industry or domainFilterId + headcount), then tighten one filter at a time. For security, AppSec, SOC, RevOps, Demand Gen, and similar function-specific lanes, do not widen with bare seniority labels like "Head" or "Director" alone; pair them with explicit function-title keywords and inspect the sample for off-function `Head of X` leakage. Prefer person location over company HQ unless HQ is explicitly needed. `campaignOfferId` routing rule: OMIT campaignOfferId ONLY in pre-mint Phase 84 `find leads` discovery mode (validating ICP before the commit gate). In every other context — post-mint lead additions, operator-driven searches on a live campaign, any search where the intent is to persist results to a specific campaign — you MUST pass campaignOfferId so the search shows up in that campaign\'s Contact Search panel. Post-mint create-campaign-v2 watch runs MUST pass currentStep: "prospeo". Omitting campaignOfferId post-mint orphans the search from the UI. Returns normalized results with pagination.',
|
|
743
743
|
inputSchema: {
|
|
744
744
|
type: "object",
|
|
745
745
|
properties: {
|
|
@@ -848,7 +848,7 @@ export const leadToolDefinitions = [
|
|
|
848
848
|
},
|
|
849
849
|
{
|
|
850
850
|
name: "search_signals",
|
|
851
|
-
description: 'Search LinkedIn posts for signal discovery. Requires get_provider_prompt({ provider: "signal-discovery" }) first. Supports keyword search, profile posts, company posts, or a single post URL. Use campaignless preview mode for directional discovery; include `campaignOfferId`
|
|
851
|
+
description: 'Search LinkedIn posts for signal discovery. Requires get_provider_prompt({ provider: "signal-discovery" }) first. Supports keyword search, profile posts, company posts, or a single post URL. Use campaignless preview mode for directional discovery; include `campaignOfferId` when the search should persist into campaign state. Post-mint create-campaign-v2 watch runs MUST pass campaignOfferId and currentStep: "signal-discovery" so the search appears in the watched campaign UI. Omitting campaignOfferId post-mint orphans the search from the UI. Returns a compact summary with recommended posts to avoid context bloat.',
|
|
852
852
|
inputSchema: {
|
|
853
853
|
type: "object",
|
|
854
854
|
properties: {
|
|
@@ -2366,6 +2366,15 @@ export async function selectPromisingPosts(input) {
|
|
|
2366
2366
|
headlineICPCriteria,
|
|
2367
2367
|
rubricGuidelines: headlineICPCriteria,
|
|
2368
2368
|
});
|
|
2369
|
+
if (selectionResult.selectedCount <= 0) {
|
|
2370
|
+
return {
|
|
2371
|
+
success: false,
|
|
2372
|
+
selectedCount: selectionResult.selectedCount,
|
|
2373
|
+
unselectedCount: selectionResult.unselectedCount,
|
|
2374
|
+
criteriaCount: selectionResult.criteriaCount,
|
|
2375
|
+
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.",
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2369
2378
|
// Update currentStep if provided (via v2 endpoint)
|
|
2370
2379
|
if (currentStep) {
|
|
2371
2380
|
await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
|
package/dist/tools/rubrics.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ type WaitForRubricResultsInput = {
|
|
|
32
32
|
campaignOfferId?: string;
|
|
33
33
|
tableId?: string;
|
|
34
34
|
targetCount?: number;
|
|
35
|
+
minPassedCount?: number;
|
|
35
36
|
timeoutMs?: number;
|
|
36
37
|
intervalMs?: number;
|
|
37
38
|
includeRows?: boolean;
|
|
@@ -109,6 +110,7 @@ export declare const rubricToolDefinitions: ({
|
|
|
109
110
|
maxProspects?: undefined;
|
|
110
111
|
tableId?: undefined;
|
|
111
112
|
targetCount?: undefined;
|
|
113
|
+
minPassedCount?: undefined;
|
|
112
114
|
timeoutMs?: undefined;
|
|
113
115
|
intervalMs?: undefined;
|
|
114
116
|
includeRows?: undefined;
|
|
@@ -141,6 +143,7 @@ export declare const rubricToolDefinitions: ({
|
|
|
141
143
|
maxProspects?: undefined;
|
|
142
144
|
tableId?: undefined;
|
|
143
145
|
targetCount?: undefined;
|
|
146
|
+
minPassedCount?: undefined;
|
|
144
147
|
timeoutMs?: undefined;
|
|
145
148
|
intervalMs?: undefined;
|
|
146
149
|
includeRows?: undefined;
|
|
@@ -198,6 +201,7 @@ export declare const rubricToolDefinitions: ({
|
|
|
198
201
|
maxProspects?: undefined;
|
|
199
202
|
tableId?: undefined;
|
|
200
203
|
targetCount?: undefined;
|
|
204
|
+
minPassedCount?: undefined;
|
|
201
205
|
timeoutMs?: undefined;
|
|
202
206
|
intervalMs?: undefined;
|
|
203
207
|
includeRows?: undefined;
|
|
@@ -255,6 +259,7 @@ export declare const rubricToolDefinitions: ({
|
|
|
255
259
|
maxProspects?: undefined;
|
|
256
260
|
tableId?: undefined;
|
|
257
261
|
targetCount?: undefined;
|
|
262
|
+
minPassedCount?: undefined;
|
|
258
263
|
timeoutMs?: undefined;
|
|
259
264
|
intervalMs?: undefined;
|
|
260
265
|
includeRows?: undefined;
|
|
@@ -314,6 +319,7 @@ export declare const rubricToolDefinitions: ({
|
|
|
314
319
|
maxProspects?: undefined;
|
|
315
320
|
tableId?: undefined;
|
|
316
321
|
targetCount?: undefined;
|
|
322
|
+
minPassedCount?: undefined;
|
|
317
323
|
timeoutMs?: undefined;
|
|
318
324
|
intervalMs?: undefined;
|
|
319
325
|
includeRows?: undefined;
|
|
@@ -343,6 +349,7 @@ export declare const rubricToolDefinitions: ({
|
|
|
343
349
|
maxProspects?: undefined;
|
|
344
350
|
tableId?: undefined;
|
|
345
351
|
targetCount?: undefined;
|
|
352
|
+
minPassedCount?: undefined;
|
|
346
353
|
timeoutMs?: undefined;
|
|
347
354
|
intervalMs?: undefined;
|
|
348
355
|
includeRows?: undefined;
|
|
@@ -372,6 +379,7 @@ export declare const rubricToolDefinitions: ({
|
|
|
372
379
|
checkName?: undefined;
|
|
373
380
|
tableId?: undefined;
|
|
374
381
|
targetCount?: undefined;
|
|
382
|
+
minPassedCount?: undefined;
|
|
375
383
|
timeoutMs?: undefined;
|
|
376
384
|
intervalMs?: undefined;
|
|
377
385
|
includeRows?: undefined;
|
|
@@ -397,6 +405,10 @@ export declare const rubricToolDefinitions: ({
|
|
|
397
405
|
type: string;
|
|
398
406
|
description: string;
|
|
399
407
|
};
|
|
408
|
+
minPassedCount: {
|
|
409
|
+
type: string;
|
|
410
|
+
description: string;
|
|
411
|
+
};
|
|
400
412
|
timeoutMs: {
|
|
401
413
|
type: string;
|
|
402
414
|
description: string;
|
|
@@ -496,6 +508,7 @@ export declare function waitForRubricResults(input: WaitForRubricResultsInput):
|
|
|
496
508
|
elapsedMs: number;
|
|
497
509
|
tableId: string;
|
|
498
510
|
passRate: {
|
|
511
|
+
minPassedCount?: number | undefined;
|
|
499
512
|
completed: number;
|
|
500
513
|
passed: number;
|
|
501
514
|
percent: number;
|
|
@@ -507,7 +520,10 @@ export declare function waitForRubricResults(input: WaitForRubricResultsInput):
|
|
|
507
520
|
diagnostic?: undefined;
|
|
508
521
|
guidance?: undefined;
|
|
509
522
|
} | {
|
|
523
|
+
stats: WorkflowTableStats;
|
|
524
|
+
rows?: import("./rows.js").LightweightRow[] | undefined;
|
|
510
525
|
ready: boolean;
|
|
526
|
+
partial: boolean;
|
|
511
527
|
reason: string;
|
|
512
528
|
attempts: number;
|
|
513
529
|
elapsedMs: number;
|
|
@@ -518,6 +534,7 @@ export declare function waitForRubricResults(input: WaitForRubricResultsInput):
|
|
|
518
534
|
pending: number;
|
|
519
535
|
percent: number;
|
|
520
536
|
targetCount: number;
|
|
537
|
+
minPassedCount: number;
|
|
521
538
|
};
|
|
522
539
|
partialResult: {
|
|
523
540
|
completed: number;
|
|
@@ -525,7 +542,35 @@ export declare function waitForRubricResults(input: WaitForRubricResultsInput):
|
|
|
525
542
|
pending: number;
|
|
526
543
|
percent: number;
|
|
527
544
|
targetCount: number;
|
|
545
|
+
minPassedCount: number;
|
|
546
|
+
enoughToDiagnose: boolean;
|
|
547
|
+
floorMet: boolean;
|
|
548
|
+
};
|
|
549
|
+
diagnostic?: undefined;
|
|
550
|
+
guidance?: undefined;
|
|
551
|
+
} | {
|
|
552
|
+
ready: boolean;
|
|
553
|
+
reason: string;
|
|
554
|
+
attempts: number;
|
|
555
|
+
elapsedMs: number;
|
|
556
|
+
tableId: string;
|
|
557
|
+
passRate: {
|
|
558
|
+
minPassedCount?: number | undefined;
|
|
559
|
+
completed: number;
|
|
560
|
+
passed: number;
|
|
561
|
+
pending: number;
|
|
562
|
+
percent: number;
|
|
563
|
+
targetCount: number;
|
|
564
|
+
};
|
|
565
|
+
partialResult: {
|
|
528
566
|
enoughToDiagnose: boolean;
|
|
567
|
+
floorMet: boolean;
|
|
568
|
+
minPassedCount?: number | undefined;
|
|
569
|
+
completed: number;
|
|
570
|
+
passed: number;
|
|
571
|
+
pending: number;
|
|
572
|
+
percent: number;
|
|
573
|
+
targetCount: number;
|
|
529
574
|
};
|
|
530
575
|
diagnostic: {
|
|
531
576
|
totalRows: number;
|
package/dist/tools/rubrics.js
CHANGED
|
@@ -103,6 +103,11 @@ function resolveMaxProspects(value) {
|
|
|
103
103
|
const parsed = typeof value === "number" && !Number.isNaN(value) ? value : fallback;
|
|
104
104
|
return Math.min(MAX_PROSPECTS_LIMIT, Math.max(1, Math.floor(parsed)));
|
|
105
105
|
}
|
|
106
|
+
function resolveMinPassedCount(value) {
|
|
107
|
+
if (typeof value !== "number" || Number.isNaN(value))
|
|
108
|
+
return null;
|
|
109
|
+
return Math.min(MAX_PROSPECTS_LIMIT, Math.max(1, Math.floor(value)));
|
|
110
|
+
}
|
|
106
111
|
function mergeRubricsWithExisting(draftRubrics, existingRubrics) {
|
|
107
112
|
const existingByCheckName = new Map();
|
|
108
113
|
existingRubrics.forEach((item) => {
|
|
@@ -348,6 +353,10 @@ export const rubricToolDefinitions = [
|
|
|
348
353
|
type: "number",
|
|
349
354
|
description: `Number of completed rubric results to wait for (default ${DEFAULT_MAX_PROSPECTS}).`,
|
|
350
355
|
},
|
|
356
|
+
minPassedCount: {
|
|
357
|
+
type: "number",
|
|
358
|
+
description: "Optional pass floor for bounded create-campaign samples. When this floor is met and remaining target rows are resolved as failed/not processing, the tool returns ready:true with partial:true instead of forcing a timeout.",
|
|
359
|
+
},
|
|
351
360
|
timeoutMs: {
|
|
352
361
|
type: "number",
|
|
353
362
|
description: `Max time to wait in ms (default ${DEFAULT_TIMEOUT_MS}).`,
|
|
@@ -625,6 +634,7 @@ export async function waitForRubricResults(input) {
|
|
|
625
634
|
const timeoutMs = Math.max(5000, input.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
626
635
|
const intervalMs = Math.max(500, input.intervalMs ?? DEFAULT_INTERVAL_MS);
|
|
627
636
|
const targetCount = resolveMaxProspects(input.targetCount);
|
|
637
|
+
const minPassedCount = resolveMinPassedCount(input.minPassedCount);
|
|
628
638
|
const includeRows = input.includeRows !== false;
|
|
629
639
|
let tableId = input.tableId;
|
|
630
640
|
if (!tableId && input.campaignOfferId) {
|
|
@@ -642,10 +652,17 @@ export async function waitForRubricResults(input) {
|
|
|
642
652
|
const stats = await api.get(`/api/v3/workflow-tables/${tableId}/stats`);
|
|
643
653
|
lastStats = stats;
|
|
644
654
|
const completed = stats.passRate?.completed ?? 0;
|
|
655
|
+
const passed = stats.passRate?.passed ?? 0;
|
|
645
656
|
const totalRows = stats.totalRows ?? 0;
|
|
646
657
|
const effectiveTarget = totalRows > 0 ? Math.min(targetCount, totalRows) : targetCount;
|
|
658
|
+
const pending = Math.max(effectiveTarget - completed, 0);
|
|
659
|
+
const failedCount = stats.failedCount ?? 0;
|
|
660
|
+
const processingCount = stats.processingCount ?? 0;
|
|
661
|
+
const needsEnrichCount = stats.needsEnrichCount ?? 0;
|
|
662
|
+
const minPassFloorMet = minPassedCount !== null && passed >= minPassedCount;
|
|
663
|
+
const unresolvedRowsResolvedAsFailures = pending > 0 && completed + failedCount >= effectiveTarget;
|
|
664
|
+
const noActiveProcessing = processingCount === 0 && needsEnrichCount === 0;
|
|
647
665
|
if (completed >= effectiveTarget) {
|
|
648
|
-
const passed = stats.passRate?.passed ?? 0;
|
|
649
666
|
const percent = completed > 0 ? Math.round((passed / completed) * 100) : 0;
|
|
650
667
|
const rowSnapshot = includeRows
|
|
651
668
|
? await getTableRowsMinimal(tableId, {
|
|
@@ -662,6 +679,45 @@ export async function waitForRubricResults(input) {
|
|
|
662
679
|
passed,
|
|
663
680
|
percent,
|
|
664
681
|
targetCount: effectiveTarget,
|
|
682
|
+
...(minPassedCount !== null ? { minPassedCount } : {}),
|
|
683
|
+
},
|
|
684
|
+
...(rowSnapshot ? { rows: rowSnapshot.rows } : {}),
|
|
685
|
+
stats,
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
if (minPassFloorMet &&
|
|
689
|
+
unresolvedRowsResolvedAsFailures &&
|
|
690
|
+
noActiveProcessing) {
|
|
691
|
+
const percent = completed > 0 ? Math.round((passed / completed) * 100) : 0;
|
|
692
|
+
const rowSnapshot = includeRows
|
|
693
|
+
? await getTableRowsMinimal(tableId, {
|
|
694
|
+
limit: effectiveTarget,
|
|
695
|
+
})
|
|
696
|
+
: null;
|
|
697
|
+
return {
|
|
698
|
+
ready: true,
|
|
699
|
+
partial: true,
|
|
700
|
+
reason: "min_passed_count_met_with_resolved_failures",
|
|
701
|
+
attempts,
|
|
702
|
+
elapsedMs: Date.now() - start,
|
|
703
|
+
tableId,
|
|
704
|
+
passRate: {
|
|
705
|
+
completed,
|
|
706
|
+
passed,
|
|
707
|
+
pending,
|
|
708
|
+
percent,
|
|
709
|
+
targetCount: effectiveTarget,
|
|
710
|
+
minPassedCount,
|
|
711
|
+
},
|
|
712
|
+
partialResult: {
|
|
713
|
+
completed,
|
|
714
|
+
passed,
|
|
715
|
+
pending,
|
|
716
|
+
percent,
|
|
717
|
+
targetCount: effectiveTarget,
|
|
718
|
+
minPassedCount,
|
|
719
|
+
enoughToDiagnose: true,
|
|
720
|
+
floorMet: true,
|
|
665
721
|
},
|
|
666
722
|
...(rowSnapshot ? { rows: rowSnapshot.rows } : {}),
|
|
667
723
|
stats,
|
|
@@ -687,6 +743,7 @@ export async function waitForRubricResults(input) {
|
|
|
687
743
|
pending,
|
|
688
744
|
percent,
|
|
689
745
|
targetCount: effectiveTarget,
|
|
746
|
+
...(minPassedCount !== null ? { minPassedCount } : {}),
|
|
690
747
|
},
|
|
691
748
|
partialResult: {
|
|
692
749
|
completed,
|
|
@@ -694,7 +751,9 @@ export async function waitForRubricResults(input) {
|
|
|
694
751
|
pending,
|
|
695
752
|
percent,
|
|
696
753
|
targetCount: effectiveTarget,
|
|
754
|
+
...(minPassedCount !== null ? { minPassedCount } : {}),
|
|
697
755
|
enoughToDiagnose: completed > 0,
|
|
756
|
+
floorMet: minPassedCount !== null && passed >= minPassedCount,
|
|
698
757
|
},
|
|
699
758
|
diagnostic: {
|
|
700
759
|
totalRows,
|
package/package.json
CHANGED
|
@@ -432,6 +432,13 @@ to be worth a LinkedIn test. I'll compare source paths by expected volume,
|
|
|
432
432
|
sampled ICP fit, activity/warmth signal, cleanup risk, and tradeoffs. This
|
|
433
433
|
usually takes ~3-5 min, and I'll show you the source decision + sample before
|
|
434
434
|
anything goes live.`
|
|
435
|
+
- In watch mode, do not leave the user sitting on only `pick-provider` while
|
|
436
|
+
source scouts run. Move the campaign to the likely primary source lane
|
|
437
|
+
(`signal-discovery`, `sales-nav`, or `prospeo`) before background source
|
|
438
|
+
scouts, then run the first campaign-attached provider prompt + provider search
|
|
439
|
+
in the parent thread with `campaignOfferId`, `confirmed: true`, and
|
|
440
|
+
`currentStep` when the tool accepts it. Background scouts may continue after
|
|
441
|
+
the visible campaign tab exists.
|
|
435
442
|
- After the lead sample/source decision is ready and approved,
|
|
436
443
|
show the next progress line:
|
|
437
444
|
`Lead source is set. I'll import the first 15-row review batch into the
|
|
@@ -542,6 +549,11 @@ message we should test.`
|
|
|
542
549
|
then draft the message from the same sample.` Never say `kicking off two
|
|
543
550
|
workstreams`, `in parallel`, or `background` unless parallel branches were
|
|
544
551
|
actually launched.
|
|
552
|
+
- Source scout parallelism must not hide all provider work from the watch UI.
|
|
553
|
+
The parent thread owns the first visible provider search: call the chosen
|
|
554
|
+
provider prompt/search with the minted CampaignOffer id and provider
|
|
555
|
+
`currentStep` before waiting on source-scout results, so relevant search tabs
|
|
556
|
+
appear in real time.
|
|
545
557
|
- For post-lead workstreams, first call
|
|
546
558
|
`get_post_find_leads_scout_registry` and launch exactly the returned
|
|
547
559
|
`filter-leads` and `message-generation` scouts when real subagents are
|
|
@@ -520,6 +520,33 @@
|
|
|
520
520
|
"when": "before_source_scouts_or_provider_search",
|
|
521
521
|
"chatRenderRule": "Move the campaign watch view to Find Contacts before the main thread starts comparing source paths. Do not mention MCP or local artifacts."
|
|
522
522
|
},
|
|
523
|
+
{
|
|
524
|
+
"action": "advance_watch_to_initial_source_lane",
|
|
525
|
+
"tool": "update_campaign",
|
|
526
|
+
"requiredFields": [
|
|
527
|
+
"campaignId",
|
|
528
|
+
"currentStep"
|
|
529
|
+
],
|
|
530
|
+
"currentStepByPrimaryLane": {
|
|
531
|
+
"signals": "signal-discovery",
|
|
532
|
+
"salesNav": "sales-nav",
|
|
533
|
+
"prospeo": "prospeo",
|
|
534
|
+
"apollo": "apollo-ai",
|
|
535
|
+
"existingList": "saved-lists",
|
|
536
|
+
"uploadedDomains": "prospeo"
|
|
537
|
+
},
|
|
538
|
+
"when": "before_background_source_scouts",
|
|
539
|
+
"rule": "Choose the likely primary visible source lane from source intake, brief preference, or the best first lane the main thread will actually search. Do this before waiting on background scouts so watch mode never sits on only Pick Provider while source work happens elsewhere."
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
"action": "run_first_campaign_attached_source_search",
|
|
543
|
+
"requiredFields": [
|
|
544
|
+
"campaignOfferId",
|
|
545
|
+
"currentStep"
|
|
546
|
+
],
|
|
547
|
+
"before": "waiting_on_background_source_scouts",
|
|
548
|
+
"rule": "Before relying on background source scouts, the parent thread must run the first provider prompt + provider search for the chosen visible lane with campaignOfferId and currentStep (signal-discovery, sales-nav, or prospeo) so the campaign UI creates the corresponding live search/tab. Background scouts may continue after that, but the user must see at least one relevant campaign-attached search begin in the watched page."
|
|
549
|
+
},
|
|
523
550
|
{
|
|
524
551
|
"action": "render_find_leads_progress",
|
|
525
552
|
"requiredVisibleContent": [
|
|
@@ -555,7 +582,7 @@
|
|
|
555
582
|
"action": "run_subskill",
|
|
556
583
|
"target": "find-leads",
|
|
557
584
|
"mode": "campaign-attached-required",
|
|
558
|
-
"sourceScoutRule": "Shell-first flow requires the CampaignOffer campaignId from durable state. Pass campaignId as campaignOfferId into every provider prompt/search that can persist source state (`get_provider_prompt({ provider, campaignOfferId, confirmed: true })`, `search_signals`, `search_sales_nav`, `search_prospeo`) so the user can watch the selected source inside the campaign. The later import_leads call must use the same campaignOfferId. When several source lanes are credible, scout them independently, then write one primary source recommendation and any runner-up tradeoffs. Do not import, confirm, enrich, queue, or start leads during source discovery."
|
|
585
|
+
"sourceScoutRule": "Shell-first flow requires the CampaignOffer campaignId from durable state. Pass campaignId as campaignOfferId into every provider prompt/search that can persist source state (`get_provider_prompt({ provider, campaignOfferId, confirmed: true })`, `search_signals`, `search_sales_nav`, `search_prospeo`) and include currentStep for tools that accept it so the user can watch the selected source inside the campaign. In watch mode, run the first campaign-attached provider search in the parent thread before waiting on background scouts; subagents can explore runner-up lanes after the visible campaign tab exists. The later import_leads call must use the same campaignOfferId. When several source lanes are credible, scout them independently, then write one primary source recommendation and any runner-up tradeoffs. Do not import, confirm, enrich, queue, or start leads during source discovery."
|
|
559
586
|
},
|
|
560
587
|
{
|
|
561
588
|
"action": "optional_debug_artifacts",
|
|
@@ -1916,17 +1943,23 @@
|
|
|
1916
1943
|
{
|
|
1917
1944
|
"tool": "wait_for_rubric_results",
|
|
1918
1945
|
"requiredFields": [
|
|
1919
|
-
"targetCount"
|
|
1946
|
+
"targetCount",
|
|
1947
|
+
"minPassedCount"
|
|
1920
1948
|
],
|
|
1921
1949
|
"targetCountSource": "stats.totalRows_or_imported_batch_count",
|
|
1950
|
+
"minPassedCountSource": "sample.minProjectedPass (3)",
|
|
1922
1951
|
"requiredValues": {
|
|
1923
1952
|
"includeRows": false
|
|
1924
1953
|
},
|
|
1925
|
-
"note": "The shell-first flow tests 15 leads first; always pass cohortSize explicitly instead of relying on default 25 behavior.",
|
|
1954
|
+
"note": "The shell-first flow tests 15 leads first; always pass cohortSize explicitly instead of relying on default 25 behavior. Pass minPassedCount so a 15-row batch with resolved failures can advance when the 3-pass floor is already met.",
|
|
1926
1955
|
"readVia": "stats_only_tool_result",
|
|
1927
1956
|
"extractFields": [
|
|
1928
1957
|
"ready",
|
|
1958
|
+
"partial",
|
|
1959
|
+
"reason",
|
|
1929
1960
|
"passRate.completed",
|
|
1961
|
+
"passRate.passed",
|
|
1962
|
+
"passRate.minPassedCount",
|
|
1930
1963
|
"stats"
|
|
1931
1964
|
],
|
|
1932
1965
|
"doNotRetain": "rows_payload",
|
|
@@ -224,6 +224,17 @@ campaignOfferId, confirmed: true })` -> `search_signals({ campaignOfferId,
|
|
|
224
224
|
selectionMode: "replace", selections, headlineICPCriteria })` ->
|
|
225
225
|
`import_leads({ campaignOfferId, provider: "signal-discovery",
|
|
226
226
|
targetLeadCount: importLimit })`.
|
|
227
|
+
For Signal Discovery, the promotion/select step is load-bearing. Use
|
|
228
|
+
post IDs from the current campaign-scoped `search_signals` response or
|
|
229
|
+
posts the user has visibly promoted in the campaign UI. Never use post IDs
|
|
230
|
+
copied only from a source-scout summary unless they have been re-resolved
|
|
231
|
+
through the current campaign search state. After `select_promising_posts`,
|
|
232
|
+
require `selectedCount > 0` before calling `import_leads`. If it returns
|
|
233
|
+
`selectedCount: 0`, do not switch providers and do not retry import.
|
|
234
|
+
Explain that the campaign has no promoted Signal Discovery posts yet,
|
|
235
|
+
re-run a narrow campaign-scoped `search_signals` call to recover current
|
|
236
|
+
post rows, or ask the user to promote the desired posts in the UI and then
|
|
237
|
+
retry `import_leads`.
|
|
227
238
|
Source approval is the explicit confirmation for this bounded review
|
|
228
239
|
batch; do not ask for a second yes/no gate between
|
|
229
240
|
`select_promising_posts` and `import_leads`.
|
|
@@ -275,11 +286,14 @@ Shape:
|
|
|
275
286
|
queue_cells({ tableId: workflowTableId, cellIds: reviewBatchEnrichCellIds })
|
|
276
287
|
wait_for_campaign_table_ready({ tableId: workflowTableId })
|
|
277
288
|
get_rows_minimal({ tableId: workflowTableId })
|
|
278
|
-
wait_for_rubric_results({ tableId: workflowTableId, targetCount: cohortSize, includeRows: false })
|
|
289
|
+
wait_for_rubric_results({ tableId: workflowTableId, targetCount: cohortSize, minPassedCount: minProjectedPass, includeRows: false })
|
|
279
290
|
passInSample = count of first sampleSize review-batch rows with passesRubric === true
|
|
280
291
|
projectedPass = round(passInSample / sampleSize * importLimit)
|
|
281
292
|
|
|
282
|
-
if wait_for_rubric_results.ready ===
|
|
293
|
+
if wait_for_rubric_results.ready === true and partial === true and reason === "min_passed_count_met_with_resolved_failures":
|
|
294
|
+
use the partial passRate/stats as a valid sample diagnostic
|
|
295
|
+
continue if projectedPass >= minProjectedPass and generated messages are present for the passing rows
|
|
296
|
+
else if wait_for_rubric_results.ready === false and reason === "timeout":
|
|
283
297
|
use the partial passRate/stats as the sample diagnostic
|
|
284
298
|
if active processing is visible:
|
|
285
299
|
wait one more time at most
|
|
@@ -377,6 +377,14 @@ selections, headlineICPCriteria })`, then call
|
|
|
377
377
|
targetLeadCount })` for the approved review batch without asking for another
|
|
378
378
|
yes/no gate. Do not import more than the approved bounded batch.
|
|
379
379
|
|
|
380
|
+
The promotion/select step is required campaign state. Use post IDs from the
|
|
381
|
+
current campaign-scoped search result, not stale IDs copied from a source review
|
|
382
|
+
or scout summary. If `select_promising_posts` returns `selectedCount: 0`, stop:
|
|
383
|
+
do not call `import_leads`, do not switch providers automatically, and do not
|
|
384
|
+
claim the source failed. Re-run a narrow campaign-scoped search to recover
|
|
385
|
+
current post rows, or have the user promote the posts in the UI and then retry
|
|
386
|
+
the bounded import.
|
|
387
|
+
|
|
380
388
|
```json
|
|
381
389
|
import_leads({
|
|
382
390
|
"campaignOfferId": "cmp_xxx",
|