@sellable/mcp 0.1.314 → 0.1.318

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -302,11 +302,32 @@ Use the same MCP runtime preflight in both hosts:
302
302
 
303
303
  Canonical shortcut:
304
304
 
305
- - `bootstrap_create_campaign({ flowVersion: "v2", campaignId?, host?, model?, reasoningEffort? })`
305
+ - `bootstrap_create_campaign({ flowVersion: "v2", campaignId?, host?, model?, reasoningEffort?, modelMetadataSource? })`
306
+
307
+ Pass model/reasoning metadata only when it comes from active turn/runtime
308
+ metadata or explicit user confirmation. For Codex, use
309
+ `nodeRepl.requestMeta["x-codex-turn-metadata"]` and pass
310
+ `modelMetadataSource: "codex_turn_metadata"`. Config files such as
311
+ `~/.codex/config.toml` are fallback/cross-check evidence only and must not
312
+ trigger model-switch warnings.
313
+
314
+ For Claude Code, pass model metadata only when the current host exposes active
315
+ runtime model and effort for the same session, or when the user explicitly
316
+ confirms current `/status` or `/model` output that includes both model and effort.
317
+ If the current Claude session context explicitly states both values, use
318
+ `modelMetadataSource: "claude_session_context"`. Use
319
+ `modelMetadataSource: "user_confirmed"` for explicit user confirmation. Do not
320
+ use a nested `claude -p` run, `~/.claude/settings.json`, or CLI defaults as
321
+ active-session evidence.
322
+
323
+ Claude session-context self-report shape:
306
324
 
307
- Pass host/model/reasoning metadata when the host exposes it. The bootstrap
308
- model-quality check uses packaged config and warns before campaign setup when a
309
- known model is below the configured minimum.
325
+ ```text
326
+ - Model (name): ...
327
+ - Model (ID): ...
328
+ - Reasoning effort: ...
329
+ - Source: active Claude Code session context
330
+ ```
310
331
 
311
332
  If bootstrap returns blocking errors, fail fast and do not continue into provider
312
333
  search/import tools.
@@ -11,6 +11,7 @@ type BootstrapCreateCampaignInput = {
11
11
  host?: string;
12
12
  model?: string;
13
13
  reasoningEffort?: string;
14
+ modelMetadataSource?: string;
14
15
  };
15
16
  type BootstrapCheck = {
16
17
  key: "auth" | "update" | "framework" | "model_quality" | "resume_context" | "subskill_catalog" | "create_campaign_subskill";
@@ -87,6 +88,10 @@ export declare const bootstrapToolDefinitions: {
87
88
  type: string;
88
89
  description: string;
89
90
  };
91
+ modelMetadataSource: {
92
+ type: string;
93
+ description: string;
94
+ };
90
95
  };
91
96
  additionalProperties: boolean;
92
97
  };
@@ -18,7 +18,7 @@ function toGuidance(check, message) {
18
18
  return "Fix framework files and rerun bootstrap_create_campaign.";
19
19
  }
20
20
  if (check === "model_quality") {
21
- return `Use at least ${getCampaignModelMinimumSummary()} for campaign-critical generation.`;
21
+ return `Use trusted active-turn metadata before enforcing the campaign model floor: ${getCampaignModelMinimumSummary()}.`;
22
22
  }
