@sellable/mcp 0.1.556 → 0.1.557

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 (63) hide show
  1. package/README.md +13 -2
  2. package/agents/registry.json +2 -2
  3. package/dist/api.js +3 -6
  4. package/dist/auth.d.ts +0 -6
  5. package/dist/auth.js +2 -44
  6. package/dist/refill-run-client.d.ts +0 -5
  7. package/dist/refill-run-client.js +0 -15
  8. package/dist/refill-run-loop.d.ts +1 -12
  9. package/dist/refill-run-loop.js +13 -158
  10. package/dist/server.js +23 -0
  11. package/dist/tools/auth.d.ts +0 -5
  12. package/dist/tools/auth.js +12 -49
  13. package/dist/tools/campaign-message-preparation.d.ts +0 -62
  14. package/dist/tools/campaign-message-preparation.js +0 -41
  15. package/dist/tools/campaigns.js +2 -2
  16. package/dist/tools/csv-dnc.js +2 -2
  17. package/dist/tools/evergreen-refill-plan.d.ts +0 -3
  18. package/dist/tools/evergreen-refill-plan.js +7 -29
  19. package/dist/tools/find-leads-runs.d.ts +151 -0
  20. package/dist/tools/find-leads-runs.js +98 -0
  21. package/dist/tools/leads.d.ts +317 -32
  22. package/dist/tools/leads.js +171 -10
  23. package/dist/tools/model-quality.js +4 -6
  24. package/dist/tools/prompts.d.ts +3 -3
  25. package/dist/tools/prompts.js +7 -15
  26. package/dist/tools/provider-preflight.d.ts +65 -2
  27. package/dist/tools/provider-preflight.js +97 -10
  28. package/dist/tools/readiness.d.ts +89 -5
  29. package/dist/tools/readiness.js +66 -0
  30. package/dist/tools/refill-executors.d.ts +0 -38
  31. package/dist/tools/refill-executors.js +3 -222
  32. package/dist/tools/refill-sends-v2.d.ts +1 -118
  33. package/dist/tools/refill-sends-v2.js +2 -310
  34. package/dist/tools/refill-sends.d.ts +32 -678
  35. package/dist/tools/refill-sends.js +13 -274
  36. package/dist/tools/refill-target-plan.js +14 -486
  37. package/dist/tools/registry.d.ts +330 -115
  38. package/dist/tools/registry.js +7 -1
  39. package/dist/tools/scheduler-fill-capacity.js +1 -1
  40. package/dist/tools/scheduler-run.d.ts +0 -71
  41. package/dist/tools/scheduler-run.js +1 -203
  42. package/dist/tools/setup-evergreen-campaigns.js +1 -1
  43. package/dist/tools/workspace-context.d.ts +1 -1
  44. package/dist/tools/workspace-context.js +3 -8
  45. package/dist/tools/workspace-export.js +2 -2
  46. package/dist/tools/workspaces.d.ts +2 -48
  47. package/dist/tools/workspaces.js +5 -48
  48. package/package.json +1 -1
  49. package/skills/create-campaign/SKILL.md +3 -3
  50. package/skills/create-campaign-v2/SKILL.md +1 -1
  51. package/skills/create-evergreen-campaigns/SKILL.md +16 -16
  52. package/skills/find-leads/SKILL.md +48 -630
  53. package/skills/find-leads-v2/SKILL.md +70 -0
  54. package/skills/find-leads-v2/core/flow.v1.json +31 -0
  55. package/skills/refill-sends/SKILL.md +353 -91
  56. package/skills/refill-sends-v2/SKILL.md +6 -6
  57. package/skills/refill-sends-v2-workflow/SKILL.md +5 -5
  58. package/skills/refill-sends-v2-workflow/core/flow.v1.json +8 -8
  59. package/skills/refill-sends-workflow/SKILL.md +743 -100
  60. package/skills/refill-sends-workflow/core/flow.v1.json +1 -185
  61. package/dist/refill-contract.d.ts +0 -157
  62. package/dist/refill-contract.js +0 -487
  63. package/skills/refill-sends-workflow/core/contract.v2.json +0 -543
package/README.md CHANGED
@@ -16,8 +16,9 @@ Each message gets 5+ minutes of Claude attention with deep research - no other t
16
16
 
17
17
  ### Prompt Source Of Truth
18
18
 
19
- There are eight public Sellable entrypoints shared across hosts:
19
+ There are nine public Sellable entrypoints shared across hosts:
20
20
 
21
+ - `sellable:find-leads`
21
22
  - `sellable:create-campaign`
22
23
  - `sellable:create-ab-test`
23
24
  - `sellable:create-evergreen-campaigns`
@@ -33,6 +34,11 @@ the approval-gated workflow from:
33
34
 
34
35
  - `mcp/sellable/skills/create-campaign-v2/SKILL.md`
35
36
 
37
+ The find-leads public wrapper builds a live exportable source list without a
38
+ campaign and loads its durable approval-gated workflow from:
39
+
40
+ - `mcp/sellable/skills/find-leads-v2/SKILL.md`
41
+
36
42
  The create-ab-test public wrapper prepares clean A/B split lead lists and
