@sellable/mcp 0.1.757 → 0.1.758

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.
@@ -893,6 +893,39 @@ function runScopedSignalDiscoveryAttemptUpdate(action, runState, now) {
893
893
  ],
894
894
  };
895
895
  }
896
+ function runScopedSignalSourceValidationUpdate(action, runState, now) {
897
+ const toolInput = actionToolInput(action);
898
+ const provider = stringValue(toolInput.leadSourceProvider);
899
+ if (provider !== "signal-discovery" && provider !== "campaign-tracked-post") {
900
+ return {};
901
+ }
902
+ const campaignId = actionCampaignId(action);
903
+ const payload = resultPayload(runState.lastOutcome);
904
+ const reviewBatch = recordValue(payload.reviewBatch);
905
+ const reviewBatchBasisHash = stringValue(reviewBatch?.basisHash);
906
+ const reviewBatchRowCount = numberValue(reviewBatch?.rowCount);
907
+ if (!campaignId || !reviewBatchBasisHash || !reviewBatchRowCount)
908
+ return {};
909
+ const attempts = arrayValue(runState.signalSourceValidationAttempts).filter(isRecord);
910
+ return {
911
+ signalSourceValidationAttempts: [
912
+ ...attempts.filter((attempt) => stringValue(attempt.campaignId) !== campaignId),
913
+ {
914
+ campaignId,
915
+ tableId: actionTableId(action),
916
+ senderId: actionSenderId(action),
917
+ sourceLeadListId: stringValue(toolInput.sourceLeadListId),
918
+ sourceFingerprint: actionSourceFingerprint(action),
919
+ reviewBatchBasisHash,
920
+ reviewBatchRowCount,
921
+ threshold: 0.1,
922
+ status: "pending",
923
+ copiedAt: now.toISOString(),
924
+ scope: "refill_run",
925
+ },
926
+ ],
927
+ };
928
+ }
896
929
  // Phase 110-05: Sales Nav / Prospeo same-source continuation gets exactly one
897
930
  // bounded add per (campaign, sender) per run, mirroring the Signal Discovery
898
931
  // run-scoped attempt bound so the loop never repeats an unchanged failed
