@sellable/mcp 0.1.746 → 0.1.748

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,6 @@
1
+ /**
2
+ * Lightweight projection of the canonical MCP registry for control-plane
3
+ * manifests. Keep this free of tool implementation imports so Next can bundle
4
+ * it; registry parity is enforced in sellable-agent-tool-policy.test.ts.
5
+ */
6
+ export declare const SELLABLE_MCP_TOOL_NAMES: readonly ["add_column", "add_on_demand_leads", "add_rubric_item", "add_teammate", "add_to_comment_campaign", "add_to_connection_campaign", "add_to_inmail_campaign", "archive_campaign", "attach_recommended_sequence", "attach_sequence", "bootstrap_create_campaign", "bootstrap_engage", "bootstrap_engage_multi", "bootstrap_find_leads", "bulk_enrich_with_prospeo", "calculate_linkedin_hook_preview", "cancel_campaign_message_preparation", "cancel_find_leads", "cancel_lead_import", "capture_post_idea", "check_inbox_reply_eligibility", "check_rubric", "commit_blueprint", "complete_sender_research", "confirm_harvest_job_companies", "confirm_lead_list", "confirm_prospeo_company_accounts", "copy_sender_config", "create_campaign", "create_on_demand_campaign", "create_on_demand_table", "create_workflow_table", "create_workspace", "delete_column", "delete_rubric_item", "draft_rubrics", "duplicate_campaign", "enrich_with_prospeo", "export_table_csv", "export_workspace_csv", "fetch_company", "fetch_company_posts", "fetch_linkedin_posts", "fetch_linkedin_profile", "fetch_post_engagers", "fill_campaign_horizon", "get_active_workspace", "get_auth_status", "get_campaign", "get_campaign_context", "get_campaign_framework", "get_campaign_message_preparation_status", "get_campaign_messages_preview", "get_campaign_navigation_state", "get_campaign_refill_state", "get_campaign_table_schema", "get_campaign_waterfall", "get_campaigns", "get_column_schema", "get_company_info", "get_engage_memory", "get_engage_state", "get_engaged_posts", "get_find_leads_run", "get_inbox_thread", "get_linkedin_profile", "get_or_create_direct_campaign_table", "get_post_draft", "get_post_find_leads_scout_registry", "get_post_idea", "get_prospeo_credits", "get_provider_prompt", "get_published_post", "get_refill_plan_v2", "get_refill_target_plan", "get_rows", "get_rows_minimal", "get_runtime_identity", "get_scheduler_fill_capacity", "get_sender", "get_sender_routing", "get_source_scout_registry", "get_subskill_asset", "get_subskill_prompt", "get_table_rows", "get_user_posts", "get_workspace", "import_leads", "init_on_demand_sequence", "list_column_types", "list_dnc_entries", "list_post_draft_iterations", "list_post_drafts", "list_post_ideas", "list_published_posts", "list_senders", "list_subskill_prompts", "list_tables", "list_workspaces", "load_csv_dnc_entries", "load_csv_domains", "load_csv_linkedin_leads", "lookup_sales_nav_filter", "mark_post_published", "migrate_flat_configs", "pause_campaign", "pause_direct_campaign", "pause_on_demand_campaign", "preflight_find_leads_provider", "prepare_campaign_ab_test", "queue_campaign_cells", "queue_cells", "record_campaign_review_batch", "record_engage_proven_search", "refill_sends", "refill_sends_v2", "refresh_paid_inmail_credits", "refresh_sender_engagement", "reissue_find_leads_watch_link", "render_linkedin_post_preview", "reorder_columns", "resolve_campaign_fill_route", "revise_message_template_and_rerun", "run_scheduler_sweep", "save_domain_filters", "save_hook_research", "save_post_draft", "save_rubrics", "search_apollo", "search_engagement_posts", "search_harvest_jobs", "search_inbox_threads", "search_prospeo", "search_prospeo_companies", "search_sales_nav", "search_signals", "search_subskill_prompts", "select_campaign_cells", "select_necessary_rubrics", "select_promising_posts", "send_inbox_draft", "send_inbox_manual_reply", "set_active_workspace", "set_campaign_waterfall_order", "set_company_info", "set_engage_state", "set_engage_style_guide", "set_headline_icp_criteria", "set_sender_calendar_link", "set_sender_routing", "setup_evergreen_campaigns", "start_campaign", "start_campaign_message_preparation", "start_cli_login", "start_direct_campaign", "start_on_demand_campaign", "update_campaign", "update_campaign_brief", "update_cell", "update_column", "update_find_leads_run", "update_inbox_draft", "update_post_draft", "update_published_post_metrics", "update_rubric_item", "upsert_engage_tracked_person", "upsert_rubric", "validate_campaign1_kickoff_handoff", "verify_table_row", "wait_for_campaign_processing", "wait_for_campaign_table_ready", "wait_for_cli_login", "wait_for_find_leads_run", "wait_for_lead_list_ready", "wait_for_rubric_results"];
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Lightweight projection of the canonical MCP registry for control-plane
3
+ * manifests. Keep this free of tool implementation imports so Next can bundle
4
+ * it; registry parity is enforced in sellable-agent-tool-policy.test.ts.
5
+ */
6
+ export const SELLABLE_MCP_TOOL_NAMES = [
7
+ "add_column",
8
+ "add_on_demand_leads",
9
+ "add_rubric_item",
10
+ "add_teammate",
11
+ "add_to_comment_campaign",
12
+ "add_to_connection_campaign",
13
+ "add_to_inmail_campaign",
14
+ "archive_campaign",
15
+ "attach_recommended_sequence",
16
+ "attach_sequence",
17
+ "bootstrap_create_campaign",
18
+ "bootstrap_engage",
19
+ "bootstrap_engage_multi",
20
+ "bootstrap_find_leads",
21
+ "bulk_enrich_with_prospeo",
22
+ "calculate_linkedin_hook_preview",
23
+ "cancel_campaign_message_preparation",
24
+ "cancel_find_leads",
25
+ "cancel_lead_import",
26
+ "capture_post_idea",
27
+ "check_inbox_reply_eligibility",
28
+ "check_rubric",
29
+ "commit_blueprint",
30
+ "complete_sender_research",
31
+ "confirm_harvest_job_companies",
32
+ "confirm_lead_list",
33
+ "confirm_prospeo_company_accounts",
34
+ "copy_sender_config",
35
+ "create_campaign",
36
+ "create_on_demand_campaign",
37
+ "create_on_demand_table",
38
+ "create_workflow_table",
39
+ "create_workspace",
40
+ "delete_column",
41
+ "delete_rubric_item",
42
+ "draft_rubrics",
43
+ "duplicate_campaign",
44
+ "enrich_with_prospeo",
45
+ "export_table_csv",
46
+ "export_workspace_csv",
47
+ "fetch_company",
48
+ "fetch_company_posts",
49
+ "fetch_linkedin_posts",
50
+ "fetch_linkedin_profile",
51
+ "fetch_post_engagers",
52
+ "fill_campaign_horizon",
53
+ "get_active_workspace",
54
+ "get_auth_status",
55
+ "get_campaign",
56
+ "get_campaign_context",
57
+ "get_campaign_framework",
58
+ "get_campaign_message_preparation_status",
59
+ "get_campaign_messages_preview",
60
+ "get_campaign_navigation_state",
61
+ "get_campaign_refill_state",
62
+ "get_campaign_table_schema",
63
+ "get_campaign_waterfall",
64
+ "get_campaigns",
65
+ "get_column_schema",
66
+ "get_company_info",
67
+ "get_engage_memory",
68
+ "get_engage_state",
69
+ "get_engaged_posts",
70
+ "get_find_leads_run",
71
+ "get_inbox_thread",
72
+ "get_linkedin_profile",
73
+ "get_or_create_direct_campaign_table",
74
+ "get_post_draft",
75
+ "get_post_find_leads_scout_registry",
76
+ "get_post_idea",
77
+ "get_prospeo_credits",
78
+ "get_provider_prompt",
79
+ "get_published_post",
80
+ "get_refill_plan_v2",
81
+ "get_refill_target_plan",
82
+ "get_rows",
83
+ "get_rows_minimal",
84
+ "get_runtime_identity",
85
+ "get_scheduler_fill_capacity",
86
+ "get_sender",
87
+ "get_sender_routing",
88
+ "get_source_scout_registry",
89
+ "get_subskill_asset",
90
+ "get_subskill_prompt",
91
+ "get_table_rows",
92
+ "get_user_posts",
93
+ "get_workspace",
94
+ "import_leads",
95
+ "init_on_demand_sequence",
96
+ "list_column_types",
97
+ "list_dnc_entries",
98
+ "list_post_draft_iterations",
99
+ "list_post_drafts",
100
+ "list_post_ideas",
101
+ "list_published_posts",
102
+ "list_senders",
103
+ "list_subskill_prompts",
104
+ "list_tables",
105
+ "list_workspaces",
106
+ "load_csv_dnc_entries",
107
+ "load_csv_domains",
108
+ "load_csv_linkedin_leads",
109
+ "lookup_sales_nav_filter",
110
+ "mark_post_published",
111
+ "migrate_flat_configs",
112
+ "pause_campaign",
113
+ "pause_direct_campaign",
114
+ "pause_on_demand_campaign",
115
+ "preflight_find_leads_provider",
116
+ "prepare_campaign_ab_test",
117
+ "queue_campaign_cells",
118
+ "queue_cells",
119
+ "record_campaign_review_batch",
120
+ "record_engage_proven_search",
121
+ "refill_sends",
122
+ "refill_sends_v2",
123
+ "refresh_paid_inmail_credits",
124
+ "refresh_sender_engagement",
125
+ "reissue_find_leads_watch_link",
126
+ "render_linkedin_post_preview",
127
+ "reorder_columns",
128
+ "resolve_campaign_fill_route",
129
+ "revise_message_template_and_rerun",
130
+ "run_scheduler_sweep",
131
+ "save_domain_filters",
132
+ "save_hook_research",
133
+ "save_post_draft",
134
+ "save_rubrics",
135
+ "search_apollo",
136
+ "search_engagement_posts",
137
+ "search_harvest_jobs",
138
+ "search_inbox_threads",
139
+ "search_prospeo",
140
+ "search_prospeo_companies",
141
+ "search_sales_nav",
142
+ "search_signals",
143
+ "search_subskill_prompts",
144
+ "select_campaign_cells",
145
+ "select_necessary_rubrics",
146
+ "select_promising_posts",
147
+ "send_inbox_draft",
148
+ "send_inbox_manual_reply",
149
+ "set_active_workspace",
150
+ "set_campaign_waterfall_order",
151
+ "set_company_info",
152
+ "set_engage_state",
153
+ "set_engage_style_guide",
154
+ "set_headline_icp_criteria",
155
+ "set_sender_calendar_link",
156
+ "set_sender_routing",
157
+ "setup_evergreen_campaigns",
158
+ "start_campaign",
159
+ "start_campaign_message_preparation",
160
+ "start_cli_login",
161
+ "start_direct_campaign",
162
+ "start_on_demand_campaign",
163
+ "update_campaign",
164
+ "update_campaign_brief",
165
+ "update_cell",
166
+ "update_column",
167
+ "update_find_leads_run",
168
+ "update_inbox_draft",
169
+ "update_post_draft",
170
+ "update_published_post_metrics",
171
+ "update_rubric_item",
172
+ "upsert_engage_tracked_person",
173
+ "upsert_rubric",
174
+ "validate_campaign1_kickoff_handoff",
175
+ "verify_table_row",
176
+ "wait_for_campaign_processing",
177
+ "wait_for_campaign_table_ready",
178
+ "wait_for_cli_login",
179
+ "wait_for_find_leads_run",
180
+ "wait_for_lead_list_ready",
181
+ "wait_for_rubric_results",
182
+ ];
@@ -47,10 +47,28 @@ export function decideAgentTool(compiled, tool) {
47
47
  };
