@sellable/install 0.1.327 → 0.1.329

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.
@@ -0,0 +1,442 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ chmodSync,
4
+ closeSync,
5
+ cpSync,
6
+ existsSync,
7
+ lstatSync,
8
+ mkdirSync,
9
+ openSync,
10
+ readFileSync,
11
+ readlinkSync,
12
+ readdirSync,
13
+ renameSync,
14
+ rmSync,
15
+ symlinkSync,
16
+ writeFileSync,
17
+ } from "node:fs";
18
+ import { basename, dirname, join, relative, resolve } from "node:path";
19
+
20
+ export const CODEX_PLUGIN_SELECTION_SCHEMA_VERSION = 1;
21
+ export const MAX_CODEX_CANARY_WINDOW_MS = 90 * 60 * 1000;
22
+
23
+ export function resolveCodexPluginSelectionPaths({
24
+ home = "",
25
+ marketplaceRoot = join(home, ".sellable", "codex-marketplace"),
26
+ cacheRoot = join(home, ".codex", "plugins", "cache", "sellable", "sellable"),
27
+ } = {}) {
28
+ if (!home) throw new Error("codex_selection_home_required");
29
+ return {
30
+ home: resolve(home),
31
+ configPath: join(home, ".codex", "config.toml"),
32
+ marketplaceRoot: resolve(marketplaceRoot),
33
+ marketplacePath: join(marketplaceRoot, ".agents", "plugins", "marketplace.json"),
34
+ pluginRoot: join(marketplaceRoot, "plugins", "sellable"),
35
+ cacheRoot: resolve(cacheRoot),
36
+ };
37
+ }
38
+
39
+ function canonicalize(value) {
40
+ if (Array.isArray(value)) return value.map(canonicalize);
41
+ if (value && typeof value === "object") {
42
+ return Object.fromEntries(
43
+ Object.keys(value)
44
+ .sort()
45
+ .map((key) => [key, canonicalize(value[key])])
46
+ );
47
+ }
48
+ return value;
49
+ }
50
+
51
+ function digest(value) {
52
+ return createHash("sha256").update(value).digest("hex");
53
+ }
54
+
55
+ function canonicalDigest(value) {
56
+ return digest(JSON.stringify(canonicalize(value)));
57
+ }
58
+
59
+ function treeRecords(root) {
60
+ if (!existsSync(root)) return [{ path: ".", type: "missing" }];
61
+ const records = [];
62
+ const visit = (path) => {
63
+ const stat = lstatSync(path);
64
+ const rel = relative(root, path) || ".";
65
+ if (stat.isSymbolicLink()) {
66
+ records.push({ path: rel, type: "symlink", target: readlinkSync(path) });
67
+ return;
68
+ }
69
+ if (stat.isDirectory()) {
70
+ records.push({ path: rel, type: "directory", mode: stat.mode & 0o777 });
71
+ for (const name of readdirSync(path).sort()) visit(join(path, name));
72
+ return;
73
+ }
74
+ records.push({
75
+ path: rel,
76
+ type: "file",
77
+ mode: stat.mode & 0o777,
78
+ sha256: digest(readFileSync(path)),
79
+ });
80
+ };
81
+ visit(root);
82
+ return records;
83
+ }
84
+
85
+ function treeHash(root) {
86
+ return canonicalDigest(treeRecords(root));
87
+ }
88
+
89
+ function quoteToml(value) {
90
+ return JSON.stringify(String(value));
91
+ }
92
+
93
+ function removeTomlTable(content, header) {
94
+ const lines = content.split("\n");
95
+ const target = `[${header}]`;
96
+ const kept = [];
97
+ let skipping = false;
98
+ for (const line of lines) {
99
+ const trimmed = line.trim();
100
+ if (/^\[[^\]]+\]$/.test(trimmed)) {
101
+ skipping = trimmed === target;
102
+ if (skipping) continue;
103
+ }
104
+ if (!skipping) kept.push(line);
105
+ }
106
+ return kept.join("\n");
107
+ }
108
+
109
+ function candidateCodexConfig(content, candidatePath) {
110
+ const mcpPath = join(candidatePath, ".mcp.json");
111
+ if (!existsSync(mcpPath)) throw new Error("codex_candidate_mcp_manifest_missing");
112
+ const manifest = JSON.parse(readFileSync(mcpPath, "utf8"));
113
+ const server = manifest?.mcpServers?.sellable;
114
+ if (
115
+ server?.type !== "stdio" ||
116
+ typeof server.command !== "string" ||
117
+ !Array.isArray(server.args) ||
118
+ server.args.some((value) => typeof value !== "string") ||
119
+ !server.env ||
120
+ typeof server.env !== "object" ||
121
+ Object.values(server.env).some((value) => typeof value !== "string")
122
+ ) {
123
+ throw new Error("codex_candidate_mcp_manifest_invalid");
124
+ }
125
+ let next = removeTomlTable(content, "mcp_servers.sellable.env");
126
+ next = removeTomlTable(next, "mcp_servers.sellable").trimEnd();
127
+ const args = `[${server.args.map(quoteToml).join(", ")}]`;
128
+ const env = Object.entries(server.env)
129
+ .sort(([left], [right]) => left.localeCompare(right))
130
+ .map(([key, value]) => `${key} = ${quoteToml(value)}`)
131
+ .join("\n");
132
+ return `${next}\n\n[mcp_servers.sellable]\ncommand = ${quoteToml(
133
+ server.command
134
+ )}\nargs = ${args}\n\n[mcp_servers.sellable.env]\n${env}\n`;
135
+ }
136
+
137
+ function pluginVersion(pluginRoot) {
138
+ const manifestPath = join(pluginRoot, ".codex-plugin", "plugin.json");
139
+ if (!existsSync(manifestPath)) throw new Error("codex_selection_plugin_manifest_missing");
140
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
141
+ if (manifest.name !== "sellable" || typeof manifest.version !== "string") {
142
+ throw new Error("codex_selection_plugin_manifest_invalid");
143
+ }
144
+ return manifest.version;
145
+ }
146
+
147
+ function ensureInside(parent, child, code) {
148
+ const root = resolve(parent);
149
+ const target = resolve(child);
150
+ if (!(target === root || target.startsWith(`${root}/`))) throw new Error(code);
151
+ }
152
+
153
+ function copyExact(source, target) {
154
+ rmSync(target, { recursive: true, force: true });
155
+ mkdirSync(dirname(target), { recursive: true });
156
+ cpSync(source, target, {
157
+ recursive: true,
158
+ dereference: false,
159
+ preserveTimestamps: true,
160
+ });
161
+ syncExactModes(source, target);
162
+ }
163
+
164
+ function syncExactModes(source, target) {
165
+ const stat = lstatSync(source);
166
+ if (stat.isSymbolicLink()) return;
167
+ chmodSync(target, stat.mode & 0o777);
168
+ if (!stat.isDirectory()) return;
169
+ for (const name of readdirSync(source)) {
170
+ syncExactModes(join(source, name), join(target, name));
171
+ }
172
+ }
173
+
174
+ function readCanonicalManifest(manifestPath, expectedSnapshotHash) {
175
+ const raw = readFileSync(manifestPath, "utf8");
176
+ const manifest = JSON.parse(raw);
177
+ if (raw !== `${JSON.stringify(manifest, null, 2)}\n`) {
178
+ throw new Error("codex_selection_snapshot_manifest_not_canonical");
179
+ }
180
+ const { snapshotHash, ...unsigned } = manifest;
181
+ if (snapshotHash !== canonicalDigest(unsigned) || snapshotHash !== expectedSnapshotHash) {
182
+ throw new Error("codex_selection_snapshot_hash_mismatch");
183
+ }
184
+ for (const item of manifest.items ?? []) {
185
+ if (treeHash(item.backupPath) !== item.hash) {
186
+ throw new Error(`codex_selection_snapshot_backup_mismatch:${item.name}`);
187
+ }
188
+ }
189
+ return manifest;
190
+ }
191
+
192
+ function stateItems(paths, stableVersion) {
193
+ return [
194
+ { name: "config", sourcePath: paths.configPath },
195
+ { name: "marketplace", sourcePath: paths.marketplacePath },
196
+ { name: "pluginRoot", sourcePath: paths.pluginRoot },
197
+ { name: "stableCache", sourcePath: join(paths.cacheRoot, stableVersion) },
198
+ ];
199
+ }
200
+
201
+ function stateHash(paths, stableVersion) {
202
+ return canonicalDigest(
203
+ stateItems(paths, stableVersion).map((item) => ({
204
+ name: item.name,
205
+ sourcePath: item.sourcePath,
206
+ hash: treeHash(item.sourcePath),
207
+ }))
208
+ );
209
+ }
210
+
211
+ export function snapshotCodexPluginSelection({ paths, snapshotRoot }) {
212
+ if (!paths || !snapshotRoot) throw new Error("codex_selection_snapshot_arguments_required");
213
+ if (existsSync(snapshotRoot)) throw new Error("codex_selection_snapshot_root_exists");
214
+ const version = pluginVersion(paths.pluginRoot);
215
+ const items = stateItems(paths, version).map((item) => {
216
+ if (!existsSync(item.sourcePath)) {
217
+ throw new Error(`codex_selection_snapshot_source_missing:${item.name}`);
218
+ }
219
+ const backupPath = join(snapshotRoot, "data", item.name);
220
+ copyExact(item.sourcePath, backupPath);
221
+ return { ...item, backupPath, hash: treeHash(item.sourcePath) };
222
+ });
223
+ const unsigned = {
224
+ schemaVersion: CODEX_PLUGIN_SELECTION_SCHEMA_VERSION,
225
+ createdAt: new Date().toISOString(),
226
+ pluginVersion: version,
227
+ stateHash: stateHash(paths, version),
228
+ paths,
229
+ items,
230
+ };
231
+ const manifest = { ...unsigned, snapshotHash: canonicalDigest(unsigned) };
232
+ const manifestPath = join(snapshotRoot, "snapshot.json");
233
+ mkdirSync(dirname(manifestPath), { recursive: true });
234
+ writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600 });
235
+ return { manifestPath, snapshotHash: manifest.snapshotHash, pluginVersion: version };
236
+ }
237
+
238
+ export function installCodexCandidateCache({ paths, sourcePluginRoot, version }) {
239
+ if (!paths || !sourcePluginRoot || !version) throw new Error("codex_candidate_cache_arguments_required");
240
+ if (pluginVersion(sourcePluginRoot) !== version) throw new Error("codex_candidate_version_mismatch");
241
+ const target = join(paths.cacheRoot, version);
242
+ ensureInside(paths.cacheRoot, target, "codex_candidate_cache_path_escape");
243
+ const sourceHash = treeHash(sourcePluginRoot);
244
+ if (existsSync(target)) {
245
+ if (treeHash(target) !== sourceHash) throw new Error("codex_candidate_cache_collision");
246
+ } else {
247
+ mkdirSync(paths.cacheRoot, { recursive: true });
248
+ const staging = join(paths.cacheRoot, `.phase98-${version}-${process.pid}`);
249
+ copyExact(sourcePluginRoot, staging);
250
+ if (treeHash(staging) !== sourceHash) throw new Error("codex_candidate_cache_copy_mismatch");
251
+ renameSync(staging, target);
252
+ }
253
+ return { version, cachePath: target, cacheHash: sourceHash };
254
+ }
255
+
256
+ function withSelectionLock(paths, fn) {
257
+ mkdirSync(paths.cacheRoot, { recursive: true });
258
+ const lockPath = join(paths.cacheRoot, ".phase98-selection.lock");
259
+ let handle;
260
+ try {
261
+ handle = openSync(lockPath, "wx", 0o600);
262
+ } catch {
263
+ throw new Error("codex_selection_concurrent_operation");
264
+ }
265
+ try {
266
+ return fn();
267
+ } finally {
268
+ closeSync(handle);
269
+ rmSync(lockPath, { force: true });
270
+ }
271
+ }
272
+
273
+ function atomicReplaceTree(source, target) {
274
+ const parent = dirname(target);
275
+ mkdirSync(parent, { recursive: true });
276
+ const staging = join(parent, `.${basename(target)}.phase98-next-${process.pid}`);
277
+ const previous = join(parent, `.${basename(target)}.phase98-prev-${process.pid}`);
278
+ copyExact(source, staging);
279
+ if (existsSync(target)) renameSync(target, previous);
280
+ try {
281
+ renameSync(staging, target);
282
+ rmSync(previous, { recursive: true, force: true });
283
+ } catch (error) {
284
+ rmSync(target, { recursive: true, force: true });
285
+ if (existsSync(previous)) renameSync(previous, target);
286
+ rmSync(staging, { recursive: true, force: true });
287
+ throw error;
288
+ }
289
+ }
290
+
291
+ function atomicReplaceFile(content, target) {
292
+ const parent = dirname(target);
293
+ mkdirSync(parent, { recursive: true });
294
+ const staging = join(parent, `.${basename(target)}.phase98-next-${process.pid}`);
295
+ const mode = existsSync(target) ? lstatSync(target).mode & 0o777 : 0o600;
296
+ writeFileSync(staging, content, { mode });
297
+ try {
298
+ renameSync(staging, target);
299
+ } catch (error) {
300
+ rmSync(staging, { force: true });
301
+ throw error;
302
+ }
303
+ }
304
+
305
+ export function activateCodexCandidateSelection(input) {
306
+ const {
307
+ paths,
308
+ snapshotManifestPath,
309
+ expectedSnapshotHash,
310
+ candidateVersion,
311
+ expectedCandidateHash,
312
+ approvalId,
313
+ approvedAt,
314
+ restoreBy,
315
+ now,
316
+ } = input;
317
+ if (!approvalId) throw new Error("codex_selection_approval_required");
318
+ const approvedMs = Date.parse(approvedAt);
319
+ const restoreMs = Date.parse(restoreBy);
320
+ const nowMs = Date.parse(now);
321
+ if (![approvedMs, restoreMs, nowMs].every(Number.isFinite)) {
322
+ throw new Error("codex_selection_window_invalid");
323
+ }
324
+ if (nowMs < approvedMs || nowMs >= restoreMs || restoreMs - approvedMs > MAX_CODEX_CANARY_WINDOW_MS) {
325
+ throw new Error("codex_selection_window_exceeds_limit");
326
+ }
327
+ return withSelectionLock(paths, () => {
328
+ const snapshot = readCanonicalManifest(snapshotManifestPath, expectedSnapshotHash);
329
+ if (stateHash(paths, snapshot.pluginVersion) !== snapshot.stateHash) {
330
+ throw new Error("codex_selection_current_state_drift");
331
+ }
332
+ const candidatePath = join(paths.cacheRoot, candidateVersion);
333
+ if (!existsSync(candidatePath) || treeHash(candidatePath) !== expectedCandidateHash) {
334
+ throw new Error("codex_selection_candidate_cache_mismatch");
335
+ }
336
+ const candidateConfig = candidateCodexConfig(
337
+ readFileSync(paths.configPath, "utf8"),
338
+ candidatePath
339
+ );
340
+ const configBackup = snapshot.items.find((item) => item.name === "config");
341
+ const pluginBackup = snapshot.items.find((item) => item.name === "pluginRoot");
342
+ if (!configBackup || !pluginBackup) {
343
+ throw new Error("codex_selection_snapshot_restore_items_missing");
344
+ }
345
+ try {
346
+ atomicReplaceFile(candidateConfig, paths.configPath);
347
+ atomicReplaceTree(candidatePath, paths.pluginRoot);
348
+ if (
349
+ pluginVersion(paths.pluginRoot) !== candidateVersion ||
350
+ treeHash(paths.pluginRoot) !== expectedCandidateHash
351
+ ) {
352
+ throw new Error("codex_selection_candidate_activation_mismatch");
353
+ }
354
+ } catch (error) {
355
+ copyExact(configBackup.backupPath, configBackup.sourcePath);
356
+ copyExact(pluginBackup.backupPath, pluginBackup.sourcePath);
357
+ throw error;
358
+ }
359
+ const candidateConfigHash = treeHash(paths.configPath);
360
+ const activation = {
361
+ schemaVersion: CODEX_PLUGIN_SELECTION_SCHEMA_VERSION,
362
+ approvalId,
363
+ approvedAt,
364
+ activatedAt: now,
365
+ restoreBy,
366
+ snapshotHash: expectedSnapshotHash,
367
+ stableVersion: snapshot.pluginVersion,
368
+ candidateVersion,
369
+ candidateHash: expectedCandidateHash,
370
+ candidateConfigHash,
371
+ changedPaths: [paths.pluginRoot, paths.configPath],
372
+ };
373
+ const activationPath = join(dirname(snapshotManifestPath), "activation.json");
374
+ writeFileSync(activationPath, `${JSON.stringify(activation, null, 2)}\n`, { mode: 0o600 });
375
+ return { ...activation, activationPath };
376
+ });
377
+ }
378
+
379
+ export function restoreCodexPluginSelection(input) {
380
+ const {
381
+ paths,
382
+ snapshotManifestPath,
383
+ expectedSnapshotHash,
384
+ activationPath,
385
+ now,
386
+ taskEvidence,
387
+ recoveryReason,
388
+ } = input;
389
+ return withSelectionLock(paths, () => {
390
+ const snapshot = readCanonicalManifest(snapshotManifestPath, expectedSnapshotHash);
391
+ const activation = JSON.parse(readFileSync(activationPath, "utf8"));
392
+ if (
393
+ activation.snapshotHash !== expectedSnapshotHash ||
394
+ activation.candidateHash !== treeHash(paths.pluginRoot)
395
+ ) throw new Error("codex_selection_active_candidate_drift");
396
+ if (
397
+ !activation.candidateConfigHash ||
398
+ activation.candidateConfigHash !== treeHash(paths.configPath)
399
+ ) {
400
+ throw new Error("codex_selection_active_config_drift");
401
+ }
402
+ if (!recoveryReason) {
403
+ if (!taskEvidence?.taskId || taskEvidence.resolvedCandidateHash !== activation.candidateHash) {
404
+ throw new Error("codex_selection_task_identity_mismatch");
405
+ }
406
+ }
407
+ for (const item of snapshot.items) copyExact(item.backupPath, item.sourcePath);
408
+ if (stateHash(paths, snapshot.pluginVersion) !== snapshot.stateHash) {
409
+ throw new Error("codex_selection_restore_hash_mismatch");
410
+ }
411
+ const deadlineExceeded = Date.parse(now) > Date.parse(activation.restoreBy);
412
+ const restoration = {
413
+ restored: true,
414
+ restoredAt: now,
415
+ deadlineExceeded,
416
+ recoveryReason: recoveryReason || null,
417
+ snapshotHash: expectedSnapshotHash,
418
+ stableVersion: snapshot.pluginVersion,
419
+ candidateVersion: activation.candidateVersion,
420
+ };
421
+ writeFileSync(
422
+ join(dirname(snapshotManifestPath), "restoration.json"),
423
+ `${JSON.stringify(restoration, null, 2)}\n`,
424
+ { mode: 0o600 }
425
+ );
426
+ return restoration;
427
+ });
428
+ }
429
+
430
+ export function readCodexPluginSelection({ paths, stableVersion } = {}) {
431
+ if (!paths) throw new Error("codex_selection_paths_required");
432
+ const version = pluginVersion(paths.pluginRoot);
433
+ const stable = stableVersion || version;
434
+ return {
435
+ pluginVersion: version,
436
+ pluginHash: treeHash(paths.pluginRoot),
437
+ configHash: treeHash(paths.configPath),
438
+ marketplaceHash: treeHash(paths.marketplacePath),
439
+ stableCacheHash: treeHash(join(paths.cacheRoot, stable)),
440
+ selectionHash: stateHash(paths, stable),
441
+ };
442
+ }
@@ -11,25 +11,13 @@ export const REQUIRED_SELLABLE_MCP_TOOLS = [
11
11
  "start_cli_login",
12
12
  "wait_for_cli_login",
13
13
  "bootstrap_create_campaign",
14
- "bootstrap_find_leads",
15
- "get_find_leads_run",
16
- "update_find_leads_run",
17
- "wait_for_find_leads_run",
18
- "cancel_find_leads",
19
- "reissue_find_leads_watch_link",
20
- "preflight_find_leads_provider",
21
14
  "get_subskill_prompt",
22
15
  "get_subskill_asset",
23
16
  "search_subskill_prompts",
24
17
  "create_campaign",
25
18
  "import_leads",
26
- "search_sales_nav",
27
- "search_prospeo",
28
- "search_signals",
29
- "select_promising_posts",
30
19
  "confirm_lead_list",
31
20
  "wait_for_lead_list_ready",
32
- "export_table_csv",
33
21
  "wait_for_campaign_table_ready",
34
22
  "update_campaign",
35
23
  "update_campaign_brief",
@@ -88,14 +76,6 @@ export const CREATE_CAMPAIGN_SMOKE_CALLS = [
88
76
  limit: 1200,
89
77
  },
90
78
  },
