@sellable/mcp 0.1.957 → 0.1.959

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.
Files changed (48) hide show
  1. package/agents/post-find-leads-message-scout.md +4 -4
  2. package/agents/registry.json +1 -1
  3. package/dist/server.js +2 -2
  4. package/dist/tools/campaigns.js +5 -4
  5. package/dist/tools/prompts.d.ts +12 -4
  6. package/dist/tools/prompts.js +23 -7
  7. package/dist/tools/refill-v3-advance.js +1 -1
  8. package/dist/tools/refill-v3-campaign-work.d.ts +6 -0
  9. package/dist/tools/refill-v3-continue-contract.d.ts +4 -0
  10. package/dist/tools/refill-v3-continue-contract.js +3 -0
  11. package/dist/tools/refill-v3-continue.d.ts +18 -5
  12. package/dist/tools/refill-v3-continue.js +177 -85
  13. package/dist/tools/registry.d.ts +6 -2
  14. package/package.json +1 -1
  15. package/skills/create-campaign/SKILL.md +3 -3
  16. package/skills/create-campaign-v2/SKILL.md +1 -1
  17. package/skills/create-campaign-v2/SOUL.md +2 -1
  18. package/skills/create-campaign-v2/core/policy.md +2 -2
  19. package/skills/create-campaign-v2/references/approval-gate-framing.md +2 -1
  20. package/skills/create-campaign-v2/references/message-approach.md +25 -16
  21. package/skills/create-campaign-v2/references/parallel-critique-protocol.md +2 -2
  22. package/skills/create-campaign-v2/references/sample-validation-loop.md +1 -1
  23. package/skills/create-campaign-v2-tail/SKILL.md +2 -2
  24. package/skills/create-campaign-v2-validation/SKILL.md +2 -2
  25. package/skills/{conversation-chess → draft-message}/SKILL.md +2 -2
  26. package/skills/{conversation-chess → draft-message}/compiled/follow-up/endpoint.md +1 -1
  27. package/skills/{conversation-chess → draft-message}/compiled/follow-up/semantic.md +3 -3
  28. package/skills/{conversation-chess → draft-message}/compiled/manifest.json +22 -22
  29. package/skills/{conversation-chess → draft-message}/compiled/opener/endpoint.md +1 -1
  30. package/skills/{conversation-chess → draft-message}/compiled/opener/semantic.md +3 -3
  31. package/skills/{conversation-chess → draft-message}/compiled/reply/endpoint.md +1 -1
  32. package/skills/{conversation-chess → draft-message}/compiled/reply/semantic.md +3 -3
  33. package/skills/{conversation-chess → draft-message}/installed/SKILL.md +7 -6
  34. package/skills/first-campaign-onboarding/SKILL.md +8 -7
  35. package/skills/generate-messages/SKILL.md +13 -11
  36. package/skills/refill-sends/SKILL.md +7 -0
  37. package/skills/refill-sends-workflow/SKILL.md +7 -0
  38. package/skills/weekly-campaign-summary/SKILL.md +1 -1
  39. /package/skills/{conversation-chess → draft-message}/core/input-contract.md +0 -0
  40. /package/skills/{conversation-chess → draft-message}/core/situation-index.json +0 -0
  41. /package/skills/{conversation-chess → draft-message}/modes/follow-up.md +0 -0
  42. /package/skills/{conversation-chess → draft-message}/modes/opener.md +0 -0
  43. /package/skills/{conversation-chess → draft-message}/modes/reply.md +0 -0
  44. /package/skills/{conversation-chess → draft-message}/references/follow-ups.md +0 -0
  45. /package/skills/{conversation-chess → draft-message}/references/opener-editor.md +0 -0
  46. /package/skills/{conversation-chess → draft-message}/references/openers.md +0 -0
  47. /package/skills/{conversation-chess → draft-message}/references/rapport.md +0 -0
  48. /package/skills/{conversation-chess → draft-message}/references/replies.md +0 -0
