@sellable/mcp 0.1.643 → 0.1.645-wip.refill-zero-shot.20260721153930

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.
@@ -965,8 +965,8 @@ export declare function executeRefillSendsCommand(input?: RefillSendsCommandInpu
965
965
  } | {
966
966
  ok: boolean;
967
967
  status: "blocked";
968
- code: "workspace_exact_target_unavailable" | "workspace_exact_target_ambiguous" | "exact_target_config_unavailable" | "exact_target_config_mismatch" | "exact_target_projection_mismatch";
969
- blocker: "workspace_exact_target_unavailable" | "workspace_exact_target_ambiguous" | "exact_target_config_unavailable" | "exact_target_config_mismatch" | "exact_target_projection_mismatch";
968
+ code: "workspace_exact_target_unavailable" | "workspace_exact_target_contract_missing" | "exact_target_config_unavailable" | "exact_target_config_mismatch" | "exact_target_projection_mismatch";
969
+ blocker: "workspace_exact_target_unavailable" | "workspace_exact_target_contract_missing" | "exact_target_config_unavailable" | "exact_target_config_mismatch" | "exact_target_projection_mismatch";
970
970
  note: string;
971
971
  targetPlan: unknown;
972
972
  exactTargetPlan: {} | null;
@@ -1454,8 +1454,8 @@ export declare function executeRefillSendsCommand(input?: RefillSendsCommandInpu
1454
1454
  } | {
1455
1455
  ok: boolean;
1456
1456
  status: "blocked";
1457
- code: "workspace_exact_target_unavailable" | "workspace_exact_target_ambiguous" | "exact_target_config_unavailable" | "exact_target_config_mismatch" | "exact_target_projection_mismatch";
1458
- blocker: "workspace_exact_target_unavailable" | "workspace_exact_target_ambiguous" | "exact_target_config_unavailable" | "exact_target_config_mismatch" | "exact_target_projection_mismatch";
1457
+ code: "workspace_exact_target_unavailable" | "workspace_exact_target_contract_missing" | "exact_target_config_unavailable" | "exact_target_config_mismatch" | "exact_target_projection_mismatch";
1458
+ blocker: "workspace_exact_target_unavailable" | "workspace_exact_target_contract_missing" | "exact_target_config_unavailable" | "exact_target_config_mismatch" | "exact_target_projection_mismatch";
1459
1459
  note: string;
1460
1460
  targetPlan: unknown;
1461
1461
  exactTargetPlan: {} | null;
@@ -1722,6 +1722,135 @@ export declare function executeRefillSendsCommand(input?: RefillSendsCommandInpu
1722
1722
  workspaceId: string | null;
1723
1723
  workspaceResolution: string;
1724
1724
  intent: RefillSendsIntent;
1725
+ } | {
1726
+ ok: boolean;
1727
+ status: "blocked";
1728
+ code: string;
1729
+ blocker: string;
1730
+ note: string;
1731
+ targetPlan: unknown;
1732
+ exactTargetPlan: unknown;
1733
+ coordinator: {
1734
+ scope: string;
1735
+ status: string;
1736
+ completedTarget: WorkspaceExactTarget | null;
1737
+ selectedTarget: WorkspaceExactTarget;
1738
+ exactRunResult: {
1739
+ runSnapshot: unknown;
1740
+ readOnly: boolean;
1741
+ forbiddenActions: string[];
1742
+ boundedAuthority: string;
1743
+ status: "terminal";
1744
+ doneReason: string;
1745
+ report: Record<string, unknown>;
1746
+ runId: string;
1747
+ guidance?: string;
1748
+ journalPath?: string | null;
1749
+ targetConfig?: RefillTargetConfigV1;
1750
+ runHandle?: RefillRunHandleV1;
1751
+ refillReporting?: Record<string, unknown>;
1752
+ blocker?: undefined;
1753
+ conflicts?: undefined;
1754
+ };
1755
+ workspaceTargetPlan?: undefined;
1756
+ exactTargetPlan?: undefined;
1757
+ note?: undefined;
1758
+ };
1759
+ continuation: {
1760
+ required: boolean;
1761
+ userActionRequired: boolean;
1762
+ kind?: undefined;
1763
+ owner?: undefined;
1764
+ tool?: undefined;
1765
+ arguments?: undefined;
1766
+ stopCondition?: undefined;
1767
+ };
1768
+ targetDate: string | null;
1769
+ untilDate: string | null;
1770
+ horizonSendDays: number | null;
1771
+ fillWindow: {
1772
+ mode: string;
1773
+ targetDate: string;
1774
+ description: string;
1775
+ untilDate?: undefined;
1776
+ horizonSendDays?: undefined;
1777
+ horizonHours?: undefined;
1778
+ } | {
1779
+ mode: string;
1780
+ untilDate: string;
1781
+ description: string;
1782
+ targetDate?: undefined;
1783
+ horizonSendDays?: undefined;
1784
+ horizonHours?: undefined;
1785
+ } | {
1786
+ mode: string;
1787
+ horizonSendDays: number | null;
1788
+ horizonHours: number | null;
1789
+ description: string;
1790
+ targetDate?: undefined;
1791
+ untilDate?: undefined;
1792
+ };
1793
+ approvalMode: RefillSendsApprovalMode;
1794
+ campaignId: string | null;
1795
+ tableId: string | null;
1796
+ targetConfig: RefillTargetConfigV1 | null;
1797
+ runHandle: RefillRunHandleV1 | null;
1798
+ reportingContext: RefillReportingContextV2 | null;
1799
+ messageTemplateRevision: MessageTemplateRevisionV1 | null;
1800
+ senderScope: string;
1801
+ senderSelectors: {
1802
+ senders: string[];
1803
+ senderIds: string[];
1804
+ senderNames: string[];
1805
+ };
1806
+ firstOperationalSteps: string[];
1807
+ approvalContract: string;
1808
+ forbiddenActions: string[];
1809
+ hostExamples: {
1810
+ claude: string[];
1811
+ codex: string[];
1812
+ mcpTool: {
1813
+ name: string;
1814
+ arguments: {
1815
+ workspaceCoordinator?: boolean | undefined;
1816
+ messageTemplateRevision?: MessageTemplateRevisionV1 | undefined;
1817
+ reportingContext?: RefillReportingContextV2 | undefined;
1818
+ targetConfig?: RefillTargetConfigV1 | undefined;
1819
+ runHandle?: RefillRunHandleV1 | undefined;
1820
+ fence?: number | undefined;
1821
+ runId?: string | undefined;
1822
+ expectedActionKey?: string | undefined;
1823
+ expectedTargetShapeRevision?: string | undefined;
1824
+ targetDate: string | null;
1825
+ untilDate: string | null;
1826
+ horizonSendDays: number | null;
1827
+ campaignId: string | undefined;
1828
+ tableId: string | undefined;
1829
+ intent: RefillSendsIntent;
1830
+ approvalMode: RefillSendsApprovalMode;
1831
+ workspaceId: string | null;
1832
+ actionTypes?: ("send_invite" | "send_inmail_closed")[] | undefined;
1833
+ yolo: boolean;
1834
+ senders: string[];
1835
+ senderIds: string[];
1836
+ senderNames: string[];
1837
+ };
1838
+ };
1839
+ };
1840
+ actionTypes?: ("send_invite" | "send_inmail_closed")[] | undefined;
1841
+ command: string;
1842
+ tool: string;
1843
+ promptName: string;
1844
+ workflowPromptName: string;
1845
+ workflowAsset: {
1846
+ subskillName: string;
1847
+ assetPath: string;
1848
+ };
1849
+ yolo: boolean;
1850
+ executionMode: RefillSendsExecutionMode;
1851
+ workspaceId: string | null;
1852
+ workspaceResolution: string;
1853
+ intent: RefillSendsIntent;
1725
1854
  } | {
1726
1855
  status: "in_progress";
1727
1856
  targetPlan: unknown;
@@ -1,12 +1,17 @@
1
1
  import { decodeRefillReadinessAuthority, refillReadinessFingerprint, } from "../refill-readiness.js";
2
2
  import { MESSAGE_TEMPLATE_REVISION_INPUT_SCHEMA, REFILL_REPORTING_CONTEXT_INPUT_SCHEMA, REFILL_RUN_HANDLE_INPUT_SCHEMA, REFILL_TARGET_CONFIG_INPUT_SCHEMA, normalizeMessageTemplateRevision, normalizeRefillRunHandle, normalizeRefillTargetConfig, } from "./campaign-processing.js";
3
- import { actionCampaignId, actionIds, actionSenderId, actionTableId, classifyPaidInmailRefreshReceipt, collectPaidInmailRefreshActions, executeOneYoloPrimitive, executeStartCampaignPrimitive, firstGlobalAction, normalizeStrings, numberValue, recordValue, refreshPaidInmailCreditsWithRetry, stringValue, } from "./refill-executors.js";
3
+ import { actionCampaignId, actionIds, actionSenderId, classifyPaidInmailRefreshReceipt, collectPaidInmailRefreshActions, executeOneYoloPrimitive, executeStartCampaignPrimitive, firstGlobalAction, normalizeStrings, numberValue, recordValue, refreshPaidInmailCreditsWithRetry, stringValue, } from "./refill-executors.js";
4
4
  import { refillSendsV2Command } from "./refill-sends-v2.js";
5
5
  import { getRefillTargetPlan } from "./refill-target-plan.js";
6
6
  import { listSenders } from "./senders.js";
7
7
  import { createWorkspaceContext, normalizeExplicitWorkspaceId, } from "./workspace-context.js";
8
8
  const REFILL_SEND_ACTION_TYPES = ["send_invite", "send_inmail_closed"];
9
9
  const REFILL_SEND_ACTION_TYPE_SET = new Set(REFILL_SEND_ACTION_TYPES);
10
+ const WORKSPACE_EXACT_BRANCH_ACTION_TYPE_SET = new Set([
11
+ "send_invite",
12
+ "send_inmail_open",
13
+ "send_inmail_closed",
14
+ ]);
10
15
  const REFILL_SEND_INTENT_SET = new Set([
11
16
  "plain",
12
17
  "active",
@@ -133,7 +138,7 @@ function normalizeRefillSendsInput(input) {
133
138
  export const refillSendsToolDefinitions = [
134
139
  {
135
140
  name: "refill_sends",
136
- description: "Typed command entrypoint for Sellable refill sends. Workspace-wide --yolo reads globalActionQueue[0], preflights its exact campaign/table/sender/lane targetConfig, and executes one fenced checkpointed run slice before rereading workspace scope for the next exact target. An active_exact_run continuation carries targetConfig plus exact runId/fence; a next_exact_target continuation carries the next preflighted targetConfig and opens a new fence. When an exact run terminalizes as loaded_awaiting_scheduler and the fresh workspace queue reselects the same campaign/table/sender/lane, the coordinator returns that scheduler-owned workspace terminal instead of opening another identical fence. The packaged workflow must immediately invoke the returned continuation arguments until workspace projected coverage fills, the same-target scheduler terminal is returned, or a concrete blocker appears, without asking the user to continue. Every primitive records actionKey/requestId/effectId before mutation and rereads the fresh target plan afterward. It may refresh stale paid InMail facts once, prepare/approve bounded existing rows, reuse/import receipt-proven same-source rows, add more leads to an existing campaign from its SAME provider source once that campaign's eligible/prepared rows are receipt-proven exhausted (cold-outbound campaigns may source Sales Nav or Prospeo), start a route-resolver start-eligible PAUSED campaign-backed sequence campaign selected by the packet when starting is needed to fill the send window, run a request-scoped product scheduler sweep, and wait/reread. Each source add and campaign start is one bounded primitive, then a full replan. It never sends directly, lowers paid InMail thresholds/floors, creates NEW campaigns, archives/deletes, or switches an existing campaign's source family (same provider source only). Unselected PAUSED campaigns that are not start-eligible remain paused. Scheduler placement stays product-native; the command never raw-writes scheduler fields.",
141
+ description: "Typed command entrypoint for Sellable refill sends. Workspace-wide --yolo reads globalActionQueue[0], preflights its exact campaign/table/sender/lane targetConfig, and executes one fenced checkpointed run slice before rereading workspace scope for the next exact target. An active_exact_run continuation carries targetConfig plus exact runId/fence; a next_exact_target continuation carries the next preflighted targetConfig and opens a new fence. Exact target configs bind the issued semantic action and revisions. A terminal exact run may advance only when a fresh exact preflight selects a different semantic action; two fresh reads of the same terminal action fail closed instead of opening another identical fence. When an exact run terminalizes as loaded_awaiting_scheduler and the fresh workspace queue reselects the same campaign/table/sender/lane, the coordinator returns that scheduler-owned workspace terminal instead of opening another identical fence. The packaged workflow must immediately invoke the returned continuation arguments until workspace projected coverage fills, the same-target scheduler terminal is returned, or a concrete blocker appears, without asking the user to continue. Every primitive records actionKey/requestId/effectId before mutation and rereads the fresh target plan afterward. It may refresh stale paid InMail facts once, prepare/approve bounded existing rows, reuse/import receipt-proven same-source rows, add more leads to an existing campaign from its SAME provider source once that campaign's eligible/prepared rows are receipt-proven exhausted (cold-outbound campaigns may source Sales Nav or Prospeo), start a route-resolver start-eligible PAUSED campaign-backed sequence campaign selected by the packet when starting is needed to fill the send window, run a request-scoped product scheduler sweep, and wait/reread. Each source add and campaign start is one bounded primitive, then a full replan. It never sends directly, lowers paid InMail thresholds/floors, creates NEW campaigns, archives/deletes, or switches an existing campaign's source family (same provider source only). Unselected PAUSED campaigns that are not start-eligible remain paused. Scheduler placement stays product-native; the command never raw-writes scheduler fields.",
137
142
  inputSchema: {
138
143
  type: "object",
139
144
  properties: {
@@ -507,6 +512,11 @@ function sameWorkspaceExactTarget(left, right) {
507
512
  sameStringSet(left.senderIds, right.senderIds) &&
508
513
  sameStringSet(left.actionTypes, right.actionTypes));
509
514
  }
515
+ function sameWorkspaceExactAction(left, right) {
516
+ return (sameWorkspaceExactTarget(left, right) &&
517
+ Boolean(left?.actionKey) &&
518
+ left?.actionKey === right.actionKey);
519
+ }
510
520
  function workspaceCoordinatorInput(input) {
511
521
  const coordinatorInput = { ...input };
512
522
  delete coordinatorInput.campaignId;
@@ -520,118 +530,37 @@ function workspaceCoordinatorInput(input) {
520
530
  delete coordinatorInput.agentKeywords;
521
531
  return coordinatorInput;
522
532
  }
523
- function recordsWithExactTarget(value) {
524
- return Array.isArray(value)
525
- ? value
526
- .map(recordValue)
527
- .filter((candidate) => Boolean(candidate &&
528
- stringValue(candidate.campaignId) &&
529
- stringValue(candidate.tableId)))
530
- : [];
531
- }
532
- function workspaceExactActionSignature(action) {
533
- const campaignId = actionCampaignId(action);
534
- const tableId = actionTableId(action);
535
- const actionKey = stringValue(action.actionKey);
536
- const actionType = stringValue(action.actionType);
537
- if (!campaignId || !tableId || !actionKey || !actionType)
538
- return null;
539
- return JSON.stringify([campaignId, tableId, actionKey, actionType]);
540
- }
541
- function workspaceAggregateActionSenderIds(workspacePlan) {
542
- const target = yoloTarget(workspacePlan);
543
- const senderPlans = Array.isArray(target?.senderRefillPlans)
544
- ? target.senderRefillPlans
545
- .map(recordValue)
546
- .filter((plan) => Boolean(plan))
547
- : [];
548
- const senderIdsByAction = new Map();
549
- for (const senderPlan of senderPlans) {
550
- const senderId = stringValue(senderPlan.senderId);
551
- const nextActions = Array.isArray(senderPlan.nextActions)
552
- ? senderPlan.nextActions
553
- .map(recordValue)
554
- .filter((action) => Boolean(action))
555
- : [];
556
- if (!senderId)
557
- continue;
558
- for (const action of nextActions) {
559
- const signature = workspaceExactActionSignature(action);
560
- if (!signature)
561
- continue;
562
- const senderIds = senderIdsByAction.get(signature) ?? new Set();
563
- senderIds.add(senderId);
564
- senderIdsByAction.set(signature, senderIds);
565
- }
533
+ function workspaceExactTargetsFromAction(action) {
534
+ if (!Array.isArray(action.exactTargets) || action.exactTargets.length === 0) {
535
+ return [];
566
536
  }
567
- return new Map([...senderIdsByAction.entries()]
568
- .filter(([, senderIds]) => senderIds.size > 1)
569
- .map(([signature, senderIds]) => [
570
- signature,
571
- [...senderIds],
572
- ]));
573
- }
574
- function workspaceExactTargetsFromAction(action, aggregateSenderIdsByAction = new Map()) {
575
- const toolInput = recordValue(action.toolInput) ?? {};
576
- const exactTargets = recordsWithExactTarget(toolInput.exactTargets);
577
- const expectedTargets = recordsWithExactTarget(toolInput.expectedTargets);
578
- const sourceScopes = recordsWithExactTarget(toolInput.sourceScopes);
579
- const exactEdges = exactTargets.length > 0
580
- ? exactTargets
581
- : expectedTargets.length > 0
582
- ? expectedTargets
583
- : sourceScopes.length > 0
584
- ? sourceScopes
585
- : [null];
586
- const campaignIds = normalizeStrings(toolInput.campaignIds);
587
- const tableIds = normalizeStrings(toolInput.tableIds);
588
- return exactEdges.flatMap((exactEdge) => {
589
- const campaignId = stringValue(exactEdge?.campaignId) ??
590
- actionCampaignId(action) ??
591
- campaignIds[0];
592
- const tableId = stringValue(exactEdge?.tableId) ?? actionTableId(action) ?? tableIds[0];
593
- if (!campaignId || !tableId)
537
+ const targets = action.exactTargets.flatMap((value) => {
538
+ const exactTarget = recordValue(value);
539
+ const campaignId = stringValue(exactTarget?.campaignId);
540
+ const tableId = stringValue(exactTarget?.tableId);
541
+ const senderIds = normalizeStrings(exactTarget?.senderIds);
542
+ const refillLaneKey = stringValue(exactTarget?.refillLaneKey);
543
+ const branchActionTypes = normalizeStrings(exactTarget?.branchActionTypes);
544
+ if (!campaignId ||
545
+ !tableId ||
546
+ senderIds.length === 0 ||
547
+ !refillLaneKey ||
548
+ !REFILL_SEND_ACTION_TYPE_SET.has(refillLaneKey) ||
549
+ branchActionTypes.length === 0 ||
550
+ branchActionTypes.some((actionType) => !WORKSPACE_EXACT_BRANCH_ACTION_TYPE_SET.has(actionType))) {
594
551
  return [];
595
- const exactEdgeSenderIds = normalizeStrings(exactEdge?.senderIds);
596
- const exactEdgeSenderId = stringValue(exactEdge?.senderId);
597
- const toolInputSenderIds = normalizeStrings(toolInput.senderIds);
598
- const aggregateSenderIds = aggregateSenderIdsByAction.get(workspaceExactActionSignature(action) ?? "") ?? [];
599
- const actionSenderIdValue = actionSenderId(action);
600
- // Explicit edge/list scope is authoritative. Legacy shared campaign
601
- // actions predate expectedTargets and can repeat the same representative
602
- // sender action across several senderRefillPlans. Preserve that recovered
603
- // member set instead of narrowing the exact coordinator to the complete
604
- // representative sender and hiding another member's actionable gap.
605
- const senderIds = exactEdgeSenderIds.length > 0
606
- ? exactEdgeSenderIds
607
- : exactEdgeSenderId
608
- ? [exactEdgeSenderId]
609
- : toolInputSenderIds.length > 0
610
- ? toolInputSenderIds
611
- : aggregateSenderIds.length > 0
612
- ? aggregateSenderIds
613
- : actionSenderIdValue
614
- ? [actionSenderIdValue]
615
- : [];
616
- const directActionType = stringValue(exactEdge?.actionType) ??
617
- stringValue(exactEdge?.refillLaneKey) ??
618
- stringValue(action.actionType);
619
- const exactEdgeActionTypes = normalizeStrings(exactEdge?.branchActionTypes);
620
- const actionTypes = directActionType && REFILL_SEND_ACTION_TYPE_SET.has(directActionType)
621
- ? [directActionType]
622
- : (exactEdgeActionTypes.length > 0
623
- ? exactEdgeActionTypes
624
- : normalizeStrings(toolInput.actionTypes)).filter((actionType) => REFILL_SEND_ACTION_TYPE_SET.has(actionType));
552
+ }
625
553
  return [
626
554
  {
627
555
  campaignId,
628
556
  tableId,
629
557
  senderIds,
630
- actionTypes,
558
+ actionTypes: [refillLaneKey],
631
559
  actionKey: stringValue(action.actionKey) ?? null,
632
560
  },
633
561
  ];
634
562
  });
563
+ return targets.length === action.exactTargets.length ? targets : [];
635
564
  }
636
565
  function workspaceExactTargetKey(target) {
637
566
  return JSON.stringify([
@@ -641,55 +570,6 @@ function workspaceExactTargetKey(target) {
641
570
  [...new Set(target.actionTypes)].sort(),
642
571
  ]);
643
572
  }
644
- function evergreenExactTargetCandidates(workspacePlan, options) {
645
- const root = recordValue(workspacePlan) ?? {};
646
- const target = yoloTarget(workspacePlan) ?? {};
647
- const evergreen = recordValue(root.evergreen) ??
648
- recordValue(recordValue(root.packet)?.evergreen) ??
649
- {};
650
- const laneOrder = Array.isArray(evergreen.laneOrder)
651
- ? evergreen.laneOrder.map(recordValue).filter(Boolean)
652
- : [];
653
- const laneByKey = new Map(laneOrder
654
- .map((lane) => [stringValue(lane?.laneKey), lane])
655
- .filter((entry) => Boolean(entry[0] && entry[1])));
656
- const campaigns = Array.isArray(target.campaigns)
657
- ? target.campaigns.map(recordValue).filter(Boolean)
658
- : [];
659
- const campaignById = new Map(campaigns
660
- .map((campaign) => [stringValue(campaign?.campaignId), campaign])
661
- .filter((entry) => Boolean(entry[0] && entry[1])));
662
- const selections = Array.isArray(evergreen.senderLaneSelections)
663
- ? evergreen.senderLaneSelections
664
- .map(recordValue)
665
- .filter(Boolean)
666
- : [];
667
- const selection = selections.find((candidate) => stringValue(candidate?.senderId) === options.preferredSenderId) ?? selections[0];
668
- const senderId = stringValue(selection?.senderId);
669
- const laneChain = Array.isArray(selection?.laneChain)
670
- ? normalizeStrings(selection.laneChain)
671
- : laneOrder
672
- .map((lane) => stringValue(lane?.laneKey))
673
- .filter((laneKey) => Boolean(laneKey));
674
- return laneChain.flatMap((laneKey) => {
675
- const lane = laneByKey.get(laneKey);
676
- const campaignId = stringValue(lane?.campaignId) ??
677
- (campaignById.has(laneKey) ? laneKey : null);
678
- const campaign = campaignId ? campaignById.get(campaignId) : null;
679
- const tableId = stringValue(campaign?.tableId) ?? stringValue(lane?.tableId);
680
- if (!campaignId || !tableId)
681
- return [];
682
- return [
683
- {
684
- campaignId,
685
- tableId,
686
- senderIds: senderId ? [senderId] : [],
687
- actionTypes: options.actionTypes ?? [],
688
- actionKey: null,
689
- },
690
- ];
691
- });
692
- }
693
573
  async function selectWorkspaceExactTarget(input, workspacePlanOverride, options = {}) {
694
574
  const workspaceInput = workspaceCoordinatorInput(input);
695
575
  let workspacePlan = workspacePlanOverride ??
@@ -724,41 +604,18 @@ async function selectWorkspaceExactTarget(input, workspacePlanOverride, options
724
604
  .filter((action) => Boolean(action))
725
605
  : [];
726
606
  const workspaceActions = [...globalActions, ...rankedCandidates];
727
- const aggregateSenderIdsByAction = workspaceAggregateActionSenderIds(workspacePlan);
728
- const legacyGlobalActionCounts = new Map();
729
- for (const action of globalActions) {
730
- const signature = workspaceExactActionSignature(action);
731
- if (!signature)
732
- continue;
733
- legacyGlobalActionCounts.set(signature, (legacyGlobalActionCounts.get(signature) ?? 0) + 1);
734
- }
735
- const ambiguousLegacyAction = globalActions.find((action) => {
736
- const signature = workspaceExactActionSignature(action);
737
- if (!signature || (legacyGlobalActionCounts.get(signature) ?? 0) < 2) {
738
- return false;
739
- }
740
- const toolInput = recordValue(action.toolInput) ?? {};
741
- const hasExplicitTargets = recordsWithExactTarget(toolInput.exactTargets).length > 0 ||
742
- recordsWithExactTarget(toolInput.expectedTargets).length > 0 ||
743
- recordsWithExactTarget(toolInput.sourceScopes).length > 0;
744
- return !hasExplicitTargets && !aggregateSenderIdsByAction.has(signature);
745
- });
746
- if (ambiguousLegacyAction) {
607
+ const queueHead = globalActions[0] ?? rankedCandidates[0];
608
+ if (queueHead && workspaceExactTargetsFromAction(queueHead).length === 0) {
747
609
  return {
748
610
  status: "blocked",
749
- code: "workspace_exact_target_ambiguous",
750
- note: "The workspace planner repeated one legacy aggregate action without explicit exact targets or recoverable sender membership. No refill run or mutation was started.",
611
+ code: "workspace_exact_target_contract_missing",
612
+ note: "The workspace planner's current action did not include a complete exactTargets contract. No refill run or mutation was started.",
751
613
  workspacePlan,
752
614
  };
753
615
  }
754
- const actionTargets = workspaceActions.flatMap((action) => workspaceExactTargetsFromAction(action, aggregateSenderIdsByAction));
755
- const evergreenTargets = evergreenExactTargetCandidates(workspacePlan, {
756
- preferredSenderId: options.preferredSenderId,
757
- actionTypes: input.actionTypes,
758
- }).filter((candidate) => !actionTargets.some((actionTarget) => actionTarget.campaignId === candidate.campaignId &&
759
- actionTarget.tableId === candidate.tableId));
616
+ const actionTargets = workspaceActions.flatMap((action) => workspaceExactTargetsFromAction(action));
760
617
  const seenTargetKeys = new Set();
761
- const targetCandidates = [...actionTargets, ...evergreenTargets].filter((candidate) => {
618
+ const targetCandidates = actionTargets.filter((candidate) => {
762
619
  const key = workspaceExactTargetKey(candidate);
763
620
  if (seenTargetKeys.has(key))
764
621
  return false;
@@ -844,7 +701,13 @@ async function selectWorkspaceExactTarget(input, workspacePlanOverride, options
844
701
  status: "selected",
845
702
  workspacePlan,
846
703
  exactTargetPlan,
847
- selectedTarget,
704
+ selectedTarget: {
705
+ ...selectedTarget,
706
+ // The workspace action chooses the structural target. The exact
707
+ // preflight owns the semantic action that the new fence is allowed to
708
+ // execute, so convergence comparisons must use its immutable binding.
709
+ actionKey: targetConfig.approvalInputs.expectedActionKey,
710
+ },
848
711
  targetConfig,
849
712
  };
850
713
  }
@@ -1878,7 +1741,6 @@ export async function executeRefillSendsCommand(input = {}) {
1878
1741
  if (isWorkspaceCoordinator && result.status === "terminal") {
1879
1742
  const resultReport = recordValue(result.report) ?? {};
1880
1743
  const coordinatorDirective = recordValue(resultReport.coordinatorDirective);
1881
- const laneExhaustion = recordValue(resultReport.laneExhaustion);
1882
1744
  const excludedTargets = Array.isArray(coordinatorDirective?.excludeTargets)
1883
1745
  ? coordinatorDirective.excludeTargets
1884
1746
  .map(recordValue)
@@ -1888,10 +1750,19 @@ export async function executeRefillSendsCommand(input = {}) {
1888
1750
  }))
1889
1751
  .filter((target) => Boolean(target.campaignId && target.tableId))
1890
1752
  : [];
1891
- const nextSelection = await selectWorkspaceExactTarget(scopedInput, undefined, {
1753
+ let nextSelection = await selectWorkspaceExactTarget(scopedInput, undefined, {
1892
1754
  excludeTargets: excludedTargets,
1893
- preferredSenderId: stringValue(laneExhaustion?.senderId) ?? undefined,
1894
1755
  });
1756
+ if (result.doneReason !== "loaded_awaiting_scheduler" &&
1757
+ nextSelection.status === "selected" &&
1758
+ sameWorkspaceExactAction(selectedCoordinatorTarget, nextSelection.selectedTarget)) {
1759
+ // Avoid treating a single eventually-consistent reread as a blocker,
1760
+ // but never create a new fence for the same action after it already
1761
+ // terminalized. The second read is still mutation-free.
1762
+ nextSelection = await selectWorkspaceExactTarget(scopedInput, undefined, {
1763
+ excludeTargets: excludedTargets,
1764
+ });
1765
+ }
1895
1766
  if (nextSelection.status === "complete") {
1896
1767
  return {
1897
1768
  ...command,
@@ -2019,6 +1890,29 @@ export async function executeRefillSendsCommand(input = {}) {
2019
1890
  },
2020
1891
  };
2021
1892
  }
1893
+ if (sameWorkspaceExactAction(selectedCoordinatorTarget, nextSelection.selectedTarget)) {
1894
+ return {
1895
+ ...command,
1896
+ ok: false,
1897
+ status: "blocked",
1898
+ code: "exact_target_terminal_no_progress",
1899
+ blocker: "exact_target_terminal_no_progress",
1900
+ note: "The exact run terminalized, but two fresh preflights selected the same campaign/table/sender/lane action. No identical fence was reopened.",
1901
+ targetPlan: nextSelection.workspacePlan,
1902
+ exactTargetPlan: nextSelection.exactTargetPlan,
1903
+ coordinator: {
1904
+ scope: "workspace",
1905
+ status: "blocked",
1906
+ completedTarget: selectedCoordinatorTarget,
1907
+ selectedTarget: nextSelection.selectedTarget,
1908
+ exactRunResult: result,
1909
+ },
1910
+ continuation: {
1911
+ required: false,
1912
+ userActionRequired: false,
1913
+ },
1914
+ };
1915
+ }
2022
1916
  return {
2023
1917
  ...command,
2024
1918
  status: "in_progress",
@@ -383,7 +383,7 @@ function sanitizeActionCandidates(candidates, selectedKeys, paidRefreshNeededSen
383
383
  const key = senderActionKey(item.senderId, allowed);
384
384
  return Boolean(key && selectedKeys.has(key));
385
385
  })
386
- .map(sanitizeActionCandidate)
386
+ .map((candidate) => sanitizeActionCandidate(candidate, selectedKeys))
387
387
  : [];
388
388
  if (status === "awaiting_scheduler_after_ready_buffer" &&
389
389
  remainingProjectedGap > 0 &&
@@ -396,7 +396,7 @@ function sanitizeActionCandidates(candidates, selectedKeys, paidRefreshNeededSen
396
396
  }
397
397
  return filtered;
398
398
  }
399
- function sanitizeActionCandidate(candidate) {
399
+ function sanitizeActionCandidate(candidate, selectedKeys) {
400
400
  const primary = decodeRefillReadinessIdentity(candidate.readinessIdentity);
401
401
  const readinessIdentities = Array.isArray(candidate.readinessIdentities)
402
402
  ? candidate.readinessIdentities.flatMap((identity) => {
@@ -404,14 +404,41 @@ function sanitizeActionCandidate(candidate) {
404
404
  return decoded.ok ? [decoded.value] : [];
405
405
  })
406
406
  : [];
407
+ const actionType = allowedActionType(candidate.actionType);
408
+ const campaignId = stringValue(candidate.campaignId);
409
+ const tableId = stringValue(candidate.tableId);
410
+ const sharedLane = isRecord(candidate.sharedLane)
411
+ ? candidate.sharedLane
412
+ : null;
413
+ const candidateSenderIds = [
414
+ ...new Set([
415
+ ...stringArray(sharedLane?.memberSenderIds),
416
+ ...(stringValue(candidate.senderId)
417
+ ? [stringValue(candidate.senderId)]
418
+ : []),
419
+ ]),
420
+ ]
421
+ .filter((senderId) => actionType && selectedKeys.has(`${senderId}:${actionType}`))
422
+ .sort();
423
+ const exactTargets = campaignId && tableId && actionType && candidateSenderIds.length > 0
424
+ ? [
425
+ {
426
+ campaignId,
427
+ tableId,
428
+ senderIds: candidateSenderIds,
429
+ refillLaneKey: actionType,
430
+ branchActionTypes: [actionType],
431
+ },
432
+ ]
433
+ : [];
407
434
  return {
408
435
  type: candidate.type,
409
436
  senderId: stringValue(candidate.senderId),
410
437
  senderName: stringValue(candidate.senderName),
411
- actionType: stringValue(candidate.actionType),
412
- campaignId: stringValue(candidate.campaignId),
438
+ actionType,
439
+ campaignId,
413
440
  campaignName: stringValue(candidate.campaignName),
414
- tableId: stringValue(candidate.tableId),
441
+ tableId,
415
442
  tableName: stringValue(candidate.tableName) ?? null,
416
443
  columnId: stringValue(candidate.columnId) ?? null,
417
444
  sourceLeadListId: stringValue(candidate.sourceLeadListId) ?? null,
@@ -444,8 +471,40 @@ function sanitizeActionCandidate(candidate) {
444
471
  effectId: stringValue(candidate.effectId),
445
472
  targetShapeRevision: stringValue(candidate.targetShapeRevision),
446
473
  stateRevision: stringValue(candidate.stateRevision),
474
+ exactTargets,
447
475
  };
448
476
  }
477
+ function sanitizeExactTargets(value) {
478
+ if (!Array.isArray(value))
479
+ return [];
480
+ return value
481
+ .slice(0, REFILL_SCHEDULER_EXPECTED_TARGET_LIMIT)
482
+ .flatMap((raw) => {
483
+ if (!isRecord(raw))
484
+ return [];
485
+ const campaignId = stringValue(raw.campaignId)?.trim();
486
+ const tableId = stringValue(raw.tableId)?.trim();
487
+ const refillLaneKey = allowedActionType(raw.refillLaneKey ?? raw.actionType);
488
+ const senderIds = sortedUniqueStrings(raw.senderIds);
489
+ const branchActionTypes = sortedUniqueStrings(raw.branchActionTypes).filter((actionType) => SCHEDULER_EXPECTED_BRANCH_ACTION_TYPES.has(actionType));
490
+ if (!campaignId ||
491
+ !tableId ||
492
+ !refillLaneKey ||
493
+ senderIds.length === 0 ||
494
+ branchActionTypes.length === 0) {
495
+ return [];
496
+ }
497
+ return [
498
+ {
499
+ campaignId,
500
+ tableId,
501
+ senderIds,
502
+ refillLaneKey,
503
+ branchActionTypes,
504
+ },
505
+ ];
506
+ });
507
+ }
449
508
  function sanitizeRowSelector(value) {
450
509
  if (!isRecord(value) || typeof value.type !== "string")
451
510
  return undefined;
@@ -840,6 +899,7 @@ function sanitizeStructuredAction(action, selectedBySender, mode, rank) {
840
899
  ...sanitizeToolInput(action.toolInput, authority),
841
900
  ...(providerSearchReference ? { providerSearchReference } : {}),
842
901
  },
902
+ exactTargets: sanitizeExactTargets(action.exactTargets),
843
903
  inputSummary: stringValue(action.inputSummary) ?? "",
844
904
  reason: stringValue(action.reason) ?? "",
845
905
  refillReceipt: sanitizeRefillReceipt(action.refillReceipt),
@@ -1182,13 +1242,78 @@ function sanitizeStructuredSenderPlans(value, selectedBySender) {
1182
1242
  });
1183
1243
  }
1184
1244
  function buildSanitizedGlobalActionQueue(senderRefillPlans) {
1185
- return senderRefillPlans
1186
- .flatMap((plan) => Array.isArray(plan.nextActions) ? plan.nextActions.slice(0, 1) : [])
1187
- .filter((action) => isRecord(action))
1188
- .map((action, index) => ({
1189
- ...action,
1190
- rank: index + 1,
1191
- }));
1245
+ const entries = senderRefillPlans.flatMap((plan) => Array.isArray(plan.nextActions)
1246
+ ? plan.nextActions
1247
+ .slice(0, 1)
1248
+ .filter((action) => isRecord(action))
1249
+ .map((action) => ({ plan, action }))
1250
+ : []);
1251
+ const signatureFor = (action) => {
1252
+ const ids = isRecord(action.ids) ? action.ids : {};
1253
+ const campaignId = stringValue(ids.campaignId);
1254
+ const tableId = stringValue(ids.tableId);
1255
+ const actionType = allowedActionType(action.actionType);
1256
+ const actionKey = stringValue(action.actionKey);
1257
+ if (!campaignId || !tableId || !actionType || !actionKey)
1258
+ return null;
1259
+ return JSON.stringify([
1260
+ action.type,
1261
+ actionKey,
1262
+ campaignId,
1263
+ tableId,
1264
+ actionType,
1265
+ ]);
1266
+ };
1267
+ const senderIdsBySignature = new Map();
1268
+ for (const { plan, action } of entries) {
1269
+ const signature = signatureFor(action);
1270
+ const senderId = stringValue(plan.senderId);
1271
+ if (!signature || !senderId)
1272
+ continue;
1273
+ const senderIds = senderIdsBySignature.get(signature) ?? new Set();
1274
+ senderIds.add(senderId);
1275
+ senderIdsBySignature.set(signature, senderIds);
1276
+ }
1277
+ const seen = new Set();
1278
+ return entries
1279
+ .filter(({ action }) => {
1280
+ const signature = signatureFor(action);
1281
+ if (!signature)
1282
+ return true;
1283
+ if (seen.has(signature))
1284
+ return false;
1285
+ seen.add(signature);
1286
+ return true;
1287
+ })
1288
+ .map(({ action }, index) => {
1289
+ const signature = signatureFor(action);
1290
+ const ids = isRecord(action.ids) ? action.ids : {};
1291
+ const actionType = allowedActionType(action.actionType);
1292
+ const senderIds = signature
1293
+ ? [...(senderIdsBySignature.get(signature) ?? [])]
1294
+ : [];
1295
+ senderIds.sort();
1296
+ const exactTargets = signature &&
1297
+ actionType &&
1298
+ stringValue(ids.campaignId) &&
1299
+ stringValue(ids.tableId) &&
1300
+ senderIds.length > 0
1301
+ ? [
1302
+ {
1303
+ campaignId: stringValue(ids.campaignId),
1304
+ tableId: stringValue(ids.tableId),
1305
+ senderIds,
1306
+ refillLaneKey: actionType,
1307
+ branchActionTypes: [actionType],
1308
+ },
1309
+ ]
1310
+ : sanitizeExactTargets(action.exactTargets);
1311
+ return {
1312
+ ...action,
1313
+ exactTargets,
1314
+ rank: index + 1,
1315
+ };
1316
+ });
1192
1317
  }
1193
1318
  function sortedUniqueStrings(value) {
1194
1319
  return [...new Set(stringArray(value))].sort();
@@ -1387,6 +1512,7 @@ function sanitizeTargetSchedulerSweep(params) {
1387
1512
  ? refillReadinessExpectationInput(authority)
1388
1513
  : {}),
1389
1514
  },
1515
+ exactTargets: expectedTargets,
1390
1516
  inputSummary: stringValue(rawAction.inputSummary) ?? "",
1391
1517
  reason: stringValue(rawAction.reason) ?? "",
1392
1518
  prerequisites: stringArray(rawAction.prerequisites),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.643",
3
+ "version": "0.1.645-wip.refill-zero-shot.20260721153930",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",