91
- {
92
- name: "get_subskill_prompt",
93
- arguments: {
94
- subskillName: "find-leads-v2",
95
- offset: 0,
96
- limit: 1200,
97
- },
98
- },
99
79
  {
100
80
  name: "get_subskill_prompt",
101
81
  arguments: {
@@ -137,15 +117,6 @@ export const CREATE_CAMPAIGN_SMOKE_CALLS = [
137
117
  limit: 1200,
138
118
  },
139
119
  },
140
- {
141
- name: "get_subskill_asset",
142
- arguments: {
143
- subskillName: "find-leads-v2",
144
- assetPath: "core/flow.v1.json",
145
- offset: 0,
146
- limit: 1200,
147
- },
148
- },
149
120
  ];
150
121
 
151
122
  function collectStderrLines(stream) {
@@ -372,7 +343,7 @@ async function verifyCreateCampaignSmoke({
372
343
  ok: false,
373
344
  missingTools,
374
345
  calls,
375
- error: `Missing or stale Sellable MCP tool(s): ${missingTools.join(", ")}. Reinstall @sellable/install and @sellable/mcp, restart or reload the host, then retry Find Leads.`,
346
+ error: `Missing smoke tool(s): ${missingTools.join(", ")}`,
376
347
  startedAt,
377
348
  completedAt: new Date().toISOString(),
378
349
  };
@@ -691,7 +662,7 @@ async function verifySellableMcpRuntimeOnce({
691
662
  ),
692
663
  calls: [],
693
664
  error:
694
- "Sellable MCP is missing required tools. Reinstall @sellable/install and @sellable/mcp, restart or reload the host, then retry Find Leads.",
665
+ "Skipping create-campaign smoke because required MCP tools are missing.",
695
666
  startedAt: new Date().toISOString(),
696
667
  completedAt: new Date().toISOString(),
697
668
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.327",
3
+ "version": "0.1.329",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {
@@ -15,7 +15,8 @@
15
15
  "lib",
16
16
  "agents",
17
17
  "skill-templates",
18
- "README.md"
18
+ "README.md",
19
+ "HERMES-SLACK-PROFILE-SCOPING.md"
19
20
  ],
20
21
  "dependencies": {
21
22
  "@modelcontextprotocol/sdk": "^1.25.2",
@@ -721,7 +721,7 @@ Treat host capabilities as concrete functions, not prose conventions:
721
721
  import and before dispatching Message Drafting only.
722
722
  - `launch_message_drafting`: Claude Code uses `Task` with `subagent_type`
723
723
  `post-find-leads-message-scout` when listed; Codex uses the returned
724
- compatibility agent or a generic `gpt-5.5` / `xhigh` Message Drafting agent.
724
+ compatibility agent or a generic `gpt-5.6-sol` / `high` Message Drafting agent.
725
725
 
726
726
  If a required interactive question function or MCP loader is missing, stop and
727
727
  explain the Sellable install/reload problem. Source work uses product-native MCP
@@ -1082,8 +1082,8 @@ updates.
1082
1082
  In Codex, the filter-choice answer is the campaign-scoped go-ahead to use
1083
1083
  this single Message Drafting background agent in step-wise and YOLO modes.
1084
1084
  Do not ask a separate question to start it. If the named custom agent is not
1085
- available, spawn a generic background agent with `model: "gpt-5.5"` and
1086
- `reasoning_effort: "xhigh"` using the same lean campaign/table basis. If no
1085
+ available, spawn a generic background agent with `model: "gpt-5.6-sol"` and
1086
+ `reasoning_effort: "high"` using the same lean campaign/table basis. If no
1087
1087
  background-agent tool is callable, start the same full message branch inline
1088
1088
  before filter drafting or skip-filter message review and record it as
1089
1089
  `statusSource: "parent-thread-fallback"`.
@@ -437,7 +437,7 @@ fallback only when app thread tools are unavailable; the receipt must include
437
437
  `fallbackReason`, or backend verify rejects it.
438
438
  When visible Codex app thread tools are unavailable but local Codex CLI is
439
439
  available, the accepted durable streaming-worker command shape is:
440
- `codex -a never -s danger-full-access -c model_reasoning_effort=xhigh exec --skip-git-repo-check -m <worker-model> -C <repo> -o <worker-final-file> -`.
440
+ `codex -a never -s danger-full-access -c model_reasoning_effort=high exec --skip-git-repo-check -m <worker-model> -C <repo> -o <worker-final-file> -`.
441
441
  The approval, sandbox, and reasoning-effort config flags must come before the
442
442
  `exec` subcommand for Codex CLI builds that expose `-a`/`-s`/`-c` only at top
443
443
  level. `--skip-git-repo-check` belongs after `exec` because current customer and VPS Codex CLI builds expose it
@@ -447,26 +447,26 @@ forms fail on current customer CLI installs. Pipe the lane packet prompt on
447
447
  stdin, require the worker to write `workerDispatch.receiptArtifactHint`, and
448
448
  pass exactly one lane packet per worker.
449
449
  When launching durable Codex CLI workers from an automation parent, pass an
450
- explicit supported worker model and explicit `xhigh` reasoning effort instead
450
+ explicit supported worker model and explicit `high` reasoning effort instead
451
451
  of relying on the Codex CLI defaults. Use the parent runtime model when known,
452
452
  for example:
453
- `codex -a never -s danger-full-access -c model_reasoning_effort=xhigh exec --skip-git-repo-check -m <worker-model> -C <repo> -o <worker-final-file> -`.
453
+ `codex -a never -s danger-full-access -c model_reasoning_effort=high exec --skip-git-repo-check -m <worker-model> -C <repo> -o <worker-final-file> -`.
454
454
  In Codex CLI, the parent runtime model is visible in the run header as
455
455
  `model: <model-name>`. Copy that exact model string into child worker launches
456
- first. If the parent header says `model: gpt-5.5`, launch workers with
457
- `-m gpt-5.5` plus `-c model_reasoning_effort=xhigh`; do not invent or probe
456
+ first. If the parent header says `model: gpt-5.6-sol`, launch workers with
457
+ `-m gpt-5.6-sol` plus `-c model_reasoning_effort=high`; do not invent or probe
458
458
  nearby aliases such as `gpt-5.3-codex`, `gpt-5.2`, `gpt-5-codex`,
459
459
  `codex-latest`, or `codex-mini-latest` before trying the exact parent model. A
460
- one-line probe must include the same `-c model_reasoning_effort=xhigh` override
460
+ one-line probe must include the same `-c model_reasoning_effort=high` override
461
461
  and count as supported only when it exits 0 and returns the requested output; a
462
462
  session header followed by a `not supported` error is rejected, not accepted.
463
463
  Do not rely on the Codex CLI default model or default reasoning effort; some
464
- customer and VPS installs default to unavailable model aliases or to `high`
465
- reasoning. `-m gpt-5.5` alone is not enough. If a child worker reports GPT 5.5
466
- with `high` reasoning, treat that as a launcher bug, relaunch with the explicit
467
- `xhigh` config before mutation, and do not ask the user to continue through the
464
+ customer and VPS installs default to unavailable model aliases or to reasoning
465
+ below `high`. `-m gpt-5.6-sol` alone is not enough. If a child worker reports GPT 5.6-Sol
466
+ with reasoning below `high`, treat that as a launcher bug, relaunch with the explicit
467
+ `high` config before mutation, and do not ask the user to continue through the
468
468
  model-quality warning. If the parent cannot identify a supported worker model
469
- and launch it with `xhigh` reasoning, stop with
469
+ and launch it with `high` reasoning, stop with
470
470
  `blocked: worker_model_unavailable` before mutation.
471
471
  When wrapping multiple local Codex CLI workers in a shell launcher, run the
472
472
  wrapper with `/bin/bash -lc` or another explicitly chosen portable shell. Do not
@@ -479,7 +479,7 @@ Do not embed `<<'WORKER_PROMPT'` heredocs inside a single quoted or double
479
479
  quoted `/bin/bash -lc '...'` command string; nested quoting is brittle and can
480
480
  truncate the first worker before mutation. For multi-worker launchers, write one
481
481
  plain prompt file per lane under the current run directory, then start each
482
- worker with `codex -a never -s danger-full-access -c model_reasoning_effort=xhigh exec --skip-git-repo-check -m "$WORKER_MODEL" -C "$REPO" -o
482
+ worker with `codex -a never -s danger-full-access -c model_reasoning_effort=high exec --skip-git-repo-check -m "$WORKER_MODEL" -C "$REPO" -o
483
483
  "$worker_final_file" - < "$worker_prompt_file"`. Keep launcher shell variables
484
484
  double-quoted and keep the prompt heredoc only in a standalone script/prompt-file
485
485
  write step, not inside an already quoted shell argument. If the first launcher
@@ -585,7 +585,7 @@ launchers, write the same prompt body to `<worker-prompt-file>` and launch the
585
585
  worker with stdin redirected from that file:
586
586
 
587
587
  ```
588
- codex -a never -s danger-full-access -c model_reasoning_effort=xhigh exec --skip-git-repo-check -m <worker-model> -C <repo> -o <worker-final-file> - <<'WORKER_PROMPT'
588
+ codex -a never -s danger-full-access -c model_reasoning_effort=high exec --skip-git-repo-check -m <worker-model> -C <repo> -o <worker-final-file> - <<'WORKER_PROMPT'
589
589
  Use $sellable:create-campaign as the governing campaign workflow for this one lane worker.
590
590
  Use the evergreen plan/packet below only for lane scope, source metadata,
591
591
  postconditions, side-effect caps, and durable receipt proof.
@@ -632,10 +632,10 @@ Do not launch, start, schedule, send, or use paid InMail.
632
632
  For filter proof, durable receipt status must be `filterDecisionReceipt.status:"applied"` only; never `completed`, `confirmed`, `done`, or aliases.
633
633
  For generated messages, `update_cell` is allowed only for the semantic Approved checkbox. Never use `update_cell` for generated message text/body/sample copy. Bad copy requires `revise_message_template_and_rerun` or brief/template revision plus Generate Message rerun. Any generated-message cell override is `blocked: generated_message_cell_override`.
634
634
  If `bootstrap_create_campaign.modelQuality.status === "warn"` because the child
635
- worker reports GPT 5.5 with `high` reasoning, that is a parent launcher
635
+ worker reports GPT 5.6-Sol with reasoning below `high`, that is a parent launcher
636
636
  configuration bug, not an operator approval path inside the worker. Stop before
637
637
  mutation, tell the parent to relaunch this lane with
638
- `-c model_reasoning_effort=xhigh`, and do not mark the worker goal complete.
638
+ `-c model_reasoning_effort=high`, and do not mark the worker goal complete.
639
639
  Complete only this lane. Do not end with narration only. Before your final
640
640
  response, run a local file-existence and JSON self-check for
641
641
  <receiptArtifactPath>. If the lane succeeded, write the canonical success
@@ -656,7 +656,7 @@ receipt, or an accepted Post Engagers no-source blocked/no-op receipt.
656
656
  WORKER_PROMPT
657
657
 
658
658
  # Multi-worker launcher equivalent:
659
- codex -a never -s danger-full-access -c model_reasoning_effort=xhigh exec --skip-git-repo-check -m <worker-model> -C <repo> -o <worker-final-file> - < <worker-prompt-file>
659
+ codex -a never -s danger-full-access -c model_reasoning_effort=high exec --skip-git-repo-check -m <worker-model> -C <repo> -o <worker-final-file> - < <worker-prompt-file>
660
660
  ```
661
661
 
662
662
  If any placeholder cannot be filled from the current plan, matching
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: refill-sends-v2
3
3
  description: Execute refill sends v2 through the fenced evergreen refill loop, with dry-run and resume support.
4
- visibility: public
4
+ visibility: internal
5
5
  allowed-tools:
6
- - mcp__sellable__refill_sends_v2
6
+ - mcp__sellable__refill_sends
7
7
  - mcp__sellable__get_refill_plan_v2
8
8
  - mcp__sellable__get_subskill_prompt
9
9
  - mcp__sellable__get_subskill_asset
@@ -24,7 +24,7 @@ Host command names:
24
24
  - Claude Code: `/sellable:refill-sends-v2`
25
25
  - Codex: `$sellable:refill-sends-v2`
26
26
 
27
- `refill_sends_v2` is the execution surface. In real-run mode it starts or
27
+ `refill_sends` is the execution surface. In real-run mode it starts or
28
28
  resumes a fenced refill run, reads a fresh packet, executes only the packet's
29
29
  named bounded work, verifies the result, and returns either a terminal report, a
30
30
  blocked report, or an in-progress resume handle. In dry-run mode it stays
@@ -40,19 +40,19 @@ with `WORKSPACE_REQUIRED`; do not switch the shared active workspace.
40
40
  For a real refill run:
41
41
 
42
42
  ```text
43
- refill_sends_v2({ workspaceId, intent:"auto", senderIds?, approvalMode? })
43
+ refill_sends({ workspaceId, intent:"auto", senderIds?, approvalMode? })
44
44
  ```
45
45
 
46
46
  For read-only inspection:
47
47
 
48
48
  ```text
49
- refill_sends_v2({ workspaceId, dryRun:true, intent:"auto", senderIds? })
49
+ refill_sends({ workspaceId, dryRun:true, intent:"auto", senderIds? })
50
50
  ```
51
51
 
52
52
  To resume an in-progress run, pass the handle back exactly:
53
53
 
54
54
  ```text
55
- refill_sends_v2({ workspaceId, runId, fence })
55
+ refill_sends({ workspaceId, runId, fence })
56
56
  ```
57
57
 
58
58
  If a stale handle loses the lease, the tool reports the holder status and the