@sellable/mcp 0.1.730 → 0.1.732

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.
@@ -3232,6 +3232,123 @@ export declare function executeRefillSendsCommand(input?: RefillSendsCommandInpu
3232
3232
  workspaceId: string | null;
3233
3233
  workspaceResolution: string;
3234
3234
  intent: RefillSendsIntent;
3235
+ } | {
3236
+ ok: boolean;
3237
+ status: string;
3238
+ blocker: string;
3239
+ note: string;
3240
+ coordinator: {
3241
+ scope: "workspace";
3242
+ status: "blocked";
3243
+ backgroundJob?: undefined;
3244
+ workspaceTargetPlan?: undefined;
3245
+ selectedTarget?: undefined;
3246
+ waitAction?: undefined;
3247
+ exactTargetPlan?: undefined;
3248
+ note?: undefined;
3249
+ completedTarget?: undefined;
3250
+ exactRunResult?: undefined;
3251
+ handBack?: undefined;
3252
+ };
3253
+ continuation: {
3254
+ required: boolean;
3255
+ userActionRequired: boolean;
3256
+ renderBeforeInvoke?: undefined;
3257
+ kind?: undefined;
3258
+ tool?: undefined;
3259
+ arguments?: undefined;
3260
+ owner?: undefined;
3261
+ retryAfterMs?: undefined;
3262
+ stopCondition?: undefined;
3263
+ };
3264
+ targetDate: string | null;
3265
+ untilDate: string | null;
3266
+ horizonSendDays: number | null;
3267
+ fillWindow: {
3268
+ mode: string;
3269
+ targetDate: string;
3270
+ description: string;
3271
+ untilDate?: undefined;
3272
+ horizonSendDays?: undefined;
3273
+ horizonHours?: undefined;
3274
+ } | {
3275
+ mode: string;
3276
+ untilDate: string;
3277
+ description: string;
3278
+ targetDate?: undefined;
3279
+ horizonSendDays?: undefined;
3280
+ horizonHours?: undefined;
3281
+ } | {
3282
+ mode: string;
3283
+ horizonSendDays: number | null;
3284
+ horizonHours: number | null;
3285
+ description: string;
3286
+ targetDate?: undefined;
3287
+ untilDate?: undefined;
3288
+ };
3289
+ approvalMode: RefillSendsApprovalMode;
3290
+ campaignId: string | null;
3291
+ tableId: string | null;
3292
+ targetConfig: RefillTargetConfigV1 | null;
3293
+ runHandle: RefillRunHandleV1 | null;
3294
+ reportingContext: RefillReportingContextV2 | null;
3295
+ messageTemplateRevision: MessageTemplateRevisionV1 | null;
3296
+ senderScope: string;
3297
+ senderSelectors: {
3298
+ senders: string[];
3299
+ senderIds: string[];
3300
+ senderNames: string[];
3301
+ };
3302
+ firstOperationalSteps: string[];
3303
+ approvalContract: string;
3304
+ forbiddenActions: string[];
3305
+ hostExamples: {
3306
+ claude: string[];
3307
+ codex: string[];
3308
+ mcpTool: {
3309
+ name: string;
3310
+ arguments: {
3311
+ workspaceRunState?: Record<string, unknown> | undefined;
3312
+ workspaceCoordinator?: boolean | undefined;
3313
+ messageTemplateRevision?: MessageTemplateRevisionV1 | undefined;
3314
+ reportingContext?: RefillReportingContextV2 | undefined;
3315
+ targetConfig?: RefillTargetConfigV1 | undefined;
3316
+ runHandle?: RefillRunHandleV1 | undefined;
3317
+ fence?: number | undefined;
3318
+ runId?: string | undefined;
3319
+ expectedActionKey?: string | undefined;
3320
+ expectedTargetShapeRevision?: string | undefined;
3321
+ targetDate: string | null;
3322
+ untilDate: string | null;
3323
+ horizonSendDays: number | null;
3324
+ campaignId: string | undefined;
3325
+ tableId: string | undefined;
3326
+ intent: RefillSendsIntent;
3327
+ approvalMode: RefillSendsApprovalMode;
3328
+ workspaceId: string | null;
3329
+ actionTypes?: ("send_invite" | "send_inmail_closed")[] | undefined;
3330
+ yolo: boolean;
3331
+ senders: string[];
3332
+ senderIds: string[];
3333
+ senderNames: string[];
3334
+ };
3335
+ };
3336
+ };
3337
+ actionTypes?: ("send_invite" | "send_inmail_closed")[] | undefined;
3338
+ command: string;
3339
+ tool: string;
3340
+ promptName: string;
3341
+ workflowPromptName: string;
3342
+ workflowAsset: {
3343
+ subskillName: string;
3344
+ assetPath: string;
3345
+ expectedVersion: string;
3346
+ };
3347
+ yolo: boolean;
3348
+ executionMode: RefillSendsExecutionMode;
3349
+ workspaceId: string | null;
3350
+ workspaceResolution: string;
3351
+ intent: RefillSendsIntent;
3235
3352
  } | {
3236
3353
  targetPlan: unknown;
3237
3354
  orchestration: {
@@ -8397,6 +8514,273 @@ export declare function executeRefillSendsCommand(input?: RefillSendsCommandInpu
8397
8514
  workspaceId: string | null;
8398
8515
  workspaceResolution: string;
8399
8516
  intent: RefillSendsIntent;
8517
+ } | {
8518
+ status: "in_progress";
8519
+ note: string;
8520
+ targetPlan: unknown;
8521
+ exactTargetPlan: {} | null;
8522
+ orchestration: {
8523
+ mode: string;
8524
+ scope: string;
8525
+ phase: string;
8526
+ checkpointedFields?: undefined;
8527
+ transportBudgetMs?: undefined;
8528
+ };
8529
+ coordinator: {
8530
+ scope: string;
8531
+ status: string;
8532
+ completedTarget: WorkspaceExactTarget;
8533
+ selectedTarget: WorkspaceExactTarget;
8534
+ workspaceTargetPlan: unknown;
8535
+ exactRunResult: {
8536
+ status: "blocked";
8537
+ blocker: string;
8538
+ guidance: string;
8539
+ conflicts: string[];
8540
+ runId: string | undefined;
8541
+ readOnly: boolean;
8542
+ forbiddenActions: string[];
8543
+ boundedAuthority: string;
8544
+ } | {
8545
+ readOnly: boolean;
8546
+ mode: string;
8547
+ forbiddenActions: string[];
8548
+ boundedAuthority: string;
8549
+ blocker: string;
8550
+ workspaceId: string;
8551
+ guidance: string;
8552
+ warnings: string[];
8553
+ journalPath: string | null;
8554
+ text: string;
8555
+ workspaceResolution: string;
8556
+ status?: undefined;
8557
+ conflicts?: undefined;
8558
+ runId?: undefined;
8559
+ } | {
8560
+ readOnly: boolean;
8561
+ mode: string;
8562
+ forbiddenActions: string[];
8563
+ boundedAuthority: string;
8564
+ warnings: any[];
8565
+ journalPath: string | null;
8566
+ text: string;
8567
+ workspaceId: string;
8568
+ workspaceResolution: string;
8569
+ blocker?: undefined;
8570
+ guidance?: undefined;
8571
+ status?: undefined;
8572
+ conflicts?: undefined;
8573
+ runId?: undefined;
8574
+ } | {
8575
+ runSnapshot: unknown;
8576
+ readOnly: boolean;
8577
+ forbiddenActions: string[];
8578
+ boundedAuthority: string;
8579
+ status: "dry_run";
8580
+ plan: Record<string, unknown>;
8581
+ blocker?: undefined;
8582
+ guidance?: undefined;
8583
+ conflicts?: undefined;
8584
+ runId?: undefined;
8585
+ } | {
8586
+ runSnapshot: unknown;
8587
+ readOnly: boolean;
8588
+ forbiddenActions: string[];
8589
+ boundedAuthority: string;
8590
+ status: "in_progress";
8591
+ runId: string;
8592
+ fence: number;
8593
+ gate: import("../refill-run-loop.js").RefillLoopGate;
8594
+ guidance: string;
8595
+ journalPath?: string | null;
8596
+ targetConfig?: RefillTargetConfigV1;
8597
+ runHandle?: RefillRunHandleV1;
8598
+ refillReporting?: Record<string, unknown>;
8599
+ blocker?: undefined;
8600
+ conflicts?: undefined;
8601
+ } | {
8602
+ runSnapshot: unknown;
8603
+ readOnly: boolean;
8604
+ forbiddenActions: string[];
8605
+ boundedAuthority: string;
8606
+ status: "blocked";
8607
+ blocker: string;
8608
+ runId?: string;
8609
+ fence?: number;
8610
+ gate?: import("../refill-run-loop.js").RefillLoopGate;
8611
+ guidance: string;
8612
+ holderRunId?: string;
8613
+ report?: Record<string, unknown>;
8614
+ journalPath?: string | null;
8615
+ targetConfig?: RefillTargetConfigV1;
8616
+ runHandle?: RefillRunHandleV1;
8617
+ refillReporting?: Record<string, unknown>;
8618
+ conflicts?: undefined;
8619
+ } | {
8620
+ runSnapshot: unknown;
8621
+ readOnly: boolean;
8622
+ forbiddenActions: string[];
8623
+ boundedAuthority: string;
8624
+ status: "terminal";
8625
+ doneReason: string;
8626
+ report: Record<string, unknown>;
8627
+ runId: string;
8628
+ guidance?: string;
8629
+ journalPath?: string | null;
8630
+ targetConfig?: RefillTargetConfigV1;
8631
+ runHandle?: RefillRunHandleV1;
8632
+ refillReporting?: Record<string, unknown>;
8633
+ runState?: Record<string, unknown>;
8634
+ blocker?: undefined;
8635
+ conflicts?: undefined;
8636
+ };
8637
+ backgroundJob: {
8638
+ jobId: string;
8639
+ status?: undefined;
8640
+ activityState?: undefined;
8641
+ checkedRows?: undefined;
8642
+ preparedMessages?: undefined;
8643
+ activeCellCount?: undefined;
8644
+ };
8645
+ waitAction?: undefined;
8646
+ exactTargetPlan?: undefined;
8647
+ note?: undefined;
8648
+ handBack?: undefined;
8649
+ };
8650
+ continuation: {
8651
+ required: boolean;
8652
+ kind: string;
8653
+ owner: string;
8654
+ userActionRequired: boolean;
8655
+ tool: string;
8656
+ retryAfterMs: number;
8657
+ arguments: {
8658
+ workspaceRunState?: Record<string, unknown> | {
8659
+ coordinatorBlockedExactTargets: (Record<string, unknown> | null)[];
8660
+ } | undefined;
8661
+ yolo: boolean;
8662
+ executionMode: "yolo";
8663
+ requireWorkspace: boolean;
8664
+ workspaceId: string;
8665
+ workspaceCoordinator: boolean;
8666
+ backgroundWait: {
8667
+ jobId: string;
8668
+ campaignId: string;
8669
+ tableId: string;
8670
+ deadlineAt: string;
8671
+ };
8672
+ senders?: string[];
8673
+ senderIds?: string[];
8674
+ senderNames?: string[];
8675
+ horizonSendDays?: number;
8676
+ untilDate?: string;
8677
+ targetDate?: string;
8678
+ campaignId?: string;
8679
+ tableId?: string;
8680
+ intent?: RefillSendsIntent;
8681
+ approvalMode?: RefillSendsApprovalMode;
8682
+ actionTypes?: RefillSendsActionType[];
8683
+ expectedTargetShapeRevision?: string;
8684
+ expectedActionKey?: string;
8685
+ runId?: string;
8686
+ fence?: number;
8687
+ runHandle?: RefillRunHandleV1;
8688
+ targetConfig?: RefillTargetConfigV1;
8689
+ reportingContext?: RefillReportingContextV2;
8690
+ messageTemplateRevision?: MessageTemplateRevisionV1;
8691
+ agentKeywords?: string[];
8692
+ };
8693
+ stopCondition: string;
8694
+ renderBeforeInvoke?: undefined;
8695
+ };
8696
+ targetDate: string | null;
8697
+ untilDate: string | null;
8698
+ horizonSendDays: number | null;
8699
+ fillWindow: {
8700
+ mode: string;
8701
+ targetDate: string;
8702
+ description: string;
8703
+ untilDate?: undefined;
8704
+ horizonSendDays?: undefined;
8705
+ horizonHours?: undefined;
8706
+ } | {
8707
+ mode: string;
8708
+ untilDate: string;
8709
+ description: string;
8710
+ targetDate?: undefined;
8711
+ horizonSendDays?: undefined;
8712
+ horizonHours?: undefined;
8713
+ } | {
8714
+ mode: string;
8715
+ horizonSendDays: number | null;
8716
+ horizonHours: number | null;
8717
+ description: string;
8718
+ targetDate?: undefined;
8719
+ untilDate?: undefined;
8720
+ };
8721
+ approvalMode: RefillSendsApprovalMode;
8722
+ campaignId: string | null;
8723
+ tableId: string | null;
8724
+ targetConfig: RefillTargetConfigV1 | null;
8725
+ runHandle: RefillRunHandleV1 | null;
8726
+ reportingContext: RefillReportingContextV2 | null;
8727
+ messageTemplateRevision: MessageTemplateRevisionV1 | null;
8728
+ senderScope: string;
8729
+ senderSelectors: {
8730
+ senders: string[];
8731
+ senderIds: string[];
8732
+ senderNames: string[];
8733
+ };
8734
+ firstOperationalSteps: string[];
8735
+ approvalContract: string;
8736
+ forbiddenActions: string[];
8737
+ hostExamples: {
8738
+ claude: string[];
8739
+ codex: string[];
8740
+ mcpTool: {
8741
+ name: string;
8742
+ arguments: {
8743
+ workspaceRunState?: Record<string, unknown> | undefined;
8744
+ workspaceCoordinator?: boolean | undefined;
8745
+ messageTemplateRevision?: MessageTemplateRevisionV1 | undefined;
8746
+ reportingContext?: RefillReportingContextV2 | undefined;
8747
+ targetConfig?: RefillTargetConfigV1 | undefined;
8748
+ runHandle?: RefillRunHandleV1 | undefined;
8749
+ fence?: number | undefined;
8750
+ runId?: string | undefined;
8751
+ expectedActionKey?: string | undefined;
8752
+ expectedTargetShapeRevision?: string | undefined;
8753
+ targetDate: string | null;
8754
+ untilDate: string | null;
8755
+ horizonSendDays: number | null;
8756
+ campaignId: string | undefined;
8757
+ tableId: string | undefined;
8758
+ intent: RefillSendsIntent;
8759
+ approvalMode: RefillSendsApprovalMode;
8760
+ workspaceId: string | null;
8761
+ actionTypes?: ("send_invite" | "send_inmail_closed")[] | undefined;
8762
+ yolo: boolean;
8763
+ senders: string[];
8764
+ senderIds: string[];
8765
+ senderNames: string[];
8766
+ };
8767
+ };
8768
+ };
8769
+ actionTypes?: ("send_invite" | "send_inmail_closed")[] | undefined;
8770
+ command: string;
8771
+ tool: string;
8772
+ promptName: string;
8773
+ workflowPromptName: string;
8774
+ workflowAsset: {
8775
+ subskillName: string;
8776
+ assetPath: string;
8777
+ expectedVersion: string;
8778
+ };
8779
+ yolo: boolean;
8780
+ executionMode: RefillSendsExecutionMode;
8781
+ workspaceId: string | null;
8782
+ workspaceResolution: string;
8783
+ intent: RefillSendsIntent;
8400
8784
  } | {
8401
8785
  status: "terminal";
8402
8786
  doneReason: "loaded_awaiting_scheduler";
@@ -219,6 +219,13 @@ function normalizeWorkspaceRunState(value) {
219
219
  if (entries.length > 0)
220
220
  attemptLedgers[key] = entries;
221
221
  }
222
+ // fix(112aj): preserve the continuation-level progress-or-settle counter across
223
+ // coordinator calls. Like the attempt ledgers above, stripping it here would
224
+ // reset the identical-active_exact_run replay count to 1 on every resume and the
225
+ // cap could never trip.
226
+ const activeExactRunProgress = recordValue(root.activeExactRunProgress);
227
+ const activeExactRunFingerprint = stringValue(activeExactRunProgress?.fingerprint);
228
+ const activeExactRunCount = numberValue(activeExactRunProgress?.count);
222
229
  return {
223
230
  version: 1,
224
231
  senderCursors,
@@ -226,6 +233,14 @@ function normalizeWorkspaceRunState(value) {
226
233
  passRates: [],
227
234
  coordinatorBlockedExactTargets,
228
235
  ...attemptLedgers,
236
+ ...(activeExactRunFingerprint && typeof activeExactRunCount === "number"
237
+ ? {
238
+ activeExactRunProgress: {
239
+ fingerprint: activeExactRunFingerprint,
240
+ count: activeExactRunCount,
241
+ },
242
+ }
243
+ : {}),
229
244
  };
230
245
  }
231
246
  const WORKSPACE_ATTEMPT_LEDGER_KEYS = [
@@ -813,6 +828,36 @@ function withKeywordsHandoffScope(result, scopedInput) {
813
828
  }
814
829
  const BACKGROUND_WAIT_FAST_PATH_TTL_MS = 10 * 60 * 1000;
815
830
  const BACKGROUND_WAIT_MIN_RETRY_MS = 5_000;
831
+ // fix(112aj): the continuation-level progress-or-settle contract (the same
832
+ // principle as the ag/af-c bypasses, applied one layer up). An identical
833
+ // active_exact_run continuation — the SAME fence resumed at the same gate /
834
+ // gateSeq / state / plan / shape revision with no observable coverage or effect
835
+ // advance — may not be re-invoked unboundedly. Clover t4 (fence 109, run
836
+ // cmrz49lii) resumed a broaden fence stuck at g2_execute 20× with a byte-stable
837
+ // runSnapshot (gate g2_execute, gateSeq 2, stateRev 36a1de45) — no product effect,
838
+ // no fresh handoff, no no_forward_progress — because the broaden's own attempt
839
+ // bookkeeping wobbles the run-loop's mortality signature. This coordinator bound
840
+ // counts identical active_exact_run resumes and, past the cap, returns an honest
841
+ // no_forward_progress terminal and settles the fence (no continuation, not
842
+ // re-spawned). A fence that IS advancing (any fingerprint field changes) resets
843
+ // the counter and resumes normally, unbounded.
844
+ const ACTIVE_EXACT_RUN_REPLAY_CAP = 3;
845
+ function activeExactRunProgressFingerprint(result) {
846
+ const snapshot = recordValue(result.runSnapshot);
847
+ const runId = stringValue(result.runId) ?? stringValue(snapshot?.runId);
848
+ const fence = numberValue(result.fence);
849
+ if (!runId || typeof fence !== "number" || !snapshot)
850
+ return null;
851
+ return [
852
+ runId,
853
+ String(fence),
854
+ stringValue(snapshot.gate) ?? "",
855
+ String(numberValue(snapshot.gateSeq) ?? ""),
856
+ stringValue(snapshot.stateRevision) ?? "",
857
+ stringValue(snapshot.planRevision) ?? "",
858
+ stringValue(snapshot.targetShapeRevision) ?? "",
859
+ ].join("|");
860
+ }
816
861
  // Action families whose successful exact terminal means NEW ROWS landed on
817
862
  // the campaign — invalidating any earlier complete/capped defer for it.
818
863
  const WORKSPACE_SUPPLY_ACTION_FAMILIES = new Set([
@@ -895,6 +940,51 @@ function backgroundWaitFromSelection(selection, now) {
895
940
  retryAfterMs,
896
941
  };
897
942
  }
943
+ // fix(112ai): extract a still-running bounded prep/approve job receipt from a
944
+ // just-completed exact run. When the census bypass admits a masked prepare/
945
+ // approve edge, the coordinator dispatches a bounded job and the exact run
946
+ // returns exact_action_complete with a preparationReceipt that is still RUNNING
947
+ // (terminal:false) — the job drains the lane ASYNC, so the actionable census is
948
+ // non-empty only because scanning has not finished. That in-flight job IS the
949
+ // queued supply behind a non-empty census, so the coordinator must wait on it
950
+ // (self-heal) rather than false-terminal actionable_supply_not_queued
951
+ // (sellable-t4 7-29: prepare_messages:7fc0766d dispatched job cmrz555uc scanning
952
+ // while 681 enrich rows sat in the census). A terminal:true receipt means the
953
+ // job already drained; that is NOT a wait case, and the normal terminal logic
954
+ // (which may legitimately block on residual, correctly-named conditions) runs.
955
+ function dispatchedRunningPrepReceipt(result, completedFamily) {
956
+ if (completedFamily !== "prepare_messages" &&
957
+ completedFamily !== "approve_messages") {
958
+ return null;
959
+ }
960
+ const r = recordValue(result);
961
+ if (stringValue(r?.status) !== "terminal")
962
+ return null;
963
+ if (stringValue(r?.doneReason) !== "exact_action_complete")
964
+ return null;
965
+ const receipt = recordValue(recordValue(r?.report)?.preparationReceipt);
966
+ if (!receipt)
967
+ return null;
968
+ // Only a still-running durable receipt is draining work. terminal:true means
969
+ // the job finished (residual census is a different, correctly-named case); a
970
+ // failed/blocked/errored receipt is a real condition the terminal must surface.
971
+ if (receipt.terminal === true)
972
+ return null;
973
+ const status = stringValue(receipt.status);
974
+ if (status === "failed" ||
975
+ status === "error" ||
976
+ status === "blocked" ||
977
+ status === "cancelled") {
978
+ return null;
979
+ }
980
+ const jobId = stringValue(receipt.jobId);
981
+ if (!jobId)
982
+ return null;
983
+ return {
984
+ jobId,
985
+ retryAfterMs: Math.max(BACKGROUND_WAIT_MIN_RETRY_MS, numberValue(receipt.retryAfterMs) ?? 15_000),
986
+ };
987
+ }
898
988
  function workspaceExactTargetsFromAction(action) {
899
989
  if (!Array.isArray(action.exactTargets) || action.exactTargets.length === 0) {
900
990
  return [];
@@ -1394,6 +1484,12 @@ async function selectWorkspaceExactTarget(input, workspacePlanOverride, options
1394
1484
  workspacePlan,
1395
1485
  };
1396
1486
  }
1487
+ // fix(112ak): empty-candidate terminal path — a ready-surplus gap resolves to
1488
+ // the truthful loaded terminal instead of the actionable_supply_not_queued
1489
+ // defect class (see readySurplusLoadedSelection).
1490
+ const readyLoadedSelection = readySurplusLoadedSelection(workspacePlan);
1491
+ if (readyLoadedSelection)
1492
+ return readyLoadedSelection;
1397
1493
  return {
1398
1494
  status: "blocked",
1399
1495
  ...classifyWorkspaceUnavailableTerminal(workspacePlan, (excludedTargets.length > 0 ||
@@ -1458,17 +1554,23 @@ async function selectWorkspaceExactTarget(input, workspacePlanOverride, options
1458
1554
  selectedActionFamily === "continue_prospeo_source" ||
1459
1555
  selectedActionFamily === "broaden_provider_search" ||
1460
1556
  selectedActionFamily === "refresh_sender_post_engagers";
1461
- // fix(112af-c): a prepare_messages edge suffers the same pooled-cascade
1462
- // masking as sources/sweeps/waits — its exact scope can read scheduler-loaded
1463
- // or complete via the shared ready buffer while the lane still holds genuine
1464
- // never-run supply (sellable.dev 7-29: cmrcd4x4a send_invite prepare skipped
1465
- // while 69 enrich + 1 generate never-run rows sat behind it). Unlike those
1466
- // families a prepare is not unconditionally bindable a genuinely-complete
1467
- // prepare must stay deferred to avoid a re-prepare loop. So bind ONLY when the
1468
- // LANE-SCOPED actionable census (per-sender existingRowFrontier stageCounts,
1469
- // never the pooled coverage) proves a real frontier; an empty census keeps the
1470
- // settled skip, and the bounded job consumes the frontier so the census drains.
1471
- const isMaskedPrepareTarget = selectedActionFamily === "prepare_messages" &&
1557
+ // fix(112af-c/112ai): a prepare_messages OR approve_messages edge suffers the
1558
+ // same pooled-cascade masking as sources/sweeps/waits — its exact scope can
1559
+ // read scheduler-loaded or complete via the shared ready buffer while the lane
1560
+ // still holds genuine actionable supply (sellable.dev 7-29: cmrcd4x4a
1561
+ // send_invite prepare skipped while 69 enrich + 1 generate never-run rows sat
1562
+ // behind it; Amplify t3 Sadhna + Dotwork cmqbt8b8: approve_messages skipped
1563
+ // while a real approval frontier needsApproval > 0 sat behind it). This
1564
+ // closes the masked-supply family (sweep→112ag, prepare→112af-c, approve→112ai)
1565
+ // with ONE census-conditional bypass. Unlike sources/sweeps/waits these are not
1566
+ // unconditionally bindable a genuinely-complete prepare/approve must stay
1567
+ // deferred to avoid a re-prepare loop. So bind ONLY when the LANE-SCOPED
1568
+ // actionable census (per-sender existingRowFrontier stageCounts —
1569
+ // needsApproval/needsGenerate/needsEnrich/needsRubric, never the pooled
1570
+ // coverage) proves a real frontier; an empty census keeps the settled skip, and
1571
+ // the bounded job consumes the frontier so the census drains.
1572
+ const isMaskedSupplyTarget = (selectedActionFamily === "prepare_messages" ||
1573
+ selectedActionFamily === "approve_messages") &&
1472
1574
  exactPlanHasLaneScopedActionableFrontier(exactTargetPlan);
1473
1575
  // fix(111f, OWNER DIRECTIVE — dispatch-first): a settled preflight read
1474
1576
  // may only skip a target when its exact plan exposes NO unresolved
@@ -1482,7 +1584,7 @@ async function selectWorkspaceExactTarget(input, workspacePlanOverride, options
1482
1584
  const bypassSettledSkips = isSchedulerSweepTarget ||
1483
1585
  isBackgroundWaitTarget ||
1484
1586
  isSourceSupplyTarget ||
1485
- isMaskedPrepareTarget ||
1587
+ isMaskedSupplyTarget ||
1486
1588
  yoloPlanHasUnresolvedCoordinatorAction(exactTargetPlan);
1487
1589
  if (!bypassSettledSkips && yoloPlanIsComplete(exactTargetPlan)) {
1488
1590
  completedExactTargetCount += 1;
@@ -1678,6 +1780,12 @@ async function selectWorkspaceExactTarget(input, workspacePlanOverride, options
1678
1780
  workspacePlan,
1679
1781
  };
1680
1782
  }
1783
+ // fix(112ak): filtered-candidate terminal path — a ready-surplus gap resolves to
1784
+ // the truthful loaded terminal instead of the actionable_supply_not_queued defect
1785
+ // class (see readySurplusLoadedSelection).
1786
+ const readyLoadedSelection = readySurplusLoadedSelection(workspacePlan);
1787
+ if (readyLoadedSelection)
1788
+ return readyLoadedSelection;
1681
1789
  return {
1682
1790
  status: "blocked",
1683
1791
  ...classifyWorkspaceUnavailableTerminal(workspacePlan, (workspaceCoverage.remainingReadyOrProjectedGap !== 0
@@ -1763,18 +1871,115 @@ function workspaceActionableSupplyCensus(plan) {
1763
1871
  senderPlanCount: senderPlans.length,
1764
1872
  };
1765
1873
  }
1874
+ // fix(112ak, OWNER closing invariant): the workspace terminal classifier must
1875
+ // only count actionable supply / frontier signal from senders that still OWE
1876
+ // coverage. A COVERED sender (remainingProjectedGap 0) can carry surplus
1877
+ // hasMoreFrontierRows or surplus stage rows; that surplus must never be read as
1878
+ // "unexposed actionable supply" for a DIFFERENT sender's gap (Amplify t5:
1879
+ // Ali/Victor gap 0 but hasMoreFrontierRows true masked Sadhna's gap-10 lane
1880
+ // whose own stage census was empty and whose 10 rows were paid-InMail
1881
+ // ready_to_schedule surplus — so the workspace false-terminaled
1882
+ // actionable_supply_not_queued while every gap-bearing lane was actually
1883
+ // scheduler/pacing-bound). This re-derives the census over ONLY gap-bearing
1884
+ // senders and buckets each gap lane: real actionable supply, ready-surplus
1885
+ // (prepared rows awaiting the scheduler), or opaque (no ready/actionable/frontier
1886
+ // residue). A missing gap is treated as gap-bearing (conservative) so plans
1887
+ // without gap accounting keep the loud actionable classification.
1888
+ function workspaceGapBearingActionableSupply(plan) {
1889
+ const target = yoloTarget(plan);
1890
+ const senderPlansRaw = target?.senderPlans;
1891
+ const senderPlans = Array.isArray(senderPlansRaw) ? senderPlansRaw : [];
1892
+ let approvalFrontier = 0;
1893
+ let generateFrontier = 0;
1894
+ let enrichFrontier = 0;
1895
+ let rubricFrontier = 0;
1896
+ let frontierHasMoreRows = false;
1897
+ let gapSenderCount = 0;
1898
+ let readySurplusGapSenders = 0;
1899
+ let opaqueGapSenders = 0;
1900
+ for (const senderPlan of senderPlans) {
1901
+ const rec = recordValue(senderPlan);
1902
+ const gap = numberValue(rec?.remainingProjectedGap);
1903
+ // Only lanes that still owe coverage classify the workspace gap. A gap-0
1904
+ // (covered) sender's surplus never contributes; a missing gap is included.
1905
+ if (typeof gap === "number" && gap <= 0)
1906
+ continue;
1907
+ gapSenderCount += 1;
1908
+ const receipt = recordValue(rec?.refillReceipt);
1909
+ const frontier = recordValue(receipt?.existingRowFrontier);
1910
+ const stageCounts = recordValue(frontier?.stageCounts);
1911
+ const approval = Math.max(numberValue(receipt?.approvalCandidates) ?? 0, numberValue(stageCounts?.needsApproval) ?? 0);
1912
+ const generate = numberValue(stageCounts?.needsGenerate) ?? 0;
1913
+ const enrich = numberValue(stageCounts?.needsEnrich) ?? 0;
1914
+ const rubric = numberValue(stageCounts?.needsRubric) ?? 0;
1915
+ const laneActionable = approval + generate + enrich + rubric;
1916
+ const laneFrontier = frontier?.hasMoreFrontierRows === true;
1917
+ const laneReady = numberValue(stageCounts?.ready) ?? 0;
1918
+ approvalFrontier += approval;
1919
+ generateFrontier += generate;
1920
+ enrichFrontier += enrich;
1921
+ rubricFrontier += rubric;
1922
+ if (laneFrontier)
1923
+ frontierHasMoreRows = true;
1924
+ if (laneActionable === 0 && !laneFrontier) {
1925
+ if (laneReady > 0)
1926
+ readySurplusGapSenders += 1;
1927
+ else
1928
+ opaqueGapSenders += 1;
1929
+ }
1930
+ }
1931
+ const actionableRows = approvalFrontier + generateFrontier + enrichFrontier + rubricFrontier;
1932
+ return {
1933
+ approvalFrontier,
1934
+ generateFrontier,
1935
+ enrichFrontier,
1936
+ rubricFrontier,
1937
+ frontierHasMoreRows,
1938
+ actionableRows,
1939
+ gapSenderCount,
1940
+ readySurplusGapSenders,
1941
+ opaqueGapSenders,
1942
+ };
1943
+ }
1944
+ // fix(112ak): when the workspace gap is owed entirely by gap-bearing lanes whose
1945
+ // residue is ready-surplus (prepared rows in stageCounts.ready, zero actionable
1946
+ // stage supply, no own frontier), the gap is scheduler/pacing-bound — the rows
1947
+ // are ready and the scheduler places them as capacity frees — NOT a
1948
+ // planner-exposure defect. Return the truthful loaded terminal instead of
1949
+ // actionable_supply_not_queued (Amplify t5: Sadhna's gap-10 lane was 10 paid-InMail
1950
+ // ready_to_schedule rows on a fully-scheduled day while COVERED senders carried
1951
+ // the only frontier signal). Gated on the gap-bearing census being empty-actionable
1952
+ // AND every gap lane ready-surplus (no opaque residue), so a genuinely-stuck or
1953
+ // genuinely-actionable gap still classifies honestly. Applied at BOTH selection
1954
+ // terminal sites (empty-candidate early path and the filtered-candidate path).
1955
+ function readySurplusLoadedSelection(workspacePlan) {
1956
+ const gapSupply = workspaceGapBearingActionableSupply(workspacePlan);
1957
+ if (gapSupply.gapSenderCount > 0 &&
1958
+ gapSupply.actionableRows === 0 &&
1959
+ !gapSupply.frontierHasMoreRows &&
1960
+ gapSupply.opaqueGapSenders === 0 &&
1961
+ gapSupply.readySurplusGapSenders > 0) {
1962
+ return {
1963
+ status: "loaded_awaiting_scheduler",
1964
+ note: "Every gap-bearing sender lane is already prepared and ready to schedule; the remaining workspace gap is scheduler/pacing-bound (ready rows awaiting placement), not unexposed actionable supply. No refill run or mutation was started." +
1965
+ workspaceTypedBlockersNote(workspacePlan),
1966
+ workspacePlan,
1967
+ };
1968
+ }
1969
+ return null;
1970
+ }
1766
1971
  function classifyWorkspaceUnavailableTerminal(plan, contextNote) {
1767
- const census = workspaceActionableSupplyCensus(plan);
1768
- const actionableRows = census.approvalFrontier +
1769
- census.generateFrontier +
1770
- census.enrichFrontier +
1771
- census.rubricFrontier;
1972
+ // fix(112ak): gap-gate the actionable_supply signal — only supply behind a
1973
+ // gap-bearing lane proves the planner failed to expose actionable work. A
1974
+ // covered sender's surplus frontier/stage rows do NOT justify the defect class.
1975
+ const census = workspaceGapBearingActionableSupply(plan);
1976
+ const actionableRows = census.actionableRows;
1772
1977
  if (actionableRows > 0 || census.frontierHasMoreRows) {
1773
1978
  return {
1774
1979
  code: "actionable_supply_not_queued",
1775
1980
  note: `Actionable existing-row supply remains (${census.approvalFrontier} approval, ` +
1776
1981
  `${census.generateFrontier} generate, ${census.enrichFrontier} enrich, ` +
1777
- `${census.rubricFrontier} rubric candidates across sender lanes` +
1982
+ `${census.rubricFrontier} rubric candidates across gap-bearing sender lanes` +
1778
1983
  `${census.frontierHasMoreRows ? ", plus unprocessed frontier rows" : ""}) ` +
1779
1984
  "but the fresh workspace queue exposed no executable exact edge for it. " +
1780
1985
  "This is retryable: re-run refill_sends with a fresh command; if it repeats, " +
@@ -1786,7 +1991,8 @@ function classifyWorkspaceUnavailableTerminal(plan, contextNote) {
1786
1991
  const gap = coverage.remainingReadyOrProjectedGap ?? coverage.remainingProjectedGap;
1787
1992
  // Claim receipt-proven exhaustion only when sender receipts were actually
1788
1993
  // present in the plan; a plan without senderPlans keeps the generic code.
1789
- if (census.senderPlanCount > 0 && typeof gap === "number" && gap > 0) {
1994
+ const senderPlanCount = workspaceActionableSupplyCensus(plan).senderPlanCount;
1995
+ if (senderPlanCount > 0 && typeof gap === "number" && gap > 0) {
1790
1996
  return {
1791
1997
  code: "new_campaign_required",
1792
1998
  note: `Receipts prove every existing planner-ranked campaign/lane/source family is ` +
@@ -1799,17 +2005,19 @@ function classifyWorkspaceUnavailableTerminal(plan, contextNote) {
1799
2005
  }
1800
2006
  return { code: "workspace_exact_target_unavailable", note: contextNote };
1801
2007
  }
1802
- // fix(112af-c): a prepare_messages edge on a cascade can read scheduler-loaded
1803
- // or complete at the pooled campaign scope (the shared ready buffer routed to
1804
- // Open/paid InMail or the connection fallback covers the aggregate gap) while a
1805
- // specific lane still holds genuine never-run supply the pooled coverage masks
1806
- // (sellable.dev 7-29: cmrcd4x4a send_invite prepare skipped as "complete or
1807
- // scheduler-loaded" while 69 enrich + 1 generate never-run rows sat behind it).
1808
- // The lane-scoped signal is the per-sender existingRowFrontier stageCounts summed
1809
- // by workspaceActionableSupplyCensus NEVER the pooled readyBuffer/coverage. When
1810
- // that census is non-empty the prepare edge is a masked-gap prepare and must bind
1811
- // so the bounded job consumes the frontier; when the census is empty the edge is
1812
- // genuinely settled and stays deferred (no re-prepare loop).
2008
+ // fix(112af-c/112ai): a prepare_messages OR approve_messages edge on a cascade can
2009
+ // read scheduler-loaded or complete at the pooled campaign scope (the shared ready
2010
+ // buffer routed to Open/paid InMail or the connection fallback covers the aggregate
2011
+ // gap) while a specific lane still holds genuine actionable supply the pooled
2012
+ // coverage masks (sellable.dev 7-29: cmrcd4x4a send_invite prepare skipped as
2013
+ // "complete or scheduler-loaded" while 69 enrich + 1 generate never-run rows sat
2014
+ // behind it; Dotwork cmqbt8b8 + Amplify t3 Sadhna: approve skipped while a real
2015
+ // needsApproval frontier sat behind it). The lane-scoped signal is the per-sender
2016
+ // existingRowFrontier stageCounts summed by workspaceActionableSupplyCensus NEVER
2017
+ // the pooled readyBuffer/coverage. When that census is non-empty the edge is a
2018
+ // masked-gap prep/approve and must bind so the bounded job consumes the frontier;
2019
+ // when the census is empty the edge is genuinely settled and stays deferred (no
2020
+ // re-prepare / re-approve loop).
1813
2021
  function exactPlanHasLaneScopedActionableFrontier(plan) {
1814
2022
  const census = workspaceActionableSupplyCensus(plan);
1815
2023
  return (census.approvalFrontier > 0 ||
@@ -3413,6 +3621,49 @@ export async function executeRefillSendsCommand(input = {}) {
3413
3621
  : supplyLandedOnCampaign
3414
3622
  ? clearCompleteDefersForCampaign(deferredCoordinatorWorkspaceRunState, selectedCoordinatorTarget?.campaignId ?? null)
3415
3623
  : deferredCoordinatorWorkspaceRunState;
3624
+ // fix(112aj): continuation-level progress-or-settle. When this dispatch is an
3625
+ // active_exact_run resume (continuationHandle set, in_progress), fingerprint
3626
+ // the fence's observable state; if the SAME fence has been re-invoked
3627
+ // ACTIVE_EXACT_RUN_REPLAY_CAP times with a byte-identical fingerprint (no
3628
+ // coverage/effect advance), settle it honestly instead of re-inviting an
3629
+ // identical continuation. A fence that advanced (any fingerprint field moved)
3630
+ // resets the counter and resumes normally.
3631
+ let progressBoundedWorkspaceRunState = coordinatorWorkspaceRunState;
3632
+ if (yolo && continuationHandle) {
3633
+ const fingerprint = activeExactRunProgressFingerprint(result);
3634
+ const priorProgress = recordValue(recordValue(scopedInput.workspaceRunState)?.activeExactRunProgress);
3635
+ const replayCount = fingerprint && stringValue(priorProgress?.fingerprint) === fingerprint
3636
+ ? (numberValue(priorProgress?.count) ?? 0) + 1
3637
+ : 1;
3638
+ if (fingerprint && replayCount >= ACTIVE_EXACT_RUN_REPLAY_CAP) {
3639
+ return {
3640
+ ...command,
3641
+ ok: false,
3642
+ status: "blocked",
3643
+ blocker: "no_forward_progress",
3644
+ note: `The active exact run (${continuationHandle.runId} fence ${continuationHandle.fence}) was resumed ${replayCount} times with no observable coverage or effect advance ` +
3645
+ "(identical gate/state fingerprint). Settling the fence instead of re-inviting an identical continuation; a fresh refill_sends command reconciles it. No run or mutation was started.",
3646
+ coordinator: {
3647
+ scope: "workspace",
3648
+ status: "blocked",
3649
+ },
3650
+ continuation: { required: false, userActionRequired: false },
3651
+ };
3652
+ }
3653
+ progressBoundedWorkspaceRunState = fingerprint
3654
+ ? {
3655
+ ...(recordValue(coordinatorWorkspaceRunState) ?? {}),
3656
+ activeExactRunProgress: { fingerprint, count: replayCount },
3657
+ }
3658
+ : coordinatorWorkspaceRunState;
3659
+ }
3660
+ else {
3661
+ const base = recordValue(coordinatorWorkspaceRunState);
3662
+ if (base && "activeExactRunProgress" in base) {
3663
+ const { activeExactRunProgress: _cleared, ...rest } = base;
3664
+ progressBoundedWorkspaceRunState = rest;
3665
+ }
3666
+ }
3416
3667
  const workspaceContinuationArguments = {
3417
3668
  yolo: true,
3418
3669
  executionMode: "yolo",
@@ -3432,8 +3683,8 @@ export async function executeRefillSendsCommand(input = {}) {
3432
3683
  : {}),
3433
3684
  approvalMode: scopedInput.approvalMode ?? "approve",
3434
3685
  ...(isWorkspaceCoordinator ? { workspaceCoordinator: true } : {}),
3435
- ...(coordinatorWorkspaceRunState
3436
- ? { workspaceRunState: coordinatorWorkspaceRunState }
3686
+ ...(progressBoundedWorkspaceRunState
3687
+ ? { workspaceRunState: progressBoundedWorkspaceRunState }
3437
3688
  : {}),
3438
3689
  };
3439
3690
  if (isWorkspaceCoordinator &&
@@ -3659,6 +3910,79 @@ export async function executeRefillSendsCommand(input = {}) {
3659
3910
  };
3660
3911
  }
3661
3912
  if (nextSelection.status === "blocked") {
3913
+ // fix(112ai, OWNER closing invariant): actionable_supply_not_queued is a
3914
+ // planner-exposure DEFECT terminal and is legal ONLY when no bounded
3915
+ // prep/approve job dispatched THIS command is still draining the census.
3916
+ // The af-c/ai census bypass admits the masked prepare/approve edge and
3917
+ // the coordinator dispatches a bounded job, but that job drains ASYNC:
3918
+ // the exact run returns exact_action_complete with a still-running
3919
+ // receipt (terminal:false) while the census still shows the un-drained
3920
+ // rows. Emitting actionable_supply_not_queued here (continuation
3921
+ // required:false) HALTS the yolo loop before the job drains — the exact
3922
+ // false-terminal the owner ruled unacceptable on sellable-t4
3923
+ // (prepare_messages:7fc0766d dispatched job cmrz555uc scanning while 681
3924
+ // enrich rows sat in the census). Recognize the in-flight job as the
3925
+ // queued supply: emit a healthy background-wait so the loop re-invokes,
3926
+ // the fast path probes the job by id (sidestepping the planner-exposure
3927
+ // gap that starved the reread), and the census drains (self-heal). This
3928
+ // makes "non-empty census + dispatched running job + halting defect
3929
+ // terminal" unrepresentable.
3930
+ const runningPrepReceipt = dispatchedRunningPrepReceipt(result, completedPrepFamily);
3931
+ if (nextSelection.code === "actionable_supply_not_queued" &&
3932
+ runningPrepReceipt &&
3933
+ selectedCoordinatorTarget) {
3934
+ const backgroundContinuationInput = workspaceCoordinatorInput(scopedInput);
3935
+ delete backgroundContinuationInput.expectedTargetShapeRevision;
3936
+ delete backgroundContinuationInput.expectedActionKey;
3937
+ const backgroundWait = {
3938
+ jobId: runningPrepReceipt.jobId,
3939
+ campaignId: selectedCoordinatorTarget.campaignId ?? null,
3940
+ tableId: selectedCoordinatorTarget.tableId ?? null,
3941
+ deadlineAt: new Date(Date.now() + BACKGROUND_WAIT_FAST_PATH_TTL_MS).toISOString(),
3942
+ };
3943
+ return {
3944
+ ...command,
3945
+ status: "in_progress",
3946
+ note: `The exact ${completedPrepFamily} edge dispatched a bounded preparation job (${runningPrepReceipt.jobId}) that is still draining the lane; the actionable census remains non-empty only because that job has not finished scanning. Waiting on the in-flight job instead of false-terminaling actionable_supply_not_queued — the loop re-invokes, the job drains the frontier, and coverage advances.`,
3947
+ targetPlan: nextSelection.workspacePlan,
3948
+ exactTargetPlan: nextSelection.exactTargetPlan ?? null,
3949
+ orchestration: {
3950
+ mode: "fenced_bounded_yolo",
3951
+ scope: "workspace_exact_target_coordinator",
3952
+ phase: "read_only_background_wait",
3953
+ },
3954
+ coordinator: {
3955
+ scope: "workspace",
3956
+ status: "waiting_background",
3957
+ completedTarget: selectedCoordinatorTarget,
3958
+ selectedTarget: selectedCoordinatorTarget,
3959
+ workspaceTargetPlan: nextSelection.workspacePlan,
3960
+ exactRunResult: result,
3961
+ backgroundJob: { jobId: runningPrepReceipt.jobId },
3962
+ },
3963
+ continuation: {
3964
+ required: true,
3965
+ kind: "workspace_background_wait",
3966
+ owner: "packaged_workflow",
3967
+ userActionRequired: false,
3968
+ tool: "refill_sends",
3969
+ retryAfterMs: runningPrepReceipt.retryAfterMs,
3970
+ arguments: {
3971
+ ...backgroundContinuationInput,
3972
+ yolo: true,
3973
+ executionMode: "yolo",
3974
+ requireWorkspace: true,
3975
+ workspaceId,
3976
+ workspaceCoordinator: true,
3977
+ backgroundWait,
3978
+ ...(coordinatorWorkspaceRunState
3979
+ ? { workspaceRunState: coordinatorWorkspaceRunState }
3980
+ : {}),
3981
+ },
3982
+ stopCondition: "A bounded preparation job dispatched this command is still draining the actionable census. After the bounded wait, replan the whole workspace; do not open or resume an exact fence solely to poll background work.",
3983
+ },
3984
+ };
3985
+ }
3662
3986
  const persistedDeferredBlocker = coordinatorDeferredBlocker(coordinatorWorkspaceRunState);
3663
3987
  const workspaceCoverage = yoloCoverage(nextSelection.workspacePlan);
3664
3988
  if (persistedDeferredBlocker === "loaded_awaiting_scheduler" &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.730",
3
+ "version": "0.1.732",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",