@sellable/mcp 0.1.111 → 0.1.112

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.
@@ -9,6 +9,7 @@ declare const LEAD_SOURCE_PROVIDERS: {
9
9
  };
10
10
  type LeadSourceProvider = (typeof LEAD_SOURCE_PROVIDERS)[keyof typeof LEAD_SOURCE_PROVIDERS];
11
11
  export declare function buildWatchUrl(config: Pick<ReturnType<typeof getConfig>, "apiUrl" | "token" | "activeWorkspaceId" | "workspaceId">, path: string): string;
12
+ export declare function getCampaignBuilderWatchModeParam(): "claude" | "codex";
12
13
  export interface Campaign {
13
14
  id: string;
14
15
  name: string;
@@ -124,6 +125,7 @@ export interface CreateCampaignInput {
124
125
  campaignBrief?: string;
125
126
  messageGenerationMode?: "template" | "ai-generated";
126
127
  currentStep?: string | null;
128
+ watchNarration?: unknown;
127
129
  leadSourceType?: string | null;
128
130
  leadSourceProvider?: LeadSourceProvider | "apollo" | null;
129
131
  selectedLeadListId?: string | null;
@@ -137,6 +139,7 @@ export interface UpdateCampaignInput {
137
139
  leadSourceProvider?: LeadSourceProvider | "apollo" | null;
138
140
  selectedLeadListId?: string | null;
139
141
  currentStep?: string | null;
142
+ watchNarration?: unknown;
140
143
  interactionMode?: InteractionMode;
141
144
  enableICPFilters?: boolean | null;
142
145
  useMessagingTemplate?: boolean | null;
@@ -165,6 +168,7 @@ export declare const campaignToolDefinitions: ({
165
168
  campaignBrief?: undefined;
166
169
  messageGenerationMode?: undefined;
167
170
  currentStep?: undefined;
171
+ watchNarration?: undefined;
168
172
  leadSourceType?: undefined;
169
173
  leadSourceProvider?: undefined;
170
174
  selectedLeadListId?: undefined;
@@ -200,6 +204,7 @@ export declare const campaignToolDefinitions: ({
200
204
  campaignBrief?: undefined;
201
205
  messageGenerationMode?: undefined;
202
206
  currentStep?: undefined;
207
+ watchNarration?: undefined;
203
208
  leadSourceType?: undefined;
204
209
  leadSourceProvider?: undefined;
205
210
  selectedLeadListId?: undefined;
@@ -278,6 +283,7 @@ export declare const campaignToolDefinitions: ({
278
283
  campaignBrief?: undefined;
279
284
  messageGenerationMode?: undefined;
280
285
  currentStep?: undefined;
286
+ watchNarration?: undefined;
281
287
  leadSourceType?: undefined;
282
288
  leadSourceProvider?: undefined;
283
289
  selectedLeadListId?: undefined;
@@ -330,6 +336,41 @@ export declare const campaignToolDefinitions: ({
330
336
  type: string[];
331
337
  description: string;
332
338
  };
339
+ watchNarration: {
340
+ type: string;
341
+ description: string;
342
+ properties: {
343
+ stage: {
344
+ type: string;
345
+ enum: string[];
346
+ };
347
+ headline: {
348
+ type: string;
349
+ };
350
+ visibleState: {
351
+ type: string;
352
+ };
353
+ agentIntent: {
354
+ type: string;
355
+ };
356
+ nextAction: {
357
+ type: string[];
358
+ };
359
+ safety: {
360
+ type: string[];
361
+ };
362
+ progressLabel: {
363
+ type: string[];
364
+ };
365
+ blockedReason: {
366
+ type: string[];
367
+ };
368
+ workerStatuses: {
369
+ type: string;
370
+ };
371
+ };
372
+ required: string[];
373
+ };
333
374
  leadSourceType: {
334
375
  type: string[];
335
376
  description: string;
@@ -399,6 +440,41 @@ export declare const campaignToolDefinitions: ({
399
440
  type: string[];
400
441
  description: string;
401
442
  };
443
+ watchNarration: {
444
+ type: string;
445
+ description: string;
446
+ properties: {
447
+ stage: {
448
+ type: string;
449
+ enum: string[];
450
+ };
451
+ headline: {
452
+ type: string;
453
+ };
454
+ visibleState: {
455
+ type: string;
456
+ };
457
+ agentIntent: {
458
+ type: string;
459
+ };
460
+ nextAction: {
461
+ type: string[];
462
+ };
463
+ safety: {
464
+ type: string[];
465
+ };
466
+ progressLabel: {
467
+ type: string[];
468
+ };
469
+ blockedReason: {
470
+ type: string[];
471
+ };
472
+ workerStatuses: {
473
+ type: string;
474
+ };
475
+ };
476
+ required: string[];
477
+ };
402
478
  interactionMode: {
403
479
  type: string;
404
480
  enum: string[];
@@ -466,6 +542,7 @@ export declare const campaignToolDefinitions: ({
466
542
  offerPositioning?: undefined;
467
543
  messageGenerationMode?: undefined;
468
544
  currentStep?: undefined;
545
+ watchNarration?: undefined;
469
546
  leadSourceType?: undefined;
470
547
  leadSourceProvider?: undefined;
471
548
  selectedLeadListId?: undefined;
@@ -27,6 +27,15 @@ export function buildWatchUrl(config, path) {
27
27
  }
28
28
  return url.toString();
29
29
  }
30
+ export function getCampaignBuilderWatchModeParam() {
31
+ const explicit = process.env.SELLABLE_WATCH_MODE_DRIVER?.trim().toLowerCase();
32
+ if (explicit === "claude" || explicit === "codex")
33
+ return explicit;
34
+ return process.env.CODEX_HOME ? "codex" : "claude";
35
+ }
36
+ function buildCampaignBuilderWatchPath(campaignId) {
37
+ return `/campaign-builder/${campaignId}?mode=${getCampaignBuilderWatchModeParam()}`;
38
+ }
30
39
  function isLinkedInProfileUrl(input) {
31
40
  try {
32
41
  const url = new URL(input);
@@ -176,6 +185,31 @@ export const campaignToolDefinitions = [
176
185
  type: ["string", "null"],
177
186
  description: "Workflow step ID (headless or UI step ID such as filter-rules)",
178
187
  },
188
+ watchNarration: {
189
+ type: "object",
190
+ description: "Single structured watch-guide narration object to send with currentStep changes. Include stage, headline, visibleState, agentIntent, and nextAction so the browser guide mirrors Codex progress.",
191
+ properties: {
192
+ stage: {
193
+ type: "string",
194
+ enum: [
195
+ "brief-review",
196
+ "find-leads",
197
+ "review-batch",
198
+ "fit-message",
199
+ "review-ready",
200
+ ],
201
+ },
202
+ headline: { type: "string" },
203
+ visibleState: { type: "string" },
204
+ agentIntent: { type: "string" },
205
+ nextAction: { type: ["string", "null"] },
206
+ safety: { type: ["string", "null"] },
207
+ progressLabel: { type: ["string", "null"] },
208
+ blockedReason: { type: ["string", "null"] },
209
+ workerStatuses: { type: "object" },
210
+ },
211
+ required: ["stage", "headline", "visibleState", "agentIntent"],
212
+ },
179
213
  leadSourceType: {
180
214
  type: ["string", "null"],
181
215
  description: "Lead source type (existing | new)",
@@ -244,10 +278,35 @@ export const campaignToolDefinitions = [
244
278
  type: ["string", "null"],
245
279
  description: "Headless workflow step ID",
246
280
  },
281
+ watchNarration: {
282
+ type: "object",
283
+ description: "Single structured watch-guide narration object to send with currentStep changes. Include stage, headline, visibleState, agentIntent, and nextAction so the browser guide mirrors Codex progress. Do not send separate guideHeadline/guideBody fields.",
284
+ properties: {
285
+ stage: {
286
+ type: "string",
287
+ enum: [
288
+ "brief-review",
289
+ "find-leads",
290
+ "review-batch",
291
+ "fit-message",
292
+ "review-ready",
293
+ ],
294
+ },
295
+ headline: { type: "string" },
296
+ visibleState: { type: "string" },
297
+ agentIntent: { type: "string" },
298
+ nextAction: { type: ["string", "null"] },
299
+ safety: { type: ["string", "null"] },
300
+ progressLabel: { type: ["string", "null"] },
301
+ blockedReason: { type: ["string", "null"] },
302
+ workerStatuses: { type: "object" },
303
+ },
304
+ required: ["stage", "headline", "visibleState", "agentIntent"],
305
+ },
247
306
  interactionMode: {
248
307
  type: "string",
249
308
  enum: ["step-by-step", "autonomous", "ask-when-needed"],
250
- description: "Execution style for lead sourcing steps (step-by-step | autonomous | ask-when-needed).",
309
+ description: "Execution style for Find Leads steps (step-by-step | autonomous | ask-when-needed).",
251
310
  },
252
311
  enableICPFilters: {
253
312
  type: ["boolean", "null"],
@@ -335,7 +394,7 @@ export async function getCampaign(campaignId) {
335
394
  api.get(`/api/v3/mcp/campaigns/${campaignId}`),
336
395
  fetchCampaignRubrics(campaignId).catch(() => null),
337
396
  ]);
338
- const watchUrl = buildWatchUrl(config, `/campaign-builder/${campaignId}?mode=claude`);
397
+ const watchUrl = buildWatchUrl(config, buildCampaignBuilderWatchPath(campaignId));
339
398
  // Merge rubrics into campaignOffer
340
399
  const rubrics = (rubricsResult?.rubrics || []).map((r) => ({
341
400
  id: r.id || "",
@@ -590,7 +649,7 @@ export async function createCampaign(input) {
590
649
  // Idempotent resume path
591
650
  if (input.campaignId) {
592
651
  const existing = await api.get(`/api/v2/campaign-offers/${input.campaignId}`);
593
- const watchUrl = buildWatchUrl(config, `/campaign-builder/${existing.id}?mode=claude`);
652
+ const watchUrl = buildWatchUrl(config, buildCampaignBuilderWatchPath(existing.id));
594
653
  const hasCreateFields = input.name ||
595
654
  input.clientProspectId ||
596
655
  input.offerPositioning !== undefined ||
@@ -708,7 +767,7 @@ export async function createCampaign(input) {
708
767
  },
709
768
  };
710
769
  const result = await api.post(`/api/v2/campaign-offers`, formattedInput);
711
- const watchUrl = buildWatchUrl(config, `/campaign-builder/${result.id}?mode=claude`);
770
+ const watchUrl = buildWatchUrl(config, buildCampaignBuilderWatchPath(result.id));
712
771
  // Serialize to only essential fields for context efficiency
713
772
  return {
714
773
  id: result.id,
@@ -415,8 +415,8 @@ function parseWatchUrl(watchUrl, campaignId) {
415
415
  direct: false,
416
416
  redirectPath: decodedRedirect,
417
417
  warning: isLegacySigned
418
- ? "Watch URL still uses a tokenized /auth/continue handoff. Return the safe direct /campaign-builder/{campaignId}?mode=claude watch URL instead."
419
- : "Watch URL is not a safe direct /campaign-builder/{campaignId}?mode=claude watch link.",
418
+ ? "Watch URL still uses a tokenized /auth/continue handoff. Return the safe direct /campaign-builder/{campaignId}?mode={claude|codex} watch URL instead."
419
+ : "Watch URL is not a safe direct /campaign-builder/{campaignId}?mode={claude|codex} watch link.",
420
420
  };
421
421
  }
422
422
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.111",
3
+ "version": "0.1.112",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -165,7 +165,7 @@ Optional debug/UAT draft directory, disabled in normal customer runs:
165
165
  ask in chat only when the run is explicitly a non-interactive smoke/rehearsal.
166
166
 
167
167
  - Customer-facing progress updates must use product language. Say "quick
168
- setup choices", "campaign brief", "lead sourcing", and "approval" instead of
168
+ setup choices", "campaign brief", "Find Leads", and "approval" instead of
169
169
  `request_user_input`, Default mode, MCP namespaces, plugin caches, prompt
170
170
  loading, runbooks, local skill files, skill versions, npm/package details,
171
171
  repo-local files, VPS/off-desktop/browser automation limitations, or
@@ -181,6 +181,13 @@ Optional debug/UAT draft directory, disabled in normal customer runs:
181
181
  or safe launch. Do not say "persist", "local draft folder", "artifact",
182
182
  "mkdir", "campaign thesis", or "same approved campaign thesis" in
183
183
  customer-facing progress copy.
184
+ - Watch guide copy must use the single `watchNarration` object whenever you set
185
+ `currentStep` or report visible progress in a watched run. Read
186
+ `references/watch-guide-narration.md` for the compact screen contract. Do not
187
+ send separate guide headline/body args. In Find Leads, the static stage label
188
+ is not enough: name the active lane/provider, what search or sample you are
189
+ trying, and why it fits this campaign. Do not promise time estimates in guide
190
+ copy.
184
191
  - Every approval gate must include live campaign access after the readable inline
185
192
  content. Show a short `Watch link:` line with the campaign link when a
186
193
  campaign shell exists. In normal customer runs, do not show `Open artifact:`
@@ -401,7 +408,7 @@ Optional debug/UAT draft directory, disabled in normal customer runs:
401
408
  `RevOps`, `outbound systems`, or `pipeline architecture` unless they are
402
409
  translated into what the user can understand.
403
410
 
404
- After rendering that brief, ask for brief approval before lead sourcing in
411
+ After rendering that brief, ask for brief approval before Find Leads in
405
412
  hosted net-new runs. The user-facing choice should be approve/revise language,
406
413
  not "looks good".
407
414
  Approval options should refer to what the user just read, e.g. `Approve this
@@ -422,16 +429,15 @@ brief`, `Revise target`, `Revise offer/proof`, and `Other / custom`.
422
429
 
423
430
  The approval question can be based on the rendered brief in chat and the live
424
431
  campaign brief. Do not wait for file-write chrome before asking for approval.
425
- Before lead sourcing, call `update_campaign({ campaignId, campaignBrief,
426
- currentStep: "pick-provider" })` after approval so the watch link moves out of
427
- Plan while the main thread compares source paths.
432
+ Before Find Leads, call `update_campaign({ campaignId, campaignBrief,
433
+ currentStep: "pick-provider", watchNarration: { ... } })` after approval so
434
+ the watch link moves out of Plan while the main thread compares source paths.
428
435
 
429
436
  - After the brief is approved, show the next progress line:
430
437
  `Cool. Now I'm going to find people who are both a good fit and active enough
431
438
  to be worth a LinkedIn test. I'll compare source paths by expected volume,
432
439
  sampled ICP fit, activity/warmth signal, cleanup risk, and tradeoffs. This
433
- usually takes ~3-5 min, and I'll show you the source decision + sample before
434
- anything goes live.`
440
+ will end with a source decision + sample before anything goes live.`
435
441
  - In watch mode, do not leave the user sitting on only `pick-provider` while
436
442
  source scouts run. Move the campaign to the likely primary source lane
437
443
  (`signal-discovery`, `sales-nav`, or `prospeo`) before background source
@@ -447,9 +453,8 @@ message we should test.`
447
453
  - During long post-intake work, show concise progress checkpoints before the
448
454
  next expensive stage: source being checked, source switch/tradeoff if any,
449
455
  lead sample usable, filter/message drafting, and message-review rule loading.
450
- Each checkpoint should include a rough remaining time when useful. If a step
451
- exceeds its estimate by roughly a minute or more, acknowledge it lightly once
452
- and explain why the extra care helps the campaign. Example:
456
+ Each checkpoint should say what changed, what is being checked next, and why
457
+ that matters for the campaign. Do not invent remaining-time estimates. Example:
453
458
 
454
459
  ```text
455
460
  This is taking a little longer than I expected, sorry. I’m being careful here
@@ -623,7 +628,7 @@ workstreams`, `in parallel`, or `background` unless parallel branches were
623
628
  There are four customer-visible gates in the net-new hosted flow:
624
629
 
625
630
  - `brief-review` asks whether to approve the rendered campaign brief before
626
- lead sourcing starts. Do not skip this by inferring approval from setup
631
+ Find Leads starts. Do not skip this by inferring approval from setup
627
632
  answers.
628
633
  - `lead-review` / source decision asks whether to approve the selected source
629
634
  path and sample before moving into filter/message work. Do not skip this by
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "version": "v2",
3
3
  "workflow": "create-campaign-v2",
4
- "principle": "CampaignOffer state and the watch link are canonical from the first brief onward. Disk artifacts are optional debug/UAT diagnostics, not the normal customer surface. Resume, gating, and handoff read campaign state first. Start from user intake, create a watchable campaign shell with a v1 brief, update currentStep before major visible work, attach source/search state to that CampaignOffer, import the bounded review batch before post-import fit/message scouts, save rubrics and an approved message template, then queue the 15-row cascade and hand off to settings/sequence/start.",
4
+ "principle": "CampaignOffer state and the watch link are canonical from the first brief onward. Disk artifacts are optional debug/UAT diagnostics, not the normal customer surface. Resume, gating, and handoff read campaign state first. Start from user intake, create a watchable campaign shell with a v1 brief, update currentStep and watchNarration before major visible work, attach source/search state to that CampaignOffer, import the bounded review batch before post-import fit/message scouts, save rubrics and an approved message template, then queue the 15-row cascade and hand off to settings/sequence/start.",
5
5
  "artifactPolicy": {
6
6
  "normalCustomerPath": "Use CampaignOffer state, MCP responses, provider search state, and campaign table rows. Do not create, read, link, or surface local draft files unless debug/UAT mode is explicit or the user asks for them.",
7
7
  "debugArtifactsAreOptional": true,
8
8
  "requiredArtifactsApplyWhen": "legacy resume, fixture validation, or explicit debug/UAT mode only",
9
- "customerFacingAccess": "Watch link and live campaign currentStep"
9
+ "customerFacingAccess": "Watch link, live campaign currentStep, and concise watchNarration"
10
10
  },
11
11
  "commitGateChoices": [
12
12
  "approve",
@@ -225,9 +225,6 @@
225
225
  "watch the draft campaign as it fills in",
226
226
  "no leads import and nothing sends yet"
227
227
  ],
228
- "timeEstimates": {
229
- "campaignBrief": "~1-2 min"
230
- },
231
228
  "forbiddenWording": [
232
229
  "brief.md",
233
230
  "lead-review.md",
@@ -279,10 +276,12 @@
279
276
  "name",
280
277
  "campaignBrief",
281
278
  "clientProspectId or senderLinkedinUrl",
282
- "currentStep"
279
+ "currentStep",
280
+ "watchNarration"
283
281
  ],
284
282
  "requiredValues": {
285
- "currentStep": "create-offer"
283
+ "currentStep": "create-offer",
284
+ "watchNarration.stage": "brief-review"
286
285
  },
287
286
  "briefMode": "v1 campaign brief shown to the user; not an empty placeholder",
288
287
  "capture": [
@@ -306,7 +305,8 @@
306
305
  "campaignId",
307
306
  "watchUrl",
308
307
  "campaignBrief",
309
- "currentStep:create-offer"
308
+ "currentStep:create-offer",
309
+ "watchNarration: brief-review with approve-the-brief guidance"
310
310
  ]
311
311
  },
312
312
  {
@@ -330,7 +330,7 @@
330
330
  "campaignBrief"
331
331
  ],
332
332
  "watchUrlSource": "create_campaign.watchUrl",
333
- "requiredWatchUrlShape": "direct /campaign-builder/{campaignId}?mode=claude watch URL",
333
+ "requiredWatchUrlShape": "direct /campaign-builder/{campaignId}?mode={claude|codex} watch URL",
334
334
  "codexBrowserHandoff": {
335
335
  "openWhenAvailable": true,
336
336
  "inspectBeforeContinuing": true,
@@ -444,7 +444,7 @@
444
444
  ],
445
445
  "debugOrUatOnly": true,
446
446
  "requiredBeforeTransition": false,
447
- "reconcileRule": "If debug/UAT artifacts are enabled, verify the sidecar persisted the same brief shown in chat. Normal customer lead sourcing reads CampaignOffer.campaignBrief, not brief.md.",
447
+ "reconcileRule": "If debug/UAT artifacts are enabled, verify the sidecar persisted the same brief shown in chat. Normal customer Find Leads work reads CampaignOffer.campaignBrief, not brief.md.",
448
448
  "chatRenderRule": "Do not block the brief approval question or find-leads transition on this step. Only mention artifact persistence if debug/UAT mode fails or the user asks."
449
449
  },
450
450
  {
@@ -457,7 +457,8 @@
457
457
  "when": "after_user_brief_confirmed_or_auto_continue",
458
458
  "fields": [
459
459
  "campaignBrief",
460
- "currentStep:pick-provider"
460
+ "currentStep:pick-provider",
461
+ "watchNarration: find-leads with the first provider/lane being tested"
461
462
  ],
462
463
  "fallback": "If campaignId is missing from CampaignOffer state, stop; shell-first flow requires an existing campaign before sourcing leads.",
463
464
  "readsCampaignStateFirst": true,
@@ -512,20 +513,23 @@
512
513
  "tool": "update_campaign",
513
514
  "requiredFields": [
514
515
  "campaignId",
515
- "currentStep"
516
+ "currentStep",
517
+ "watchNarration"
516
518
  ],
517
519
  "requiredValues": {
518
- "currentStep": "pick-provider"
520
+ "currentStep": "pick-provider",
521
+ "watchNarration.stage": "find-leads"
519
522
  },
520
523
  "when": "before_source_scouts_or_provider_search",
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."
524
+ "chatRenderRule": "Move the campaign watch view to Find Leads before the main thread starts comparing source paths. The watchNarration headline/body must name the likely provider or lane being tested and why it fits this campaign. Do not mention MCP or local artifacts."
522
525
  },
523
526
  {
524
527
  "action": "advance_watch_to_initial_source_lane",
525
528
  "tool": "update_campaign",
526
529
  "requiredFields": [
527
530
  "campaignId",
528
- "currentStep"
531
+ "currentStep",
532
+ "watchNarration"
529
533
  ],
530
534
  "currentStepByPrimaryLane": {
531
535
  "signals": "signal-discovery",
@@ -536,7 +540,7 @@
536
540
  "uploadedDomains": "prospeo"
537
541
  },
538
542
  "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."
543
+ "rule": "Choose the likely primary visible source lane from source intake, brief preference, or the best first lane the main thread will actually search. Send watchNarration with stage find-leads that says what search was tried or is being tried next, what sample is being checked, and why it helps this campaign. Do this before waiting on background scouts so watch mode never sits on only Pick Provider while source work happens elsewhere."
540
544
  },
541
545
  {
542
546
  "action": "run_first_campaign_attached_source_search",
@@ -565,7 +569,7 @@
565
569
  "source decision + sample",
566
570
  "no import or enrichment yet"
567
571
  ],
568
- "timeEstimate": "~3-5 min"
572
+ "watchNarrationRule": "Do not include time estimates. Use current, campaign-specific search reasoning instead."
569
573
  },
570
574
  {
571
575
  "action": "read_optional_source_intake",
@@ -755,7 +759,7 @@
755
759
  "import the first 15-row review batch before fit/message work",
756
760
  "selectedLeadListId stays the source list and workflowTableId is the campaign table"
757
761
  ],
758
- "timeEstimate": "~2-3 min",
762
+ "watchNarrationRule": "Do not include time estimates. Say what review-batch work is happening and what the user will approve next.",
759
763
  "chatRenderRule": "Lead source is set. Next import and confirm only the first 15-row review batch into the campaign table. After workflowTableId exists, run the fit-filter and message-generation workstreams from the campaign table sample. If real parallel MCP/tool branches or host subagents were actually launched, say so; otherwise say the branches will run sequentially. Never claim parallelism unless parallel execution actually started."
760
764
  },
761
765
  {
@@ -929,10 +933,12 @@
929
933
  "tool": "update_campaign",
930
934
  "requiredFields": [
931
935
  "campaignId",
932
- "currentStep"
936
+ "currentStep",
937
+ "watchNarration"
933
938
  ],
934
939
  "requiredValues": {
935
- "currentStep": "filter-choice"
940
+ "currentStep": "filter-choice",
941
+ "watchNarration.stage": "fit-message"
936
942
  }
937
943
  }
938
944
  ],
@@ -1489,7 +1495,8 @@
1489
1495
  "message-review-decision.md"
1490
1496
  ],
1491
1497
  "requiredValues": {
1492
- "currentStep": "validate-sample"
1498
+ "currentStep": "validate-sample",
1499
+ "watchNarration.stage": "review-ready"
1493
1500
  },
1494
1501
  "when": "after_update_campaign_brief_succeeds",
1495
1502
  "requiredBeforeCascade": true,
@@ -1846,7 +1853,7 @@
1846
1853
  "Campaign created",
1847
1854
  "approved brief",
1848
1855
  "Open this to watch",
1849
- "lead sourcing",
1856
+ "Find Leads",
1850
1857
  "rubric scoring",
1851
1858
  "messaging populate live",
1852
1859
  "Watch link:"
@@ -2118,10 +2125,12 @@
2118
2125
  "tool": "update_campaign",
2119
2126
  "requiredFields": [
2120
2127
  "campaignId",
2121
- "currentStep"
2128
+ "currentStep",
2129
+ "watchNarration"
2122
2130
  ],
2123
2131
  "requiredValues": {
2124
- "currentStep": "awaiting-user-greenlight"
2132
+ "currentStep": "awaiting-user-greenlight",
2133
+ "watchNarration.stage": "review-ready"
2125
2134
  }
2126
2135
  }
2127
2136
  ],
@@ -2188,7 +2197,7 @@
2188
2197
  },
2189
2198
  {
2190
2199
  "action": "surface_sender_and_slack_handoff",
2191
- "settingsUrlPattern": "/campaign-builder/{campaignId}/settings?mode=claude",
2200
+ "settingsUrlPattern": "/campaign-builder/{campaignId}/settings?mode={claude|codex}",
2192
2201
  "requiredVisibleContent": [
2193
2202
  "connect or select a LinkedIn sender",
2194
2203
  "Slack reply review",
@@ -0,0 +1,141 @@
1
+ # Watch Guide Narration
2
+
3
+ Use one `watchNarration` object whenever you set `currentStep` or report visible
4
+ progress in a watched create-campaign run. Do not add separate guide headline or
5
+ body args.
6
+
7
+ ## Render
8
+
9
+ ```text
10
+ Building campaign with {Claude Code or Codex} [Exit watch mode]
11
+ [progress bar] {stage label} · Step {n} of 5
12
+
13
+ {headline}
14
+ {visibleState} {agentIntent}
15
+
16
+ Next: {nextAction}
17
+ {safety?}
18
+ ```
19
+
20
+ UI owns the header, driver label, button, progress bar, step count, and static
21
+ stage labels. The driver label comes from the watch URL mode (`mode=claude` or
22
+ `mode=codex`), so do not add separate guide args for it. You own `stage`,
23
+ `headline`, `visibleState`, `agentIntent`, and any exceptional `nextAction`,
24
+ `safety`, `workerStatuses`, or `blockedReason`.
25
+
26
+ ## Copy Rules
27
+
28
+ - One primary user action per frame.
29
+ - Headline under 8 words.
30
+ - Body is 1-2 short sentences total.
31
+ - Say where the user acts: usually `in Claude Code`, `in Codex`, or `in chat`,
32
+ matching the host that created the watch URL.
33
+ - `Find Leads` is only the static progress label. The copy must name the active
34
+ lane/provider and campaign strategy: Signal Discovery, Sales Nav, Prospeo,
35
+ Apollo, or existing list.
36
+ - During search iteration, say what you tried, what you are trying next, what
37
+ sample you are checking, and why that helps this campaign.
38
+ - Avoid internal terms: MCP, tool, currentStep, workflow table, scout, debug.
39
+ - Do not invent time estimates.
40
+
41
+ ## Stages
42
+
43
+ | stage | UI label | Next |
44
+ | -------------- | ------------- | ------------- |
45
+ | `brief-review` | Brief review | Find Leads |
46
+ | `find-leads` | Find Leads | Review batch |
47
+ | `review-batch` | Review batch | Fit + message |
48
+ | `fit-message` | Fit + message | Review ready |
49
+ | `review-ready` | Review ready | Validation |
50
+
51
+ ## Examples
52
+
53
+ Brief review:
54
+
55
+ ```json
56
+ {
57
+ "stage": "brief-review",
58
+ "headline": "Approve the brief in Codex",
59
+ "visibleState": "This page is a live preview of the campaign brief.",
60
+ "agentIntent": "Ask for changes in chat if anything looks off.",
61
+ "nextAction": "Find Leads"
62
+ }
63
+ ```
64
+
65
+ Signal Discovery:
66
+
67
+ ```json
68
+ {
69
+ "stage": "find-leads",
70
+ "headline": "Testing Signal Discovery",
71
+ "visibleState": "You are watching Signal Discovery search posts about Claude Code and GTM automation.",
72
+ "agentIntent": "Codex is sampling engagers to see if they match the founder/operator campaign brief.",
73
+ "nextAction": "Review batch"
74
+ }
75
+ ```
76
+
77
+ Search iteration:
78
+
79
+ ```json
80
+ {
81
+ "stage": "find-leads",
82
+ "headline": "Tightening the search",
83
+ "visibleState": "The first Signal Discovery search was broad, so Codex is trying a narrower Claude Code founder lane.",
84
+ "agentIntent": "It is sampling a few engagers before importing so the review batch stays on-brief.",
85
+ "nextAction": "Review batch"
86
+ }
87
+ ```
88
+
89
+ Review batch:
90
+
91
+ ```json
92
+ {
93
+ "stage": "review-batch",
94
+ "headline": "Preparing your review batch",
95
+ "visibleState": "This page shows the leads Codex selected for review.",
96
+ "agentIntent": "Codex is preparing the first campaign batch from those leads.",
97
+ "nextAction": "Fit + message"
98
+ }
99
+ ```
100
+
101
+ Fit + message:
102
+
103
+ ```json
104
+ {
105
+ "stage": "fit-message",
106
+ "headline": "Building fit and message",
107
+ "visibleState": "Codex is preparing the campaign's fit rules and first message sample.",
108
+ "agentIntent": "It is checking who should receive this campaign and what the first offer should say.",
109
+ "nextAction": "Review ready",
110
+ "workerStatuses": {
111
+ "leadFitBuilder": "running",
112
+ "messageDraftBuilder": "running"
113
+ }
114
+ }
115
+ ```
116
+
117
+ Review ready:
118
+
119
+ ```json
120
+ {
121
+ "stage": "review-ready",
122
+ "headline": "Review fit and message",
123
+ "visibleState": "The fit rules and message sample are ready.",
124
+ "agentIntent": "Approve them or ask for changes in chat.",
125
+ "nextAction": "Validation",
126
+ "safety": "Codex will only continue after you approve."
127
+ }
128
+ ```
129
+
130
+ Blocked/recovering:
131
+
132
+ ```json
133
+ {
134
+ "stage": "review-batch",
135
+ "headline": "Fixing the review batch",
136
+ "visibleState": "The browser is not showing the campaign batch yet.",
137
+ "agentIntent": "Codex is reconnecting the campaign state before moving forward.",
138
+ "nextAction": "Fit + message",
139
+ "blockedReason": "Campaign batch is not ready yet."
140
+ }
141
+ ```
@@ -45,7 +45,7 @@ Cool, let's find leads.
45
45
  Atomic-mint legacy orientation:
46
46
 
47
47
  ```text
48
- Open this to watch lead sourcing, rubric scoring, and messaging populate live.
48
+ Open this to watch Find Leads, rubric scoring, and messaging populate live.
49
49
  ```
50
50
 
51
51
  The exact wording may be adapted for tone, but the copy must make three things