48
48
  }
49
49
  export function listToolsForAgent(compiled, definitions) {
50
- const allowed = new Set(compiled.entries
51
- .filter((entry) => entry.decision !== "DENY")
50
+ const denied = new Set(compiled.entries
51
+ .filter((entry) => entry.decision === "DENY")
52
52
  .map((entry) => entry.tool));
53
- return definitions.filter((definition) => allowed.has(definition.name));
53
+ return definitions.filter((definition) => !denied.has(definition.name));
54
+ }
55
+ function decideRegisteredAgentTool(compiled, definitions, tool) {
56
+ const explicit = decideAgentTool(compiled, tool);
57
+ if (explicit.version !== "unknown")
58
+ return explicit;
59
+ if (!definitions.some((definition) => definition.name === tool)) {
60
+ return explicit;
61
+ }
62
+ return {
63
+ tool,
64
+ version: "registry-v1",
65
+ decision: "AUTO",
66
+ method: "POST",
67
+ route: `mcp://sellable/${tool}`,
68
+ handler: tool,
69
+ workspaceBinding: "workspaceId",
70
+ allowed: true,
71
+ };
54
72
  }
55
73
  function canonicalize(value) {
56
74
  if (Array.isArray(value))
@@ -63,18 +81,16 @@ function canonicalize(value) {
63
81
  return value;
64
82
  }
65
83
  const SHA256_CONSTANTS = [
66
- 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b,
67
- 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01,
68
- 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7,
69
- 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
70
- 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152,
71
- 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
72
- 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
73
- 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
74
- 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
75
- 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08,
76
- 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f,
77
- 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
84
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
85
+ 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
86
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
87
+ 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
88
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
89
+ 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
90
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
91
+ 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
92
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
93
+ 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
78
94
  0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
79
95
  ];
80
96
  function rotateRight(value, bits) {
@@ -91,8 +107,8 @@ export function portableSha256(value) {
91
107
  view.setUint32(paddedLength - 8, Math.floor(bitLength / 0x100000000));
92
108
  view.setUint32(paddedLength - 4, bitLength >>> 0);
93
109
  const hash = [
94
- 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f,
95
- 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
110
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c,
111
+ 0x1f83d9ab, 0x5be0cd19,
96
112
  ];
97
113
  const words = new Uint32Array(64);
98
114
  for (let offset = 0; offset < paddedLength; offset += 64) {
@@ -212,15 +228,28 @@ export function createAgentMcpAuthorizationHandlers(input) {
212
228
  async authorizeCall(call) {
213
229
  const principal = call.context.principal;
214
230
  if (!principal) {
215
- return { allowed: false, decision: "DENY", code: "agent_principal_required" };
231
+ return {
232
+ allowed: false,
233
+ decision: "DENY",
234
+ code: "agent_principal_required",
235
+ };
216
236
  }
217
237
  if (principal.policyHash !== policy.policyHash) {
218
- return { allowed: false, decision: "DENY", code: "policy_hash_mismatch" };
238
+ return {
239
+ allowed: false,
240
+ decision: "DENY",
241
+ code: "policy_hash_mismatch",
242
+ };
219
243
  }
220
- if (!Number.isSafeInteger(principal.generation) || principal.generation < 1) {
221
- return { allowed: false, decision: "DENY", code: "credential_generation_invalid" };
244
+ if (!Number.isSafeInteger(principal.generation) ||
245
+ principal.generation < 1) {
246
+ return {
247
+ allowed: false,
248
+ decision: "DENY",
249
+ code: "credential_generation_invalid",
250
+ };
222
251
  }
223
- const decision = decideAgentTool(policy, call.name);
252
+ const decision = decideRegisteredAgentTool(policy, input.allTools, call.name);
224
253
  if (!decision.allowed) {
225
254
  return { allowed: false, decision: "DENY", code: "tool_denied" };
226
255
  }
@@ -214,6 +214,8 @@ export type SchedulerChangedCounts = {
214
214
  }>;
215
215
  };
216
216
  export declare function normalizeSchedulerChangedCounts(value: unknown): SchedulerChangedCounts | null;
217
+ export declare function setSweepSettlePollDelayMsForTests(ms: number): void;
218
+ export declare function resetSweepSettlePollDelayMsForTests(): void;
217
219
  export declare function schedulerEnvelopeIsTerminal(schedulerStatus: string, receipt?: Record<string, unknown> | null): boolean;
218
220
  export declare function normalizeSchedulerPrimitiveResult(value: unknown, expected?: {
219
221
  expectedTargets: SchedulerExpectedTargetInput[];
@@ -294,6 +294,34 @@ function hasTerminalSchedulerReceipt(receipt) {
294
294
  const status = receipt ? stringValue(receipt.status) : null;
295
295
  return Boolean(status && SCHEDULER_TERMINAL_RECEIPT_STATUSES.has(status));
296
296
  }
297
+ // fix(112ap): poll ONLY a genuinely in-flight attach. `!terminal` is the wrong
298
+ // test — it is also true for `backoff` and `failed`, and re-polling those would
299
+ // both waste the budget and misrepresent a refused dispatch as a pending one.
300
+ // In-flight means an attaching envelope (accepted|attached) that has NOT yet
301
+ // yielded a terminal receipt, matching the 112ao-2b contract exactly.
302
+ function schedulerDispatchIsStillRunning(value) {
303
+ const response = recordValue(value);
304
+ if (!response)
305
+ return false;
306
+ const status = stringValue(response.status) ?? "";
307
+ if (!SCHEDULER_ATTACHING_ENVELOPE_STATUSES.has(status))
308
+ return false;
309
+ return !hasTerminalSchedulerReceipt(recordValue(response.receipt));
310
+ }
311
+ // fix(112ap): bounded self-settle for a dispatched sweep. 4 x 8s = 32s, just past
312
+ // the scheduler's own ~28s observed pass duration and its 30s retryAfterMs hint,
313
+ // while staying well inside the coordinator's per-call budget.
314
+ const SWEEP_SETTLE_POLL_ATTEMPTS = 4;
315
+ const SWEEP_SETTLE_POLL_DEFAULT_DELAY_MS = 8_000;
316
+ // Injectable so pins can exercise the settle loop without real wall-clock waits.
317
+ // Production never calls the setter; the default is what ships.
318
+ let sweepSettlePollDelayMs = SWEEP_SETTLE_POLL_DEFAULT_DELAY_MS;
319
+ export function setSweepSettlePollDelayMsForTests(ms) {
320
+ sweepSettlePollDelayMs = ms;
321
+ }
322
+ export function resetSweepSettlePollDelayMsForTests() {
323
+ sweepSettlePollDelayMs = SWEEP_SETTLE_POLL_DEFAULT_DELAY_MS;
324
+ }
297
325
  export function schedulerEnvelopeIsTerminal(schedulerStatus, receipt = null) {
298
326
  if (SCHEDULER_TERMINAL_ENVELOPE_STATUSES.has(schedulerStatus))
299
327
  return true;
@@ -3052,7 +3080,7 @@ export async function executeOneYoloPrimitive(action, workspaceId) {
3052
3080
  },
3053
3081
  };
3054
3082
  }
3055
- const result = await runSchedulerSweep({
3083
+ let result = await runSchedulerSweep({
3056
3084
  workspaceId,
3057
3085
  action: "run",
3058
3086
  targetDate,
@@ -3062,6 +3090,56 @@ export async function executeOneYoloPrimitive(action, workspaceId) {
3062
3090
  expectedTargetsHash,
3063
3091
  maxPlacements,
3064
3092
  });
3093
+ // fix(112ap): settle our OWN dispatch before returning.
3094
+ //
3095
+ // The route holds a sweep for SCHEDULER_RUN_RESPONSE_BUDGET_MS (1s,
3096
+ // route.ts:20) and then returns `attached` with the real pass still running
3097
+ // in the background. We used to return that immediately, so the coordinator
3098
+ // replanned; concurrent approve/prepare churned readyCounts, which churns
3099
+ // stateRevision, which is hashed into actionKey/requestKey — so the NEXT
3100
+ // iteration minted a BRAND NEW key and polled a run that had never been
3101
+ // dispatched. scopeMatches was correctly false, and it dispatched again.
3102
+ //
3103
+ // Dotwork 2026-07-29 receipts: 12 DISTINCT run_scheduler_sweep keys, each
3104
+ // dispatched exactly once, every one returning exact_action_complete with
3105
+ // `lanesTouched: []`, coverage frozen at {sent:0, scheduled:0, ready:20}.
3106
+ // approve_messages/prepare_messages in the same runs DID report lanesTouched.
3107
+ // Meanwhile the scheduler itself was healthy — its own on-demand receipt
3108
+ // placed 12 ready / 12 considered / 12 scheduled with zero skips, zero
3109
+ // defers, zero prefilters. The sweeps simply never became scheduler runs.
3110
+ //
3111
+ // The key we just dispatched is in scope RIGHT HERE, so poll it here instead
3112
+ // of trying to remember it across a replan. That matters: per-fence runState
3113
+ // does NOT survive next_exact_target (it reset 112e's cohort guard, 112aj's
3114
+ // counter and 112an's stall bound), so a cross-fence memory would have needed
3115
+ // the WORKSPACE_ATTEMPT_LEDGER_KEYS allowlist. Polling in-scope needs none of
3116
+ // that. Bounded so a slow pass still degrades to the existing in-flight
3117
+ // handling rather than holding the tool call open.
3118
+ for (let attempt = 0; attempt < SWEEP_SETTLE_POLL_ATTEMPTS &&
3119
+ schedulerDispatchIsStillRunning(result); attempt += 1) {
3120
+ await sleep(sweepSettlePollDelayMs);
3121
+ const settled = await runSchedulerSweep({
3122
+ workspaceId,
3123
+ action: "status",
3124
+ targetDate,
3125
+ requestKey,
3126
+ targetShapeRevision,
3127
+ expectedTargetsHash,
3128
+ maxPlacements,
3129
+ });
3130
+ // Adopt the polled response ONLY when it actually settled. A status
3131
+ // probe returns a DIFFERENT shape ({scopeMatches, running}) than a
3132
+ // dispatch envelope ({status, receipt}); overwriting unconditionally
3133
+ // destroyed the in-flight envelope and with it
3134
+ // matching_scheduler_active / bounded_reread_wait / retryAfterMs, which
3135
+ // is the handling the coordinator relies on when a run is still going.
3136
+ // Keep the dispatch envelope until a terminal receipt exists.
3137
+ const settledRecord = recordValue(settled);
3138
+ if (settledRecord &&
3139
+ hasTerminalSchedulerReceipt(recordValue(settledRecord.receipt))) {
3140
+ result = settled;
3141
+ }
3142
+ }
3065
3143
  return {
3066
3144
  status: "executed_and_reread",
3067
3145
  result: normalizeSchedulerPrimitiveResult(result, {
@@ -688,6 +688,7 @@ type WorkspaceCoordinatorWaitAction = {
688
688
  senderId: string | null;
689
689
  reason: string | null;
690
690
  };
691
+ declare function clearCompleteDefersForCampaign(runState: Record<string, unknown> | undefined, campaignId: string | null, preserveActionKey?: string | null): Record<string, unknown> | undefined;
691
692
  export declare function approvedPacketComparison(input: RefillSendsCommandInput, targetPlan: unknown): {
692
693
  changed: boolean;
693
694
  expectedTargetShapeRevision: string | null;
@@ -728,6 +729,10 @@ type RefillSenderSelectorMiss = {
728
729
  displayName: string;
729
730
  }>;
730
731
  };
732
+ export declare const refillSendsDeferInternals: {
733
+ clearCompleteDefersForCampaign: typeof clearCompleteDefersForCampaign;
734
+ SUPPLY_INVALIDATED_DEFER_BLOCKERS: Set<string>;
735
+ };
731
736
  export declare function executeRefillSendsCommand(input?: RefillSendsCommandInput): Promise<import("./workspace-context.js").WorkspaceRequiredResult | {
732
737
  ok: boolean;
733
738
  code: string;
@@ -918,7 +918,32 @@ const WORKSPACE_SUPPLY_ACTION_FAMILIES = new Set([
918
918
  "broaden_provider_search",
919
919
  "refresh_sender_post_engagers",
920
920
  ]);
921
- function clearCompleteDefersForCampaign(runState, campaignId) {
921
+ // fix(112ar): the blocker vocabulary the ledger WRITES and the one this cleaner
922
+ // MATCHED had drifted apart. `workspaceExactDeferredBlocker` records
923
+ // "exact_action_complete" (see deferWorkspaceExactTarget), but the filter only
924
+ // recognised "complete"/"capped_by_scheduler" -- so the clear never fired for
925
+ // the blocker that actually gets written, and a satisfied-before-supply defer
926
+ // became a STANDING claim for the rest of the session.
927
+ //
928
+ // Amplify 7-30: three exposed edges (run_scheduler_sweep, approve_messages,
929
+ // prepare_messages) were all held at "exact_action_complete" while the same
930
+ // payload reported 18 ready and 12 rows still to prepare. Every edge that could
931
+ // have filled the day was suppressed, and the run terminaled
932
+ // `actionable_supply_not_queued` -- an owner-invariant breach produced by a
933
+ // stale proof, not by absent supply.
934
+ //
935
+ // Same shape as 112c: a bounded ATTEMPT must not harden into a standing claim.
936
+ const SUPPLY_INVALIDATED_DEFER_BLOCKERS = new Set([
937
+ "complete",
938
+ "capped_by_scheduler",
939
+ "exact_action_complete",
940
+ ]);
941
+ function clearCompleteDefersForCampaign(runState, campaignId,
942
+ // fix(112ar): the action that JUST landed keeps its own defer. Clearing it
943
+ // would re-expose the edge we just completed and invite a duplicate copy /
944
+ // approve. Only the OTHER campaign edges held on a now-stale completeness
945
+ // proof are released.
946
+ preserveActionKey) {
922
947
  if (!runState || !campaignId)
923
948
  return runState;
924
949
  const entries = Array.isArray(runState.coordinatorBlockedExactTargets)
@@ -929,7 +954,11 @@ function clearCompleteDefersForCampaign(runState, campaignId) {
929
954
  const kept = entries.filter((entry) => {
930
955
  const blocked = recordValue(entry);
931
956
  const blocker = stringValue(blocked?.blocker);
932
- if (blocker !== "complete" && blocker !== "capped_by_scheduler") {
957
+ if (!SUPPLY_INVALIDATED_DEFER_BLOCKERS.has(blocker ?? "")) {
958
+ return true;
959
+ }
960
+ if (preserveActionKey &&
961
+ stringValue(blocked?.actionKey) === preserveActionKey) {
933
962
  return true;
934
963
  }
935
964
  return stringValue(blocked?.campaignId) !== campaignId;
@@ -3018,6 +3047,16 @@ async function resolveRefillSenderSelectors(params) {
3018
3047
  resolvedSelectors,
3019
3048
  };
3020
3049
  }
3050
+ // fix(112ar): exported for a DIRECT pin. Binding this rule through the full
3051
+ // command harness proved impossible without silently voiding the test --
3052
+ // deferWorkspaceExactTarget replaces same-identity entries, and
3053
+ // clearApprovalAttemptsForCampaign removes approve-family keys, so a seeded
3054
+ // defer disappears for reasons that have nothing to do with this fix. A unit
3055
+ // pin binds the writer/cleaner vocabulary contract directly.
3056
+ export const refillSendsDeferInternals = {
3057
+ clearCompleteDefersForCampaign,
3058
+ SUPPLY_INVALIDATED_DEFER_BLOCKERS,
3059
+ };
3021
3060
  export async function executeRefillSendsCommand(input = {}) {
3022
3061
  const normalizedInput = normalizeRefillSendsInput(input);
3023
3062
  const yolo = normalizedInput.yolo === true;
@@ -3871,10 +3910,10 @@ export async function executeRefillSendsCommand(input = {}) {
3871
3910
  WORKSPACE_SUPPLY_ACTION_FAMILIES.has(supplyActionFamily)));
3872
3911
  const coordinatorWorkspaceRunState = prepFamilyLandedOnCampaign
3873
3912
  ? clearApprovalAttemptsForCampaign(supplyLandedOnCampaign
3874
- ? clearCompleteDefersForCampaign(deferredCoordinatorWorkspaceRunState, selectedCoordinatorTarget?.campaignId ?? null)
3913
+ ? clearCompleteDefersForCampaign(deferredCoordinatorWorkspaceRunState, selectedCoordinatorTarget?.campaignId ?? null, selectedCoordinatorTarget?.actionKey ?? null)
3875
3914
  : deferredCoordinatorWorkspaceRunState, selectedCoordinatorTarget?.campaignId ?? null)
3876
3915
  : supplyLandedOnCampaign
3877
- ? clearCompleteDefersForCampaign(deferredCoordinatorWorkspaceRunState, selectedCoordinatorTarget?.campaignId ?? null)
3916
+ ? clearCompleteDefersForCampaign(deferredCoordinatorWorkspaceRunState, selectedCoordinatorTarget?.campaignId ?? null, selectedCoordinatorTarget?.actionKey ?? null)
3878
3917
  : deferredCoordinatorWorkspaceRunState;
3879
3918
  // fix(112aj): continuation-level progress-or-settle. When this dispatch is an
3880
3919
  // active_exact_run resume (continuationHandle set, in_progress), fingerprint
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.746",
3
+ "version": "0.1.748",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",