@sellable/mcp 0.1.614 → 0.1.615-wip.121.2

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.
@@ -470,8 +470,67 @@ function selectedLaneAuthority(plan, action) {
470
470
  planRevision: stringValue(plan.planRevision),
471
471
  };
472
472
  }
473
+ const PROVIDER_SAVED_SEARCH_OUTCOME_CLASSES = new Set([
474
+ "stale_packet_replan",
475
+ "source_terminal",
476
+ "authorization_blocker",
477
+ "capacity",
478
+ "transient",
479
+ "uncertain_effect",
480
+ "executed",
481
+ ]);
482
+ function providerSavedSearchOutcome(value) {
483
+ const envelope = recordValue(value) ?? {};
484
+ const result = recordValue(envelope.result) ?? {};
485
+ const typedCandidate = recordValue(envelope.providerOutcome) ??
486
+ recordValue(result.providerOutcome) ??
487
+ (PROVIDER_SAVED_SEARCH_OUTCOME_CLASSES.has(stringValue(envelope.class) ?? "")
488
+ ? envelope
489
+ : null);
490
+ const legacyJobId = stringValue(recordValue(result.importResult)?.jobId) ??
491
+ stringValue(result.jobId);
492
+ const candidate = typedCandidate ??
493
+ (stringValue(envelope.status) === "executed_and_reread" && legacyJobId
494
+ ? {
495
+ class: "executed",
496
+ code: "provider_job_started",
497
+ detail: "Compatibility-normalized confirmed provider job receipt.",
498
+ jobId: legacyJobId,
499
+ facts: { jobId: legacyJobId },
500
+ }
501
+ : null);
502
+ const outcomeClass = stringValue(candidate?.class);
503
+ return outcomeClass && PROVIDER_SAVED_SEARCH_OUTCOME_CLASSES.has(outcomeClass)
504
+ ? candidate
505
+ : null;
506
+ }
507
+ function providerPreExecutorOutcome(action) {
508
+ return providerSavedSearchOutcome(action.preExecutorOutcome);
509
+ }
510
+ function providerActionIdentity(action) {
511
+ return {
512
+ actionKey: stringValue(action.actionKey),
513
+ effectId: stringValue(action.effectId) ??
514
+ stringValue(actionToolInput(action).effectId),
515
+ };
516
+ }
517
+ function providerAttemptMatchesAction(attempt, action) {
518
+ const identity = providerActionIdentity(action);
519
+ return Boolean(identity.actionKey &&
520
+ identity.effectId &&
521
+ stringValue(attempt.actionKey) === identity.actionKey &&
522
+ stringValue(attempt.effectId) === identity.effectId);
523
+ }
473
524
  function laneExhaustionReason(action, outcome) {
474
525
  const type = stringValue(action.type);
526
+ if (type &&
527
+ (PROVIDER_SOURCE_CONTINUATION_TYPES.has(type) ||
528
+ BROADEN_PROVIDER_SEARCH_TYPES.has(type))) {
529
+ const providerOutcome = providerSavedSearchOutcome(outcome);
530
+ return stringValue(providerOutcome?.class) === "source_terminal"
531
+ ? (stringValue(providerOutcome?.code) ?? "provider_source_terminal")
532
+ : null;
533
+ }
475
534
  if (type === "next_campaign") {
476
535
  return (stringValue(recordValue(actionToolInput(action).laneAdvance)?.reason) ??
477
536
  "fresh_planner_requested_lane_advance");
@@ -553,30 +612,27 @@ const PROVIDER_SOURCE_CONTINUATION_TYPES = new Set([
553
612
  "continue_prospeo_source",
554
613
  ]);
555
614
  function hasRunScopedProviderSourceAttempt(action, runState) {
556
- const campaignId = actionCampaignId(action);
557
- const senderId = actionSenderId(action);
558
- if (!campaignId)
559
- return false;
560
615
  return arrayValue(runState.providerSourceAttempts)
561
616
  .filter(isRecord)
562
- .some((attempt) => stringValue(attempt.campaignId) === campaignId &&
563
- stringValue(attempt.senderId) === senderId);
617
+ .some((attempt) => providerAttemptMatchesAction(attempt, action));
564
618
  }
565
619
  function runScopedProviderSourceAttemptUpdate(action, runState, now) {
566
620
  const campaignId = actionCampaignId(action);
567
621
  const senderId = actionSenderId(action);
568
- if (!campaignId)
622
+ const identity = providerActionIdentity(action);
623
+ if (!campaignId || !identity.actionKey || !identity.effectId)
569
624
  return {};
570
625
  const attempts = arrayValue(runState.providerSourceAttempts).filter(isRecord);
571
626
  return {
572
627
  providerSourceAttempts: [
573
- ...attempts.filter((attempt) => stringValue(attempt.campaignId) !== campaignId ||
574
- stringValue(attempt.senderId) !== senderId),
628
+ ...attempts.filter((attempt) => !providerAttemptMatchesAction(attempt, action)),
575
629
  {
630
+ ...identity,
576
631
  campaignId,
577
632
  senderId,
578
633
  provider: stringValue(actionToolInput(action).provider),
579
634
  sourceFingerprint: actionSourceFingerprint(action),
635
+ outcomeClass: stringValue(providerSavedSearchOutcome(runState.lastOutcome)?.class) ?? stringValue(providerPreExecutorOutcome(action)?.class),
580
636
  attemptedAt: now.toISOString(),
581
637
  scope: "refill_run",
582
638
  },
@@ -589,26 +645,26 @@ function runScopedProviderSourceAttemptUpdate(action, runState, now) {
589
645
  // second executor dispatch, mirroring the cold-continuation one-attempt bound.
590
646
  const BROADEN_PROVIDER_SEARCH_TYPES = new Set(["broaden_provider_search"]);
591
647
  function hasRunScopedBroadenProviderSearchAttempt(action, runState) {
592
- const campaignId = actionCampaignId(action);
593
- if (!campaignId)
594
- return false;
595
648
  return arrayValue(runState.broadenProviderSearchAttempts)
596
649
  .filter(isRecord)
597
- .some((attempt) => stringValue(attempt.campaignId) === campaignId);
650
+ .some((attempt) => providerAttemptMatchesAction(attempt, action));
598
651
  }
599
652
  function runScopedBroadenProviderSearchAttemptUpdate(action, runState, now) {
600
653
  const campaignId = actionCampaignId(action);
601
- if (!campaignId)
654
+ const identity = providerActionIdentity(action);
655
+ if (!campaignId || !identity.actionKey || !identity.effectId)
602
656
  return {};
603
657
  const attempts = arrayValue(runState.broadenProviderSearchAttempts).filter(isRecord);
604
658
  return {
605
659
  broadenProviderSearchAttempts: [
606
- ...attempts.filter((attempt) => stringValue(attempt.campaignId) !== campaignId),
660
+ ...attempts.filter((attempt) => !providerAttemptMatchesAction(attempt, action)),
607
661
  {
662
+ ...identity,
608
663
  campaignId,
609
664
  senderId: actionSenderId(action),
610
665
  provider: stringValue(actionToolInput(action).provider),
611
666
  sourceFingerprint: actionSourceFingerprint(action),
667
+ outcomeClass: stringValue(providerSavedSearchOutcome(runState.lastOutcome)?.class) ?? stringValue(providerPreExecutorOutcome(action)?.class),
612
668
  attemptedAt: now.toISOString(),
613
669
  scope: "refill_run",
614
670
  },
@@ -1263,6 +1319,9 @@ function noProgressSignature(plan, action) {
1263
1319
  actionCampaignId(action),
1264
1320
  actionTableId(action),
1265
1321
  actionSenderId(action),
1322
+ stringValue(action.actionKey),
1323
+ stringValue(action.effectId) ??
1324
+ stringValue(actionToolInput(action).effectId),
1266
1325
  ]
1267
1326
  .filter(Boolean)
1268
1327
  .join(":"),
@@ -1963,7 +2022,42 @@ async function gateExecute(input, deps, ctx) {
1963
2022
  `${ctx.runId}:${ctx.gateSeq}`;
1964
2023
  let did;
1965
2024
  let outcome;
1966
- if (action.type === "continue_signal_discovery_source" &&
2025
+ const preExecutorProviderOutcome = providerPreExecutorOutcome(action);
2026
+ const repeatedProviderAttempt = (typeof action.type === "string" &&
2027
+ PROVIDER_SOURCE_CONTINUATION_TYPES.has(action.type) &&
2028
+ hasRunScopedProviderSourceAttempt(action, ctx.runState)) ||
2029
+ (typeof action.type === "string" &&
2030
+ BROADEN_PROVIDER_SEARCH_TYPES.has(action.type) &&
2031
+ hasRunScopedBroadenProviderSearchAttempt(action, ctx.runState));
2032
+ if (preExecutorProviderOutcome && repeatedProviderAttempt) {
2033
+ const providerOutcome = {
2034
+ class: "stale_packet_replan",
2035
+ code: "provider_action_effect_already_attempted",
2036
+ detail: "This actionKey/effectId already owns a recorded provider attempt in the fenced run.",
2037
+ facts: {
2038
+ ...providerActionIdentity(action),
2039
+ campaignId: actionCampaignId(action),
2040
+ senderId: actionSenderId(action),
2041
+ },
2042
+ };
2043
+ did = {
2044
+ status: "refused",
2045
+ refusalReason: `${providerOutcome.code}: ${providerOutcome.detail}`,
2046
+ providerOutcome,
2047
+ result: { providerOutcome },
2048
+ };
2049
+ outcome = did;
2050
+ }
2051
+ else if (preExecutorProviderOutcome) {
2052
+ did = {
2053
+ status: "refused",
2054
+ refusalReason: `${String(preExecutorProviderOutcome.code)}: ${String(preExecutorProviderOutcome.detail)}`,
2055
+ providerOutcome: preExecutorProviderOutcome,
2056
+ result: { providerOutcome: preExecutorProviderOutcome },
2057
+ };
2058
+ outcome = did;
2059
+ }
2060
+ else if (action.type === "continue_signal_discovery_source" &&
1967
2061
  hasRunScopedSignalDiscoveryAttempt(action, ctx.runState)) {
1968
2062
  did = {
1969
2063
  status: "refused",
@@ -1985,6 +2079,12 @@ async function gateExecute(input, deps, ctx) {
1985
2079
  did = {
1986
2080
  status: "refused",
1987
2081
  refusalReason: "same-provider-source continuation already used its bounded run-scoped add without closing projected coverage",
2082
+ providerOutcome: {
2083
+ class: "stale_packet_replan",
2084
+ code: "provider_action_effect_already_attempted",
2085
+ detail: "This actionKey/effectId already owns a recorded provider continuation attempt.",
2086
+ facts: providerActionIdentity(action),
2087
+ },
1988
2088
  result: {
1989
2089
  scope: "refill_run",
1990
2090
  campaignId: actionCampaignId(action),
@@ -2002,6 +2102,12 @@ async function gateExecute(input, deps, ctx) {
2002
2102
  did = {
2003
2103
  status: "refused",
2004
2104
  refusalReason: "provider search broadening already used its bounded run-scoped notch for this campaign without closing projected coverage",
2105
+ providerOutcome: {
2106
+ class: "stale_packet_replan",
2107
+ code: "provider_action_effect_already_attempted",
2108
+ detail: "This actionKey/effectId already owns a recorded provider broadening attempt.",
2109
+ facts: providerActionIdentity(action),
2110
+ },
2005
2111
  result: {
2006
2112
  scope: "refill_run",
2007
2113
  blocker: "broaden_provider_search_refused",
@@ -3157,20 +3263,32 @@ async function gateVerify(input, deps, ctx, budgets) {
3157
3263
  // the universeExhausted marker alongside the lane advance so any later broaden
3158
3264
  // emission this run is fenced and the planner reads a receipt-proven terminal.
3159
3265
  const runStateForExhaustion = typeof actionType === "string" &&
3160
- BROADEN_SIGNAL_SEARCH_TYPES.has(actionType)
3266
+ PROVIDER_SOURCE_CONTINUATION_TYPES.has(actionType)
3161
3267
  ? {
3162
3268
  ...ctx.runState,
3163
- ...runScopedBroadenSignalSearchAttemptUpdate(action, ctx.runState, deps.now?.() ?? new Date(), {
3164
- newPostCount: 0,
3165
- universeExhausted: true,
3166
- // feat(110-23d): a below-floor round at ANY tier proves the 45-day
3167
- // universe cannot fill the gap — carry its keywordSource onto the
3168
- // terminal marker.
3169
- keywordSource: broadenSignalSearchKeywordSource(ctx.runState.lastOutcome),
3170
- searchedKeywords: broadenSignalSearchKeywordsTried(ctx.runState.lastOutcome),
3171
- }),
3269
+ ...runScopedProviderSourceAttemptUpdate(action, ctx.runState, deps.now?.() ?? new Date()),
3172
3270
  }
3173
- : ctx.runState;
3271
+ : typeof actionType === "string" &&
3272
+ BROADEN_PROVIDER_SEARCH_TYPES.has(actionType)
3273
+ ? {
3274
+ ...ctx.runState,
3275
+ ...runScopedBroadenProviderSearchAttemptUpdate(action, ctx.runState, deps.now?.() ?? new Date()),
3276
+ }
3277
+ : typeof actionType === "string" &&
3278
+ BROADEN_SIGNAL_SEARCH_TYPES.has(actionType)
3279
+ ? {
3280
+ ...ctx.runState,
3281
+ ...runScopedBroadenSignalSearchAttemptUpdate(action, ctx.runState, deps.now?.() ?? new Date(), {
3282
+ newPostCount: 0,
3283
+ universeExhausted: true,
3284
+ // feat(110-23d): a below-floor round at ANY tier proves the 45-day
3285
+ // universe cannot fill the gap — carry its keywordSource onto the
3286
+ // terminal marker.
3287
+ keywordSource: broadenSignalSearchKeywordSource(ctx.runState.lastOutcome),
3288
+ searchedKeywords: broadenSignalSearchKeywordsTried(ctx.runState.lastOutcome),
3289
+ }),
3290
+ }
3291
+ : ctx.runState;
3174
3292
  const nextRunState = laneExhaustionRunState(exhaustionPlan, action, runStateForExhaustion, exhaustionReason, deps.now?.() ?? new Date());
3175
3293
  if (!nextRunState) {
3176
3294
  return {
@@ -3201,6 +3319,39 @@ async function gateVerify(input, deps, ctx, budgets) {
3201
3319
  });
3202
3320
  return advanceBackToPlan(input, deps, ctx, nextRunState);
3203
3321
  }
3322
+ const typedProviderOutcome = typeof actionType === "string" &&
3323
+ (PROVIDER_SOURCE_CONTINUATION_TYPES.has(actionType) ||
3324
+ BROADEN_PROVIDER_SEARCH_TYPES.has(actionType))
3325
+ ? providerSavedSearchOutcome(ctx.runState.lastOutcome)
3326
+ : null;
3327
+ const typedProviderClass = stringValue(typedProviderOutcome?.class);
3328
+ if (typedProviderOutcome &&
3329
+ typedProviderClass !== "executed" &&
3330
+ typedProviderClass !== "source_terminal") {
3331
+ const attemptUpdate = PROVIDER_SOURCE_CONTINUATION_TYPES.has(actionType ?? "")
3332
+ ? runScopedProviderSourceAttemptUpdate(action, ctx.runState, deps.now?.() ?? new Date())
3333
+ : runScopedBroadenProviderSearchAttemptUpdate(action, ctx.runState, deps.now?.() ?? new Date());
3334
+ const advanced = await advanceBackToPlan(input, deps, ctx, attemptUpdate);
3335
+ if (advanced)
3336
+ return advanced;
3337
+ if (typedProviderClass === "stale_packet_replan")
3338
+ return null;
3339
+ return {
3340
+ status: "blocked",
3341
+ blocker: `provider_${typedProviderClass ?? "unknown"}`,
3342
+ runId: ctx.runId,
3343
+ fence: ctx.fence,
3344
+ gate: ctx.gate,
3345
+ guidance: "The provider attempt did not prove a consumable source terminal. Preserve the same fenced run and resolve or replan from the typed outcome before any new effect identity is dispatched.",
3346
+ report: {
3347
+ actionKey: stringValue(action.actionKey),
3348
+ effectId: stringValue(action.effectId) ??
3349
+ stringValue(actionToolInput(action).effectId),
3350
+ providerOutcome: typedProviderOutcome,
3351
+ },
3352
+ journalPath: ctx.journalPath,
3353
+ };
3354
+ }
3204
3355
  if (actionType === "prepare_messages") {
3205
3356
  return verifyPrepareAction(input, deps, ctx, action, budgets);
3206
3357
  }
@@ -3222,8 +3373,8 @@ async function gateVerify(input, deps, ctx, budgets) {
3222
3373
  }
3223
3374
  if (typeof actionType === "string" &&
3224
3375
  PROVIDER_SOURCE_CONTINUATION_TYPES.has(actionType) &&
3225
- stringValue(recordValue(ctx.runState.lastOutcome)?.status) ===
3226
- "executed_and_reread") {
3376
+ stringValue(providerSavedSearchOutcome(ctx.runState.lastOutcome)?.class) ===
3377
+ "executed") {
3227
3378
  // Phase 110-05: record the bounded same-source add so the run does not
3228
3379
  // repeat it for this (campaign, sender) before replanning.
3229
3380
  return advanceBackToPlan(input, deps, ctx, {
@@ -3235,8 +3386,8 @@ async function gateVerify(input, deps, ctx, budgets) {
3235
3386
  }
3236
3387
  if (typeof actionType === "string" &&
3237
3388
  BROADEN_PROVIDER_SEARCH_TYPES.has(actionType) &&
3238
- stringValue(recordValue(ctx.runState.lastOutcome)?.status) ===
3239
- "executed_and_reread") {
3389
+ stringValue(providerSavedSearchOutcome(ctx.runState.lastOutcome)?.class) ===
3390
+ "executed") {
3240
3391
  // feat(110-23a): record the bounded one-notch broaden so the run does not
3241
3392
  // repeat it for this campaign, and clear the dry-source prep bound so a
3242
3393
  // genuine re-prep against the wider search's new rows can run on replan.
@@ -41,7 +41,50 @@ export type YoloPrimitiveAttempt = {
41
41
  status: "no_action" | "read_only_reread" | "executed_and_reread" | "refused";
42
42
  result?: unknown;
43
43
  refusalReason?: string;
44
+ providerOutcome?: ProviderSavedSearchOutcome;
44
45
  };
46
+ export type ProviderSearchReference = {
47
+ workspaceId: string;
48
+ provider: "sales-nav" | "prospeo";
49
+ sourceTableId: string;
50
+ searchId: string;
51
+ resolutionOrigin: "config_import_job" | "exact_table_job" | "find_leads_run";
52
+ owner: {
53
+ kind: "campaign" | "find_leads_run";
54
+ id: string;
55
+ };
56
+ basisFingerprint: string;
57
+ };
58
+ type ProviderOutcomeBase = {
59
+ code: string;
60
+ detail: string;
61
+ facts: Record<string, unknown>;
62
+ };
63
+ export type ProviderSavedSearchOutcome = (ProviderOutcomeBase & {
64
+ class: "stale_packet_replan";
65
+ }) | (ProviderOutcomeBase & {
66
+ class: "source_terminal";
67
+ }) | (ProviderOutcomeBase & {
68
+ class: "authorization_blocker";
69
+ scope: "global" | "workspace";
70
+ status: 401 | 403;
71
+ }) | (ProviderOutcomeBase & {
72
+ class: "capacity";
73
+ }) | (ProviderOutcomeBase & {
74
+ class: "transient";
75
+ retryable: true;
76
+ }) | (ProviderOutcomeBase & {
77
+ class: "uncertain_effect";
78
+ }) | (ProviderOutcomeBase & {
79
+ class: "executed";
80
+ jobId: string;
81
+ });
82
+ /**
83
+ * The sole compact provider-lineage decoder used at both the MCP packet and
84
+ * executor boundaries. Legacy loose searchId/provider fields never satisfy
85
+ * this contract.
86
+ */
87
+ export declare function sanitizeProviderSearchReference(value: unknown): ProviderSearchReference | null;
45
88
  export type UserAddedRowsLimitPayload = {
46
89
  error?: string;
47
90
  code: "USER_ADDED_ROWS_LIMIT_EXCEEDED";
@@ -297,32 +340,10 @@ export declare function continueSignalDiscoverySource(action: Record<string, unk
297
340
  * going until capacity fills.
298
341
  */
299
342
  export declare function broadenSignalSearch(action: Record<string, unknown>, workspaceId?: string): Promise<YoloPrimitiveAttempt>;
300
- /**
301
- * Phase 110-05: bounded SAME-provider-source continuation for cold-outbound
302
- * Sales Nav / Prospeo campaigns. Mirrors continueSignalDiscoverySource's
303
- * guardrails (campaign + provider + selected-source match) and reuses the
304
- * EXISTING import_leads tool to add leads from the campaign's own saved
305
- * provider search (mode "add") once eligible/prepared rows are receipt-proven
306
- * exhausted. It never switches source families and never creates a campaign.
307
- * The add is bounded by the packet's existing sourceRowLimit cap; when the
308
- * packet carries no reusable provider search reference the add cannot start
309
- * autonomously and the executor refuses with a bounded reason so the run
310
- * records the attempt instead of looping on an unchanged mutation.
311
- */
343
+ type ProviderSavedSearchMode = "continuation" | "broadening";
344
+ /** Shared continuation/broadening saved-search executor and classifier. */
345
+ export declare function executeProviderSavedSearch(action: Record<string, unknown>, workspaceId: string | undefined, mode: ProviderSavedSearchMode): Promise<YoloPrimitiveAttempt>;
312
346
  export declare function continueProviderSource(action: Record<string, unknown>, workspaceId?: string): Promise<YoloPrimitiveAttempt>;
313
- /**
314
- * feat(110-23a): rung 3 of the automatic escalation ladder — bounded provider
315
- * search broadening. When rungs 1-2 (same-source refill + cold continuation) are
316
- * receipt-proven exhausted at the current search depth and schedulable capacity
317
- * remains, this re-runs the campaign's EXISTING provider saved search one bounded
318
- * notch wider. It does NOT invent provider filter semantics: the "notch" is a
319
- * deeper page/depth window on the SAME searchId, expressed as a larger bounded
320
- * targetLeadCount into the same saved search via the EXISTING import_leads path
321
- * continueProviderSource already uses (mode "add"). One notch per campaign per run
322
- * (the loop enforces the run-scoped bound). The receipt records prior depth, new
323
- * depth, and the widened window; the actual rows gained surface on the next replan.
324
- * Never switches source families, never creates a campaign, never sends.
325
- */
326
347
  export declare function broadenProviderSearch(action: Record<string, unknown>, workspaceId?: string): Promise<YoloPrimitiveAttempt>;
327
348
  /**
328
349
  * feat(110-23b): rung 4 of the automatic escalation ladder — bounded, reversible