37
43
  review-copy campaigns from:
38
44
 
@@ -205,7 +211,7 @@ thread and select `Sellable Create Campaign`, `Sellable Create A/B Test`,
205
211
  `Sellable Create Evergreen Campaigns`, `Sellable Foundation`,
206
212
  `Sellable Content`, `Sellable Create Post`, `Sellable Refresh Sender Engagement`,
207
213
  or `Sellable Refill Sends`; or invoke
208
- `$sellable:create-campaign`, `$sellable:create-ab-test`,
214
+ `$sellable:find-leads`, `$sellable:create-campaign`, `$sellable:create-ab-test`,
209
215
  `$sellable:create-evergreen-campaigns`, `$sellable:foundation`,
210
216
  `$sellable:content`, `$sellable:create-post`,
211
217
  `$sellable:refresh-sender-engagement`, or `$sellable:refill-sends`. If the app still says
@@ -220,6 +226,7 @@ the Sellable MCP tools for Codex Desktop.
220
226
  Use these names consistently:
221
227
 
222
228
  - Claude Code command: `/sellable:create-campaign`
229
+ - Claude Code command: `/sellable:find-leads`
223
230
  - Claude Code command: `/sellable:create-ab-test`
224
231
  - Claude Code command: `/sellable:create-evergreen-campaigns`
225
232
  - Claude Code command: `/sellable:foundation`
@@ -228,6 +235,7 @@ Use these names consistently:
228
235
  - Claude Code command: `/sellable:refresh-sender-engagement`
229
236
  - Claude Code command: `/sellable:refill-sends`
230
237
  - Codex command: `$sellable:create-campaign`
238
+ - Codex command: `$sellable:find-leads`
231
239
  - Codex command: `$sellable:create-ab-test`
232
240
  - Codex command: `$sellable:create-evergreen-campaigns`
233
241
  - Codex command: `$sellable:foundation`
@@ -237,6 +245,7 @@ Use these names consistently:
237
245
  - Codex command: `$sellable:refill-sends`
238
246
  - Codex Desktop plugin: `sellable@sellable`
239
247
  - Codex visible skill: `Sellable Create Campaign`
248
+ - Codex visible skill: `Sellable Find Leads`
240
249
  - Codex visible skill: `Sellable Create A/B Test`
241
250
  - Codex visible skill: `Sellable Create Evergreen Campaigns`
242
251
  - Codex visible skill: `Sellable Foundation`
@@ -245,6 +254,7 @@ Use these names consistently:
245
254
  - Codex visible skill: `Sellable Refresh Sender Engagement`
246
255
  - Codex visible skill: `Sellable Refill Sends`
247
256
  - Codex skill frontmatter name: `create-campaign`
257
+ - Codex skill frontmatter name: `find-leads`
248
258
  - Codex skill frontmatter name: `create-ab-test`
249
259
  - Codex skill frontmatter name: `create-evergreen-campaigns`
250
260
  - Codex skill frontmatter name: `foundation`
@@ -254,6 +264,7 @@ Use these names consistently:
254
264
  - Codex skill frontmatter name: `refill-sends`
255
265
  - MCP server name: `sellable`
256
266
  - Internal workflow prompt: `create-campaign-v2`
267
+ - Internal workflow prompt: `find-leads-v2`
257
268
  - Internal workflow prompt: `refill-sends-workflow`
258
269
  - Internal/backward-compatible memory prompt: `interview`
259
270
 
@@ -26,8 +26,8 @@
26
26
  "ownership": "message strategy, proof inventory, token rules, skeptical-prospect review, and selected winner only",
