@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.
@@ -448,6 +448,38 @@ var Signals = class {
448
448
  constructor(client) {
449
449
  this.client = client;
450
450
  }
451
+ /** Turn the strongest supported company signal into three complete, evidence-backed emails. */
452
+ async signalToCopy(params) {
453
+ const data = await this.client.post("api/v1/signal-to-copy", {
454
+ company_domain: params.companyDomain,
455
+ person_name: params.personName,
456
+ title: params.title,
457
+ campaign_offer: params.campaignOffer,
458
+ research_prompt: params.researchPrompt
459
+ });
460
+ return {
461
+ success: data.success ?? true,
462
+ strongestSignal: data.strongest_signal ?? "",
463
+ whyNow: data.why_now ?? "",
464
+ subjectLine: data.subject_line ?? "",
465
+ openingLine: data.opening_line ?? "",
466
+ confidence: data.confidence ?? 0,
467
+ evidenceUrl: data.evidence_url ?? "",
468
+ researchPrompt: data.research_prompt,
469
+ variations: (data.variations ?? []).map((v) => ({
470
+ style: v.style,
471
+ subjectLine: v.subject_line ?? "",
472
+ openingLine: v.opening_line ?? "",
473
+ cta: v.cta ?? "",
474
+ prediction: v.prediction,
475
+ email: v.email
476
+ }))
477
+ };
478
+ }
479
+ /** @deprecated Use signalToCopy. Retained for compatibility. */
480
+ async personalize(params) {
481
+ return this.signalToCopy(params);
482
+ }
451
483
  /** Enrich a company with actionable signals (V1) */
452
484
  async enrich(params) {
453
485
  const args = {
@@ -456,6 +488,9 @@ var Signals = class {
456
488
  if (params.domain) args.domain = params.domain;
457
489
  if (params.researchPrompt) args.research_prompt = params.researchPrompt;
458
490
  if (params.signalTypes) args.signal_types = params.signalTypes;
491
+ if (params.targetSignalCount) args.target_signal_count = params.targetSignalCount;
492
+ if (params.lookbackDays) args.lookback_days = params.lookbackDays;
493
+ if (params.enableDeepSearch !== void 0) args.enable_deep_search = params.enableDeepSearch;
459
494
  const data = await this.client.mcp("tools/call", {
460
495
  name: "enrich_company_signals",
461
496
  arguments: args
@@ -465,6 +500,16 @@ var Signals = class {
465
500
  signals: (data.signals ?? []).map((s) => ({
466
501
  title: s.title,
467
502
  signalType: s.signal_type,
503
+ sourceType: s.source_type ?? s.signal_classification?.label ?? "unknown",
504
+ sourceProvenance: s.source_provenance ?? s.metadata?.source_provenance ?? null,
505
+ signalFamily: s.signal_family ?? s.signal_type ?? null,
506
+ signalEventType: s.signal_event_type ?? s.signal_classification?.slug ?? null,
507
+ signalEventLabel: s.signal_event_label ?? s.signal_classification?.label ?? null,
508
+ signalClassification: s.signal_classification ? {
509
+ label: s.signal_classification.label,
510
+ slug: s.signal_classification.slug,
511
+ rationale: s.signal_classification.rationale
512
+ } : null,
468
513
  confidenceScore: s.confidence_score ?? 0,
469
514
  detectedAt: s.detected_at ?? "",
470
515
  url: s.url,
@@ -505,8 +550,17 @@ var Signals = class {
505
550
  content: s.content ?? "",
506
551
  date: s.date ?? null,
507
552
  sourceUrl: s.source_url ?? null,
508
- sourceType: s.source_type ?? "unknown",
509
- confidence: s.confidence ?? null
553
+ sourceType: s.source_type ?? s.signal_classification?.label ?? "unknown",
554
+ sourceProvenance: s.source_provenance ?? s.metadata?.source_provenance ?? null,
555
+ signalFamily: s.signal_family ?? s.type ?? null,
556
+ signalEventType: s.signal_event_type ?? s.signal_classification?.slug ?? null,
557
+ signalEventLabel: s.signal_event_label ?? s.signal_classification?.label ?? null,
558
+ confidence: s.confidence ?? null,
559
+ signalClassification: s.signal_classification ? {
560
+ label: s.signal_classification.label,
561
+ slug: s.signal_classification.slug,
562
+ rationale: s.signal_classification.rationale
563
+ } : null
510
564
  })),
511
565
  signalCount: data.signal_count ?? 0,
512
566
  intelligence: data.intelligence ? {
@@ -549,6 +603,58 @@ var Signals = class {
549
603
  }
550
604
  };
551
605
  }
606
+ /** Plan or run experimental Signal Fusion with explicit spend controls. */
607
+ async fusion(params) {
608
+ const args = {};
609
+ if (params.companyName) args.company_name = params.companyName;
610
+ if (params.domain) args.domain = params.domain;
611
+ if (params.companyDomain) args.company_domain = params.companyDomain;
612
+ if (params.linkedinUrl) args.linkedin_url = params.linkedinUrl;
613
+ if (params.companies) args.companies = params.companies;
614
+ if (params.preset) args.preset = params.preset;
615
+ if (params.researchPrompt) args.research_prompt = params.researchPrompt;
616
+ if (params.signalTypes) args.signal_types = params.signalTypes;
617
+ if (params.lookbackDays) args.lookback_days = params.lookbackDays;
618
+ if (params.targetSignalCount) args.target_signal_count = params.targetSignalCount;
619
+ if (params.maxToolCalls) args.max_tool_calls = params.maxToolCalls;
620
+ if (params.maxTokens) args.max_tokens = params.maxTokens;
621
+ if (params.timeoutMs) args.timeout_ms = params.timeoutMs;
622
+ const dryRun = params.dryRun ?? params.dry_run;
623
+ const confirmSpend = params.confirmSpend ?? params.confirm_spend;
624
+ const maxCredits = params.maxCredits ?? params.max_credits;
625
+ const maxCostUsd = params.maxCostUsd ?? params.max_cost_usd;
626
+ if (dryRun !== void 0) args.dry_run = dryRun;
627
+ if (confirmSpend !== void 0) args.confirm_spend = confirmSpend;
628
+ if (maxCredits !== void 0) args.max_credits = maxCredits;
629
+ if (maxCostUsd !== void 0) args.max_cost_usd = maxCostUsd;
630
+ const data = await this.client.post("signal-fusion", args);
631
+ return {
632
+ success: data.success ?? false,
633
+ capability: data.capability ?? "signal_fusion",
634
+ displayName: data.display_name ?? "Signal Fusion",
635
+ company: data.company,
636
+ signals: (data.signals ?? []).map((s) => ({
637
+ id: s.id,
638
+ title: s.title,
639
+ type: s.type,
640
+ content: s.content ?? "",
641
+ date: s.date ?? null,
642
+ sourceUrl: s.source_url ?? null,
643
+ sourceType: s.source_type ?? "unknown",
644
+ confidence: s.confidence ?? null
645
+ })),
646
+ signalCount: data.signal_count ?? data.signals?.length ?? 0,
647
+ results: data.results ?? [],
648
+ model: data.model,
649
+ fusion: data.fusion,
650
+ costUsd: data.cost_usd ?? 0,
651
+ tokensUsed: data.tokens_used ?? 0,
652
+ costSummary: data.cost_summary,
653
+ creditsUsed: data.total_credits_used ?? data.credits_used ?? 0,
654
+ billingMetadata: data.billing_metadata,
655
+ raw: data
656
+ };
657
+ }
552
658
  };
553
659
 
554
660
  // src/resources/systems.ts
@@ -837,7 +943,11 @@ var Campaigns = class {
837
943
  maxSupportedTargetCount: data.max_supported_target_count,
838
944
  brainContext: data.brain_context ?? {},
839
945
  learningHoldout: data.learning_holdout ?? {},
840
- providerRoute: mapProviderRoute(data)
946
+ providerRoute: mapProviderRoute(data),
947
+ effectiveSourceRouting: recordOrNull(data.effective_source_routing ?? data.source_routing),
948
+ sourcePlan: recordOrNull(data.source_plan ?? data.plan?.source_plan),
949
+ sourceRoutes: recordArray(data.source_routes ?? data.source_plan?.routes ?? data.plan?.source_plan?.routes),
950
+ sourceShards: recordArray(data.source_shards ?? data.source_plan?.shards ?? data.plan?.source_plan?.shards)
841
951
  };
842
952
  }
843
953
  async getCampaignBuildStatus(campaignBuildId) {
@@ -983,6 +1093,7 @@ function mapCustomerRowCounts(value) {
983
1093
  acceptedRows: numberOrNull(record.accepted_rows) ?? void 0,
984
1094
  usableRows: numberOrNull(record.usable_rows) ?? void 0,
985
1095
  copiedRows: numberOrNull(record.copied_rows) ?? void 0,
1096
+ copySkippedRows: numberOrNull(record.copy_skipped_rows) ?? void 0,
986
1097
  approvalReadyRows: numberOrNull(record.approval_ready_rows) ?? void 0,
987
1098
  qaReadyRows: numberOrNull(record.qa_ready_rows) ?? void 0,
988
1099
  deliveredRows: numberOrNull(record.delivered_rows) ?? void 0,
@@ -1029,6 +1140,9 @@ function recordOrNull(value) {
1029
1140
  function stringArray(value) {
1030
1141
  return Array.isArray(value) ? value.map((item) => String(item || "").trim()).filter(Boolean) : [];
1031
1142
  }
1143
+ function recordArray(value) {
1144
+ return Array.isArray(value) ? value.map(recordOrNull).filter((item) => Boolean(item)) : [];
1145
+ }
1032
1146
  function diagnosticMessages(value) {
1033
1147
  if (!Array.isArray(value)) return [];
1034
1148
  return value.map((item) => {
@@ -1190,7 +1304,8 @@ function buildArgs(request) {
1190
1304
  suppress_prior_campaign_ids: request.cacheReusePolicy.suppressPriorCampaignIds,
1191
1305
  suppress_prior_list_ids: request.cacheReusePolicy.suppressPriorListIds,
1192
1306
  uniqueness: request.cacheReusePolicy.uniqueness,
1193
- require_usage_metadata: request.cacheReusePolicy.requireUsageMetadata
1307
+ require_usage_metadata: request.cacheReusePolicy.requireUsageMetadata,
1308
+ verified_cache_ttl_days: request.cacheReusePolicy.verifiedCacheTtlDays
1194
1309
  };
1195
1310
  }
1196
1311
  if (request.brainPreflight) args.brain_preflight = request.brainPreflight;
@@ -1215,7 +1330,8 @@ function buildArgs(request) {
1215
1330
  geographies: request.icp.geographies,
1216
1331
  keywords: request.icp.keywords,
1217
1332
  exclusions: request.icp.exclusions,
1218
- require_verified_email: request.icp.requireVerifiedEmail
1333
+ require_verified_email: request.icp.requireVerifiedEmail,
1334
+ persona_expansion_mode: request.icp.personaExpansionMode
1219
1335
  };
1220
1336
  }
1221
1337
  if (request.policy) {
@@ -1255,6 +1371,7 @@ function buildArgs(request) {
1255
1371
  } : void 0,
1256
1372
  sequence_steps: request.copy.sequenceSteps,
1257
1373
  copy_schema: request.copy.copySchema,
1374
+ evidence_mode: request.copy.evidenceMode,
1258
1375
  banned_phrases: request.copy.bannedPhrases,
1259
1376
  approval_required: request.copy.approvalRequired,
1260
1377
  quality_tier: request.copy.qualityTier
@@ -1311,7 +1428,8 @@ var DEFAULT_SIGNALIZ_CAMPAIGN_BUILDER_DEFAULTS = {
1311
1428
  copy: {
1312
1429
  enabled: true,
1313
1430
  tone: "direct",
1314
- maxBodyWords: 125
1431
+ maxBodyWords: 125,
1432
+ evidenceMode: "signal_led"
1315
1433
  },
1316
1434
  delivery: {
1317
1435
  enabled: true,
@@ -1368,7 +1486,8 @@ var CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS = [
1368
1486
  "Block export when required identity, company, or email fields are missing."
1369
1487
  ],
1370
1488
  activationBoundary: "Cache inventory is read-only. Fresh sourcing, cache writes, export, and sender loading require explicit approval.",
1371
- memoryKeywords: ["cache inventory", "large list", "unique emails", "top-up sourcing", "source metadata"]
1489
+ memoryKeywords: ["cache inventory", "large list", "unique emails", "top-up sourcing", "source metadata"],
1490
+ knowledgeSources: ["data-ops/contact-data-lifecycle", "data-ops/credit-optimization-guide", "gtm/data-quality-ops", "gtm/lead-generation-strategies"]
1372
1491
  },
1373
1492
  {
1374
1493
  slug: "net-new-suppressed-list",
@@ -1393,7 +1512,8 @@ var CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS = [
1393
1512
  "Do not describe rows as fully qualified when the approved path only proves net-new email status."
1394
1513
  ],
1395
1514
  activationBoundary: "Suppression review must pass before export, upload, CRM sync, sender load, or sequence launch.",
1396
- memoryKeywords: ["suppression", "net-new", "prior email overlap", "domain review", "provenance-rich QA"]
1515
+ memoryKeywords: ["suppression", "net-new", "prior email overlap", "domain review", "provenance-rich QA"],
1516
+ knowledgeSources: ["gtm/data-quality-ops", "data-ops/contact-data-lifecycle", "gtm/outbound-sequencing"]
1397
1517
  },
1398
1518
  {
1399
1519
  slug: "proof-first-vertical-gate",
@@ -1418,7 +1538,8 @@ var CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS = [
1418
1538
  "Contact fit should meet the configured threshold before email finding or copy generation."
1419
1539
  ],
1420
1540
  activationBoundary: "Proof gates are required before contact spend, copy, export, or external writes.",
1421
- memoryKeywords: ["proof gate", "vertical exclusion", "company before contact", "pass review fail", "fit threshold"]
1541
+ memoryKeywords: ["proof gate", "vertical exclusion", "company before contact", "pass review fail", "fit threshold"],
1542
+ knowledgeSources: ["gtm/icp-design-framework", "gtm/strategy/scoring.yaml", "gtm/advanced/account-prioritization-frameworks", "gtm/advanced/predictive-lead-scoring", "gtm/verticals"]
1422
1543
  },
1423
1544
  {
1424
1545
  slug: "signal-led-copy-approval",
@@ -1443,7 +1564,8 @@ var CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS = [
1443
1564
  "Destination fields must remain blocked until approval metadata is present."
1444
1565
  ],
1445
1566
  activationBoundary: "Copy readiness is not delivery approval. Export, sync, sender load, and launch require a separate human-approved gate.",
1446
- memoryKeywords: ["company signals", "evidence-fed copy", "why now", "copy blocker", "approval gate"]
1567
+ memoryKeywords: ["company signals", "evidence-fed copy", "why now", "copy blocker", "approval gate"],
1568
+ knowledgeSources: ["gtm/email-personalization", "gtm/signal-based-selling", "gtm/signal-combination-matrix", "gtm/advanced/buyer-journey-mapping"]
1447
1569
  },
1448
1570
  {
1449
1571
  slug: "domain-first-recovery",
@@ -1468,7 +1590,8 @@ var CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS = [
1468
1590
  "Target count means final held rows, not raw sourced rows."
1469
1591
  ],
1470
1592
  activationBoundary: "Domain recovery informs sourcing. Contact enrichment, paid qualification, and export still require approved gates.",
1471
- memoryKeywords: ["domain-first", "yield modeling", "attrition", "accepted domains", "gap fill"]
1593
+ memoryKeywords: ["domain-first", "yield modeling", "attrition", "accepted domains", "gap fill"],
1594
+ knowledgeSources: ["gtm/lead-generation-strategies", "data-ops/enrichment-waterfall-strategy", "data-ops/credit-optimization-guide", "gtm/territory-account-planning"]
1472
1595
  },
1473
1596
  {
1474
1597
  slug: "table-workflow-handoff",
@@ -1493,7 +1616,112 @@ var CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS = [
1493
1616
  "Provider route activation must dry-run before any external write."
1494
1617
  ],
1495
1618
  activationBoundary: "MCP, webhook, managed integration, and destination routes remain dry-run until the reviewed route and campaign are approved.",
1496
- memoryKeywords: ["table workflow", "request ready", "provider status", "export blocker", "BYO integration"]
1619
+ memoryKeywords: ["table workflow", "request ready", "provider status", "export blocker", "BYO integration"],
1620
+ knowledgeSources: ["gtm/multi-channel-orchestration", "data-ops/enrichment-waterfall-strategy", "recipes/clay-gtm-webhook-recipe", "systems/blueprints/multi-channel-orchestration"]
1621
+ },
1622
+ {
1623
+ slug: "icp-persona-segmentation",
1624
+ label: "ICP And Persona Segmentation",
1625
+ whenToUse: [
1626
+ "The brief has a broad market, multiple personas, or unclear buying committee.",
1627
+ "The campaign should rank account tiers, persona fit, vertical nuance, and exclusions before sourcing at scale."
1628
+ ],
1629
+ sequence: [
1630
+ "Convert the brief into account tiers, persona roles, buying committee influence, and explicit exclusions.",
1631
+ "Choose vertical guidance only from the customer-safe GTM library and keep internal platform docs out of prompts.",
1632
+ "Score accounts before contacts, then score contacts against persona, seniority, function, geography, and intent fit.",
1633
+ "Return segment counts and review buckets before spend, copy, export, or launch."
1634
+ ],
1635
+ requiredFields: {
1636
+ segment: ["segment_name", "tier", "persona_priority", "buying_committee_role", "fit_reason", "exclusion_reason"],
1637
+ scoring: ["account_fit_score", "persona_fit_score", "segment_confidence", "review_bucket"]
1638
+ },
1639
+ qualityGates: [
1640
+ "Do not treat adjacent personas or verticals as matches unless the brief allows them.",
1641
+ "Keep ICP, persona, and exclusion evidence visible in the proof packet.",
1642
+ "Low-confidence segments route to review before contact discovery or copy."
1643
+ ],
1644
+ activationBoundary: "Segmentation is planning guidance. Paid sourcing, enrichment, export, and launch require the normal approval gates.",
1645
+ memoryKeywords: ["ICP design", "persona segmentation", "buying committee", "account tiers", "fit scoring", "vertical nuance"],
1646
+ 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"]
1647
+ },
1648
+ {
1649
+ slug: "buying-signal-prioritization",
1650
+ label: "Buying Signal Prioritization",
1651
+ whenToUse: [
1652
+ "The campaign depends on timing, intent, trigger events, or account prioritization.",
1653
+ "The operator wants why-now proof instead of generic list generation."
1654
+ ],
1655
+ sequence: [
1656
+ "Map the campaign goal to signal families, trigger freshness, and buyer-journey stage.",
1657
+ "Rank signals by relevance, recency, source quality, and persona-specific actionability.",
1658
+ "Attach evidence URLs or evidence summaries without exposing private memory rows.",
1659
+ "Use signal strength to decide source order, copy angle, and review priority."
1660
+ ],
1661
+ requiredFields: {
1662
+ signal: ["signal_type", "signal_date", "signal_source", "why_now", "signal_confidence", "buyer_stage"],
1663
+ prioritization: ["priority_score", "priority_reason", "next_best_action", "review_reason"]
1664
+ },
1665
+ qualityGates: [
1666
+ "Do not use stale, unsupported, or weak signals as personalization proof.",
1667
+ "Signal fit cannot override hard ICP, geography, suppression, or verified-email gates.",
1668
+ "Rows with uncertain why-now evidence must stay review-only."
1669
+ ],
1670
+ activationBoundary: "Signal ranking can prioritize review and copy. It does not approve outreach, sender loading, exports, or sends.",
1671
+ memoryKeywords: ["buying signals", "intent data", "funding trigger", "new executive", "tech adoption", "buyer journey", "why now"],
1672
+ 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"]
1673
+ },
1674
+ {
1675
+ slug: "multi-channel-sequence-fit",
1676
+ label: "Multi-Channel Sequence Fit",
1677
+ whenToUse: [
1678
+ "The campaign needs email, LinkedIn, call, webhook, CRM, or sequencer coordination.",
1679
+ "Copy and delivery should adapt by persona, channel, deliverability risk, and proof strength."
1680
+ ],
1681
+ sequence: [
1682
+ "Choose channel mix from persona, deal motion, evidence strength, deliverability constraints, and connected destination readiness.",
1683
+ "Draft sequence logic with email, LinkedIn, call, and task handoffs only where supported by the approved destination.",
1684
+ "Keep copy concise, proof-backed, channel-specific, and reviewable before delivery.",
1685
+ "Run deliverability and destination readiness checks before sender load or launch approval."
1686
+ ],
1687
+ requiredFields: {
1688
+ channel: ["channel", "touch_number", "persona_context", "message_angle", "channel_blocker", "approval_status"],
1689
+ deliverability: ["verified_email", "domain_risk", "send_window", "unsubscribe_risk", "sender_ready"]
1690
+ },
1691
+ qualityGates: [
1692
+ "Email copy must be row-supported and under the configured campaign tone/length limits.",
1693
+ "LinkedIn, phone, CRM, webhook, and sequencer actions remain destination-locked until approved.",
1694
+ "Deliverability risk or missing sender readiness blocks launch even when rows are qualified."
1695
+ ],
1696
+ activationBoundary: "Sequence strategy is draft-only. External writes, sender loading, launch, and sending need separate explicit approval.",
1697
+ memoryKeywords: ["outbound sequencing", "email personalization", "LinkedIn outreach", "cold calling", "multi-channel orchestration", "deliverability"],
1698
+ 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"]
1699
+ },
1700
+ {
1701
+ slug: "revops-feedback-loop",
1702
+ label: "RevOps Feedback Loop",
1703
+ whenToUse: [
1704
+ "The campaign should improve from replies, meetings, delivery results, CRM outcomes, or operator QA.",
1705
+ "The operator needs post-build monitoring, governance, or continuous ops follow-up."
1706
+ ],
1707
+ sequence: [
1708
+ "Define success metrics, guardrails, and feedback sources before launch.",
1709
+ "Collect aggregate outcomes, QA blockers, delivery health, and operator edits after each campaign phase.",
1710
+ "Separate workspace-private evidence from public-safe learnings and Brain defaults.",
1711
+ "Feed approved aggregate learnings back into future sourcing, qualification, copy, and suppression rules."
1712
+ ],
1713
+ requiredFields: {
1714
+ feedback: ["source", "event_type", "metric", "sample_size", "confidence", "learning_candidate"],
1715
+ governance: ["approval_owner", "last_reviewed_at", "risk_flag", "next_review_action"]
1716
+ },
1717
+ qualityGates: [
1718
+ "Do not write memory or Brain learnings from raw replies, private labels, or provider payloads without approved redaction.",
1719
+ "Use holdouts or pre/post comparisons before claiming causal lift.",
1720
+ "Delivery, suppression, and opt-out signals must update future gates before scale-up."
1721
+ ],
1722
+ activationBoundary: "Feedback review is read-only until memory writes, Brain writes, CRM updates, or campaign changes are explicitly approved.",
1723
+ memoryKeywords: ["RevOps", "feedback loop", "campaign learning", "reply outcomes", "sales marketing alignment", "data hygiene", "holdout"],
1724
+ 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"]
1497
1725
  }
1498
1726
  ];
1499
1727
  var CAMPAIGN_BUILDER_STRATEGY_TEMPLATES = [
@@ -1580,7 +1808,15 @@ var CAMPAIGN_BUILDER_STRATEGY_TEMPLATES = [
1580
1808
  agencyContext: {
1581
1809
  partnerEcosystem: ["signaliz", "instantly", "nango"]
1582
1810
  },
1583
- operatingPlaybookSlugs: ["net-new-suppressed-list", "proof-first-vertical-gate", "signal-led-copy-approval"]
1811
+ operatingPlaybookSlugs: [
1812
+ "net-new-suppressed-list",
1813
+ "proof-first-vertical-gate",
1814
+ "signal-led-copy-approval",
1815
+ "icp-persona-segmentation",
1816
+ "buying-signal-prioritization",
1817
+ "multi-channel-sequence-fit",
1818
+ "revops-feedback-loop"
1819
+ ]
1584
1820
  },
1585
1821
  {
1586
1822
  slug: "non-medical-home-care",
@@ -1653,7 +1889,15 @@ var CAMPAIGN_BUILDER_STRATEGY_TEMPLATES = [
1653
1889
  agencyContext: {
1654
1890
  partnerEcosystem: ["clay", "octave", "signaliz", "instantly", "nango"]
1655
1891
  },
1656
- operatingPlaybookSlugs: ["proof-first-vertical-gate", "domain-first-recovery", "table-workflow-handoff"]
1892
+ operatingPlaybookSlugs: [
1893
+ "proof-first-vertical-gate",
1894
+ "domain-first-recovery",
1895
+ "table-workflow-handoff",
1896
+ "icp-persona-segmentation",
1897
+ "buying-signal-prioritization",
1898
+ "multi-channel-sequence-fit",
1899
+ "revops-feedback-loop"
1900
+ ]
1657
1901
  },
1658
1902
  {
1659
1903
  slug: "agency-founder-led",
@@ -1714,7 +1958,15 @@ var CAMPAIGN_BUILDER_STRATEGY_TEMPLATES = [
1714
1958
  agencyContext: {
1715
1959
  partnerEcosystem: ["signaliz", "octave", "instantly", "nango"]
1716
1960
  },
1717
- operatingPlaybookSlugs: ["net-new-suppressed-list", "signal-led-copy-approval", "table-workflow-handoff"]
1961
+ operatingPlaybookSlugs: [
1962
+ "net-new-suppressed-list",
1963
+ "signal-led-copy-approval",
1964
+ "table-workflow-handoff",
1965
+ "icp-persona-segmentation",
1966
+ "buying-signal-prioritization",
1967
+ "multi-channel-sequence-fit",
1968
+ "revops-feedback-loop"
1969
+ ]
1718
1970
  },
1719
1971
  {
1720
1972
  slug: "cloud-infrastructure-displacement",
@@ -1801,7 +2053,15 @@ var CAMPAIGN_BUILDER_STRATEGY_TEMPLATES = [
1801
2053
  agencyContext: {
1802
2054
  partnerEcosystem: ["signaliz", "octave", "instantly", "nango"]
1803
2055
  },
1804
- operatingPlaybookSlugs: ["proof-first-vertical-gate", "domain-first-recovery", "signal-led-copy-approval"]
2056
+ operatingPlaybookSlugs: [
2057
+ "proof-first-vertical-gate",
2058
+ "domain-first-recovery",
2059
+ "signal-led-copy-approval",
2060
+ "icp-persona-segmentation",
2061
+ "buying-signal-prioritization",
2062
+ "multi-channel-sequence-fit",
2063
+ "revops-feedback-loop"
2064
+ ]
1805
2065
  }
1806
2066
  ];
1807
2067
  var CampaignBuilderAgent = class {
@@ -1832,6 +2092,9 @@ var CampaignBuilderAgent = class {
1832
2092
  const plan = await this.createPlan(request, options);
1833
2093
  return createCampaignBuilderReadiness(plan);
1834
2094
  }
2095
+ activeWorkContext(input) {
2096
+ return createCampaignBuilderActiveWorkContext(input);
2097
+ }
1835
2098
  async proof(request, options = {}) {
1836
2099
  const plan = await this.createPlan(request, options);
1837
2100
  const result = await this.dryRunPlan(plan, {
@@ -1873,6 +2136,7 @@ var CampaignBuilderAgent = class {
1873
2136
  },
1874
2137
  brain_defaults: plan.buildRequest.brainDefaults,
1875
2138
  brain_preflight: plan.buildRequest.brainPreflight,
2139
+ learning_holdout: plan.buildRequest.learningHoldout,
1876
2140
  campaign_strategy_context: plan.buildRequest.campaignStrategyContext,
1877
2141
  delivery_risk_preflight: plan.buildRequest.deliveryRisk,
1878
2142
  delivery_risk: plan.buildRequest.deliveryRisk
@@ -1886,7 +2150,8 @@ var CampaignBuilderAgent = class {
1886
2150
  estimated_credits: plan.estimatedCredits
1887
2151
  },
1888
2152
  brain_delivery_risk_preflight: plan.buildRequest.deliveryRisk,
1889
- delivery_risk: plan.buildRequest.deliveryRisk
2153
+ delivery_risk: plan.buildRequest.deliveryRisk,
2154
+ learning_holdout: plan.buildRequest.learningHoldout
1890
2155
  },
1891
2156
  approval_required: plan.approvals.some((approval) => approval.blocking),
1892
2157
  actor_type: "agent",
@@ -2214,9 +2479,10 @@ function createCampaignBuilderAgentPlan(request, context = {}) {
2214
2479
  const estimatedCredits = estimateCredits(resolvedRequest, defaults, targetCount);
2215
2480
  const buildRequest = createBuildRequest(resolvedRequest, defaults, targetCount, campaignName, context.scopeBuildArgs, routes, memory, context.scopeBrainPreflight);
2216
2481
  const brainPreflight = asRecord(buildRequest.brainPreflight);
2482
+ const evidence = buildRequest.evidence ?? null;
2217
2483
  const approvals = deriveApprovals(resolvedRequest, routes, memory, estimatedCredits, defaults.delivery?.approvalRequired !== false);
2218
2484
  return {
2219
- planId: `cbp_${hashString(JSON.stringify({ goal: resolvedRequest.goal, campaignName, targetCount, routes }))}`,
2485
+ planId: `cbp_${hashString(JSON.stringify({ goal: resolvedRequest.goal, campaignName, targetCount, routes, evidence }))}`,
2220
2486
  campaignName,
2221
2487
  goal: resolvedRequest.goal,
2222
2488
  targetCount,
@@ -2231,6 +2497,7 @@ function createCampaignBuilderAgentPlan(request, context = {}) {
2231
2497
  approvals,
2232
2498
  buildRequest,
2233
2499
  brainPreflight,
2500
+ evidence,
2234
2501
  operatingPlaybooks,
2235
2502
  mcpFlow: createMcpFlow(resolvedRequest, buildRequest, routes, memory, approvals),
2236
2503
  estimatedCredits,
@@ -2322,6 +2589,90 @@ function createCampaignBuilderReadiness(plan) {
2322
2589
  nextActions: campaignReadinessNextActions(plan, ready, customRoutes.length)
2323
2590
  };
2324
2591
  }
2592
+ function createCampaignBuilderActiveWorkContext(input) {
2593
+ const plan = input.readiness?.plan ?? input.plan;
2594
+ const remembered = input.remembered ?? {};
2595
+ const lastResult = asRecord(input.lastResult);
2596
+ const readinessBlockers = input.readiness?.summary.blockers ?? [];
2597
+ const explicitBlockers = input.blockers ?? [];
2598
+ const blockers = uniqueStrings([...explicitBlockers, ...readinessBlockers]);
2599
+ const warnings = uniqueStrings([
2600
+ ...input.warnings ?? [],
2601
+ ...input.readiness?.summary.warnings ?? [],
2602
+ ...plan?.warnings ?? []
2603
+ ]);
2604
+ const missingApprovals = plan ? (input.approval ? getMissingCampaignBuilderApprovals(plan, input.approval) : plan.approvals.filter((approval) => approval.blocking)).map((approval) => approval.type) : [];
2605
+ const refs = normalizeCampaignBuilderActiveWorkRefs({
2606
+ plan,
2607
+ remembered,
2608
+ refs: input.refs,
2609
+ lastResult
2610
+ });
2611
+ const state = inferCampaignBuilderActiveWorkState({
2612
+ plan,
2613
+ readiness: input.readiness,
2614
+ remembered,
2615
+ lastResult,
2616
+ blockers,
2617
+ missingApprovals
2618
+ });
2619
+ const nextSafeAction = campaignBuilderActiveWorkNextAction(state, blockers, missingApprovals, refs);
2620
+ const approvalPacket = createCampaignBuilderApprovalPacket(plan, input.approval, missingApprovals);
2621
+ const nextSafeMcpAction = campaignBuilderActiveWorkNextMcpAction({
2622
+ state,
2623
+ plan,
2624
+ refs,
2625
+ approvalPacket
2626
+ });
2627
+ const summary = {
2628
+ plan_id: plan?.planId ?? remembered.planId ?? null,
2629
+ campaign_name: plan?.campaignName ?? null,
2630
+ target_count: plan?.targetCount ?? null,
2631
+ gtm_campaign_id: plan?.buildRequest.gtmCampaignId ?? remembered.gtmCampaignId ?? null,
2632
+ campaign_build_id: stringValue(lastResult.campaign_build_id ?? lastResult.campaignBuildId) ?? remembered.campaignBuildId ?? null,
2633
+ provider_campaign_id: remembered.providerCampaignId ?? null
2634
+ };
2635
+ const approvalBoundary = campaignBuilderActiveWorkApprovalBoundary(missingApprovals, state);
2636
+ const conversationStarters = campaignBuilderActiveWorkConversationStarters({
2637
+ state,
2638
+ blockers,
2639
+ missingApprovals,
2640
+ refs,
2641
+ nextSafeAction
2642
+ });
2643
+ return {
2644
+ packet_version: "campaign-builder-active-work-context.v1",
2645
+ read_only: true,
2646
+ no_spend: true,
2647
+ no_provider_writes: true,
2648
+ private_safe: true,
2649
+ state,
2650
+ summary,
2651
+ refs,
2652
+ blockers,
2653
+ warnings,
2654
+ missing_approval_types: missingApprovals,
2655
+ approval_packet: approvalPacket,
2656
+ approval_boundary: approvalBoundary,
2657
+ next_safe_action: nextSafeAction,
2658
+ next_safe_mcp_action: nextSafeMcpAction,
2659
+ conversation_starters: conversationStarters,
2660
+ openai_agent_handoff: createCampaignBuilderOpenAiAgentHandoff({
2661
+ plan,
2662
+ state,
2663
+ summary,
2664
+ refs,
2665
+ blockers,
2666
+ warnings,
2667
+ missingApprovals,
2668
+ approvalPacket,
2669
+ approvalBoundary,
2670
+ nextSafeAction,
2671
+ nextSafeMcpAction,
2672
+ conversationStarters
2673
+ })
2674
+ };
2675
+ }
2325
2676
  function createCampaignBuilderProofReceipt(plan, result) {
2326
2677
  const strategyMemory = asRecord(plan.strategyMemoryStatus);
2327
2678
  const dryRunResult = asRecord(result);
@@ -2380,6 +2731,7 @@ function createCampaignBuilderProofReceipt(plan, result) {
2380
2731
  estimated_credits: plan.estimatedCredits,
2381
2732
  operating_playbooks: plan.operatingPlaybooks.map((playbook) => playbook.slug)
2382
2733
  },
2734
+ evidence: plan.evidence,
2383
2735
  gates,
2384
2736
  strategy_memory_status: summarizeCampaignBuilderProofStrategyMemory(strategyMemory),
2385
2737
  dry_run_result: summarizeCampaignBuilderProofDryRun(dryRunResult),
@@ -2435,6 +2787,56 @@ function getCampaignBuilderOperatingPlaybooksForRequest(request) {
2435
2787
  ]);
2436
2788
  return slugs.map(getCampaignBuilderOperatingPlaybook).filter((playbook) => Boolean(playbook));
2437
2789
  }
2790
+ function normalizeCampaignBuilderEvidence(evidence) {
2791
+ if (!evidence) return null;
2792
+ const attachmentSummary = (evidence.attachments ?? []).filter((attachment) => stringValue(attachment.name)).slice(0, 5).map((attachment) => ({
2793
+ name: stringValue(attachment.name) ?? "attachment",
2794
+ kind: stringValue(attachment.kind) ?? null,
2795
+ media_type: stringValue(attachment.mediaType) ?? null,
2796
+ redacted: attachment.redacted !== false,
2797
+ columns: uniqueStrings(attachment.columns ?? []).slice(0, 25),
2798
+ row_count: numberOrUndefined2(attachment.rowCount) ?? null,
2799
+ content_summary: stringValue(attachment.contentSummary)?.slice(0, 500) ?? null,
2800
+ evidence_fields: uniqueStrings(attachment.evidenceFields ?? []).slice(0, 25)
2801
+ }));
2802
+ const urlSummary = (evidence.urls ?? []).filter((url) => stringValue(url.url)).slice(0, 8).map((url) => {
2803
+ const rawUrl = stringValue(url.url) ?? "";
2804
+ return {
2805
+ url: rawUrl,
2806
+ host: stringValue(url.host) ?? hostFromUrl(rawUrl) ?? null,
2807
+ type: stringValue(url.type) ?? null,
2808
+ label: stringValue(url.label) ?? null,
2809
+ summary: stringValue(url.summary)?.slice(0, 500) ?? null,
2810
+ redacted: url.redacted === true
2811
+ };
2812
+ });
2813
+ const operatorNotes = uniqueStrings(evidence.notes ?? []).map((note) => note.slice(0, 240)).slice(0, 5);
2814
+ if (!attachmentSummary.length && !urlSummary.length && !operatorNotes.length) return null;
2815
+ return {
2816
+ packet_version: "campaign-builder-evidence.v1",
2817
+ private_safe: true,
2818
+ attachment_summary: attachmentSummary,
2819
+ url_summary: urlSummary,
2820
+ operator_notes: operatorNotes,
2821
+ 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.",
2822
+ raw_private_fields_withheld: [
2823
+ "raw_private_attachment_text",
2824
+ "raw_attachment_rows",
2825
+ "raw_leads",
2826
+ "email_addresses",
2827
+ "provider_auth_payloads",
2828
+ "copy_bodies",
2829
+ "secrets"
2830
+ ]
2831
+ };
2832
+ }
2833
+ function hostFromUrl(value) {
2834
+ try {
2835
+ return new URL(value).hostname || void 0;
2836
+ } catch {
2837
+ return void 0;
2838
+ }
2839
+ }
2438
2840
  function applyCampaignBuilderStrategyTemplate(request) {
2439
2841
  const template = getCampaignBuilderStrategyTemplate(request.strategyTemplate);
2440
2842
  if (!template) return request;
@@ -3126,7 +3528,7 @@ function normalizeMemory(request) {
3126
3528
  };
3127
3529
  }
3128
3530
  function defaultAgencyMemoryQueries(request, configured) {
3129
- const operatingPlaybookTerms = getCampaignBuilderOperatingPlaybooksForRequest(request).flatMap((playbook) => [playbook.label, ...playbook.memoryKeywords]).join(" ");
3531
+ const operatingPlaybookTerms = getCampaignBuilderOperatingPlaybooksForRequest(request).flatMap((playbook) => [playbook.label, ...playbook.memoryKeywords, ...playbook.knowledgeSources]).join(" ");
3130
3532
  const queries = [{
3131
3533
  query: request.goal,
3132
3534
  scopes: ["workspace", configured.useNetworkPatterns ? "network_patterns" : "operator_notes"],
@@ -3292,6 +3694,21 @@ function routesFromCustomerTools(tools) {
3292
3694
  }))
3293
3695
  );
3294
3696
  }
3697
+ function normalizeCampaignBuilderLearningHoldout(input, gtmCampaignId, campaignName) {
3698
+ const source = asRecord(input);
3699
+ if (source.enabled === false) return { enabled: false };
3700
+ const controlPercentage = numberOrUndefined2(source.control_percentage ?? source.controlPercentage);
3701
+ const minControlSize = numberOrUndefined2(source.min_control_size ?? source.minControlSize);
3702
+ const experimentKey = stringValue(source.experiment_key ?? source.experimentKey) ?? (gtmCampaignId ? `gtm_campaign:${gtmCampaignId}:learning_holdout` : `campaign_builder_agent:${hashString(campaignName)}:learning_holdout`);
3703
+ return {
3704
+ enabled: true,
3705
+ controlPercentage: controlPercentage ?? 0.2,
3706
+ minControlSize: minControlSize ?? 50,
3707
+ experimentKey,
3708
+ sourceCampaignId: stringValue(source.source_campaign_id ?? source.sourceCampaignId) ?? gtmCampaignId,
3709
+ primaryMetric: stringValue(source.primary_metric ?? source.primaryMetric) ?? "positive_reply_rate"
3710
+ };
3711
+ }
3295
3712
  function createBuildRequest(request, defaults, targetCount, campaignName, scopeBuildArgs, routes = [], memory, scopeBrainPreflight) {
3296
3713
  const buildRequest = {
3297
3714
  name: campaignName,
@@ -3322,8 +3739,15 @@ function createBuildRequest(request, defaults, targetCount, campaignName, scopeB
3322
3739
  fillPolicy: "aggressive"
3323
3740
  }
3324
3741
  };
3742
+ const evidence = normalizeCampaignBuilderEvidence(request.evidence);
3743
+ if (evidence) buildRequest.evidence = evidence;
3325
3744
  const scoped = asRecord(scopeBuildArgs);
3326
3745
  buildRequest.gtmCampaignId = request.gtmCampaignId ?? stringValue(scoped.gtm_campaign_id ?? scoped.gtmCampaignId);
3746
+ buildRequest.learningHoldout = normalizeCampaignBuilderLearningHoldout(
3747
+ request.learningHoldout ?? scoped.learning_holdout ?? scoped.learningHoldout,
3748
+ buildRequest.gtmCampaignId,
3749
+ campaignName
3750
+ );
3327
3751
  if (typeof scoped.name === "string") buildRequest.name = scoped.name;
3328
3752
  if (typeof scoped.prompt === "string") buildRequest.prompt = scoped.prompt;
3329
3753
  if (typeof scoped.target_count === "number") buildRequest.targetCount = scoped.target_count;
@@ -3348,6 +3772,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
3348
3772
  memory.enabled ? "feedback" : void 0
3349
3773
  ]);
3350
3774
  const providerChain = uniqueStrings(["signaliz", ...request.preferredProviders ?? [], ...routes.map((route) => route.provider)]);
3775
+ const evidence = buildRequest.evidence ?? normalizeCampaignBuilderEvidence(request.evidence);
3351
3776
  const targetIcp = compact({
3352
3777
  personas: buildRequest.icp?.personas,
3353
3778
  industries: buildRequest.icp?.industries,
@@ -3360,6 +3785,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
3360
3785
  const learningArgs = compact({
3361
3786
  campaign_brief: request.goal,
3362
3787
  target_icp: targetIcp,
3788
+ evidence_context: evidence,
3363
3789
  layers,
3364
3790
  provider_chain: providerChain,
3365
3791
  lead_source: providerChain[0] ?? "signaliz",
@@ -3371,6 +3797,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
3371
3797
  const defaultsArgs = compact({
3372
3798
  campaign_brief: request.goal,
3373
3799
  target_icp: targetIcp,
3800
+ evidence_context: evidence,
3374
3801
  layers,
3375
3802
  include_global: memory.useNetworkPatterns,
3376
3803
  include_memory: memory.enabled,
@@ -3381,6 +3808,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
3381
3808
  provider_chain: providerChain,
3382
3809
  lead_source: providerChain[0] ?? "signaliz",
3383
3810
  target_icp: targetIcp,
3811
+ evidence_context: evidence,
3384
3812
  include_global: memory.useNetworkPatterns,
3385
3813
  limit: 25
3386
3814
  });
@@ -3393,11 +3821,14 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
3393
3821
  label: playbook.label,
3394
3822
  quality_gates: playbook.qualityGates,
3395
3823
  required_fields: playbook.requiredFields,
3396
- activation_boundary: playbook.activationBoundary
3824
+ activation_boundary: playbook.activationBoundary,
3825
+ knowledge_sources: playbook.knowledgeSources
3397
3826
  })),
3398
3827
  target_icp: targetIcp,
3828
+ evidence_context: evidence,
3399
3829
  provider_chain: providerChain,
3400
3830
  lead_source: providerChain[0] ?? "signaliz",
3831
+ learning_holdout: buildRequest.learningHoldout,
3401
3832
  tool_sequence: [
3402
3833
  { tool: "gtm_brain_learning_cycle_plan", purpose: "Plan the Brain learning loop for this campaign.", arguments: learningArgs },
3403
3834
  { tool: "gtm_brain_seed_defaults", purpose: "Seed campaign defaults from workspace memory and safe aggregate patterns.", arguments: defaultsArgs },
@@ -3667,7 +4098,7 @@ function createMcpFlow(request, buildRequest, routes, memory, approvals) {
3667
4098
  phase: "plan",
3668
4099
  tool: "nango_mcp_tools_list",
3669
4100
  arguments: {
3670
- format: "mcp"
4101
+ format: "openai"
3671
4102
  },
3672
4103
  readOnly: true,
3673
4104
  approvalRequired: false
@@ -3976,6 +4407,7 @@ function buildGtmCampaignBuildPlanArgs(request, buildRequest, routes, memory) {
3976
4407
  layers: layers.length > 0 ? layers : void 0,
3977
4408
  preferred_providers: preferredProviders.length > 0 ? preferredProviders : void 0,
3978
4409
  strategy_model: strategyModel,
4410
+ evidence_context: buildRequest.evidence ?? normalizeCampaignBuilderEvidence(request.evidence),
3979
4411
  include_strategy_patterns: campaignBuilderStrategyPatternsEnabled(agencyContext),
3980
4412
  include_workflow_patterns: campaignBuilderWorkflowPatternsEnabled(agencyContext),
3981
4413
  include_nango_catalog: agencyContext.includeNangoCatalog !== false,
@@ -3984,7 +4416,8 @@ function buildGtmCampaignBuildPlanArgs(request, buildRequest, routes, memory) {
3984
4416
  slug: playbook.slug,
3985
4417
  label: playbook.label,
3986
4418
  quality_gates: playbook.qualityGates,
3987
- activation_boundary: playbook.activationBoundary
4419
+ activation_boundary: playbook.activationBoundary,
4420
+ knowledge_sources: playbook.knowledgeSources
3988
4421
  })),
3989
4422
  include_memory: memory.enabled,
3990
4423
  include_brain: true,
@@ -4065,8 +4498,19 @@ function buildCampaignArgs(request) {
4065
4498
  if (request.dedupKeys) args.dedup_keys = request.dedupKeys;
4066
4499
  if (request.brainPreflight) args.brain_preflight = request.brainPreflight;
4067
4500
  if (request.brainDefaults) args.brain_defaults = request.brainDefaults;
4501
+ if (request.learningHoldout) {
4502
+ args.learning_holdout = compact({
4503
+ enabled: request.learningHoldout.enabled,
4504
+ control_percentage: request.learningHoldout.controlPercentage,
4505
+ min_control_size: request.learningHoldout.minControlSize,
4506
+ experiment_key: request.learningHoldout.experimentKey,
4507
+ source_campaign_id: request.learningHoldout.sourceCampaignId,
4508
+ primary_metric: request.learningHoldout.primaryMetric
4509
+ });
4510
+ }
4068
4511
  if (request.campaignStrategyContext) args.campaign_strategy_context = request.campaignStrategyContext;
4069
4512
  if (request.deliveryRisk) args.delivery_risk = request.deliveryRisk;
4513
+ if (request.evidence) args.evidence_context = request.evidence;
4070
4514
  if (request.icp) {
4071
4515
  args.icp = compact({
4072
4516
  personas: request.icp.personas,
@@ -4116,7 +4560,8 @@ function buildCampaignArgs(request) {
4116
4560
  max_body_words: request.copy.maxBodyWords,
4117
4561
  sender_context: request.copy.senderContext,
4118
4562
  offer_context: request.copy.offerContext,
4119
- model: request.copy.model
4563
+ model: request.copy.model,
4564
+ evidence_mode: request.copy.evidenceMode
4120
4565
  });
4121
4566
  }
4122
4567
  if (request.delivery) {
@@ -4161,6 +4606,7 @@ function buildExecutionPrepareArgs(request, dryRun, confirmSpend) {
4161
4606
  delivery_risk: buildArgs2.delivery_risk,
4162
4607
  dedup_keys: buildArgs2.dedup_keys,
4163
4608
  policy: buildArgs2.policy,
4609
+ learning_holdout: buildArgs2.learning_holdout,
4164
4610
  signals: buildArgs2.signals,
4165
4611
  qualification: buildArgs2.qualification,
4166
4612
  copy: buildArgs2.copy,
@@ -4195,10 +4641,145 @@ function mapBuildResult(data, dryRun) {
4195
4641
  targetLimitReason: data.target_limit_reason,
4196
4642
  maxSupportedTargetCount: data.max_supported_target_count,
4197
4643
  brainContext: data.brain_context ?? {},
4644
+ learningHoldout: data.learning_holdout ?? {},
4198
4645
  providerRoute: mapProviderRoute2(data)
4199
4646
  };
4200
4647
  }
4648
+ var CAMPAIGN_BUILDER_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "canceled", "cancelled"]);
4649
+ var CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["failed", "canceled", "cancelled"]);
4650
+ function mapAgentCampaignPhaseHealth(value) {
4651
+ if (!Array.isArray(value)) return [];
4652
+ return value.map((phase) => {
4653
+ const record = asRecord(phase);
4654
+ return {
4655
+ phase: stringValue(record.phase) ?? "unknown",
4656
+ status: stringValue(record.status) ?? "unknown",
4657
+ recordsProcessed: numberOrNull2(record.records_processed ?? record.recordsProcessed),
4658
+ recordsSucceeded: numberOrNull2(record.records_succeeded ?? record.recordsSucceeded),
4659
+ recordsFailed: numberOrNull2(record.records_failed ?? record.recordsFailed),
4660
+ heartbeatAgeSeconds: numberOrNull2(record.heartbeat_age_seconds ?? record.heartbeatAgeSeconds ?? record.phase_age_seconds)
4661
+ };
4662
+ });
4663
+ }
4664
+ function deriveCampaignBuildTerminalState(input) {
4665
+ const status = input.status.toLowerCase();
4666
+ const heartbeatAgeSeconds = input.phaseAgeSeconds;
4667
+ const stale = input.staleRunningPhase === true;
4668
+ const safeWatchAction = input.triggerRunId ? `signaliz ops run-status ${input.triggerRunId} --watch` : "signaliz campaign-agent status <campaign_build_id>";
4669
+ if (CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES.has(status)) {
4670
+ return {
4671
+ kind: "blocked",
4672
+ label: "Blocked terminal state",
4673
+ reviewable: true,
4674
+ terminal: true,
4675
+ stale: false,
4676
+ phase: input.phase,
4677
+ phaseStatus: input.phaseStatus,
4678
+ heartbeatAgeSeconds,
4679
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
4680
+ terminalReason: input.terminalReason,
4681
+ staleReason: null,
4682
+ safeNextAction: input.nextAction || "Review errors and rerun only after the blocker is resolved."
4683
+ };
4684
+ }
4685
+ if (status === "pending_approval") {
4686
+ return {
4687
+ kind: "pending_approval",
4688
+ label: "Pending delivery approval",
4689
+ reviewable: true,
4690
+ terminal: false,
4691
+ stale: false,
4692
+ phase: input.phase,
4693
+ phaseStatus: input.phaseStatus,
4694
+ heartbeatAgeSeconds,
4695
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
4696
+ terminalReason: input.terminalReason,
4697
+ staleReason: null,
4698
+ safeNextAction: input.approvalAction || input.nextAction || "Review rows and approve delivery only after the approval packet passes."
4699
+ };
4700
+ }
4701
+ if (CAMPAIGN_BUILDER_TERMINAL_STATUSES.has(status)) {
4702
+ return {
4703
+ kind: "terminal",
4704
+ label: "Terminal and reviewable",
4705
+ reviewable: true,
4706
+ terminal: true,
4707
+ stale: false,
4708
+ phase: input.phase,
4709
+ phaseStatus: input.phaseStatus,
4710
+ heartbeatAgeSeconds,
4711
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
4712
+ terminalReason: input.terminalReason,
4713
+ staleReason: null,
4714
+ safeNextAction: input.nextAction || "Review rows, artifacts, scorecard, and approval locks before delivery."
4715
+ };
4716
+ }
4717
+ if (status === "running" || status === "queued" || status === "processing") {
4718
+ if (stale) {
4719
+ return {
4720
+ kind: "running_stale",
4721
+ label: "Running but stale",
4722
+ reviewable: false,
4723
+ terminal: false,
4724
+ stale: true,
4725
+ phase: input.phase,
4726
+ phaseStatus: input.phaseStatus,
4727
+ heartbeatAgeSeconds,
4728
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
4729
+ terminalReason: null,
4730
+ staleReason: input.staleReason || "The active phase heartbeat is stale.",
4731
+ safeNextAction: input.nextAction || safeWatchAction
4732
+ };
4733
+ }
4734
+ return {
4735
+ kind: "running_healthy",
4736
+ label: "Running and healthy",
4737
+ reviewable: false,
4738
+ terminal: false,
4739
+ stale: false,
4740
+ phase: input.phase,
4741
+ phaseStatus: input.phaseStatus,
4742
+ heartbeatAgeSeconds,
4743
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
4744
+ terminalReason: null,
4745
+ staleReason: null,
4746
+ safeNextAction: input.nextAction || safeWatchAction
4747
+ };
4748
+ }
4749
+ return {
4750
+ kind: "unknown",
4751
+ label: "Unknown build state",
4752
+ reviewable: false,
4753
+ terminal: false,
4754
+ stale,
4755
+ phase: input.phase,
4756
+ phaseStatus: input.phaseStatus,
4757
+ heartbeatAgeSeconds,
4758
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
4759
+ terminalReason: input.terminalReason,
4760
+ staleReason: input.staleReason,
4761
+ safeNextAction: input.nextAction || "Read campaign build status again before making a launch or delivery decision."
4762
+ };
4763
+ }
4201
4764
  function mapAgentCampaignBuildStatus(data) {
4765
+ const customerRowCounts = mapAgentCustomerRowCounts(data.customer_row_counts);
4766
+ const phaseHealth = mapAgentCampaignPhaseHealth(data.phases);
4767
+ const terminalReason = stringValue(data.terminal_reason) ?? customerRowCounts?.terminalReason ?? null;
4768
+ const staleReason = stringValue(data.stale_reason) ?? stringValue(asRecord(data.diagnostics).stale_reason) ?? null;
4769
+ const nextPollAfterSeconds = numberOrNull2(data.next_poll_after_seconds ?? data.nextPollAfterSeconds);
4770
+ const terminalState = deriveCampaignBuildTerminalState({
4771
+ status: stringValue(data.status) ?? "unknown",
4772
+ phase: stringValue(data.current_phase) ?? null,
4773
+ phaseStatus: stringValue(data.current_phase_status) ?? null,
4774
+ staleRunningPhase: data.stale_running_phase === true,
4775
+ phaseAgeSeconds: numberOrNull2(data.phase_age_seconds),
4776
+ nextPollAfterSeconds,
4777
+ terminalReason,
4778
+ staleReason,
4779
+ nextAction: formatAgentNextAction(data.next_action),
4780
+ approvalAction: formatAgentNextAction(data.approval_action),
4781
+ triggerRunId: stringValue(data.trigger_run_id) ?? null
4782
+ });
4202
4783
  return {
4203
4784
  campaignBuildId: stringValue(data.campaign_build_id) ?? "",
4204
4785
  campaignId: stringValue(data.campaign_id) ?? null,
@@ -4216,12 +4797,18 @@ function mapAgentCampaignBuildStatus(data) {
4216
4797
  errors: diagnosticMessages2(data.errors),
4217
4798
  artifactCount: numberOrUndefined2(data.artifact_count) ?? 0,
4218
4799
  artifactDownloads: Array.isArray(data.artifact_downloads) ? data.artifact_downloads.map(mapAgentCampaignArtifactDownload) : [],
4219
- customerRowCounts: mapAgentCustomerRowCounts(data.customer_row_counts),
4800
+ customerRowCounts,
4220
4801
  phaseAttemptTotals: nullableRecord(data.phase_attempt_totals) ?? void 0,
4221
4802
  providerRoute: mapProviderRoute2(data),
4222
4803
  triggerRunId: stringValue(data.trigger_run_id) ?? null,
4223
4804
  staleRunningPhase: data.stale_running_phase === true,
4224
4805
  phaseAgeSeconds: numberOrNull2(data.phase_age_seconds),
4806
+ nextPollAfterSeconds,
4807
+ terminalReason,
4808
+ staleReason,
4809
+ safeNextAction: terminalState.safeNextAction,
4810
+ terminalState,
4811
+ phaseHealth,
4225
4812
  diagnostics: nullableRecord(data.diagnostics) ?? {},
4226
4813
  nextAction: formatAgentNextAction(data.next_action),
4227
4814
  approvalAction: formatAgentNextAction(data.approval_action),
@@ -4251,6 +4838,7 @@ function mapAgentCustomerRowCounts(value) {
4251
4838
  acceptedRows: numberOrUndefined2(record.accepted_rows),
4252
4839
  usableRows: numberOrUndefined2(record.usable_rows),
4253
4840
  copiedRows: numberOrUndefined2(record.copied_rows),
4841
+ copySkippedRows: numberOrUndefined2(record.copy_skipped_rows),
4254
4842
  approvalReadyRows: numberOrUndefined2(record.approval_ready_rows),
4255
4843
  qaReadyRows: numberOrUndefined2(record.qa_ready_rows),
4256
4844
  deliveredRows: numberOrUndefined2(record.delivered_rows),
@@ -4309,67 +4897,285 @@ function mapAgentCampaignArtifact(data) {
4309
4897
  createdAt: stringValue(data.created_at) ?? ""
4310
4898
  };
4311
4899
  }
4900
+ var NORTH_STAR_BANNED_COPY_PHRASES = [
4901
+ "i have been following",
4902
+ "i saw that",
4903
+ "would you be opposed",
4904
+ "unlock new opportunities",
4905
+ "significant success",
4906
+ "we understand",
4907
+ "hope this finds you well"
4908
+ ];
4909
+ function campaignReviewGate(id, label, status, detail) {
4910
+ return {
4911
+ id,
4912
+ label,
4913
+ status,
4914
+ passed: status !== "blocked",
4915
+ detail
4916
+ };
4917
+ }
4918
+ function northStarMetric(id, label, status, detail, value, target) {
4919
+ return {
4920
+ id,
4921
+ label,
4922
+ status,
4923
+ detail,
4924
+ ...value !== void 0 ? { value } : {},
4925
+ ...target !== void 0 ? { target } : {}
4926
+ };
4927
+ }
4928
+ function createCampaignBuilderNorthStarScorecard(status, rows, artifacts, terminalState) {
4929
+ if (!terminalState.reviewable) {
4930
+ const pendingMetric = northStarMetric(
4931
+ "terminal_state",
4932
+ "Terminal-state contract",
4933
+ terminalState.kind === "running_stale" ? "blocked" : "pending",
4934
+ 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.`,
4935
+ terminalState.kind,
4936
+ "terminal or pending_approval"
4937
+ );
4938
+ return {
4939
+ version: "campaign-builder-north-star.v1",
4940
+ status: pendingMetric.status,
4941
+ score: null,
4942
+ moatState: "scaffolding_ready",
4943
+ metrics: [pendingMetric],
4944
+ blockers: pendingMetric.status === "blocked" ? [pendingMetric.detail] : [],
4945
+ warnings: pendingMetric.status === "pending" ? [pendingMetric.detail] : [],
4946
+ nextAction: terminalState.safeNextAction
4947
+ };
4948
+ }
4949
+ const sampledRows = rows.rows.length;
4950
+ const rowData = rows.rows.map((row) => asRecord(row.data));
4951
+ const requestedTarget = status.customerRowCounts?.requestedTarget ?? status.recordsTotal ?? rows.count;
4952
+ const finalRows = status.customerRowCounts?.deliveredRows ?? status.customerRowCounts?.qaReadyRows ?? status.customerRowCounts?.copySkippedRows ?? status.customerRowCounts?.copiedRows ?? status.customerRowCounts?.acceptedRows ?? rows.count;
4953
+ const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
4954
+ const verifiedEmailRows = rowData.filter(campaignReviewDataHasVerifiedEmail).length;
4955
+ const uniqueEmails = new Set(rowData.map(campaignReviewDataEmail).filter(Boolean)).size;
4956
+ const uniqueDomains = new Set(rowData.map(campaignReviewDataDomain).filter(Boolean)).size;
4957
+ const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
4958
+ const copyQaIssuesByRow = rowData.map(campaignReviewDataCopyQaIssues).filter((issues) => issues.length > 0);
4959
+ const copyQaIssueCounts = countCampaignReviewIssues(copyQaIssuesByRow);
4960
+ const signalBackedRows = rowData.filter(campaignReviewDataHasSignalBasis).length;
4961
+ const noSupportedSignalRows = rowData.filter(campaignReviewDataHasNoSupportedSignal).length;
4962
+ const unsupportedSignalClaims = rowData.filter(campaignReviewDataHasUnsupportedSignalClaim).length;
4963
+ const approvalLockedRows = rowData.filter(campaignReviewDataHasApprovalLock).length;
4964
+ const holdoutRows = rowData.filter(campaignReviewDataHasHoldoutAttribution).length;
4965
+ const artifactCount = artifacts.length || status.artifactCount || 0;
4966
+ const deliveredRows = status.customerRowCounts?.deliveredRows ?? 0;
4967
+ const feedbackReady = campaignReviewStatusHasFeedbackReadiness(status);
4968
+ const feedbackRequiredForApproval = terminalState.kind === "pending_approval";
4969
+ const moat = campaignReviewMoatState(status, holdoutRows);
4970
+ const metrics = [
4971
+ northStarMetric(
4972
+ "terminal_state",
4973
+ "Terminal-state contract",
4974
+ terminalState.kind === "blocked" ? "blocked" : "pass",
4975
+ terminalState.label,
4976
+ terminalState.kind,
4977
+ "terminal or pending_approval"
4978
+ ),
4979
+ northStarMetric(
4980
+ "target_fill",
4981
+ "Target fill",
4982
+ requestedTarget && finalRows < requestedTarget ? "review" : "pass",
4983
+ requestedTarget ? `${finalRows || 0}/${requestedTarget} final or reviewable row(s) are accounted for.` : "No requested target was reported; using sampled rows for review.",
4984
+ finalRows || sampledRows,
4985
+ requestedTarget || sampledRows
4986
+ ),
4987
+ northStarMetric(
4988
+ "unique_emails",
4989
+ "Unique emails",
4990
+ sampledRows === 0 ? "blocked" : uniqueEmails === rowsWithEmail ? "pass" : "blocked",
4991
+ sampledRows === 0 ? "No sampled rows were available for email uniqueness review." : `${uniqueEmails}/${rowsWithEmail} sampled email(s) are unique.`,
4992
+ uniqueEmails,
4993
+ rowsWithEmail
4994
+ ),
4995
+ northStarMetric(
4996
+ "unique_domains",
4997
+ "Unique domains",
4998
+ sampledRows === 0 ? "blocked" : uniqueDomains === sampledRows ? "pass" : "review",
4999
+ sampledRows === 0 ? "No sampled rows were available for domain uniqueness review." : `${uniqueDomains}/${sampledRows} sampled company domain(s) are unique.`,
5000
+ uniqueDomains,
5001
+ sampledRows
5002
+ ),
5003
+ northStarMetric(
5004
+ "verified_email_rows",
5005
+ "Verified email rows",
5006
+ sampledRows === 0 ? "blocked" : verifiedEmailRows === sampledRows ? "pass" : rowsWithEmail === sampledRows ? "review" : "blocked",
5007
+ sampledRows === 0 ? "No sampled rows were available for email verification review." : `${verifiedEmailRows}/${sampledRows} sampled row(s) carry verified-email evidence.`,
5008
+ verifiedEmailRows,
5009
+ sampledRows
5010
+ ),
5011
+ northStarMetric(
5012
+ "copy_completeness",
5013
+ "Copy completeness",
5014
+ sampledRows === 0 ? "blocked" : copyReadyRows === sampledRows ? "pass" : "blocked",
5015
+ sampledRows === 0 ? "No sampled rows were available for copy review." : `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.`,
5016
+ copyReadyRows,
5017
+ sampledRows
5018
+ ),
5019
+ northStarMetric(
5020
+ "copy_quality",
5021
+ "Copy QA",
5022
+ copyQaIssuesByRow.length === 0 ? "pass" : "blocked",
5023
+ copyQaIssuesByRow.length === 0 ? "No sampled copy QA blockers were detected." : `${copyQaIssuesByRow.length}/${sampledRows} sampled row(s) have copy QA issue(s): ${formatCampaignReviewIssueCounts(copyQaIssueCounts)}.`,
5024
+ copyQaIssuesByRow.length,
5025
+ 0
5026
+ ),
5027
+ northStarMetric(
5028
+ "signal_grounding",
5029
+ "Signal grounding",
5030
+ unsupportedSignalClaims > 0 ? "blocked" : signalBackedRows > 0 ? "pass" : "review",
5031
+ 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.",
5032
+ signalBackedRows,
5033
+ sampledRows
5034
+ ),
5035
+ northStarMetric(
5036
+ "approval_locks",
5037
+ "Approval locks",
5038
+ approvalLockedRows > 0 || deliveredRows === 0 ? "pass" : "review",
5039
+ 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.",
5040
+ approvalLockedRows,
5041
+ sampledRows
5042
+ ),
5043
+ northStarMetric(
5044
+ "export_send_safety",
5045
+ "Export/send safety",
5046
+ deliveredRows > 0 && status.status !== "completed" ? "blocked" : "pass",
5047
+ deliveredRows > 0 ? `${deliveredRows} row(s) are delivered; status is ${status.status}.` : "No delivered rows are reported before approval.",
5048
+ deliveredRows,
5049
+ 0
5050
+ ),
5051
+ northStarMetric(
5052
+ "holdout_attribution",
5053
+ "Holdout attribution",
5054
+ holdoutRows > 0 ? "pass" : "review",
5055
+ holdoutRows > 0 ? `${holdoutRows}/${sampledRows} sampled row(s) preserve holdout attribution.` : "No holdout attribution was visible in the sampled rows.",
5056
+ holdoutRows,
5057
+ sampledRows
5058
+ ),
5059
+ northStarMetric(
5060
+ "feedback_readiness",
5061
+ "Feedback readiness",
5062
+ feedbackReady ? "pass" : feedbackRequiredForApproval ? "blocked" : "review",
5063
+ 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.",
5064
+ feedbackReady,
5065
+ true
5066
+ ),
5067
+ northStarMetric(
5068
+ "causal_moat",
5069
+ "Causal moat",
5070
+ moat.state === "proved" ? "pass" : "review",
5071
+ moat.detail,
5072
+ moat.state,
5073
+ "proved"
5074
+ ),
5075
+ northStarMetric(
5076
+ "artifact_readback",
5077
+ "Artifact readback",
5078
+ artifactCount > 0 ? "pass" : "review",
5079
+ artifactCount > 0 ? `${artifactCount} artifact(s) are available for review.` : "No review artifact is available yet.",
5080
+ artifactCount,
5081
+ 1
5082
+ )
5083
+ ];
5084
+ const gradedMetrics = metrics.filter((metric) => metric.status !== "pending");
5085
+ const score = gradedMetrics.length === 0 ? null : Math.round(gradedMetrics.filter((metric) => metric.status === "pass").length / gradedMetrics.length * 100);
5086
+ const blockers = metrics.filter((metric) => metric.status === "blocked").map((metric) => metric.detail);
5087
+ const warnings = metrics.filter((metric) => metric.status === "review").map((metric) => metric.detail);
5088
+ const scorecardStatus = blockers.length > 0 ? "blocked" : warnings.length > 0 ? "review" : "pass";
5089
+ return {
5090
+ version: "campaign-builder-north-star.v1",
5091
+ status: scorecardStatus,
5092
+ score,
5093
+ moatState: moat.state,
5094
+ metrics,
5095
+ blockers,
5096
+ warnings,
5097
+ nextAction: scorecardStatus === "blocked" ? "Fix blocked North Star gates before approval or delivery." : terminalState.safeNextAction
5098
+ };
5099
+ }
4312
5100
  function createCampaignBuilderBuildReview(status, rows, artifacts) {
5101
+ const terminalState = status.terminalState ?? deriveCampaignBuildTerminalState({
5102
+ status: status.status,
5103
+ phase: status.currentPhase,
5104
+ phaseStatus: status.currentPhaseStatus,
5105
+ staleRunningPhase: status.staleRunningPhase === true,
5106
+ phaseAgeSeconds: status.phaseAgeSeconds ?? null,
5107
+ nextPollAfterSeconds: status.nextPollAfterSeconds ?? null,
5108
+ terminalReason: status.terminalReason ?? status.customerRowCounts?.terminalReason ?? null,
5109
+ staleReason: status.staleReason ?? null,
5110
+ nextAction: status.nextAction,
5111
+ approvalAction: status.approvalAction,
5112
+ triggerRunId: status.triggerRunId ?? null
5113
+ });
4313
5114
  const sampledRows = rows.rows.length;
4314
5115
  const availableRows = rows.count || sampledRows;
4315
5116
  const qualifiedRows = rows.rows.filter((row) => row.qualified !== false).length;
4316
5117
  const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
4317
5118
  const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
4318
5119
  const artifactCount = artifacts.length || status.artifactCount || 0;
5120
+ const northStarScorecard = createCampaignBuilderNorthStarScorecard(status, rows, artifacts, terminalState);
4319
5121
  const gates = [
4320
- {
4321
- id: "build_completed",
4322
- label: "Build completed",
4323
- passed: status.status === "completed",
4324
- detail: status.status === "completed" ? "The campaign build is completed." : `The campaign build is ${status.status}; review again after completion.`
4325
- },
4326
- {
4327
- id: "rows_available",
4328
- label: "Rows available",
4329
- passed: sampledRows > 0,
4330
- detail: sampledRows > 0 ? `${sampledRows} sampled row(s) are available for review.` : "No rows were returned for review."
4331
- },
4332
- {
4333
- id: "qualified_sample",
4334
- label: "Sample rows qualified",
4335
- passed: sampledRows > 0 && qualifiedRows === sampledRows,
4336
- detail: sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
4337
- },
4338
- {
4339
- id: "email_coverage",
4340
- label: "Email coverage",
4341
- passed: sampledRows > 0 && rowsWithEmail === sampledRows,
4342
- detail: sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
4343
- },
4344
- {
4345
- id: "artifact_available",
4346
- label: "Artifact available",
4347
- passed: artifactCount > 0,
4348
- detail: artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
4349
- },
4350
- {
4351
- id: "no_failed_rows",
4352
- label: "No failed rows",
4353
- passed: status.recordsFailed === 0,
4354
- detail: status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
4355
- }
5122
+ campaignReviewGate(
5123
+ "build_reviewable",
5124
+ "Build reviewable",
5125
+ terminalState.kind === "running_healthy" ? "pending" : terminalState.kind === "running_stale" || terminalState.kind === "blocked" ? "blocked" : "pass",
5126
+ terminalState.label
5127
+ ),
5128
+ campaignReviewGate(
5129
+ "rows_available",
5130
+ "Rows available",
5131
+ sampledRows > 0 ? "pass" : terminalState.reviewable ? "blocked" : "pending",
5132
+ 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."
5133
+ ),
5134
+ campaignReviewGate(
5135
+ "qualified_sample",
5136
+ "Sample rows qualified",
5137
+ sampledRows === 0 ? terminalState.reviewable ? "blocked" : "pending" : qualifiedRows === sampledRows ? "pass" : "blocked",
5138
+ sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
5139
+ ),
5140
+ campaignReviewGate(
5141
+ "email_coverage",
5142
+ "Email coverage",
5143
+ sampledRows === 0 ? terminalState.reviewable ? "blocked" : "pending" : rowsWithEmail === sampledRows ? "pass" : "blocked",
5144
+ sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
5145
+ ),
5146
+ campaignReviewGate(
5147
+ "artifact_available",
5148
+ "Artifact available",
5149
+ artifactCount > 0 ? "pass" : terminalState.reviewable ? "review" : "pending",
5150
+ artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
5151
+ ),
5152
+ campaignReviewGate(
5153
+ "no_failed_rows",
5154
+ "No failed rows",
5155
+ status.recordsFailed === 0 ? "pass" : "blocked",
5156
+ status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
5157
+ )
4356
5158
  ];
4357
- const blockedReasons = gates.filter((gate) => !gate.passed).map((gate) => gate.detail);
5159
+ const blockedReasons = gates.filter((gate) => gate.status === "blocked" || !gate.passed).map((gate) => gate.detail);
4358
5160
  const warnings = [
4359
5161
  rows.hasMore ? "This review is based on a row sample; page through remaining rows before final approval." : void 0,
4360
5162
  sampledRows > 0 && copyReadyRows < sampledRows ? `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.` : void 0,
4361
5163
  ...status.warnings
4362
5164
  ].filter((item) => Boolean(item));
4363
- const readyForDeliveryApproval = blockedReasons.length === 0;
5165
+ const readyForDeliveryApproval = terminalState.reviewable && blockedReasons.length === 0 && northStarScorecard.status !== "blocked";
4364
5166
  return {
4365
5167
  campaignBuildId: status.campaignBuildId || rows.campaignBuildId,
4366
5168
  status,
4367
5169
  rows,
4368
5170
  artifacts,
4369
5171
  gates,
5172
+ northStarScorecard,
4370
5173
  summary: {
4371
5174
  status: status.status,
4372
5175
  phase: status.currentPhase,
5176
+ terminalState: terminalState.kind,
5177
+ scorecardStatus: northStarScorecard.status,
5178
+ scorecardScore: northStarScorecard.score,
4373
5179
  sampledRows,
4374
5180
  availableRows,
4375
5181
  qualifiedRows,
@@ -4423,6 +5229,184 @@ function campaignReviewRowHasCopy(row) {
4423
5229
  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)
4424
5230
  );
4425
5231
  }
5232
+ function campaignReviewDataEmail(data) {
5233
+ return stringValue(data.email) || stringValue(data.work_email) || stringValue(data.workEmail) || stringValue(asRecord(data.contact).email);
5234
+ }
5235
+ function campaignReviewDataDomain(data) {
5236
+ const domain = stringValue(data.company_domain) || stringValue(data.companyDomain) || stringValue(asRecord(data.company).domain);
5237
+ return domain?.toLowerCase();
5238
+ }
5239
+ function campaignReviewDataHasVerifiedEmail(data) {
5240
+ const status = String(data.email_status ?? data.emailStatus ?? asRecord(data.contact).email_status ?? "").toLowerCase();
5241
+ return data.email_verified === true || data.emailVerified === true || asRecord(data.contact).email_verified === true || ["valid", "validated", "verified"].includes(status);
5242
+ }
5243
+ function campaignReviewDataCopyParts(data) {
5244
+ const copy = asRecord(data.copy);
5245
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
5246
+ return [
5247
+ stringValue(data.subject),
5248
+ stringValue(data.subject_line),
5249
+ stringValue(data.opener),
5250
+ stringValue(data.body),
5251
+ stringValue(data.cta),
5252
+ stringValue(copy.subject),
5253
+ stringValue(copy.opener),
5254
+ stringValue(copy.body),
5255
+ stringValue(copy.cta),
5256
+ stringValue(rawCopy.subject),
5257
+ stringValue(rawCopy.opener),
5258
+ stringValue(rawCopy.body),
5259
+ stringValue(rawCopy.cta)
5260
+ ].filter((part) => Boolean(part));
5261
+ }
5262
+ function campaignReviewDataHasBadGreeting(data) {
5263
+ return campaignReviewDataCopyParts(data).some(
5264
+ (part) => /\bhi\s*(?:,|\{\{|\[)|\bhello\s*(?:,|\{\{|\[)/i.test(part) || /\b(first_name|firstname|name)\b/i.test(part)
5265
+ );
5266
+ }
5267
+ function campaignReviewDataHasBannedPhrase(data) {
5268
+ const text = campaignReviewDataCopyParts(data).join(" ").toLowerCase();
5269
+ return NORTH_STAR_BANNED_COPY_PHRASES.some((phrase) => text.includes(phrase));
5270
+ }
5271
+ function campaignReviewDataSubject(data) {
5272
+ const copy = asRecord(data.copy);
5273
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
5274
+ return stringValue(data.subject) || stringValue(data.subject_line) || stringValue(data.subjectLine) || stringValue(copy.subject) || stringValue(rawCopy.subject);
5275
+ }
5276
+ function campaignReviewDataCta(data) {
5277
+ const copy = asRecord(data.copy);
5278
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
5279
+ return stringValue(data.cta) || stringValue(copy.cta) || stringValue(rawCopy.cta);
5280
+ }
5281
+ function countCampaignReviewIssues(issueRows) {
5282
+ return issueRows.flat().reduce((acc, issue) => {
5283
+ acc[issue] = (acc[issue] || 0) + 1;
5284
+ return acc;
5285
+ }, {});
5286
+ }
5287
+ function formatCampaignReviewIssueCounts(counts) {
5288
+ const entries = Object.entries(counts);
5289
+ return entries.length > 0 ? entries.map(([issue, count]) => `${issue}=${count}`).join(", ") : "none";
5290
+ }
5291
+ function campaignReviewDataCopyQaIssues(data) {
5292
+ const issues = /* @__PURE__ */ new Set();
5293
+ const copyParts = campaignReviewDataCopyParts(data);
5294
+ if (copyParts.length === 0) return [];
5295
+ if (campaignReviewDataHasBadGreeting(data)) issues.add("bad_greeting");
5296
+ if (campaignReviewDataHasBannedPhrase(data)) issues.add("banned_or_vague_phrase");
5297
+ if (campaignReviewDataHasUnsupportedSignalClaim(data)) issues.add("unsupported_signal_claim");
5298
+ if (campaignReviewDataPersonalizationBasis(data).length === 0) issues.add("missing_personalization_basis");
5299
+ if (!campaignReviewDataCta(data)) issues.add("missing_cta");
5300
+ const subject = campaignReviewDataSubject(data);
5301
+ if (subject && subject.length > 70) issues.add("overlong_subject");
5302
+ return [...issues];
5303
+ }
5304
+ function campaignReviewDataPersonalizationBasis(data) {
5305
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
5306
+ const copy = asRecord(data.copy);
5307
+ const metadata = asRecord(data.metadata);
5308
+ const signalEvidence = asRecord(data.signal_evidence ?? metadata.signal_evidence ?? metadata.signalEvidence);
5309
+ return [
5310
+ ...arrayOfStrings(data.personalization_basis) ?? [],
5311
+ ...arrayOfStrings(data.personalizationBasis) ?? [],
5312
+ ...arrayOfStrings(copy.personalization_basis) ?? [],
5313
+ ...arrayOfStrings(rawCopy.personalization_basis) ?? [],
5314
+ ...arrayOfStrings(metadata.personalization_basis) ?? [],
5315
+ ...arrayOfStrings(metadata.personalizationBasis) ?? [],
5316
+ ...arrayOfStrings(signalEvidence.personalization_basis) ?? []
5317
+ ];
5318
+ }
5319
+ function campaignReviewDataHasNoSupportedSignal(data) {
5320
+ const metadata = asRecord(data.metadata);
5321
+ const copy = asRecord(data.copy);
5322
+ const state = [
5323
+ data.copy_signal_state,
5324
+ data.signal_basis,
5325
+ data.signal_type,
5326
+ metadata.copy_signal_state,
5327
+ metadata.signal_basis,
5328
+ metadata.signal_type,
5329
+ copy.copy_signal_state
5330
+ ].map((value) => stringValue(value)?.toLowerCase()).filter(Boolean);
5331
+ return data.no_supported_signal === true || metadata.no_supported_signal === true || state.includes("no_supported_signal");
5332
+ }
5333
+ function campaignReviewDataHasSignalBasis(data) {
5334
+ if (campaignReviewDataHasNoSupportedSignal(data)) return false;
5335
+ const metadata = asRecord(data.metadata);
5336
+ const signal = asRecord(data.signal);
5337
+ const signalEvidence = asRecord(data.signal_evidence ?? metadata.signal_evidence ?? metadata.signalEvidence);
5338
+ const basis = campaignReviewDataPersonalizationBasis(data);
5339
+ return Boolean(
5340
+ 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))
5341
+ );
5342
+ }
5343
+ function campaignReviewDataHasUnsupportedSignalClaim(data) {
5344
+ const copyText = campaignReviewDataCopyParts(data).join(" ").toLowerCase();
5345
+ const claimsSignal = /\b(hiring|funding|raised|launched|expanding|appointed|announced|recent news|new role|job posting)\b/.test(copyText);
5346
+ return claimsSignal && !campaignReviewDataHasSignalBasis(data);
5347
+ }
5348
+ function campaignReviewDataHasApprovalLock(data) {
5349
+ const metadata = asRecord(data.fit_metadata ?? data.metadata);
5350
+ const approval = String(data.approval_status ?? data.delivery_approval_status ?? metadata.approval_status ?? "").toLowerCase();
5351
+ const exportStatus = String(data.export_status ?? data.exportStatus ?? "").toLowerCase();
5352
+ 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);
5353
+ }
5354
+ function campaignReviewDataHasHoldoutAttribution(data) {
5355
+ const fitMetadata = asRecord(data.fit_metadata ?? data.fitMetadata);
5356
+ const metadata = asRecord(data.metadata);
5357
+ const holdout = asRecord(
5358
+ fitMetadata.learning_holdout ?? fitMetadata.learningHoldout ?? metadata.learning_holdout ?? metadata.learningHoldout ?? data.learning_holdout ?? data.learningHoldout
5359
+ );
5360
+ const experimentKey = stringValue(holdout.experiment_key ?? holdout.experimentKey ?? data.experiment_key ?? data.experimentKey);
5361
+ const cohort = stringValue(holdout.cohort_role ?? holdout.cohortRole ?? data.cohort_role ?? data.learning_cohort ?? data.learningCohort);
5362
+ const brainApplied = holdout.brain_applied ?? holdout.brainApplied ?? data.brain_applied ?? data.signaliz_brain_applied;
5363
+ return Boolean(experimentKey && cohort && brainApplied !== void 0);
5364
+ }
5365
+ function campaignReviewStatusHasFeedbackReadiness(status) {
5366
+ const diagnostics = asRecord(status.diagnostics);
5367
+ const feedbackReadiness = asRecord(diagnostics.feedback_readiness ?? diagnostics.feedbackReadiness);
5368
+ const feedbackSubstrate = asRecord(diagnostics.feedback_substrate ?? diagnostics.feedbackSubstrate);
5369
+ const evidenceCounts = asRecord(diagnostics.evidence_counts ?? diagnostics.evidenceCounts);
5370
+ return feedbackReadiness.ready === true || feedbackReadiness.webhook_configured === true || feedbackSubstrate.stage === "ready" || Number(evidenceCounts.feedback_events ?? 0) > 0;
5371
+ }
5372
+ function campaignReviewMoatState(status, holdoutRows) {
5373
+ const diagnostics = asRecord(status.diagnostics);
5374
+ const evidenceCounts = asRecord(diagnostics.evidence_counts ?? diagnostics.evidenceCounts);
5375
+ const embeddedHoldout = asRecord(diagnostics.embedded_holdout ?? diagnostics.embeddedHoldout);
5376
+ const holdoutLift = asRecord(diagnostics.holdout_lift ?? diagnostics.holdoutLift ?? diagnostics.holdout_measurement ?? diagnostics.holdoutMeasurement);
5377
+ const controlSample = Math.max(
5378
+ Number(evidenceCounts.holdout_control_sample_size ?? 0),
5379
+ Number(evidenceCounts.embedded_holdout_control_sample ?? 0),
5380
+ Number(embeddedHoldout.control_sample_size ?? 0)
5381
+ );
5382
+ const learnedSample = Math.max(
5383
+ Number(evidenceCounts.holdout_learned_sample_size ?? 0),
5384
+ Number(evidenceCounts.embedded_holdout_learned_sample ?? 0),
5385
+ Number(embeddedHoldout.learned_sample_size ?? 0)
5386
+ );
5387
+ const feedbackEvents = Math.max(
5388
+ Number(evidenceCounts.feedback_events ?? 0),
5389
+ Number(evidenceCounts.campaign_build_feedback_events ?? 0),
5390
+ Number(evidenceCounts.delivery_holdout_sent_outcomes ?? 0)
5391
+ );
5392
+ const proved = diagnostics.closed_loop_proof_ready === true || holdoutLift.honest_measurement === true && holdoutLift.positive_lift === true;
5393
+ if (proved) {
5394
+ return {
5395
+ state: "proved",
5396
+ detail: "Real attributed feedback shows positive lift for explicit control and learned cohorts."
5397
+ };
5398
+ }
5399
+ if (controlSample > 0 && learnedSample > 0 && feedbackEvents > 0) {
5400
+ return {
5401
+ state: "moved_needs_lift_volume",
5402
+ detail: `Control and learned cohorts have attributed feedback (${controlSample} control, ${learnedSample} learned, ${feedbackEvents} feedback event(s)); keep collecting lift volume before claiming proof.`
5403
+ };
5404
+ }
5405
+ return {
5406
+ state: "scaffolding_ready",
5407
+ 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."
5408
+ };
5409
+ }
4426
5410
  function createCampaignBuilderReadinessLane(route, plan) {
4427
5411
  const builtIn = campaignBuilderBuiltInForRoute(route);
4428
5412
  const customRoute = route.provider !== "signaliz" && route.provider !== "csv" && builtIn === void 0;
@@ -4456,6 +5440,342 @@ function createCampaignBuilderReadinessLane(route, plan) {
4456
5440
  nextAction: customRoute ? `Review ${route.provider} route setup before launch approval.` : void 0
4457
5441
  };
4458
5442
  }
5443
+ function createCampaignBuilderApprovalPacket(plan, approval, missingTypes) {
5444
+ const requestedTypes = plan ? uniqueStrings(plan.approvals.filter((item) => item.blocking).map((item) => item.type)) : [];
5445
+ const approvalMatchesPlan = Boolean(plan && approval?.planId === plan.planId);
5446
+ const approvedTypes = approvalMatchesPlan ? approval?.approvedTypes ?? [] : [];
5447
+ const approvedRoutes = approvalMatchesPlan ? approval?.approvedRouteIds ?? [] : [];
5448
+ const missingIds = new Set(
5449
+ plan && approvalMatchesPlan && approval ? getMissingCampaignBuilderApprovals(plan, approval).map((item) => item.id) : plan?.approvals.filter((item) => item.blocking).map((item) => item.id) ?? []
5450
+ );
5451
+ const routeIdsRequired = plan ? uniqueStrings(plan.approvals.map((item) => item.routeId).filter((id) => Boolean(id))) : [];
5452
+ const spendLimitRequired = plan ? Math.max(0, ...plan.approvals.filter((item) => item.type === "spend" && item.blocking).map((item) => item.estimatedCredits ?? plan.estimatedCredits)) : 0;
5453
+ const normalizedSpendLimit = spendLimitRequired > 0 ? spendLimitRequired : null;
5454
+ const approvalState = !plan ? "unscoped" : missingTypes.length > 0 ? "requires_approval" : "approved";
5455
+ const approvalInput = {
5456
+ approved_by: approval?.approvedBy ?? null,
5457
+ approved_at: approval?.approvedAt ?? null,
5458
+ approved_types: approvedTypes,
5459
+ approved_route_ids: approvedRoutes,
5460
+ spend_limit_credits: approval?.spendLimitCredits ?? null,
5461
+ notes: approval?.notes
5462
+ };
5463
+ return {
5464
+ packet_version: "campaign-builder-approval-packet.v1",
5465
+ read_only: true,
5466
+ no_spend: true,
5467
+ no_provider_writes: true,
5468
+ private_safe: true,
5469
+ approval_state: approvalState,
5470
+ plan_id: plan?.planId ?? null,
5471
+ campaign_name: plan?.campaignName ?? null,
5472
+ requested_approval_types: requestedTypes,
5473
+ missing_approval_types: uniqueStrings(missingTypes),
5474
+ spend_limit_credits_required: normalizedSpendLimit,
5475
+ route_ids_required: routeIdsRequired,
5476
+ required_approvals: (plan?.approvals ?? []).map((item) => compact({
5477
+ id: item.id,
5478
+ type: item.type,
5479
+ label: item.label,
5480
+ reason: item.reason,
5481
+ blocking: item.blocking,
5482
+ route_id: item.routeId,
5483
+ provider: item.provider,
5484
+ estimated_credits: item.estimatedCredits,
5485
+ status: missingIds.has(item.id) ? "missing" : "approved"
5486
+ })),
5487
+ approval_input: approvalInput,
5488
+ cli_handoff: plan ? {
5489
+ command: campaignBuilderApprovalCliHandoff(requestedTypes, routeIdsRequired, normalizedSpendLimit),
5490
+ safety: "Template only: run after explicit human approval identity, approved types, route scope, and spend limit are filled in."
5491
+ } : null,
5492
+ sdk_handoff: plan ? {
5493
+ method: "campaignBuilderAgent.buildApprovedPlan",
5494
+ approval: compact({
5495
+ approvedBy: approval?.approvedBy ?? "<operator-email>",
5496
+ approvedTypes: requestedTypes,
5497
+ approvedRouteIds: routeIdsRequired,
5498
+ spendLimitCredits: normalizedSpendLimit ?? void 0,
5499
+ notes: approval?.notes
5500
+ }),
5501
+ options: {
5502
+ idempotencyKey: "<set-before-execution>"
5503
+ },
5504
+ safety: "Call only after the approval packet is accepted; this helper does not execute writes or spend."
5505
+ } : null,
5506
+ mcp_handoffs: plan ? campaignBuilderApprovalMcpHandoffs(plan, requestedTypes, routeIdsRequired, normalizedSpendLimit) : [],
5507
+ 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."
5508
+ };
5509
+ }
5510
+ function campaignBuilderApprovalCliHandoff(requestedTypes, routeIds, spendLimit) {
5511
+ return [
5512
+ "signaliz campaign-agent build-approved --request-file campaign-request.json --confirm-launch",
5513
+ requestedTypes.length > 0 ? `--approved-types ${shellArg(requestedTypes.join(","))}` : "--approve-all",
5514
+ "--approved-by <operator-email>",
5515
+ spendLimit !== null ? `--spend-limit ${spendLimit}` : void 0,
5516
+ routeIds.length > 0 ? `--approved-route-ids ${shellArg(routeIds.join(","))}` : void 0
5517
+ ].filter(Boolean).join(" ");
5518
+ }
5519
+ function campaignBuilderApprovalMcpHandoffs(plan, requestedTypes, routeIds, spendLimit) {
5520
+ const approvalContext = compact({
5521
+ plan_id: plan.planId,
5522
+ approval_types: requestedTypes,
5523
+ approved_route_ids: routeIds,
5524
+ spend_limit_credits: spendLimit,
5525
+ approved_by: "<operator-email>"
5526
+ });
5527
+ return plan.mcpFlow.filter((step) => ["execution-prepare", "approved-build", "delivery-approval"].includes(step.id)).map((step) => ({
5528
+ id: step.id,
5529
+ tool: step.tool,
5530
+ arguments: step.id === "approved-build" ? compact({ ...step.arguments, approval_context: approvalContext }) : step.arguments,
5531
+ read_only: step.readOnly,
5532
+ approval_required: step.approvalRequired,
5533
+ safety: step.readOnly ? "Read-only readiness handoff; safe to inspect before approval." : "Write-capable handoff; run only after the approval packet is accepted."
5534
+ }));
5535
+ }
5536
+ function campaignBuilderActiveWorkNextMcpAction(input) {
5537
+ const campaignBuildRef = input.refs.find((ref) => ref.kind === "campaign_build" && ref.id);
5538
+ if (campaignBuildRef) {
5539
+ return {
5540
+ id: "check-campaign-build-status",
5541
+ tool: "get_campaign_build_status",
5542
+ arguments: { campaign_build_id: campaignBuildRef.id },
5543
+ read_only: true,
5544
+ approval_required: false,
5545
+ safety: "Read-only status check for the remembered campaign build; does not spend, write, export, send, or approve anything."
5546
+ };
5547
+ }
5548
+ const campaignRef = input.refs.find((ref) => ref.kind === "gtm_campaign" && ref.id);
5549
+ if (campaignRef) {
5550
+ return {
5551
+ id: "check-gtm-campaign-status",
5552
+ tool: "gtm_campaign_execution_status",
5553
+ arguments: { campaign_id: campaignRef.id },
5554
+ read_only: true,
5555
+ approval_required: false,
5556
+ safety: "Read-only campaign execution status check; does not start or mutate the campaign."
5557
+ };
5558
+ }
5559
+ const preferredStepId = input.state === "ready_for_dry_run" ? "dry-run-build" : input.state === "planning" || input.state === "blocked" ? "agency-campaign-build-plan" : null;
5560
+ const preferredStep = preferredStepId ? input.plan?.mcpFlow.find((step) => step.id === preferredStepId && step.readOnly) : void 0;
5561
+ if (preferredStep) return campaignBuilderMcpStepToActiveWorkAction(preferredStep, "Read-only continuation from the saved campaign-agent plan.");
5562
+ const readOnlyApprovalHandoff = input.approvalPacket.mcp_handoffs.find((handoff) => handoff.read_only && !handoff.approval_required);
5563
+ if (readOnlyApprovalHandoff) return readOnlyApprovalHandoff;
5564
+ const firstReadOnlyPlanStep = input.plan?.mcpFlow.find((step) => step.readOnly && !step.approvalRequired);
5565
+ return firstReadOnlyPlanStep ? campaignBuilderMcpStepToActiveWorkAction(firstReadOnlyPlanStep, "Read-only saved plan check; safe before approvals or writes.") : null;
5566
+ }
5567
+ function campaignBuilderMcpStepToActiveWorkAction(step, safety) {
5568
+ return {
5569
+ id: step.id,
5570
+ tool: step.tool,
5571
+ arguments: step.arguments,
5572
+ read_only: step.readOnly,
5573
+ approval_required: step.approvalRequired,
5574
+ safety
5575
+ };
5576
+ }
5577
+ function normalizeCampaignBuilderActiveWorkRefs(input) {
5578
+ const refs = [];
5579
+ const pushRef = (ref) => {
5580
+ if (!ref?.id) return;
5581
+ refs.push(ref);
5582
+ };
5583
+ pushRef(input.plan?.planId ? { kind: "plan", id: input.plan.planId, label: input.plan.campaignName, source: "plan" } : void 0);
5584
+ pushRef(input.remembered.planId ? { kind: "plan", id: input.remembered.planId, source: "remembered" } : void 0);
5585
+ pushRef(
5586
+ input.plan?.buildRequest.gtmCampaignId ?? input.remembered.gtmCampaignId ? {
5587
+ kind: "gtm_campaign",
5588
+ id: input.plan?.buildRequest.gtmCampaignId ?? input.remembered.gtmCampaignId,
5589
+ label: input.plan?.campaignName,
5590
+ source: input.plan?.buildRequest.gtmCampaignId ? "plan" : "remembered"
5591
+ } : void 0
5592
+ );
5593
+ const campaignBuildId = stringValue(input.lastResult.campaign_build_id ?? input.lastResult.campaignBuildId) ?? input.remembered.campaignBuildId;
5594
+ pushRef(campaignBuildId ? { kind: "campaign_build", id: campaignBuildId, status: activeWorkStatus(input.lastResult) ?? input.remembered.status, source: "result" } : void 0);
5595
+ pushRef(
5596
+ input.remembered.providerCampaignId ? {
5597
+ kind: "provider_campaign",
5598
+ id: input.remembered.providerCampaignId,
5599
+ label: input.remembered.provider,
5600
+ status: input.remembered.status,
5601
+ source: "remembered"
5602
+ } : void 0
5603
+ );
5604
+ for (const ref of input.remembered.refs ?? []) pushRef(ref);
5605
+ for (const ref of input.refs ?? []) pushRef(ref);
5606
+ const seen = /* @__PURE__ */ new Set();
5607
+ return refs.filter((ref) => {
5608
+ const key = `${ref.kind}:${ref.id}`;
5609
+ if (seen.has(key)) return false;
5610
+ seen.add(key);
5611
+ return true;
5612
+ });
5613
+ }
5614
+ function inferCampaignBuilderActiveWorkState(input) {
5615
+ if (input.blockers.length > 0) return "blocked";
5616
+ const status = activeWorkStatus(input.lastResult) ?? input.remembered.status;
5617
+ const normalized = status?.toLowerCase();
5618
+ if (normalized && ["completed", "complete", "succeeded", "success", "done"].includes(normalized)) return "completed";
5619
+ if (normalized && ["queued", "running", "in_progress", "processing", "pending"].includes(normalized)) return "queued_or_running";
5620
+ const dryRunComplete = input.lastResult.dry_run === true || input.lastResult.dryRun === true || normalized === "dry_run";
5621
+ if (dryRunComplete) return input.missingApprovals.length > 0 ? "needs_approval" : "dry_run_complete";
5622
+ if (input.missingApprovals.length > 0) return "needs_approval";
5623
+ if (input.readiness?.summary.ready === true) return "ready_for_dry_run";
5624
+ if (input.plan) return "planning";
5625
+ return "unknown";
5626
+ }
5627
+ function campaignBuilderActiveWorkApprovalBoundary(missingApprovals, state) {
5628
+ if (missingApprovals.length > 0) {
5629
+ return `Blocked before spend, provider writes, delivery, or launch until approval covers: ${uniqueStrings(missingApprovals).join(", ")}.`;
5630
+ }
5631
+ if (state === "dry_run_complete" || state === "ready_for_dry_run") {
5632
+ return "Read-only planning and dry-runs are allowed; spend, provider writes, sender loading, delivery, and launch still require explicit approval.";
5633
+ }
5634
+ return "This packet is read-only and does not authorize spend, provider writes, sender loading, delivery, or launch.";
5635
+ }
5636
+ function campaignBuilderActiveWorkNextAction(state, blockers, missingApprovals, refs) {
5637
+ if (blockers.length > 0) return `Resolve blocker: ${blockers[0]}`;
5638
+ if (missingApprovals.length > 0) return `Ask the operator to approve ${uniqueStrings(missingApprovals).join(", ")} before any write or spend action.`;
5639
+ if (state === "ready_for_dry_run") return "Run or inspect the read-only campaign dry-run, then return the receipt for approval review.";
5640
+ if (state === "dry_run_complete") return "Review the dry-run receipt and collect explicit launch or delivery approval before any write action.";
5641
+ if (state === "queued_or_running") return "Poll the remembered campaign build or provider job status; do not start a duplicate job.";
5642
+ if (state === "completed") return "Review final artifacts and delivery gates before export, sync, sender load, or launch.";
5643
+ if (refs.length > 0) return "Use the remembered refs to fetch current read-only status before asking the operator for IDs.";
5644
+ return "Create or refresh a campaign-builder plan before taking any spendful or write action.";
5645
+ }
5646
+ function campaignBuilderActiveWorkConversationStarters(input) {
5647
+ const starters = [];
5648
+ if (input.blockers.length > 0) {
5649
+ starters.push(
5650
+ `I found a blocker: ${input.blockers[0]}. Want me to stay in read-only diagnostics and prepare the fix path?`
5651
+ );
5652
+ }
5653
+ if (input.missingApprovals.length > 0) {
5654
+ starters.push(
5655
+ `You still owe approval for ${uniqueStrings(input.missingApprovals).join(", ")}. Want the exact approval packet before anything writes or spends?`
5656
+ );
5657
+ }
5658
+ if (input.state === "queued_or_running") {
5659
+ const buildRef = input.refs.find((ref) => ref.kind === "campaign_build");
5660
+ 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.");
5661
+ }
5662
+ if (input.state === "dry_run_complete") {
5663
+ starters.push("The dry run is complete. Want me to summarize blockers, artifacts, and the approval handoff?");
5664
+ }
5665
+ if (input.state === "ready_for_dry_run") {
5666
+ starters.push("Everything is ready for a safe dry run. Want me to run the no-spend preview first?");
5667
+ }
5668
+ if (input.state === "completed") {
5669
+ starters.push("The build looks complete. Want me to review rows, artifacts, and delivery gates before export or send approval?");
5670
+ }
5671
+ starters.push(`Next safe action: ${input.nextSafeAction}`);
5672
+ return uniqueStrings(starters).slice(0, 4);
5673
+ }
5674
+ function createCampaignBuilderOpenAiAgentHandoff(input) {
5675
+ 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.")) ?? [];
5676
+ const routeSetupActions = readOnlyPlanActions.filter(
5677
+ (action) => action.tool === "gtm_provider_recipe_prepare" || action.tool === "gtm_provider_route_activate"
5678
+ );
5679
+ const safeReadActions = uniqueCampaignBuilderActiveWorkActions([
5680
+ input.nextSafeMcpAction,
5681
+ ...routeSetupActions,
5682
+ ...readOnlyPlanActions
5683
+ ]);
5684
+ const approvalGatedActions = uniqueCampaignBuilderActiveWorkActions([
5685
+ ...input.approvalPacket.mcp_handoffs.filter((action) => action.approval_required || !action.read_only),
5686
+ ...input.plan?.mcpFlow.filter((step) => step.approvalRequired || !step.readOnly).map((step) => campaignBuilderMcpStepToActiveWorkAction(step, "Approval-gated Signaliz MCP continuation for the saved campaign-agent plan.")) ?? []
5687
+ ]);
5688
+ const approvalTypes = uniqueStrings([
5689
+ ...input.missingApprovals,
5690
+ ...input.approvalPacket.requested_approval_types
5691
+ ]);
5692
+ return {
5693
+ packet_version: "campaign-builder-openai-agent-handoff.v1",
5694
+ surface: "openai_agents_sdk",
5695
+ agent: {
5696
+ name: "Signaliz GTM Campaign Agent",
5697
+ handoff_description: "Owns safe, conversational GTM campaign planning and continuation across Signaliz MCP, SDK, attachments, URLs, customer tools, approvals, and sender/export gates.",
5698
+ instructions: [
5699
+ "Be warm, direct, and expert-level GTM. Translate the current state into plain operator language before proposing work.",
5700
+ "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.",
5701
+ "Use attachment_summary, url_summary, and operator_notes as first-class planning evidence, while honoring the evidence privacy_boundary and withheld raw fields.",
5702
+ "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.",
5703
+ "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.",
5704
+ "When approval is missing, show the approval_packet and the exact missing approval scope before any side-effecting step."
5705
+ ],
5706
+ output_contract: [
5707
+ "current_state: one concise sentence describing where the campaign work stands.",
5708
+ "evidence_used: attachment, URL, memory, route, or integration evidence used without exposing private raw rows or secrets.",
5709
+ "next_safe_mcp_action: the read-only Signaliz MCP action to run next, or null when none is safe.",
5710
+ "approval_boundary: the human approval scope required before spend, provider writes, delivery, export, sender load, or launch.",
5711
+ "operator_reply: conversational GTM guidance with blockers, risks, and the safest next move."
5712
+ ]
5713
+ },
5714
+ context: {
5715
+ state: input.state,
5716
+ summary: input.summary,
5717
+ refs: input.refs,
5718
+ blockers: input.blockers,
5719
+ warnings: input.warnings,
5720
+ evidence_context: input.plan?.evidence ?? null,
5721
+ approval_packet: input.approvalPacket,
5722
+ approval_boundary: input.approvalBoundary,
5723
+ next_safe_action: input.nextSafeAction,
5724
+ conversation_starters: input.conversationStarters
5725
+ },
5726
+ tools: {
5727
+ mcp_server_label: "signaliz",
5728
+ remote_mcp_compatible: true,
5729
+ next_safe_mcp_action: input.nextSafeMcpAction,
5730
+ safe_read_tools: safeReadActions.map(
5731
+ (action) => campaignBuilderOpenAiAgentTool(action, "Safe read-only Signaliz MCP action available before approval.")
5732
+ ),
5733
+ approval_gated_tools: approvalGatedActions.map(
5734
+ (action) => campaignBuilderOpenAiAgentTool(action, "Requires explicit human review before execution.")
5735
+ )
5736
+ },
5737
+ guardrails: {
5738
+ input: [
5739
+ "Reject or pause requests to expose raw private attachment text, raw rows, leads, emails, provider payloads, copy bodies, API keys, or secrets.",
5740
+ "Pause when the user asks to spend credits, write to a provider, export, load a sender, deliver, or launch without explicit approval scope."
5741
+ ],
5742
+ tool: [
5743
+ "Read-only tools may run when they match safe_read_tools and their arguments match this packet.",
5744
+ "Any tool with approval_required=true or read_only=false requires human review and the approval_packet scope first."
5745
+ ],
5746
+ human_review_required_for: approvalTypes,
5747
+ 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."
5748
+ }
5749
+ };
5750
+ }
5751
+ function campaignBuilderOpenAiAgentTool(action, purpose) {
5752
+ return {
5753
+ id: action.id,
5754
+ type: "mcp",
5755
+ server_label: "signaliz",
5756
+ name: action.tool,
5757
+ arguments: action.arguments,
5758
+ read_only: action.read_only,
5759
+ approval_required: action.approval_required,
5760
+ purpose,
5761
+ safety: action.safety
5762
+ };
5763
+ }
5764
+ function uniqueCampaignBuilderActiveWorkActions(actions) {
5765
+ const seen = /* @__PURE__ */ new Set();
5766
+ const unique = [];
5767
+ for (const action of actions) {
5768
+ if (!action) continue;
5769
+ const key = `${action.id}:${action.tool}`;
5770
+ if (seen.has(key)) continue;
5771
+ seen.add(key);
5772
+ unique.push(action);
5773
+ }
5774
+ return unique;
5775
+ }
5776
+ function activeWorkStatus(record) {
5777
+ return stringValue(record.status ?? record.state ?? record.phase);
5778
+ }
4459
5779
  function campaignBuilderBuiltInForRoute(route) {
4460
5780
  return Object.entries(CAMPAIGN_BUILDER_BUILT_IN_ROUTE_IDS).find(([, routeId]) => route.id === routeId)?.[0];
4461
5781
  }
@@ -7252,6 +8572,36 @@ function recordsFromParams(params) {
7252
8572
  if (params.input) return { input: params.input };
7253
8573
  return { input: {} };
7254
8574
  }
8575
+ function toFusionConfig(params) {
8576
+ return {
8577
+ system_prompt: params.systemPrompt || params.system_prompt,
8578
+ user_template: params.userTemplate || params.user_template || params.prompt,
8579
+ preset: params.preset,
8580
+ temperature: params.temperature,
8581
+ max_tool_calls: params.maxToolCalls || params.max_tool_calls,
8582
+ max_tokens: params.maxTokens || params.max_tokens,
8583
+ timeout_ms: params.timeoutMs || params.timeout_ms,
8584
+ output_fields: params.outputFields || params.output_fields || [{ name: "response", type: "text", description: "Structured Fusion enrichment result" }]
8585
+ };
8586
+ }
8587
+ function toFusionSpendControls(params) {
8588
+ const controls = {};
8589
+ const dryRun = params.dryRun ?? params.dry_run;
8590
+ const confirmSpend = params.confirmSpend ?? params.confirm_spend;
8591
+ const maxCredits = params.maxCredits ?? params.max_credits;
8592
+ const maxCostUsd = params.maxCostUsd ?? params.max_cost_usd;
8593
+ if (dryRun !== void 0) controls.dry_run = dryRun;
8594
+ if (confirmSpend !== void 0) controls.confirm_spend = confirmSpend;
8595
+ if (maxCredits !== void 0) controls.max_credits = maxCredits;
8596
+ if (maxCostUsd !== void 0) controls.max_cost_usd = maxCostUsd;
8597
+ return controls;
8598
+ }
8599
+ function fusionRecordsFromParams(params) {
8600
+ const records = params.records || params.inputs;
8601
+ if (records?.length) return { records };
8602
+ if (params.input) return { input: params.input };
8603
+ return { input: {} };
8604
+ }
7255
8605
  var Ai = class {
7256
8606
  constructor(client) {
7257
8607
  this.client = client;
@@ -7285,6 +8635,33 @@ var Ai = class {
7285
8635
  raw: data
7286
8636
  };
7287
8637
  }
8638
+ /**
8639
+ * Plan or run experimental AI Enrichment Fusion with explicit spend controls.
8640
+ */
8641
+ async fusion(params) {
8642
+ if (!params.prompt && !params.userTemplate && !params.user_template) {
8643
+ throw new Error("prompt or userTemplate is required.");
8644
+ }
8645
+ const data = await this.client.post("ai-enrichment-fusion", {
8646
+ ...fusionRecordsFromParams(params),
8647
+ config: toFusionConfig(params),
8648
+ ...toFusionSpendControls(params)
8649
+ });
8650
+ return {
8651
+ success: data.success ?? false,
8652
+ capability: data.capability ?? "ai_enrichment_fusion",
8653
+ displayName: data.display_name ?? "AI Enrichment Fusion",
8654
+ results: data.results ?? [],
8655
+ creditsUsed: data.total_credits_used ?? data.credits_used ?? 0,
8656
+ model: data.model,
8657
+ fusion: data.fusion,
8658
+ tokensUsed: data.tokens_used ?? 0,
8659
+ costUsd: data.cost_usd ?? 0,
8660
+ costSummary: data.cost_summary,
8661
+ billingMetadata: data.billing_metadata,
8662
+ raw: data
8663
+ };
8664
+ }
7288
8665
  };
7289
8666
 
7290
8667
  // src/resources/gtm-kernel.ts
@@ -7638,6 +9015,16 @@ var GtmKernel = class {
7638
9015
  min_privacy_k: input.minPrivacyK
7639
9016
  });
7640
9017
  }
9018
+ /** Queue a dry-run-first bridge from imported historical feedback to attribution-only Campaign Build surrogates. */
9019
+ async runHistoricalCampaignBuildBridge(input = {}) {
9020
+ return this.callMcp("gtm_historical_campaign_build_bridge_run", {
9021
+ dry_run: input.dryRun,
9022
+ write_approved: input.writeApproved,
9023
+ max_events: input.maxEvents,
9024
+ max_campaigns: input.maxCampaigns,
9025
+ min_feedback_events: input.minFeedbackEvents
9026
+ });
9027
+ }
7641
9028
  /** Prepare a provider-agnostic feedback webhook URL for Smartlead, HeyReach, Airbyte, or custom sender events. */
7642
9029
  async prepareFeedbackWebhook(input) {
7643
9030
  return this.callMcp("gtm_feedback_webhook_prepare", {
@@ -7814,6 +9201,24 @@ var GtmKernel = class {
7814
9201
  limit: input.limit
7815
9202
  });
7816
9203
  }
9204
+ /** Predict booked-meeting likelihood for uploaded, inline, or MCP-sourced lead rows without persisting raw rows. */
9205
+ async predictMeetingLikelihood(input) {
9206
+ return this.callMcp("gtm_predict_meeting_likelihood", {
9207
+ lead_rows: input.leadRows,
9208
+ input_source: input.inputSource,
9209
+ campaign_id: input.campaignId,
9210
+ campaign_build_id: input.campaignBuildId,
9211
+ days: input.days,
9212
+ feedback_limit: input.feedbackLimit,
9213
+ include_features: input.includeFeatures,
9214
+ include_global: input.includeGlobal,
9215
+ min_feedback_events: input.minFeedbackEvents,
9216
+ min_historical_feature_sample_size: input.minHistoricalFeatureSampleSize,
9217
+ min_workspace_count: input.minWorkspaceCount,
9218
+ min_privacy_k: input.minPrivacyK,
9219
+ limit: input.limit
9220
+ });
9221
+ }
7817
9222
  /** Rank provider-chain, ICP, source, copy, and sequence failure patterns without writing raw lead data. */
7818
9223
  async failurePatterns(options = {}) {
7819
9224
  return this.callMcp("gtm_brain_failure_patterns", {
@@ -8429,6 +9834,70 @@ function compact2(value) {
8429
9834
  return out;
8430
9835
  }
8431
9836
 
9837
+ // src/resources/public-ground-truth.ts
9838
+ var PublicGroundTruth = class {
9839
+ constructor(client) {
9840
+ this.client = client;
9841
+ }
9842
+ /**
9843
+ * Search the public-ground-truth cache by company/name/domain/native ID,
9844
+ * or by plain-language industry + location.
9845
+ * Results use the same snake_case wire shape returned by the hosted MCP API.
9846
+ */
9847
+ async search(params) {
9848
+ return this.client.mcp("tools/call", {
9849
+ name: "search_public_ground_truth",
9850
+ arguments: {
9851
+ query: params.query ?? params.search,
9852
+ industry: params.industry,
9853
+ location: params.location,
9854
+ source_id: params.sourceId ?? params.source_id,
9855
+ entity_type: params.entityType ?? params.entity_type,
9856
+ native_id: params.nativeId ?? params.native_id,
9857
+ entity_name: params.entityName ?? params.entity_name,
9858
+ domain: params.domain,
9859
+ website_url: params.websiteUrl ?? params.website_url,
9860
+ phone: params.phone,
9861
+ email: params.email,
9862
+ city: params.city,
9863
+ state: params.state,
9864
+ postal_code: params.postalCode ?? params.postal_code,
9865
+ country: params.country,
9866
+ source_url: params.sourceUrl ?? params.source_url,
9867
+ join_keys: params.joinKeys ?? params.join_keys,
9868
+ row_data: params.rowData ?? params.row_data,
9869
+ naics: params.naics,
9870
+ industry_code: params.industryCode ?? params.industry_code,
9871
+ industry_type: params.industryType ?? params.industry_type,
9872
+ year: params.year,
9873
+ quarter: params.quarter,
9874
+ area_fips: params.areaFips ?? params.area_fips,
9875
+ geo_id: params.geoId ?? params.geo_id,
9876
+ own_code: params.ownCode ?? params.own_code,
9877
+ source_updated_after: params.sourceUpdatedAfter ?? params.source_updated_after,
9878
+ source_updated_before: params.sourceUpdatedBefore ?? params.source_updated_before,
9879
+ observed_after: params.observedAfter ?? params.observed_after,
9880
+ observed_before: params.observedBefore ?? params.observed_before,
9881
+ limit: params.limit,
9882
+ offset: params.offset
9883
+ }
9884
+ });
9885
+ }
9886
+ /**
9887
+ * List available public-ground-truth cache sources and their join-key metadata.
9888
+ */
9889
+ async listSources(params = {}) {
9890
+ return this.client.mcp("tools/call", {
9891
+ name: "list_public_ground_truth_sources",
9892
+ arguments: {
9893
+ query: params.query ?? params.search,
9894
+ status: params.status,
9895
+ limit: params.limit
9896
+ }
9897
+ });
9898
+ }
9899
+ };
9900
+
8432
9901
  // src/index.ts
8433
9902
  var MCP_TOOL_CACHE_TTL_MS = 3e4;
8434
9903
  function inferMcpToolCategory(toolName) {
@@ -8471,6 +9940,7 @@ function inferMcpToolCategory(toolName) {
8471
9940
  }
8472
9941
  if (toolName.includes("icp")) return "icp";
8473
9942
  if (toolName.startsWith("ai_clean_")) return "data_cleaning";
9943
+ if (toolName.includes("public_ground_truth")) return "public_data";
8474
9944
  if (toolName.includes("system") || toolName.includes("workflow")) return "automation";
8475
9945
  if (toolName.includes("agent") || toolName.includes("platform_health") || toolName === "discover_capabilities") return "observability";
8476
9946
  return void 0;
@@ -8522,6 +9992,7 @@ var Signaliz = class {
8522
9992
  this.ops = new Ops(this.client);
8523
9993
  this.ai = new Ai(this.client);
8524
9994
  this.gtm = new GtmKernel(this.client);
9995
+ this.publicGroundTruth = new PublicGroundTruth(this.client);
8525
9996
  }
8526
9997
  /** Get current workspace info including credits */
8527
9998
  async getWorkspace() {
@@ -8627,6 +10098,7 @@ export {
8627
10098
  CampaignBuilderAgent,
8628
10099
  createCampaignBuilderAgentPlan,
8629
10100
  createCampaignBuilderReadiness,
10101
+ createCampaignBuilderActiveWorkContext,
8630
10102
  createCampaignBuilderProofReceipt,
8631
10103
  getMissingCampaignBuilderApprovals,
8632
10104
  createCampaignBuilderApproval,
@@ -8644,5 +10116,6 @@ export {
8644
10116
  Ops,
8645
10117
  Ai,
8646
10118
  GtmKernel,
10119
+ PublicGroundTruth,
8647
10120
  Signaliz
8648
10121
  };