23
23
  if (check === "resume_context") {
24
24
  return "Confirm the campaignId exists and is accessible, then retry bootstrap_create_campaign.";
@@ -98,15 +98,19 @@ export const bootstrapToolDefinitions = [
98
98
  },
99
99
  host: {
100
100
  type: "string",
101
- description: "Optional host label for model-quality preflight, for example Claude Code or Codex.",
101
+ description: "Optional host label for active model-quality preflight, for example Claude Code or Codex.",
102
102
  },
103
103
  model: {
104
104
  type: "string",
105
- description: `Optional current host model name. Minimum campaign quality config: ${getCampaignModelMinimumSummary()}.`,
105
+ description: "Optional active host model name from runtime metadata. Do not pass config defaults.",
106
106
  },
107
107
  reasoningEffort: {
108
108
  type: "string",
109
- description: `Optional current reasoning setting. Minimum campaign quality config: ${getCampaignModelMinimumSummary()}.`,
109
+ description: "Optional active reasoning setting from runtime metadata. Do not pass config defaults.",
110
+ },
111
+ modelMetadataSource: {
112
+ type: "string",
113
+ description: "Where active model metadata came from, for example codex_turn_metadata, claude_runtime_metadata, claude_session_context, active_turn_metadata, or user_confirmed.",
110
114
  },
111
115
  },
112
116
  additionalProperties: false,
@@ -129,6 +133,7 @@ export async function bootstrapCreateCampaign(input = {}) {
129
133
  host: input.host,
130
134
  model: input.model,
131
135
  reasoningEffort: input.reasoningEffort,
136
+ metadataSource: input.modelMetadataSource,
132
137
  });
