@sellable/mcp 0.1.309 → 0.1.310

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.
@@ -11,7 +11,7 @@ type LeadSourceProvider = (typeof LEAD_SOURCE_PROVIDERS)[keyof typeof LEAD_SOURC
11
11
  export declare function buildWatchUrl(config: Pick<ReturnType<typeof getConfig>, "apiUrl" | "token" | "activeWorkspaceId" | "workspaceId">, path: string): string;
12
12
  export type CampaignBuilderWatchMode = "claude" | "codex";
13
13
  export declare function getCampaignBuilderWatchModeParam(): CampaignBuilderWatchMode;
14
- export declare function getCampaignBuilderWatchModeDriverLabel(mode?: CampaignBuilderWatchMode): "Claude Code" | "Codex";
14
+ export declare function getCampaignBuilderWatchModeDriverLabel(mode?: CampaignBuilderWatchMode): "Codex" | "Claude Code";
15
15
  export declare function buildCampaignWatchHandoffMarkdown(watchUrl: string, mode?: CampaignBuilderWatchMode): string;
16
16
  export interface Campaign {
17
17
  id: string;
@@ -416,6 +416,90 @@ export declare const campaignToolDefinitions: ({
416
416
  };
417
417
  workerStatuses: {
418
418
  type: string;
419
+ description: string;
420
+ properties: {
421
+ leadFitBuilder: {
422
+ type: string[];
423
+ enum: (string | null)[];
424
+ };
425
+ messageDraftBuilder: {
426
+ type: string[];
427
+ enum: (string | null)[];
428
+ };
429
+ };
430
+ additionalProperties: boolean;
431
+ };
432
+ workerDetails: {
433
+ type: string;
434
+ description: string;
435
+ properties: {
436
+ messageDraftBuilder: {
437
+ type: string[];
438
+ description: string;
439
+ properties: {
440
+ statusSource: {
441
+ type: string;
442
+ enum: string[];
443
+ };
444
+ status: {
445
+ type: string;
446
+ enum: string[];
447
+ };
448
+ runId: {
449
+ type: string[];
450
+ };
451
+ fallbackId: {
452
+ type: string[];
453
+ };
454
+ startedAt: {
455
+ type: string;
456
+ };
457
+ updatedAt: {
458
+ type: string;
459
+ };
460
+ basisToken: {
461
+ type: string[];
462
+ };
463
+ basis: {
464
+ type: string;
465
+ properties: {
466
+ campaignId: {
467
+ type: string[];
468
+ };
469
+ selectedLeadListId: {
470
+ type: string[];
471
+ };
472
+ workflowTableId: {
473
+ type: string[];
474
+ };
475
+ reviewBatchRowHash: {
476
+ type: string[];
477
+ };
478
+ reviewBatchRowIds: {
479
+ type: string[];
480
+ items: {
481
+ type: string;
482
+ };
483
+ };
484
+ filterChoice: {
485
+ type: string[];
486
+ };
487
+ };
488
+ };
489
+ messageDraftOutputRef: {
490
+ type: string[];
491
+ };
492
+ messageDraftOutput: {
493
+ type: string[];
494
+ };
495
+ error: {
496
+ type: string[];
497
+ };
498
+ };
499
+ required: string[];
500
+ };
501
+ };
502
+ additionalProperties: boolean;
419
503
  };
420
504
  };
421
505
  required: string[];
@@ -531,6 +615,90 @@ export declare const campaignToolDefinitions: ({
531
615
  };
532
616
  workerStatuses: {
533
617
  type: string;
618
+ description: string;
619
+ properties: {
620
+ leadFitBuilder: {
621
+ type: string[];
622
+ enum: (string | null)[];
623
+ };
624
+ messageDraftBuilder: {
625
+ type: string[];
626
+ enum: (string | null)[];
627
+ };
628
+ };
629
+ additionalProperties: boolean;
630
+ };
631
+ workerDetails: {
632
+ type: string;
633
+ description: string;
634
+ properties: {
635
+ messageDraftBuilder: {
636
+ type: string[];
637
+ description: string;
638
+ properties: {
639
+ statusSource: {
640
+ type: string;
641
+ enum: string[];
642
+ };
643
+ status: {
644
+ type: string;
645
+ enum: string[];
646
+ };
647
+ runId: {
648
+ type: string[];
649
+ };
650
+ fallbackId: {
651
+ type: string[];
652
+ };
653
+ startedAt: {
654
+ type: string;
655
+ };
656
+ updatedAt: {
657
+ type: string;
658
+ };
659
+ basisToken: {
660
+ type: string[];
661
+ };
662
+ basis: {
663
+ type: string;
664
+ properties: {
665
+ campaignId: {
666
+ type: string[];
667
+ };
668
+ selectedLeadListId: {
669
+ type: string[];
670
+ };
671
+ workflowTableId: {
672
+ type: string[];
673
+ };
674
+ reviewBatchRowHash: {
675
+ type: string[];
676
+ };
677
+ reviewBatchRowIds: {
678
+ type: string[];
679
+ items: {
680
+ type: string;
681
+ };
682
+ };
683
+ filterChoice: {
684
+ type: string[];
685
+ };
686
+ };
687
+ };
688
+ messageDraftOutputRef: {
689
+ type: string[];
690
+ };
691
+ messageDraftOutput: {
692
+ type: string[];
693
+ };
694
+ error: {
695
+ type: string[];
696
+ };
697
+ };
698
+ required: string[];
699
+ };
700
+ };
701
+ additionalProperties: boolean;
534
702
  };
535
703
  };
536
704
  required: string[];
@@ -10,6 +10,99 @@ const LEAD_SOURCE_PROVIDERS = {
10
10
  PROSPEO: "prospeo",
11
11
  SIGNAL_DISCOVERY: "signal-discovery",
12
12
  };
13
+ const WATCH_NARRATION_TOOL_SCHEMA = {
14
+ type: "object",
15
+ description: "Single structured watch-guide narration object to send with currentStep changes. Include what just happened, what the browser is showing now, what Codex is doing next, and nextAction so the browser guide mirrors Codex progress. Do not send separate guideHeadline/guideBody fields. Use workerStatuses only for simple string badges; put Message Drafting runtime proof under workerDetails.messageDraftBuilder.",
16
+ properties: {
17
+ stage: {
18
+ type: "string",
19
+ enum: [
20
+ "brief-review",
21
+ "find-leads",
22
+ "review-batch",
23
+ "fit-message",
24
+ "review-ready",
25
+ ],
26
+ },
27
+ headline: { type: "string" },
28
+ visibleState: { type: "string" },
29
+ agentIntent: { type: "string" },
30
+ nextAction: { type: ["string", "null"] },
31
+ safety: { type: ["string", "null"] },
32
+ progressLabel: { type: ["string", "null"] },
33
+ blockedReason: { type: ["string", "null"] },
34
+ workerStatuses: {
35
+ type: "object",
36
+ description: 'Simple display badges only. Allowed keys are leadFitBuilder and messageDraftBuilder; values are "idle", "running", "ready", "blocked", or null. Do not put runId, statusSource, basis, or rich worker proof here.',
37
+ properties: {
38
+ leadFitBuilder: {
39
+ type: ["string", "null"],
40
+ enum: ["idle", "running", "ready", "blocked", null],
41
+ },
42
+ messageDraftBuilder: {
43
+ type: ["string", "null"],
44
+ enum: ["idle", "running", "ready", "blocked", null],
45
+ },
46
+ },
47
+ additionalProperties: false,
48
+ },
49
+ workerDetails: {
50
+ type: "object",
51
+ description: "Rich runtime proof for background workers. Message Drafting proof must use workerDetails.messageDraftBuilder, not workerStatuses.messageDrafting.",
52
+ properties: {
53
+ messageDraftBuilder: {
54
+ type: ["object", "null"],
55
+ description: 'Runtime proof that Message Drafting actually started or fell back inline. For a spawned background worker use statusSource "branch" and status "branch-running"; for inline fallback use statusSource "parent-thread-fallback" and status "fallback-active". The basis must include selectedLeadListId, workflowTableId, and either reviewBatchRowHash or reviewBatchRowIds so the watched UI can trust the current execution slice.',
56
+ properties: {
57
+ statusSource: {
58
+ type: "string",
59
+ enum: ["branch", "parent-thread-fallback"],
60
+ },
61
+ status: {
62
+ type: "string",
63
+ enum: [
64
+ "branch-running",
65
+ "fallback-active",
66
+ "spawn-failed",
67
+ "fallback-superseded",
68
+ "branch-superseded",
69
+ "ready",
70
+ "blocked",
71
+ "retry-needed",
72
+ "stale",
73
+ ],
74
+ },
75
+ runId: { type: ["string", "null"] },
76
+ fallbackId: { type: ["string", "null"] },
77
+ startedAt: { type: "string" },
78
+ updatedAt: { type: "string" },
79
+ basisToken: { type: ["string", "null"] },
80
+ basis: {
81
+ type: "object",
82
+ properties: {
83
+ campaignId: { type: ["string", "null"] },
84
+ selectedLeadListId: { type: ["string", "null"] },
85
+ workflowTableId: { type: ["string", "null"] },
86
+ reviewBatchRowHash: { type: ["string", "null"] },
87
+ reviewBatchRowIds: {
88
+ type: ["array", "null"],
89
+ items: { type: "string" },
90
+ },
91
+ filterChoice: { type: ["string", "null"] },
92
+ },
93
+ },
94
+ messageDraftOutputRef: { type: ["string", "null"] },
95
+ messageDraftOutput: { type: ["object", "null"] },
96
+ error: { type: ["string", "null"] },
97
+ },
98
+ required: ["statusSource", "status", "startedAt", "updatedAt", "basis"],
99
+ },
100
+ },
101
+ additionalProperties: false,
102
+ },
103
+ },
104
+ required: ["stage", "headline", "visibleState", "agentIntent", "nextAction"],
105
+ };
13
106
  function normalizeLeadSourceProvider(input) {
14
107
  if (!input)
15
108
  return null;
@@ -236,37 +329,7 @@ export const campaignToolDefinitions = [
236
329
  type: ["string", "null"],
237
330
  description: "Workflow step ID (headless or UI step ID such as filter-rules)",
238
331
  },
239
- watchNarration: {
240
- type: "object",
241
- description: "Single structured watch-guide narration object to send with currentStep changes. Include what just happened, what the browser is showing now, what Codex is doing next, and nextAction so the browser guide mirrors Codex progress.",
242
- properties: {
243
- stage: {
244
- type: "string",
245
- enum: [
246
- "brief-review",
247
- "find-leads",
248
- "review-batch",
249
- "fit-message",
250
- "review-ready",
251
- ],
252
- },
253
- headline: { type: "string" },
254
- visibleState: { type: "string" },
255
- agentIntent: { type: "string" },
256
- nextAction: { type: ["string", "null"] },
257
- safety: { type: ["string", "null"] },
258
- progressLabel: { type: ["string", "null"] },
259
- blockedReason: { type: ["string", "null"] },
260
- workerStatuses: { type: "object" },
261
- },
262
- required: [
263
- "stage",
264
- "headline",
265
- "visibleState",
266
- "agentIntent",
267
- "nextAction",
268
- ],
269
- },
332
+ watchNarration: WATCH_NARRATION_TOOL_SCHEMA,
270
333
  leadSourceType: {
271
334
  type: ["string", "null"],
272
335
  description: "Lead source type (existing | new)",
@@ -344,37 +407,7 @@ export const campaignToolDefinitions = [
344
407
  type: "string",
345
408
  description: "Guarded manual recovery clear. currentStep:null is accepted only when this matches the durable step being cleared.",
346
409
  },
347
- watchNarration: {
348
- type: "object",
349
- description: "Single structured watch-guide narration object to send with currentStep changes. Include what just happened, what the browser is showing now, what Codex is doing next, and nextAction so the browser guide mirrors Codex progress. Do not send separate guideHeadline/guideBody fields.",
350
- properties: {
351
- stage: {
352
- type: "string",
353
- enum: [
354
- "brief-review",
355
- "find-leads",
356
- "review-batch",
357
- "fit-message",
358
- "review-ready",
359
- ],
360
- },
361
- headline: { type: "string" },
362
- visibleState: { type: "string" },
363
- agentIntent: { type: "string" },
364
- nextAction: { type: ["string", "null"] },
365
- safety: { type: ["string", "null"] },
366
- progressLabel: { type: ["string", "null"] },
367
- blockedReason: { type: ["string", "null"] },
368
- workerStatuses: { type: "object" },
369
- },
370
- required: [
371
- "stage",
372
- "headline",
373
- "visibleState",
374
- "agentIntent",
375
- "nextAction",
376
- ],
377
- },
410
+ watchNarration: WATCH_NARRATION_TOOL_SCHEMA,
378
411
  interactionMode: {
379
412
  type: "string",
380
413
  enum: ["step-by-step", "autonomous", "ask-when-needed"],
@@ -12,31 +12,7 @@ export type CampaignModelQualityResult = {
12
12
  reasoningEffort: string | null;
13
13
  recommendedModel: string;
14
14
  recommendedReasoningEffort: string;
15
- minimumSummary: string;
16
15
  confirmationRequired: boolean;
17
16
  message: string;
18
17
  };
19
- export type CampaignModelQualityHostConfig = {
20
- label: string;
21
- minimumModel: string;
22
- familyKeywords: string[];
23
- minimumVersion: string;
24
- minimumReasoningEffort: string;
25
- acceptedReasoningEfforts: string[];
26
- recommendedReasoningEffort: string;
27
- };
28
- export type CampaignModelQualityConfig = {
29
- version: number;
30
- hosts: {
31
- claude: CampaignModelQualityHostConfig;
32
- codex: CampaignModelQualityHostConfig;
33
- };
34
- warningCopy: {
35
- ok: string;
36
- unknownSettings: string;
37
- belowMinimum: string;
38
- };
39
- };
40
- export declare function getCampaignModelQualityConfig(): CampaignModelQualityConfig;
41
- export declare function getCampaignModelMinimumSummary(config?: CampaignModelQualityConfig): string;
42
18
  export declare function evaluateCampaignModelQuality(input?: CampaignModelQualityInput): CampaignModelQualityResult;
@@ -1,57 +1,9 @@
1
- import { existsSync, readFileSync } from "fs";
2
- import { join } from "path";
3
- import { resolveSkillsDir } from "../skills.js";
4
- const DEFAULT_MODEL_QUALITY_CONFIG = {
5
- version: 1,
6
- hosts: {
7
- claude: {
8
- label: "Claude Code",
9
- minimumModel: "Claude Opus 4.8",
10
- familyKeywords: ["opus"],
11
- minimumVersion: "4.8",
12
- minimumReasoningEffort: "high",
13
- acceptedReasoningEfforts: [
14
- "high",
15
- "extra high",
16
- "extra-high",
17
- "xhigh",
18
- "extra_high",
19
- ],
20
- recommendedReasoningEffort: "high or better",
21
- },
22
- codex: {
23
- label: "Codex",
24
- minimumModel: "GPT 5.5",
25
- familyKeywords: ["gpt"],
26
- minimumVersion: "5.5",
27
- minimumReasoningEffort: "high",
28
- acceptedReasoningEfforts: [
29
- "high",
30
- "extra high",
31
- "extra-high",
32
- "xhigh",
33
- "extra_high",
34
- ],
35
- recommendedReasoningEffort: "high or better",
36
- },
37
- },
38
- warningCopy: {
39
- ok: "Campaign model settings meet the configured minimum: {minimumSummary}.",
40
- unknownSettings: "Model settings were not provided by the host. Best campaigns need at least {minimumSummary}. Confirm the user is on one of those settings, or ask them to switch before continuing.",
41
- belowMinimum: "Best campaigns need at least {minimumSummary}. Current settings look below that: {currentSettings}. Please switch before continuing, or explicitly say to continue anyway.",
42
- },
43
- };
44
- let cachedConfig = null;
45
1
  const normalize = (value) => String(value ?? "")
46
2
  .trim()
47
3
  .toLowerCase();
48
4
  const normalizeHost = (host) => {
49
5
  const normalized = normalize(host);
50
- if (normalized.includes("claude") ||
51
- normalized.includes("clod") ||
52
- normalized.includes("opus") ||
53
- normalized.includes("sonnet") ||
54
- normalized.includes("haiku")) {
6
+ if (normalized.includes("claude") || normalized.includes("clod")) {
55
7
  return "claude";
56
8
  }
57
9
  if (normalized.includes("codex") ||
@@ -61,101 +13,20 @@ const normalizeHost = (host) => {
61
13
  }
62
14
  return "unknown";
63
15
  };
64
- const normalizeReasoning = (reasoning) => normalize(reasoning).replace(/[_\s-]+/g, "");
65
- const compareVersion = (candidate, minimum) => {
66
- const candidateParts = candidate.split(".").map((part) => Number(part));
67
- const minimumParts = minimum.split(".").map((part) => Number(part));
68
- const length = Math.max(candidateParts.length, minimumParts.length);
69
- for (let index = 0; index < length; index += 1) {
70
- const candidatePart = candidateParts[index] ?? 0;
71
- const minimumPart = minimumParts[index] ?? 0;
72
- if (candidatePart > minimumPart)
73
- return 1;
74
- if (candidatePart < minimumPart)
75
- return -1;
76
- }
77
- return 0;
16
+ const isHighClaudeReasoning = (reasoning) => {
17
+ const normalized = normalize(reasoning).replace(/[_\s-]+/g, "");
18
+ return ["high", "xhigh", "extrahigh"].includes(normalized);
19
+ };
20
+ const isExtraHighCodexReasoning = (reasoning) => {
21
+ const normalized = normalize(reasoning).replace(/[_\s-]+/g, "");
22
+ return ["xhigh", "extrahigh"].includes(normalized);
78
23
  };
79
- const extractModelVersions = (model) => Array.from(normalize(model).matchAll(/\b(\d+(?:\.\d+){0,2})\b/g)).map((match) => match[1]);
80
- function mergeConfig(rawConfig) {
81
- return {
82
- ...DEFAULT_MODEL_QUALITY_CONFIG,
83
- ...rawConfig,
84
- hosts: {
85
- claude: {
86
- ...DEFAULT_MODEL_QUALITY_CONFIG.hosts.claude,
87
- ...(rawConfig.hosts?.claude || {}),
88
- },
89
- codex: {
90
- ...DEFAULT_MODEL_QUALITY_CONFIG.hosts.codex,
91
- ...(rawConfig.hosts?.codex || {}),
92
- },
93
- },
94
- warningCopy: {
95
- ...DEFAULT_MODEL_QUALITY_CONFIG.warningCopy,
96
- ...(rawConfig.warningCopy || {}),
97
- },
98
- };
99
- }
100
- export function getCampaignModelQualityConfig() {
101
- if (cachedConfig)
102
- return cachedConfig;
103
- const configPath = join(resolveSkillsDir(), "create-campaign", "core", "model-quality.json");
104
- if (!existsSync(configPath)) {
105
- cachedConfig = DEFAULT_MODEL_QUALITY_CONFIG;
106
- return cachedConfig;
107
- }
108
- try {
109
- cachedConfig = mergeConfig(JSON.parse(readFileSync(configPath, "utf-8")));
110
- }
111
- catch {
112
- cachedConfig = DEFAULT_MODEL_QUALITY_CONFIG;
113
- }
114
- return cachedConfig;
115
- }
116
- export function getCampaignModelMinimumSummary(config = getCampaignModelQualityConfig()) {
117
- return `${config.hosts.claude.minimumModel} with ${config.hosts.claude.minimumReasoningEffort} reasoning or ${config.hosts.codex.minimumModel} with ${config.hosts.codex.minimumReasoningEffort} reasoning`;
118
- }
119
- function formatCopy(template, values) {
120
- return Object.entries(values).reduce((text, [key, value]) => text.replaceAll(`{${key}}`, value), template);
121
- }
122
- function acceptsReasoning(reasoning, hostConfig) {
123
- const normalized = normalizeReasoning(reasoning);
124
- return hostConfig.acceptedReasoningEfforts
125
- .map((value) => normalizeReasoning(value))
126
- .includes(normalized);
127
- }
128
- function modelMeetsMinimum(model, hostConfig) {
129
- const normalizedModel = normalize(model).replace(/[_-]+/g, " ");
130
- if (!normalizedModel)
131
- return false;
132
- const familyMatches = hostConfig.familyKeywords.every((keyword) => normalizedModel.includes(normalize(keyword)));
133
- if (!familyMatches)
134
- return false;
135
- const versions = extractModelVersions(normalizedModel);
136
- if (!hostConfig.minimumVersion)
137
- return true;
138
- return versions.some((version) => compareVersion(version, hostConfig.minimumVersion) >= 0);
139
- }
140
- function findAcceptedHostConfig(host, model, config) {
141
- const candidates = host === "unknown"
142
- ? [
143
- ["claude", config.hosts.claude],
144
- ["codex", config.hosts.codex],
145
- ]
146
- : [[host, config.hosts[host]]];
147
- return candidates.find(([, hostConfig]) => modelMeetsMinimum(model, hostConfig));
148
- }
149
24
  export function evaluateCampaignModelQuality(input = {}) {
150
- const config = getCampaignModelQualityConfig();
151
- const host = normalizeHost([input.host, input.model].filter(Boolean).join(" "));
25
+ const host = normalizeHost(input.host);
152
26
  const model = input.model?.trim() || null;
153
27
  const reasoningEffort = input.reasoningEffort?.trim() || null;
154
- const recommendationHost = host === "claude" ? "claude" : "codex";
155
- const recommendedHostConfig = config.hosts[recommendationHost];
156
- const minimumSummary = getCampaignModelMinimumSummary(config);
157
- const recommendedModel = recommendedHostConfig.minimumModel;
158
- const recommendedReasoningEffort = recommendedHostConfig.recommendedReasoningEffort;
28
+ const recommendedModel = host === "claude" ? "Opus 4.8" : "GPT 5.5";
29
+ const recommendedReasoningEffort = host === "claude" ? "high or extra high" : "extra high";
159
30
  if (!model && !reasoningEffort) {
160
31
  return {
161
32
  status: "unknown",
@@ -164,37 +35,30 @@ export function evaluateCampaignModelQuality(input = {}) {
164
35
  reasoningEffort,
165
36
  recommendedModel,
166
37
  recommendedReasoningEffort,
167
- minimumSummary,
168
38
  confirmationRequired: true,
169
- message: formatCopy(config.warningCopy.unknownSettings, {
170
- minimumSummary,
171
- currentSettings: "unknown",
172
- }),
39
+ message: "Model settings were not provided by the host. Before campaign-critical generation, confirm the user is using Claude Opus 4.8 at high/extra high or Codex GPT 5.5 at extra high.",
173
40
  };
174
41
  }
175
- const acceptedHostConfig = findAcceptedHostConfig(host, model, config);
176
- const ok = Boolean(acceptedHostConfig && acceptsReasoning(reasoningEffort, acceptedHostConfig[1]));
42
+ const normalizedModel = normalize(model);
43
+ const ok = host === "claude"
44
+ ? normalizedModel.includes("opus") &&
45
+ normalizedModel.includes("4.8") &&
46
+ isHighClaudeReasoning(reasoningEffort)
47
+ : normalizedModel.includes("gpt") &&
48
+ normalizedModel.includes("5.5") &&
49
+ isExtraHighCodexReasoning(reasoningEffort);
177
50
  if (ok) {
178
51
  return {
179
52
  status: "ok",
180
- host: acceptedHostConfig?.[0] ?? host,
53
+ host,
181
54
  model,
182
55
  reasoningEffort,
183
- recommendedModel: acceptedHostConfig?.[1].minimumModel ?? recommendedModel,
184
- recommendedReasoningEffort: acceptedHostConfig?.[1].recommendedReasoningEffort ??
185
- recommendedReasoningEffort,
186
- minimumSummary,
56
+ recommendedModel,
57
+ recommendedReasoningEffort,
187
58
  confirmationRequired: false,
188
- message: formatCopy(config.warningCopy.ok, {
189
- minimumSummary,
190
- currentSettings: "current settings",
191
- }),
59
+ message: "Recommended campaign-quality model settings are active.",
192
60
  };
193
61
  }
194
- const currentSettings = [
195
- model ? `model "${model}"` : "unknown model",
196
- reasoningEffort ? `reasoning "${reasoningEffort}"` : "unknown reasoning",
197
- ].join(", ");
198
62
  return {
199
63
  status: "warn",
200
64
  host,
@@ -202,11 +66,7 @@ export function evaluateCampaignModelQuality(input = {}) {
202
66
  reasoningEffort,
203
67
  recommendedModel,
204
68
  recommendedReasoningEffort,
205
- minimumSummary,
206
69
  confirmationRequired: true,
207
- message: formatCopy(config.warningCopy.belowMinimum, {
208
- minimumSummary,
209
- currentSettings,
210
- }),
70
+ message: `For best campaign quality, switch to ${recommendedModel} with ${recommendedReasoningEffort} reasoning before lead filtering, message generation, or launch review.`,
211
71
  };
212
72
  }