@sellable/mcp 0.1.910 → 0.1.912

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.
@@ -35,7 +35,7 @@ function sanitizeForCampaign(text) {
35
35
  export const directCampaignToolDefinitions = [
36
36
  {
37
37
  name: "add_to_inmail_campaign",
38
- description: "Add leads to a sender's InMail direct campaign table (dedupes by default). Auto-creates the table if missing.",
38
+ description: "Add leads with caller-authored Subject and Message copy to a sender's direct InMail execution queue. This is the canonical one-off InMail path when recipients and exact copy are already known; it auto-creates the correctly typed table and dedupes by default. Do not create a normal campaign or a Generate Message column for this shape.",
39
39
  inputSchema: {
40
40
  type: "object",
41
41
  properties: {
@@ -63,7 +63,7 @@ export const directCampaignToolDefinitions = [
63
63
  },
64
64
  {
65
65
  name: "add_to_connection_campaign",
66
- description: "Add leads to a sender's Connection direct campaign table (dedupes by default). Auto-creates the table if missing.",
66
+ description: "Add leads with caller-authored Message copy to a sender's direct Connection execution queue. This is the canonical one-off connection-plus-DM path when recipients and exact copy are already known; it auto-creates the correctly typed table and dedupes by default.",
67
67
  inputSchema: {
68
68
  type: "object",
69
69
  properties: {
@@ -158,7 +158,7 @@ export const directCampaignToolDefinitions = [
158
158
  },
159
159
  {
160
160
  name: "start_direct_campaign",
161
- description: "Start a direct campaign for a sender (sets status to ACTIVE).",
161
+ description: "Start the sender's canonical direct campaign after its rows/copy are reviewed and the user explicitly approves launch (sets status to ACTIVE). Use the same type passed to the add tool. Do not use start_campaign for a table created by add_to_inmail_campaign or add_to_connection_campaign.",
162
162
  inputSchema: {
163
163
  type: "object",
164
164
  properties: {
@@ -200,7 +200,7 @@ export const directCampaignToolDefinitions = [
200
200
  },
201
201
  {
202
202
  name: "get_or_create_direct_campaign_table",
203
- description: "Get or create the direct campaign workflow table for a sender and type (comment_campaign, inmail_campaign, connection_campaign, post_campaign). Returns the tableId and whether it was created.",
203
+ description: "Inspect or explicitly create the canonical direct campaign workflow table for a sender and type (comment_campaign, inmail_campaign, connection_campaign, post_campaign). Usually call the matching add_to_* tool instead because it creates the table and rows together. Returns the tableId and whether it was created.",
204
204
  inputSchema: {
205
205
  type: "object",
206
206
  properties: {
@@ -73,7 +73,7 @@ export const onDemandToolDefinitions = [
73
73
  },
74
74
  {
75
75
  name: "create_on_demand_campaign",
76
- description: "Create a lightweight CampaignOffer + workflow table, set senders, and initialize sequence columns. Auto-selects template by sender tiers unless provided.\n\n" +
76
+ description: "Create a lightweight general CampaignOffer + workflow table, set senders, and initialize sequence columns. This is not the caller-authored direct InMail path: when exact recipients, Subject, and Message are already supplied, use add_to_inmail_campaign and start_direct_campaign instead. Auto-selects template by sender tiers unless provided.\n\n" +
77
77
  "INPUTS: Pass EITHER clientProspectId (existing prospect ID) OR senderLinkedinUrl (sender's LinkedIn profile URL). One is required.",
78
78
  inputSchema: {
79
79
  type: "object",
@@ -149,7 +149,7 @@ export const onDemandToolDefinitions = [
149
149
  },
150
150
  {
151
151
  name: "start_on_demand_campaign",
152
- description: "Start an on-demand campaign table (set campaignStatus ACTIVE).",
152
+ description: "Start a general on-demand campaign table (set campaignStatus ACTIVE). Do not use this for a typed direct InMail/Connection table; use start_direct_campaign with the matching type.",
153
153
  inputSchema: {
154
154
  type: "object",
155
155
  properties: {
@@ -270,7 +270,7 @@ export declare const REFILL_V3_EXHAUSTION_PREDICATE_CONSUMERS: readonly ["mcp/se
270
270
  * this constant and GENERATES its prompt's allowed-action list from it; wave 8
271
271
  * consumes it. Neither may declare a second list (error law 1).
272
272
  */
273
- export declare const REFILL_V3_CAMPAIGN_ATTENTION_ACTIONS: readonly ["rerun_errored_cells", "repair_campaign_config", "enrich_bounded_sample", "regenerate_stale_messages", "patch_filter_criteria", "patch_source_criteria", "broaden_source_family", "request_rubric_trial", "await_active_job", "record_source_frontier_exhausted"];
273
+ export declare const REFILL_V3_CAMPAIGN_ATTENTION_ACTIONS: readonly ["copy_selected_source_rows", "rerun_errored_cells", "repair_campaign_config", "enrich_bounded_sample", "regenerate_stale_messages", "patch_filter_criteria", "patch_source_criteria", "broaden_source_family", "request_rubric_trial", "await_active_job", "record_source_frontier_exhausted"];
274
274
  export type RefillV3CampaignAttentionAction = (typeof REFILL_V3_CAMPAIGN_ATTENTION_ACTIONS)[number];
275
275
  /**
276
276
  * The genuinely judgment-bearing subset exposed to the model.
@@ -288,7 +288,7 @@ export type RefillV3ModelCampaignAttentionAction = (typeof REFILL_V3_MODEL_CAMPA
288
288
  * the AI records exhaustion through `record_source_frontier_exhausted`, and the
289
289
  * deterministic authority answers with the result.
290
290
  */
291
- export declare const REFILL_V3_CAMPAIGN_ATTENTION_RESULTS: readonly ["cells_rerun", "campaign_config_repaired", "bounded_sample_enriched", "stale_messages_regenerated", "filter_criteria_patched", "source_criteria_patched", "source_family_broadened", "rubric_trial_requested", "active_job_awaited", "source_frontier_exhausted", "health_recheck_required", "no_action_available"];
291
+ export declare const REFILL_V3_CAMPAIGN_ATTENTION_RESULTS: readonly ["selected_source_rows_copied", "cells_rerun", "campaign_config_repaired", "bounded_sample_enriched", "stale_messages_regenerated", "filter_criteria_patched", "source_criteria_patched", "source_family_broadened", "rubric_trial_requested", "active_job_awaited", "source_frontier_exhausted", "health_recheck_required", "no_action_available"];
292
292
  export type RefillV3CampaignAttentionResult = (typeof REFILL_V3_CAMPAIGN_ATTENTION_RESULTS)[number];
293
293
  /**
294
294
  * Prompt assets that must GENERATE their allowed outputs from the constants
@@ -359,6 +359,8 @@ export type RefillV3CampaignAttentionEvidence = {
359
359
  provider: string | null;
360
360
  /** Exact selected source table, when the campaign has one. */
361
361
  selectedLeadListId?: string | null;
362
+ /** Exact bounded source rows proven absent from the campaign table. */
363
+ netNewSourceRowIds?: string[];
362
364
  /**
363
365
  * Ownership-proven reusable saved-search authority for cold providers.
364
366
  * Loaded server-side through the existing target-plan resolver; callers and
@@ -513,6 +515,14 @@ export declare const REFILL_V3_CAMPAIGN_ATTENTION_PACKET_SCHEMA: Readonly<{
513
515
  readonly selectedLeadListId: {
514
516
  readonly type: readonly ["string", "null"];
515
517
  };
518
+ readonly netNewSourceRowIds: {
519
+ readonly type: "array";
520
+ readonly items: {
521
+ readonly type: "string";
522
+ readonly minLength: 1;
523
+ };
524
+ readonly uniqueItems: true;
525
+ };
516
526
  readonly providerSearchReference: {
517
527
  readonly type: readonly ["object", "null"];
518
528
  readonly additionalProperties: false;
@@ -590,7 +600,7 @@ export declare const REFILL_V3_CAMPAIGN_ATTENTION_PACKET_SCHEMA: Readonly<{
590
600
  readonly uniqueItems: true;
591
601
  readonly items: {
592
602
  readonly type: "string";
593
- readonly enum: readonly ["rerun_errored_cells", "repair_campaign_config", "enrich_bounded_sample", "regenerate_stale_messages", "patch_filter_criteria", "patch_source_criteria", "broaden_source_family", "request_rubric_trial", "await_active_job", "record_source_frontier_exhausted"];
603
+ readonly enum: readonly ["copy_selected_source_rows", "rerun_errored_cells", "repair_campaign_config", "enrich_bounded_sample", "regenerate_stale_messages", "patch_filter_criteria", "patch_source_criteria", "broaden_source_family", "request_rubric_trial", "await_active_job", "record_source_frontier_exhausted"];
594
604
  };
595
605
  };
596
606
  readonly sourceSearchReceipts: {
@@ -344,6 +344,7 @@ export const REFILL_V3_EXHAUSTION_PREDICATE_CONSUMERS = [
344
344
  * consumes it. Neither may declare a second list (error law 1).
345
345
  */
346
346
  export const REFILL_V3_CAMPAIGN_ATTENTION_ACTIONS = [
347
+ "copy_selected_source_rows",
347
348
  "rerun_errored_cells",
348
349
  "repair_campaign_config",
349
350
  "enrich_bounded_sample",
@@ -379,6 +380,7 @@ export const REFILL_V3_MODEL_CAMPAIGN_ATTENTION_ACTIONS = [
379
380
  * deterministic authority answers with the result.
380
381
  */
381
382
  export const REFILL_V3_CAMPAIGN_ATTENTION_RESULTS = [
383
+ "selected_source_rows_copied",
382
384
  "cells_rerun",
383
385
  "campaign_config_repaired",
384
386
  "bounded_sample_enriched",
@@ -589,6 +591,11 @@ export const REFILL_V3_CAMPAIGN_ATTENTION_PACKET_SCHEMA = Object.freeze({
589
591
  },
590
592
  provider: { type: ["string", "null"] },
591
593
  selectedLeadListId: { type: ["string", "null"] },
594
+ netNewSourceRowIds: {
595
+ type: "array",
596
+ items: { type: "string", minLength: 1 },
597
+ uniqueItems: true,
598
+ },
592
599
  providerSearchReference: {
593
600
  type: ["object", "null"],
594
601
  additionalProperties: false,
@@ -1114,6 +1121,12 @@ function decodeEvidence(value) {
1114
1121
  !isNonEmptyString(selectedLeadListId)) {
1115
1122
  return null;
1116
1123
  }
1124
+ const netNewSourceRowIds = "netNewSourceRowIds" in value ? value.netNewSourceRowIds : undefined;
1125
+ if (netNewSourceRowIds !== undefined &&
1126
+ (!isStringArray(netNewSourceRowIds) ||
1127
+ new Set(netNewSourceRowIds).size !== netNewSourceRowIds.length)) {
1128
+ return null;
1129
+ }
1117
1130
  const providerSearchReference = "providerSearchReference" in value
1118
1131
  ? decodeProviderSearchReference(value.providerSearchReference)
1119
1132
  : undefined;
@@ -1142,6 +1155,9 @@ function decodeEvidence(value) {
1142
1155
  leadSourceFamily: value.leadSourceFamily,
1143
1156
  provider: value.provider,
1144
1157
  ...(selectedLeadListId === undefined ? {} : { selectedLeadListId }),
1158
+ ...(netNewSourceRowIds === undefined
1159
+ ? {}
1160
+ : { netNewSourceRowIds: [...netNewSourceRowIds] }),
1145
1161
  ...(providerSearchReference === undefined
1146
1162
  ? {}
1147
1163
  : { providerSearchReference: providerSearchReference.value }),
@@ -1235,6 +1251,11 @@ export function encodeRefillV3CampaignAttentionPacket(packet) {
1235
1251
  ...(packet.evidence.selectedLeadListId === undefined
1236
1252
  ? {}
1237
1253
  : { selectedLeadListId: packet.evidence.selectedLeadListId }),
1254
+ ...(packet.evidence.netNewSourceRowIds === undefined
1255
+ ? {}
1256
+ : {
1257
+ netNewSourceRowIds: [...packet.evidence.netNewSourceRowIds],
1258
+ }),
1238
1259
  ...(packet.evidence.providerSearchReference === undefined
1239
1260
  ? {}
1240
1261
  : {
@@ -27,9 +27,10 @@
27
27
  * through the source-cycle route; config edits persist through the
28
28
  * edit-selected-campaign route.
29
29
  */
30
- export declare const REFILL_V3_CAMPAIGN_WORK_RECEIPT_ACTIONS: readonly ["enrich_bounded_sample", "await_active_job", "rerun_errored_cells", "regenerate_stale_messages", "broaden_source_family", "patch_filter_criteria", "patch_source_criteria"];
30
+ export declare const REFILL_V3_CAMPAIGN_WORK_RECEIPT_ACTIONS: readonly ["enrich_bounded_sample", "await_active_job", "rerun_errored_cells", "regenerate_stale_messages", "broaden_source_family", "patch_filter_criteria", "patch_source_criteria", "copy_selected_source_rows"];
31
31
  export type RefillV3CampaignWorkReceiptAction = (typeof REFILL_V3_CAMPAIGN_WORK_RECEIPT_ACTIONS)[number];
32
32
  export declare const REFILL_V3_CAMPAIGN_WORK_ACTION_KINDS: Readonly<{
33
+ readonly copy_selected_source_rows: "campaign_work_copy_selected_source_rows";
33
34
  readonly enrich_bounded_sample: "campaign_work_enrich_bounded_sample";
34
35
  readonly await_active_job: "campaign_work_await_active_job";
35
36
  readonly rerun_errored_cells: "campaign_work_rerun_errored_cells";
@@ -35,8 +35,10 @@ export const REFILL_V3_CAMPAIGN_WORK_RECEIPT_ACTIONS = [
35
35
  "broaden_source_family",
36
36
  "patch_filter_criteria",
37
37
  "patch_source_criteria",
38
+ "copy_selected_source_rows",
38
39
  ];
39
40
  export const REFILL_V3_CAMPAIGN_WORK_ACTION_KINDS = Object.freeze({
41
+ copy_selected_source_rows: "campaign_work_copy_selected_source_rows",
40
42
  enrich_bounded_sample: "campaign_work_enrich_bounded_sample",
41
43
  await_active_job: "campaign_work_await_active_job",
42
44
  rerun_errored_cells: "campaign_work_rerun_errored_cells",
@@ -68,6 +68,7 @@ export declare const REFILL_V3_FORBIDDEN_COMMAND_KEYS: readonly ["batchSize", "c
68
68
  export declare const REFILL_V3_PREPARATION_REUSE_POINTS: readonly ["frontier", "remainingGapPassEvidence", "serialBatchCap", "totalRowBudget", "replayKey", "lowPassGuard", "reclaimBehavior"];
69
69
  export type RefillV3PreparationReusePoint = (typeof REFILL_V3_PREPARATION_REUSE_POINTS)[number];
70
70
  declare const ACTION: {
71
+ copySelectedSourceRows: "copy_selected_source_rows";
71
72
  rerunErroredCells: "rerun_errored_cells";
72
73
  repairCampaignConfig: "repair_campaign_config";
73
74
  enrichBoundedSample: "enrich_bounded_sample";
@@ -82,7 +83,7 @@ declare const ACTION: {
82
83
  /** Actions that may only be chosen once the bounded sample is ENRICHED. */
83
84
  export declare const REFILL_V3_ENRICHMENT_GATED_ACTIONS: readonly ["request_rubric_trial", "broaden_source_family", "record_source_frontier_exhausted"];
84
85
  /** Actions that OUTRANK rubric relaxation and source broadening. */
85
- export declare const REFILL_V3_REPAIR_FIRST_ACTIONS: readonly ["rerun_errored_cells", "repair_campaign_config", "enrich_bounded_sample", "regenerate_stale_messages", "patch_filter_criteria", "patch_source_criteria"];
86
+ export declare const REFILL_V3_REPAIR_FIRST_ACTIONS: readonly ["copy_selected_source_rows", "rerun_errored_cells", "repair_campaign_config", "enrich_bounded_sample", "regenerate_stale_messages", "patch_filter_criteria", "patch_source_criteria"];
86
87
  /** Row-scoped actions whose exact cohort is owned by the diagnostic authority. */
87
88
  export declare const REFILL_V3_ROW_SCOPED_ACTIONS: readonly ["rerun_errored_cells", "enrich_bounded_sample", "regenerate_stale_messages"];
88
89
  /** Actions that carry a bounded source command. */
@@ -140,6 +140,7 @@ export const REFILL_V3_ENRICHMENT_GATED_ACTIONS = [
140
140
  ];
141
141
  /** Actions that OUTRANK rubric relaxation and source broadening. */
142
142
  export const REFILL_V3_REPAIR_FIRST_ACTIONS = [
143
+ ACTION.copySelectedSourceRows,
143
144
  ACTION.rerunErroredCells,
144
145
  ACTION.repairCampaignConfig,
145
146
  ACTION.enrichBoundedSample,
@@ -160,6 +161,7 @@ export const REFILL_V3_SOURCE_ACTIONS = [
160
161
  ];
161
162
  /** One deterministic result per chosen action. */
162
163
  export const REFILL_V3_ACTION_RESULTS = Object.freeze({
164
+ [ACTION.copySelectedSourceRows]: RESULT.selectedSourceRowsCopied,
163
165
  [ACTION.rerunErroredCells]: RESULT.cellsRerun,
164
166
  [ACTION.repairCampaignConfig]: RESULT.campaignConfigRepaired,
165
167
  [ACTION.enrichBoundedSample]: RESULT.boundedSampleEnriched,
@@ -1,6 +1,7 @@
1
1
  import { handleAddColumn } from "./blueprint-commit.js";
2
2
  import { getCampaignTableSchema } from "./campaign-processing.js";
3
3
  import { handleUpdateColumn } from "./column-update.js";
4
+ import { executeOneYoloPrimitive } from "./refill-executors.js";
4
5
  import type { RefillV3AdvanceResult } from "./refill-v3-advance-contract.js";
5
6
  import { type RefillV3AdvanceToolInput } from "./refill-v3-advance.js";
6
7
  import { type RefillV3CampaignAttentionPacket } from "./refill-v3-attention-packet-contract.js";
@@ -509,6 +510,7 @@ type RefillV3ContinueProductionOwners = {
509
510
  startPrepareCampaignMessages: typeof startPrepareCampaignMessages;
510
511
  getProviderPrompt: typeof getProviderPrompt;
511
512
  executeRerunErroredCellsPrimitive: typeof executeRerunErroredCellsPrimitive;
513
+ executeOneYoloPrimitive: typeof executeOneYoloPrimitive;
512
514
  refillV3SourceFamilyOwners: typeof refillV3SourceFamilyOwners;
513
515
  dispatchRefillV3SourceFamily: typeof dispatchRefillV3SourceFamily;
514
516
  broadenRubrics: typeof broadenRubrics;
@@ -3,6 +3,7 @@ import { SellableApiError, getApi } from "../api.js";
3
3
  import { handleAddColumn } from "./blueprint-commit.js";
4
4
  import { getCampaignTableSchema, waitForCampaignProcessing, } from "./campaign-processing.js";
5
5
  import { handleUpdateColumn } from "./column-update.js";
6
+ import { executeOneYoloPrimitive } from "./refill-executors.js";
6
7
  import { advanceRefillV3Tool, } from "./refill-v3-advance.js";
7
8
  import { REFILL_V3_EXHAUSTION_PREDICATE, REFILL_V3_POSTS_SEARCH_SPENT_AVENUE, } from "./refill-v3-attention-packet-contract.js";
8
9
  import { isRefillV3CampaignWorkReceiptAction, } from "./refill-v3-campaign-work-receipt-contract.js";
@@ -17,7 +18,7 @@ export const REFILL_V3_CONTINUE_TOOL_NAME = "refill_v3_continue";
17
18
  export const refillV3ContinueToolDefinitions = [
18
19
  {
19
20
  name: REFILL_V3_CONTINUE_TOOL_NAME,
20
- description: "Continue exactly one server-issued Refill V3 campaign-attention packet. Call first without decision: after a fresh revalidation, the tool deterministically owns live-job waiting, 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. 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
22
  inputSchema: REFILL_V3_CONTINUE_INPUT_SCHEMA,
22
23
  outputSchema: REFILL_V3_CONTINUE_OUTPUT_SCHEMA,
23
24
  annotations: {
@@ -171,6 +172,7 @@ const DEFAULT_PRODUCTION_OWNERS = {
171
172
  startPrepareCampaignMessages,
172
173
  getProviderPrompt,
173
174
  executeRerunErroredCellsPrimitive,
175
+ executeOneYoloPrimitive,
174
176
  refillV3SourceFamilyOwners,
175
177
  dispatchRefillV3SourceFamily,
176
178
  broadenRubrics,
@@ -370,6 +372,64 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
370
372
  },
371
373
  async dispatchAuthority(request) {
372
374
  const result = REFILL_V3_ACTION_RESULTS[request.action];
375
+ if (request.action === "copy_selected_source_rows") {
376
+ const sourceLeadListId = input.packet.evidence.selectedLeadListId;
377
+ const sourceFingerprint = input.packet.evidence.sourceLineage.at(-1) ?? null;
378
+ const sourceRowIds = [
379
+ ...new Set(input.packet.evidence.netNewSourceRowIds ?? []),
380
+ ];
381
+ if (!sourceLeadListId ||
382
+ !sourceFingerprint ||
383
+ sourceRowIds.length === 0) {
384
+ return receipt({
385
+ outcome: "refused",
386
+ result,
387
+ idempotencyKey: request.idempotencyKey,
388
+ refusalReason: "selected_source_copy_requires_exact_list_fingerprint_and_rows",
389
+ });
390
+ }
391
+ const attempt = await owners.executeOneYoloPrimitive({
392
+ type: "copy_selected_source_rows",
393
+ yoloEligible: true,
394
+ leadSourceProvider: input.packet.evidence.provider,
395
+ ids: {
396
+ campaignId: request.scope.campaignId,
397
+ sourceLeadListId,
398
+ sourceFingerprint,
399
+ },
400
+ toolInput: {
401
+ campaignOfferId: request.scope.campaignId,
402
+ sourceLeadListId,
403
+ sourceFingerprint,
404
+ leadSourceProvider: input.packet.evidence.provider,
405
+ sourceRowIds,
406
+ sourceRowLimit: sourceRowIds.length,
407
+ },
408
+ }, request.scope.workspaceId);
409
+ if (attempt.status === "executed_and_reread") {
410
+ return receipt({
411
+ outcome: "applied",
412
+ result,
413
+ idempotencyKey: request.idempotencyKey,
414
+ changedFields: ["campaignSourceRows"],
415
+ after: attempt.result,
416
+ });
417
+ }
418
+ if (attempt.status === "read_only_reread") {
419
+ return receipt({
420
+ outcome: "no_effect",
421
+ result,
422
+ idempotencyKey: request.idempotencyKey,
423
+ after: attempt.result,
424
+ });
425
+ }
426
+ return receipt({
427
+ outcome: "refused",
428
+ result,
429
+ idempotencyKey: request.idempotencyKey,
430
+ refusalReason: attempt.refusalReason ?? "selected_source_copy_not_executed",
431
+ });
432
+ }
373
433
  if (request.action === "enrich_bounded_sample") {
374
434
  // V3.1 · V31-BATCH-001: the deterministic batch ladder. An exact
375
435
  // diagnostic frontier is the first calibration cohort. After that
@@ -1223,9 +1283,16 @@ function deterministicCampaignDecision(packet) {
1223
1283
  const cellRepairIsUnspent = !settledNoProgressActions.has("rerun_errored_cells");
1224
1284
  const needsModelRepair = evidence.configurationDefect != null ||
1225
1285
  (cellRepairIsUnspent && evidence.failedJobRef !== null);
1226
- if (needsModelRepair)
1286
+ if (!settledNoProgressActions.has("copy_selected_source_rows") &&
1287
+ evidence.selectedLeadListId &&
1288
+ evidence.sourceLineage.length > 0 &&
1289
+ (evidence.netNewSourceRowIds?.length ?? 0) > 0) {
1290
+ action = "copy_selected_source_rows";
1291
+ }
1292
+ else if (needsModelRepair) {
1227
1293
  return null;
1228
- if (!settledNoProgressActions.has("enrich_bounded_sample") &&
1294
+ }
1295
+ else if (!settledNoProgressActions.has("enrich_bounded_sample") &&
1229
1296
  evidence.diagnosticRowRef?.selectorRef ===
1230
1297
  "needsEnrichment:actionable:v1" &&
1231
1298
  evidence.diagnosticRowRef.count > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.910",
3
+ "version": "0.1.912",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",
@@ -77,6 +77,10 @@ allowed-tools:
77
77
  - mcp__sellable__attach_sequence
78
78
  - mcp__sellable__attach_recommended_sequence
79
79
  - mcp__sellable__start_campaign
80
+ - mcp__sellable__add_to_inmail_campaign
81
+ - mcp__sellable__add_to_connection_campaign
82
+ - mcp__sellable__get_or_create_direct_campaign_table
83
+ - mcp__sellable__start_direct_campaign
80
84
  ---
81
85
 
82
86
  # Sellable Create Campaign
@@ -86,6 +90,30 @@ It bootstraps auth and host capabilities, then loads the internal
86
90
  the internal campaign workflow prompt and `core/flow.v2.json` through MCP. Keep
87
91
  this wrapper thin; the packaged workflow is the operational source of truth.
88
92
 
93
+ ## Direct Manual-Copy Campaign Routing
94
+
95
+ Route a bounded one-off request to the direct campaign tools when the user
96
+ already supplies the recipients and exact message copy. For InMail, require a
97
+ LinkedIn URL, Subject, and Message per row, select the intended sender, then use
98
+ `add_to_inmail_campaign`. For a connection-plus-DM request, use
99
+ `add_to_connection_campaign`. These tools create or reuse the canonical typed
100
+ execution-queue table; do not call `create_campaign`,
101
+ `create_on_demand_campaign`, or a connection helper first.
102
+
103
+ Do not create a normal Campaign Builder Generate Message column, rewrite the
104
+ approved manual copy, or repair the typed direct table merely because its
105
+ message is stored in a plain `Message` text column. That is the owned schema for
106
+ `inmail_campaign` and `connection_campaign` tables.
107
+
108
+ Show the bounded recipients and exact copy for review before adding rows. Never
109
+ call `start_direct_campaign` until the user explicitly approves launch. When
110
+ approved, start the same direct type used by the add tool; do not call
111
+ `start_campaign` for that table.
112
+
113
+ Billing diagnostics are not launch authority. Honor a billing error returned by
114
+ the actual add/start mutation, but never interpret `billing_required`
115
+ diagnostics as an enforced block when the billing rollout gate is disabled.
116
+
89
117
  ## Inbox Reply Tool Boundary
90
118
 
91
119
  Inbox reply tools are outside campaign creation. If a user asks to search,
@@ -291,6 +319,7 @@ only from a source that can describe this turn:
291
319
  context. Do not infer an ID from the friendly name, do not infer effort from
292
320
  `alwaysThinkingEnabled`, and do not show this self-report to the user during
293
321
  normal campaign setup.
322
+
294
323
  - Do not run a nested `claude -p`, inspect `~/.claude/settings.json`, or read
295
324
  Claude CLI defaults as proof of the user's active Claude Code session. Those
296
325
  checks can validate a new child session or saved defaults, but not this