@sellable/mcp 0.1.529 → 0.1.530

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.
package/dist/index-dev.js CHANGED
File without changes
package/dist/index.js CHANGED
File without changes
@@ -124,7 +124,7 @@ export async function getAuthStatus() {
124
124
  " 3. Click it, come back here, and we'll keep going\\n\\n" +
125
125
  "What email should I use?` " +
126
126
  "2) Wait for the user to type their email in normal chat (do NOT use AskUserQuestion / request_user_input). " +
127
- "3) Call `mcp__sellable__start_cli_login({ email })` with that email in Claude Code/Codex, or `mcp_sellable_start_cli_login({ email })` in Hermes. " +
127
+ "3) Call `mcp__sellable__start_cli_login({ email })` with that email. " +
128
128
  "4) On `ok: true`, say verbatim (substituting the email exactly as typed):\\n" +
129
129
  "`Magic link sent to {email}.\\n\\n" +
130
130
  "─────────────────────────────────────────────\\n" +
@@ -135,8 +135,8 @@ export async function getAuthStatus() {
135
135
  " 3. Come back here when you're done\\n\\n" +
136
136
  "I'll be waiting right here.\\n\\n" +
137
137
  " (If your team already uses Sellable, ask an admin to invite you into their shared workspace instead — that gets you straight in.)` " +
138
- "5) Call `mcp__sellable__wait_for_cli_login({ sessionId })` in Claude Code/Codex, or `mcp_sellable_wait_for_cli_login({ sessionId })` in Hermes, using the sessionId returned by start_cli_login. " +
139
- "6) If the result is `error.type === 'tool_timeout_guard'`, IMMEDIATELY re-call wait_for_cli_login with the SAME sessionId — do not narrate, do not call start_cli_login again. In Hermes, re-call `mcp_sellable_wait_for_cli_login({ sessionId })`. Loop until you get a different result. " +
138
+ "5) Call `mcp__sellable__wait_for_cli_login({ sessionId })` using the sessionId returned by start_cli_login. " +
139
+ "6) If the result is `error.type === 'tool_timeout_guard'`, IMMEDIATELY re-call wait_for_cli_login with the SAME sessionId — do not narrate, do not call start_cli_login again. Loop until you get a different result. " +
140
140
  "7) On `ok: true`, the user is signed in and `~/.sellable/config.json` has been written. Branch on `isReturningUser` and use `activeWorkspaceName` when present, otherwise `activeWorkspaceId`, as `{workspaceLabel}`: " +
141
141
  "if true, say `You're in {workspaceLabel}.\\n\\nExcited to help you launch your LinkedIn outbound campaign. We're at setup: first I'll use your LinkedIn profile to understand the company, then I'll draft the campaign brief, help choose where to find buyers, review messages, and wait for final launch approval.\\n\\nWhat's your LinkedIn profile URL or handle?`; " +
142
142
  "if false, say `You're set up in {workspaceLabel}.\\n\\nExcited to help you launch your LinkedIn outbound campaign. We're at setup: first I'll use your LinkedIn profile to understand the company, then I'll draft the campaign brief, help choose where to find buyers, review messages, and wait for final launch approval.\\n\\nWhat's your LinkedIn profile URL or handle?`";
@@ -307,7 +307,7 @@ export async function bootstrapCreateCampaign(input = {}) {
307
307
  ? resumeDetected
308
308
  ? `Bootstrap complete.${workspaceNotice}${modelNotice} Resume from campaign state and navigation diagnostics first; treat local draft artifacts as debug-only evidence. Then load ${createCampaignSubskill?.name ?? "create-campaign"} instructions with get_subskill_prompt({ subskillName: "${createCampaignSubskill?.name ?? "create-campaign"}" }); if the response has hasMore=true, continue with nextOffset until hasMore=false.`
309
309
  : flowVersion === "v2"
310
- ? `Bootstrap complete.${workspaceNotice}${modelNotice} Load the compact create-campaign-v2 entry prompt once with get_subskill_prompt({ subskillName: "create-campaign-v2" }); Hermes users should start this flow with /sellable-create-campaign. Load flow/reference assets lazily only when that stage needs them. Preserve the pre-intake sequence: confirm auth/workspace status, ask only for the LinkedIn profile URL or handle, normalize handles to a full profile URL, require that profile identity before continuing, run lightweight profile/company lookup, then ask the target, offer, credibility, and prospect-source setup questions. Do not call list_senders or sender discovery during setup; sender availability belongs only to Settings after message approval. Then write the campaign brief, call create_campaign once to mint the watchable shell, surface the returned watch link once before brief approval, and hand off to lead finding without repeating the link.`
310
+ ? `Bootstrap complete.${workspaceNotice}${modelNotice} Load the compact create-campaign-v2 entry prompt once with get_subskill_prompt({ subskillName: "create-campaign-v2" }); load flow/reference assets lazily only when that stage needs them. Preserve the pre-intake sequence: confirm auth/workspace status, ask only for the LinkedIn profile URL or handle, normalize handles to a full profile URL, require that profile identity before continuing, run lightweight profile/company lookup, then ask the target, offer, credibility, and prospect-source setup questions. Do not call list_senders or sender discovery during setup; sender availability belongs only to Settings after message approval. Then write the campaign brief, call create_campaign once to mint the watchable shell, surface the returned watch link once before brief approval, and hand off to lead finding without repeating the link.`
311
311
  : `Bootstrap complete.${workspaceNotice}${modelNotice} Load ${createCampaignSubskill?.name ?? "create-campaign"} instructions with get_subskill_prompt({ subskillName: "${createCampaignSubskill?.name ?? "create-campaign"}" }); if the response has hasMore=true, continue with nextOffset until hasMore=false. Follow that flow before calling create_campaign.`
312
312
  : "Bootstrap incomplete. Resolve blockingErrors and rerun bootstrap_create_campaign before provider/search/import tools.";
313
313
  // Strip prompt body from createCampaignSubskill — it's loaded via the host
@@ -9,9 +9,9 @@ declare const LEAD_SOURCE_PROVIDERS: {
9
9
  };
10
10
  type LeadSourceProvider = (typeof LEAD_SOURCE_PROVIDERS)[keyof typeof LEAD_SOURCE_PROVIDERS];
11
11
  export declare function buildWatchUrl(config: Pick<ReturnType<typeof getConfig>, "apiUrl" | "token" | "activeWorkspaceId" | "workspaceId">, path: string): string;
12
- export type CampaignBuilderWatchMode = "claude" | "codex" | "hermes";
12
+ export type CampaignBuilderWatchMode = "claude" | "codex";
13
13
  export declare function getCampaignBuilderWatchModeParam(): CampaignBuilderWatchMode;
14
- export declare function getCampaignBuilderWatchModeDriverLabel(mode?: CampaignBuilderWatchMode): "Claude Code" | "Codex" | "Hermes";
14
+ export declare function getCampaignBuilderWatchModeDriverLabel(mode?: CampaignBuilderWatchMode): "Claude Code" | "Codex";
15
15
  export declare function buildCampaignWatchHandoffMarkdown(watchUrl: string, mode?: CampaignBuilderWatchMode): string;
16
16
  export interface Campaign {
17
17
  id: string;
@@ -124,35 +124,23 @@ export function buildWatchUrl(config, path) {
124
124
  }
125
125
  return url.toString();
126
126
  }
127
- const CAMPAIGN_BUILDER_AGENT_WATCH_MODES = new Set([
128
- "claude",
129
- "codex",
130
- "hermes",
131
- ]);
132
127
  export function getCampaignBuilderWatchModeParam() {
133
128
  const explicit = process.env.SELLABLE_WATCH_MODE_DRIVER?.trim().toLowerCase();
134
- if (CAMPAIGN_BUILDER_AGENT_WATCH_MODES.has(explicit)) {
129
+ if (explicit === "claude" || explicit === "codex")
135
130
  return explicit;
136
- }
137
131
  return process.env.CODEX_HOME ? "codex" : "claude";
138
132
  }
139
133
  function getCampaignBuilderWatchModeFromUrl(watchUrl) {
140
134
  try {
141
135
  const mode = new URL(watchUrl).searchParams.get("mode");
142
- return CAMPAIGN_BUILDER_AGENT_WATCH_MODES.has(mode)
143
- ? mode
144
- : null;
136
+ return mode === "claude" || mode === "codex" ? mode : null;
145
137
  }
146
138
  catch {
147
139
  return null;
148
140
  }
149
141
  }
150
142
  export function getCampaignBuilderWatchModeDriverLabel(mode = getCampaignBuilderWatchModeParam()) {
151
- if (mode === "codex")
152
- return "Codex";
153
- if (mode === "hermes")
154
- return "Hermes";
155
- return "Claude Code";
143
+ return mode === "codex" ? "Codex" : "Claude Code";
156
144
  }
157
145
  export function buildCampaignWatchHandoffMarkdown(watchUrl, mode = getCampaignBuilderWatchModeFromUrl(watchUrl) ?? getCampaignBuilderWatchModeParam()) {
158
146
  const driverLabel = getCampaignBuilderWatchModeDriverLabel(mode);
@@ -173,7 +161,7 @@ function isValidBriefHandoffWatchUrl(watchUrl, campaignId) {
173
161
  const url = new URL(watchUrl);
174
162
  const mode = url.searchParams.get("mode");
175
163
  return (url.pathname === `/campaign-builder/${campaignId}` &&
176
- CAMPAIGN_BUILDER_AGENT_WATCH_MODES.has(mode) &&
164
+ (mode === "claude" || mode === "codex") &&
177
165
  Boolean(url.searchParams.get("workspaceId")) &&
178
166
  Boolean(url.searchParams.get("token")));
179
167
  }
@@ -185,7 +173,7 @@ function assertBriefHandoffWatchUrl(watchUrl, campaignId) {
185
173
  if (isValidBriefHandoffWatchUrl(watchUrl, campaignId))
186
174
  return;
187
175
  throw new Error("create_campaign produced an invalid watchUrl for the brief approval handoff. " +
188
- "Recover a fresh direct /campaign-builder/{campaignId}?mode={claude|codex|hermes}&workspaceId=...&token=... URL " +
176
+ "Recover a fresh direct /campaign-builder/{campaignId}?mode={claude|codex}&workspaceId=...&token=... URL " +
189
177
  "with create_campaign({ campaignId }) or get_campaign before asking for approval.");
190
178
  }
191
179
  export const campaignToolDefinitions = [
@@ -1316,14 +1316,6 @@ function normalizeImportProvider(provider) {
1316
1316
  return "signal-discovery";
1317
1317
  return undefined;
1318
1318
  }
1319
- function inferImportProviderFromLeadListConfig(config) {
1320
- const provider = normalizeImportProvider(config?.importProvider);
1321
- if (provider)
1322
- return provider;
1323
- if (config?.type === "signal_lead_list")
1324
- return "signal-discovery";
1325
- return undefined;
1326
- }
1327
1319
  function assertPhase126ImportProvider(provider, campaignOfferId) {
1328
1320
  if (!provider) {
1329
1321
  throw new Error(`import_leads requires a supported leadSourceProvider for campaign ${campaignOfferId}. Provide provider or set campaign.leadSourceProvider to sales-nav, prospeo, or signal-discovery, then call get_provider_prompt({ provider, campaignOfferId }).`);
@@ -4210,8 +4202,7 @@ export async function confirmLeadList(input) {
4210
4202
  });
4211
4203
  let resolvedLeadListId = sourceLeadListId;
4212
4204
  let resolvedProvider;
4213
- const isSameSourceRefillCopy = currentStep === null && Array.isArray(sourceRowIds) && sourceRowIds.length > 0;
4214
- if (!resolvedLeadListId || currentStep === undefined || !isSameSourceRefillCopy) {
4205
+ if (!resolvedLeadListId || currentStep === undefined) {
4215
4206
  const campaign = requestOptions
4216
4207
  ? await api.get(`/api/v2/campaign-offers/${campaignOfferId}`, requestOptions)
4217
4208
  : await api.get(`/api/v2/campaign-offers/${campaignOfferId}`);
@@ -4267,7 +4258,7 @@ export async function confirmLeadList(input) {
4267
4258
  : await api.get(`/api/v3/workflow-tables/${resolvedLeadListId}?mode=meta`);
4268
4259
  const leadListConfig = leadListMeta.table?.config ?? null;
4269
4260
  if (!resolvedProvider) {
4270
- resolvedProvider = inferImportProviderFromLeadListConfig(leadListConfig);
4261
+ resolvedProvider = normalizeImportProvider(leadListConfig?.importProvider);
4271
4262
  }
4272
4263
  const leadListRowCount = leadListMeta.rowCount ?? 0;
4273
4264
  const importProgress = leadListConfig?.importProgress ?? null;
@@ -1,4 +1,4 @@
1
- export type CampaignModelHost = "claude" | "codex" | "hermes" | "unknown";
1
+ export type CampaignModelHost = "claude" | "codex" | "unknown";
2
2
  export type CampaignModelQualityInput = {
3
3
  host?: string | null;
4
4
  model?: string | null;
@@ -33,7 +33,6 @@ export type CampaignModelQualityConfig = {
33
33
  hosts: {
34
34
  claude: CampaignModelQualityHostConfig;
35
35
  codex: CampaignModelQualityHostConfig;
36
- hermes: CampaignModelQualityHostConfig;
37
36
  };
38
37
  warningCopy: {
39
38
  ok: string;
@@ -30,20 +30,6 @@ const DEFAULT_MODEL_QUALITY_CONFIG = {
30
30
  ],
31
31
  recommendedReasoningEffort: "xhigh",
32
32
  },
33
- hermes: {
34
- label: "Hermes",
35
- minimumModel: "GPT 5.5",
36
- familyKeywords: ["gpt"],
37
- minimumVersion: "5.5",
38
- minimumReasoningEffort: "xhigh",
39
- acceptedReasoningEfforts: [
40
- "extra high",
41
- "extra-high",
42
- "xhigh",
43
- "extra_high",
44
- ],
45
- recommendedReasoningEffort: "xhigh",
46
- },
47
33
  },
48
34
  warningCopy: {
49
35
  ok: "Active host model metadata meets the configured campaign floor: {currentSettings}.",
@@ -55,8 +41,6 @@ const TRUSTED_METADATA_SOURCE_KEYWORDS = [
55
41
  "codex_turn_metadata",
56
42
  "claude_runtime_metadata",
57
43
  "claude_session_context",
58
- "hermes_runtime_metadata",
59
- "hermes_session_context",
60
44
  "active_turn_metadata",
61
45
  "user_confirmed",
62
46
  ];
@@ -65,9 +49,6 @@ const normalize = (value) => String(value ?? "")
65
49
  .toLowerCase();
66
50
  const normalizeHost = (host) => {
67
51
  const normalized = normalize(host);
68
- if (normalized.includes("hermes")) {
69
- return "hermes";
70
- }
71
52
  if (normalized.includes("claude") ||
72
53
  normalized.includes("opus") ||
73
54
  normalized.includes("sonnet") ||
@@ -133,7 +114,6 @@ function findHostConfig(host, model, config) {
133
114
  ? [
134
115
  ["claude", config.hosts.claude],
135
116
  ["codex", config.hosts.codex],
136
- ["hermes", config.hosts.hermes],
137
117
  ]
138
118
  : [[host, config.hosts[host]]];
139
119
  return candidates.find(([, hostConfig]) => modelMeetsMinimum(model, hostConfig, {
@@ -150,7 +130,7 @@ export function evaluateCampaignModelQuality(input = {}) {
150
130
  const model = input.model?.trim() || null;
151
131
  const reasoningEffort = input.reasoningEffort?.trim() || null;
152
132
  const metadataSource = input.metadataSource?.trim() || null;
153
- const recommendationHost = host === "claude" || host === "hermes" ? host : "codex";
133
+ const recommendationHost = host === "claude" ? "claude" : "codex";
154
134
  const recommendedHostConfig = config.hosts[recommendationHost];
155
135
  const minimumSummary = getCampaignModelMinimumSummary(config);
156
136
  const currentSettings = [
@@ -55,24 +55,6 @@ const SOURCE_PLAN_STATES = new Set([
55
55
  "provider_missing",
56
56
  "blocked",
57
57
  ]);
58
- const PREP_FAILURE_STATUSES = new Set(["ok", "zero_yield", "low_yield"]);
59
- const PREP_FAILURE_STAGES = new Set([
60
- "enrichment_fetch_failure",
61
- "icp_or_rubric_rejection",
62
- "message_generation_failure",
63
- "dependency_blocked",
64
- "approval_blocked",
65
- "mixed_or_unknown",
66
- ]);
67
- const PREP_FAILURE_RECOMMENDED_ACTIONS = new Set([
68
- "skip_errored_rows_and_continue_frontier",
69
- "retry_repairable_cells",
70
- "review_rubric_or_source_quality",
71
- "repair_message_generation",
72
- "wait_for_dependencies",
73
- "approve_or_repair_approval_cells",
74
- "inspect_rows",
75
- ]);
76
58
  async function postRefillTargetPlan(body, workspaceId) {
77
59
  const api = getApi();
78
60
  const requestOptions = workspaceRequestOptions(workspaceId);
@@ -129,38 +111,6 @@ function stringArray(value) {
129
111
  return [];
130
112
  return value.filter((item) => typeof item === "string");
131
113
  }
132
- function sanitizePrepFailureDiagnosis(value) {
133
- if (!isRecord(value))
134
- return undefined;
135
- const status = stringValue(value.status);
136
- const stage = stringValue(value.stage);
137
- const recommendedAction = stringValue(value.recommendedAction);
138
- if (!status ||
139
- !PREP_FAILURE_STATUSES.has(status) ||
140
- !stage ||
141
- !PREP_FAILURE_STAGES.has(stage) ||
142
- !recommendedAction ||
143
- !PREP_FAILURE_RECOMMENDED_ACTIONS.has(recommendedAction)) {
144
- return undefined;
145
- }
146
- const evidence = isRecord(value.evidence) ? value.evidence : {};
147
- return {
148
- status,
149
- stage,
150
- retryable: booleanValue(value.retryable) ?? false,
151
- recommendedAction,
152
- evidence: {
153
- checkedRows: numberValue(evidence.checkedRows),
154
- preparedRows: numberValue(evidence.preparedRows),
155
- affectedRows: numberValue(evidence.affectedRows),
156
- sampleRowIds: stringArray(evidence.sampleRowIds).slice(0, 10),
157
- counts: numericRecord(evidence.counts),
158
- dominantReason: stringValue(evidence.dominantReason) ?? null,
159
- dominantDetail: stringValue(evidence.dominantDetail) ?? null,
160
- httpStatus: stringValue(evidence.httpStatus) ?? null,
161
- },
162
- };
163
- }
164
114
  function evidenceLatestMs(evidence) {
165
115
  const latestAt = evidence.latestAt;
166
116
  if (typeof latestAt !== "string")
@@ -351,7 +301,6 @@ function sanitizeExistingRowFrontier(value) {
351
301
  stuckActiveCells: sanitizeStuckActiveCells(value.stuckActiveCells),
352
302
  approvedNotDispatched: sanitizeApprovedNotDispatched(value.approvedNotDispatched),
353
303
  wait: sanitizeWait(value.wait),
354
- failureDiagnosis: sanitizePrepFailureDiagnosis(value.failureDiagnosis),
355
304
  };
356
305
  }
357
306
  function sanitizeSkippedRungs(value) {
@@ -464,7 +413,6 @@ function sanitizeToolInput(value) {
464
413
  operations: sanitizeRerunErroredCellsOperations(value.operations),
465
414
  wait: sanitizeWait(value.wait),
466
415
  refillReceipt: sanitizeRefillReceipt(value.refillReceipt),
467
- capSaturated: booleanValue(value.capSaturated) === true ? true : undefined,
468
416
  };
469
417
  }
470
418
  function sanitizePacketIds(value) {
@@ -515,8 +463,6 @@ function sanitizeStructuredAction(action, selectedBySender, mode, rank) {
515
463
  : null,
516
464
  stopCondition: stringValue(action.stopCondition) ?? "",
517
465
  yoloEligible,
518
- capSaturated: booleanValue(action.capSaturated) === true ? true : undefined,
519
- prepFailureDiagnosis: sanitizePrepFailureDiagnosis(action.prepFailureDiagnosis),
520
466
  };
521
467
  }
522
468
  function sanitizeLedgerBlockers(value) {
@@ -605,11 +551,9 @@ function sanitizeSourcePlan(value) {
605
551
  sourceLeadListId: stringValue(value.sourceLeadListId) ?? null,
606
552
  leadSourceProvider: stringValue(value.leadSourceProvider) ?? null,
607
553
  sourceFingerprint: stringValue(value.sourceFingerprint) ?? null,
608
- capSaturated: booleanValue(value.capSaturated) === true ? true : undefined,
609
554
  remainingRowsNeeded: typeof value.remainingRowsNeeded === "number"
610
555
  ? value.remainingRowsNeeded
611
556
  : undefined,
612
- prepFailureDiagnosis: sanitizePrepFailureDiagnosis(value.prepFailureDiagnosis),
613
557
  };
614
558
  }
615
559
  function sanitizePaidInmail(value) {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.529",
3
+ "version": "0.1.530",
4
4
  "type": "module",
5
- "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
5
+ "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "mcp": "dist/index.js",
@@ -20,7 +20,6 @@
20
20
  "mcp",
21
21
  "claude-code",
22
22
  "codex",
23
- "hermes",
24
23
  "sellable",
25
24
  "linkedin",
26
25
  "outreach"
@@ -1,28 +0,0 @@
1
- type RefillSendsEvergreenInput = {
2
- workspaceId?: string;
3
- };
4
- export declare const refillSendsEvergreenToolDefinitions: {
5
- name: string;
6
- description: string;
7
- inputSchema: {
8
- type: string;
9
- properties: {
10
- workspaceId: {
11
- type: string;
12
- description: string;
13
- };
14
- };
15
- required: string[];
16
- additionalProperties: boolean;
17
- };
18
- }[];
19
- export declare function refillSendsEvergreenCommand(input: RefillSendsEvergreenInput): {
20
- readOnly: boolean;
21
- workspaceId: string | null;
22
- firstOperationalSteps: string[];
23
- approvalContract: string;
24
- forbiddenActions: string[];
25
- fillWindow: string;
26
- hostExamples: string[];
27
- };
28
- export {};
@@ -1,47 +0,0 @@
1
- export const refillSendsEvergreenToolDefinitions = [
2
- {
3
- name: "refill_sends_evergreen",
4
- description: "Read-only Phase 85 evergreen refill command contract. It performs no mutations and only tells the operator to call get_evergreen_refill_plan for a dry-run packet and journal.",
5
- inputSchema: {
6
- type: "object",
7
- properties: {
8
- workspaceId: {
9
- type: "string",
10
- description: "Explicit request-scoped workspace id.",
11
- },
12
- },
13
- required: ["workspaceId"],
14
- additionalProperties: false,
15
- },
16
- },
17
- ];
18
- export function refillSendsEvergreenCommand(input) {
19
- return {
20
- readOnly: true,
21
- workspaceId: input.workspaceId ?? null,
22
- firstOperationalSteps: [
23
- "Call get_evergreen_refill_plan with the explicit workspaceId.",
24
- "Read the returned packet, globalActionQueue, per-sender plans, and itinerary before taking any action.",
25
- "Review the dry-run journal file path returned by get_evergreen_refill_plan.",
26
- "Phase 85 is PLAN-ONLY; execution arrives in Phase 86.",
27
- ],
28
- approvalContract: "Nothing is approved or executable in Phase 85. The evergreen command is read-only; Phase 86 introduces execution approval.",
29
- forbiddenActions: [
30
- "Do not schedule sends.",
31
- "Do not send messages.",
32
- "Do not approve messages.",
33
- "Do not prepare messages.",
34
- "Do not start or launch campaigns.",
35
- "Do not create campaigns.",
36
- "Do not switch providers or source families.",
37
- "Do not lower paid InMail thresholds.",
38
- "Do not refresh paid InMail credits.",
39
- "Do not write scheduler fields.",
40
- ],
41
- fillWindow: "Use only the target window and caps returned by get_evergreen_refill_plan.",
42
- hostExamples: [
43
- "refill_sends_evergreen({ workspaceId })",
44
- "get_evergreen_refill_plan({ workspaceId })",
45
- ],
46
- };
47
- }
@@ -1,247 +0,0 @@
1
- {
2
- "version": "v1.0",
3
- "workflow": "refill-sends-evergreen-workflow",
4
- "principle": "Plan evergreen waterfall refill as a read-only dry-run with zero mutations.",
5
- "enumeratedRungs": [
6
- "start_campaign",
7
- "approve_messages",
8
- "wait_for_active_work",
9
- "enrich_more",
10
- "reconcile_source_copy",
11
- "add_leads_same_source",
12
- "paid_lane_fallback",
13
- "next_campaign",
14
- "wait_for_scheduler",
15
- "done"
16
- ],
17
- "requiredBootstrap": [
18
- {
19
- "tool": "get_auth_status",
20
- "purpose": "Confirm MCP auth before reading workspace or plan facts."
21
- },
22
- {
23
- "tool": "get_active_workspace",
24
- "purpose": "Resolve explicit workspace evidence when the request did not provide workspaceId."
25
- },
26
- {
27
- "tool": "refill_sends_evergreen",
28
- "requiredValues": {
29
- "workspaceId": "{workspaceId}"
30
- },
31
- "requiredOutput": [
32
- "readOnly true",
33
- "approvalContract says Phase 85 is plan-only"
34
- ]
35
- },
36
- {
37
- "tool": "get_subskill_prompt",
38
- "requiredValues": {
39
- "subskillName": "refill-sends-evergreen-workflow"
40
- },
41
- "mustReadUntil": {
42
- "hasMore": false
43
- }
44
- },
45
- {
46
- "tool": "get_subskill_asset",
47
- "requiredValues": {
48
- "subskillName": "refill-sends-evergreen-workflow",
49
- "assetPath": "core/flow.v1.json"
50
- },
51
- "mustReadUntil": {
52
- "hasMore": false
53
- },
54
- "validation": {
55
- "workflow": "refill-sends-evergreen-workflow",
56
- "versionPrefix": "v1",
57
- "requiredTopLevelKeys": [
58
- "states",
59
- "gateOrder",
60
- "enumeratedRungs",
61
- "verification"
62
- ]
63
- }
64
- },
65
- {
66
- "tool": "get_evergreen_refill_plan",
67
- "requiredValues": {
68
- "workspaceId": "{workspaceId}"
69
- },
70
- "requiredOutput": [
71
- "planRevision",
72
- "stateRevision",
73
- "packet",
74
- "journalPath",
75
- "sideEffects"
76
- ]
77
- }
78
- ],
79
- "gateOrder": ["bootstrap", "plan", "report"],
80
- "states": [
81
- {
82
- "id": "bootstrap",
83
- "description": "Load the read-only command contract, workflow prompt, flow asset, and workspace facts.",
84
- "allowedTools": [
85
- "get_auth_status",
86
- "get_active_workspace",
87
- "list_workspaces",
88
- "get_workspace",
89
- "refill_sends_evergreen",
90
- "get_subskill_prompt",
91
- "get_subskill_asset",
92
- "get_evergreen_refill_plan"
93
- ],
94
- "onEnter": [
95
- {
96
- "tool": "get_auth_status"
97
- },
98
- {
99
- "tool": "refill_sends_evergreen",
100
- "requiredFields": ["workspaceId"]
101
- },
102
- {
103
- "tool": "get_subskill_prompt",
104
- "requiredValues": {
105
- "subskillName": "refill-sends-evergreen-workflow"
106
- }
107
- },
108
- {
109
- "tool": "get_subskill_asset",
110
- "requiredValues": {
111
- "subskillName": "refill-sends-evergreen-workflow",
112
- "assetPath": "core/flow.v1.json"
113
- }
114
- }
115
- ],
116
- "transitions": {
117
- "contract_loaded": "plan"
118
- }
119
- },
120
- {
121
- "id": "plan",
122
- "description": "Call the dry-run evergreen refill planner exactly once for this invocation.",
123
- "allowedTools": ["get_evergreen_refill_plan"],
124
- "onEnter": [
125
- {
126
- "tool": "get_evergreen_refill_plan",
127
- "requiredFields": ["workspaceId"],
128
- "optionalFields": ["senderIds", "runState", "journal", "journalNote"]
129
- }
130
- ],
131
- "requiredOutput": [
132
- "sideEffects all false",
133
- "planRevision starts with pr1:",
134
- "journalPath reported",
135
- "chosen evergreenRung is in enumeratedRungs"
136
- ],
137
- "allowedRungsSource": "enumeratedRungs",
138
- "doNotAllow": [
139
- "add_column",
140
- "add_on_demand_leads",
141
- "add_rubric_item",
142
- "add_teammate",
143
- "add_to_comment_campaign",
144
- "add_to_connection_campaign",
145
- "add_to_inmail_campaign",
146
- "archive_campaign",
147
- "attach_recommended_sequence",
148
- "attach_sequence",
149
- "bulk_enrich_with_prospeo",
150
- "cancel_campaign_message_preparation",
151
- "cancel_lead_import",
152
- "capture_post_idea",
153
- "check_rubric",
154
- "commit_blueprint",
155
- "confirm_harvest_job_companies",
156
- "confirm_lead_list",
157
- "confirm_prospeo_company_accounts",
158
- "copy_sender_config",
159
- "create_campaign",
160
- "create_on_demand_campaign",
161
- "create_on_demand_table",
162
- "create_workspace",
163
- "create_workflow_table",
164
- "delete_column",
165
- "delete_rubric_item",
166
- "duplicate_campaign",
167
- "enrich_with_prospeo",
168
- "fill_campaign_horizon",
169
- "get_or_create_direct_campaign_table",
170
- "import_leads",
171
- "load_csv_dnc_entries",
172
- "load_csv_domains",
173
- "load_csv_linkedin_leads",
174
- "mark_post_published",
175
- "migrate_flat_configs",
176
- "pause_campaign",
177
- "pause_direct_campaign",
178
- "pause_on_demand_campaign",
179
- "prepare_campaign_ab_test",
180
- "queue_campaign_cells",
181
- "queue_cells",
182
- "record_campaign_review_batch",
183
- "record_engage_proven_search",
184
- "refill_sends",
185
- "refresh_paid_inmail_credits",
186
- "revise_message_template_and_rerun",
187
- "save_domain_filters",
188
- "save_hook_research",
189
- "save_post_draft",
190
- "save_rubrics",
191
- "send_inbox_draft",
192
- "send_inbox_manual_reply",
193
- "set_active_workspace",
194
- "set_campaign_waterfall_order",
195
- "set_company_info",
196
- "set_engage_state",
197
- "set_engage_style_guide",
198
- "set_headline_icp_criteria",
199
- "set_sender_routing",
200
- "setup_evergreen_campaigns",
201
- "start_campaign",
202
- "start_campaign_message_preparation",
203
- "start_direct_campaign",
204
- "start_on_demand_campaign",
205
- "update_campaign",
206
- "update_campaign_brief",
207
- "update_cell",
208
- "update_column",
209
- "update_inbox_draft",
210
- "update_post_draft",
211
- "update_published_post_metrics",
212
- "update_rubric_item",
213
- "upsert_engage_tracked_person",
214
- "upsert_rubric"
215
- ],
216
- "transitions": {
217
- "packet_returned": "report"
218
- }
219
- },
220
- {
221
- "id": "report",
222
- "description": "Present packet, itinerary, warnings, blockers, plan revision, and journal path; then stop.",
223
- "allowedTools": [],
224
- "onEnter": [
225
- {
226
- "action": "present_packet_itinerary_and_journal_path"
227
- }
228
- ],
229
- "terminal": true
230
- }
231
- ],
232
- "verification": {
233
- "requiredProof": [
234
- "sideEffects all false in get_evergreen_refill_plan response",
235
- "planRevision has pr1: prefix",
236
- "journalPath reported or journalWriteFailed warning surfaced",
237
- "chosen evergreenRung belongs to enumeratedRungs",
238
- "packet reviewed with itinerary and blockers"
239
- ],
240
- "notCompletionProof": [
241
- "called get_evergreen_refill_plan alone",
242
- "action queue exists",
243
- "ready rows alone",
244
- "operator wants execution"
245
- ]
246
- }
247
- }