@@ -18,7 +18,7 @@ export const REFILL_V3_CONTINUE_TOOL_NAME = "refill_v3_continue";
18
18
  export const refillV3ContinueToolDefinitions = [
19
19
  {
20
20
  name: REFILL_V3_CONTINUE_TOOL_NAME,
21
- description: "Continue exactly one server-issued Refill V3 campaign-attention packet. continuation_pending means the same operation is still running: wait retryAfterMs and call this tool with identical scope and no decision until it returns. Reconcile transport timeouts the same way; do not call advance or dispatch another search while pending. Call first without decision: after a fresh revalidation, the tool deterministically owns live-job waiting, selected-source row copying, exact actionable enrichment, and same-template message generation. It returns model_decision_required only when the accurate packet needs a genuine repair, criteria, rubric, waterfall, or source-expansion judgment; then call again with that one closed decision. Only an exact fresh packet can dispatch one existing campaign-work authority, which re-observes the exact sender/date and settles before a sibling campaign may advance. The caller cannot supply batch sizes, readiness digests, retry counters, fill inputs, scheduler inputs, or idempotency keys.",
21
+ description: "Continue exactly one server-issued Refill V3 campaign-attention packet. continuation_pending means the same operation is still running: wait retryAfterMs and call this tool with identical scope and no decision until it returns. Reconcile transport timeouts the same way; do not call advance or dispatch another search while pending. Call first without decision: after a fresh revalidation, the tool deterministically owns live-job waiting, selected-source row copying, exact actionable enrichment, and same-template message generation. It returns model_decision_required only when the accurate packet needs a genuine repair, criteria, rubric, waterfall, or source-expansion judgment; then call again with that one closed decision. Only an exact fresh packet can dispatch one existing campaign-work authority, which re-observes the exact sender/date and settles before a sibling campaign may advance. The caller cannot supply batch sizes, readiness digests, retry counters, fill inputs, scheduler inputs, or idempotency keys. A result with recoveredFromError already re-observed after a failure: handle it by its kind. A thrown error is not a terminal: call refill_v3_advance once more with the identical scope and report a blocker only if it fails the same way.",
22
22
  inputSchema: REFILL_V3_CONTINUE_INPUT_SCHEMA,
23
23
  outputSchema: REFILL_V3_CONTINUE_OUTPUT_SCHEMA,
24
24
  annotations: {
@@ -183,6 +183,33 @@ const DEFAULT_PRODUCTION_OWNERS = {
183
183
  export async function runRefillV3CampaignWorkWithProductionOwners(input, owners = DEFAULT_PRODUCTION_OWNERS) {
184
184
  const decoded = decodeRefillV3CampaignWorkOutput(input.output);
185
185
  const chosen = decoded.ok ? decoded.value.actions[0] : null;
186
+ // A source-cycle row is the run's BOOKKEEPING for a source effect that has
187
+ // already happened or been refused; it is never the effect itself. Throwing
188
+ // here on 2026-10-05 discarded Justin's applied 396-row engager import and
189
+ // stopped the Slack run with nothing scheduled. A transient failure is
190
+ // retried in place (retrying the whole stage would re-dispatch the search);
191
+ // any other failure rides on the receipt, and the persisted campaign-work
192
+ // receipt plus the fresh re-observation still guard against a repeat.
193
+ const recordSourceCycle = async (record) => {
194
+ if (!owners.recordSourceCycle)
195
+ return undefined;
196
+ const sleep = owners.sleep ??
197
+ ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
198
+ for (let attempt = 0;; attempt += 1) {
199
+ try {
200
+ await owners.recordSourceCycle(record);
201
+ return undefined;
202
+ }
203
+ catch (error) {
204
+ const retryDelay = POST_ADVANCE_TRANSPORT_RETRY_DELAYS_MS[attempt];
205
+ if (retryDelay === undefined ||
206
+ !isRetryablePostAdvanceTransportError(error)) {
207
+ return error instanceof Error ? error.message : String(error);
208
+ }
209
+ await sleep(retryDelay);
210
+ }
211
+ }
212
+ };
186
213
  let diagnosticRows = [];
187
214
  const record = (value) => value && typeof value === "object" && !Array.isArray(value)
188
215
  ? value
@@ -195,6 +222,9 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
195
222
  const scopeEcho = `${input.packet.scope.workspaceId}:${input.packet.scope.campaignId}:${input.packet.scope.tableId}`;
196
223
  const receipt = (params) => ({
197
224
  ...(params.batchPlan === undefined ? {} : { batchPlan: params.batchPlan }),
225
+ ...(params.sourceCycleRecordError === undefined
226
+ ? {}
227
+ : { sourceCycleRecordError: params.sourceCycleRecordError }),
198
228
  outcome: params.outcome,
199
229
  result: params.result,
200
230
  scopeEcho,
@@ -813,7 +843,10 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
813
843
  senderId: request.scope.senderId,
814
844
  date: request.scope.date,
815
845
  lane: input.packet.scope.lane,
816
- family: input.packet.evidence.leadSourceFamily,
846
+ // The family being SEARCHED, which a family switch makes differ from
847
+ // the packet's observed family. Its negative set is the one that
848
+ // guards this dispatch, and the receipt below names the same family.
849
+ family: request.sourceCommand.family,
817
850
  protectedFitDigest,
818
851
  };
819
852
  // The run fact scopes both sides of reconciliation: writes are stamped
@@ -884,28 +917,27 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
884
917
  : null,
885
918
  });
886
919
  if (sourceOutcome.kind === "dispatched") {
887
- if (owners.recordSourceCycle) {
888
- await owners.recordSourceCycle({
889
- ...sourceCycleScope,
890
- ...sourceCycleRunFact,
891
- receipt: {
892
- schemaVersion: 1,
893
- kind: "search",
894
- campaignId: request.scope.campaignId,
895
- family: request.sourceCommand.family,
896
- protectedFitDigest,
897
- requestFingerprint: request.sourceCommand.requestFingerprint,
898
- sourceFingerprint: sourceOutcome.receipt.sourceAfterFingerprint,
899
- keywordBatch: request.sourceCommand.keywordBatch,
900
- postIds: request.sourceCommand.selectedPostIds,
901
- importedCount: sourceOutcome.receipt.importedCount,
902
- yieldEvidence: sourceOutcome.receipt.yieldEvidence,
903
- observedAt: request.scope.date,
904
- },
905
- });
906
- }
920
+ const sourceCycleRecordError = await recordSourceCycle({
921
+ ...sourceCycleScope,
922
+ ...sourceCycleRunFact,
923
+ receipt: {
924
+ schemaVersion: 1,
925
+ kind: "search",
926
+ campaignId: request.scope.campaignId,
927
+ family: request.sourceCommand.family,
928
+ protectedFitDigest,
929
+ requestFingerprint: request.sourceCommand.requestFingerprint,
930
+ sourceFingerprint: sourceOutcome.receipt.sourceAfterFingerprint,
931
+ keywordBatch: request.sourceCommand.keywordBatch,
932
+ postIds: request.sourceCommand.selectedPostIds,
933
+ importedCount: sourceOutcome.receipt.importedCount,
934
+ yieldEvidence: sourceOutcome.receipt.yieldEvidence,
935
+ observedAt: request.scope.date,
936
+ },
937
+ });
907
938
  const changed = sourceOutcome.receipt.importedCount > 0;
908
939
  return receipt({
940
+ sourceCycleRecordError,
909
941
  outcome: changed ? "applied" : "no_effect",
910
942
  result,
911
943
  idempotencyKey: request.idempotencyKey,
@@ -914,27 +946,26 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
914
946
  });
915
947
  }
916
948
  if (sourceOutcome.kind === "recorded_exhaustion") {
917
- if (owners.recordSourceCycle) {
918
- await owners.recordSourceCycle({
919
- ...sourceCycleScope,
920
- ...sourceCycleRunFact,
921
- receipt: {
922
- schemaVersion: 1,
923
- kind: "exhaustion",
924
- campaignId: request.scope.campaignId,
925
- family: request.sourceCommand.family,
926
- protectedFitDigest,
927
- requestFingerprint: request.idempotencyKey,
928
- sourceFingerprint: priorSourceFingerprint ?? request.scope.evidenceFingerprint,
929
- keywordBatch: null,
930
- postIds: null,
931
- importedCount: 0,
932
- yieldEvidence: sourceOutcome.receipt.yieldEvidence,
933
- observedAt: request.scope.date,
934
- },
935
- });
936
- }
949
+ const sourceCycleRecordError = await recordSourceCycle({
950
+ ...sourceCycleScope,
951
+ ...sourceCycleRunFact,
952
+ receipt: {
953
+ schemaVersion: 1,
954
+ kind: "exhaustion",
955
+ campaignId: request.scope.campaignId,
956
+ family: request.sourceCommand.family,
957
+ protectedFitDigest,
958
+ requestFingerprint: request.idempotencyKey,
959
+ sourceFingerprint: priorSourceFingerprint ?? request.scope.evidenceFingerprint,
960
+ keywordBatch: null,
961
+ postIds: null,
962
+ importedCount: 0,
963
+ yieldEvidence: sourceOutcome.receipt.yieldEvidence,
964
+ observedAt: request.scope.date,
965
+ },
966
+ });
937
967
  return receipt({
968
+ sourceCycleRecordError,
938
969
  outcome: "no_effect",
939
970
  result,
940
971
  idempotencyKey: request.idempotencyKey,
@@ -949,7 +980,7 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
949
980
  // returning the retained job wait. Previously the pending branch
950
981
  // wrote nothing, so the job could settle and disappear while the
951
982
  // next advance still advertised the identical round as untried.
952
- await owners.recordSourceCycle({
983
+ const sourceCycleRecordError = await recordSourceCycle({
953
984
  ...sourceCycleScope,
954
985
  ...sourceCycleRunFact,
955
986
  receipt: {
@@ -975,6 +1006,7 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
975
1006
  // evidence-free timeout: reobserve so the next coordinator step can
976
1007
  // wait on (or consume) the exact job without replaying the search.
977
1008
  return receipt({
1009
+ sourceCycleRecordError,
978
1010
  outcome: "no_effect",
979
1011
  result,
980
1012
  idempotencyKey: request.idempotencyKey,
@@ -995,27 +1027,26 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
995
1027
  // authority, and let the same outer run re-observe/continue. Returning
996
1028
  // `receipt_incomplete` here left settlement null and repeated the
997
1029
  // same packet while every sibling sender/lane stayed unreachable.
998
- if (owners.recordSourceCycle) {
999
- await owners.recordSourceCycle({
1000
- ...sourceCycleScope,
1001
- ...sourceCycleRunFact,
1002
- receipt: {
1003
- schemaVersion: 1,
1004
- kind: "exhaustion",
1005
- campaignId: request.scope.campaignId,
1006
- family: request.sourceCommand.family,
1007
- protectedFitDigest,
1008
- requestFingerprint: request.idempotencyKey,
1009
- sourceFingerprint: priorSourceFingerprint ?? request.scope.evidenceFingerprint,
1010
- keywordBatch: null,
1011
- postIds: null,
1012
- importedCount: 0,
1013
- yieldEvidence: input.packet.evidence.priorYieldReceipt,
1014
- observedAt: request.scope.date,
1015
- },
1016
- });
1017
- }
1030
+ const sourceCycleRecordError = await recordSourceCycle({
1031
+ ...sourceCycleScope,
1032
+ ...sourceCycleRunFact,
1033
+ receipt: {
1034
+ schemaVersion: 1,
1035
+ kind: "exhaustion",
1036
+ campaignId: request.scope.campaignId,
1037
+ family: request.sourceCommand.family,
1038
+ protectedFitDigest,
1039
+ requestFingerprint: request.idempotencyKey,
1040
+ sourceFingerprint: priorSourceFingerprint ?? request.scope.evidenceFingerprint,
1041
+ keywordBatch: null,
1042
+ postIds: null,
1043
+ importedCount: 0,
1044
+ yieldEvidence: input.packet.evidence.priorYieldReceipt,
1045
+ observedAt: request.scope.date,
1046
+ },
1047
+ });
1018
1048
  return receipt({
1049
+ sourceCycleRecordError,
1019
1050
  outcome: "no_effect",
1020
1051
  result: "source_frontier_exhausted",
1021
1052
  idempotencyKey: request.idempotencyKey,
@@ -1059,28 +1090,26 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
1059
1090
  yieldEvidence: input.packet.evidence.priorYieldReceipt,
1060
1091
  });
1061
1092
  if (exhaustionVerdict.exhausted) {
1062
- if (owners.recordSourceCycle) {
1063
- await owners.recordSourceCycle({
1064
- ...sourceCycleScope,
1065
- ...sourceCycleRunFact,
1066
- receipt: {
1067
- schemaVersion: 1,
1068
- kind: "exhaustion",
1069
- campaignId: request.scope.campaignId,
1070
- family: request.sourceCommand.family,
1071
- protectedFitDigest,
1072
- requestFingerprint: request.idempotencyKey,
1073
- sourceFingerprint: priorSourceFingerprint ??
1074
- request.scope.evidenceFingerprint,
1075
- keywordBatch: null,
1076
- postIds: null,
1077
- importedCount: 0,
1078
- yieldEvidence: input.packet.evidence.priorYieldReceipt,
1079
- observedAt: request.scope.date,
1080
- },
1081
- });
1082
- }
1093
+ const sourceCycleRecordError = await recordSourceCycle({
1094
+ ...sourceCycleScope,
1095
+ ...sourceCycleRunFact,
1096
+ receipt: {
1097
+ schemaVersion: 1,
1098
+ kind: "exhaustion",
1099
+ campaignId: request.scope.campaignId,
1100
+ family: request.sourceCommand.family,
1101
+ protectedFitDigest,
1102
+ requestFingerprint: request.idempotencyKey,
1103
+ sourceFingerprint: priorSourceFingerprint ?? request.scope.evidenceFingerprint,
1104
+ keywordBatch: null,
1105
+ postIds: null,
1106
+ importedCount: 0,
1107
+ yieldEvidence: input.packet.evidence.priorYieldReceipt,
1108
+ observedAt: request.scope.date,
1109
+ },
1110
+ });
1083
1111
  return receipt({
1112
+ sourceCycleRecordError,
1084
1113
  outcome: "no_effect",
1085
1114
  result: "source_frontier_exhausted",
1086
1115
  idempotencyKey: request.idempotencyKey,
@@ -1143,12 +1172,13 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
1143
1172
  });
1144
1173
  }
1145
1174
  if (request.action === "record_source_frontier_exhausted") {
1175
+ let sourceCycleRecordError;
1146
1176
  if (owners.recordSourceCycle) {
1147
1177
  const protectedFitDigest = input.packet.evidence.protectedFitDigest ||
1148
1178
  `sha256:${createHash("sha256")
1149
1179
  .update(input.packet.evidence.campaignObjective)
1150
1180
  .digest("hex")}`;
1151
- await owners.recordSourceCycle({
1181
+ sourceCycleRecordError = await recordSourceCycle({
1152
1182
  workspaceId: request.scope.workspaceId,
1153
1183
  runToken: input.runToken,
1154
1184
  scopeFingerprint: input.scopeFingerprint,
@@ -1176,6 +1206,7 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
1176
1206
  });
1177
1207
  }
1178
1208
  return receipt({
1209
+ sourceCycleRecordError,
1179
1210
  outcome: "no_effect",
1180
1211
  result,
1181
1212
  idempotencyKey: request.idempotencyKey,
@@ -1368,7 +1399,63 @@ function deterministicCampaignDecision(packet) {
1368
1399
  consumedDiagnosticRowIds: null,
1369
1400
  };
1370
1401
  }
1402
+ /**
1403
+ * V33-JUSTIN-SELF-HEAL · an unattended refill never ends on a raw error that
1404
+ * a fresh observation can answer.
1405
+ *
1406
+ * On 2026-10-05 Justin's own-post refresh imported 396 engagers and a
1407
+ * bookkeeping 409 then escaped as a raw error, so the Slack host reported a
1408
+ * blocker with nothing scheduled although the run credential was still
1409
+ * current. Once this continuation holds a refreshed credential, any failure
1410
+ * gets ONE re-observation with it. When the world moved, that observation is
1411
+ * returned like any other and names the error it recovered from. When nothing
1412
+ * moved, the same work would fail the same way, so the original error stands:
1413
+ * a deterministic failure surfaces instead of looping. A failure before the
1414
+ * credential was refreshed is never masked.
1415
+ */
1371
1416
  async function executeRefillV3Continuation(input, dependencies) {
1417
+ const progress = {
1418
+ runToken: null,
1419
+ packet: null,
1420
+ };
1421
+ try {
1422
+ return await executeRefillV3ContinuationStep(input, dependencies, progress);
1423
+ }
1424
+ catch (error) {
1425
+ const runToken = progress.runToken;
1426
+ const failedPacket = progress.packet;
1427
+ if (!runToken || !failedPacket)
1428
+ throw error;
1429
+ let observed;
1430
+ try {
1431
+ observed = await reconnectPostAdvanceStage({
1432
+ sleep: dependencies.sleep,
1433
+ operation: () => dependencies.advance({
1434
+ workspaceId: failedPacket.scope.workspaceId,
1435
+ scope: input.scope,
1436
+ ...(input.campaignId === undefined
1437
+ ? {}
1438
+ : { campaignId: input.campaignId }),
1439
+ ...(input.yolo === undefined ? {} : { yolo: input.yolo }),
1440
+ runToken,
1441
+ }),
1442
+ });
1443
+ }
1444
+ catch {
1445
+ throw error;
1446
+ }
1447
+ if (observed.kind === "campaign_attention_required" &&
1448
+ samePacket(failedPacket, observed.packet)) {
1449
+ throw error;
1450
+ }
1451
+ return {
1452
+ ...continuationResultFromAdvance(observed),
1453
+ recoveredFromError: (error instanceof Error ? error.message : String(error)) ||
1454
+ "unknown_error",
1455
+ };
1456
+ }
1457
+ }
1458
+ async function executeRefillV3ContinuationStep(input, dependencies, progress) {
1372
1459
  const workspaceId = normalizeExplicitWorkspaceId(input.workspaceId);
1373
1460
  if (!workspaceId || workspaceId !== input.packet.scope.workspaceId) {
1374
1461
  throw new Error("refill_v3_continue requires one exact workspace matching the server-issued packet.");
@@ -1422,6 +1509,8 @@ async function executeRefillV3Continuation(input, dependencies) {
1422
1509
  if (!runToken || !scopeFingerprint) {
1423
1510
  throw new Error("refill_v3_continue got no refreshed run credential from its own internal advance, which already consumed this run's sequence. Refusing to return a spent token.");
1424
1511
  }
1512
+ progress.runToken = runToken;
1513
+ progress.packet = advanceResult.packet;
1425
1514
  const deterministicDecision = deterministicCampaignDecision(advanceResult.packet);
1426
1515
  const decision = deterministicDecision ?? input.decision;
1427
1516
  if (decision === undefined) {
@@ -1642,6 +1731,9 @@ async function executeRefillV3Continuation(input, dependencies) {
1642
1731
  if (input.yolo === true &&
1643
1732
  (preparationOwnsSettlement || directlyQueuedRowsOwnSettlement) &&
1644
1733
  campaignSettlementIsReady(settlement)) {
1734
+ // This advance spends the refreshed credential, so a failure past here has
1735
+ // no unspent credential left to re-observe with.
1736
+ progress.runToken = null;
1645
1737
  const postSettlementAdvance = await reconnectPostAdvanceStage({
1646
1738
  sleep: dependencies.sleep,
1647
1739
  operation: () => dependencies.advance({
@@ -1313,7 +1313,7 @@ export declare const allTools: ({
1313
1313
  properties: {
1314
1314
  subskillName: {
1315
1315
  type: string;
1316
- enum: readonly ["avoid-ai-writing", "building-gtm-tables", "campaign-daily-results", "campaign-daily-review", "campaign-weekly-review", "content", "conversation-chess", "create-ab-test", "create-campaign", "create-campaign-brief", "create-campaign-v2", "create-campaign-v2-tail", "create-campaign-v2-validation", "create-evergreen-campaigns", "create-post", "create-rubric", "engage", "enrich-prospects", "fill-send-horizon", "find-leads", "find-leads-v2", "first-campaign-onboarding", "foundation", "generate-messages", "interview", "load-voice", "refresh-sender-engagement", "refill-sends", "refill-sends-waterfall-order", "refill-sends-work-campaign", "refill-sends-workflow", "research", "research-prospect", "research-sender", "weekly-campaign-summary", "workflow-sequences"];
1316
+ enum: readonly ["avoid-ai-writing", "building-gtm-tables", "campaign-daily-results", "campaign-daily-review", "campaign-weekly-review", "content", "conversation-chess", "create-ab-test", "create-campaign", "create-campaign-brief", "create-campaign-v2", "create-campaign-v2-tail", "create-campaign-v2-validation", "create-evergreen-campaigns", "create-post", "create-rubric", "draft-message", "engage", "enrich-prospects", "fill-send-horizon", "find-leads", "find-leads-v2", "first-campaign-onboarding", "foundation", "generate-messages", "interview", "load-voice", "refresh-sender-engagement", "refill-sends", "refill-sends-waterfall-order", "refill-sends-work-campaign", "refill-sends-workflow", "research", "research-prospect", "research-sender", "weekly-campaign-summary", "workflow-sequences"];
1317
1317
  description: string;
1318
1318
  };
1319
1319
  offset: {
@@ -1348,7 +1348,7 @@ export declare const allTools: ({
1348
1348
  properties: {
1349
1349
  subskillName: {
1350
1350
  type: string;
1351
- enum: readonly ["avoid-ai-writing", "building-gtm-tables", "campaign-daily-results", "campaign-daily-review", "campaign-weekly-review", "content", "conversation-chess", "create-ab-test", "create-campaign", "create-campaign-brief", "create-campaign-v2", "create-campaign-v2-tail", "create-campaign-v2-validation", "create-evergreen-campaigns", "create-post", "create-rubric", "engage", "enrich-prospects", "fill-send-horizon", "find-leads", "find-leads-v2", "first-campaign-onboarding", "foundation", "generate-messages", "interview", "load-voice", "refresh-sender-engagement", "refill-sends", "refill-sends-waterfall-order", "refill-sends-work-campaign", "refill-sends-workflow", "research", "research-prospect", "research-sender", "weekly-campaign-summary", "workflow-sequences"];
1351
+ enum: readonly ["avoid-ai-writing", "building-gtm-tables", "campaign-daily-results", "campaign-daily-review", "campaign-weekly-review", "content", "conversation-chess", "create-ab-test", "create-campaign", "create-campaign-brief", "create-campaign-v2", "create-campaign-v2-tail", "create-campaign-v2-validation", "create-evergreen-campaigns", "create-post", "create-rubric", "draft-message", "engage", "enrich-prospects", "fill-send-horizon", "find-leads", "find-leads-v2", "first-campaign-onboarding", "foundation", "generate-messages", "interview", "load-voice", "refresh-sender-engagement", "refill-sends", "refill-sends-waterfall-order", "refill-sends-work-campaign", "refill-sends-workflow", "research", "research-prospect", "research-sender", "weekly-campaign-summary", "workflow-sequences"];
1352
1352
  description: string;
1353
1353
  };
1354
1354
  assetPath: {
@@ -11516,6 +11516,10 @@ export declare const allTools: ({
11516
11516
  readonly receiptPersistence: {
11517
11517
  readonly type: "object";
11518
11518
  };
11519
+ readonly recoveredFromError: {
11520
+ readonly type: "string";
11521
+ readonly minLength: 1;
11522
+ };
11519
11523
  };
11520
11524
  readonly additionalProperties: false;
11521
11525
  readonly allOf: readonly [{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.957",
3
+ "version": "0.1.959",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",
@@ -216,8 +216,8 @@ preparation, the target is wrong, or status shows the wrong campaign/table, call
216
216
  `cancel_campaign_message_preparation`; otherwise do not cancel a healthy prepare
217
217
  run. Never call `start_campaign` from message preparation; final launch remains
218
218
  a separate explicit user greenlight.
219
- Use Template is the default message path; AI Generated is only an explicit
220
- opt-out.
219
+ First messages use Conversations or Message Testing; name the one you pick
220
+ as create-campaign-v2 `references/message-approach.md` says.
221
221
 
222
222
  ## DNC And Blocklist Handling
223
223
 
@@ -598,7 +598,7 @@ Drafting finishes or the recommendation is reviewed. If the user skips filters,
598
598
  first, then move to Messages/message review after it has started or returned a
599
599
  ready recommendation. `update_campaign({ currentStep: "messages" })` is not
600
600
  proof of kickoff. Enrichment/filtering and Generate Message cells wait for
601
- message approval. AI Generated is an explicit opt-out from the template path.
601
+ message approval (Message Testing only; Conversations has no template).
602
602
 
603
603
  The Message Drafting handoff must stay lean. Include only `campaignId`,
604
604
  `workflowTableId`, a concise brief summary, concise source summary/source-use
@@ -86,7 +86,7 @@ and carry proof gaps into the brief.
86
86
 
87
87
  Before `create_campaign`, load `references/message-approach.md` and choose how
88
88
  first messages are written: Conversations (each written for the person, no
89
- template) or Message Lab (one approved template). Pass it as `messageApproach`.
89
+ template) or Message Testing (one approved template). Pass it as `messageApproach`.
90
90
 
91
91
  ## Brief Provenance
92
92
 
@@ -339,7 +339,8 @@ Leads with durable `currentStep: "apply-icp-rubric"` while Message Drafting
339
339
  finishes or the template is approved. The join gate is saved-filter approval or
340
340
  a resolved skip-filter choice, plus a message recommendation grounded in the
341
341
  same campaign/table basis. Enrichment, filtering, and Generate Message cells
342
- wait for template approval on the Use Template path.
342
+ wait for template approval in a Message Testing campaign; a Conversations
343
+ campaign has no template (see `references/message-approach.md`).
343
344
  Message Drafting does not wait for `save_rubrics`: if filters were chosen but
344
345
  saved `leadScoringRubrics` are not visible yet, it should still return the
345
346
  initial reusable template from the campaign-table execution slice with
@@ -39,8 +39,8 @@ mutation gates:
39
39
  - Copy the confirmed source list into the campaign, but process only the first
40
40
  review/process sample before message approval.
41
41
  - Do not call `queue_campaign_cells` for enrichment/filtering until filters are
42
- approved when enabled and the default Use Template path has an approved
43
- message template/token rule set.
42
+ approved when enabled and, in a Message Testing campaign, there is an approved
43
+ message template/token rule set (a Conversations campaign has none).
44
44
  - Do not attach a sequence or call `start_campaign` until the user explicitly chooses to launch, unless `launchConsent.mode` is `autopilot`: that one-time answer pre-approves every gate in this list, while the quality floors and sample caps still apply.
45
45
  - If the user requested an exact scheduled send count, or asked to fill up/load
46
46
  sends for attached senders, prepare that bounded count first. Capacity-fill
@@ -213,7 +213,8 @@ If `message-validation.md` has an extractable `Selected Winner`, the packet's
213
213
  supported prospect-row `{{tokens}}`. Do not write a fresh approval-template
214
214
  message from the brief. If no winner/template can be extracted, this exact-match
215
215
  gate is skipped, but the packet still needs a usable approved template unless
216
- the user explicitly chose AI-generated messages.
216
+ the campaign uses Conversations (`messageApproach: "conversation"`), which has
217
+ no template.
217
218
 
218
219
  Do NOT render the packet as a bundle of opaque validation anchors or a dump
219
220
  of raw JSON. The user is approving a live campaign — they need to see what
@@ -2,25 +2,34 @@
2
2
 
3
3
  Every campaign writes its first message one of two ways. Decide which before
4
4
  `create_campaign`, pass it as `messageApproach`, and say which you picked in
5
- one plain line the customer can correct.
5
+ one plain line the customer can correct. Use these two names, and no other
6
+ names, for them.
6
7
 
7
- - **Conversations** (`messageApproach: "conversation"`). Each first message is
8
- written for that one person by Sellable's writer and opens a conversation.
9
- It carries no pitch and no ask; the offer comes up once they reply. Choose
10
- it when replies have an owner: reply drafting is on for the campaign (the
11
- default), so each reply arrives as a draft to approve, or the workspace's
12
- Reply Agent answers.
13
- - **Message Lab** (`messageApproach: "message_test"`). One approved message
14
- goes to everyone, so the customer can test which copy wins. This is the
15
- template flow described in the create-campaign-v2 prompt and flow file. Choose it when the
16
- customer wants to approve exact wording, test copy against copy, or has
17
- switched reply drafting off.
8
+ - **Conversations** (`messageApproach: "conversation"`). Each first message is written for that one person and starts a real conversation. The pitch waits until they reply.
9
+ Sellable writes each message from the person's profile; it carries no pitch
10
+ and no ask, and the offer comes up once they reply. Choose it when replies
11
+ have an owner: reply drafting is on for the campaign (the default), so each
12
+ reply arrives as a draft to approve, or the workspace's Reply Agent answers.
13
+ Best for fewer, higher-value prospects, where you want replies you can talk with.
14
+ - **Message Testing** (`messageApproach: "message_test"`). You approve one message, it goes to everyone, and you see which wording gets replies.
15
+ This is the template flow described in the create-campaign-v2 prompt and
16
+ flow file. Choose it when the customer wants to approve exact wording, test
17
+ copy against copy, or has switched reply drafting off. Best for a clear
18
+ offer and a large market, where you want to know which copy wins.
19
+
20
+ When you explain the choice, use the two one-line descriptions above as they
21
+ are. Short form: "Message Testing finds the right pitch. Conversations earn
22
+ the right to pitch."
18
23
 
19
24
  When the customer has not said, use Conversations if replies have an owner and
20
- Message Lab otherwise. "Same message to everyone", "test my copy" or "I want
21
- to approve the wording" means Message Lab. "Write each one", "personal",
22
- "conversational" or "use the reply agent" means Conversations. Change an
23
- existing campaign with `update_campaign({ campaignId, messageApproach })`.
25
+ Message Testing otherwise. "Same message to everyone", "test my copy" or "I
26
+ want to approve the wording" means Message Testing. "Write each one",
27
+ "personal", "conversational" or "use the reply agent" means Conversations.
28
+ Change an existing campaign with `update_campaign({ campaignId,
29
+ messageApproach })`; the customer can also change it in the app. Both
30
+ approaches draft new copy with the same writing skill, `draft-message`; the
31
+ approach decides whether one approved message goes to everyone or each person
32
+ gets their own.
24
33
 
25
34
  **A Conversations campaign has no template.** The backend writes each message
26
35
  only when the campaign carries no approved template, so:
@@ -108,8 +108,8 @@ return results
108
108
  `is_highest_value(...)` and Opus eligibility live in
109
109
  `references/thomas-variant-selection.md`. The synthesis step's
110
110
  finalizer pass belongs to this optional legacy/template critique protocol.
111
- It does not apply a second writing-strategy gate to new Conversation Chess
112
- recommendations; their selected Chess assets and one language check remain
111
+ It does not apply a second writing-strategy gate to new draft-message
112
+ recommendations; their selected draft-message assets and one language check remain
113
113
  authoritative.
114
114
 
115
115
  ## Critic Groups
@@ -54,7 +54,7 @@ rerun `import_leads`.
54
54
  only after saved-filter approval; `validate-sample` is the logical loop name,
55
55
  not a visible route)
56
56
  - Approved message template/token rules exist in the campaign brief before any
57
- enrichment or scoring cells are queued (Message Lab only; a Conversations
57
+ enrichment or scoring cells are queued (Message Testing only; a Conversations
58
58
  campaign has no template)
59
59
  - Confirmed campaign rows from Step 13, with the internal execution slice stored
60
60
  in `WorkflowTable.config.mcp.reviewBatch` (default 15)
@@ -12,7 +12,7 @@ This is the tail detail extracted from the main create-campaign-v2 SKILL.md to k
12
12
  `create-campaign-v2` `references/message-approach.md`) have no message
13
13
  template. Every rule below that requires an approved template, `{{...}}`
14
14
  tokens in the brief, Message Drafting, or template approval before queueing
15
- applies to Message Lab campaigns only. In a Conversations campaign the same
15
+ applies to Message Testing campaigns only. In a Conversations campaign the same
16
16
  cascade writes each message once a row passes the fit check.
17
17
 
18
18
  CampaignOffer state and the watch link are canonical. Disk artifacts are
@@ -409,7 +409,7 @@ the output — not to generate messages manually.
409
409
 
410
410
  1. Before queueing or waiting on Generate Message cells, confirm the minted
411
411
  campaign brief still contains `{{...}}` in `## Approved Message Template`
412
- (Message Lab only; a Conversations campaign must have no template).
412
+ (Message Testing only; a Conversations campaign must have no template).
413
413
  If it does not, fail before the cascade runs. Do not repair after mint; the
414
414
  template must be present during mint so Step 15 never starts in freeform
415
415
  generation mode.
@@ -216,12 +216,12 @@ This gate has two existing callers:
216
216
  call generate-messages in DRY MODE and write message-validation.md atomically.
217
217
  No campaignId, live context reads, fresh research or DB writes in DRY MODE.
218
218
 
219
- For NEW, UNAPPROVED recommendations, require the actual Conversation Chess
219
+ For NEW, UNAPPROVED recommendations, require the actual draft-message
220
220
  prompt, core input contract and selected mode/examples loaded through
221
221
  packaged MCP asset reads, with complete pagination. Preserve approved template
222
222
  bytes and explicit operator instructions; there is no writing-strategy flag.
223
223
  Do not layer gold-standard product-clarity, CTA/PS, required candidates,
224
- critic/finalizer or source→product→next-step recipes over Chess.
224
+ critic/finalizer or source→product→next-step recipes over draft-message.
225
225
 
226
226
  Validate these observable properties:
227
227
 
@@ -1,9 +1,9 @@
1
1
  ---
2
- name: conversation-chess
2
+ name: draft-message
3
3
  description: Draft or improve a prospect opener, reply or caller-eligible follow-up from the actual sender, offer and conversation, with relevant message examples.
4
4
  ---
5
5
 
6
- # Conversation Chess
6
+ # Draft Message
7
7
 
8
8
  Write a message a person can naturally answer. One skill, three sections; select a useful conversational move rather than assemble a fixed sequence.
9
9
 
@@ -1,4 +1,4 @@
1
- # Conversation Chess
1
+ # Draft Message
2
2
 
3
3
  One draft for the actual sender/situation. Rules/modes/examples loaded: select relevant ones; never fetch assets. Synthetic examples and histories: their facts, links and outcomes are not customer evidence. Creative rapport needs scoped permission.
4
4