@sellable/mcp 0.1.93 → 0.1.95

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.
@@ -68,6 +68,22 @@ export declare function getCampaignContext(input: GetCampaignContextInput): Prom
68
68
  expectedHeadlessStep: string | null;
69
69
  headlessCurrentStep: string | null;
70
70
  uiStep: string | null;
71
+ orientation: {
72
+ visibleStep: string | null;
73
+ browserStepLabel: string;
74
+ customerSummary: string;
75
+ nextVisibleMilestone: string;
76
+ blockedReason: string | null;
77
+ agentGuidance: string;
78
+ browserCheckpoint: string;
79
+ senderSelectionAllowed: boolean;
80
+ safeToAskSender: boolean;
81
+ };
82
+ watchUrl: {
83
+ urlPresent: boolean;
84
+ signed: boolean;
85
+ redirectPath: string | null;
86
+ };
71
87
  table: {
72
88
  workflowTableId: string | null;
73
89
  checked: boolean;
@@ -165,6 +165,22 @@ export function markCampaignContextDirty(campaignId, reason) {
165
165
  expectedHeadlessStep: "create-offer",
166
166
  headlessCurrentStep: null,
167
167
  uiStep: "plan",
168
+ orientation: {
169
+ visibleStep: "plan",
170
+ browserStepLabel: "Campaign brief",
171
+ customerSummary: "The campaign view is waiting for saved campaign context.",
172
+ nextVisibleMilestone: "Lead source search",
173
+ blockedReason: "Missing campaignContextNotLoaded",
174
+ agentGuidance: "Refresh campaign navigation state before describing the browser.",
175
+ browserCheckpoint: "brief",
176
+ senderSelectionAllowed: false,
177
+ safeToAskSender: false,
178
+ },
179
+ watchUrl: {
180
+ urlPresent: false,
181
+ signed: false,
182
+ redirectPath: null,
183
+ },
168
184
  table: {
169
185
  workflowTableId: null,
170
186
  checked: false,
@@ -25,6 +25,7 @@ export type CampaignOfferNavigation = {
25
25
  campaignStatus?: string | null;
26
26
  status?: string | null;
27
27
  currentStep?: string | null;
28
+ watchUrl?: string | null;
28
29
  };
29
30
  type NavigationDebugPayload = Record<string, unknown>;
30
31
  export declare function logNavigationDebug(event: string, payload?: NavigationDebugPayload, campaignId?: string | null): void;
@@ -67,6 +68,22 @@ export declare function computeCampaignNavigationStateFromCampaign(campaign: Cam
67
68
  expectedHeadlessStep: string | null;
68
69
  headlessCurrentStep: string | null;
69
70
  uiStep: string | null;
71
+ orientation: {
72
+ visibleStep: string | null;
73
+ browserStepLabel: string;
74
+ customerSummary: string;
75
+ nextVisibleMilestone: string;
76
+ blockedReason: string | null;
77
+ agentGuidance: string;
78
+ browserCheckpoint: string;
79
+ senderSelectionAllowed: boolean;
80
+ safeToAskSender: boolean;
81
+ };
82
+ watchUrl: {
83
+ urlPresent: boolean;
84
+ signed: boolean;
85
+ redirectPath: string | null;
86
+ };
70
87
  table: {
71
88
  workflowTableId: string | null;
72
89
  checked: boolean;
@@ -95,6 +112,22 @@ export declare function getCampaignNavigationState(input: GetCampaignNavigationS
95
112
  expectedHeadlessStep: string | null;
96
113
  headlessCurrentStep: string | null;
97
114
  uiStep: string | null;
115
+ orientation: {
116
+ visibleStep: string | null;
117
+ browserStepLabel: string;
118
+ customerSummary: string;
119
+ nextVisibleMilestone: string;
120
+ blockedReason: string | null;
121
+ agentGuidance: string;
122
+ browserCheckpoint: string;
123
+ senderSelectionAllowed: boolean;
124
+ safeToAskSender: boolean;
125
+ };
126
+ watchUrl: {
127
+ urlPresent: boolean;
128
+ signed: boolean;
129
+ redirectPath: string | null;
130
+ };
98
131
  table: {
99
132
  workflowTableId: string | null;
100
133
  checked: boolean;
@@ -301,6 +301,120 @@ function mapHeadlessToUiStep(step) {
301
301
  }
302
302
  return null;
303
303
  }
304
+ function describeVisibleStep(step) {
305
+ switch (step) {
306
+ case "plan":
307
+ return {
308
+ label: "Campaign brief",
309
+ summary: "The campaign brief is visible in the campaign view.",
310
+ nextMilestone: "Lead source search",
311
+ guidance: "Confirm the browser-visible campaign state before moving to lead sourcing.",
312
+ checkpoint: "brief",
313
+ };
314
+ case "pick-provider":
315
+ case "contact-search":
316
+ case "signal-discovery":
317
+ return {
318
+ label: "Lead sourcing",
319
+ summary: "Lead source selection or search is visible.",
320
+ nextMilestone: "Lead preview",
321
+ guidance: "Explain the selected lead-source lane and visible search state before continuing.",
322
+ checkpoint: "lead-source",
323
+ };
324
+ case "leads":
325
+ return {
326
+ label: "Lead import",
327
+ summary: "The lead preview or imported review batch is visible.",
328
+ nextMilestone: "Fit filtering and message review",
329
+ guidance: "Confirm the browser-visible campaign state and row batch before filter/message work.",
330
+ checkpoint: "import",
331
+ };
332
+ case "filter-choice":
333
+ case "filter-rules":
334
+ case "filter-leads":
335
+ return {
336
+ label: "Fit filtering",
337
+ summary: "Fit filtering or sample validation is visible.",
338
+ nextMilestone: "Message review or validation",
339
+ guidance: "Explain the visible filter or validation state and wait for the next approved step.",
340
+ checkpoint: "filters",
341
+ };
342
+ case "messages":
343
+ return {
344
+ label: "Message review",
345
+ summary: "Message review or generation is visible.",
346
+ nextMilestone: "10-row validation",
347
+ guidance: "Explain the visible message state before asking for approval or validation.",
348
+ checkpoint: "messages",
349
+ };
350
+ case "settings":
351
+ return {
352
+ label: "Settings",
353
+ summary: "Settings is visible. Sender selection belongs here.",
354
+ nextMilestone: "Safe sender selection",
355
+ guidance: "Select a safe connected sender here only; stop before sequence/start/live send in UAT.",
356
+ checkpoint: "settings",
357
+ };
358
+ case "sequence":
359
+ return {
360
+ label: "Sequence setup",
361
+ summary: "Sequence setup is visible.",
362
+ nextMilestone: "Explicit launch approval",
363
+ guidance: "Do not attach or change sequence state during watch UAT unless explicitly approved.",
364
+ checkpoint: "sequence",
365
+ };
366
+ case "send":
367
+ return {
368
+ label: "Send review",
369
+ summary: "Send review or running status is visible.",
370
+ nextMilestone: "Explicit launch approval",
371
+ guidance: "Do not start the campaign or trigger live send without explicit user confirmation.",
372
+ checkpoint: "send",
373
+ };
374
+ default:
375
+ return {
376
+ label: "Campaign view",
377
+ summary: "The campaign view is loading or waiting for the next saved step.",
378
+ nextMilestone: "Next saved campaign step",
379
+ guidance: "Inspect the browser-visible campaign state before continuing with more tools.",
380
+ checkpoint: "unknown",
381
+ };
382
+ }
383
+ }
384
+ function parseSignedWatchUrl(watchUrl, campaignId) {
385
+ if (!watchUrl) {
386
+ return {
387
+ urlPresent: false,
388
+ signed: false,
389
+ redirectPath: null,
390
+ warning: null,
391
+ };
392
+ }
393
+ try {
394
+ const url = new URL(watchUrl);
395
+ const redirect = url.searchParams.get("redirect");
396
+ const decodedRedirect = redirect ? decodeURIComponent(redirect) : null;
397
+ const signed = url.pathname === "/auth/continue" &&
398
+ Boolean(url.searchParams.get("token")) &&
399
+ decodedRedirect === `/campaign-builder/${campaignId}?mode=claude`;
400
+ return {
401
+ urlPresent: true,
402
+ signed,
403
+ redirectPath: decodedRedirect,
404
+ warning: signed
405
+ ? null
406
+ : "Watch URL is not a signed /auth/continue watch link for /campaign-builder/{campaignId}?mode=claude.",
407
+ };
408
+ }
409
+ catch {
410
+ return {
411
+ urlPresent: true,
412
+ signed: false,
413
+ redirectPath: null,
414
+ warning: "Watch URL is malformed; recover a fresh signed /auth/continue watch link before browser handoff.",
415
+ };
416
+ }
417
+ }
304
418
  export const navigationToolDefinitions = [
305
419
  {
306
420
  name: "get_campaign_navigation_state",
@@ -401,30 +515,49 @@ export function computeCampaignNavigationStateFromCampaign(campaign, options) {
401
515
  if (!blockedAt && evaluateTailState) {
402
516
  computedStep = isRunningCampaign(campaign) ? "running" : "send";
403
517
  }
404
- const expectedHeadlessStep = computedStep === "campaign-created"
518
+ const stepForHeadless = blockedAt ?? computedStep;
519
+ const expectedHeadlessStep = stepForHeadless === "campaign-created"
405
520
  ? "create-offer"
406
- : computedStep === "provider-search"
521
+ : stepForHeadless === "provider-search"
407
522
  ? providerConfig?.currentStep || campaign.leadSourceProvider || null
408
- : computedStep === "filter-rules"
523
+ : stepForHeadless === "filter-rules"
409
524
  ? "create-icp-rubric"
410
- : computedStep === "messages"
525
+ : stepForHeadless === "messages"
411
526
  ? "messages"
412
- : computedStep === "settings"
527
+ : stepForHeadless === "settings"
413
528
  ? "settings"
414
- : computedStep === "sequence"
529
+ : stepForHeadless === "sequence"
415
530
  ? "sequence"
416
- : computedStep === "running"
531
+ : stepForHeadless === "running"
417
532
  ? "running"
418
- : computedStep;
533
+ : stepForHeadless;
534
+ const currentUiStep = mapHeadlessToUiStep(campaign.currentStep);
535
+ const expectedUiStep = mapHeadlessToUiStep(expectedHeadlessStep);
419
536
  const stepAligned = !campaign.currentStep || !expectedHeadlessStep
420
537
  ? null
421
- : campaign.currentStep === expectedHeadlessStep;
538
+ : campaign.currentStep === expectedHeadlessStep ||
539
+ (currentUiStep !== null && currentUiStep === expectedUiStep);
422
540
  if (stepAligned === false && campaign.currentStep && expectedHeadlessStep) {
423
541
  warnings.push(`Headless step mismatch: expected "${expectedHeadlessStep}" but campaign.currentStep is "${campaign.currentStep}".`);
424
542
  }
425
543
  if (!providerConfig && campaign.leadSourceProvider) {
426
544
  warnings.push(`Provider config not found for "${campaign.leadSourceProvider}".`);
427
545
  }
546
+ const uiStep = currentUiStep;
547
+ const orientationStep = uiStep || mapHeadlessToUiStep(expectedHeadlessStep);
548
+ const orientationSource = isRunningCampaign(campaign)
549
+ ? {
550
+ label: "Running campaign",
551
+ summary: "The campaign is running and the send review is visible.",
552
+ nextMilestone: "Explicit launch approval",
553
+ guidance: "Confirm the browser-visible campaign state before describing the running status.",
554
+ checkpoint: "send",
555
+ }
556
+ : describeVisibleStep(orientationStep);
557
+ const watchUrlState = parseSignedWatchUrl(campaign.watchUrl, campaign.id);
558
+ if (watchUrlState.warning) {
559
+ warnings.push(watchUrlState.warning);
560
+ }
428
561
  const result = {
429
562
  campaignId: campaign.id,
430
563
  computedStep,
@@ -432,7 +565,23 @@ export function computeCampaignNavigationStateFromCampaign(campaign, options) {
432
565
  missing,
433
566
  expectedHeadlessStep,
434
567
  headlessCurrentStep: campaign.currentStep ?? null,
435
- uiStep: mapHeadlessToUiStep(campaign.currentStep),
568
+ uiStep,
569
+ orientation: {
570
+ visibleStep: orientationStep,
571
+ browserStepLabel: orientationSource.label,
572
+ customerSummary: orientationSource.summary,
573
+ nextVisibleMilestone: orientationSource.nextMilestone,
574
+ blockedReason: missing.length > 0 ? `Missing ${missing.join(", ")}` : null,
575
+ agentGuidance: orientationSource.guidance,
576
+ browserCheckpoint: orientationSource.checkpoint,
577
+ senderSelectionAllowed: orientationStep === "settings",
578
+ safeToAskSender: orientationStep === "settings",
579
+ },
580
+ watchUrl: {
581
+ urlPresent: watchUrlState.urlPresent,
582
+ signed: watchUrlState.signed,
583
+ redirectPath: watchUrlState.redirectPath,
584
+ },
436
585
  table: {
437
586
  workflowTableId: campaign.workflowTableId ?? null,
438
587
  checked: options.tableChecked,
@@ -461,6 +610,8 @@ export function computeCampaignNavigationStateFromCampaign(campaign, options) {
461
610
  expectedHeadlessStep: result.expectedHeadlessStep,
462
611
  headlessCurrentStep: result.headlessCurrentStep,
463
612
  uiStep: result.uiStep,
613
+ orientation: result.orientation,
614
+ watchUrl: result.watchUrl,
464
615
  table: result.table,
465
616
  context: result.context,
466
617
  warnings: result.warnings,
@@ -121,6 +121,22 @@ export declare function waitForCampaignTableReady(input: WaitForCampaignTableRea
121
121
  expectedHeadlessStep: string | null;
122
122
  headlessCurrentStep: string | null;
123
123
  uiStep: string | null;
124
+ orientation: {
125
+ visibleStep: string | null;
126
+ browserStepLabel: string;
127
+ customerSummary: string;
128
+ nextVisibleMilestone: string;
129
+ blockedReason: string | null;
130
+ agentGuidance: string;
131
+ browserCheckpoint: string;
132
+ senderSelectionAllowed: boolean;
133
+ safeToAskSender: boolean;
134
+ };
135
+ watchUrl: {
136
+ urlPresent: boolean;
137
+ signed: boolean;
138
+ redirectPath: string | null;
139
+ };
124
140
  table: {
125
141
  workflowTableId: string | null;
126
142
  checked: boolean;
@@ -154,6 +170,22 @@ export declare function waitForCampaignTableReady(input: WaitForCampaignTableRea
154
170
  expectedHeadlessStep: string | null;
155
171
  headlessCurrentStep: string | null;
156
172
  uiStep: string | null;
173
+ orientation: {
174
+ visibleStep: string | null;
175
+ browserStepLabel: string;
176
+ customerSummary: string;
177
+ nextVisibleMilestone: string;
178
+ blockedReason: string | null;
179
+ agentGuidance: string;
180
+ browserCheckpoint: string;
181
+ senderSelectionAllowed: boolean;
182
+ safeToAskSender: boolean;
183
+ };
184
+ watchUrl: {
185
+ urlPresent: boolean;
186
+ signed: boolean;
187
+ redirectPath: string | null;
188
+ };
157
189
  table: {
158
190
  workflowTableId: string | null;
159
191
  checked: boolean;
@@ -189,6 +221,22 @@ export declare function waitForCampaignTableReady(input: WaitForCampaignTableRea
189
221
  expectedHeadlessStep: string | null;
190
222
  headlessCurrentStep: string | null;
191
223
  uiStep: string | null;
224
+ orientation: {
225
+ visibleStep: string | null;
226
+ browserStepLabel: string;
227
+ customerSummary: string;
228
+ nextVisibleMilestone: string;
229
+ blockedReason: string | null;
230
+ agentGuidance: string;
231
+ browserCheckpoint: string;
232
+ senderSelectionAllowed: boolean;
233
+ safeToAskSender: boolean;
234
+ };
235
+ watchUrl: {
236
+ urlPresent: boolean;
237
+ signed: boolean;
238
+ redirectPath: string | null;
239
+ };
192
240
  table: {
193
241
  workflowTableId: string | null;
194
242
  checked: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.93",
3
+ "version": "0.1.95",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -66,10 +66,10 @@ allowed-tools:
66
66
 
67
67
  Use this as the customer-facing entrypoint for Sellable campaign creation.
68
68
 
69
- CampaignOffer state is canonical; disk artifacts are a debug trail. The
70
- internal create-campaign-v2 flow still writes the diagnostics, and all 14 disk
71
- artifacts remain as debug outputs, but resume, gating, and handoff read campaign
72
- state first. The
69
+ CampaignOffer state and the watch link are the customer-facing source of truth.
70
+ Disk artifacts are optional debug/UAT diagnostics; normal customer runs should
71
+ not create, link, or surface local draft files unless the user explicitly asks
72
+ for them. Resume, gating, and handoff read campaign state first. The
73
73
  watchable campaign exists after the short brief; lead import is bounded to the
74
74
  first review batch, and enrichment/message generation waits until rubrics and
75
75
  the approved message template are saved on the campaign.
@@ -147,18 +147,56 @@ Use rendered Markdown for user review surfaces, not fenced code blocks. Keep
147
147
  lines short, use indexed section labels and bullets, and translate internal
148
148
  sourcing terms into plain language.
149
149
 
150
- Every approval gate must include artifact access after the readable inline
151
- content. Show a short `Open artifact:` line with the one key clickable markdown
152
- link for that stage. Do not show raw filesystem paths unless links cannot be
153
- created or the user asks. Do this for brief approval, lead-source
154
- approval/review, lead-filter review, and message review.
155
- The link is for deeper inspection; never use it as a substitute for showing the
156
- content in chat.
150
+ Every approval gate must include live campaign access after the readable inline
151
+ content. Show a short `Watch link:` line once the campaign shell exists. In
152
+ normal customer runs, do not show `Open artifact:` lines, raw filesystem paths,
153
+ or local draft filenames. Local artifacts are debug/UAT-only unless the user asks
154
+ for them. The link is for deeper inspection; never use it as a substitute for
155
+ showing the content in chat.
157
156
 
158
157
  Never mention MCP namespaces, prompt chunking, plugin cache paths, missing
159
158
  linked skill versions, runbooks, or local skill files in normal customer-facing
160
159
  copy.
161
160
 
161
+ ## Codex Watch Browser Handoff
162
+
163
+ When a campaign tool returns `watchUrl`, treat it as an active browser handoff,
164
+ not only a URL to print. A valid handoff link must be a signed
165
+ `/auth/continue?token=...&redirect=/campaign-builder/{campaignId}?mode=claude`
166
+ URL. `create_campaign.watchUrl`, `create_campaign({ campaignId }).watchUrl`,
167
+ and `get_campaign.watchUrl` are all acceptable only when they return that signed
168
+ shape.
169
+
170
+ In Codex Desktop, when in-app browser control is available, open the returned watch link on the user's behalf. After opening it, inspect the browser-visible campaign state, then explain what the user is seeing before continuing with more campaign tools. Use customer language such as:
171
+
172
+ ```text
173
+ I’ll open the campaign view and keep it in sync as I build.
174
+ ```
175
+
176
+ If browser control is unavailable, say so briefly and provide the watch link.
177
+ In VPS or other off-desktop Codex runs, this fallback is the expected handoff for
178
+ the local headed watch observer: make the signed link easy to copy/open, then
179
+ continue with explicit chat progress and `get_campaign_navigation_state`
180
+ orientation checks.
181
+ Use this fallback shape:
182
+
183
+ ```text
184
+ I can’t inspect the campaign browser from this Codex session, so I’ll keep the
185
+ chat steps explicit here. Watch link: {watchUrl}
186
+ ```
187
+
188
+ If opening the watch link lands on an auth, 404, permission, blank, or visible
189
+ error state, report only what is visible, recover a fresh signed watch link once
190
+ with `create_campaign({ campaignId })` or `get_campaign`, and try that link. Do
191
+ not claim the browser was opened, inspected, or synchronized unless the visible
192
+ browser state was actually observed. Do not claim the browser was opened unless
193
+ it was opened and observed.
194
+
195
+ After every `update_campaign({ campaignId, currentStep })`, use
196
+ `get_campaign_navigation_state` when available as a compact orientation check:
197
+ match the browser-visible step to the saved campaign state, explain the current
198
+ state in one sentence, and only then continue. Sender selection belongs at Settings after message approval and 10-row validation. Do not attach a sequence, start the campaign, or trigger a live send unless the user explicitly confirms that launch action outside UAT.
199
+
162
200
  ## Names To Use
163
201
 
164
202
  Use these exact public names so Claude Code and Codex do not drift:
@@ -503,9 +541,16 @@ updates.
503
541
  `message-validation.md` proves the message-review safety-gate workflow ran,
504
542
  `message-review.md` approves the template, rubrics are saved, and the
505
543
  approved message set is synced into the campaign brief.
506
- 6. Keep `selectedLeadListId` as the source list and `workflowTableId` as the
544
+ 6. The main thread owns watch navigation. Call
545
+ `mcp__sellable__update_campaign({ campaignId, currentStep })` before major
546
+ visible work so the user can watch progress in the app: `create-offer` for
547
+ the brief, `pick-provider` or the selected provider step while sourcing,
548
+ `filter-choice` after the 10-row review batch, `messages` or
549
+ `auto-execute-messaging` for message work, `validate-sample` for validation,
550
+ and `awaiting-user-greenlight` for Settings. Do not advance the step backward.
551
+ 7. Keep `selectedLeadListId` as the source list and `workflowTableId` as the
507
552
  campaign table. Do not use disk files as the post-mint source of truth.
508
- 7. Do not ask the user to run another command.
553
+ 8. Do not ask the user to run another command.
509
554
 
510
555
  ## Fallback
511
556
 
@@ -31,19 +31,21 @@ Run the configured JSON flow in durable stages:
31
31
  8. queue and validate the 10-row cascade
32
32
  9. settings/sender, sequence, and start handoff
33
33
 
34
- The JSON flow is the source of truth for stage order, `requiredArtifacts`,
35
- `producesArtifacts`, `allowedTools`, `doNotAllow`, `waitFor`, and
36
- `transitions`. This prompt explains how to execute those gates; it does not
37
- replace them.
38
-
39
- CampaignOffer state is canonical; disk artifacts are a debug trail. In this
40
- phase, all 14 disk artifacts remain as debug outputs, but resume, gating, and
34
+ The JSON flow is the source of truth for stage order, artifact policy,
35
+ `requiredArtifacts`, `producesArtifacts`, `allowedTools`, `doNotAllow`,
36
+ `waitFor`, and `transitions`. In normal customer runs, obey
37
+ `artifactPolicy.normalCustomerPath`: use campaign state and MCP responses instead
38
+ of local draft files. This prompt explains how to execute those gates; it does
39
+ not replace them.
40
+
41
+ CampaignOffer state and the watch link are the customer-facing source of truth.
42
+ Disk artifacts are optional debug/UAT diagnostics only. In normal customer runs,
43
+ do not create, link, or surface local draft files unless the user explicitly asks
44
+ for debug artifacts or the run is an explicit UAT/rehearsal. Resume, gating, and
41
45
  handoff read campaign state first: campaign id, watch URL, campaign brief,
42
46
  currentStep, provider/search association, selectedLeadListId as the source
43
47
  list, workflowTableId as the campaign table, saved rubrics, approved message
44
- template, senderIds, sequence template, and running state. `brief.md` is the
45
- single customer-facing debug copy of the brief; the campaign brief field is the
46
- durable downstream campaign thesis input.
48
+ template, senderIds, sequence template, and running state.
47
49
 
48
50
  There is no separate approval-packet, commit-gate, or mint step in the active
49
51
  shell-first flow. The campaign exists early for watch mode. The hard boundary
@@ -54,7 +56,7 @@ are saved, and the approved message set is synced into the campaign brief.
54
56
 
55
57
  <files>
56
58
 
57
- Validated draft directory:
59
+ Optional debug/UAT draft directory, disabled in normal customer runs:
58
60
 
59
61
  ```text
60
62
  .sellable/create-campaign-v2/drafts/{workspace-slug}/{campaign-slug}/
@@ -88,9 +90,17 @@ Validated draft directory:
88
90
  `watchUrl`; when the user opens it they should see the brief, not an empty
89
91
  campaign. If shell creation fails, stop and surface the error. There is no
90
92
  no-shell mint fallback in the active shell-first flow.
93
+ - The main thread owns watch navigation. Before expensive work in each major
94
+ stage, call `update_campaign({ campaignId, currentStep })` with the visible UI
95
+ step the user should be watching, then continue the work from MCP/product
96
+ state. Use `create-offer` for the brief, `pick-provider` or the selected
97
+ provider step while sourcing, `filter-choice` after the 10-row review batch is
98
+ imported, `messages`/`auto-execute-messaging` for message work,
99
+ `validate-sample` for the 10-row validation cascade, and
100
+ `awaiting-user-greenlight` for Settings. Do not advance `currentStep` backward.
91
101
  - After the shell is minted, normal resume paths read the CampaignOffer first.
92
102
  Use debug files to explain or reconstruct work, not to decide whether a
93
- post-mint gate is allowed. Validation/debug artifacts are not durable campaign
103
+ post-mint gate is allowed. validation/debug artifacts are not durable campaign
94
104
  state.
95
105
  - The campaign shell may receive setup state before import: approved brief text,
96
106
  campaign-attached provider searches/selections, the bounded review-batch
@@ -110,7 +120,7 @@ Validated draft directory:
110
120
  `request_user_input` (enabled in Default mode by
111
121
  `[features].default_mode_request_user_input = true`, not available in
112
122
  `codex exec`). Treat them as equivalent approval/intake gates and persist the
113
- same draft artifacts after the user answers. Use this structured gate only for
123
+ same campaign state after the user answers. Use this structured gate only for
114
124
  single-choice decisions with fixed options or approval gates. Every campaign
115
125
  setup gate must be single-choice in both hosts: set or assume
116
126
  `multiSelect: false`, use mutually exclusive option labels, and do not ask
@@ -163,13 +173,13 @@ Validated draft directory:
163
173
  or safe launch. Do not say "persist", "local draft folder", "artifact",
164
174
  "mkdir", "campaign thesis", or "same approved campaign thesis" in
165
175
  customer-facing progress copy.
166
- - Every approval gate must include artifact access after the readable inline
167
- content. Show a short `Open artifact:` line with the one key clickable
168
- markdown link for that stage. Do not show raw filesystem paths unless links
169
- cannot be created or the user asks. Do this for brief approval, lead-source
170
- approval/review, lead-filter review, and message review. Do not use the link
171
- as a substitute for rendering the decision in chat; links are for deeper
172
- inspection.
176
+ - Every approval gate must include live campaign access after the readable inline
177
+ content. Show a short `Watch link:` line with the campaign link when a
178
+ campaign shell exists. In normal customer runs, do not show `Open artifact:`
179
+ lines, raw filesystem paths, or local draft filenames. If the run is explicit
180
+ debug/UAT or the user asks for local artifacts, artifact links may be shown as
181
+ secondary diagnostics. The link is never a substitute for rendering the
182
+ decision in chat.
173
183
  - Any time the user is reviewing a list or decision, use rendered Markdown with
174
184
  short indexed sections and bullets. Do not use label-plus-paragraph blocks
175
185
  like `Key numbers:` followed by one long paragraph. Do not use fenced code
@@ -274,8 +284,9 @@ Validated draft directory:
274
284
  Before that first
275
285
  structured question gate, do not run source discovery, Sales Nav, Prospeo,
276
286
  Signals, Bash, Read, Write, Edit, Glob, Grep, full company research, or
277
- draft-directory inspection/creation. Do draft-directory setup only after the
278
- founder answers. After launch identity and any ambiguous campaign focus are
287
+ draft-directory inspection/creation. In normal customer runs, do not create a
288
+ draft directory after the founder answers; create/update the campaign shell and
289
+ campaign state instead. After launch identity and any ambiguous campaign focus are
279
290
  confirmed, the setup packet must
280
291
  use the structured question gate and ask buyer, offer/CTA, proof, and lead
281
292
  source. All four questions must include an `Other / custom` option.
@@ -314,14 +325,16 @@ Validated draft directory:
314
325
  Settings/final handoff.
315
326
 
316
327
  - Before asking for brief approval, render a slim approval brief in the chat and
317
- keep the rich current brief in `brief.md`. Use rendered Markdown directly:
328
+ write the rich current brief into `CampaignOffer.campaignBrief` through
329
+ `create_campaign`/`update_campaign`. Use rendered Markdown directly:
318
330
  `##` heading, bold field labels, numbered section labels, and short bullets.
319
331
  Do not wrap the user-facing approval view in a fenced code block because that
320
332
  creates horizontal scrolling in Claude Code and Codex. Do not ask the user to
321
333
  approve a one-paragraph direction, a risk note, or hidden artifact they cannot
322
334
  inspect. The chat approval view should be skimmable in under 45 seconds; full
323
- reasoning, source notes, and robustness stay in the artifacts. Include these
324
- sections, with short wrapped lines:
335
+ reasoning, source notes, and robustness can stay in optional debug artifacts
336
+ only during explicit debug/UAT runs. Include these sections, with short wrapped
337
+ lines:
325
338
 
326
339
  ```text
327
340
  ## Campaign brief
@@ -378,21 +391,19 @@ brief`, `Revise target`, `Revise offer/proof`, and `Other / custom`.
378
391
  not visible in this session, use `Approve brief + compare source paths`
379
392
  instead. The customer approves the sourcing decision, not the host
380
393
  implementation detail.
381
- Include an `Open artifact:` link to `brief.md` before the approval question.
382
- The visible brief must come before local persistence chrome. After the brief
383
- is synthesized, render the approval-ready brief in chat before running visible
384
- `mkdir`, `Write`, artifact-copy, or similar local draft setup. When the host
385
- supports sidecar/background work, let a background writer persist `brief.md`
386
- while the main thread keeps the brief review moving. If no background writer
387
- is available, write `brief.md` synchronously only after the visible brief is
388
- already in chat. File/folder creation is not the user's value moment; the
389
- brief is.
390
-
391
- The approval question can be based on the rendered brief in chat. Do not wait
392
- for file-write chrome before asking for approval. Before lead sourcing or any
393
- downstream step reads `brief.md`, reconcile that the persisted file matches
394
- the visible approved brief; if the sidecar is still running, wait quietly or
395
- finish the write synchronously at that boundary.
394
+ Include a `Watch link:` line before the approval question once
395
+ `create_campaign` has returned `watchUrl`. The visible brief must come before
396
+ any optional debug persistence. After the brief is synthesized, render the
397
+ approval-ready brief in chat and create/update the campaign shell before any
398
+ visible `mkdir`, `Write`, artifact-copy, or similar local draft setup. In
399
+ normal customer runs, skip local draft setup entirely. File/folder creation is
400
+ not the user's value moment; the live campaign brief is.
401
+
402
+ The approval question can be based on the rendered brief in chat and the live
403
+ campaign brief. Do not wait for file-write chrome before asking for approval.
404
+ Before lead sourcing, call `update_campaign({ campaignId, campaignBrief,
405
+ currentStep: "pick-provider" })` after approval so the watch link moves out of
406
+ Plan while the main thread compares source paths.
396
407
 
397
408
  - After the brief is approved, show the next progress line:
398
409
  `Cool. Now I'm going to find people who are both a good fit and active enough
@@ -418,26 +429,30 @@ message we should test.`
418
429
  show you the draft next so you can approve it or change it.
419
430
  ```
420
431
 
421
- - In hosted/rehearsal runs, `Bash` is only for safe local draft-directory
432
+ - In explicit debug/UAT runs, `Bash` is only for safe local draft-directory
422
433
  housekeeping before the import/test batch: `mkdir`, `ls`, `find`, `test`, `pwd`, `echo`,
423
- or `cat` inside the repo. Do not use
434
+ or `cat` inside the repo. In normal customer runs, do not use Bash for local
435
+ draft setup. Do not use
424
436
  Bash to run interpreters/scripts (`python`, `node`, `npm`, `pnpm`, `yarn`,
425
437
  `npx`), call APIs, query databases, inspect prompt dumps, run git, or
426
438
  synthesize artifact content. Use `Read`/`Write`/`Edit` for artifact files and
427
439
  MCP tools for product actions.
428
- - `brief.md` is the single user-facing debug copy of the brief. The
429
- `CampaignOffer.campaignBrief` field is the stable downstream thesis source.
430
- - `lead-review.md` and `lead-sample.json` are the required outputs of `find leads`.
431
- `lead-review.md` is customer-readable: describe source paths as provider
432
- lanes checked (Signals, Sales Nav, Prospeo) and never include custom agent
433
- names, host availability, install state, allowed-tool state, or fallback
434
- implementation details.
440
+ - `CampaignOffer.campaignBrief` is the stable downstream thesis source.
441
+ `brief.md` is an optional debug/UAT copy only; never make it the user-facing
442
+ approval surface in normal customer runs.
443
+ - The lead-source decision and lead sample must be rendered in chat and attached
444
+ to campaign state/search state. `lead-review.md` and `lead-sample.json` are
445
+ optional debug/UAT outputs of `find leads`; when they exist, describe source
446
+ paths as provider lanes checked (Signals, Sales Nav, Prospeo) and never
447
+ include custom agent names, host availability, install state, allowed-tool
448
+ state, or fallback implementation details.
435
449
  - `lead-filter.md` is the primary output of `filter leads`.
436
450
  - `rubric.json` is optional and secondary to `lead-filter.md`.
437
451
  - `message-prep.md` is an optional speed artifact produced by the explicit
438
452
  message path after find-leads. It can prepare proof inventory, buyer
439
453
  objections, CTA options, gold-standard strategy maps, and candidate angles
440
- from `brief.md`, `lead-review.md`, and `lead-sample.json`.
454
+ from the live campaign brief, source decision, lead sample, and optional debug
455
+ copies.
441
456
  - `message-candidate-drafts.md` is an optional provisional speed artifact from
442
457
  sample rows that already look like probable good fits. It can contain rough
443
458
  candidate messages and element tests, but it cannot select the final winner
@@ -448,10 +463,10 @@ message we should test.`
448
463
  message quality gate outputs between `message-validation.md` and
449
464
  `approval-packet.md`.
450
465
  - Run the dependency chain as a DAG: `create-campaign-brief` -> `find leads` ->
451
- bounded import/confirm. Once `lead-review.md`, `lead-sample.json`, and the
452
- campaign table `workflowTableId` exist, the normal path is the
466
+ bounded import/confirm. Once the source decision, lead sample, and campaign
467
+ table `workflowTableId` exist, the normal path is the
453
468
  `post-lead-workstreams` step. Launch `filter leads` and `message generation`
454
- from the same basis (`brief.md`, `lead-review.md`, `lead-sample.json`, and
469
+ from the same basis (live campaign brief, source decision, lead sample, and
455
470
  the imported review-batch rows) as separate workstreams when the host exposes
456
471
  the named Sellable post-find-leads agents or real background work.
457
472
  First call `get_post_find_leads_scout_registry` and use the returned
@@ -466,8 +481,8 @@ message we should test.`
466
481
  resume targets.
467
482
  - Message generation does not need `lead-filter.md` to start. The moment the
468
483
  bounded review batch is imported and `workflowTableId` is ready, start the
469
- message-generation workstream from `brief.md`, `lead-review.md`,
470
- `lead-sample.json`, and the imported campaign table sample. It can prepare
484
+ message-generation workstream from the live campaign brief, source decision,
485
+ lead sample, and the imported campaign table sample. It can prepare
471
486
  proof inventory, token strategy, and candidate angles while filter-leads
472
487
  tightens keep/exclude rules. Approval still waits for both `lead-filter.md`
473
488
  and `message-validation.md`, then reconciles that the selected message basis
@@ -512,10 +527,11 @@ workstreams`, `in parallel`, or `background` unless parallel branches were
512
527
  available. This is the two Task/Agent subagents path for the
513
528
  `post-lead-workstreams` step. This is the same registry pattern as source scouting, but the
514
529
  trigger is source approval and the join gate is both `lead-filter.md` and
515
- `message-validation.md` existing from the same `brief.md`, `lead-review.md`,
516
- and `lead-sample.json`.
517
- - Never run a downstream stage until the active `flow.v2.json` step's
518
- `requiredArtifacts` exist.
530
+ `message-validation.md` existing from the same live campaign brief/source
531
+ decision/lead-sample basis.
532
+ - Never run a downstream stage until the active `flow.v2.json` step's required
533
+ campaign state and required normal inputs exist. In normal customer runs, do
534
+ not create optional debug artifacts just to satisfy a file gate.
519
535
  - Never call a tool outside the active step's `allowedTools`, and never call a
520
536
  tool listed in the active step's `doNotAllow`.
521
537
  - Before the source is approved, `import_leads` and `confirm_lead_list` are
@@ -922,7 +938,7 @@ user-facing lead review. The visible response must include:
922
938
  usable prospects per post, and use/discard decision.
923
939
  - `Sample leads` with 3-5 representative `Name — Title, Company` rows
924
940
  - `Tradeoff`
925
- - `Open artifact: lead-review.md`
941
+ - `Watch link: {watchUrl}` when the campaign shell exists
926
942
 
927
943
  The first sentence of the visible decision must make the actual choice clear:
928
944
  `I recommend {primary source} using {exact filter/source recipe}. The runner-up
@@ -948,9 +964,9 @@ fresh, high-density posts when available, sample engagers, show fit rate, then
948
964
  state how many additional posts could be added/scraped if that first sample is
949
965
  good but volume is low.
950
966
 
951
- Keep discarded paths, full sample rows, and `lead-sample.json` details in
952
- artifacts. Do not show raw filesystem paths unless links cannot be created or
953
- the user asks.
967
+ Keep discarded paths and full sample rows in the campaign/source decision
968
+ context. In explicit debug/UAT runs they may also be copied into
969
+ `lead-sample.json`. Do not show raw filesystem paths unless the user asks.
954
970
 
955
971
  For supplied profile CSVs and existing lead lists, `lead-review.md` must not
956
972
  describe a generic TAM estimate or pretend the rows came from Sales Nav/Prospeo
@@ -60,10 +60,11 @@ Approvals only feel safe when the user can see what they are approving. Before
60
60
  any approve/revise question, show the relevant decision in plain language. For a
61
61
  brief approval, render the brief itself, not just a direction summary.
62
62
 
63
- Every approval should also give the user a way to inspect the source artifact.
64
- After the readable inline content, include an `Open artifacts:` line with links
65
- or plain paths to the files behind the decision. The artifact links are a backup
66
- for inspection, not a replacement for showing the content in chat.
63
+ Every approval should also give the user a way to inspect the live campaign.
64
+ After the readable inline content, include a `Watch link:` line when a campaign
65
+ shell exists. Local artifacts are optional debug/UAT diagnostics only; do not
66
+ surface `Open artifact:` links, file paths, or local draft filenames in normal
67
+ customer runs.
67
68
 
68
69
  This applies especially to message approvals. Never ask someone to approve a
69
70
  message they cannot see. Show the subject, tokenized template, a filled sample
@@ -138,9 +139,10 @@ because the campaign state is what the lead source, filters, and messages will
138
139
  build from. I’ll show you the draft next so you can approve it or change it.
139
140
  ```
140
141
 
141
- CampaignOffer state is canonical; disk artifacts are a debug trail. Keep the
142
- diagnostics for inspection, and all 14 disk artifacts remain as debug outputs,
143
- but resume, gating, and handoff read campaign state first.
142
+ CampaignOffer state and the watch link are canonical. Disk artifacts are
143
+ optional debug/UAT diagnostics. Normal customer runs should read, resume, gate,
144
+ and hand off from campaign state first, and should not expose local draft files
145
+ unless the user asks for them.
144
146
 
145
147
  Good opening:
146
148
 
@@ -1,7 +1,13 @@
1
1
  {
2
2
  "version": "v2",
3
3
  "workflow": "create-campaign-v2",
4
- "principle": "CampaignOffer state is canonical from the first brief onward. The flow still writes the 14 disk artifacts as a debug trail, but resume, gating, and handoff read campaign state first. Start from user intake, create a watchable campaign shell with a v1 brief, 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 10-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 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 10-row cascade and hand off to settings/sequence/start.",
5
+ "artifactPolicy": {
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
+ "debugArtifactsAreOptional": true,
8
+ "requiredArtifactsApplyWhen": "legacy resume, fixture validation, or explicit debug/UAT mode only",
9
+ "customerFacingAccess": "Watch link and live campaign currentStep"
10
+ },
5
11
  "commitGateChoices": [
6
12
  "approve",
7
13
  "revise-brief",
@@ -211,7 +217,7 @@
211
217
  "target": "create-campaign-brief"
212
218
  },
213
219
  {
214
- "action": "render_brief_preview_before_artifact_write",
220
+ "action": "render_brief_preview_before_debug_artifact_write",
215
221
  "requiredVisibleContent": [
216
222
  "Campaign brief",
217
223
  "Decision",
@@ -224,20 +230,21 @@
224
230
  "What happens next"
225
231
  ],
226
232
  "mustRunBefore": [
227
- "start_background_artifact_writer",
233
+ "optional_debug_artifact_writer",
228
234
  "Bash",
229
235
  "mkdir"
230
236
  ],
231
- "chatRenderRule": "After synthesizing the brief, render the approval-ready brief in chat before any visible local folder creation, file write, or artifact copy. The user should see useful brief content before local persistence chrome. Do not ask the user to approve a hidden artifact."
237
+ "chatRenderRule": "After synthesizing the brief, render the approval-ready brief in chat before any optional local folder creation, file write, or artifact copy. Normal customer runs should not create local draft files here. The user should see useful brief content and live campaign state, not local persistence chrome. Do not ask the user to approve a hidden artifact."
232
238
  },
233
239
  {
234
- "action": "start_background_artifact_writer",
240
+ "action": "optional_debug_artifact_writer",
235
241
  "artifact": "brief.md",
236
- "mode": "sidecar_when_host_supports_background_agents",
242
+ "mode": "debug_or_uat_only",
243
+ "shouldNotRunInNormalCustomerPath": true,
237
244
  "shouldNotBlockMainThread": true,
238
245
  "allowedToRunWhileUserReviewsInlineBrief": true,
239
- "fallback": "If no background writer is available, write brief.md synchronously only after the approval-ready brief is already visible in chat.",
240
- "chatRenderRule": "Do not surface folder/file-write progress in chat unless the write fails or the user asks. The main thread should keep the brief review moving while the sidecar persists brief.md."
246
+ "fallback": "If debug/UAT artifacts are requested and no background writer is available, write brief.md synchronously only after the approval-ready brief is already visible in chat. In normal customer runs, skip the write.",
247
+ "chatRenderRule": "Do not surface folder/file-write progress in chat unless the write fails or the user asks. The main thread should keep the brief review moving from campaign state."
241
248
  },
242
249
  {
243
250
  "action": "create_watchable_campaign_shell_with_v1_brief",
@@ -300,14 +307,19 @@
300
307
  "campaignBrief"
301
308
  ],
302
309
  "watchUrlSource": "create_campaign.watchUrl",
310
+ "requiredWatchUrlShape": "signed /auth/continue?token=...&redirect=/campaign-builder/{campaignId}?mode=claude",
311
+ "codexBrowserHandoff": {
312
+ "openWhenAvailable": true,
313
+ "inspectBeforeContinuing": true,
314
+ "explainVisibleStateBeforeContinuing": true,
315
+ "fallbackMustNotClaimInspection": true,
316
+ "recoverFreshSignedLinkOnceOnOpenFailure": true
317
+ },
303
318
  "immediateNextMainChatLine": "Cool, let's find leads."
304
319
  }
305
320
  ],
306
321
  "requiredArtifacts": [],
307
- "producesArtifacts": [
308
- "brief.md",
309
- "campaign-shell.json"
310
- ],
322
+ "producesArtifacts": [],
311
323
  "allowedTools": [
312
324
  "get_subskill_prompt",
313
325
  "get_subskill_asset",
@@ -359,7 +371,8 @@
359
371
  "onEnter": [
360
372
  {
361
373
  "action": "show_brief_summary",
362
- "artifact": "brief.md"
374
+ "source": "campaignBrief",
375
+ "fallbackDebugArtifact": "brief.md"
363
376
  },
364
377
  {
365
378
  "action": "render_brief_approval_checkpoint",
@@ -378,10 +391,15 @@
378
391
  "then I will find good-fit leads"
379
392
  ],
380
393
  "minimumVisibleBriefDetail": "full_readable_brief_before_question",
381
- "requiredArtifactLinks": [
382
- "brief.md"
394
+ "requiredCampaignLinks": [
395
+ "watchUrl"
396
+ ],
397
+ "campaignLinkTiming": "before_approval_question",
398
+ "forbiddenNormalCustomerLinks": [
399
+ "brief.md",
400
+ "local draft path",
401
+ "Open artifact:"
383
402
  ],
384
- "artifactLinkTiming": "when_ready_before_downstream",
385
403
  "avoidQuestionWhenOnlyUsefulAnswerIs": "looks good"
386
404
  },
387
405
  {
@@ -397,13 +415,14 @@
397
415
  }
398
416
  },
399
417
  {
400
- "action": "ensure_background_artifact_ready",
418
+ "action": "optional_debug_artifact_sync",
401
419
  "artifacts": [
402
420
  "brief.md"
403
421
  ],
404
- "requiredBeforeTransition": "find-leads",
405
- "reconcileRule": "Before lead sourcing reads brief.md, verify the sidecar persisted the same brief shown in chat. If the sidecar is still running, wait quietly or finish the write synchronously; if it differs, reconcile to the visible approved brief.",
406
- "chatRenderRule": "Do not block the brief approval question on this step. Only mention artifact persistence if it fails or needs user action."
422
+ "debugOrUatOnly": true,
423
+ "requiredBeforeTransition": false,
424
+ "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.",
425
+ "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."
407
426
  },
408
427
  {
409
428
  "action": "sync_approved_brief_to_campaign_shell",
@@ -415,7 +434,7 @@
415
434
  "when": "after_user_brief_confirmed_or_auto_continue",
416
435
  "fields": [
417
436
  "campaignBrief",
418
- "currentStep:create-offer"
437
+ "currentStep:pick-provider"
419
438
  ],
420
439
  "fallback": "If campaignId is missing from CampaignOffer state, stop; shell-first flow requires an existing campaign before sourcing leads.",
421
440
  "readsCampaignStateFirst": true,
@@ -425,9 +444,7 @@
425
444
  ]
426
445
  }
427
446
  ],
428
- "requiredArtifacts": [
429
- "brief.md"
430
- ],
447
+ "requiredArtifacts": [],
431
448
  "producesArtifacts": [],
432
449
  "allowedTools": [
433
450
  "AskUserQuestion",
@@ -467,6 +484,19 @@
467
484
  "id": "find-leads",
468
485
  "label": "Find leads",
469
486
  "onEnter": [
487
+ {
488
+ "action": "advance_watch_to_source_selection",
489
+ "tool": "update_campaign",
490
+ "requiredFields": [
491
+ "campaignId",
492
+ "currentStep"
493
+ ],
494
+ "requiredValues": {
495
+ "currentStep": "pick-provider"
496
+ },
497
+ "when": "before_source_scouts_or_provider_search",
498
+ "chatRenderRule": "Move the campaign watch view to Find Contacts before the main thread starts comparing source paths. Do not mention MCP or local artifacts."
499
+ },
470
500
  {
471
501
  "action": "render_find_leads_progress",
472
502
  "requiredVisibleContent": [
@@ -505,20 +535,18 @@
505
535
  "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."
506
536
  },
507
537
  {
508
- "action": "write_artifacts",
538
+ "action": "optional_debug_artifacts",
509
539
  "artifacts": [
510
540
  "lead-review.md",
511
541
  "lead-sample.json"
512
- ]
542
+ ],
543
+ "debugOrUatOnly": true,
544
+ "shouldNotRunInNormalCustomerPath": true,
545
+ "chatRenderRule": "Do not create or surface local lead-review artifacts in normal customer runs. The main thread renders the source decision in chat and persists provider/source state to the campaign."
513
546
  }
514
547
  ],
515
- "requiredArtifacts": [
516
- "brief.md"
517
- ],
518
- "producesArtifacts": [
519
- "lead-review.md",
520
- "lead-sample.json"
521
- ],
548
+ "requiredArtifacts": [],
549
+ "producesArtifacts": [],
522
550
  "allowedTools": [
523
551
  "get_subskill_prompt",
524
552
  "get_subskill_asset",
@@ -1319,7 +1347,7 @@
1319
1347
  "Question: approve-message or revise-messaging?",
1320
1348
  "Recommendation:"
1321
1349
  ],
1322
- "chatRenderRule": "Show a slim rendered-Markdown message review, never a fenced code block. Use ## Message review plus indexed sections and short bullets. Show one fully filled sample message with no {{tokens}} before asking for approval. Include subject, sample message, why it should work, one concern or None, recommendation, and Open artifact: message-review.md. Keep tokenized template, token fill basis, rendered examples, good/bad token fill examples, validation notes, and message-validation.md details in artifacts. Do not show plain filesystem paths unless links cannot be created.",
1350
+ "chatRenderRule": "Show a slim rendered-Markdown message review, never a fenced code block. Use ## Message review plus indexed sections and short bullets. Show one fully filled sample message with no {{tokens}} before asking for approval. Include subject, sample message, why it should work, one concern or None, recommendation, and Watch link when a campaign shell exists. Keep tokenized template, token fill basis, rendered examples, good/bad token fill examples, validation notes, and message-validation.md details in campaign state or optional debug artifacts. Do not show plain filesystem paths unless the user asks.",
1323
1351
  "allowedRecommendations": [
1324
1352
  "approve-message",
1325
1353
  "revise-messaging"
@@ -1395,13 +1423,26 @@
1395
1423
  "when": "after_message_approved_before_import",
1396
1424
  "fields": [
1397
1425
  "campaignBrief with ## Approved Message Template, Token Fill Rules, Token Fill Examples",
1398
- "currentStep:validate-sample",
1399
1426
  "useMessagingTemplate:true"
1400
1427
  ],
1401
1428
  "requiredBeforeImport": false,
1402
1429
  "onFailure": "stop_before_import_or_enrichment",
1403
1430
  "requiredBeforeCascade": true,
1404
1431
  "writesCampaignState": "campaignBrief.Approved Message Template"
1432
+ },
1433
+ {
1434
+ "action": "advance_watch_to_validation_after_message_approval",
1435
+ "tool": "update_campaign",
1436
+ "requires": [
1437
+ "campaignId",
1438
+ "message-review-decision.md"
1439
+ ],
1440
+ "requiredValues": {
1441
+ "currentStep": "validate-sample"
1442
+ },
1443
+ "when": "after_update_campaign_brief_succeeds",
1444
+ "requiredBeforeCascade": true,
1445
+ "writesCampaignState": "currentStep:validate-sample"
1405
1446
  }
1406
1447
  ],
1407
1448
  "requiredArtifacts": [
@@ -1419,7 +1460,8 @@
1419
1460
  "AskUserQuestion",
1420
1461
  "request_user_input",
1421
1462
  "get_subskill_prompt",
1422
- "update_campaign_brief"
1463
+ "update_campaign_brief",
1464
+ "update_campaign"
1423
1465
  ],
1424
1466
  "doNotAllow": [
1425
1467
  "create_campaign",
@@ -5,10 +5,11 @@ runs do not route through `approval-packet`, `commit-gate`, or `atomic-mint`.
5
5
  Load this file only when resuming a compatibility run that already has those
6
6
  debug artifacts.
7
7
 
8
- CampaignOffer state is canonical; disk artifacts are a debug trail. All 14 disk
9
- artifacts remain as debug outputs, but resume, gating, and handoff read campaign
10
- state first. In new runs, the live campaign object appears after the short brief,
11
- not after this approval packet.
8
+ CampaignOffer state and the watch link are canonical. Disk artifacts are
9
+ optional debug/UAT diagnostics, and normal customer runs should not expose local
10
+ draft files. Resume, gating, and handoff read campaign state first. In new runs,
11
+ the live campaign object appears after the short brief, not after this approval
12
+ packet.
12
13
 
13
14
  ## Purpose
14
15
 
@@ -1,8 +1,9 @@
1
1
  # Filter Leads
2
2
 
3
- CampaignOffer state is canonical; disk artifacts are a debug trail. All 14 disk
4
- artifacts remain as debug outputs, but resume, gating, and handoff read campaign
5
- state first. Filter design may use the debug sample, but the active rubric save
3
+ CampaignOffer state and the watch link are canonical. Disk artifacts are
4
+ optional debug/UAT diagnostics, and normal customer runs should not expose local
5
+ draft files. Resume, gating, and handoff read campaign state first. Filter design
6
+ may use the live lead sample or optional debug sample, but the active rubric save
6
7
  is `save_rubrics({ campaignOfferId, leadScoringRubrics }) after the campaign
7
8
  table exists`.
8
9
 
@@ -14,10 +14,11 @@ that decision — UI and Claude greenlight — and the skill must handle both
14
14
  cleanly, including the case where one channel has already started the
15
15
  campaign before the other arrives.
16
16
 
17
- CampaignOffer state is canonical; disk artifacts are a debug trail. All 14 disk
18
- artifacts remain as debug outputs, but resume, gating, and handoff read campaign
19
- state first. At this point, `workflowTableId` is the campaign table, while
20
- `selectedLeadListId` remains the source list that produced the review batch.
17
+ CampaignOffer state and the watch link are canonical. Disk artifacts are
18
+ optional debug/UAT diagnostics, and normal customer runs should not expose local
19
+ draft files. Resume, gating, and handoff read campaign state first. At this
20
+ point, `workflowTableId` is the campaign table, while `selectedLeadListId`
21
+ remains the source list that produced the review batch.
21
22
 
22
23
  ## Step 16 Setup
23
24
 
@@ -6,9 +6,9 @@ Step 13.
6
6
 
7
7
  ## Principle
8
8
 
9
- CampaignOffer state is canonical; disk artifacts are a debug trail. All 14 disk
10
- artifacts remain as debug outputs, but resume, gating, and handoff read campaign
11
- state first.
9
+ CampaignOffer state and the watch link are canonical. Disk artifacts are
10
+ optional debug/UAT diagnostics, and normal customer runs should not expose local
11
+ draft files. Resume, gating, and handoff read campaign state first.
12
12
 
13
13
  Bounded review-batch sourcing + import happens after the existing campaign
14
14
  shell has the selected source attached with `campaignOfferId`. It happens before
@@ -6,8 +6,9 @@ message direction hold up against real preview evidence.
6
6
 
7
7
  ## Primary Contract
8
8
 
9
- - CampaignOffer state is canonical; disk artifacts are a debug trail.
10
- - validation/debug artifacts are not durable campaign state.
9
+ - CampaignOffer state and the watch link are canonical.
10
+ - validation/debug artifacts are optional debug/UAT diagnostics, not durable campaign state.
11
+ - normal customer runs should not expose local draft files.
11
12
  - resume and handoff read campaign state before debug files.
12
13
  - `CampaignOffer.campaignBrief` is the durable Phase 83 thesis input;
13
14
  `brief.md` is its debug copy
@@ -4,9 +4,10 @@ This reference governs how create-campaign-v2 surfaces the watch link in the
4
4
  shell-first flow. Load it before showing the link and on every resume where the
5
5
  link needs to be re-surfaced.
6
6
 
7
- CampaignOffer state is canonical; disk artifacts are a debug trail. All 14 disk
8
- artifacts remain as debug outputs, but resume, gating, and handoff read campaign
9
- state first.
7
+ CampaignOffer state and the watch link are canonical. Disk artifacts are
8
+ optional debug/UAT diagnostics; normal customer runs should not create, link, or
9
+ surface local draft files unless the user explicitly asks for them. Resume,
10
+ gating, and handoff read campaign state first.
10
11
 
11
12
  ## Plumbing Reuse
12
13
 
@@ -61,8 +62,9 @@ Stop before `save_rubrics` and recover the missing signed URL first.
61
62
 
62
63
  ## Step Orientation
63
64
 
64
- After the shell exists, re-surface the same watch link only when it helps orient
65
- the user after a meaningful step change:
65
+ After the shell exists, the main thread must update `currentStep` before each
66
+ meaningful visible stage, then re-surface the same watch link only when it helps
67
+ orient the user after a meaningful step change:
66
68
 
67
69
  - source selected: the campaign should point at the primary provider step
68
70
  - rubrics saved: the campaign should show filter/rubric state
@@ -8,10 +8,11 @@ visibility: internal
8
8
 
9
9
  This is the tail detail extracted from the main create-campaign-v2 SKILL.md to keep the entry prompt slim. The agent loads this on-demand BEFORE entering the auto-execute or validate-sample steps. Follow this verbatim.
10
10
 
11
- CampaignOffer state is canonical; disk artifacts are a debug trail. All 14 disk
12
- artifacts remain as debug outputs, but resume, gating, and handoff read campaign
13
- state first. selectedLeadListId remains the source list; workflowTableId is the
14
- campaign table.
11
+ CampaignOffer state and the watch link are canonical. Disk artifacts are
12
+ optional debug/UAT diagnostics; normal customer runs should not expose local
13
+ draft files. Resume, gating, and handoff read campaign state first.
14
+ selectedLeadListId remains the source list; workflowTableId is the campaign
15
+ table.
15
16
 
16
17
  ## MANDATORY TOOL ORDER (read this BEFORE any tail step)
17
18