@sellable/mcp 0.1.738 → 0.1.739

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.
@@ -172,6 +172,21 @@ type SchedulerRefusalDisposition = {
172
172
  };
173
173
  declare function classifySchedulerRefusal(reason: string | null): SchedulerRefusalDisposition;
174
174
  declare function schedulerSweepNoOpBlocker(ctx: LoopContext, reportInput: Record<string, unknown>): Record<string, unknown> | null;
175
+ declare function coverageOnlySignature(plan: Record<string, unknown>): {
176
+ sent: number;
177
+ scheduled: number;
178
+ ready: number;
179
+ goal: number;
180
+ };
181
+ declare function nextCoverageStallState(ctx: LoopContext, plan: Record<string, unknown>): {
182
+ signature: {
183
+ sent: number;
184
+ scheduled: number;
185
+ ready: number;
186
+ goal: number;
187
+ };
188
+ repeatCount: number;
189
+ };
175
190
  export declare function packetCoherenceFailure(plan: Record<string, unknown>): string | null;
176
191
  export declare function terminalProjection(value: {
177
192
  targetConfig?: unknown;
@@ -190,5 +205,8 @@ export declare const refillRunLoopInternals: {
190
205
  schedulerSweepNoOpBlocker: typeof schedulerSweepNoOpBlocker;
191
206
  dominantSchedulerRefusalReason: typeof dominantSchedulerRefusalReason;
192
207
  classifySchedulerRefusal: typeof classifySchedulerRefusal;
208
+ coverageOnlySignature: typeof coverageOnlySignature;
209
+ nextCoverageStallState: typeof nextCoverageStallState;
210
+ COVERAGE_STALL_LIMIT: number;
193
211
  };
194
212
  export {};
@@ -1864,6 +1864,46 @@ function nextNoProgressState(ctx, plan, action) {
1864
1864
  repeatCount,
1865
1865
  };
1866
1866
  }
1867
+ // fix(112an, UNIVERSAL progress-or-settle): 112aj's bound keys on
1868
+ // noProgressSignature, which includes actionFingerprint (type:campaign:table:
1869
+ // sender:actionKey:effectId). A loop that re-plans a DIFFERENT action each
1870
+ // iteration therefore resets the counter and never trips it, no matter how many
1871
+ // iterations produce zero coverage movement. Both expensive livelocks we hit
1872
+ // had exactly that shape:
1873
+ // - Dotwork s3: 136 refill_sends calls, 21 exact_action_complete, coverage
1874
+ // frozen at {sent:0, scheduled:0, ready:20, goal:50} throughout.
1875
+ // - sellable FINAL: 6 of 7 exact runs returned byte-identical coverage
1876
+ // {goal:180, ready:5, scheduled:9, sent:0}, each under a distinct
1877
+ // next_exact_target, so 112aj saw six "different" actions.
1878
+ // This is the coverage-only companion: it ignores WHICH action ran and asks
1879
+ // only whether the world moved. A command that cannot move sent/scheduled/ready
1880
+ // across COVERAGE_STALL_LIMIT consecutive iterations is burning wall-clock and
1881
+ // credits for nothing and must settle with an honest terminal naming the frozen
1882
+ // counters and the last action it attempted.
1883
+ //
1884
+ // Threshold is deliberately looser than 112aj's 3: distinct actions legitimately
1885
+ // run in sequence before coverage moves (prepare -> approve -> sweep), so a tight
1886
+ // cap would cut off real ladders. Six is above the longest legitimate
1887
+ // zero-movement prefix observed across the 8-workspace matrix (the healthy rows
1888
+ // settle in 0-20 calls; only true stalls exceed it).
1889
+ const COVERAGE_STALL_LIMIT = 6;
1890
+ function coverageOnlySignature(plan) {
1891
+ const coverage = coverageSnapshot(plan);
1892
+ return {
1893
+ sent: coverage.sent,
1894
+ scheduled: coverage.scheduled,
1895
+ ready: coverage.ready,
1896
+ goal: coverage.goal,
1897
+ };
1898
+ }
1899
+ function nextCoverageStallState(ctx, plan) {
1900
+ const next = coverageOnlySignature(plan);
1901
+ const prior = recordValue(runStateProgress(ctx).coverageStall) ?? {};
1902
+ const priorSignature = recordValue(prior.signature);
1903
+ const same = priorSignature !== undefined && safeJson(priorSignature) === safeJson(next);
1904
+ const repeatCount = same ? (numberValue(prior.repeatCount) ?? 1) + 1 : 1;
1905
+ return { signature: next, repeatCount };
1906
+ }
1867
1907
  function sourceHandoffText(action) {
1868
1908
  const campaignId = actionCampaignId(action) ?? "campaign";
1869
1909
  const toolInput = actionToolInput(action);
@@ -2601,6 +2641,11 @@ async function gatePlan(input, deps, ctx) {
2601
2641
  fallbackSummary: line.fallbackSummary,
2602
2642
  }));