133
138
  const runAuthCheck = async () => {
134
139
  let status = await getAuthStatus();
@@ -188,7 +193,7 @@ export async function bootstrapCreateCampaign(input = {}) {
188
193
  }
189
194
  requiredChecks.push({
190
195
  key: "model_quality",
191
- ok: modelQuality.status === "ok",
196
+ ok: modelQuality.status !== "warn",
192
197
  blocking: false,
193
198
  detail: modelQuality.message,
194
199
  });
@@ -3,6 +3,7 @@ export type CampaignModelQualityInput = {
3
3
  host?: string | null;
4
4
  model?: string | null;
5
5
  reasoningEffort?: string | null;
6
+ metadataSource?: string | null;
6
7
  };
7
8
  export type CampaignModelQualityStatus = "ok" | "warn" | "unknown";
8
9
  export type CampaignModelQualityResult = {
@@ -10,6 +11,7 @@ export type CampaignModelQualityResult = {
10
11
  host: CampaignModelHost;
11
12
  model: string | null;
12
13
  reasoningEffort: string | null;
14
+ metadataSource: string | null;
13
15
  recommendedModel: string;
14
16
  recommendedReasoningEffort: string;
15
17
  minimumSummary: string;
@@ -34,8 +36,7 @@ export type CampaignModelQualityConfig = {
34
36
  };
35
37
  warningCopy: {
36
38
  ok: string;
37
- staleCodexMetadata: string;
38
- unknownSettings: string;
39
+ skipped: string;
39
40
  belowMinimum: string;
40
41
  };
41
42
  };
@@ -1,8 +1,5 @@
1
- import { existsSync, readFileSync } from "fs";
2
- import { join } from "path";
3
- import { resolveSkillsDir } from "../skills.js";
4
1
  const DEFAULT_MODEL_QUALITY_CONFIG = {
5
- version: 1,
2
+ version: 2,
6
3
  hosts: {
7
4
  claude: {
8
5
  label: "Claude Code",
@@ -36,20 +33,24 @@ const DEFAULT_MODEL_QUALITY_CONFIG = {
36
33
  },
37
34
  },
38
35
  warningCopy: {
39
- ok: "Campaign model settings meet the configured minimum: {minimumSummary}.",
40
- staleCodexMetadata: "Codex host metadata appears stale: it can report GPT-5/default or GPT-5/high even when the UI is set to GPT 5.5 Extra High. Treating this as acceptable; continue without asking the user to switch.",
41
- 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.",
42
- belowMinimum: "Best campaigns need at least {minimumSummary}. Current settings look below that: {currentSettings}. Please switch before continuing, or explicitly say to continue anyway.",
36
+ ok: "Active host model metadata meets the configured campaign floor: {currentSettings}.",
37
+ skipped: "Active host model metadata was not available from a trusted runtime source. Continue without asking the user to switch models.",
38
+ belowMinimum: "Active host metadata reports {currentSettings}, which is below the configured campaign floor: {minimumSummary}. Ask the user to switch before continuing, or explicitly say to continue anyway.",
43
39
  },
44
40
  };
45
- let cachedConfig = null;
41
+ const TRUSTED_METADATA_SOURCE_KEYWORDS = [
42
+ "codex_turn_metadata",
43
+ "claude_runtime_metadata",
44
+ "claude_session_context",
45
+ "active_turn_metadata",
46
+ "user_confirmed",
47
+ ];
46
48
  const normalize = (value) => String(value ?? "")
47
49
  .trim()
48
50
  .toLowerCase();
49
51
  const normalizeHost = (host) => {
50
52
  const normalized = normalize(host);
51
53
  if (normalized.includes("claude") ||
52
- normalized.includes("clod") ||
53
54
  normalized.includes("opus") ||
54
55
  normalized.includes("sonnet") ||
55
56
  normalized.includes("haiku")) {
@@ -77,42 +78,12 @@ const compareVersion = (candidate, minimum) => {
77
78
  }
78
79
  return 0;
79
80
  };
80
- const extractModelVersions = (model) => Array.from(normalize(model).matchAll(/\b(\d+(?:\.\d+){0,2})\b/g)).map((match) => match[1]);
81
- function mergeConfig(rawConfig) {
82
- return {
83
- ...DEFAULT_MODEL_QUALITY_CONFIG,
84
- ...rawConfig,
85
- hosts: {
86
- claude: {
87
- ...DEFAULT_MODEL_QUALITY_CONFIG.hosts.claude,
88
- ...(rawConfig.hosts?.claude || {}),
89
- },
90
- codex: {
91
- ...DEFAULT_MODEL_QUALITY_CONFIG.hosts.codex,
92
- ...(rawConfig.hosts?.codex || {}),
93
- },
94
- },
95
- warningCopy: {
96
- ...DEFAULT_MODEL_QUALITY_CONFIG.warningCopy,
97
- ...(rawConfig.warningCopy || {}),
98
- },
99
- };
100
- }
81
+ const extractModelVersions = (model) => {
82
+ const normalized = normalize(model).replace(/(?<=\d)[_-](?=\d)/g, ".");
83
+ return Array.from(normalized.matchAll(/\b(\d+(?:\.\d+){0,2})\b/g)).map((match) => match[1]);
84
+ };
101
85
  export function getCampaignModelQualityConfig() {
102
- if (cachedConfig)
103
- return cachedConfig;
104
- const configPath = join(resolveSkillsDir(), "create-campaign", "core", "model-quality.json");
105
- if (!existsSync(configPath)) {
106
- cachedConfig = DEFAULT_MODEL_QUALITY_CONFIG;
107
- return cachedConfig;
108
- }
109
- try {
110
- cachedConfig = mergeConfig(JSON.parse(readFileSync(configPath, "utf-8")));
111
- }
112
- catch {
113
- cachedConfig = DEFAULT_MODEL_QUALITY_CONFIG;
114
- }
115
- return cachedConfig;
86
+ return DEFAULT_MODEL_QUALITY_CONFIG;
116
87
  }
117
88
  export function getCampaignModelMinimumSummary(config = getCampaignModelQualityConfig()) {
118
89
  return `${config.hosts.claude.minimumModel} with ${config.hosts.claude.minimumReasoningEffort} reasoning or ${config.hosts.codex.minimumModel} with ${config.hosts.codex.minimumReasoningEffort} reasoning`;
@@ -134,12 +105,12 @@ function modelMeetsMinimum(model, hostConfig, options = {}) {
134
105
  hostConfig.familyKeywords.every((keyword) => normalizedModel.includes(normalize(keyword)));
135
106
  if (!familyMatches)
136
107
  return false;
137
- const versions = extractModelVersions(normalizedModel);
108
+ const versions = extractModelVersions(model);
138
109
  if (!hostConfig.minimumVersion)
139
110
  return true;
140
111
  return versions.some((version) => compareVersion(version, hostConfig.minimumVersion) >= 0);
141
112
  }
142
- function findAcceptedHostConfig(host, model, config) {
113
+ function findHostConfig(host, model, config) {
143
114
  const candidates = host === "unknown"
144
115
  ? [
145
116
  ["claude", config.hosts.claude],
@@ -150,100 +121,63 @@ function findAcceptedHostConfig(host, model, config) {
150
121
  familyKnownFromHost: host !== "unknown",
151
122
  }));
152
123
  }
153
- function looksLikeCodexStaleMetadata(host, model, reasoningEffort, config) {
154
- if (host !== "codex")
155
- return false;
156
- if (!model || !reasoningEffort)
157
- return false;
158
- const normalizedModel = normalize(model).replace(/[_-]+/g, " ");
159
- const normalizedReasoning = normalizeReasoning(reasoningEffort);
160
- if (!normalizedModel.includes("gpt"))
161
- return false;
162
- const versions = extractModelVersions(normalizedModel);
163
- const looksLikeBaseGpt5 = versions.some((version) => version === "5" || version === "5.0");
164
- if (!looksLikeBaseGpt5)
165
- return false;
166
- return (["default", "auto", "standard"].includes(normalizedReasoning) ||
167
- acceptsReasoning(reasoningEffort, config.hosts.codex));
124
+ function isTrustedMetadataSource(source) {
125
+ const normalized = normalize(source).replace(/[\s-]+/g, "_");
126
+ return TRUSTED_METADATA_SOURCE_KEYWORDS.some((keyword) => normalized.includes(keyword));
168
127
  }
169
128
  export function evaluateCampaignModelQuality(input = {}) {
170
129
  const config = getCampaignModelQualityConfig();
171
130
  const host = normalizeHost([input.host, input.model].filter(Boolean).join(" "));
172
131
  const model = input.model?.trim() || null;
173
132
  const reasoningEffort = input.reasoningEffort?.trim() || null;
133
+ const metadataSource = input.metadataSource?.trim() || null;
174
134
  const recommendationHost = host === "claude" ? "claude" : "codex";
175
135
  const recommendedHostConfig = config.hosts[recommendationHost];
176
136
  const minimumSummary = getCampaignModelMinimumSummary(config);
177
- const recommendedModel = recommendedHostConfig.minimumModel;
178
- const recommendedReasoningEffort = recommendedHostConfig.recommendedReasoningEffort;
179
- if (!model && !reasoningEffort) {
137
+ const currentSettings = [
138
+ model ? `model "${model}"` : "unknown model",
139
+ reasoningEffort ? `reasoning "${reasoningEffort}"` : "unknown reasoning",
140
+ metadataSource ? `source "${metadataSource}"` : "unknown source",
141
+ ].join(", ");
142
+ const base = {
143
+ host,
144
+ model,
145
+ reasoningEffort,
146
+ metadataSource,
147
+ recommendedModel: recommendedHostConfig.minimumModel,
148
+ recommendedReasoningEffort: recommendedHostConfig.recommendedReasoningEffort,
149
+ minimumSummary,
150
+ metadataStale: false,
151
+ };
152
+ if (!isTrustedMetadataSource(metadataSource) || !model || !reasoningEffort) {
180
153
  return {
154
+ ...base,
181
155
  status: "unknown",
182
- host,
183
- model,
184
- reasoningEffort,
185
- recommendedModel,
186
- recommendedReasoningEffort,
187
- minimumSummary,
188
- confirmationRequired: true,
189
- metadataStale: false,
190
- message: formatCopy(config.warningCopy.unknownSettings, {
191
- minimumSummary,
192
- currentSettings: "unknown",
193
- }),
156
+ confirmationRequired: false,
157
+ message: config.warningCopy.skipped,
194
158
  };
195
159
  }
196
- const acceptedHostConfig = findAcceptedHostConfig(host, model, config);
160
+ const acceptedHostConfig = findHostConfig(host, model, config);
197
161
  const ok = Boolean(acceptedHostConfig && acceptsReasoning(reasoningEffort, acceptedHostConfig[1]));
198
162
  if (ok) {
199
163
  return {
164
+ ...base,
200
165
  status: "ok",
201
166
  host: acceptedHostConfig?.[0] ?? host,
202
- model,
203
- reasoningEffort,
204
- recommendedModel: acceptedHostConfig?.[1].minimumModel ?? recommendedModel,
167
+ recommendedModel: acceptedHostConfig?.[1].minimumModel ?? base.recommendedModel,
205
168
  recommendedReasoningEffort: acceptedHostConfig?.[1].recommendedReasoningEffort ??
206
- recommendedReasoningEffort,
207
- minimumSummary,
169
+ base.recommendedReasoningEffort,
208
170
  confirmationRequired: false,
209
- metadataStale: false,
210
171
  message: formatCopy(config.warningCopy.ok, {
211
172
  minimumSummary,
212
- currentSettings: "current settings",
173
+ currentSettings,
213
174
  }),
214
175
  };
215
176
  }
216
- if (looksLikeCodexStaleMetadata(host, model, reasoningEffort, config)) {
217
- return {
218
- status: "ok",
219
- host,
220
- model,
221
- reasoningEffort,
222
- recommendedModel,
223
- recommendedReasoningEffort,
224
- minimumSummary,
225
- confirmationRequired: false,
226
- metadataStale: true,
227
- message: formatCopy(config.warningCopy.staleCodexMetadata, {
228
- minimumSummary,
229
- currentSettings: "stale Codex host metadata",
230
- }),
231
- };
232
- }
233
- const currentSettings = [
234
- model ? `model "${model}"` : "unknown model",
235
- reasoningEffort ? `reasoning "${reasoningEffort}"` : "unknown reasoning",
236
- ].join(", ");
237
177
  return {
178
+ ...base,
238
179
  status: "warn",
239
- host,
240
- model,
241
- reasoningEffort,
242
- recommendedModel,
243
- recommendedReasoningEffort,
244
- minimumSummary,
245
180
  confirmationRequired: true,
246
- metadataStale: false,
247
181
  message: formatCopy(config.warningCopy.belowMinimum, {
248
182
  minimumSummary,
249
183
  currentSettings,
@@ -407,6 +407,10 @@ export declare const allTools: ({
407
407
  type: string;
408
408
  description: string;
409
409
  };
410
+ modelMetadataSource: {
411
+ type: string;
412
+ description: string;
413
+ };
410
414
  };
411
415
  additionalProperties: boolean;
412
416
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.314",
3
+ "version": "0.1.318",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -179,17 +179,11 @@ person/company this campaign is for, then I’ll turn that into a campaign brief
179
179
  before we move into lead sourcing.
180
180
  ```
181
181
 
182
- Exception: if `bootstrap_create_campaign.modelQuality.status === "warn"` and
183
- `bootstrap_create_campaign.modelQuality.metadataStale !== true`, the first
184
- visible campaign message must be the model-quality warning from
185
- `modelQuality.message`. Ask the user to switch to the configured minimum model
186
- or explicitly continue anyway before identity setup, research, lead filtering,
187
- message generation, or launch review. If `metadataStale === true`, continue
188
- normally and do not ask the user to switch.
189
-
190
- If `bootstrap_create_campaign.modelQuality.metadataStale === true`, continue
191
- normally. Do not ask the user to switch models; this is an accepted Codex host
192
- metadata mismatch.
182
+ Exception: if `bootstrap_create_campaign.modelQuality.status === "warn"`, the
183
+ first visible campaign message must be the model-quality warning from
184
+ `modelQuality.message`. Only trust that warning when bootstrap received active
185
+ turn/runtime metadata or an explicit user-confirmed model. Do not warn from
186
+ config defaults, stale host labels, or inferred model names.
193
187
 
194
188
  If a linked/local skill file is stale or missing, silently use the installed
195
189
  `sellable@sellable` plugin copy. Do not tell the user about the stale link,
@@ -207,6 +201,55 @@ clear business decisions, tradeoffs, and approval gates. Use product language:
207
201
  - "I can create a draft shell for you to watch with approval gates before
208
202
  sourcing", not mutation jargon
209
203
 
204
+ ## Active Model Metadata
205
+
206
+ Before calling `bootstrap_create_campaign`, collect the active host model data
207
+ only from a source that can describe this turn:
208
+
209
+ - Codex: if `mcp__node_repl__js` is available, inspect only
210
+ `nodeRepl.requestMeta["x-codex-turn-metadata"].model` and
211
+ `nodeRepl.requestMeta["x-codex-turn-metadata"].reasoning_effort`. Pass those
212
+ values as `model` and `reasoningEffort`, with
213
+ `modelMetadataSource: "codex_turn_metadata"`.
214
+ - Codex fallback/cross-check: if active turn metadata is unavailable, you may
215
+ inspect `~/.codex/config.toml` for `model` and `model_reasoning_effort`, but
216
+ pass `modelMetadataSource: "codex_config_fallback"` and do not treat it as a
217
+ reason to ask the user to switch models. Active turn metadata wins if it
218
+ differs from config.
219
+ - Claude Code / Opus: use active Claude runtime metadata only if the current
220
+ host exposes model and effort for this same session, with
221
+ `modelMetadataSource: "claude_runtime_metadata"`. Current Claude Code MCP
222
+ tool calls may not include model or effort metadata; when they do not, omit
223
+ `model`, `reasoningEffort`, and `modelMetadataSource`.
224
+ - Claude Code session-context self-report: if your current Claude session
225
+ context explicitly states both the exact model ID and effort/thinking level,
226
+ report it internally in this shape and pass it to bootstrap with
227
+ `modelMetadataSource: "claude_session_context"`:
228
+
229
+ ```text
230
+ - Model (name): ...
231
+ - Model (ID): ...
232
+ - Reasoning effort: ...
233
+ - Source: active Claude Code session context
234
+ ```
235
+
236
+ Use this only when the values are explicitly present in the current session
237
+ context. Do not infer an ID from the friendly name, do not infer effort from
238
+ `alwaysThinkingEnabled`, and do not show this self-report to the user during
239
+ normal campaign setup.
240
+ - Do not run a nested `claude -p`, inspect `~/.claude/settings.json`, or read
241
+ Claude CLI defaults as proof of the user's active Claude Code session. Those
242
+ checks can validate a new child session or saved defaults, but not this
243
+ session's actual model and effort.
244
+ - If the user explicitly provides active Claude `/status` or `/model` output
245
+ that includes both model and effort, pass it with
246
+ `modelMetadataSource: "user_confirmed"`. If it is missing either model or
247
+ effort, treat the metadata as unknown and continue.
248
+
249
+ Never invent the model or reasoning effort. Never pass config defaults as active
250
+ metadata. If bootstrap returns `modelQuality.status === "unknown"`, continue
251
+ without asking the user to switch models.
252
+
210
253
  Approval and safety copy should be tasteful. State what the current approval
211
254
  covers once, in one short sentence, then move on. Do not append repeated
212
255
  "nothing starts / no leads import / no sending" disclaimers to routine progress
@@ -938,13 +981,19 @@ updates.
938
981
  - Do not call `mcp__sellable__get_campaigns`.
939
982
  - Do not call `mcp__sellable__get_campaign` to hunt for IDs.
940
983
  - Do not call `mcp__sellable__create_campaign({ campaignId: ... })` unless the user supplied that id.
941
- 6. Call `mcp__sellable__bootstrap_create_campaign({ flowVersion: "v2", campaignId?, host?, model?, reasoningEffort? })`.
942
- Pass the current host, model, and reasoning when the host exposes them.
984
+ 6. Call `mcp__sellable__bootstrap_create_campaign({ flowVersion: "v2", campaignId?, host?, model?, reasoningEffort?, modelMetadataSource? })`.
985
+ Pass model metadata only when collected by the Active Model Metadata rules
986
+ above. For Codex active turn metadata, pass
987
+ `modelMetadataSource: "codex_turn_metadata"`. For explicit Claude session
988
+ context, pass `modelMetadataSource: "claude_session_context"`. For explicit
989
+ user-confirmed Claude `/status` or `/model` output, pass
990
+ `modelMetadataSource: "user_confirmed"` only when it includes both model and
991
+ effort.
943
992
  7. If `safeToProceed !== true`, stop and show `blockingErrors` + `nextStep`.
944
- 8. If `modelQuality.status === "warn"` and `modelQuality.metadataStale !== true`,
945
- show `modelQuality.message` before any setup/research and wait for the user
946
- to switch or explicitly continue. If `metadataStale === true`, continue
947
- normally and do not tell the user to switch.
993
+ 8. If `modelQuality.status === "warn"`, show `modelQuality.message` before any
994
+ setup/research and wait for the user to switch or explicitly continue. If
995
+ `modelQuality.status === "unknown"`, continue; do not tell the user to
996
+ switch models.
948
997
 
949
998
  ## Execute Workflow
950
999
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": 1,
2
+ "version": 2,
3
3
  "hosts": {
4
4
  "claude": {
5
5
  "label": "Claude Code",
@@ -21,9 +21,8 @@
21
21
  }
22
22
  },
23
23
  "warningCopy": {
24
- "ok": "Campaign model settings meet the configured minimum: {minimumSummary}.",
25
- "staleCodexMetadata": "Codex host metadata appears stale: it can report GPT-5/default or GPT-5/high even when the UI is set to GPT 5.5 Extra High. Treating this as acceptable; continue without asking the user to switch.",
26
- "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.",
27
- "belowMinimum": "Best campaigns need at least {minimumSummary}. Current settings look below that: {currentSettings}. Please switch before continuing, or explicitly say to continue anyway."
24
+ "ok": "Active host model metadata meets the configured campaign floor: {currentSettings}.",
25
+ "skipped": "Active host model metadata was not available from a trusted runtime source. Continue without asking the user to switch models.",
26
+ "belowMinimum": "Active host metadata reports {currentSettings}, which is below the configured campaign floor: {minimumSummary}. Ask the user to switch before continuing, or explicitly say to continue anyway."
28
27
  }
29
28
  }
@@ -296,7 +296,12 @@ atomic-mint step:
296
296
 
297
297
  0. **verify workspace/sender invariant** (see §Preconditions) — if the
298
298
  invariant fails, abort BEFORE `bootstrap_create_campaign`
299
- 1. `bootstrap_create_campaign({ flowVersion: "v2", host?, model?, reasoningEffort? })`
299
+ 1. `bootstrap_create_campaign({ flowVersion: "v2", host?, model?, reasoningEffort?, modelMetadataSource? })`
300
+ Pass model metadata only when it comes from active turn/runtime metadata or
301
+ explicit user confirmation that includes both model and effort. Use
302
+ `modelMetadataSource: "claude_session_context"` only when the current Claude
303
+ session context explicitly states both values. Never use inferred config
304
+ defaults, nested Claude sessions, or saved settings files.
300
305
  2. If `campaign-shell.json` exists, call `create_campaign({ campaignId })` to
301
306
  resume/recover `{ campaignId, watchUrl }`, then
302
307
  `update_campaign({ campaignId, campaignBrief, leadSourceType,