27
27
  "codex": {
28
28
  "description": "Message Drafting worker for campaign-backed template proposals after confirm_lead_list imports a non-empty bounded review batch.",
29
- "model": "gpt-5.6-sol",
30
- "modelReasoningEffort": "high",
29
+ "model": "gpt-5.5",
30
+ "modelReasoningEffort": "xhigh",
31
31
  "sandboxMode": "read-only",
32
32
  "nicknameCandidates": [
33
33
  "Message Drafting",
package/dist/api.js CHANGED
@@ -3,7 +3,7 @@ import { mkdir, rename, rm, stat } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { Readable } from "node:stream";
5
5
  import { pipeline } from "node:stream/promises";
6
- import { getConfig, getConfigPath, getEffectiveConfiguredWorkspaceId, resolveWorkspaceIdForRequest, } from "./auth.js";
6
+ import { getConfig, getConfigPath } from "./auth.js";
7
7
  export class SellableApiError extends Error {
8
8
  status;
9
9
  body;
@@ -42,12 +42,9 @@ export class SellableApi {
42
42
  // Re-read config on every request so workspace switches take effect immediately
43
43
  const config = getConfig();
44
44
  const url = `${config.apiUrl}${path}`;
45
- const requestedWorkspaceId = options && Object.prototype.hasOwnProperty.call(options, "workspaceId")
46
- ? options.workspaceId
47
- : getEffectiveConfiguredWorkspaceId(config);
48
45
  const workspaceId = options && Object.prototype.hasOwnProperty.call(options, "workspaceId")
49
- ? resolveWorkspaceIdForRequest(requestedWorkspaceId, `${method} ${path}`)
50
- : requestedWorkspaceId;
46
+ ? options.workspaceId
47
+ : config.activeWorkspaceId || config.workspaceId || null;
51
48
  const response = await fetch(url, {
52
49
  method,
53
50
  headers: {
package/dist/auth.d.ts CHANGED
@@ -40,12 +40,6 @@ export type SkillState = {
40
40
  export declare function setConfigFile(fileName: string): void;
41
41
  export declare function getResolvedConfigsDir(): string | null;
42
42
  export declare function getConfigPath(): string;
43
- export declare function getLockedWorkspaceId(): string | null;
44
- export declare function isWorkspaceLockRequired(): boolean;
45
- export declare function workspaceLockRequiredMessage(action?: string): string;
46
- export declare function assertWorkspaceLockSatisfied(action?: string): void;
47
- export declare function getEffectiveConfiguredWorkspaceId(config: Pick<SellableConfig, "activeWorkspaceId" | "workspaceId">): string | null;
48
- export declare function resolveWorkspaceIdForRequest(requestedWorkspaceId: string | null | undefined, action?: string): string | null;
49
43
  export declare function getConfig(): SellableConfig;
50
44
  export declare function updateActiveWorkspace(params: {
51
45
  workspaceId: string;
package/dist/auth.js CHANGED
@@ -2,8 +2,6 @@ import * as fs from "fs";
2
2
  import * as os from "os";
3
3
  import * as path from "path";
4
4
  const DEFAULT_API_URL = "https://app.sellable.dev";
5
- const WORKSPACE_LOCK_ENV = "SELLABLE_LOCK_WORKSPACE_ID";
6
- const REQUIRE_WORKSPACE_LOCK_ENV = "SELLABLE_REQUIRE_WORKSPACE_LOCK";
7
5
  // No caching — re-read config on every call so token/workspace changes
8
6
  // made by the LLM (editing sellable.json) take effect immediately
9
7
  // without requiring an MCP server restart.
@@ -87,44 +85,6 @@ function normalizeConfig(raw) {
87
85
  apiUrl: raw.apiUrl || DEFAULT_API_URL,
88
86
  };
89
87
  }
90
- export function getLockedWorkspaceId() {
91
- const value = process.env[WORKSPACE_LOCK_ENV]?.trim();
92
- return value || null;
93
- }
94
- export function isWorkspaceLockRequired() {
95
- const value = process.env[REQUIRE_WORKSPACE_LOCK_ENV]?.trim().toLowerCase();
96
- return value === "1" || value === "true" || value === "yes";
97
- }
98
- export function workspaceLockRequiredMessage(action = "Sellable request") {
99
- return (`Workspace lock required: ${action} cannot run because ` +
100
- `${REQUIRE_WORKSPACE_LOCK_ENV}=1 but ${WORKSPACE_LOCK_ENV} is missing. ` +
101
- "Ask the sellable-admin profile to provision this customer profile with an approved customer workspace.");
102
- }
103
- export function assertWorkspaceLockSatisfied(action = "Sellable request") {
104
- if (isWorkspaceLockRequired() && !getLockedWorkspaceId()) {
105
- throw new Error(workspaceLockRequiredMessage(action));
106
- }
107
- }
108
- function normalizeWorkspaceId(value) {
109
- return typeof value === "string" && value.trim() ? value.trim() : null;
110
- }
111
- export function getEffectiveConfiguredWorkspaceId(config) {
112
- assertWorkspaceLockSatisfied("workspace resolution");
113
- return (getLockedWorkspaceId() ||
114
- normalizeWorkspaceId(config.activeWorkspaceId) ||
115
- normalizeWorkspaceId(config.workspaceId));
116
- }
117
- export function resolveWorkspaceIdForRequest(requestedWorkspaceId, action = "Sellable request") {
118
- assertWorkspaceLockSatisfied(action);
119
- const lockedWorkspaceId = getLockedWorkspaceId();
120
- const requested = normalizeWorkspaceId(requestedWorkspaceId);
121
- if (!lockedWorkspaceId)
122
- return requested;
123
- if (requested && requested !== lockedWorkspaceId) {
124
- throw new Error(`Workspace lock violation: ${action} requested workspace ${requested}, but this profile is locked to ${lockedWorkspaceId}.`);
125
- }
126
- return lockedWorkspaceId;
127
- }
128
88
  export function getConfig() {
129
89
  // Config cache removed 2026-05-04: we always re-read from disk so
130
90
  // workspace switches (set_active_workspace) take effect immediately
@@ -179,7 +139,6 @@ export function getConfig() {
179
139
  }
180
140
  }
181
141
  export function updateActiveWorkspace(params) {
182
- resolveWorkspaceIdForRequest(params.workspaceId, "set_active_workspace");
183
142
  const configPath = getConfigPath();
184
143
  const configDir = path.dirname(configPath);
185
144
  if (!fs.existsSync(configDir)) {
@@ -267,7 +226,6 @@ function writeRawConfigFile(configPath, raw) {
267
226
  * new config without an MCP server restart.
268
227
  */
269
228
  export function writeNewConfig(opts) {
270
- resolveWorkspaceIdForRequest(opts.activeWorkspaceId, "write Sellable auth config");
271
229
  const configPath = getConfigWritePath();
272
230
  let raw = {};
273
231
  if (fs.existsSync(configPath)) {
@@ -325,7 +283,7 @@ function getActiveEnvConfigRef(raw) {
325
283
  }
326
284
  export function getEngageState() {
327
285
  const config = getConfig();
328
- const workspaceId = getEffectiveConfiguredWorkspaceId(config);
286
+ const workspaceId = config.activeWorkspaceId || config.workspaceId || null;
329
287
  if (!workspaceId) {
330
288
  return { activeWorkspaceId: null, state: null };
331
289
  }
@@ -336,7 +294,7 @@ export function updateEngageState(patch) {
336
294
  const { configPath, raw } = readRawConfigFile();
337
295
  const { envConfig, set } = getActiveEnvConfigRef(raw);
338
296
  const normalized = normalizeConfig(envConfig);
339
- const workspaceId = getEffectiveConfiguredWorkspaceId(normalized);
297
+ const workspaceId = normalized.activeWorkspaceId || normalized.workspaceId || null;
340
298
  if (!workspaceId) {
341
299
  throw new Error("No active workspace selected. Run list_workspaces then set_active_workspace.");
342
300
  }
@@ -5,11 +5,6 @@ export type RefillRunBlockerResult = {
5
5
  runId: string;
6
6
  gate?: string;
7
7
  gateSeq?: number;
8
- } | {
9
- blocker: "scope_drift" | "approval_expired" | "approval_fingerprint_mismatch";
10
- runId: string;
11
- expectedScopeHash: string | null;
12
- receivedScopeHash: string | null;
13
8
  };
14
9
  export declare function startRefillRunRemote(input: {
15
10
  workspaceId?: string;
@@ -15,21 +15,6 @@ function parseBlockerBody(body) {
15
15
  gateSeq: typeof parsed.gateSeq === "number" ? parsed.gateSeq : undefined,
16
16
  };
17
17
  }
18
- if ((parsed.blocker === "scope_drift" ||
19
- parsed.blocker === "approval_expired" ||
20
- parsed.blocker === "approval_fingerprint_mismatch") &&
21
- typeof parsed.runId === "string") {
22
- return {
23
- blocker: parsed.blocker,
24
- runId: parsed.runId,
25
- expectedScopeHash: typeof parsed.expectedScopeHash === "string"
26
- ? parsed.expectedScopeHash
27
- : null,
28
- receivedScopeHash: typeof parsed.receivedScopeHash === "string"
29
- ? parsed.receivedScopeHash
30
- : null,
31
- };
32
- }
33
18
  }
34
19
  catch {
35
20
  return null;
@@ -1,6 +1,6 @@
1
1
  import type { appendIndexLine, appendJournalEvent, createRunJournal, renderBootstrapSection, renderCrashedAbandonedSection, renderExecutionEventSection, renderPlanSection, renderTerminalSection } from "./refill-journal.js";
2
2
  import type { advanceRefillRunGateRemote, completeRefillRun, heartbeatRefillRun, recordRunOutcome, recordRunPlanned, refillRunStatus, startRefillRunRemote } from "./refill-run-client.js";
3
- import type { getPrepareCampaignMessagesStatus, stopSatisfiedPrepareCampaignMessages } from "./tools/campaign-message-preparation.js";
3
+ import type { getPrepareCampaignMessagesStatus } from "./tools/campaign-message-preparation.js";
4
4
  import type { executeOneYoloPrimitive, executeStartCampaignPrimitive, prepareRowSelectorValue, refillPrepareRequestHash, refreshPaidInmailCreditsWithRetry } from "./tools/refill-executors.js";
5
5
  export type RefillLoopGate = "g0_bootstrap" | "g1_plan" | "g2_execute" | "g3_verify" | "g4_terminal";
6
6
  export interface RefillV2LoopInput {
@@ -13,12 +13,6 @@ export interface RefillV2LoopInput {
13
13
  };
14
14
  approvalMode?: "approve" | "mark_ready";
15
15
  dryRun?: boolean;
16
- targetDate?: string;
17
- untilDate?: string;
18
- horizonSendDays?: number;
19
- scope?: Record<string, unknown>;
20
- scopeHash?: string;
21
- approval?: Record<string, unknown>;
22
16
  }
23
17
  export interface RefillV2LoopBudgets {
24
18
  maxGateCyclesPerInvocation: number;
@@ -42,7 +36,6 @@ export interface RefillV2Executors {
42
36
  executeStartCampaignPrimitive: typeof executeStartCampaignPrimitive;
43
37
  refreshPaidInmailCreditsWithRetry: typeof refreshPaidInmailCreditsWithRetry;
44
38
  getPrepareCampaignMessagesStatus?: typeof getPrepareCampaignMessagesStatus;
45
- stopSatisfiedPrepareCampaignMessages?: typeof stopSatisfiedPrepareCampaignMessages;
46
39
  refillPrepareRequestHash?: typeof refillPrepareRequestHash;
47
40
  prepareRowSelectorValue?: typeof prepareRowSelectorValue;
48
41
  }
@@ -68,10 +61,6 @@ export interface RefillV2LoopDeps {
68
61
  senderIds?: string[];
69
62
  runState?: Record<string, unknown>;
70
63
  journal?: boolean;
71
- targetDate?: string;
72
- untilDate?: string;
73
- horizonSendDays?: number;
74
- scopeHash?: string;
75
64
  }) => Promise<Record<string, unknown>>;
76
65
  executors: RefillV2Executors;
77
66
  journal: RefillV2JournalDeps;
@@ -59,16 +59,6 @@ const SCHEDULER_RUN_NEXT_ACTIONS = new Set([
59
59
  function isRecord(value) {
60
60
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
61
61
  }
62
- function planScopeInput(input) {
63
- return {
64
- ...(input.targetDate ? { targetDate: input.targetDate } : {}),
65
- ...(input.untilDate ? { untilDate: input.untilDate } : {}),
66
- ...(input.horizonSendDays !== undefined
67
- ? { horizonSendDays: input.horizonSendDays }
68
- : {}),
69
- ...(input.scopeHash ? { scopeHash: input.scopeHash } : {}),
70
- };
71
- }
72
62
  function stringValue(value) {
73
63
  return typeof value === "string" && value.trim() ? value.trim() : null;
74
64
  }
@@ -266,8 +256,10 @@ function sanitizeSchedulerRunReceipt(raw) {
266
256
  const prefilteredCells = numberValue(rawReceipt.prefilteredCells) ?? 0;
267
257
  const tablesFilteredForSenderMismatch = numberValue(rawReceipt.tablesFilteredForSenderMismatch) ?? 0;
268
258
  dominantSkipReason = dominantSchedulerRunSkipReason(skipReasons);
269
- dominantPrefilterReason = dominantSchedulerRunCountReason(prefilterReasons);
270
- dominantDeferredReason = dominantSchedulerRunSkipReason(deferredReasons);
259
+ dominantPrefilterReason =
260
+ dominantSchedulerRunCountReason(prefilterReasons);
261
+ dominantDeferredReason =
262
+ dominantSchedulerRunSkipReason(deferredReasons);
271
263
  recommendedNextAction = schedulerRunRecommendedAction({
272
264
  rawAction: stringValue(rawReceipt.nextAction),
273
265
  readyCellsFound,
@@ -437,43 +429,6 @@ function normalizeDoneReason(value) {
437
429
  function runStateProgress(ctx) {
438
430
  return recordValue(ctx.runState.progress) ?? {};
439
431
  }
440
- function prepCircuitFrom(value) {
441
- const root = recordValue(value);
442
- const result = recordValue(root?.result);
443
- const progress = recordValue(root?.progress);
444
- return (recordValue(root?.circuit) ??
445
- recordValue(result?.circuit) ??
446
- recordValue(progress?.circuit) ??
447
- null);
448
- }
449
- function openCircuitWaits(circuit, now) {
450
- if (stringValue(circuit?.state) !== "open")
451
- return false;
452
- const nextProbeAt = stringValue(circuit?.nextProbeAt);
453
- return Boolean(nextProbeAt && Date.parse(nextProbeAt) > now.getTime());
454
- }
455
- function activePreparationScopeSatisfied(plan, action) {
456
- const packet = recordValue(plan.packet) ?? plan;
457
- const target = recordValue(packet.target) ?? recordValue(plan.target) ?? {};
458
- const senderId = actionSenderId(action);
459
- const toolInput = actionToolInput(action);
460
- const targetDate = stringValue(toolInput.targetDate);
461
- const refillLaneKey = stringValue(toolInput.refillLaneKey);
462
- const senderPlans = arrayValue(target.senderRefillPlans).filter(isRecord);
463
- const ledgers = senderPlans
464
- .filter((senderPlan) => !senderId || stringValue(senderPlan.senderId) === senderId)
465
- .flatMap((senderPlan) => arrayValue(senderPlan.laneLedgers).filter(isRecord));
466
- const matching = ledgers.filter((ledger) => (!senderId || stringValue(ledger.senderId) === senderId) &&
467
- (!targetDate || stringValue(ledger.targetDate) === targetDate) &&
468
- (!refillLaneKey || stringValue(ledger.refillLaneKey) === refillLaneKey));
469
- return (matching.length > 0 &&
470
- matching.every((ledger) => {
471
- const checks = recordValue(ledger.identityChecks);
472
- return (numberValue(ledger.initialAllocatablePrepDeficit) === 0 &&
473
- checks != null &&
474
- Object.values(checks).every((value) => value === true));
475
- }));
476
- }
477
432
  function mergeProgress(ctx, updates) {
478
433
  return {
479
434
  ...runStateProgress(ctx),
@@ -717,28 +672,14 @@ async function createJournal(input, deps, runId) {
717
672
  }
718
673
  async function startOrResume(input, deps) {
719
674
  const initialRunState = buildRunStateFromLocalHints(input.workspaceId);
720
- const scopedInitialRunState = input.scope
721
- ? {
722
- ...initialRunState,
723
- scope: input.scope,
724
- scopeHash: input.scopeHash,
725
- approval: input.approval,
726
- }
727
- : initialRunState;
728
675
  const started = await deps.runClient.startRefillRunRemote({
729
676
  workspaceId: input.workspaceId,
730
- config: input.scope
731
- ? {
732
- scope: input.scope,
733
- scopeHash: input.scopeHash,
734
- approval: input.approval,
735
- }
736
- : {
737
- intent: input.intent ?? "auto",
738
- senderIds: input.senderIds,
739
- approvalMode: input.approvalMode ?? "approve",
740
- },
741
- runState: scopedInitialRunState,
677
+ config: {
678
+ intent: input.intent ?? "auto",
679
+ senderIds: input.senderIds,
680
+ approvalMode: input.approvalMode ?? "approve",
681
+ },
682
+ runState: initialRunState,
742
683
  resume: input.resume,
743
684
  });
744
685
  if (isLeaseLost(started)) {
@@ -771,7 +712,7 @@ async function startOrResume(input, deps) {
771
712
  const gate = (stringValue(result.gate) ?? "g0_bootstrap");
772
713
  const gateSeq = numberValue(result.gateSeq) ?? 0;
773
714
  const runState = recordValue(result.runState) ??
774
- recordValue(scopedInitialRunState) ??
715
+ recordValue(initialRunState) ??
775
716
  {
776
717
  version: 1,
777
718
  senderCursors: [],
@@ -814,7 +755,6 @@ async function gateBootstrap(input, deps, ctx) {
814
755
  senderIds: input.senderIds,
815
756
  runState: ctx.runState,
816
757
  journal: false,
817
- ...planScopeInput(input),
818
758
  });
819
759
  const paidCredit = arrayValue(recordValue(plan.bootstrap)?.paidCredit).filter(isRecord);
820
760
  const refreshedSenderIds = new Set(arrayValue(recordValue(ctx.runState.creditTrust)?.senderIds)
@@ -961,7 +901,6 @@ async function gatePlan(input, deps, ctx) {
961
901
  senderIds: input.senderIds,
962
902
  runState: ctx.runState,
963
903
  journal: false,
964
- ...planScopeInput(input),
965
904
  });
966
905
  if (plan.blocker === "workspace_access") {
967
906
  return {
@@ -1021,17 +960,6 @@ async function gatePlan(input, deps, ctx) {
1021
960
  fallbackSummary: line.fallbackSummary,
1022
961
  }));
1023
962
  const fingerprint = fingerprintFromPlan(plan);
1024
- const persistedCircuit = recordValue(runStateProgress(ctx).prepCircuit);
1025
- if (stringValue(action.type) === "prepare_messages" &&
1026
- openCircuitWaits(persistedCircuit, deps.now?.() ?? new Date())) {
1027
- const heartbeat = await heartbeatAndContinue(input, deps, ctx);
1028
- if (heartbeat.status !== "in_progress")
1029
- return heartbeat;
1030
- return {
1031
- ...heartbeat,
1032
- guidance: `Preparation circuit remains open until ${String(persistedCircuit?.nextProbeAt)}; re-invoke with this handle after the probe deadline.`,
1033
- };
1034
- }
1035
963
  const noProgress = nextNoProgressState(ctx, plan, action);
1036
964
  if (noProgress.repeatCount >= 3) {
1037
965
  return completeTerminal(input, deps, ctx, "blocked", {
@@ -1042,17 +970,10 @@ async function gatePlan(input, deps, ctx) {
1042
970
  guidance: "The same refill rung repeated three times with no coverage, frontier, or lane movement.",
1043
971
  });
1044
972
  }
1045
- const circuitProgress = stringValue(action.type) === "prepare_messages" &&
1046
- stringValue(persistedCircuit?.state) === "open"
1047
- ? { ...persistedCircuit, state: "half_open" }
1048
- : persistedCircuit;
1049
973
  const nextRunState = mergeRunState(ctx.runState, {
1050
974
  lastPlan: fingerprint,
1051
975
  headAction: action,
1052
- progress: mergeProgress(ctx, {
1053
- noProgress,
1054
- ...(circuitProgress ? { prepCircuit: circuitProgress } : {}),
1055
- }),
976
+ progress: mergeProgress(ctx, { noProgress }),
1056
977
  });
1057
978
  const advanced = await deps.runClient.advanceRefillRunGateRemote({
1058
979
  workspaceId: input.workspaceId,
@@ -1089,7 +1010,6 @@ async function verifyRecoveryPoint(input, deps, ctx) {
1089
1010
  senderIds: input.senderIds,
1090
1011
  runState: ctx.runState,
1091
1012
  journal: false,
1092
- ...planScopeInput(input),
1093
1013
  });
1094
1014
  const changed = fingerprintChanged(expected, fingerprintFromPlan(fresh));
1095
1015
  if (changed.length > 0) {
@@ -1159,9 +1079,6 @@ async function gateExecute(input, deps, ctx) {
1159
1079
  dispatchKey: stringValue(action.type),
1160
1080
  evergreenRung: stringValue(action.evergreenRung),
1161
1081
  sideEffectClass: stringValue(action.sideEffectClass),
1162
- scopeHash: input.scopeHash ?? null,
1163
- expectedTargetsHash: stringValue(input.scope?.expectedTargetsHash) ?? null,
1164
- dateSelector: recordValue(input.scope?.dateSelector),
1165
1082
  };
1166
1083
  const plannedResult = await deps.runClient.recordRunPlanned({
1167
1084
  workspaceId: input.workspaceId,
@@ -1255,12 +1172,6 @@ async function advanceBackToPlan(input, deps, ctx, runStateUpdates = {}) {
1255
1172
  async function verifyPrepareAction(input, deps, ctx, action, budgets) {
1256
1173
  const getStatus = deps.executors.getPrepareCampaignMessagesStatus;
1257
1174
  const jobId = activeJobIdFromOutcome(ctx.runState.lastOutcome);
1258
- const executionCircuit = prepCircuitFrom(ctx.runState.lastOutcome);
1259
- if (!jobId && executionCircuit) {
1260
- return advanceBackToPlan(input, deps, ctx, {
1261
- progress: mergeProgress(ctx, { prepCircuit: executionCircuit }),
1262
- });
1263
- }
1264
1175
  if (!getStatus || !jobId) {
1265
1176
  return {
1266
1177
  status: "in_progress",
@@ -1300,58 +1211,6 @@ async function verifyPrepareAction(input, deps, ctx, action, budgets) {
1300
1211
  }),
1301
1212
  });
1302
1213
  }
1303
- if (ownJob &&
1304
- !isTerminalJobStatus(latestStatus) &&
1305
- deps.executors.stopSatisfiedPrepareCampaignMessages) {
1306
- const fresh = await deps.readPlan({
1307
- workspaceId: input.workspaceId,
1308
- intent: input.intent,
1309
- senderIds: input.senderIds,
1310
- runState: ctx.runState,
1311
- journal: false,
1312
- ...planScopeInput(input),
1313
- });
1314
- ctx.plan = fresh;
1315
- if (activePreparationScopeSatisfied(fresh, action)) {
1316
- const plannedStop = await deps.runClient.recordRunPlanned({
1317
- workspaceId: input.workspaceId,
1318
- runId: ctx.runId,
1319
- fence: ctx.fence,
1320
- gate: ctx.gate,
1321
- gateSeq: ctx.gateSeq,
1322
- planned: {
1323
- toolName: "stop_satisfied_campaign_message_preparation",
1324
- toolInput: { jobId },
1325
- dispatchKey: `stop_satisfied:${jobId}`,
1326
- sideEffectClass: "message_preparation_stop",
1327
- scopeHash: input.scopeHash ?? null,
1328
- },
1329
- });
1330
- if (isLeaseLost(plannedStop))
1331
- return leaseLostResult(ctx);
1332
- const stopEventId = stringValue(recordValue(plannedStop)?.eventId) ??
1333
- `${ctx.runId}:${ctx.gateSeq}:stop`;
1334
- const stopReceipt = await deps.executors.stopSatisfiedPrepareCampaignMessages({
1335
- workspaceId: input.workspaceId,
1336
- jobId,
1337
- expectedUpdatedAt: stringValue(statusRecord.updatedAt) ?? undefined,
1338
- });
1339
- await deps.runClient.recordRunOutcome({
1340
- workspaceId: input.workspaceId,
1341
- runId: ctx.runId,
1342
- fence: ctx.fence,
1343
- eventId: stopEventId,
1344
- did: { jobId },
1345
- outcome: stopReceipt,
1346
- });
1347
- return advanceBackToPlan(input, deps, ctx, {
1348
- progress: mergeProgress(ctx, {
1349
- prepStopReceipt: stopReceipt,
1350
- prepStoppedAt: (deps.now?.() ?? new Date()).toISOString(),
1351
- }),
1352
- });
1353
- }
1354
- }
1355
1214
  if (isTerminalJobStatus(latestStatus)) {
1356
1215
  const preparedCount = preparedCountFromStatus(latestStatus);
1357
1216
  const checkedRows = checkedRowsFromStatus(latestStatus);
@@ -1419,7 +1278,6 @@ async function verifyReadOnlyWait(input, deps, ctx, budgets, waitKind) {
1419
1278
  senderIds: input.senderIds,
1420
1279
  runState: ctx.runState,
1421
1280
  journal: false,
1422
- ...planScopeInput(input),
1423
1281
  });
1424
1282
  ctx.plan = fresh;
1425
1283
  const doneReason = doneReasonFromPlan(fresh);
@@ -1502,7 +1360,6 @@ async function verifySchedulerWait(input, deps, ctx, action, budgets) {
1502
1360
  senderIds: input.senderIds,
1503
1361
  runState: ctx.runState,
1504
1362
  journal: false,
1505
- ...planScopeInput(input),
1506
1363
  });
1507
1364
  ctx.plan = fresh;
1508
1365
  const doneReason = normalizeDoneReason(doneReasonFromPlan(fresh));
@@ -1565,7 +1422,6 @@ async function gateVerify(input, deps, ctx, budgets) {
1565
1422
  senderIds: input.senderIds,
1566
1423
  runState: ctx.runState,
1567
1424
  journal: false,
1568
- ...planScopeInput(input),
1569
1425
  });
1570
1426
  ctx.plan = fresh;
1571
1427
  const doneReason = doneReasonFromPlan(fresh);
@@ -1630,7 +1486,7 @@ async function heartbeatAndContinue(input, deps, ctx) {
1630
1486
  runId: ctx.runId,
1631
1487
  fence: ctx.fence,
1632
1488
  gate: ctx.gate,
1633
- guidance: "Per-invocation gate budget reached. Re-invoke public refill_sends with this durable run handle to continue.",
1489
+ guidance: "Per-invocation gate budget reached. Re-invoke refill_sends_v2 with this runId/fence to continue.",
1634
1490
  journalPath: ctx.journalPath,
1635
1491
  };
1636
1492
  }
@@ -1644,7 +1500,6 @@ export async function runRefillV2Loop(input, deps) {
1644
1500
  intent: input.intent,
1645
1501
  senderIds: input.senderIds,
1646
1502
  journal: true,
1647
- ...planScopeInput(input),
1648
1503
  });
1649
1504
  return { status: "dry_run", plan };
1650
1505
  }
package/dist/server.js CHANGED
@@ -29,6 +29,7 @@ import { getEngageStateTool, setEngageStateTool, } from "./tools/engage-state.js
29
29
  import { bulkEnrichWithProspeo, enrichWithProspeo, getProspeoCredits, } from "./tools/enrichment.js";
30
30
  import { getRefillPlanV2 } from "./tools/evergreen-refill-plan.js";
31
31
  import { getCampaignFramework } from "./tools/framework.js";
32
+ import { bootstrapFindLeads, cancelFindLeads, getFindLeadsRun, reissueFindLeadsWatchLink, updateFindLeadsRun, waitForFindLeadsRun, } from "./tools/find-leads-runs.js";
32
33
  import { confirmHarvestJobCompanies, searchHarvestJobs, } from "./tools/harvest-jobs.js";
33
34
  import { checkInboxReplyEligibility, getInboxThread, searchInboxThreads, sendInboxDraft, sendInboxManualReply, updateInboxDraft, } from "./tools/inbox.js";
34
35
  import { cancelLeadImport, confirmLeadList, confirmProspeoCompanyAccounts, getProviderPrompt, importLeads, listDncEntriesTool, loadCsvDncEntriesTool, loadCsvDomains, loadCsvLinkedinLeads, lookupSalesNavFilter, saveDomainFilters, searchApollo, searchProspeo, searchProspeoCompanies, searchSalesNav, searchSignals, selectPromisingPosts, setHeadlineICPCriteria, } from "./tools/leads.js";
@@ -36,6 +37,7 @@ import { fetchCompany, fetchCompanyPosts, fetchLinkedInPosts, fetchLinkedInProfi
36
37
  import { getCampaignNavigationState } from "./tools/navigation.js";
37
38
  import { addOnDemandLeads, createOnDemandCampaign, createOnDemandTable, initOnDemandSequence, pauseOnDemandCampaign, startOnDemandCampaign, } from "./tools/one-off.js";
38
39
  import { upsertRubric } from "./tools/processing.js";
40
+ import { preflightFindLeadsProvider } from "./tools/provider-preflight.js";
39
41
  import { completeSenderResearch, getPostFindLeadsScoutRegistry, getSourceScoutRegistry, getSubskillAsset, getSubskillPrompt, listSubskillPrompts, searchSubskillPrompts, } from "./tools/prompts.js";
40
42
  import { waitForCampaignTableReady, waitForLeadListReady, } from "./tools/readiness.js";
41
43
  import { refreshSenderEngagementCommand } from "./tools/refresh-sender-engagement.js";
@@ -146,6 +148,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
146
148
  case "wait_for_cli_login":
147
149
  result = await handleWaitForCliLogin(args);
148
150
  break;
151
+ case "bootstrap_find_leads":
152
+ result = await bootstrapFindLeads(args);
153
+ break;
154
+ case "get_find_leads_run":
155
+ result = await getFindLeadsRun(args);
156
+ break;
157
+ case "update_find_leads_run":
158
+ result = await updateFindLeadsRun(args);
159
+ break;
160
+ case "wait_for_find_leads_run":
161
+ result = await waitForFindLeadsRun(args);
162
+ break;
163
+ case "cancel_find_leads":
164
+ result = await cancelFindLeads(args);
165
+ break;
166
+ case "reissue_find_leads_watch_link":
167
+ result = await reissueFindLeadsWatchLink(args);
168
+ break;
169
+ case "preflight_find_leads_provider":
170
+ result = await preflightFindLeadsProvider(args);
171
+ break;
149
172
  case "bootstrap_engage":
150
173
  result = await bootstrapEngage(args);
151
174
  break;
@@ -11,11 +11,6 @@ export type AuthStatus = {
11
11
  tokenPresent: boolean;
12
12
  tokenPrefix: string | null;
13
13
  workspacesCount: number | null;
14
- workspaceLock: {
15
- enabled: boolean;
16
- workspaceId: string | null;
17
- required: boolean;
18
- };
19
14
  checkedAt: string;
20
15
  /** Short message the LLM MUST show the user after auth succeeds. */
21
16
  _userNotice: string | null;