@sellable/mcp 0.1.746 → 0.1.747

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, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.746",
3
+ "version": "0.1.747",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",