2603
2643
  const fingerprint = fingerprintFromPlan(plan);
2644
+ // fix(112an): universal coverage-only bound. Evaluated BEFORE 112aj's
2645
+ // action-scoped bound only for its counter update, but enforced AFTER it so
2646
+ // the more specific typed terminals (broaden keyword handoff, etc.) keep
2647
+ // precedence when they apply. See COVERAGE_STALL_LIMIT.
2648
+ const coverageStall = nextCoverageStallState(ctx, plan);
2604
2649
  const noProgress = nextNoProgressState(ctx, plan, action);
2605
2650
  if (noProgress.repeatCount >= 3) {
2606
2651
  // fix(111y, CloverL): when the stuck rung is a broaden whose derived tier
@@ -2665,14 +2710,33 @@ async function gatePlan(input, deps, ctx) {
2665
2710
  blocker: "no_forward_progress",
2666
2711
  stuckRung: stringValue(action.evergreenRung) ?? stringValue(action.type),
2667
2712
  repeatedHeadAction: action,
2668
- progress: { noProgress },
2713
+ progress: { noProgress, coverageStall },
2669
2714
  guidance: "The same refill rung repeated three times with no coverage, frontier, or lane movement.",
2670
2715
  });
2671
2716
  }
2717
+ // fix(112an): universal bound — the world did not move for
2718
+ // COVERAGE_STALL_LIMIT consecutive iterations, regardless of which action ran
2719
+ // each time. Settle with an honest terminal naming the frozen counters rather
2720
+ // than burning further wall-clock and credits on a command that cannot advance.
2721
+ if (coverageStall.repeatCount >= COVERAGE_STALL_LIMIT) {
2722
+ const frozen = coverageStall.signature;
2723
+ return completeTerminal(input, deps, ctx, "blocked", {
2724
+ blocker: "no_coverage_movement",
2725
+ stuckRung: stringValue(action.evergreenRung) ?? stringValue(action.type),
2726
+ repeatedHeadAction: action,
2727
+ progress: { coverageStall },
2728
+ coverage: frozen,
2729
+ guidance: `Coverage did not move across ${COVERAGE_STALL_LIMIT} consecutive refill iterations ` +
2730
+ `(sent ${frozen.sent}, scheduled ${frozen.scheduled}, ready ${frozen.ready}, ` +
2731
+ `goal ${frozen.goal}); the actions differed each time, so this is a workspace-level ` +
2732
+ "stall rather than one repeated rung. Settling instead of continuing to spend. " +
2733
+ "Re-run after the underlying blocker changes; the last attempted action is reported above.",
2734
+ });
2735
+ }
2672
2736
  const nextRunState = mergeRunState(ctx.runState, {
2673
2737
  lastPlan: fingerprint,
2674
2738
  headAction: action,
2675
- progress: mergeProgress(ctx, { noProgress }),
2739
+ progress: mergeProgress(ctx, { noProgress, coverageStall }),
2676
2740
  });
2677
2741
  const advanced = await deps.runClient.advanceRefillRunGateRemote({
2678
2742
  workspaceId: input.workspaceId,
@@ -4826,4 +4890,7 @@ export const refillRunLoopInternals = {
4826
4890
  schedulerSweepNoOpBlocker,
4827
4891
  dominantSchedulerRefusalReason,
4828
4892
  classifySchedulerRefusal,
4893
+ coverageOnlySignature,
4894
+ nextCoverageStallState,
4895
+ COVERAGE_STALL_LIMIT,
4829
4896
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.738",
3
+ "version": "0.1.739",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",
@@ -1,9 +0,0 @@
1
- {
2
- "parallelMode": "wide",
3
- "agentCount": 6,
4
- "maxToolCallsPerAgent": 2,
5
- "senderMaxAgents": 2,
6
- "senderMaxToolCallsPerAgent": 3,
7
- "progressMode": true,
8
- "debugMode": true
9
- }