@sellable/mcp 0.1.600 → 0.1.601-phase110.20260717111356

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.
@@ -224,35 +224,15 @@ function sanitizePrepFailureDiagnosis(value) {
224
224
  },
225
225
  };
226
226
  }
227
- function evidenceLatestMs(evidence) {
228
- const latestAt = evidence.latestAt;
229
- if (typeof latestAt !== "string")
230
- return 0;
231
- const timestamp = new Date(latestAt).getTime();
232
- return Number.isFinite(timestamp) ? timestamp : 0;
233
- }
234
- function chooseEvidenceAction(evidence, fallback) {
235
- const byAction = new Map();
236
- for (const item of evidence) {
237
- if (!isRecord(item))
238
- continue;
239
- const actionType = allowedActionType(item.actionType);
240
- if (!actionType)
241
- continue;
242
- const current = byAction.get(actionType) ?? { count: 0, latestMs: 0 };
243
- current.count += numberValue(item.count);
244
- current.latestMs = Math.max(current.latestMs, evidenceLatestMs(item));
245
- byAction.set(actionType, current);
246
- }
247
- const [best] = [...byAction.entries()].sort((a, b) => {
248
- if (a[1].latestMs !== b[1].latestMs)
249
- return b[1].latestMs - a[1].latestMs;
250
- if (a[1].count !== b[1].count)
251
- return b[1].count - a[1].count;
252
- return (REFILL_TARGET_ACTION_TYPES.indexOf(a[0]) -
253
- REFILL_TARGET_ACTION_TYPES.indexOf(b[0]));
254
- });
255
- return best?.[0] ?? fallback[0] ?? null;
227
+ // Phase 110: the backend planner is the single authority on lane selection —
228
+ // it collapses to one lane for non-cascade senders and emits BOTH lanes for
229
+ // invite-capable Sales Nav cascades. This wrapper must not re-collapse
230
+ // (zero-shot i02-clover receipt: request.actionTypes was forced to a single
231
+ // lane with no caller override, so paid lanes with available credits were
232
+ // never targeted). Lanes are ordered by REFILL_TARGET_ACTION_TYPES.
233
+ function orderedLanes(lanes) {
234
+ const laneSet = new Set(lanes);
235
+ return REFILL_TARGET_ACTION_TYPES.filter((lane) => laneSet.has(lane));
256
236
  }
257
237
  function senderActionKey(senderId, actionType) {
258
238
  return typeof senderId === "string" && typeof actionType === "string"
@@ -650,8 +630,8 @@ function sanitizeEligibleSenderLedger(value, selectedBySender) {
650
630
  return selectedBySender.has(row.senderId);
651
631
  })
652
632
  .map((row) => {
653
- const selectedLane = selectedBySender.get(row.senderId);
654
- const eligibleActionTypes = actionTypesFrom(row.eligibleActionTypes).filter((actionType) => actionType === selectedLane);
633
+ const selectedLanes = selectedBySender.get(row.senderId) ?? new Set();
634
+ const eligibleActionTypes = actionTypesFrom(row.eligibleActionTypes).filter((actionType) => selectedLanes.has(actionType));
655
635
  return {
656
636
  senderId: row.senderId,
657
637
  senderName: stringValue(row.senderName) ?? "",
@@ -841,7 +821,7 @@ function sanitizeStructuredSenderPlans(value, selectedBySender) {
841
821
  if (!isRecord(plan) || typeof plan.senderId !== "string")
842
822
  return false;
843
823
  const selectedLane = allowedActionType(plan.selectedLane);
844
- return Boolean(selectedLane && selectedBySender.get(plan.senderId) === selectedLane);
824
+ return Boolean(selectedLane && selectedBySender.get(plan.senderId)?.has(selectedLane));
845
825
  })
846
826
  .map((plan) => {
847
827
  const paidInmail = sanitizePaidInmail(plan.paidInmail);
@@ -1038,7 +1018,7 @@ function sanitizeTargetSchedulerSweep(params) {
1038
1018
  }
1039
1019
  const expectedSenderIds = [...params.sweepEligibleBySender.keys()].sort();
1040
1020
  const expectedActionTypes = [
1041
- ...new Set(params.sweepEligibleBySender.values()),
1021
+ ...new Set([...params.sweepEligibleBySender.values()].flatMap((lanes) => [...lanes])),
1042
1022
  ].sort();
1043
1023
  if (expectedSenderIds.length === 0 || expectedActionTypes.length === 0) {
1044
1024
  return null;
@@ -1324,31 +1304,36 @@ function sanitizeRefillTargetPlanResult(result) {
1324
1304
  const rawSenderPlans = Array.isArray(target.senderPlans)
1325
1305
  ? target.senderPlans
1326
1306
  : [];
1307
+ // Phase 110: accept every senderId:actionType pair the backend emitted —
1308
+ // the backend planner already decided lane selection (single-lane for
1309
+ // non-cascades, both lanes for invite-capable cascades). Do not pick a
1310
+ // winner per sender here.
1327
1311
  const selectedBySender = new Map();
1328
- for (const rawSelection of rawSelections) {
1329
- if (!isRecord(rawSelection) || typeof rawSelection.senderId !== "string") {
1330
- continue;
1331
- }
1332
- const fallback = actionTypesFrom(rawSelection.actionTypes);
1333
- const evidence = Array.isArray(rawSelection.evidence)
1334
- ? rawSelection.evidence
1335
- : [];
1336
- const chosen = chooseEvidenceAction(evidence, fallback);
1337
- if (chosen)
1338
- selectedBySender.set(rawSelection.senderId, chosen);
1339
- }
1312
+ const addSelectedLane = (senderId, actionType) => {
1313
+ const lanes = selectedBySender.get(senderId) ?? new Set();
1314
+ lanes.add(actionType);
1315
+ selectedBySender.set(senderId, lanes);
1316
+ };
1340
1317
  for (const rawPlan of rawSenderPlans) {
1341
- if (!isRecord(rawPlan) ||
1342
- typeof rawPlan.senderId !== "string" ||
1343
- selectedBySender.has(rawPlan.senderId)) {
1318
+ if (!isRecord(rawPlan) || typeof rawPlan.senderId !== "string")
1344
1319
  continue;
1345
- }
1346
1320
  const actionType = allowedActionType(rawPlan.actionType);
1347
1321
  if (actionType)
1348
- selectedBySender.set(rawPlan.senderId, actionType);
1322
+ addSelectedLane(rawPlan.senderId, actionType);
1323
+ }
1324
+ if (selectedBySender.size === 0) {
1325
+ for (const rawSelection of rawSelections) {
1326
+ if (!isRecord(rawSelection) ||
1327
+ typeof rawSelection.senderId !== "string") {
1328
+ continue;
1329
+ }
1330
+ for (const actionType of actionTypesFrom(rawSelection.actionTypes)) {
1331
+ addSelectedLane(rawSelection.senderId, actionType);
1332
+ }
1333
+ }
1349
1334
  }
1350
- const selectedKeys = new Set([...selectedBySender.entries()].map(([senderId, actionType]) => `${senderId}:${actionType}`));
1351
- const actionTypes = [...new Set(selectedBySender.values())];
1335
+ const selectedKeys = new Set([...selectedBySender.entries()].flatMap(([senderId, lanes]) => [...lanes].map((actionType) => `${senderId}:${actionType}`)));
1336
+ const actionTypes = orderedLanes([...selectedBySender.values()].flatMap((lanes) => [...lanes]));
1352
1337
  if (selectedKeys.size === 0)
1353
1338
  return emptyUnsupportedResult(result);
1354
1339
  const senderPlans = rawSenderPlans.filter((plan) => isRecord(plan) &&
@@ -1362,13 +1347,13 @@ function sanitizeRefillTargetPlanResult(result) {
1362
1347
  typeof selection.senderId === "string" &&
1363
1348
  selectedBySender.has(selection.senderId))
1364
1349
  .map((selection) => {
1365
- const actionType = selectedBySender.get(selection.senderId);
1350
+ const lanes = selectedBySender.get(selection.senderId) ?? new Set();
1366
1351
  const evidence = Array.isArray(selection.evidence)
1367
- ? selection.evidence.filter((item) => isRecord(item) && item.actionType === actionType)
1352
+ ? selection.evidence.filter((item) => isRecord(item) && lanes.has(String(item.actionType)))
1368
1353
  : [];
1369
1354
  return {
1370
1355
  ...selection,
1371
- actionTypes: actionType ? [actionType] : [],
1356
+ actionTypes: orderedLanes(lanes),
1372
1357
  evidence,
1373
1358
  };
1374
1359
  });
@@ -1401,7 +1386,9 @@ function sanitizeRefillTargetPlanResult(result) {
1401
1386
  numberValue(plan.remainingProjectedGap) > 0 &&
1402
1387
  numberValue(plan.remainingReadyOrProjectedGap) === 0 &&
1403
1388
  numberValue(plan.readyBuffer) > 0) {
1404
- sweepEligibleBySender.set(senderId, actionType);
1389
+ const lanes = sweepEligibleBySender.get(senderId) ?? new Set();
1390
+ lanes.add(actionType);
1391
+ sweepEligibleBySender.set(senderId, lanes);
1405
1392
  }
1406
1393
  }
1407
1394
  const schedulerSweepAction = sanitizeTargetSchedulerSweep({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.600",
3
+ "version": "0.1.601-phase110.20260717111356",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",