@@ -4513,6 +4546,14 @@ async function gateVerify(input, deps, ctx, budgets) {
4513
4546
  // keep going — instead of terminalizing the invite lane with confirmed-fillable
4514
4547
  // slots still open. Once a broadening round proves the universe dry this run, this
4515
4548
  // no longer intercepts and the refusal terminalizes typed below.
4549
+ if (actionType === "copy_selected_source_rows" &&
4550
+ stringValue(recordValue(ctx.runState.lastOutcome)?.status) ===
4551
+ "executed_and_reread") {
4552
+ return advanceBackToPlan(input, deps, ctx, {
4553
+ ...runScopedSignalSourceValidationUpdate(action, ctx.runState, deps.now?.() ?? new Date()),
4554
+ ...clearRunScopedDrySourcePrepAttempts(actionCampaignId(action), ctx.runState),
4555
+ });
4556
+ }
4516
4557
  if (actionType === "continue_signal_discovery_source" &&
4517
4558
  isSignalSelectionBroadenableRefusal(ctx.runState.lastOutcome) &&
4518
4559
  !hasRunScopedSignalUniverseExhausted(action, ctx.runState)) {
@@ -12,6 +12,7 @@ type RowSelector = {
12
12
  } | {
13
13
  type: "reviewBatch";
14
14
  limit?: number;
15
+ basisHash?: string;
15
16
  } | {
16
17
  type: "staleGeneratedMessages";
17
18
  limit?: number;
@@ -121,6 +122,11 @@ export declare const campaignMessagePreparationToolDefinitions: ({
121
122
  minimum: number;
122
123
  maximum: number;
123
124
  };
125
+ basisHash: {
126
+ type: string;
127
+ minLength: number;
128
+ maxLength: number;
129
+ };
124
130
  rowIds: {
125
131
  type: string;
126
132
  items: {
@@ -75,6 +75,7 @@ export const campaignMessagePreparationToolDefinitions = [
75
75
  ],
76
76
  },
77
77
  limit: { type: "number", minimum: 1, maximum: 500 },
78
+ basisHash: { type: "string", minLength: 1, maxLength: 200 },
78
79
  rowIds: {
79
80
  type: "array",
80
81
  items: { type: "string", minLength: 1 },
@@ -120,6 +120,7 @@ export type PrepareRowSelector = {
120
120
  } | {
121
121
  type: "reviewBatch";
122
122
  limit?: number;
123
+ basisHash?: string;
123
124
  } | {
124
125
  type: "staleGeneratedMessages";
125
126
  limit?: number;
@@ -450,9 +450,11 @@ export function prepareRowSelectorValue(value) {
450
450
  return undefined;
451
451
  }
452
452
  const limit = numberValue(selector?.limit);
453
+ const basisHash = type === "reviewBatch" ? stringValue(selector?.basisHash) : null;
453
454
  return {
454
455
  type,
455
456
  ...(limit && limit > 0 ? { limit: Math.floor(limit) } : {}),
457
+ ...(type === "reviewBatch" && basisHash ? { basisHash } : {}),
456
458
  };
457
459
  }
458
460
  export function uniqueStrings(values) {
@@ -1052,7 +1054,9 @@ export function refillPrepareRequestHash(params) {
1052
1054
  ? `rowIds:${createHash("sha256")
1053
1055
  .update(JSON.stringify([...params.rowSelector.rowIds].sort()))
1054
1056
  .digest("hex")}`
1055
- : `${params.rowSelector.type}:${params.rowSelector.limit ?? "no-limit"}`
1057
+ : params.rowSelector.type === "reviewBatch"
1058
+ ? `reviewBatch:${params.rowSelector.limit ?? "no-limit"}:${params.rowSelector.basisHash ?? "no-basis"}`
1059
+ : `${params.rowSelector.type}:${params.rowSelector.limit ?? "no-limit"}`
1056
1060
  : "no-row-selector";
1057
1061
  // Request hashes are persisted in the preparation job's normalized config.
1058
1062
  // Keep the public identity fixed-width: the former concatenated form could
@@ -3221,6 +3225,16 @@ export async function executeOneYoloPrimitive(action, workspaceId) {
3221
3225
  const allowPartialSourceList = toolInput.allowPartialSourceList === true;
3222
3226
  const sourceFingerprint = stringValue(toolInput.sourceFingerprint) ??
3223
3227
  stringValue(actionIds(action).sourceFingerprint);
3228
+ const signalSource = actionLeadSourceProvider(action) === "signal-discovery" ||
3229
+ actionLeadSourceProvider(action) === "campaign-tracked-post";
3230
+ const requestedReviewBatchLimit = sourceRowIds.length > 0
3231
+ ? sourceRowIds.length
3232
+ : (sourceRowLimit ?? undefined);
3233
+ const reviewBatchLimit = requestedReviewBatchLimit
3234
+ ? signalSource
3235
+ ? Math.min(15, requestedReviewBatchLimit)
3236
+ : requestedReviewBatchLimit
3237
+ : undefined;
3224
3238
  if (!campaignOfferId || !sourceLeadListId || !sourceFingerprint) {
3225
3239
  return {
3226
3240
  status: "refused",
@@ -3248,9 +3262,7 @@ export async function executeOneYoloPrimitive(action, workspaceId) {
3248
3262
  ...(workspaceId ? { workspaceId } : {}),
3249
3263
  sourceRowIds: sourceRowIds.length > 0 ? sourceRowIds : undefined,
3250
3264
  sourceRowLimit: sourceRowIds.length > 0 ? undefined : (sourceRowLimit ?? undefined),
3251
- reviewBatchLimit: sourceRowIds.length > 0
3252
- ? sourceRowIds.length
3253
- : (sourceRowLimit ?? undefined),
3265
+ reviewBatchLimit,
3254
3266
  ...(allowPartialSourceList ? { allowPartialSourceList: true } : {}),
3255
3267
  };
3256
3268
  let result;
@@ -3288,16 +3300,19 @@ export async function executeOneYoloPrimitive(action, workspaceId) {
3288
3300
  const retrySourceRowLimit = retrySourceRowIds.length > 0
3289
3301
  ? undefined
3290
3302
  : Math.min(sourceRowLimit ?? remainingRows, remainingRows);
3291
- const retryReviewBatchLimit = retrySourceRowIds.length > 0
3303
+ const retryCopyLimit = retrySourceRowIds.length > 0
3292
3304
  ? retrySourceRowIds.length
3293
3305
  : retrySourceRowLimit;
3294
- if (!retryReviewBatchLimit || retryReviewBatchLimit <= 0) {
3306
+ if (!retryCopyLimit || retryCopyLimit <= 0) {
3295
3307
  return {
3296
3308
  status: "refused",
3297
3309
  refusalReason: rowLimitPayload.error ??
3298
3310
  "selected campaign table cannot accept more workflow rows",
3299
3311
  };
3300
3312
  }
3313
+ const retryReviewBatchLimit = signalSource
3314
+ ? Math.min(15, retryCopyLimit)
3315
+ : retryCopyLimit;
3301
3316
  const retryResult = await confirmLeadList({
3302
3317
  ...copyInput,
3303
3318
  sourceRowIds: retrySourceRowIds.length > 0 ? retrySourceRowIds : undefined,
@@ -247,6 +247,7 @@ const WORKSPACE_ATTEMPT_LEDGER_KEYS = [
247
247
  "signalDiscoveryAttempts",
248
248
  "providerSourceAttempts",
249
249
  "broadenSignalSearchAttempts",
250
+ "signalSourceValidationAttempts",
250
251
  "broadenProviderSearchAttempts",
251
252
  "rubricRelaxationAttempts",
252
253
  "senderPostRefreshAttempts",
@@ -790,8 +791,29 @@ function sameWorkspaceExactAction(left, right) {
790
791
  Boolean(left?.actionKey) &&
791
792
  left?.actionKey === right.actionKey);
792
793
  }
794
+ function coordinatorSenderIds(input) {
795
+ const explicit = normalizeStrings(input.senderIds);
796
+ if (explicit.length > 0)
797
+ return explicit;
798
+ const targetConfigSenderIds = normalizeStrings(input.targetConfig?.senderScope.senderIds);
799
+ if (targetConfigSenderIds.length > 0)
800
+ return targetConfigSenderIds;
801
+ const runState = recordValue(input.workspaceRunState);
802
+ const senderCursors = Array.isArray(runState?.senderCursors)
803
+ ? runState.senderCursors
804
+ : [];
805
+ return normalizeStrings(senderCursors
806
+ .map((entry) => stringValue(recordValue(entry)?.senderId))
807
+ .filter((senderId) => Boolean(senderId)));
808
+ }
793
809
  function workspaceCoordinatorInput(input) {
794
810
  const coordinatorInput = { ...input };
811
+ const senderIds = coordinatorSenderIds(input);
812
+ if (senderIds.length > 0) {
813
+ coordinatorInput.senderIds = senderIds;
814
+ coordinatorInput.senders = [];
815
+ coordinatorInput.senderNames = [];
816
+ }
795
817
  delete coordinatorInput.campaignId;
796
818
  delete coordinatorInput.tableId;
797
819
  delete coordinatorInput.runId;
@@ -843,7 +865,9 @@ function withKeywordsHandoffScope(result, scopedInput) {
843
865
  (handleRunId && typeof handleFence === "number"
844
866
  ? { runId: handleRunId, fence: handleFence }
845
867
  : null);
868
+ const senderIds = coordinatorSenderIds(scopedInput);
846
869
  const scopeAdditions = {
870
+ ...(senderIds.length > 0 ? { senderIds } : {}),
847
871
  ...(scopedInput.targetDate ? { targetDate: scopedInput.targetDate } : {}),
848
872
  ...(scopedInput.untilDate ? { untilDate: scopedInput.untilDate } : {}),
849
873
  ...(typeof scopedInput.horizonSendDays === "number"
@@ -4150,7 +4174,7 @@ export async function executeRefillSendsCommand(input = {}) {
4150
4174
  intent: targetConfigIntent(continuationTargetConfig) ??
4151
4175
  scopedInput.intent ??
4152
4176
  "auto",
4153
- senderIds: normalizeStrings(scopedInput.senderIds),
4177
+ senderIds: coordinatorSenderIds(scopedInput),
4154
4178
  ...(scopedInput.actionTypes
4155
4179
  ? { actionTypes: scopedInput.actionTypes }
4156
4180
  : {}),
@@ -6453,6 +6453,11 @@ export declare const allTools: ({
6453
6453
  minimum: number;
6454
6454
  maximum: number;
6455
6455
  };
6456
+ basisHash: {
6457
+ type: string;
6458
+ minLength: number;
6459
+ maxLength: number;
6460
+ };
6456
6461
  rowIds: {
6457
6462
  type: string;
6458
6463
  items: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.757",
3
+ "version": "0.1.758",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",