@signaliz/sdk 1.0.17 → 1.0.19

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.
package/dist/index.js CHANGED
@@ -31,10 +31,12 @@ __export(index_exports, {
31
31
  GtmKernel: () => GtmKernel,
32
32
  Icps: () => Icps,
33
33
  Ops: () => Ops,
34
+ PublicGroundTruth: () => PublicGroundTruth,
34
35
  Signaliz: () => Signaliz,
35
36
  SignalizError: () => SignalizError,
36
37
  applyCampaignBuilderStrategyTemplate: () => applyCampaignBuilderStrategyTemplate,
37
38
  collectExecutionReferences: () => collectExecutionReferences,
39
+ createCampaignBuilderActiveWorkContext: () => createCampaignBuilderActiveWorkContext,
38
40
  createCampaignBuilderAgentPlan: () => createCampaignBuilderAgentPlan,
39
41
  createCampaignBuilderAgentRequestTemplate: () => createCampaignBuilderAgentRequestTemplate,
40
42
  createCampaignBuilderApproval: () => createCampaignBuilderApproval,
@@ -501,6 +503,38 @@ var Signals = class {
501
503
  constructor(client) {
502
504
  this.client = client;
503
505
  }
506
+ /** Turn the strongest supported company signal into three complete, evidence-backed emails. */
507
+ async signalToCopy(params) {
508
+ const data = await this.client.post("api/v1/signal-to-copy", {
509
+ company_domain: params.companyDomain,
510
+ person_name: params.personName,
511
+ title: params.title,
512
+ campaign_offer: params.campaignOffer,
513
+ research_prompt: params.researchPrompt
514
+ });
515
+ return {
516
+ success: data.success ?? true,
517
+ strongestSignal: data.strongest_signal ?? "",
518
+ whyNow: data.why_now ?? "",
519
+ subjectLine: data.subject_line ?? "",
520
+ openingLine: data.opening_line ?? "",
521
+ confidence: data.confidence ?? 0,
522
+ evidenceUrl: data.evidence_url ?? "",
523
+ researchPrompt: data.research_prompt,
524
+ variations: (data.variations ?? []).map((v) => ({
525
+ style: v.style,
526
+ subjectLine: v.subject_line ?? "",
527
+ openingLine: v.opening_line ?? "",
528
+ cta: v.cta ?? "",
529
+ prediction: v.prediction,
530
+ email: v.email
531
+ }))
532
+ };
533
+ }
534
+ /** @deprecated Use signalToCopy. Retained for compatibility. */
535
+ async personalize(params) {
536
+ return this.signalToCopy(params);
537
+ }
504
538
  /** Enrich a company with actionable signals (V1) */
505
539
  async enrich(params) {
506
540
  const args = {
@@ -509,6 +543,9 @@ var Signals = class {
509
543
  if (params.domain) args.domain = params.domain;
510
544
  if (params.researchPrompt) args.research_prompt = params.researchPrompt;
511
545
  if (params.signalTypes) args.signal_types = params.signalTypes;
546
+ if (params.targetSignalCount) args.target_signal_count = params.targetSignalCount;
547
+ if (params.lookbackDays) args.lookback_days = params.lookbackDays;
548
+ if (params.enableDeepSearch !== void 0) args.enable_deep_search = params.enableDeepSearch;
512
549
  const data = await this.client.mcp("tools/call", {
513
550
  name: "enrich_company_signals",
514
551
  arguments: args
@@ -518,6 +555,16 @@ var Signals = class {
518
555
  signals: (data.signals ?? []).map((s) => ({
519
556
  title: s.title,
520
557
  signalType: s.signal_type,
558
+ sourceType: s.source_type ?? s.signal_classification?.label ?? "unknown",
559
+ sourceProvenance: s.source_provenance ?? s.metadata?.source_provenance ?? null,
560
+ signalFamily: s.signal_family ?? s.signal_type ?? null,
561
+ signalEventType: s.signal_event_type ?? s.signal_classification?.slug ?? null,
562
+ signalEventLabel: s.signal_event_label ?? s.signal_classification?.label ?? null,
563
+ signalClassification: s.signal_classification ? {
564
+ label: s.signal_classification.label,
565
+ slug: s.signal_classification.slug,
566
+ rationale: s.signal_classification.rationale
567
+ } : null,
521
568
  confidenceScore: s.confidence_score ?? 0,
522
569
  detectedAt: s.detected_at ?? "",
523
570
  url: s.url,
@@ -558,8 +605,17 @@ var Signals = class {
558
605
  content: s.content ?? "",
559
606
  date: s.date ?? null,
560
607
  sourceUrl: s.source_url ?? null,
561
- sourceType: s.source_type ?? "unknown",
562
- confidence: s.confidence ?? null
608
+ sourceType: s.source_type ?? s.signal_classification?.label ?? "unknown",
609
+ sourceProvenance: s.source_provenance ?? s.metadata?.source_provenance ?? null,
610
+ signalFamily: s.signal_family ?? s.type ?? null,
611
+ signalEventType: s.signal_event_type ?? s.signal_classification?.slug ?? null,
612
+ signalEventLabel: s.signal_event_label ?? s.signal_classification?.label ?? null,
613
+ confidence: s.confidence ?? null,
614
+ signalClassification: s.signal_classification ? {
615
+ label: s.signal_classification.label,
616
+ slug: s.signal_classification.slug,
617
+ rationale: s.signal_classification.rationale
618
+ } : null
563
619
  })),
564
620
  signalCount: data.signal_count ?? 0,
565
621
  intelligence: data.intelligence ? {
@@ -602,6 +658,58 @@ var Signals = class {
602
658
  }
603
659
  };
604
660
  }
661
+ /** Plan or run experimental Signal Fusion with explicit spend controls. */
662
+ async fusion(params) {
663
+ const args = {};
664
+ if (params.companyName) args.company_name = params.companyName;
665
+ if (params.domain) args.domain = params.domain;
666
+ if (params.companyDomain) args.company_domain = params.companyDomain;
667
+ if (params.linkedinUrl) args.linkedin_url = params.linkedinUrl;
668
+ if (params.companies) args.companies = params.companies;
669
+ if (params.preset) args.preset = params.preset;
670
+ if (params.researchPrompt) args.research_prompt = params.researchPrompt;
671
+ if (params.signalTypes) args.signal_types = params.signalTypes;
672
+ if (params.lookbackDays) args.lookback_days = params.lookbackDays;
673
+ if (params.targetSignalCount) args.target_signal_count = params.targetSignalCount;
674
+ if (params.maxToolCalls) args.max_tool_calls = params.maxToolCalls;
675
+ if (params.maxTokens) args.max_tokens = params.maxTokens;
676
+ if (params.timeoutMs) args.timeout_ms = params.timeoutMs;
677
+ const dryRun = params.dryRun ?? params.dry_run;
678
+ const confirmSpend = params.confirmSpend ?? params.confirm_spend;
679
+ const maxCredits = params.maxCredits ?? params.max_credits;
680
+ const maxCostUsd = params.maxCostUsd ?? params.max_cost_usd;
681
+ if (dryRun !== void 0) args.dry_run = dryRun;
682
+ if (confirmSpend !== void 0) args.confirm_spend = confirmSpend;
683
+ if (maxCredits !== void 0) args.max_credits = maxCredits;
684
+ if (maxCostUsd !== void 0) args.max_cost_usd = maxCostUsd;
685
+ const data = await this.client.post("signal-fusion", args);
686
+ return {
687
+ success: data.success ?? false,
688
+ capability: data.capability ?? "signal_fusion",
689
+ displayName: data.display_name ?? "Signal Fusion",
690
+ company: data.company,
691
+ signals: (data.signals ?? []).map((s) => ({
692
+ id: s.id,
693
+ title: s.title,
694
+ type: s.type,
695
+ content: s.content ?? "",
696
+ date: s.date ?? null,
697
+ sourceUrl: s.source_url ?? null,
698
+ sourceType: s.source_type ?? "unknown",
699
+ confidence: s.confidence ?? null
700
+ })),
701
+ signalCount: data.signal_count ?? data.signals?.length ?? 0,
702
+ results: data.results ?? [],
703
+ model: data.model,
704
+ fusion: data.fusion,
705
+ costUsd: data.cost_usd ?? 0,
706
+ tokensUsed: data.tokens_used ?? 0,
707
+ costSummary: data.cost_summary,
708
+ creditsUsed: data.total_credits_used ?? data.credits_used ?? 0,
709
+ billingMetadata: data.billing_metadata,
710
+ raw: data
711
+ };
712
+ }
605
713
  };
606
714
 
607
715
  // src/resources/systems.ts
@@ -890,7 +998,11 @@ var Campaigns = class {
890
998
  maxSupportedTargetCount: data.max_supported_target_count,
891
999
  brainContext: data.brain_context ?? {},
892
1000
  learningHoldout: data.learning_holdout ?? {},
893
- providerRoute: mapProviderRoute(data)
1001
+ providerRoute: mapProviderRoute(data),
1002
+ effectiveSourceRouting: recordOrNull(data.effective_source_routing ?? data.source_routing),
1003
+ sourcePlan: recordOrNull(data.source_plan ?? data.plan?.source_plan),
1004
+ sourceRoutes: recordArray(data.source_routes ?? data.source_plan?.routes ?? data.plan?.source_plan?.routes),
1005
+ sourceShards: recordArray(data.source_shards ?? data.source_plan?.shards ?? data.plan?.source_plan?.shards)
894
1006
  };
895
1007
  }
896
1008
  async getCampaignBuildStatus(campaignBuildId) {
@@ -1036,6 +1148,7 @@ function mapCustomerRowCounts(value) {
1036
1148
  acceptedRows: numberOrNull(record.accepted_rows) ?? void 0,
1037
1149
  usableRows: numberOrNull(record.usable_rows) ?? void 0,
1038
1150
  copiedRows: numberOrNull(record.copied_rows) ?? void 0,
1151
+ copySkippedRows: numberOrNull(record.copy_skipped_rows) ?? void 0,
1039
1152
  approvalReadyRows: numberOrNull(record.approval_ready_rows) ?? void 0,
1040
1153
  qaReadyRows: numberOrNull(record.qa_ready_rows) ?? void 0,
1041
1154
  deliveredRows: numberOrNull(record.delivered_rows) ?? void 0,
@@ -1082,6 +1195,9 @@ function recordOrNull(value) {
1082
1195
  function stringArray(value) {
1083
1196
  return Array.isArray(value) ? value.map((item) => String(item || "").trim()).filter(Boolean) : [];
1084
1197
  }
1198
+ function recordArray(value) {
1199
+ return Array.isArray(value) ? value.map(recordOrNull).filter((item) => Boolean(item)) : [];
1200
+ }
1085
1201
  function diagnosticMessages(value) {
1086
1202
  if (!Array.isArray(value)) return [];
1087
1203
  return value.map((item) => {
@@ -1243,7 +1359,8 @@ function buildArgs(request) {
1243
1359
  suppress_prior_campaign_ids: request.cacheReusePolicy.suppressPriorCampaignIds,
1244
1360
  suppress_prior_list_ids: request.cacheReusePolicy.suppressPriorListIds,
1245
1361
  uniqueness: request.cacheReusePolicy.uniqueness,
1246
- require_usage_metadata: request.cacheReusePolicy.requireUsageMetadata
1362
+ require_usage_metadata: request.cacheReusePolicy.requireUsageMetadata,
1363
+ verified_cache_ttl_days: request.cacheReusePolicy.verifiedCacheTtlDays
1247
1364
  };
1248
1365
  }
1249
1366
  if (request.brainPreflight) args.brain_preflight = request.brainPreflight;
@@ -1268,7 +1385,8 @@ function buildArgs(request) {
1268
1385
  geographies: request.icp.geographies,
1269
1386
  keywords: request.icp.keywords,
1270
1387
  exclusions: request.icp.exclusions,
1271
- require_verified_email: request.icp.requireVerifiedEmail
1388
+ require_verified_email: request.icp.requireVerifiedEmail,
1389
+ persona_expansion_mode: request.icp.personaExpansionMode
1272
1390
  };
1273
1391
  }
1274
1392
  if (request.policy) {
@@ -1308,6 +1426,7 @@ function buildArgs(request) {
1308
1426
  } : void 0,
1309
1427
  sequence_steps: request.copy.sequenceSteps,
1310
1428
  copy_schema: request.copy.copySchema,
1429
+ evidence_mode: request.copy.evidenceMode,
1311
1430
  banned_phrases: request.copy.bannedPhrases,
1312
1431
  approval_required: request.copy.approvalRequired,
1313
1432
  quality_tier: request.copy.qualityTier
@@ -1364,7 +1483,8 @@ var DEFAULT_SIGNALIZ_CAMPAIGN_BUILDER_DEFAULTS = {
1364
1483
  copy: {
1365
1484
  enabled: true,
1366
1485
  tone: "direct",
1367
- maxBodyWords: 125
1486
+ maxBodyWords: 125,
1487
+ evidenceMode: "signal_led"
1368
1488
  },
1369
1489
  delivery: {
1370
1490
  enabled: true,
@@ -1421,7 +1541,8 @@ var CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS = [
1421
1541
  "Block export when required identity, company, or email fields are missing."
1422
1542
  ],
1423
1543
  activationBoundary: "Cache inventory is read-only. Fresh sourcing, cache writes, export, and sender loading require explicit approval.",
1424
- memoryKeywords: ["cache inventory", "large list", "unique emails", "top-up sourcing", "source metadata"]
1544
+ memoryKeywords: ["cache inventory", "large list", "unique emails", "top-up sourcing", "source metadata"],
1545
+ knowledgeSources: ["data-ops/contact-data-lifecycle", "data-ops/credit-optimization-guide", "gtm/data-quality-ops", "gtm/lead-generation-strategies"]
1425
1546
  },
1426
1547
  {
1427
1548
  slug: "net-new-suppressed-list",
@@ -1446,7 +1567,8 @@ var CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS = [
1446
1567
  "Do not describe rows as fully qualified when the approved path only proves net-new email status."
1447
1568
  ],
1448
1569
  activationBoundary: "Suppression review must pass before export, upload, CRM sync, sender load, or sequence launch.",
1449
- memoryKeywords: ["suppression", "net-new", "prior email overlap", "domain review", "provenance-rich QA"]
1570
+ memoryKeywords: ["suppression", "net-new", "prior email overlap", "domain review", "provenance-rich QA"],
1571
+ knowledgeSources: ["gtm/data-quality-ops", "data-ops/contact-data-lifecycle", "gtm/outbound-sequencing"]
1450
1572
  },
1451
1573
  {
1452
1574
  slug: "proof-first-vertical-gate",
@@ -1471,7 +1593,8 @@ var CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS = [
1471
1593
  "Contact fit should meet the configured threshold before email finding or copy generation."
1472
1594
  ],
1473
1595
  activationBoundary: "Proof gates are required before contact spend, copy, export, or external writes.",
1474
- memoryKeywords: ["proof gate", "vertical exclusion", "company before contact", "pass review fail", "fit threshold"]
1596
+ memoryKeywords: ["proof gate", "vertical exclusion", "company before contact", "pass review fail", "fit threshold"],
1597
+ knowledgeSources: ["gtm/icp-design-framework", "gtm/strategy/scoring.yaml", "gtm/advanced/account-prioritization-frameworks", "gtm/advanced/predictive-lead-scoring", "gtm/verticals"]
1475
1598
  },
1476
1599
  {
1477
1600
  slug: "signal-led-copy-approval",
@@ -1496,7 +1619,8 @@ var CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS = [
1496
1619
  "Destination fields must remain blocked until approval metadata is present."
1497
1620
  ],
1498
1621
  activationBoundary: "Copy readiness is not delivery approval. Export, sync, sender load, and launch require a separate human-approved gate.",
1499
- memoryKeywords: ["company signals", "evidence-fed copy", "why now", "copy blocker", "approval gate"]
1622
+ memoryKeywords: ["company signals", "evidence-fed copy", "why now", "copy blocker", "approval gate"],
1623
+ knowledgeSources: ["gtm/email-personalization", "gtm/signal-based-selling", "gtm/signal-combination-matrix", "gtm/advanced/buyer-journey-mapping"]
1500
1624
  },
1501
1625
  {
1502
1626
  slug: "domain-first-recovery",
@@ -1521,7 +1645,8 @@ var CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS = [
1521
1645
  "Target count means final held rows, not raw sourced rows."
1522
1646
  ],
1523
1647
  activationBoundary: "Domain recovery informs sourcing. Contact enrichment, paid qualification, and export still require approved gates.",
1524
- memoryKeywords: ["domain-first", "yield modeling", "attrition", "accepted domains", "gap fill"]
1648
+ memoryKeywords: ["domain-first", "yield modeling", "attrition", "accepted domains", "gap fill"],
1649
+ knowledgeSources: ["gtm/lead-generation-strategies", "data-ops/enrichment-waterfall-strategy", "data-ops/credit-optimization-guide", "gtm/territory-account-planning"]
1525
1650
  },
1526
1651
  {
1527
1652
  slug: "table-workflow-handoff",
@@ -1546,7 +1671,112 @@ var CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS = [
1546
1671
  "Provider route activation must dry-run before any external write."
1547
1672
  ],
1548
1673
  activationBoundary: "MCP, webhook, managed integration, and destination routes remain dry-run until the reviewed route and campaign are approved.",
1549
- memoryKeywords: ["table workflow", "request ready", "provider status", "export blocker", "BYO integration"]
1674
+ memoryKeywords: ["table workflow", "request ready", "provider status", "export blocker", "BYO integration"],
1675
+ knowledgeSources: ["gtm/multi-channel-orchestration", "data-ops/enrichment-waterfall-strategy", "recipes/clay-gtm-webhook-recipe", "systems/blueprints/multi-channel-orchestration"]
1676
+ },
1677
+ {
1678
+ slug: "icp-persona-segmentation",
1679
+ label: "ICP And Persona Segmentation",
1680
+ whenToUse: [
1681
+ "The brief has a broad market, multiple personas, or unclear buying committee.",
1682
+ "The campaign should rank account tiers, persona fit, vertical nuance, and exclusions before sourcing at scale."
1683
+ ],
1684
+ sequence: [
1685
+ "Convert the brief into account tiers, persona roles, buying committee influence, and explicit exclusions.",
1686
+ "Choose vertical guidance only from the customer-safe GTM library and keep internal platform docs out of prompts.",
1687
+ "Score accounts before contacts, then score contacts against persona, seniority, function, geography, and intent fit.",
1688
+ "Return segment counts and review buckets before spend, copy, export, or launch."
1689
+ ],
1690
+ requiredFields: {
1691
+ segment: ["segment_name", "tier", "persona_priority", "buying_committee_role", "fit_reason", "exclusion_reason"],
1692
+ scoring: ["account_fit_score", "persona_fit_score", "segment_confidence", "review_bucket"]
1693
+ },
1694
+ qualityGates: [
1695
+ "Do not treat adjacent personas or verticals as matches unless the brief allows them.",
1696
+ "Keep ICP, persona, and exclusion evidence visible in the proof packet.",
1697
+ "Low-confidence segments route to review before contact discovery or copy."
1698
+ ],
1699
+ activationBoundary: "Segmentation is planning guidance. Paid sourcing, enrichment, export, and launch require the normal approval gates.",
1700
+ memoryKeywords: ["ICP design", "persona segmentation", "buying committee", "account tiers", "fit scoring", "vertical nuance"],
1701
+ knowledgeSources: ["gtm/icp-design-framework", "gtm/personas/sdr.yaml", "gtm/personas/ae.yaml", "gtm/personas/cro.yaml", "gtm/personas/revops.yaml", "gtm/advanced/account-prioritization-frameworks", "gtm/verticals"]
1702
+ },
1703
+ {
1704
+ slug: "buying-signal-prioritization",
1705
+ label: "Buying Signal Prioritization",
1706
+ whenToUse: [
1707
+ "The campaign depends on timing, intent, trigger events, or account prioritization.",
1708
+ "The operator wants why-now proof instead of generic list generation."
1709
+ ],
1710
+ sequence: [
1711
+ "Map the campaign goal to signal families, trigger freshness, and buyer-journey stage.",
1712
+ "Rank signals by relevance, recency, source quality, and persona-specific actionability.",
1713
+ "Attach evidence URLs or evidence summaries without exposing private memory rows.",
1714
+ "Use signal strength to decide source order, copy angle, and review priority."
1715
+ ],
1716
+ requiredFields: {
1717
+ signal: ["signal_type", "signal_date", "signal_source", "why_now", "signal_confidence", "buyer_stage"],
1718
+ prioritization: ["priority_score", "priority_reason", "next_best_action", "review_reason"]
1719
+ },
1720
+ qualityGates: [
1721
+ "Do not use stale, unsupported, or weak signals as personalization proof.",
1722
+ "Signal fit cannot override hard ICP, geography, suppression, or verified-email gates.",
1723
+ "Rows with uncertain why-now evidence must stay review-only."
1724
+ ],
1725
+ activationBoundary: "Signal ranking can prioritize review and copy. It does not approve outreach, sender loading, exports, or sends.",
1726
+ memoryKeywords: ["buying signals", "intent data", "funding trigger", "new executive", "tech adoption", "buyer journey", "why now"],
1727
+ knowledgeSources: ["gtm/signal-based-selling", "gtm/signal-combination-matrix", "gtm/advanced/intent-data-strategies", "gtm/advanced/buyer-journey-mapping", "gtm/signals/funding-round", "gtm/signals/new-executive", "gtm/signals/tech-adoption", "recipes/funding-trigger-outreach", "recipes/new-hire-outreach"]
1728
+ },
1729
+ {
1730
+ slug: "multi-channel-sequence-fit",
1731
+ label: "Multi-Channel Sequence Fit",
1732
+ whenToUse: [
1733
+ "The campaign needs email, LinkedIn, call, webhook, CRM, or sequencer coordination.",
1734
+ "Copy and delivery should adapt by persona, channel, deliverability risk, and proof strength."
1735
+ ],
1736
+ sequence: [
1737
+ "Choose channel mix from persona, deal motion, evidence strength, deliverability constraints, and connected destination readiness.",
1738
+ "Draft sequence logic with email, LinkedIn, call, and task handoffs only where supported by the approved destination.",
1739
+ "Keep copy concise, proof-backed, channel-specific, and reviewable before delivery.",
1740
+ "Run deliverability and destination readiness checks before sender load or launch approval."
1741
+ ],
1742
+ requiredFields: {
1743
+ channel: ["channel", "touch_number", "persona_context", "message_angle", "channel_blocker", "approval_status"],
1744
+ deliverability: ["verified_email", "domain_risk", "send_window", "unsubscribe_risk", "sender_ready"]
1745
+ },
1746
+ qualityGates: [
1747
+ "Email copy must be row-supported and under the configured campaign tone/length limits.",
1748
+ "LinkedIn, phone, CRM, webhook, and sequencer actions remain destination-locked until approved.",
1749
+ "Deliverability risk or missing sender readiness blocks launch even when rows are qualified."
1750
+ ],
1751
+ activationBoundary: "Sequence strategy is draft-only. External writes, sender loading, launch, and sending need separate explicit approval.",
1752
+ memoryKeywords: ["outbound sequencing", "email personalization", "LinkedIn outreach", "cold calling", "multi-channel orchestration", "deliverability"],
1753
+ knowledgeSources: ["gtm/outbound-sequencing", "gtm/email-personalization", "gtm/linkedin-outreach-strategies", "gtm/cold-calling-frameworks", "gtm/multi-channel-orchestration", "gtm/advanced/email-deliverability", "systems/blueprints/multi-channel-orchestration"]
1754
+ },
1755
+ {
1756
+ slug: "revops-feedback-loop",
1757
+ label: "RevOps Feedback Loop",
1758
+ whenToUse: [
1759
+ "The campaign should improve from replies, meetings, delivery results, CRM outcomes, or operator QA.",
1760
+ "The operator needs post-build monitoring, governance, or continuous ops follow-up."
1761
+ ],
1762
+ sequence: [
1763
+ "Define success metrics, guardrails, and feedback sources before launch.",
1764
+ "Collect aggregate outcomes, QA blockers, delivery health, and operator edits after each campaign phase.",
1765
+ "Separate workspace-private evidence from public-safe learnings and Brain defaults.",
1766
+ "Feed approved aggregate learnings back into future sourcing, qualification, copy, and suppression rules."
1767
+ ],
1768
+ requiredFields: {
1769
+ feedback: ["source", "event_type", "metric", "sample_size", "confidence", "learning_candidate"],
1770
+ governance: ["approval_owner", "last_reviewed_at", "risk_flag", "next_review_action"]
1771
+ },
1772
+ qualityGates: [
1773
+ "Do not write memory or Brain learnings from raw replies, private labels, or provider payloads without approved redaction.",
1774
+ "Use holdouts or pre/post comparisons before claiming causal lift.",
1775
+ "Delivery, suppression, and opt-out signals must update future gates before scale-up."
1776
+ ],
1777
+ activationBoundary: "Feedback review is read-only until memory writes, Brain writes, CRM updates, or campaign changes are explicitly approved.",
1778
+ memoryKeywords: ["RevOps", "feedback loop", "campaign learning", "reply outcomes", "sales marketing alignment", "data hygiene", "holdout"],
1779
+ knowledgeSources: ["gtm/revenue-operations", "gtm/advanced/sales-marketing-alignment", "gtm/advanced/predictive-lead-scoring", "gtm/data-quality-ops", "data-ops/contact-data-lifecycle", "recipes/crm-data-hygiene", "gtm/playbooks/revops-automation-playbook"]
1550
1780
  }
1551
1781
  ];
1552
1782
  var CAMPAIGN_BUILDER_STRATEGY_TEMPLATES = [
@@ -1633,7 +1863,15 @@ var CAMPAIGN_BUILDER_STRATEGY_TEMPLATES = [
1633
1863
  agencyContext: {
1634
1864
  partnerEcosystem: ["signaliz", "instantly", "nango"]
1635
1865
  },
1636
- operatingPlaybookSlugs: ["net-new-suppressed-list", "proof-first-vertical-gate", "signal-led-copy-approval"]
1866
+ operatingPlaybookSlugs: [
1867
+ "net-new-suppressed-list",
1868
+ "proof-first-vertical-gate",
1869
+ "signal-led-copy-approval",
1870
+ "icp-persona-segmentation",
1871
+ "buying-signal-prioritization",
1872
+ "multi-channel-sequence-fit",
1873
+ "revops-feedback-loop"
1874
+ ]
1637
1875
  },
1638
1876
  {
1639
1877
  slug: "non-medical-home-care",
@@ -1706,7 +1944,15 @@ var CAMPAIGN_BUILDER_STRATEGY_TEMPLATES = [
1706
1944
  agencyContext: {
1707
1945
  partnerEcosystem: ["clay", "octave", "signaliz", "instantly", "nango"]
1708
1946
  },
1709
- operatingPlaybookSlugs: ["proof-first-vertical-gate", "domain-first-recovery", "table-workflow-handoff"]
1947
+ operatingPlaybookSlugs: [
1948
+ "proof-first-vertical-gate",
1949
+ "domain-first-recovery",
1950
+ "table-workflow-handoff",
1951
+ "icp-persona-segmentation",
1952
+ "buying-signal-prioritization",
1953
+ "multi-channel-sequence-fit",
1954
+ "revops-feedback-loop"
1955
+ ]
1710
1956
  },
1711
1957
  {
1712
1958
  slug: "agency-founder-led",
@@ -1767,7 +2013,15 @@ var CAMPAIGN_BUILDER_STRATEGY_TEMPLATES = [
1767
2013
  agencyContext: {
1768
2014
  partnerEcosystem: ["signaliz", "octave", "instantly", "nango"]
1769
2015
  },
1770
- operatingPlaybookSlugs: ["net-new-suppressed-list", "signal-led-copy-approval", "table-workflow-handoff"]
2016
+ operatingPlaybookSlugs: [
2017
+ "net-new-suppressed-list",
2018
+ "signal-led-copy-approval",
2019
+ "table-workflow-handoff",
2020
+ "icp-persona-segmentation",
2021
+ "buying-signal-prioritization",
2022
+ "multi-channel-sequence-fit",
2023
+ "revops-feedback-loop"
2024
+ ]
1771
2025
  },
1772
2026
  {
1773
2027
  slug: "cloud-infrastructure-displacement",
@@ -1854,7 +2108,15 @@ var CAMPAIGN_BUILDER_STRATEGY_TEMPLATES = [
1854
2108
  agencyContext: {
1855
2109
  partnerEcosystem: ["signaliz", "octave", "instantly", "nango"]
1856
2110
  },
1857
- operatingPlaybookSlugs: ["proof-first-vertical-gate", "domain-first-recovery", "signal-led-copy-approval"]
2111
+ operatingPlaybookSlugs: [
2112
+ "proof-first-vertical-gate",
2113
+ "domain-first-recovery",
2114
+ "signal-led-copy-approval",
2115
+ "icp-persona-segmentation",
2116
+ "buying-signal-prioritization",
2117
+ "multi-channel-sequence-fit",
2118
+ "revops-feedback-loop"
2119
+ ]
1858
2120
  }
1859
2121
  ];
1860
2122
  var CampaignBuilderAgent = class {
@@ -1885,6 +2147,9 @@ var CampaignBuilderAgent = class {
1885
2147
  const plan = await this.createPlan(request, options);
1886
2148
  return createCampaignBuilderReadiness(plan);
1887
2149
  }
2150
+ activeWorkContext(input) {
2151
+ return createCampaignBuilderActiveWorkContext(input);
2152
+ }
1888
2153
  async proof(request, options = {}) {
1889
2154
  const plan = await this.createPlan(request, options);
1890
2155
  const result = await this.dryRunPlan(plan, {
@@ -1926,6 +2191,7 @@ var CampaignBuilderAgent = class {
1926
2191
  },
1927
2192
  brain_defaults: plan.buildRequest.brainDefaults,
1928
2193
  brain_preflight: plan.buildRequest.brainPreflight,
2194
+ learning_holdout: plan.buildRequest.learningHoldout,
1929
2195
  campaign_strategy_context: plan.buildRequest.campaignStrategyContext,
1930
2196
  delivery_risk_preflight: plan.buildRequest.deliveryRisk,
1931
2197
  delivery_risk: plan.buildRequest.deliveryRisk
@@ -1939,7 +2205,8 @@ var CampaignBuilderAgent = class {
1939
2205
  estimated_credits: plan.estimatedCredits
1940
2206
  },
1941
2207
  brain_delivery_risk_preflight: plan.buildRequest.deliveryRisk,
1942
- delivery_risk: plan.buildRequest.deliveryRisk
2208
+ delivery_risk: plan.buildRequest.deliveryRisk,
2209
+ learning_holdout: plan.buildRequest.learningHoldout
1943
2210
  },
1944
2211
  approval_required: plan.approvals.some((approval) => approval.blocking),
1945
2212
  actor_type: "agent",
@@ -2267,9 +2534,10 @@ function createCampaignBuilderAgentPlan(request, context = {}) {
2267
2534
  const estimatedCredits = estimateCredits(resolvedRequest, defaults, targetCount);
2268
2535
  const buildRequest = createBuildRequest(resolvedRequest, defaults, targetCount, campaignName, context.scopeBuildArgs, routes, memory, context.scopeBrainPreflight);
2269
2536
  const brainPreflight = asRecord(buildRequest.brainPreflight);
2537
+ const evidence = buildRequest.evidence ?? null;
2270
2538
  const approvals = deriveApprovals(resolvedRequest, routes, memory, estimatedCredits, defaults.delivery?.approvalRequired !== false);
2271
2539
  return {
2272
- planId: `cbp_${hashString(JSON.stringify({ goal: resolvedRequest.goal, campaignName, targetCount, routes }))}`,
2540
+ planId: `cbp_${hashString(JSON.stringify({ goal: resolvedRequest.goal, campaignName, targetCount, routes, evidence }))}`,
2273
2541
  campaignName,
2274
2542
  goal: resolvedRequest.goal,
2275
2543
  targetCount,
@@ -2284,6 +2552,7 @@ function createCampaignBuilderAgentPlan(request, context = {}) {
2284
2552
  approvals,
2285
2553
  buildRequest,
2286
2554
  brainPreflight,
2555
+ evidence,
2287
2556
  operatingPlaybooks,
2288
2557
  mcpFlow: createMcpFlow(resolvedRequest, buildRequest, routes, memory, approvals),
2289
2558
  estimatedCredits,
@@ -2375,6 +2644,90 @@ function createCampaignBuilderReadiness(plan) {
2375
2644
  nextActions: campaignReadinessNextActions(plan, ready, customRoutes.length)
2376
2645
  };
2377
2646
  }
2647
+ function createCampaignBuilderActiveWorkContext(input) {
2648
+ const plan = input.readiness?.plan ?? input.plan;
2649
+ const remembered = input.remembered ?? {};
2650
+ const lastResult = asRecord(input.lastResult);
2651
+ const readinessBlockers = input.readiness?.summary.blockers ?? [];
2652
+ const explicitBlockers = input.blockers ?? [];
2653
+ const blockers = uniqueStrings([...explicitBlockers, ...readinessBlockers]);
2654
+ const warnings = uniqueStrings([
2655
+ ...input.warnings ?? [],
2656
+ ...input.readiness?.summary.warnings ?? [],
2657
+ ...plan?.warnings ?? []
2658
+ ]);
2659
+ const missingApprovals = plan ? (input.approval ? getMissingCampaignBuilderApprovals(plan, input.approval) : plan.approvals.filter((approval) => approval.blocking)).map((approval) => approval.type) : [];
2660
+ const refs = normalizeCampaignBuilderActiveWorkRefs({
2661
+ plan,
2662
+ remembered,
2663
+ refs: input.refs,
2664
+ lastResult
2665
+ });
2666
+ const state = inferCampaignBuilderActiveWorkState({
2667
+ plan,
2668
+ readiness: input.readiness,
2669
+ remembered,
2670
+ lastResult,
2671
+ blockers,
2672
+ missingApprovals
2673
+ });
2674
+ const nextSafeAction = campaignBuilderActiveWorkNextAction(state, blockers, missingApprovals, refs);
2675
+ const approvalPacket = createCampaignBuilderApprovalPacket(plan, input.approval, missingApprovals);
2676
+ const nextSafeMcpAction = campaignBuilderActiveWorkNextMcpAction({
2677
+ state,
2678
+ plan,
2679
+ refs,
2680
+ approvalPacket
2681
+ });
2682
+ const summary = {
2683
+ plan_id: plan?.planId ?? remembered.planId ?? null,
2684
+ campaign_name: plan?.campaignName ?? null,
2685
+ target_count: plan?.targetCount ?? null,
2686
+ gtm_campaign_id: plan?.buildRequest.gtmCampaignId ?? remembered.gtmCampaignId ?? null,
2687
+ campaign_build_id: stringValue(lastResult.campaign_build_id ?? lastResult.campaignBuildId) ?? remembered.campaignBuildId ?? null,
2688
+ provider_campaign_id: remembered.providerCampaignId ?? null
2689
+ };
2690
+ const approvalBoundary = campaignBuilderActiveWorkApprovalBoundary(missingApprovals, state);
2691
+ const conversationStarters = campaignBuilderActiveWorkConversationStarters({
2692
+ state,
2693
+ blockers,
2694
+ missingApprovals,
2695
+ refs,
2696
+ nextSafeAction
2697
+ });
2698
+ return {
2699
+ packet_version: "campaign-builder-active-work-context.v1",
2700
+ read_only: true,
2701
+ no_spend: true,
2702
+ no_provider_writes: true,
2703
+ private_safe: true,
2704
+ state,
2705
+ summary,
2706
+ refs,
2707
+ blockers,
2708
+ warnings,
2709
+ missing_approval_types: missingApprovals,
2710
+ approval_packet: approvalPacket,
2711
+ approval_boundary: approvalBoundary,
2712
+ next_safe_action: nextSafeAction,
2713
+ next_safe_mcp_action: nextSafeMcpAction,
2714
+ conversation_starters: conversationStarters,
2715
+ openai_agent_handoff: createCampaignBuilderOpenAiAgentHandoff({
2716
+ plan,
2717
+ state,
2718
+ summary,
2719
+ refs,
2720
+ blockers,
2721
+ warnings,
2722
+ missingApprovals,
2723
+ approvalPacket,
2724
+ approvalBoundary,
2725
+ nextSafeAction,
2726
+ nextSafeMcpAction,
2727
+ conversationStarters
2728
+ })
2729
+ };
2730
+ }
2378
2731
  function createCampaignBuilderProofReceipt(plan, result) {
2379
2732
  const strategyMemory = asRecord(plan.strategyMemoryStatus);
2380
2733
  const dryRunResult = asRecord(result);
@@ -2433,6 +2786,7 @@ function createCampaignBuilderProofReceipt(plan, result) {
2433
2786
  estimated_credits: plan.estimatedCredits,
2434
2787
  operating_playbooks: plan.operatingPlaybooks.map((playbook) => playbook.slug)
2435
2788
  },
2789
+ evidence: plan.evidence,
2436
2790
  gates,
2437
2791
  strategy_memory_status: summarizeCampaignBuilderProofStrategyMemory(strategyMemory),
2438
2792
  dry_run_result: summarizeCampaignBuilderProofDryRun(dryRunResult),
@@ -2488,6 +2842,56 @@ function getCampaignBuilderOperatingPlaybooksForRequest(request) {
2488
2842
  ]);
2489
2843
  return slugs.map(getCampaignBuilderOperatingPlaybook).filter((playbook) => Boolean(playbook));
2490
2844
  }
2845
+ function normalizeCampaignBuilderEvidence(evidence) {
2846
+ if (!evidence) return null;
2847
+ const attachmentSummary = (evidence.attachments ?? []).filter((attachment) => stringValue(attachment.name)).slice(0, 5).map((attachment) => ({
2848
+ name: stringValue(attachment.name) ?? "attachment",
2849
+ kind: stringValue(attachment.kind) ?? null,
2850
+ media_type: stringValue(attachment.mediaType) ?? null,
2851
+ redacted: attachment.redacted !== false,
2852
+ columns: uniqueStrings(attachment.columns ?? []).slice(0, 25),
2853
+ row_count: numberOrUndefined2(attachment.rowCount) ?? null,
2854
+ content_summary: stringValue(attachment.contentSummary)?.slice(0, 500) ?? null,
2855
+ evidence_fields: uniqueStrings(attachment.evidenceFields ?? []).slice(0, 25)
2856
+ }));
2857
+ const urlSummary = (evidence.urls ?? []).filter((url) => stringValue(url.url)).slice(0, 8).map((url) => {
2858
+ const rawUrl = stringValue(url.url) ?? "";
2859
+ return {
2860
+ url: rawUrl,
2861
+ host: stringValue(url.host) ?? hostFromUrl(rawUrl) ?? null,
2862
+ type: stringValue(url.type) ?? null,
2863
+ label: stringValue(url.label) ?? null,
2864
+ summary: stringValue(url.summary)?.slice(0, 500) ?? null,
2865
+ redacted: url.redacted === true
2866
+ };
2867
+ });
2868
+ const operatorNotes = uniqueStrings(evidence.notes ?? []).map((note) => note.slice(0, 240)).slice(0, 5);
2869
+ if (!attachmentSummary.length && !urlSummary.length && !operatorNotes.length) return null;
2870
+ return {
2871
+ packet_version: "campaign-builder-evidence.v1",
2872
+ private_safe: true,
2873
+ attachment_summary: attachmentSummary,
2874
+ url_summary: urlSummary,
2875
+ operator_notes: operatorNotes,
2876
+ privacy_boundary: "Use these attachment and URL summaries as planning evidence only. Do not expose raw private attachment text, raw rows, leads, emails, provider payloads, copy bodies, or secrets.",
2877
+ raw_private_fields_withheld: [
2878
+ "raw_private_attachment_text",
2879
+ "raw_attachment_rows",
2880
+ "raw_leads",
2881
+ "email_addresses",
2882
+ "provider_auth_payloads",
2883
+ "copy_bodies",
2884
+ "secrets"
2885
+ ]
2886
+ };
2887
+ }
2888
+ function hostFromUrl(value) {
2889
+ try {
2890
+ return new URL(value).hostname || void 0;
2891
+ } catch {
2892
+ return void 0;
2893
+ }
2894
+ }
2491
2895
  function applyCampaignBuilderStrategyTemplate(request) {
2492
2896
  const template = getCampaignBuilderStrategyTemplate(request.strategyTemplate);
2493
2897
  if (!template) return request;
@@ -3179,7 +3583,7 @@ function normalizeMemory(request) {
3179
3583
  };
3180
3584
  }
3181
3585
  function defaultAgencyMemoryQueries(request, configured) {
3182
- const operatingPlaybookTerms = getCampaignBuilderOperatingPlaybooksForRequest(request).flatMap((playbook) => [playbook.label, ...playbook.memoryKeywords]).join(" ");
3586
+ const operatingPlaybookTerms = getCampaignBuilderOperatingPlaybooksForRequest(request).flatMap((playbook) => [playbook.label, ...playbook.memoryKeywords, ...playbook.knowledgeSources]).join(" ");
3183
3587
  const queries = [{
3184
3588
  query: request.goal,
3185
3589
  scopes: ["workspace", configured.useNetworkPatterns ? "network_patterns" : "operator_notes"],
@@ -3345,6 +3749,21 @@ function routesFromCustomerTools(tools) {
3345
3749
  }))
3346
3750
  );
3347
3751
  }
3752
+ function normalizeCampaignBuilderLearningHoldout(input, gtmCampaignId, campaignName) {
3753
+ const source = asRecord(input);
3754
+ if (source.enabled === false) return { enabled: false };
3755
+ const controlPercentage = numberOrUndefined2(source.control_percentage ?? source.controlPercentage);
3756
+ const minControlSize = numberOrUndefined2(source.min_control_size ?? source.minControlSize);
3757
+ const experimentKey = stringValue(source.experiment_key ?? source.experimentKey) ?? (gtmCampaignId ? `gtm_campaign:${gtmCampaignId}:learning_holdout` : `campaign_builder_agent:${hashString(campaignName)}:learning_holdout`);
3758
+ return {
3759
+ enabled: true,
3760
+ controlPercentage: controlPercentage ?? 0.2,
3761
+ minControlSize: minControlSize ?? 50,
3762
+ experimentKey,
3763
+ sourceCampaignId: stringValue(source.source_campaign_id ?? source.sourceCampaignId) ?? gtmCampaignId,
3764
+ primaryMetric: stringValue(source.primary_metric ?? source.primaryMetric) ?? "positive_reply_rate"
3765
+ };
3766
+ }
3348
3767
  function createBuildRequest(request, defaults, targetCount, campaignName, scopeBuildArgs, routes = [], memory, scopeBrainPreflight) {
3349
3768
  const buildRequest = {
3350
3769
  name: campaignName,
@@ -3375,8 +3794,15 @@ function createBuildRequest(request, defaults, targetCount, campaignName, scopeB
3375
3794
  fillPolicy: "aggressive"
3376
3795
  }
3377
3796
  };
3797
+ const evidence = normalizeCampaignBuilderEvidence(request.evidence);
3798
+ if (evidence) buildRequest.evidence = evidence;
3378
3799
  const scoped = asRecord(scopeBuildArgs);
3379
3800
  buildRequest.gtmCampaignId = request.gtmCampaignId ?? stringValue(scoped.gtm_campaign_id ?? scoped.gtmCampaignId);
3801
+ buildRequest.learningHoldout = normalizeCampaignBuilderLearningHoldout(
3802
+ request.learningHoldout ?? scoped.learning_holdout ?? scoped.learningHoldout,
3803
+ buildRequest.gtmCampaignId,
3804
+ campaignName
3805
+ );
3380
3806
  if (typeof scoped.name === "string") buildRequest.name = scoped.name;
3381
3807
  if (typeof scoped.prompt === "string") buildRequest.prompt = scoped.prompt;
3382
3808
  if (typeof scoped.target_count === "number") buildRequest.targetCount = scoped.target_count;
@@ -3401,6 +3827,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
3401
3827
  memory.enabled ? "feedback" : void 0
3402
3828
  ]);
3403
3829
  const providerChain = uniqueStrings(["signaliz", ...request.preferredProviders ?? [], ...routes.map((route) => route.provider)]);
3830
+ const evidence = buildRequest.evidence ?? normalizeCampaignBuilderEvidence(request.evidence);
3404
3831
  const targetIcp = compact({
3405
3832
  personas: buildRequest.icp?.personas,
3406
3833
  industries: buildRequest.icp?.industries,
@@ -3413,6 +3840,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
3413
3840
  const learningArgs = compact({
3414
3841
  campaign_brief: request.goal,
3415
3842
  target_icp: targetIcp,
3843
+ evidence_context: evidence,
3416
3844
  layers,
3417
3845
  provider_chain: providerChain,
3418
3846
  lead_source: providerChain[0] ?? "signaliz",
@@ -3424,6 +3852,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
3424
3852
  const defaultsArgs = compact({
3425
3853
  campaign_brief: request.goal,
3426
3854
  target_icp: targetIcp,
3855
+ evidence_context: evidence,
3427
3856
  layers,
3428
3857
  include_global: memory.useNetworkPatterns,
3429
3858
  include_memory: memory.enabled,
@@ -3434,6 +3863,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
3434
3863
  provider_chain: providerChain,
3435
3864
  lead_source: providerChain[0] ?? "signaliz",
3436
3865
  target_icp: targetIcp,
3866
+ evidence_context: evidence,
3437
3867
  include_global: memory.useNetworkPatterns,
3438
3868
  limit: 25
3439
3869
  });
@@ -3446,11 +3876,14 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
3446
3876
  label: playbook.label,
3447
3877
  quality_gates: playbook.qualityGates,
3448
3878
  required_fields: playbook.requiredFields,
3449
- activation_boundary: playbook.activationBoundary
3879
+ activation_boundary: playbook.activationBoundary,
3880
+ knowledge_sources: playbook.knowledgeSources
3450
3881
  })),
3451
3882
  target_icp: targetIcp,
3883
+ evidence_context: evidence,
3452
3884
  provider_chain: providerChain,
3453
3885
  lead_source: providerChain[0] ?? "signaliz",
3886
+ learning_holdout: buildRequest.learningHoldout,
3454
3887
  tool_sequence: [
3455
3888
  { tool: "gtm_brain_learning_cycle_plan", purpose: "Plan the Brain learning loop for this campaign.", arguments: learningArgs },
3456
3889
  { tool: "gtm_brain_seed_defaults", purpose: "Seed campaign defaults from workspace memory and safe aggregate patterns.", arguments: defaultsArgs },
@@ -3720,7 +4153,7 @@ function createMcpFlow(request, buildRequest, routes, memory, approvals) {
3720
4153
  phase: "plan",
3721
4154
  tool: "nango_mcp_tools_list",
3722
4155
  arguments: {
3723
- format: "mcp"
4156
+ format: "openai"
3724
4157
  },
3725
4158
  readOnly: true,
3726
4159
  approvalRequired: false
@@ -4029,6 +4462,7 @@ function buildGtmCampaignBuildPlanArgs(request, buildRequest, routes, memory) {
4029
4462
  layers: layers.length > 0 ? layers : void 0,
4030
4463
  preferred_providers: preferredProviders.length > 0 ? preferredProviders : void 0,
4031
4464
  strategy_model: strategyModel,
4465
+ evidence_context: buildRequest.evidence ?? normalizeCampaignBuilderEvidence(request.evidence),
4032
4466
  include_strategy_patterns: campaignBuilderStrategyPatternsEnabled(agencyContext),
4033
4467
  include_workflow_patterns: campaignBuilderWorkflowPatternsEnabled(agencyContext),
4034
4468
  include_nango_catalog: agencyContext.includeNangoCatalog !== false,
@@ -4037,7 +4471,8 @@ function buildGtmCampaignBuildPlanArgs(request, buildRequest, routes, memory) {
4037
4471
  slug: playbook.slug,
4038
4472
  label: playbook.label,
4039
4473
  quality_gates: playbook.qualityGates,
4040
- activation_boundary: playbook.activationBoundary
4474
+ activation_boundary: playbook.activationBoundary,
4475
+ knowledge_sources: playbook.knowledgeSources
4041
4476
  })),
4042
4477
  include_memory: memory.enabled,
4043
4478
  include_brain: true,
@@ -4118,8 +4553,19 @@ function buildCampaignArgs(request) {
4118
4553
  if (request.dedupKeys) args.dedup_keys = request.dedupKeys;
4119
4554
  if (request.brainPreflight) args.brain_preflight = request.brainPreflight;
4120
4555
  if (request.brainDefaults) args.brain_defaults = request.brainDefaults;
4556
+ if (request.learningHoldout) {
4557
+ args.learning_holdout = compact({
4558
+ enabled: request.learningHoldout.enabled,
4559
+ control_percentage: request.learningHoldout.controlPercentage,
4560
+ min_control_size: request.learningHoldout.minControlSize,
4561
+ experiment_key: request.learningHoldout.experimentKey,
4562
+ source_campaign_id: request.learningHoldout.sourceCampaignId,
4563
+ primary_metric: request.learningHoldout.primaryMetric
4564
+ });
4565
+ }
4121
4566
  if (request.campaignStrategyContext) args.campaign_strategy_context = request.campaignStrategyContext;
4122
4567
  if (request.deliveryRisk) args.delivery_risk = request.deliveryRisk;
4568
+ if (request.evidence) args.evidence_context = request.evidence;
4123
4569
  if (request.icp) {
4124
4570
  args.icp = compact({
4125
4571
  personas: request.icp.personas,
@@ -4169,7 +4615,8 @@ function buildCampaignArgs(request) {
4169
4615
  max_body_words: request.copy.maxBodyWords,
4170
4616
  sender_context: request.copy.senderContext,
4171
4617
  offer_context: request.copy.offerContext,
4172
- model: request.copy.model
4618
+ model: request.copy.model,
4619
+ evidence_mode: request.copy.evidenceMode
4173
4620
  });
4174
4621
  }
4175
4622
  if (request.delivery) {
@@ -4214,6 +4661,7 @@ function buildExecutionPrepareArgs(request, dryRun, confirmSpend) {
4214
4661
  delivery_risk: buildArgs2.delivery_risk,
4215
4662
  dedup_keys: buildArgs2.dedup_keys,
4216
4663
  policy: buildArgs2.policy,
4664
+ learning_holdout: buildArgs2.learning_holdout,
4217
4665
  signals: buildArgs2.signals,
4218
4666
  qualification: buildArgs2.qualification,
4219
4667
  copy: buildArgs2.copy,
@@ -4248,10 +4696,145 @@ function mapBuildResult(data, dryRun) {
4248
4696
  targetLimitReason: data.target_limit_reason,
4249
4697
  maxSupportedTargetCount: data.max_supported_target_count,
4250
4698
  brainContext: data.brain_context ?? {},
4699
+ learningHoldout: data.learning_holdout ?? {},
4251
4700
  providerRoute: mapProviderRoute2(data)
4252
4701
  };
4253
4702
  }
4703
+ var CAMPAIGN_BUILDER_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "canceled", "cancelled"]);
4704
+ var CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["failed", "canceled", "cancelled"]);
4705
+ function mapAgentCampaignPhaseHealth(value) {
4706
+ if (!Array.isArray(value)) return [];
4707
+ return value.map((phase) => {
4708
+ const record = asRecord(phase);
4709
+ return {
4710
+ phase: stringValue(record.phase) ?? "unknown",
4711
+ status: stringValue(record.status) ?? "unknown",
4712
+ recordsProcessed: numberOrNull2(record.records_processed ?? record.recordsProcessed),
4713
+ recordsSucceeded: numberOrNull2(record.records_succeeded ?? record.recordsSucceeded),
4714
+ recordsFailed: numberOrNull2(record.records_failed ?? record.recordsFailed),
4715
+ heartbeatAgeSeconds: numberOrNull2(record.heartbeat_age_seconds ?? record.heartbeatAgeSeconds ?? record.phase_age_seconds)
4716
+ };
4717
+ });
4718
+ }
4719
+ function deriveCampaignBuildTerminalState(input) {
4720
+ const status = input.status.toLowerCase();
4721
+ const heartbeatAgeSeconds = input.phaseAgeSeconds;
4722
+ const stale = input.staleRunningPhase === true;
4723
+ const safeWatchAction = input.triggerRunId ? `signaliz ops run-status ${input.triggerRunId} --watch` : "signaliz campaign-agent status <campaign_build_id>";
4724
+ if (CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES.has(status)) {
4725
+ return {
4726
+ kind: "blocked",
4727
+ label: "Blocked terminal state",
4728
+ reviewable: true,
4729
+ terminal: true,
4730
+ stale: false,
4731
+ phase: input.phase,
4732
+ phaseStatus: input.phaseStatus,
4733
+ heartbeatAgeSeconds,
4734
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
4735
+ terminalReason: input.terminalReason,
4736
+ staleReason: null,
4737
+ safeNextAction: input.nextAction || "Review errors and rerun only after the blocker is resolved."
4738
+ };
4739
+ }
4740
+ if (status === "pending_approval") {
4741
+ return {
4742
+ kind: "pending_approval",
4743
+ label: "Pending delivery approval",
4744
+ reviewable: true,
4745
+ terminal: false,
4746
+ stale: false,
4747
+ phase: input.phase,
4748
+ phaseStatus: input.phaseStatus,
4749
+ heartbeatAgeSeconds,
4750
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
4751
+ terminalReason: input.terminalReason,
4752
+ staleReason: null,
4753
+ safeNextAction: input.approvalAction || input.nextAction || "Review rows and approve delivery only after the approval packet passes."
4754
+ };
4755
+ }
4756
+ if (CAMPAIGN_BUILDER_TERMINAL_STATUSES.has(status)) {
4757
+ return {
4758
+ kind: "terminal",
4759
+ label: "Terminal and reviewable",
4760
+ reviewable: true,
4761
+ terminal: true,
4762
+ stale: false,
4763
+ phase: input.phase,
4764
+ phaseStatus: input.phaseStatus,
4765
+ heartbeatAgeSeconds,
4766
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
4767
+ terminalReason: input.terminalReason,
4768
+ staleReason: null,
4769
+ safeNextAction: input.nextAction || "Review rows, artifacts, scorecard, and approval locks before delivery."
4770
+ };
4771
+ }
4772
+ if (status === "running" || status === "queued" || status === "processing") {
4773
+ if (stale) {
4774
+ return {
4775
+ kind: "running_stale",
4776
+ label: "Running but stale",
4777
+ reviewable: false,
4778
+ terminal: false,
4779
+ stale: true,
4780
+ phase: input.phase,
4781
+ phaseStatus: input.phaseStatus,
4782
+ heartbeatAgeSeconds,
4783
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
4784
+ terminalReason: null,
4785
+ staleReason: input.staleReason || "The active phase heartbeat is stale.",
4786
+ safeNextAction: input.nextAction || safeWatchAction
4787
+ };
4788
+ }
4789
+ return {
4790
+ kind: "running_healthy",
4791
+ label: "Running and healthy",
4792
+ reviewable: false,
4793
+ terminal: false,
4794
+ stale: false,
4795
+ phase: input.phase,
4796
+ phaseStatus: input.phaseStatus,
4797
+ heartbeatAgeSeconds,
4798
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
4799
+ terminalReason: null,
4800
+ staleReason: null,
4801
+ safeNextAction: input.nextAction || safeWatchAction
4802
+ };
4803
+ }
4804
+ return {
4805
+ kind: "unknown",
4806
+ label: "Unknown build state",
4807
+ reviewable: false,
4808
+ terminal: false,
4809
+ stale,
4810
+ phase: input.phase,
4811
+ phaseStatus: input.phaseStatus,
4812
+ heartbeatAgeSeconds,
4813
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
4814
+ terminalReason: input.terminalReason,
4815
+ staleReason: input.staleReason,
4816
+ safeNextAction: input.nextAction || "Read campaign build status again before making a launch or delivery decision."
4817
+ };
4818
+ }
4254
4819
  function mapAgentCampaignBuildStatus(data) {
4820
+ const customerRowCounts = mapAgentCustomerRowCounts(data.customer_row_counts);
4821
+ const phaseHealth = mapAgentCampaignPhaseHealth(data.phases);
4822
+ const terminalReason = stringValue(data.terminal_reason) ?? customerRowCounts?.terminalReason ?? null;
4823
+ const staleReason = stringValue(data.stale_reason) ?? stringValue(asRecord(data.diagnostics).stale_reason) ?? null;
4824
+ const nextPollAfterSeconds = numberOrNull2(data.next_poll_after_seconds ?? data.nextPollAfterSeconds);
4825
+ const terminalState = deriveCampaignBuildTerminalState({
4826
+ status: stringValue(data.status) ?? "unknown",
4827
+ phase: stringValue(data.current_phase) ?? null,
4828
+ phaseStatus: stringValue(data.current_phase_status) ?? null,
4829
+ staleRunningPhase: data.stale_running_phase === true,
4830
+ phaseAgeSeconds: numberOrNull2(data.phase_age_seconds),
4831
+ nextPollAfterSeconds,
4832
+ terminalReason,
4833
+ staleReason,
4834
+ nextAction: formatAgentNextAction(data.next_action),
4835
+ approvalAction: formatAgentNextAction(data.approval_action),
4836
+ triggerRunId: stringValue(data.trigger_run_id) ?? null
4837
+ });
4255
4838
  return {
4256
4839
  campaignBuildId: stringValue(data.campaign_build_id) ?? "",
4257
4840
  campaignId: stringValue(data.campaign_id) ?? null,
@@ -4269,12 +4852,18 @@ function mapAgentCampaignBuildStatus(data) {
4269
4852
  errors: diagnosticMessages2(data.errors),
4270
4853
  artifactCount: numberOrUndefined2(data.artifact_count) ?? 0,
4271
4854
  artifactDownloads: Array.isArray(data.artifact_downloads) ? data.artifact_downloads.map(mapAgentCampaignArtifactDownload) : [],
4272
- customerRowCounts: mapAgentCustomerRowCounts(data.customer_row_counts),
4855
+ customerRowCounts,
4273
4856
  phaseAttemptTotals: nullableRecord(data.phase_attempt_totals) ?? void 0,
4274
4857
  providerRoute: mapProviderRoute2(data),
4275
4858
  triggerRunId: stringValue(data.trigger_run_id) ?? null,
4276
4859
  staleRunningPhase: data.stale_running_phase === true,
4277
4860
  phaseAgeSeconds: numberOrNull2(data.phase_age_seconds),
4861
+ nextPollAfterSeconds,
4862
+ terminalReason,
4863
+ staleReason,
4864
+ safeNextAction: terminalState.safeNextAction,
4865
+ terminalState,
4866
+ phaseHealth,
4278
4867
  diagnostics: nullableRecord(data.diagnostics) ?? {},
4279
4868
  nextAction: formatAgentNextAction(data.next_action),
4280
4869
  approvalAction: formatAgentNextAction(data.approval_action),
@@ -4304,6 +4893,7 @@ function mapAgentCustomerRowCounts(value) {
4304
4893
  acceptedRows: numberOrUndefined2(record.accepted_rows),
4305
4894
  usableRows: numberOrUndefined2(record.usable_rows),
4306
4895
  copiedRows: numberOrUndefined2(record.copied_rows),
4896
+ copySkippedRows: numberOrUndefined2(record.copy_skipped_rows),
4307
4897
  approvalReadyRows: numberOrUndefined2(record.approval_ready_rows),
4308
4898
  qaReadyRows: numberOrUndefined2(record.qa_ready_rows),
4309
4899
  deliveredRows: numberOrUndefined2(record.delivered_rows),
@@ -4362,67 +4952,285 @@ function mapAgentCampaignArtifact(data) {
4362
4952
  createdAt: stringValue(data.created_at) ?? ""
4363
4953
  };
4364
4954
  }
4955
+ var NORTH_STAR_BANNED_COPY_PHRASES = [
4956
+ "i have been following",
4957
+ "i saw that",
4958
+ "would you be opposed",
4959
+ "unlock new opportunities",
4960
+ "significant success",
4961
+ "we understand",
4962
+ "hope this finds you well"
4963
+ ];
4964
+ function campaignReviewGate(id, label, status, detail) {
4965
+ return {
4966
+ id,
4967
+ label,
4968
+ status,
4969
+ passed: status !== "blocked",
4970
+ detail
4971
+ };
4972
+ }
4973
+ function northStarMetric(id, label, status, detail, value, target) {
4974
+ return {
4975
+ id,
4976
+ label,
4977
+ status,
4978
+ detail,
4979
+ ...value !== void 0 ? { value } : {},
4980
+ ...target !== void 0 ? { target } : {}
4981
+ };
4982
+ }
4983
+ function createCampaignBuilderNorthStarScorecard(status, rows, artifacts, terminalState) {
4984
+ if (!terminalState.reviewable) {
4985
+ const pendingMetric = northStarMetric(
4986
+ "terminal_state",
4987
+ "Terminal-state contract",
4988
+ terminalState.kind === "running_stale" ? "blocked" : "pending",
4989
+ terminalState.kind === "running_stale" ? `Build is stale in ${terminalState.phase || "unknown phase"}; do not grade output yet.` : `Build is ${terminalState.label.toLowerCase()}; wait for terminal or approval state before grading.`,
4990
+ terminalState.kind,
4991
+ "terminal or pending_approval"
4992
+ );
4993
+ return {
4994
+ version: "campaign-builder-north-star.v1",
4995
+ status: pendingMetric.status,
4996
+ score: null,
4997
+ moatState: "scaffolding_ready",
4998
+ metrics: [pendingMetric],
4999
+ blockers: pendingMetric.status === "blocked" ? [pendingMetric.detail] : [],
5000
+ warnings: pendingMetric.status === "pending" ? [pendingMetric.detail] : [],
5001
+ nextAction: terminalState.safeNextAction
5002
+ };
5003
+ }
5004
+ const sampledRows = rows.rows.length;
5005
+ const rowData = rows.rows.map((row) => asRecord(row.data));
5006
+ const requestedTarget = status.customerRowCounts?.requestedTarget ?? status.recordsTotal ?? rows.count;
5007
+ const finalRows = status.customerRowCounts?.deliveredRows ?? status.customerRowCounts?.qaReadyRows ?? status.customerRowCounts?.copySkippedRows ?? status.customerRowCounts?.copiedRows ?? status.customerRowCounts?.acceptedRows ?? rows.count;
5008
+ const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
5009
+ const verifiedEmailRows = rowData.filter(campaignReviewDataHasVerifiedEmail).length;
5010
+ const uniqueEmails = new Set(rowData.map(campaignReviewDataEmail).filter(Boolean)).size;
5011
+ const uniqueDomains = new Set(rowData.map(campaignReviewDataDomain).filter(Boolean)).size;
5012
+ const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
5013
+ const copyQaIssuesByRow = rowData.map(campaignReviewDataCopyQaIssues).filter((issues) => issues.length > 0);
5014
+ const copyQaIssueCounts = countCampaignReviewIssues(copyQaIssuesByRow);
5015
+ const signalBackedRows = rowData.filter(campaignReviewDataHasSignalBasis).length;
5016
+ const noSupportedSignalRows = rowData.filter(campaignReviewDataHasNoSupportedSignal).length;
5017
+ const unsupportedSignalClaims = rowData.filter(campaignReviewDataHasUnsupportedSignalClaim).length;
5018
+ const approvalLockedRows = rowData.filter(campaignReviewDataHasApprovalLock).length;
5019
+ const holdoutRows = rowData.filter(campaignReviewDataHasHoldoutAttribution).length;
5020
+ const artifactCount = artifacts.length || status.artifactCount || 0;
5021
+ const deliveredRows = status.customerRowCounts?.deliveredRows ?? 0;
5022
+ const feedbackReady = campaignReviewStatusHasFeedbackReadiness(status);
5023
+ const feedbackRequiredForApproval = terminalState.kind === "pending_approval";
5024
+ const moat = campaignReviewMoatState(status, holdoutRows);
5025
+ const metrics = [
5026
+ northStarMetric(
5027
+ "terminal_state",
5028
+ "Terminal-state contract",
5029
+ terminalState.kind === "blocked" ? "blocked" : "pass",
5030
+ terminalState.label,
5031
+ terminalState.kind,
5032
+ "terminal or pending_approval"
5033
+ ),
5034
+ northStarMetric(
5035
+ "target_fill",
5036
+ "Target fill",
5037
+ requestedTarget && finalRows < requestedTarget ? "review" : "pass",
5038
+ requestedTarget ? `${finalRows || 0}/${requestedTarget} final or reviewable row(s) are accounted for.` : "No requested target was reported; using sampled rows for review.",
5039
+ finalRows || sampledRows,
5040
+ requestedTarget || sampledRows
5041
+ ),
5042
+ northStarMetric(
5043
+ "unique_emails",
5044
+ "Unique emails",
5045
+ sampledRows === 0 ? "blocked" : uniqueEmails === rowsWithEmail ? "pass" : "blocked",
5046
+ sampledRows === 0 ? "No sampled rows were available for email uniqueness review." : `${uniqueEmails}/${rowsWithEmail} sampled email(s) are unique.`,
5047
+ uniqueEmails,
5048
+ rowsWithEmail
5049
+ ),
5050
+ northStarMetric(
5051
+ "unique_domains",
5052
+ "Unique domains",
5053
+ sampledRows === 0 ? "blocked" : uniqueDomains === sampledRows ? "pass" : "review",
5054
+ sampledRows === 0 ? "No sampled rows were available for domain uniqueness review." : `${uniqueDomains}/${sampledRows} sampled company domain(s) are unique.`,
5055
+ uniqueDomains,
5056
+ sampledRows
5057
+ ),
5058
+ northStarMetric(
5059
+ "verified_email_rows",
5060
+ "Verified email rows",
5061
+ sampledRows === 0 ? "blocked" : verifiedEmailRows === sampledRows ? "pass" : rowsWithEmail === sampledRows ? "review" : "blocked",
5062
+ sampledRows === 0 ? "No sampled rows were available for email verification review." : `${verifiedEmailRows}/${sampledRows} sampled row(s) carry verified-email evidence.`,
5063
+ verifiedEmailRows,
5064
+ sampledRows
5065
+ ),
5066
+ northStarMetric(
5067
+ "copy_completeness",
5068
+ "Copy completeness",
5069
+ sampledRows === 0 ? "blocked" : copyReadyRows === sampledRows ? "pass" : "blocked",
5070
+ sampledRows === 0 ? "No sampled rows were available for copy review." : `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.`,
5071
+ copyReadyRows,
5072
+ sampledRows
5073
+ ),
5074
+ northStarMetric(
5075
+ "copy_quality",
5076
+ "Copy QA",
5077
+ copyQaIssuesByRow.length === 0 ? "pass" : "blocked",
5078
+ copyQaIssuesByRow.length === 0 ? "No sampled copy QA blockers were detected." : `${copyQaIssuesByRow.length}/${sampledRows} sampled row(s) have copy QA issue(s): ${formatCampaignReviewIssueCounts(copyQaIssueCounts)}.`,
5079
+ copyQaIssuesByRow.length,
5080
+ 0
5081
+ ),
5082
+ northStarMetric(
5083
+ "signal_grounding",
5084
+ "Signal grounding",
5085
+ unsupportedSignalClaims > 0 ? "blocked" : signalBackedRows > 0 ? "pass" : "review",
5086
+ unsupportedSignalClaims > 0 ? `${unsupportedSignalClaims} sampled row(s) make unsupported signal claims.` : signalBackedRows > 0 ? `${signalBackedRows}/${sampledRows} sampled row(s) include supported signal or personalization basis.` : noSupportedSignalRows > 0 ? `${noSupportedSignalRows}/${sampledRows} sampled row(s) are explicitly marked no_supported_signal; copy should stay conservative.` : "No sampled rows include supported signal evidence; copy should stay conservative.",
5087
+ signalBackedRows,
5088
+ sampledRows
5089
+ ),
5090
+ northStarMetric(
5091
+ "approval_locks",
5092
+ "Approval locks",
5093
+ approvalLockedRows > 0 || deliveredRows === 0 ? "pass" : "review",
5094
+ approvalLockedRows > 0 ? `${approvalLockedRows}/${sampledRows} sampled row(s) show approval/export lock metadata.` : "Approval-lock metadata was not visible in sampled rows; preserve delivery approval gates.",
5095
+ approvalLockedRows,
5096
+ sampledRows
5097
+ ),
5098
+ northStarMetric(
5099
+ "export_send_safety",
5100
+ "Export/send safety",
5101
+ deliveredRows > 0 && status.status !== "completed" ? "blocked" : "pass",
5102
+ deliveredRows > 0 ? `${deliveredRows} row(s) are delivered; status is ${status.status}.` : "No delivered rows are reported before approval.",
5103
+ deliveredRows,
5104
+ 0
5105
+ ),
5106
+ northStarMetric(
5107
+ "holdout_attribution",
5108
+ "Holdout attribution",
5109
+ holdoutRows > 0 ? "pass" : "review",
5110
+ holdoutRows > 0 ? `${holdoutRows}/${sampledRows} sampled row(s) preserve holdout attribution.` : "No holdout attribution was visible in the sampled rows.",
5111
+ holdoutRows,
5112
+ sampledRows
5113
+ ),
5114
+ northStarMetric(
5115
+ "feedback_readiness",
5116
+ "Feedback readiness",
5117
+ feedbackReady ? "pass" : feedbackRequiredForApproval ? "blocked" : "review",
5118
+ feedbackReady ? "Feedback readiness is visible in status diagnostics." : feedbackRequiredForApproval ? "Feedback webhook or approved pull readiness is required before delivery approval." : "Feedback webhook or approved pull readiness was not visible; require it before real send approval.",
5119
+ feedbackReady,
5120
+ true
5121
+ ),
5122
+ northStarMetric(
5123
+ "causal_moat",
5124
+ "Causal moat",
5125
+ moat.state === "proved" ? "pass" : "review",
5126
+ moat.detail,
5127
+ moat.state,
5128
+ "proved"
5129
+ ),
5130
+ northStarMetric(
5131
+ "artifact_readback",
5132
+ "Artifact readback",
5133
+ artifactCount > 0 ? "pass" : "review",
5134
+ artifactCount > 0 ? `${artifactCount} artifact(s) are available for review.` : "No review artifact is available yet.",
5135
+ artifactCount,
5136
+ 1
5137
+ )
5138
+ ];
5139
+ const gradedMetrics = metrics.filter((metric) => metric.status !== "pending");
5140
+ const score = gradedMetrics.length === 0 ? null : Math.round(gradedMetrics.filter((metric) => metric.status === "pass").length / gradedMetrics.length * 100);
5141
+ const blockers = metrics.filter((metric) => metric.status === "blocked").map((metric) => metric.detail);
5142
+ const warnings = metrics.filter((metric) => metric.status === "review").map((metric) => metric.detail);
5143
+ const scorecardStatus = blockers.length > 0 ? "blocked" : warnings.length > 0 ? "review" : "pass";
5144
+ return {
5145
+ version: "campaign-builder-north-star.v1",
5146
+ status: scorecardStatus,
5147
+ score,
5148
+ moatState: moat.state,
5149
+ metrics,
5150
+ blockers,
5151
+ warnings,
5152
+ nextAction: scorecardStatus === "blocked" ? "Fix blocked North Star gates before approval or delivery." : terminalState.safeNextAction
5153
+ };
5154
+ }
4365
5155
  function createCampaignBuilderBuildReview(status, rows, artifacts) {
5156
+ const terminalState = status.terminalState ?? deriveCampaignBuildTerminalState({
5157
+ status: status.status,
5158
+ phase: status.currentPhase,
5159
+ phaseStatus: status.currentPhaseStatus,
5160
+ staleRunningPhase: status.staleRunningPhase === true,
5161
+ phaseAgeSeconds: status.phaseAgeSeconds ?? null,
5162
+ nextPollAfterSeconds: status.nextPollAfterSeconds ?? null,
5163
+ terminalReason: status.terminalReason ?? status.customerRowCounts?.terminalReason ?? null,
5164
+ staleReason: status.staleReason ?? null,
5165
+ nextAction: status.nextAction,
5166
+ approvalAction: status.approvalAction,
5167
+ triggerRunId: status.triggerRunId ?? null
5168
+ });
4366
5169
  const sampledRows = rows.rows.length;
4367
5170
  const availableRows = rows.count || sampledRows;
4368
5171
  const qualifiedRows = rows.rows.filter((row) => row.qualified !== false).length;
4369
5172
  const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
4370
5173
  const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
4371
5174
  const artifactCount = artifacts.length || status.artifactCount || 0;
5175
+ const northStarScorecard = createCampaignBuilderNorthStarScorecard(status, rows, artifacts, terminalState);
4372
5176
  const gates = [
4373
- {
4374
- id: "build_completed",
4375
- label: "Build completed",
4376
- passed: status.status === "completed",
4377
- detail: status.status === "completed" ? "The campaign build is completed." : `The campaign build is ${status.status}; review again after completion.`
4378
- },
4379
- {
4380
- id: "rows_available",
4381
- label: "Rows available",
4382
- passed: sampledRows > 0,
4383
- detail: sampledRows > 0 ? `${sampledRows} sampled row(s) are available for review.` : "No rows were returned for review."
4384
- },
4385
- {
4386
- id: "qualified_sample",
4387
- label: "Sample rows qualified",
4388
- passed: sampledRows > 0 && qualifiedRows === sampledRows,
4389
- detail: sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
4390
- },
4391
- {
4392
- id: "email_coverage",
4393
- label: "Email coverage",
4394
- passed: sampledRows > 0 && rowsWithEmail === sampledRows,
4395
- detail: sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
4396
- },
4397
- {
4398
- id: "artifact_available",
4399
- label: "Artifact available",
4400
- passed: artifactCount > 0,
4401
- detail: artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
4402
- },
4403
- {
4404
- id: "no_failed_rows",
4405
- label: "No failed rows",
4406
- passed: status.recordsFailed === 0,
4407
- detail: status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
4408
- }
5177
+ campaignReviewGate(
5178
+ "build_reviewable",
5179
+ "Build reviewable",
5180
+ terminalState.kind === "running_healthy" ? "pending" : terminalState.kind === "running_stale" || terminalState.kind === "blocked" ? "blocked" : "pass",
5181
+ terminalState.label
5182
+ ),
5183
+ campaignReviewGate(
5184
+ "rows_available",
5185
+ "Rows available",
5186
+ sampledRows > 0 ? "pass" : terminalState.reviewable ? "blocked" : "pending",
5187
+ sampledRows > 0 ? `${sampledRows} sampled row(s) are available for review.` : terminalState.reviewable ? "No rows were returned for review." : "Rows are not graded until the build is reviewable."
5188
+ ),
5189
+ campaignReviewGate(
5190
+ "qualified_sample",
5191
+ "Sample rows qualified",
5192
+ sampledRows === 0 ? terminalState.reviewable ? "blocked" : "pending" : qualifiedRows === sampledRows ? "pass" : "blocked",
5193
+ sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
5194
+ ),
5195
+ campaignReviewGate(
5196
+ "email_coverage",
5197
+ "Email coverage",
5198
+ sampledRows === 0 ? terminalState.reviewable ? "blocked" : "pending" : rowsWithEmail === sampledRows ? "pass" : "blocked",
5199
+ sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
5200
+ ),
5201
+ campaignReviewGate(
5202
+ "artifact_available",
5203
+ "Artifact available",
5204
+ artifactCount > 0 ? "pass" : terminalState.reviewable ? "review" : "pending",
5205
+ artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
5206
+ ),
5207
+ campaignReviewGate(
5208
+ "no_failed_rows",
5209
+ "No failed rows",
5210
+ status.recordsFailed === 0 ? "pass" : "blocked",
5211
+ status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
5212
+ )
4409
5213
  ];
4410
- const blockedReasons = gates.filter((gate) => !gate.passed).map((gate) => gate.detail);
5214
+ const blockedReasons = gates.filter((gate) => gate.status === "blocked" || !gate.passed).map((gate) => gate.detail);
4411
5215
  const warnings = [
4412
5216
  rows.hasMore ? "This review is based on a row sample; page through remaining rows before final approval." : void 0,
4413
5217
  sampledRows > 0 && copyReadyRows < sampledRows ? `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.` : void 0,
4414
5218
  ...status.warnings
4415
5219
  ].filter((item) => Boolean(item));
4416
- const readyForDeliveryApproval = blockedReasons.length === 0;
5220
+ const readyForDeliveryApproval = terminalState.reviewable && blockedReasons.length === 0 && northStarScorecard.status !== "blocked";
4417
5221
  return {
4418
5222
  campaignBuildId: status.campaignBuildId || rows.campaignBuildId,
4419
5223
  status,
4420
5224
  rows,
4421
5225
  artifacts,
4422
5226
  gates,
5227
+ northStarScorecard,
4423
5228
  summary: {
4424
5229
  status: status.status,
4425
5230
  phase: status.currentPhase,
5231
+ terminalState: terminalState.kind,
5232
+ scorecardStatus: northStarScorecard.status,
5233
+ scorecardScore: northStarScorecard.score,
4426
5234
  sampledRows,
4427
5235
  availableRows,
4428
5236
  qualifiedRows,
@@ -4476,6 +5284,184 @@ function campaignReviewRowHasCopy(row) {
4476
5284
  stringValue(data.subject) || stringValue(data.subject_line) || stringValue(data.subjectLine) || stringValue(data.opener) || stringValue(data.body) || stringValue(data.cta) || stringValue(copy.subject) || stringValue(copy.body) || stringValue(rawCopy.subject) || stringValue(rawCopy.body)
4477
5285
  );
4478
5286
  }
5287
+ function campaignReviewDataEmail(data) {
5288
+ return stringValue(data.email) || stringValue(data.work_email) || stringValue(data.workEmail) || stringValue(asRecord(data.contact).email);
5289
+ }
5290
+ function campaignReviewDataDomain(data) {
5291
+ const domain = stringValue(data.company_domain) || stringValue(data.companyDomain) || stringValue(asRecord(data.company).domain);
5292
+ return domain?.toLowerCase();
5293
+ }
5294
+ function campaignReviewDataHasVerifiedEmail(data) {
5295
+ const status = String(data.email_status ?? data.emailStatus ?? asRecord(data.contact).email_status ?? "").toLowerCase();
5296
+ return data.email_verified === true || data.emailVerified === true || asRecord(data.contact).email_verified === true || ["valid", "validated", "verified"].includes(status);
5297
+ }
5298
+ function campaignReviewDataCopyParts(data) {
5299
+ const copy = asRecord(data.copy);
5300
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
5301
+ return [
5302
+ stringValue(data.subject),
5303
+ stringValue(data.subject_line),
5304
+ stringValue(data.opener),
5305
+ stringValue(data.body),
5306
+ stringValue(data.cta),
5307
+ stringValue(copy.subject),
5308
+ stringValue(copy.opener),
5309
+ stringValue(copy.body),
5310
+ stringValue(copy.cta),
5311
+ stringValue(rawCopy.subject),
5312
+ stringValue(rawCopy.opener),
5313
+ stringValue(rawCopy.body),
5314
+ stringValue(rawCopy.cta)
5315
+ ].filter((part) => Boolean(part));
5316
+ }
5317
+ function campaignReviewDataHasBadGreeting(data) {
5318
+ return campaignReviewDataCopyParts(data).some(
5319
+ (part) => /\bhi\s*(?:,|\{\{|\[)|\bhello\s*(?:,|\{\{|\[)/i.test(part) || /\b(first_name|firstname|name)\b/i.test(part)
5320
+ );
5321
+ }
5322
+ function campaignReviewDataHasBannedPhrase(data) {
5323
+ const text = campaignReviewDataCopyParts(data).join(" ").toLowerCase();
5324
+ return NORTH_STAR_BANNED_COPY_PHRASES.some((phrase) => text.includes(phrase));
5325
+ }
5326
+ function campaignReviewDataSubject(data) {
5327
+ const copy = asRecord(data.copy);
5328
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
5329
+ return stringValue(data.subject) || stringValue(data.subject_line) || stringValue(data.subjectLine) || stringValue(copy.subject) || stringValue(rawCopy.subject);
5330
+ }
5331
+ function campaignReviewDataCta(data) {
5332
+ const copy = asRecord(data.copy);
5333
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
5334
+ return stringValue(data.cta) || stringValue(copy.cta) || stringValue(rawCopy.cta);
5335
+ }
5336
+ function countCampaignReviewIssues(issueRows) {
5337
+ return issueRows.flat().reduce((acc, issue) => {
5338
+ acc[issue] = (acc[issue] || 0) + 1;
5339
+ return acc;
5340
+ }, {});
5341
+ }
5342
+ function formatCampaignReviewIssueCounts(counts) {
5343
+ const entries = Object.entries(counts);
5344
+ return entries.length > 0 ? entries.map(([issue, count]) => `${issue}=${count}`).join(", ") : "none";
5345
+ }
5346
+ function campaignReviewDataCopyQaIssues(data) {
5347
+ const issues = /* @__PURE__ */ new Set();
5348
+ const copyParts = campaignReviewDataCopyParts(data);
5349
+ if (copyParts.length === 0) return [];
5350
+ if (campaignReviewDataHasBadGreeting(data)) issues.add("bad_greeting");
5351
+ if (campaignReviewDataHasBannedPhrase(data)) issues.add("banned_or_vague_phrase");
5352
+ if (campaignReviewDataHasUnsupportedSignalClaim(data)) issues.add("unsupported_signal_claim");
5353
+ if (campaignReviewDataPersonalizationBasis(data).length === 0) issues.add("missing_personalization_basis");
5354
+ if (!campaignReviewDataCta(data)) issues.add("missing_cta");
5355
+ const subject = campaignReviewDataSubject(data);
5356
+ if (subject && subject.length > 70) issues.add("overlong_subject");
5357
+ return [...issues];
5358
+ }
5359
+ function campaignReviewDataPersonalizationBasis(data) {
5360
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
5361
+ const copy = asRecord(data.copy);
5362
+ const metadata = asRecord(data.metadata);
5363
+ const signalEvidence = asRecord(data.signal_evidence ?? metadata.signal_evidence ?? metadata.signalEvidence);
5364
+ return [
5365
+ ...arrayOfStrings(data.personalization_basis) ?? [],
5366
+ ...arrayOfStrings(data.personalizationBasis) ?? [],
5367
+ ...arrayOfStrings(copy.personalization_basis) ?? [],
5368
+ ...arrayOfStrings(rawCopy.personalization_basis) ?? [],
5369
+ ...arrayOfStrings(metadata.personalization_basis) ?? [],
5370
+ ...arrayOfStrings(metadata.personalizationBasis) ?? [],
5371
+ ...arrayOfStrings(signalEvidence.personalization_basis) ?? []
5372
+ ];
5373
+ }
5374
+ function campaignReviewDataHasNoSupportedSignal(data) {
5375
+ const metadata = asRecord(data.metadata);
5376
+ const copy = asRecord(data.copy);
5377
+ const state = [
5378
+ data.copy_signal_state,
5379
+ data.signal_basis,
5380
+ data.signal_type,
5381
+ metadata.copy_signal_state,
5382
+ metadata.signal_basis,
5383
+ metadata.signal_type,
5384
+ copy.copy_signal_state
5385
+ ].map((value) => stringValue(value)?.toLowerCase()).filter(Boolean);
5386
+ return data.no_supported_signal === true || metadata.no_supported_signal === true || state.includes("no_supported_signal");
5387
+ }
5388
+ function campaignReviewDataHasSignalBasis(data) {
5389
+ if (campaignReviewDataHasNoSupportedSignal(data)) return false;
5390
+ const metadata = asRecord(data.metadata);
5391
+ const signal = asRecord(data.signal);
5392
+ const signalEvidence = asRecord(data.signal_evidence ?? metadata.signal_evidence ?? metadata.signalEvidence);
5393
+ const basis = campaignReviewDataPersonalizationBasis(data);
5394
+ return Boolean(
5395
+ stringValue(data.signal_type) || stringValue(data.signalType) || stringValue(data.signal_summary) || stringValue(data.signalSummary) || stringValue(data.signal_source_url) || stringValue(data.signalSourceUrl) || stringValue(metadata.signal_type) || stringValue(metadata.signal_summary) || stringValue(metadata.signal_source_url) || stringValue(signal.type) || stringValue(signal.summary) || stringValue(signal.source_url) || stringValue(signalEvidence.signal_type) || stringValue(signalEvidence.summary) || stringValue(signalEvidence.source_url) || basis.some((item) => /\bsignal|hiring|funding|leadership|growth|launch|news|technology\b/i.test(item))
5396
+ );
5397
+ }
5398
+ function campaignReviewDataHasUnsupportedSignalClaim(data) {
5399
+ const copyText = campaignReviewDataCopyParts(data).join(" ").toLowerCase();
5400
+ const claimsSignal = /\b(hiring|funding|raised|launched|expanding|appointed|announced|recent news|new role|job posting)\b/.test(copyText);
5401
+ return claimsSignal && !campaignReviewDataHasSignalBasis(data);
5402
+ }
5403
+ function campaignReviewDataHasApprovalLock(data) {
5404
+ const metadata = asRecord(data.fit_metadata ?? data.metadata);
5405
+ const approval = String(data.approval_status ?? data.delivery_approval_status ?? metadata.approval_status ?? "").toLowerCase();
5406
+ const exportStatus = String(data.export_status ?? data.exportStatus ?? "").toLowerCase();
5407
+ return data.export_ready === false || data.exportReady === false || data.delivery_approved === false || data.deliveryApproved === false || ["pending", "pending_approval", "blocked", "approval_required", "held"].includes(approval) || ["pending", "blocked", "approval_required", "held"].includes(exportStatus);
5408
+ }
5409
+ function campaignReviewDataHasHoldoutAttribution(data) {
5410
+ const fitMetadata = asRecord(data.fit_metadata ?? data.fitMetadata);
5411
+ const metadata = asRecord(data.metadata);
5412
+ const holdout = asRecord(
5413
+ fitMetadata.learning_holdout ?? fitMetadata.learningHoldout ?? metadata.learning_holdout ?? metadata.learningHoldout ?? data.learning_holdout ?? data.learningHoldout
5414
+ );
5415
+ const experimentKey = stringValue(holdout.experiment_key ?? holdout.experimentKey ?? data.experiment_key ?? data.experimentKey);
5416
+ const cohort = stringValue(holdout.cohort_role ?? holdout.cohortRole ?? data.cohort_role ?? data.learning_cohort ?? data.learningCohort);
5417
+ const brainApplied = holdout.brain_applied ?? holdout.brainApplied ?? data.brain_applied ?? data.signaliz_brain_applied;
5418
+ return Boolean(experimentKey && cohort && brainApplied !== void 0);
5419
+ }
5420
+ function campaignReviewStatusHasFeedbackReadiness(status) {
5421
+ const diagnostics = asRecord(status.diagnostics);
5422
+ const feedbackReadiness = asRecord(diagnostics.feedback_readiness ?? diagnostics.feedbackReadiness);
5423
+ const feedbackSubstrate = asRecord(diagnostics.feedback_substrate ?? diagnostics.feedbackSubstrate);
5424
+ const evidenceCounts = asRecord(diagnostics.evidence_counts ?? diagnostics.evidenceCounts);
5425
+ return feedbackReadiness.ready === true || feedbackReadiness.webhook_configured === true || feedbackSubstrate.stage === "ready" || Number(evidenceCounts.feedback_events ?? 0) > 0;
5426
+ }
5427
+ function campaignReviewMoatState(status, holdoutRows) {
5428
+ const diagnostics = asRecord(status.diagnostics);
5429
+ const evidenceCounts = asRecord(diagnostics.evidence_counts ?? diagnostics.evidenceCounts);
5430
+ const embeddedHoldout = asRecord(diagnostics.embedded_holdout ?? diagnostics.embeddedHoldout);
5431
+ const holdoutLift = asRecord(diagnostics.holdout_lift ?? diagnostics.holdoutLift ?? diagnostics.holdout_measurement ?? diagnostics.holdoutMeasurement);
5432
+ const controlSample = Math.max(
5433
+ Number(evidenceCounts.holdout_control_sample_size ?? 0),
5434
+ Number(evidenceCounts.embedded_holdout_control_sample ?? 0),
5435
+ Number(embeddedHoldout.control_sample_size ?? 0)
5436
+ );
5437
+ const learnedSample = Math.max(
5438
+ Number(evidenceCounts.holdout_learned_sample_size ?? 0),
5439
+ Number(evidenceCounts.embedded_holdout_learned_sample ?? 0),
5440
+ Number(embeddedHoldout.learned_sample_size ?? 0)
5441
+ );
5442
+ const feedbackEvents = Math.max(
5443
+ Number(evidenceCounts.feedback_events ?? 0),
5444
+ Number(evidenceCounts.campaign_build_feedback_events ?? 0),
5445
+ Number(evidenceCounts.delivery_holdout_sent_outcomes ?? 0)
5446
+ );
5447
+ const proved = diagnostics.closed_loop_proof_ready === true || holdoutLift.honest_measurement === true && holdoutLift.positive_lift === true;
5448
+ if (proved) {
5449
+ return {
5450
+ state: "proved",
5451
+ detail: "Real attributed feedback shows positive lift for explicit control and learned cohorts."
5452
+ };
5453
+ }
5454
+ if (controlSample > 0 && learnedSample > 0 && feedbackEvents > 0) {
5455
+ return {
5456
+ state: "moved_needs_lift_volume",
5457
+ detail: `Control and learned cohorts have attributed feedback (${controlSample} control, ${learnedSample} learned, ${feedbackEvents} feedback event(s)); keep collecting lift volume before claiming proof.`
5458
+ };
5459
+ }
5460
+ return {
5461
+ state: "scaffolding_ready",
5462
+ detail: holdoutRows > 0 ? `${holdoutRows} sampled row(s) carry holdout attribution, but real control/learned feedback lift is not proved yet.` : "Holdout and feedback scaffolding may be ready, but no real control/learned feedback lift is proved yet."
5463
+ };
5464
+ }
4479
5465
  function createCampaignBuilderReadinessLane(route, plan) {
4480
5466
  const builtIn = campaignBuilderBuiltInForRoute(route);
4481
5467
  const customRoute = route.provider !== "signaliz" && route.provider !== "csv" && builtIn === void 0;
@@ -4509,6 +5495,342 @@ function createCampaignBuilderReadinessLane(route, plan) {
4509
5495
  nextAction: customRoute ? `Review ${route.provider} route setup before launch approval.` : void 0
4510
5496
  };
4511
5497
  }
5498
+ function createCampaignBuilderApprovalPacket(plan, approval, missingTypes) {
5499
+ const requestedTypes = plan ? uniqueStrings(plan.approvals.filter((item) => item.blocking).map((item) => item.type)) : [];
5500
+ const approvalMatchesPlan = Boolean(plan && approval?.planId === plan.planId);
5501
+ const approvedTypes = approvalMatchesPlan ? approval?.approvedTypes ?? [] : [];
5502
+ const approvedRoutes = approvalMatchesPlan ? approval?.approvedRouteIds ?? [] : [];
5503
+ const missingIds = new Set(
5504
+ plan && approvalMatchesPlan && approval ? getMissingCampaignBuilderApprovals(plan, approval).map((item) => item.id) : plan?.approvals.filter((item) => item.blocking).map((item) => item.id) ?? []
5505
+ );
5506
+ const routeIdsRequired = plan ? uniqueStrings(plan.approvals.map((item) => item.routeId).filter((id) => Boolean(id))) : [];
5507
+ const spendLimitRequired = plan ? Math.max(0, ...plan.approvals.filter((item) => item.type === "spend" && item.blocking).map((item) => item.estimatedCredits ?? plan.estimatedCredits)) : 0;
5508
+ const normalizedSpendLimit = spendLimitRequired > 0 ? spendLimitRequired : null;
5509
+ const approvalState = !plan ? "unscoped" : missingTypes.length > 0 ? "requires_approval" : "approved";
5510
+ const approvalInput = {
5511
+ approved_by: approval?.approvedBy ?? null,
5512
+ approved_at: approval?.approvedAt ?? null,
5513
+ approved_types: approvedTypes,
5514
+ approved_route_ids: approvedRoutes,
5515
+ spend_limit_credits: approval?.spendLimitCredits ?? null,
5516
+ notes: approval?.notes
5517
+ };
5518
+ return {
5519
+ packet_version: "campaign-builder-approval-packet.v1",
5520
+ read_only: true,
5521
+ no_spend: true,
5522
+ no_provider_writes: true,
5523
+ private_safe: true,
5524
+ approval_state: approvalState,
5525
+ plan_id: plan?.planId ?? null,
5526
+ campaign_name: plan?.campaignName ?? null,
5527
+ requested_approval_types: requestedTypes,
5528
+ missing_approval_types: uniqueStrings(missingTypes),
5529
+ spend_limit_credits_required: normalizedSpendLimit,
5530
+ route_ids_required: routeIdsRequired,
5531
+ required_approvals: (plan?.approvals ?? []).map((item) => compact({
5532
+ id: item.id,
5533
+ type: item.type,
5534
+ label: item.label,
5535
+ reason: item.reason,
5536
+ blocking: item.blocking,
5537
+ route_id: item.routeId,
5538
+ provider: item.provider,
5539
+ estimated_credits: item.estimatedCredits,
5540
+ status: missingIds.has(item.id) ? "missing" : "approved"
5541
+ })),
5542
+ approval_input: approvalInput,
5543
+ cli_handoff: plan ? {
5544
+ command: campaignBuilderApprovalCliHandoff(requestedTypes, routeIdsRequired, normalizedSpendLimit),
5545
+ safety: "Template only: run after explicit human approval identity, approved types, route scope, and spend limit are filled in."
5546
+ } : null,
5547
+ sdk_handoff: plan ? {
5548
+ method: "campaignBuilderAgent.buildApprovedPlan",
5549
+ approval: compact({
5550
+ approvedBy: approval?.approvedBy ?? "<operator-email>",
5551
+ approvedTypes: requestedTypes,
5552
+ approvedRouteIds: routeIdsRequired,
5553
+ spendLimitCredits: normalizedSpendLimit ?? void 0,
5554
+ notes: approval?.notes
5555
+ }),
5556
+ options: {
5557
+ idempotencyKey: "<set-before-execution>"
5558
+ },
5559
+ safety: "Call only after the approval packet is accepted; this helper does not execute writes or spend."
5560
+ } : null,
5561
+ mcp_handoffs: plan ? campaignBuilderApprovalMcpHandoffs(plan, requestedTypes, routeIdsRequired, normalizedSpendLimit) : [],
5562
+ safety_boundary: "This approval packet is a reusable request for human approval; it is not an approval grant and it does not authorize spend, provider writes, sender loading, delivery, or launch by itself."
5563
+ };
5564
+ }
5565
+ function campaignBuilderApprovalCliHandoff(requestedTypes, routeIds, spendLimit) {
5566
+ return [
5567
+ "signaliz campaign-agent build-approved --request-file campaign-request.json --confirm-launch",
5568
+ requestedTypes.length > 0 ? `--approved-types ${shellArg(requestedTypes.join(","))}` : "--approve-all",
5569
+ "--approved-by <operator-email>",
5570
+ spendLimit !== null ? `--spend-limit ${spendLimit}` : void 0,
5571
+ routeIds.length > 0 ? `--approved-route-ids ${shellArg(routeIds.join(","))}` : void 0
5572
+ ].filter(Boolean).join(" ");
5573
+ }
5574
+ function campaignBuilderApprovalMcpHandoffs(plan, requestedTypes, routeIds, spendLimit) {
5575
+ const approvalContext = compact({
5576
+ plan_id: plan.planId,
5577
+ approval_types: requestedTypes,
5578
+ approved_route_ids: routeIds,
5579
+ spend_limit_credits: spendLimit,
5580
+ approved_by: "<operator-email>"
5581
+ });
5582
+ return plan.mcpFlow.filter((step) => ["execution-prepare", "approved-build", "delivery-approval"].includes(step.id)).map((step) => ({
5583
+ id: step.id,
5584
+ tool: step.tool,
5585
+ arguments: step.id === "approved-build" ? compact({ ...step.arguments, approval_context: approvalContext }) : step.arguments,
5586
+ read_only: step.readOnly,
5587
+ approval_required: step.approvalRequired,
5588
+ safety: step.readOnly ? "Read-only readiness handoff; safe to inspect before approval." : "Write-capable handoff; run only after the approval packet is accepted."
5589
+ }));
5590
+ }
5591
+ function campaignBuilderActiveWorkNextMcpAction(input) {
5592
+ const campaignBuildRef = input.refs.find((ref) => ref.kind === "campaign_build" && ref.id);
5593
+ if (campaignBuildRef) {
5594
+ return {
5595
+ id: "check-campaign-build-status",
5596
+ tool: "get_campaign_build_status",
5597
+ arguments: { campaign_build_id: campaignBuildRef.id },
5598
+ read_only: true,
5599
+ approval_required: false,
5600
+ safety: "Read-only status check for the remembered campaign build; does not spend, write, export, send, or approve anything."
5601
+ };
5602
+ }
5603
+ const campaignRef = input.refs.find((ref) => ref.kind === "gtm_campaign" && ref.id);
5604
+ if (campaignRef) {
5605
+ return {
5606
+ id: "check-gtm-campaign-status",
5607
+ tool: "gtm_campaign_execution_status",
5608
+ arguments: { campaign_id: campaignRef.id },
5609
+ read_only: true,
5610
+ approval_required: false,
5611
+ safety: "Read-only campaign execution status check; does not start or mutate the campaign."
5612
+ };
5613
+ }
5614
+ const preferredStepId = input.state === "ready_for_dry_run" ? "dry-run-build" : input.state === "planning" || input.state === "blocked" ? "agency-campaign-build-plan" : null;
5615
+ const preferredStep = preferredStepId ? input.plan?.mcpFlow.find((step) => step.id === preferredStepId && step.readOnly) : void 0;
5616
+ if (preferredStep) return campaignBuilderMcpStepToActiveWorkAction(preferredStep, "Read-only continuation from the saved campaign-agent plan.");
5617
+ const readOnlyApprovalHandoff = input.approvalPacket.mcp_handoffs.find((handoff) => handoff.read_only && !handoff.approval_required);
5618
+ if (readOnlyApprovalHandoff) return readOnlyApprovalHandoff;
5619
+ const firstReadOnlyPlanStep = input.plan?.mcpFlow.find((step) => step.readOnly && !step.approvalRequired);
5620
+ return firstReadOnlyPlanStep ? campaignBuilderMcpStepToActiveWorkAction(firstReadOnlyPlanStep, "Read-only saved plan check; safe before approvals or writes.") : null;
5621
+ }
5622
+ function campaignBuilderMcpStepToActiveWorkAction(step, safety) {
5623
+ return {
5624
+ id: step.id,
5625
+ tool: step.tool,
5626
+ arguments: step.arguments,
5627
+ read_only: step.readOnly,
5628
+ approval_required: step.approvalRequired,
5629
+ safety
5630
+ };
5631
+ }
5632
+ function normalizeCampaignBuilderActiveWorkRefs(input) {
5633
+ const refs = [];
5634
+ const pushRef = (ref) => {
5635
+ if (!ref?.id) return;
5636
+ refs.push(ref);
5637
+ };
5638
+ pushRef(input.plan?.planId ? { kind: "plan", id: input.plan.planId, label: input.plan.campaignName, source: "plan" } : void 0);
5639
+ pushRef(input.remembered.planId ? { kind: "plan", id: input.remembered.planId, source: "remembered" } : void 0);
5640
+ pushRef(
5641
+ input.plan?.buildRequest.gtmCampaignId ?? input.remembered.gtmCampaignId ? {
5642
+ kind: "gtm_campaign",
5643
+ id: input.plan?.buildRequest.gtmCampaignId ?? input.remembered.gtmCampaignId,
5644
+ label: input.plan?.campaignName,
5645
+ source: input.plan?.buildRequest.gtmCampaignId ? "plan" : "remembered"
5646
+ } : void 0
5647
+ );
5648
+ const campaignBuildId = stringValue(input.lastResult.campaign_build_id ?? input.lastResult.campaignBuildId) ?? input.remembered.campaignBuildId;
5649
+ pushRef(campaignBuildId ? { kind: "campaign_build", id: campaignBuildId, status: activeWorkStatus(input.lastResult) ?? input.remembered.status, source: "result" } : void 0);
5650
+ pushRef(
5651
+ input.remembered.providerCampaignId ? {
5652
+ kind: "provider_campaign",
5653
+ id: input.remembered.providerCampaignId,
5654
+ label: input.remembered.provider,
5655
+ status: input.remembered.status,
5656
+ source: "remembered"
5657
+ } : void 0
5658
+ );
5659
+ for (const ref of input.remembered.refs ?? []) pushRef(ref);
5660
+ for (const ref of input.refs ?? []) pushRef(ref);
5661
+ const seen = /* @__PURE__ */ new Set();
5662
+ return refs.filter((ref) => {
5663
+ const key = `${ref.kind}:${ref.id}`;
5664
+ if (seen.has(key)) return false;
5665
+ seen.add(key);
5666
+ return true;
5667
+ });
5668
+ }
5669
+ function inferCampaignBuilderActiveWorkState(input) {
5670
+ if (input.blockers.length > 0) return "blocked";
5671
+ const status = activeWorkStatus(input.lastResult) ?? input.remembered.status;
5672
+ const normalized = status?.toLowerCase();
5673
+ if (normalized && ["completed", "complete", "succeeded", "success", "done"].includes(normalized)) return "completed";
5674
+ if (normalized && ["queued", "running", "in_progress", "processing", "pending"].includes(normalized)) return "queued_or_running";
5675
+ const dryRunComplete = input.lastResult.dry_run === true || input.lastResult.dryRun === true || normalized === "dry_run";
5676
+ if (dryRunComplete) return input.missingApprovals.length > 0 ? "needs_approval" : "dry_run_complete";
5677
+ if (input.missingApprovals.length > 0) return "needs_approval";
5678
+ if (input.readiness?.summary.ready === true) return "ready_for_dry_run";
5679
+ if (input.plan) return "planning";
5680
+ return "unknown";
5681
+ }
5682
+ function campaignBuilderActiveWorkApprovalBoundary(missingApprovals, state) {
5683
+ if (missingApprovals.length > 0) {
5684
+ return `Blocked before spend, provider writes, delivery, or launch until approval covers: ${uniqueStrings(missingApprovals).join(", ")}.`;
5685
+ }
5686
+ if (state === "dry_run_complete" || state === "ready_for_dry_run") {
5687
+ return "Read-only planning and dry-runs are allowed; spend, provider writes, sender loading, delivery, and launch still require explicit approval.";
5688
+ }
5689
+ return "This packet is read-only and does not authorize spend, provider writes, sender loading, delivery, or launch.";
5690
+ }
5691
+ function campaignBuilderActiveWorkNextAction(state, blockers, missingApprovals, refs) {
5692
+ if (blockers.length > 0) return `Resolve blocker: ${blockers[0]}`;
5693
+ if (missingApprovals.length > 0) return `Ask the operator to approve ${uniqueStrings(missingApprovals).join(", ")} before any write or spend action.`;
5694
+ if (state === "ready_for_dry_run") return "Run or inspect the read-only campaign dry-run, then return the receipt for approval review.";
5695
+ if (state === "dry_run_complete") return "Review the dry-run receipt and collect explicit launch or delivery approval before any write action.";
5696
+ if (state === "queued_or_running") return "Poll the remembered campaign build or provider job status; do not start a duplicate job.";
5697
+ if (state === "completed") return "Review final artifacts and delivery gates before export, sync, sender load, or launch.";
5698
+ if (refs.length > 0) return "Use the remembered refs to fetch current read-only status before asking the operator for IDs.";
5699
+ return "Create or refresh a campaign-builder plan before taking any spendful or write action.";
5700
+ }
5701
+ function campaignBuilderActiveWorkConversationStarters(input) {
5702
+ const starters = [];
5703
+ if (input.blockers.length > 0) {
5704
+ starters.push(
5705
+ `I found a blocker: ${input.blockers[0]}. Want me to stay in read-only diagnostics and prepare the fix path?`
5706
+ );
5707
+ }
5708
+ if (input.missingApprovals.length > 0) {
5709
+ starters.push(
5710
+ `You still owe approval for ${uniqueStrings(input.missingApprovals).join(", ")}. Want the exact approval packet before anything writes or spends?`
5711
+ );
5712
+ }
5713
+ if (input.state === "queued_or_running") {
5714
+ const buildRef = input.refs.find((ref) => ref.kind === "campaign_build");
5715
+ starters.push(buildRef ? `I can check campaign build ${buildRef.id} now and avoid starting a duplicate job.` : "I can check the running job status now and avoid starting duplicate work.");
5716
+ }
5717
+ if (input.state === "dry_run_complete") {
5718
+ starters.push("The dry run is complete. Want me to summarize blockers, artifacts, and the approval handoff?");
5719
+ }
5720
+ if (input.state === "ready_for_dry_run") {
5721
+ starters.push("Everything is ready for a safe dry run. Want me to run the no-spend preview first?");
5722
+ }
5723
+ if (input.state === "completed") {
5724
+ starters.push("The build looks complete. Want me to review rows, artifacts, and delivery gates before export or send approval?");
5725
+ }
5726
+ starters.push(`Next safe action: ${input.nextSafeAction}`);
5727
+ return uniqueStrings(starters).slice(0, 4);
5728
+ }
5729
+ function createCampaignBuilderOpenAiAgentHandoff(input) {
5730
+ const readOnlyPlanActions = input.plan?.mcpFlow.filter((step) => step.readOnly && !step.approvalRequired).map((step) => campaignBuilderMcpStepToActiveWorkAction(step, "Read-only Signaliz MCP continuation for the saved campaign-agent plan.")) ?? [];
5731
+ const routeSetupActions = readOnlyPlanActions.filter(
5732
+ (action) => action.tool === "gtm_provider_recipe_prepare" || action.tool === "gtm_provider_route_activate"
5733
+ );
5734
+ const safeReadActions = uniqueCampaignBuilderActiveWorkActions([
5735
+ input.nextSafeMcpAction,
5736
+ ...routeSetupActions,
5737
+ ...readOnlyPlanActions
5738
+ ]);
5739
+ const approvalGatedActions = uniqueCampaignBuilderActiveWorkActions([
5740
+ ...input.approvalPacket.mcp_handoffs.filter((action) => action.approval_required || !action.read_only),
5741
+ ...input.plan?.mcpFlow.filter((step) => step.approvalRequired || !step.readOnly).map((step) => campaignBuilderMcpStepToActiveWorkAction(step, "Approval-gated Signaliz MCP continuation for the saved campaign-agent plan.")) ?? []
5742
+ ]);
5743
+ const approvalTypes = uniqueStrings([
5744
+ ...input.missingApprovals,
5745
+ ...input.approvalPacket.requested_approval_types
5746
+ ]);
5747
+ return {
5748
+ packet_version: "campaign-builder-openai-agent-handoff.v1",
5749
+ surface: "openai_agents_sdk",
5750
+ agent: {
5751
+ name: "Signaliz GTM Campaign Agent",
5752
+ handoff_description: "Owns safe, conversational GTM campaign planning and continuation across Signaliz MCP, SDK, attachments, URLs, customer tools, approvals, and sender/export gates.",
5753
+ instructions: [
5754
+ "Be warm, direct, and expert-level GTM. Translate the current state into plain operator language before proposing work.",
5755
+ "Start from the packet state, refs, evidence_context, approval_boundary, and next_safe_action. Do not ask for IDs already present in refs or summary.",
5756
+ "Use attachment_summary, url_summary, and operator_notes as first-class planning evidence, while honoring the evidence privacy_boundary and withheld raw fields.",
5757
+ "Prefer Signaliz-native, cached, remembered, and read-only MCP actions before provider calls. Never start duplicate jobs when a campaign_build or provider job is queued or running.",
5758
+ "Call only tools listed in safe_read_tools without approval. Treat approval_gated_tools as human-review surfaces, not permission to spend, write, export, load senders, deliver, or launch.",
5759
+ "When approval is missing, show the approval_packet and the exact missing approval scope before any side-effecting step."
5760
+ ],
5761
+ output_contract: [
5762
+ "current_state: one concise sentence describing where the campaign work stands.",
5763
+ "evidence_used: attachment, URL, memory, route, or integration evidence used without exposing private raw rows or secrets.",
5764
+ "next_safe_mcp_action: the read-only Signaliz MCP action to run next, or null when none is safe.",
5765
+ "approval_boundary: the human approval scope required before spend, provider writes, delivery, export, sender load, or launch.",
5766
+ "operator_reply: conversational GTM guidance with blockers, risks, and the safest next move."
5767
+ ]
5768
+ },
5769
+ context: {
5770
+ state: input.state,
5771
+ summary: input.summary,
5772
+ refs: input.refs,
5773
+ blockers: input.blockers,
5774
+ warnings: input.warnings,
5775
+ evidence_context: input.plan?.evidence ?? null,
5776
+ approval_packet: input.approvalPacket,
5777
+ approval_boundary: input.approvalBoundary,
5778
+ next_safe_action: input.nextSafeAction,
5779
+ conversation_starters: input.conversationStarters
5780
+ },
5781
+ tools: {
5782
+ mcp_server_label: "signaliz",
5783
+ remote_mcp_compatible: true,
5784
+ next_safe_mcp_action: input.nextSafeMcpAction,
5785
+ safe_read_tools: safeReadActions.map(
5786
+ (action) => campaignBuilderOpenAiAgentTool(action, "Safe read-only Signaliz MCP action available before approval.")
5787
+ ),
5788
+ approval_gated_tools: approvalGatedActions.map(
5789
+ (action) => campaignBuilderOpenAiAgentTool(action, "Requires explicit human review before execution.")
5790
+ )
5791
+ },
5792
+ guardrails: {
5793
+ input: [
5794
+ "Reject or pause requests to expose raw private attachment text, raw rows, leads, emails, provider payloads, copy bodies, API keys, or secrets.",
5795
+ "Pause when the user asks to spend credits, write to a provider, export, load a sender, deliver, or launch without explicit approval scope."
5796
+ ],
5797
+ tool: [
5798
+ "Read-only tools may run when they match safe_read_tools and their arguments match this packet.",
5799
+ "Any tool with approval_required=true or read_only=false requires human review and the approval_packet scope first."
5800
+ ],
5801
+ human_review_required_for: approvalTypes,
5802
+ privacy: input.plan?.evidence?.privacy_boundary ?? "Use summarized context only. Do not expose raw private attachment text, raw rows, leads, emails, provider payloads, copy bodies, secrets, or credentials."
5803
+ }
5804
+ };
5805
+ }
5806
+ function campaignBuilderOpenAiAgentTool(action, purpose) {
5807
+ return {
5808
+ id: action.id,
5809
+ type: "mcp",
5810
+ server_label: "signaliz",
5811
+ name: action.tool,
5812
+ arguments: action.arguments,
5813
+ read_only: action.read_only,
5814
+ approval_required: action.approval_required,
5815
+ purpose,
5816
+ safety: action.safety
5817
+ };
5818
+ }
5819
+ function uniqueCampaignBuilderActiveWorkActions(actions) {
5820
+ const seen = /* @__PURE__ */ new Set();
5821
+ const unique = [];
5822
+ for (const action of actions) {
5823
+ if (!action) continue;
5824
+ const key = `${action.id}:${action.tool}`;
5825
+ if (seen.has(key)) continue;
5826
+ seen.add(key);
5827
+ unique.push(action);
5828
+ }
5829
+ return unique;
5830
+ }
5831
+ function activeWorkStatus(record) {
5832
+ return stringValue(record.status ?? record.state ?? record.phase);
5833
+ }
4512
5834
  function campaignBuilderBuiltInForRoute(route) {
4513
5835
  return Object.entries(CAMPAIGN_BUILDER_BUILT_IN_ROUTE_IDS).find(([, routeId]) => route.id === routeId)?.[0];
4514
5836
  }
@@ -7305,6 +8627,36 @@ function recordsFromParams(params) {
7305
8627
  if (params.input) return { input: params.input };
7306
8628
  return { input: {} };
7307
8629
  }
8630
+ function toFusionConfig(params) {
8631
+ return {
8632
+ system_prompt: params.systemPrompt || params.system_prompt,
8633
+ user_template: params.userTemplate || params.user_template || params.prompt,
8634
+ preset: params.preset,
8635
+ temperature: params.temperature,
8636
+ max_tool_calls: params.maxToolCalls || params.max_tool_calls,
8637
+ max_tokens: params.maxTokens || params.max_tokens,
8638
+ timeout_ms: params.timeoutMs || params.timeout_ms,
8639
+ output_fields: params.outputFields || params.output_fields || [{ name: "response", type: "text", description: "Structured Fusion enrichment result" }]
8640
+ };
8641
+ }
8642
+ function toFusionSpendControls(params) {
8643
+ const controls = {};
8644
+ const dryRun = params.dryRun ?? params.dry_run;
8645
+ const confirmSpend = params.confirmSpend ?? params.confirm_spend;
8646
+ const maxCredits = params.maxCredits ?? params.max_credits;
8647
+ const maxCostUsd = params.maxCostUsd ?? params.max_cost_usd;
8648
+ if (dryRun !== void 0) controls.dry_run = dryRun;
8649
+ if (confirmSpend !== void 0) controls.confirm_spend = confirmSpend;
8650
+ if (maxCredits !== void 0) controls.max_credits = maxCredits;
8651
+ if (maxCostUsd !== void 0) controls.max_cost_usd = maxCostUsd;
8652
+ return controls;
8653
+ }
8654
+ function fusionRecordsFromParams(params) {
8655
+ const records = params.records || params.inputs;
8656
+ if (records?.length) return { records };
8657
+ if (params.input) return { input: params.input };
8658
+ return { input: {} };
8659
+ }
7308
8660
  var Ai = class {
7309
8661
  constructor(client) {
7310
8662
  this.client = client;
@@ -7338,6 +8690,33 @@ var Ai = class {
7338
8690
  raw: data
7339
8691
  };
7340
8692
  }
8693
+ /**
8694
+ * Plan or run experimental AI Enrichment Fusion with explicit spend controls.
8695
+ */
8696
+ async fusion(params) {
8697
+ if (!params.prompt && !params.userTemplate && !params.user_template) {
8698
+ throw new Error("prompt or userTemplate is required.");
8699
+ }
8700
+ const data = await this.client.post("ai-enrichment-fusion", {
8701
+ ...fusionRecordsFromParams(params),
8702
+ config: toFusionConfig(params),
8703
+ ...toFusionSpendControls(params)
8704
+ });
8705
+ return {
8706
+ success: data.success ?? false,
8707
+ capability: data.capability ?? "ai_enrichment_fusion",
8708
+ displayName: data.display_name ?? "AI Enrichment Fusion",
8709
+ results: data.results ?? [],
8710
+ creditsUsed: data.total_credits_used ?? data.credits_used ?? 0,
8711
+ model: data.model,
8712
+ fusion: data.fusion,
8713
+ tokensUsed: data.tokens_used ?? 0,
8714
+ costUsd: data.cost_usd ?? 0,
8715
+ costSummary: data.cost_summary,
8716
+ billingMetadata: data.billing_metadata,
8717
+ raw: data
8718
+ };
8719
+ }
7341
8720
  };
7342
8721
 
7343
8722
  // src/resources/gtm-kernel.ts
@@ -7691,6 +9070,16 @@ var GtmKernel = class {
7691
9070
  min_privacy_k: input.minPrivacyK
7692
9071
  });
7693
9072
  }
9073
+ /** Queue a dry-run-first bridge from imported historical feedback to attribution-only Campaign Build surrogates. */
9074
+ async runHistoricalCampaignBuildBridge(input = {}) {
9075
+ return this.callMcp("gtm_historical_campaign_build_bridge_run", {
9076
+ dry_run: input.dryRun,
9077
+ write_approved: input.writeApproved,
9078
+ max_events: input.maxEvents,
9079
+ max_campaigns: input.maxCampaigns,
9080
+ min_feedback_events: input.minFeedbackEvents
9081
+ });
9082
+ }
7694
9083
  /** Prepare a provider-agnostic feedback webhook URL for Smartlead, HeyReach, Airbyte, or custom sender events. */
7695
9084
  async prepareFeedbackWebhook(input) {
7696
9085
  return this.callMcp("gtm_feedback_webhook_prepare", {
@@ -7867,6 +9256,24 @@ var GtmKernel = class {
7867
9256
  limit: input.limit
7868
9257
  });
7869
9258
  }
9259
+ /** Predict booked-meeting likelihood for uploaded, inline, or MCP-sourced lead rows without persisting raw rows. */
9260
+ async predictMeetingLikelihood(input) {
9261
+ return this.callMcp("gtm_predict_meeting_likelihood", {
9262
+ lead_rows: input.leadRows,
9263
+ input_source: input.inputSource,
9264
+ campaign_id: input.campaignId,
9265
+ campaign_build_id: input.campaignBuildId,
9266
+ days: input.days,
9267
+ feedback_limit: input.feedbackLimit,
9268
+ include_features: input.includeFeatures,
9269
+ include_global: input.includeGlobal,
9270
+ min_feedback_events: input.minFeedbackEvents,
9271
+ min_historical_feature_sample_size: input.minHistoricalFeatureSampleSize,
9272
+ min_workspace_count: input.minWorkspaceCount,
9273
+ min_privacy_k: input.minPrivacyK,
9274
+ limit: input.limit
9275
+ });
9276
+ }
7870
9277
  /** Rank provider-chain, ICP, source, copy, and sequence failure patterns without writing raw lead data. */
7871
9278
  async failurePatterns(options = {}) {
7872
9279
  return this.callMcp("gtm_brain_failure_patterns", {
@@ -8482,6 +9889,70 @@ function compact2(value) {
8482
9889
  return out;
8483
9890
  }
8484
9891
 
9892
+ // src/resources/public-ground-truth.ts
9893
+ var PublicGroundTruth = class {
9894
+ constructor(client) {
9895
+ this.client = client;
9896
+ }
9897
+ /**
9898
+ * Search the public-ground-truth cache by company/name/domain/native ID,
9899
+ * or by plain-language industry + location.
9900
+ * Results use the same snake_case wire shape returned by the hosted MCP API.
9901
+ */
9902
+ async search(params) {
9903
+ return this.client.mcp("tools/call", {
9904
+ name: "search_public_ground_truth",
9905
+ arguments: {
9906
+ query: params.query ?? params.search,
9907
+ industry: params.industry,
9908
+ location: params.location,
9909
+ source_id: params.sourceId ?? params.source_id,
9910
+ entity_type: params.entityType ?? params.entity_type,
9911
+ native_id: params.nativeId ?? params.native_id,
9912
+ entity_name: params.entityName ?? params.entity_name,
9913
+ domain: params.domain,
9914
+ website_url: params.websiteUrl ?? params.website_url,
9915
+ phone: params.phone,
9916
+ email: params.email,
9917
+ city: params.city,
9918
+ state: params.state,
9919
+ postal_code: params.postalCode ?? params.postal_code,
9920
+ country: params.country,
9921
+ source_url: params.sourceUrl ?? params.source_url,
9922
+ join_keys: params.joinKeys ?? params.join_keys,
9923
+ row_data: params.rowData ?? params.row_data,
9924
+ naics: params.naics,
9925
+ industry_code: params.industryCode ?? params.industry_code,
9926
+ industry_type: params.industryType ?? params.industry_type,
9927
+ year: params.year,
9928
+ quarter: params.quarter,
9929
+ area_fips: params.areaFips ?? params.area_fips,
9930
+ geo_id: params.geoId ?? params.geo_id,
9931
+ own_code: params.ownCode ?? params.own_code,
9932
+ source_updated_after: params.sourceUpdatedAfter ?? params.source_updated_after,
9933
+ source_updated_before: params.sourceUpdatedBefore ?? params.source_updated_before,
9934
+ observed_after: params.observedAfter ?? params.observed_after,
9935
+ observed_before: params.observedBefore ?? params.observed_before,
9936
+ limit: params.limit,
9937
+ offset: params.offset
9938
+ }
9939
+ });
9940
+ }
9941
+ /**
9942
+ * List available public-ground-truth cache sources and their join-key metadata.
9943
+ */
9944
+ async listSources(params = {}) {
9945
+ return this.client.mcp("tools/call", {
9946
+ name: "list_public_ground_truth_sources",
9947
+ arguments: {
9948
+ query: params.query ?? params.search,
9949
+ status: params.status,
9950
+ limit: params.limit
9951
+ }
9952
+ });
9953
+ }
9954
+ };
9955
+
8485
9956
  // src/index.ts
8486
9957
  var MCP_TOOL_CACHE_TTL_MS = 3e4;
8487
9958
  function inferMcpToolCategory(toolName) {
@@ -8524,6 +9995,7 @@ function inferMcpToolCategory(toolName) {
8524
9995
  }
8525
9996
  if (toolName.includes("icp")) return "icp";
8526
9997
  if (toolName.startsWith("ai_clean_")) return "data_cleaning";
9998
+ if (toolName.includes("public_ground_truth")) return "public_data";
8527
9999
  if (toolName.includes("system") || toolName.includes("workflow")) return "automation";
8528
10000
  if (toolName.includes("agent") || toolName.includes("platform_health") || toolName === "discover_capabilities") return "observability";
8529
10001
  return void 0;
@@ -8575,6 +10047,7 @@ var Signaliz = class {
8575
10047
  this.ops = new Ops(this.client);
8576
10048
  this.ai = new Ai(this.client);
8577
10049
  this.gtm = new GtmKernel(this.client);
10050
+ this.publicGroundTruth = new PublicGroundTruth(this.client);
8578
10051
  }
8579
10052
  /** Get current workspace info including credits */
8580
10053
  async getWorkspace() {
@@ -8681,10 +10154,12 @@ var Signaliz = class {
8681
10154
  GtmKernel,
8682
10155
  Icps,
8683
10156
  Ops,
10157
+ PublicGroundTruth,
8684
10158
  Signaliz,
8685
10159
  SignalizError,
8686
10160
  applyCampaignBuilderStrategyTemplate,
8687
10161
  collectExecutionReferences,
10162
+ createCampaignBuilderActiveWorkContext,
8688
10163
  createCampaignBuilderAgentPlan,
8689
10164
  createCampaignBuilderAgentRequestTemplate,
8690
10165
  createCampaignBuilderApproval,