"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/relationship-cadence/index.ts
var relationship_cadence_exports = {};
__export(relationship_cadence_exports, {
  ClaudeDraftSuggester: () => ClaudeDraftSuggester,
  ClaudeInterventionDrafter: () => ClaudeInterventionDrafter,
  DEFAULT_POLICIES: () => DEFAULT_POLICIES,
  EmailDrafter: () => EmailDrafter,
  InMemoryCommitmentTracker: () => InMemoryCommitmentTracker,
  InMemoryInteractionHistory: () => InMemoryInteractionHistory,
  InMemoryPersonalFacts: () => InMemoryPersonalFacts,
  InMemoryRelationshipTracker: () => InMemoryRelationshipTracker,
  LINKEDIN_CHAR_LIMIT: () => LINKEDIN_CHAR_LIMIT,
  LinkedInDrafter: () => LinkedInDrafter,
  Nyx: () => Nyx,
  PolicyInterventionRecommender: () => PolicyInterventionRecommender,
  PolicyNextTouchpointRecommender: () => PolicyNextTouchpointRecommender,
  SMS_CHAR_LIMIT: () => SMS_CHAR_LIMIT,
  SlackDrafter: () => SlackDrafter,
  TextDrafter: () => TextDrafter,
  cadenceDetector: () => cadenceDetector,
  computeHeatScore: () => computeHeatScore,
  detectEscalationNeeded: () => detectEscalationNeeded,
  detectFrequencyShift: () => detectFrequencyShift,
  detectGoingCold: () => detectGoingCold,
  detectUnilateralSilence: () => detectUnilateralSilence,
  escalationNeededDetector: () => escalationNeededDetector,
  frequencyShiftDetector: () => frequencyShiftDetector,
  getCadencePolicy: () => getCadencePolicy,
  makeTouchpoint: () => makeTouchpoint,
  runAllCadenceDetectors: () => runAllCadenceDetectors,
  silenceDetector: () => silenceDetector
});
module.exports = __toCommonJS(relationship_cadence_exports);

// src/relationship-cadence/relationship-tracker.ts
function makeContactId() {
  return `cp_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
}
var InMemoryRelationshipTracker = class {
  store = /* @__PURE__ */ new Map();
  operatorStore(operatorId) {
    let map = this.store.get(operatorId);
    if (!map) {
      map = /* @__PURE__ */ new Map();
      this.store.set(operatorId, map);
    }
    return map;
  }
  async recordContact(operatorId, relationshipId, contact) {
    const store = this.operatorStore(operatorId);
    let rel = store.get(relationshipId);
    if (!rel) {
      rel = { id: relationshipId, displayName: relationshipId, contacts: [] };
      store.set(relationshipId, rel);
    }
    const point = { ...contact, id: makeContactId(), relationshipId };
    rel.contacts.push(point);
    rel.contacts.sort((a, b) => a.occurredAt.getTime() - b.occurredAt.getTime());
    return point;
  }
  async getRelationship(operatorId, relationshipId) {
    return this.operatorStore(operatorId).get(relationshipId) ?? null;
  }
  async upsertRelationship(operatorId, id, displayName, meta) {
    const store = this.operatorStore(operatorId);
    const existing = store.get(id);
    const rel = {
      contacts: [],
      ...existing,
      id,
      displayName,
      ...meta !== void 0 ? { meta } : {}
    };
    store.set(id, rel);
    return rel;
  }
  async listRelationships(operatorId) {
    return Array.from(this.operatorStore(operatorId).values());
  }
};

// src/relationship-cadence/heat-scorer.ts
function daysBetween(past, now) {
  return Math.max(0, (now.getTime() - past.getTime()) / (1e3 * 60 * 60 * 24));
}
function heatLevel(days2, thresholds) {
  if (days2 <= thresholds.coolAfterDays) return "warm";
  if (days2 <= thresholds.coldAfterDays) return "cool";
  return "cold";
}
function computeHeatScore(relationship, policy, phase = "nurture", now = /* @__PURE__ */ new Date()) {
  const thresholds = resolveThresholds(policy, phase);
  if (relationship.contacts.length === 0) {
    return {
      level: "cold",
      score: 0,
      daysSinceLastContact: Infinity,
      rationale: "No contact history recorded.",
      computedAt: now
    };
  }
  const lastContact = relationship.contacts.at(-1);
  const daysSinceLastContact = daysBetween(lastContact.occurredAt, now);
  const recencyScore = Math.max(0, 1 - daysSinceLastContact / thresholds.coldAfterDays);
  const windowStart = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1e3);
  const recent = relationship.contacts.filter((c) => c.occurredAt >= windowStart);
  const inboundCount = recent.filter((c) => c.direction === "inbound").length;
  const reciprocityScore = recent.length > 0 ? inboundCount / recent.length : 0;
  const intensityScore = Math.min(recent.length / 5, 1);
  const score = Math.min(
    Math.max(recencyScore * 0.6 + reciprocityScore * 0.25 + intensityScore * 0.15, 0),
    1
  );
  const level = heatLevel(daysSinceLastContact, thresholds);
  const rationale = buildRationale(level, daysSinceLastContact, thresholds, inboundCount, recent.length);
  return { level, score, daysSinceLastContact, rationale, computedAt: now };
}
function resolveThresholds(policy, phase) {
  if (phase === "engagement_active") return policy.engagementActive;
  if (phase === "maintenance") return policy.maintenance;
  return policy.nurture;
}
function buildRationale(level, days2, thresholds, inboundCount, recentTotal) {
  const daysStr = days2 === Infinity ? "never" : `${Math.round(days2)}d`;
  const recip = recentTotal > 0 ? `${inboundCount}/${recentTotal} recent contacts inbound` : "no recent contacts";
  if (level === "warm") {
    return `Last contact ${daysStr} ago (threshold ${thresholds.coolAfterDays}d). ${recip}.`;
  }
  if (level === "cool") {
    return `Last contact ${daysStr} ago \u2014 cooling (>${thresholds.coolAfterDays}d). ${recip}.`;
  }
  return `Cold \u2014 last contact ${daysStr} ago (>${thresholds.coldAfterDays}d). ${recip}.`;
}

// src/relationship-cadence/cadence-policy.ts
var STANDARD_CHANNELS = ["email", "linkedin", "phone"];
var SERVICES_CHANNELS = ["email", "phone", "in_person"];
var DEFAULT_POLICIES = {
  saas_b2b: {
    vertical: "saas_b2b",
    engagementActive: { coolAfterDays: 3, coldAfterDays: 7, minDaysBetweenTouches: 2 },
    nurture: { coolAfterDays: 14, coldAfterDays: 30, minDaysBetweenTouches: 7 },
    maintenance: { coolAfterDays: 45, coldAfterDays: 90, minDaysBetweenTouches: 21 },
    preferredChannels: STANDARD_CHANNELS
  },
  professional_services: {
    vertical: "professional_services",
    engagementActive: { coolAfterDays: 5, coldAfterDays: 14, minDaysBetweenTouches: 3 },
    nurture: { coolAfterDays: 21, coldAfterDays: 45, minDaysBetweenTouches: 10 },
    maintenance: { coolAfterDays: 60, coldAfterDays: 120, minDaysBetweenTouches: 30 },
    preferredChannels: SERVICES_CHANNELS
  },
  financial_advisory: {
    vertical: "financial_advisory",
    engagementActive: { coolAfterDays: 7, coldAfterDays: 21, minDaysBetweenTouches: 4 },
    nurture: { coolAfterDays: 30, coldAfterDays: 60, minDaysBetweenTouches: 14 },
    maintenance: { coolAfterDays: 90, coldAfterDays: 180, minDaysBetweenTouches: 45 },
    preferredChannels: SERVICES_CHANNELS
  },
  recruiting: {
    vertical: "recruiting",
    engagementActive: { coolAfterDays: 2, coldAfterDays: 5, minDaysBetweenTouches: 1 },
    nurture: { coolAfterDays: 10, coldAfterDays: 21, minDaysBetweenTouches: 5 },
    maintenance: { coolAfterDays: 30, coldAfterDays: 60, minDaysBetweenTouches: 14 },
    preferredChannels: ["email", "linkedin", "phone"]
  },
  general: {
    vertical: "general",
    engagementActive: { coolAfterDays: 5, coldAfterDays: 14, minDaysBetweenTouches: 3 },
    nurture: { coolAfterDays: 21, coldAfterDays: 45, minDaysBetweenTouches: 10 },
    maintenance: { coolAfterDays: 60, coldAfterDays: 120, minDaysBetweenTouches: 30 },
    preferredChannels: STANDARD_CHANNELS
  }
};
function getCadencePolicy(vertical) {
  const policy = DEFAULT_POLICIES[vertical];
  if (!policy) throw new Error(`No cadence policy defined for vertical: ${vertical}`);
  return policy;
}

// src/relationship-cadence/intervention-recommender.ts
var URGENCY_ORDER = { high: 0, medium: 1, low: 2 };
var PolicyInterventionRecommender = class {
  calibrationLearner;
  scoreThreshold;
  maxRecommendations;
  constructor(config = {}) {
    this.calibrationLearner = config.calibrationLearner;
    this.scoreThreshold = config.scoreThreshold ?? 0.5;
    this.maxRecommendations = config.maxRecommendations ?? 10;
  }
  async recommend(operatorId, relationships, policy, phase = "nurture", now = /* @__PURE__ */ new Date()) {
    const multiplier = this.calibrationLearner ? await this.calibrationLearner.getCalibration(operatorId, "relationship") : 1;
    const effectiveThreshold = Math.min(this.scoreThreshold * multiplier, 1);
    const recommendations = [];
    for (const rel of relationships) {
      if (recommendations.length >= this.maxRecommendations) break;
      const heat = computeHeatScore(rel, policy, phase, now);
      if (heat.score >= effectiveThreshold) continue;
      recommendations.push({
        relationshipId: rel.id,
        displayName: rel.displayName,
        reason: resolveReason(rel, heat, now),
        urgency: resolveUrgency(heat),
        suggestedChannel: policy.preferredChannels[0] ?? "email",
        heatScore: heat,
        rationale: heat.rationale,
        recommendedAt: now
      });
    }
    return recommendations.sort(
      (a, b) => URGENCY_ORDER[a.urgency] - URGENCY_ORDER[b.urgency]
    );
  }
};
function resolveReason(rel, heat, now) {
  if (rel.contacts.length === 0) return "relationship_going_cold";
  const windowStart = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1e3);
  const recent = rel.contacts.filter((c) => c.occurredAt >= windowStart);
  if (recent.length > 0) {
    const lastOutbound2 = findLastOutbound(recent);
    if (lastOutbound2) {
      const hasInboundAfter = recent.some(
        (c) => c.direction === "inbound" && c.occurredAt > lastOutbound2.occurredAt
      );
      if (!hasInboundAfter) return "no_response_to_outbound";
    }
    const inboundRatio = recent.filter((c) => c.direction === "inbound").length / recent.length;
    if (inboundRatio > 0.8) return "reciprocity_imbalance";
  }
  if (heat.level === "cold") return "relationship_going_cold";
  if (heat.level === "cool") return "long_gap_since_meaningful_contact";
  return "reengagement_opportunity";
}
function findLastOutbound(contacts) {
  return [...contacts].reverse().find((c) => c.direction === "outbound");
}
function resolveUrgency(heat) {
  if (heat.level === "cold") return "high";
  if (heat.level === "cool") return "medium";
  return "low";
}

// src/personality/types.ts
var import_zod = require("zod");
var TONE_REGISTERS = [
  "formal",
  "professional",
  "casual",
  "warm"
];
var TONE_ENERGIES = ["calm", "measured", "energetic"];
var TONE_HUMORS = ["none", "subtle", "dry", "warm"];
var TONE_DIRECTNESS = [
  "diplomatic",
  "balanced",
  "direct",
  "blunt"
];
var SENTENCE_LENGTHS = ["short", "medium", "mixed"];
var EMOJI_POLICIES = ["never", "sparingly", "situational"];
var SELF_REFERENCES = ["first-person", "never-self-refer"];
var semverRegex = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
var personalityToneSchema = import_zod.z.object({
  register: import_zod.z.enum(TONE_REGISTERS),
  energy: import_zod.z.enum(TONE_ENERGIES),
  humor: import_zod.z.enum(TONE_HUMORS),
  directness: import_zod.z.enum(TONE_DIRECTNESS)
});
var personalityStyleSchema = import_zod.z.object({
  sentenceLength: import_zod.z.enum(SENTENCE_LENGTHS),
  listsAllowed: import_zod.z.boolean(),
  emojiPolicy: import_zod.z.enum(EMOJI_POLICIES),
  addressTheUserAs: import_zod.z.string().min(1).max(120).optional(),
  selfReference: import_zod.z.enum(SELF_REFERENCES).optional()
});
var partialPersonalitySchema = import_zod.z.object({
  tone: personalityToneSchema.partial().optional(),
  style: personalityStyleSchema.partial().optional(),
  preferredPhrasing: import_zod.z.array(import_zod.z.string()).optional(),
  avoidPhrasing: import_zod.z.array(import_zod.z.string()).optional(),
  signaturePhrases: import_zod.z.array(import_zod.z.string()).optional(),
  doList: import_zod.z.array(import_zod.z.string()).optional(),
  dontList: import_zod.z.array(import_zod.z.string()).optional()
});
var personalityDefinitionSchema = import_zod.z.object({
  name: import_zod.z.string().min(1).max(120),
  role: import_zod.z.string().min(1).max(240),
  about: import_zod.z.string().min(1).max(2e3),
  tone: personalityToneSchema,
  style: personalityStyleSchema,
  preferredPhrasing: import_zod.z.array(import_zod.z.string()),
  avoidPhrasing: import_zod.z.array(import_zod.z.string()),
  signaturePhrases: import_zod.z.array(import_zod.z.string()).optional(),
  doList: import_zod.z.array(import_zod.z.string()),
  dontList: import_zod.z.array(import_zod.z.string()),
  channels: import_zod.z.object({
    telegram: partialPersonalitySchema.optional(),
    email: partialPersonalitySchema.optional(),
    dashboard: partialPersonalitySchema.optional(),
    voice: partialPersonalitySchema.optional()
  }).optional(),
  version: import_zod.z.string().regex(semverRegex, "version must be valid semver"),
  updatedAt: import_zod.z.date()
});

// src/personality/calibration.ts
function applyCalibrationsToPersonality(personality, recentCalibrations) {
  if (recentCalibrations.length === 0) {
    return { ...personality, tone: { ...personality.tone }, style: { ...personality.style } };
  }
  const ordered = [...recentCalibrations].sort(
    (a, b) => a.createdAt.getTime() - b.createdAt.getTime()
  );
  let register = personality.tone.register;
  let energy = personality.tone.energy;
  let humor = personality.tone.humor;
  let directness = personality.tone.directness;
  let sentenceLength = personality.style.sentenceLength;
  let emojiPolicy = personality.style.emojiPolicy;
  for (const cal of ordered) {
    const { dimension, direction } = cal.payload;
    switch (dimension) {
      case "register":
        register = step(TONE_REGISTERS, register, direction);
        break;
      case "energy":
        energy = step(TONE_ENERGIES, energy, direction);
        break;
      case "humor":
        humor = step(TONE_HUMORS, humor, direction);
        break;
      case "directness":
        directness = step(TONE_DIRECTNESS, directness, direction);
        break;
      case "sentenceLength":
      case "verbosity":
        sentenceLength = step(SENTENCE_LENGTHS, sentenceLength, direction);
        break;
      case "emojiPolicy":
        emojiPolicy = step(EMOJI_POLICIES, emojiPolicy, direction);
        break;
    }
  }
  return {
    ...personality,
    tone: { register, energy, humor, directness },
    style: { ...personality.style, sentenceLength, emojiPolicy }
  };
}
function step(scale, current, direction) {
  const idx = scale.indexOf(current);
  if (idx === -1) return current;
  const delta = directionToDelta(direction);
  const next = Math.max(0, Math.min(scale.length - 1, idx + delta));
  return scale[next];
}
function directionToDelta(direction) {
  switch (direction) {
    case "less":
    case "softer":
      return -1;
    case "more":
    case "sharper":
      return 1;
  }
}

// src/personality/channels.ts
function applyChannelOverride(base, channel) {
  const override = base.channels?.[channel];
  const merged = {
    name: base.name,
    role: base.role,
    about: base.about,
    tone: { ...base.tone, ...override?.tone ?? {} },
    style: { ...base.style, ...override?.style ?? {} },
    preferredPhrasing: mergeList(
      base.preferredPhrasing,
      override?.preferredPhrasing
    ),
    avoidPhrasing: mergeList(base.avoidPhrasing, override?.avoidPhrasing),
    signaturePhrases: mergeOptionalList(
      base.signaturePhrases,
      override?.signaturePhrases
    ),
    doList: mergeList(base.doList, override?.doList),
    dontList: mergeList(base.dontList, override?.dontList),
    version: base.version,
    updatedAt: base.updatedAt
  };
  return merged;
}
function mergeList(base, addition) {
  if (!addition || addition.length === 0) return [...base];
  const seen = /* @__PURE__ */ new Set();
  const out = [];
  for (const item of [...base, ...addition]) {
    if (seen.has(item)) continue;
    seen.add(item);
    out.push(item);
  }
  return out;
}
function mergeOptionalList(base, addition) {
  if (!base && !addition) return void 0;
  return mergeList(base ?? [], addition);
}

// src/personality/builder.ts
function buildSystemPrompt(args) {
  const channelResolved = applyChannelOverride(args.personality, args.channel);
  const calibrated = applyCalibrationsToPersonality(
    channelResolved,
    args.recentCalibrations ?? []
  );
  const sections = [];
  sections.push(renderIdentity(calibrated));
  sections.push(renderVoice(calibrated));
  const vocab = renderVocabulary(calibrated);
  if (vocab) sections.push(vocab);
  const rules = renderRules(calibrated);
  if (rules) sections.push(rules);
  sections.push(renderChannelNotes(args.channel, calibrated));
  if (args.includeCapabilitiesHint) {
    sections.push(
      "Capabilities: you have access to a set of tools. When you are asked what you can do, describe your tools accurately \u2014 do not invent capabilities you do not have."
    );
  }
  if (args.context && args.context.trim().length > 0) {
    sections.push(`Context:
${args.context.trim()}`);
  }
  const calibrationNotes = renderCalibrations(args.recentCalibrations);
  if (calibrationNotes) sections.push(calibrationNotes);
  if (args.task && args.task.trim().length > 0) {
    sections.push(`Task:
${args.task.trim()}`);
  }
  return sections.join("\n\n");
}
function renderIdentity(p) {
  return [
    `You are ${p.name}, ${p.role}.`,
    p.about.trim()
  ].join(" ");
}
function renderVoice(p) {
  const lines = [
    "Voice and style:",
    `- Register: ${p.tone.register}`,
    `- Energy: ${p.tone.energy}`,
    `- Humor: ${p.tone.humor}`,
    `- Directness: ${p.tone.directness}`,
    `- Sentence length: ${p.style.sentenceLength}`,
    `- Lists allowed: ${p.style.listsAllowed ? "yes" : "no"}`,
    `- Emoji policy: ${p.style.emojiPolicy}`
  ];
  if (p.style.addressTheUserAs) {
    lines.push(`- Address the user as: ${p.style.addressTheUserAs}`);
  }
  if (p.style.selfReference) {
    lines.push(`- Self-reference: ${p.style.selfReference}`);
  }
  return lines.join("\n");
}
function renderVocabulary(p) {
  const blocks = [];
  if (p.preferredPhrasing.length > 0) {
    blocks.push(
      `Preferred phrasing:
${bulletize(p.preferredPhrasing)}`
    );
  }
  if (p.avoidPhrasing.length > 0) {
    blocks.push(`Avoid:
${bulletize(p.avoidPhrasing)}`);
  }
  if (p.signaturePhrases && p.signaturePhrases.length > 0) {
    blocks.push(`Signature phrases:
${bulletize(p.signaturePhrases)}`);
  }
  return blocks.length > 0 ? blocks.join("\n\n") : null;
}
function renderRules(p) {
  const blocks = [];
  if (p.doList.length > 0) {
    blocks.push(`Do:
${bulletize(p.doList)}`);
  }
  if (p.dontList.length > 0) {
    blocks.push(`Don't:
${bulletize(p.dontList)}`);
  }
  return blocks.length > 0 ? blocks.join("\n\n") : null;
}
function renderChannelNotes(channel, p) {
  const baseLine = `Channel: ${channel}.`;
  switch (channel) {
    case "telegram":
      return `${baseLine} Reply in HTML-safe text. Short paragraphs. ${p.style.listsAllowed ? "Lists allowed for structured information." : "Avoid bulleted lists."}`;
    case "email":
      return `${baseLine} Reply in paragraph form. No markdown formatting. Sign off appropriately for the register.`;
    case "dashboard":
      return `${baseLine} Reply in markdown. Lists, headings, and code blocks are allowed where they aid readability.`;
    case "voice":
      return `${baseLine} Reply in spoken-language sentences only. No markdown, no lists, no bullet points. The response will be read aloud.`;
  }
}
function renderCalibrations(calibrations) {
  if (!calibrations || calibrations.length === 0) return null;
  const lines = calibrations.map((c) => {
    const note = c.payload.note ? ` \u2014 ${c.payload.note}` : "";
    return `- ${c.payload.direction} ${c.payload.dimension}${note}`;
  });
  return [
    "Recent tone calibration from the user:",
    ...lines
  ].join("\n");
}
function bulletize(items) {
  return items.map((item) => `- ${item}`).join("\n");
}

// src/relationship-cadence/intervention-drafter.ts
var ClaudeInterventionDrafter = class {
  constructor(anthropic) {
    this.anthropic = anthropic;
  }
  anthropic;
  async draft(recommendation, relationship, personality, channel) {
    const personalityChannel = draftChannelToPersonalityChannel(channel);
    const systemPrompt = buildSystemPrompt({
      personality,
      channel: personalityChannel,
      task: `You are drafting a ${channelLabel(channel)} outreach message on behalf of ${personality.name}.`
    });
    const userPrompt = buildUserPrompt(recommendation, relationship, channel);
    const response = await this.anthropic.messages.create({
      model: "claude-sonnet-4-6",
      max_tokens: 1024,
      system: systemPrompt,
      messages: [{ role: "user", content: userPrompt }]
    });
    const rawText = extractText(response);
    const { subject, body } = parseResponse(rawText, channel);
    return {
      relationshipId: relationship.id,
      displayName: relationship.displayName,
      channel,
      subject,
      body,
      requiresApproval: true,
      draftedAt: /* @__PURE__ */ new Date(),
      recommendation
    };
  }
};
function draftChannelToPersonalityChannel(channel) {
  if (channel === "email") return "email";
  if (channel === "linkedin") return "telegram";
  return "voice";
}
function channelLabel(channel) {
  if (channel === "email") return "email";
  if (channel === "linkedin") return "LinkedIn message";
  return "call script";
}
function buildUserPrompt(rec, relationship, channel) {
  const lastContact = relationship.contacts.at(-1);
  const lastContactStr = lastContact ? `Last contact: ${lastContact.occurredAt.toISOString().split("T")[0]} via ${lastContact.channel} (${lastContact.direction}).` : "No prior contact history recorded.";
  const lines = [
    `Contact: ${rec.displayName}`,
    lastContactStr,
    `Reason for outreach: ${rec.reason.replace(/_/g, " ")}`,
    `Relationship heat: ${rec.heatScore.level} (${rec.heatScore.rationale})`,
    ""
  ];
  if (channel === "email") {
    lines.push(
      "Draft a warm, natural re-engagement email. Format your response as:",
      "Subject: <subject line>",
      "",
      "<email body>"
    );
  } else if (channel === "linkedin") {
    lines.push(
      "Draft a short, warm LinkedIn message (under 300 characters). Reply with the message body only \u2014 no subject line."
    );
  } else {
    lines.push(
      "Draft a brief call script: an opening, 2\u20133 talking points, and a clear call to action."
    );
  }
  return lines.join("\n");
}
function extractText(response) {
  for (const block of response.content) {
    if (block.type === "text" && block.text) return block.text;
  }
  return "";
}
function parseResponse(raw, channel) {
  if (channel !== "email") return { body: raw.trim() };
  const subjectMatch = /^Subject:\s*(.+)$/im.exec(raw);
  if (!subjectMatch) return { body: raw.trim() };
  const subject = subjectMatch[1]?.trim();
  const body = raw.replace(/^Subject:.*$/im, "").trim();
  return { subject, body };
}

// src/relationship-cadence/detectors/shared.ts
var DAY_MS = 24 * 60 * 60 * 1e3;
function daysBetween2(past, future) {
  return Math.max(0, (future.getTime() - past.getTime()) / DAY_MS);
}
function days(n) {
  return n * DAY_MS;
}
function contactsInWindow(contacts, start, end) {
  return contacts.filter(
    (c) => c.occurredAt >= start && c.occurredAt <= end
  );
}
function lastContactOf(relationship) {
  return relationship.contacts.at(-1);
}
function lastInbound(relationship) {
  for (let i = relationship.contacts.length - 1; i >= 0; i--) {
    const c = relationship.contacts[i];
    if (c?.direction === "inbound") return c;
  }
  return void 0;
}
function lastOutbound(relationship) {
  for (let i = relationship.contacts.length - 1; i >= 0; i--) {
    const c = relationship.contacts[i];
    if (c?.direction === "outbound") return c;
  }
  return void 0;
}
function sentimentScore(sentiment) {
  if (sentiment === "positive") return 1;
  if (sentiment === "negative") return -1;
  return 0;
}
function patternId() {
  return `cdpat_${Math.random().toString(36).slice(2, 10)}${Date.now().toString(36)}`;
}
function clamp(n, lo, hi) {
  return Math.min(Math.max(n, lo), hi);
}

// src/relationship-cadence/detectors/cadence-detector.ts
function detectGoingCold(operatorId, relationships, config) {
  const phase = config.phase ?? "nurture";
  const now = config.now ?? /* @__PURE__ */ new Date();
  const emitOn = config.emitOn ?? "cool";
  const maxPatterns = config.maxPatterns ?? 50;
  const thresholds = resolveThresholds2(config.policy, phase);
  const patterns = [];
  for (const rel of relationships) {
    if (patterns.length >= maxPatterns) break;
    const heat = computeHeatScore(rel, config.policy, phase, now);
    if (heat.level === "warm") continue;
    if (emitOn === "cold" && heat.level !== "cold") continue;
    const last = lastContactOf(rel);
    const evidenceIds = last ? [last.id] : [];
    const days2 = Number.isFinite(heat.daysSinceLastContact) ? Math.round(heat.daysSinceLastContact) : -1;
    const confidence = computeConfidence(heat.daysSinceLastContact, thresholds.coldAfterDays);
    const windowStart = last?.occurredAt ?? new Date(now.getTime() - thresholds.coldAfterDays * 24 * 60 * 60 * 1e3);
    patterns.push({
      id: patternId(),
      type: "going_cold",
      operatorId,
      relationshipId: rel.id,
      displayName: rel.displayName,
      description: days2 < 0 ? `${rel.displayName} \u2014 no recorded contact history; relationship considered ${heat.level}.` : `${rel.displayName} is ${heat.level} \u2014 last contact ${days2}d ago (cool >${thresholds.coolAfterDays}d, cold >${thresholds.coldAfterDays}d).`,
      evidenceContactIds: evidenceIds,
      confidence,
      metrics: {
        daysSinceLastContact: Number.isFinite(heat.daysSinceLastContact) ? heat.daysSinceLastContact : -1,
        coolAfterDays: thresholds.coolAfterDays,
        coldAfterDays: thresholds.coldAfterDays,
        heatScore: heat.score
      },
      windowStart,
      windowEnd: now,
      detectedAt: now
    });
  }
  return patterns;
}
function resolveThresholds2(policy, phase) {
  if (phase === "engagement_active") return policy.engagementActive;
  if (phase === "maintenance") return policy.maintenance;
  return policy.nurture;
}
function computeConfidence(daysSince, coldAfter) {
  if (!Number.isFinite(daysSince)) return 0.6;
  const ratio = daysSince / Math.max(1, coldAfter);
  return clamp(0.55 + ratio * 0.2, 0.55, 0.95);
}
var cadenceDetector = {
  name: "going_cold",
  detect: (operatorId, relationships, config) => detectGoingCold(operatorId, relationships, config)
};

// src/relationship-cadence/detectors/frequency-shift-detector.ts
var DEFAULTS = {
  lookbackDays: 60,
  minDelta: 0.5,
  minTotal: 3,
  maxPatterns: 50
};
function detectFrequencyShift(operatorId, relationships, config = {}) {
  const cfg = { ...DEFAULTS, ...config };
  const now = config.now ?? /* @__PURE__ */ new Date();
  const windowStart = new Date(now.getTime() - days(cfg.lookbackDays));
  const midpoint = new Date(now.getTime() - days(cfg.lookbackDays / 2));
  const shifts = [];
  for (const rel of relationships) {
    const inWindow = contactsInWindow(rel.contacts, windowStart, now);
    if (inWindow.length < cfg.minTotal) continue;
    let baseline = 0;
    let current = 0;
    for (const c of inWindow) {
      if (c.occurredAt.getTime() < midpoint.getTime()) baseline++;
      else current++;
    }
    const denom = baseline === 0 ? 1 : baseline;
    const delta = (current - baseline) / denom;
    if (Math.abs(delta) < cfg.minDelta) continue;
    shifts.push({
      rel,
      baseline,
      current,
      delta,
      evidenceIds: inWindow.slice(0, 10).map((c) => c.id)
    });
  }
  shifts.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
  return shifts.slice(0, cfg.maxPatterns).map((shift) => {
    const pct = Math.round(Math.abs(shift.delta) * 100);
    const direction = shift.delta > 0 ? "up" : "down";
    const summary = shift.delta < 0 ? "Cadence has dropped \u2014 relationship may be cooling." : "Cadence has accelerated \u2014 momentum is on your side.";
    return {
      id: patternId(),
      type: "frequency_shift",
      operatorId,
      relationshipId: shift.rel.id,
      displayName: shift.rel.displayName,
      description: `${shift.rel.displayName} contact frequency is ${direction} ${pct}% period-over-period (${shift.baseline} \u2192 ${shift.current}). ${summary}`,
      evidenceContactIds: shift.evidenceIds,
      confidence: clamp(0.6 + Math.min(Math.abs(shift.delta), 2) * 0.15, 0.6, 0.95),
      metrics: {
        baseline: shift.baseline,
        current: shift.current,
        delta: shift.delta,
        lookbackDays: cfg.lookbackDays
      },
      windowStart,
      windowEnd: now,
      detectedAt: now
    };
  });
}
var frequencyShiftDetector = {
  name: "frequency_shift",
  detect: (operatorId, relationships, config) => detectFrequencyShift(operatorId, relationships, config)
};

// src/relationship-cadence/detectors/silence-detector.ts
var DEFAULTS2 = {
  silenceAfterDays: 7,
  maxOutboundAgeDays: 60,
  maxPatterns: 50
};
function detectUnilateralSilence(operatorId, relationships, config = {}) {
  const cfg = { ...DEFAULTS2, ...config };
  const now = config.now ?? /* @__PURE__ */ new Date();
  const patterns = [];
  for (const rel of relationships) {
    if (patterns.length >= cfg.maxPatterns) break;
    const lastOut = lastOutbound(rel);
    if (!lastOut) continue;
    const ageDays = daysBetween2(lastOut.occurredAt, now);
    if (ageDays < cfg.silenceAfterDays) continue;
    if (ageDays > cfg.maxOutboundAgeDays) continue;
    const lastIn = lastInbound(rel);
    if (lastIn && lastIn.occurredAt.getTime() > lastOut.occurredAt.getTime()) {
      continue;
    }
    const evidenceIds = collectSilenceEvidence(rel.contacts, lastOut);
    const confidence = computeSilenceConfidence(ageDays, cfg.silenceAfterDays);
    patterns.push({
      id: patternId(),
      type: "unilateral_silence",
      operatorId,
      relationshipId: rel.id,
      displayName: rel.displayName,
      description: `${rel.displayName} hasn't replied to ${lastOut.channel} sent ${Math.round(ageDays)}d ago. One-sided silence beyond the ${cfg.silenceAfterDays}d threshold.`,
      evidenceContactIds: evidenceIds,
      confidence,
      metrics: {
        daysSinceLastOutbound: ageDays,
        silenceAfterDays: cfg.silenceAfterDays
      },
      windowStart: lastOut.occurredAt,
      windowEnd: now,
      detectedAt: now
    });
  }
  return patterns;
}
function collectSilenceEvidence(contacts, lastOut) {
  const ids = [lastOut.id];
  const idx = contacts.findIndex((c) => c.id === lastOut.id);
  if (idx > 0) {
    for (let i = Math.max(0, idx - 2); i < idx; i++) {
      const c = contacts[i];
      if (c) ids.unshift(c.id);
    }
  }
  return ids;
}
function computeSilenceConfidence(ageDays, threshold) {
  const ratio = ageDays / Math.max(1, threshold);
  return clamp(0.6 + ratio * 0.08, 0.65, 0.95);
}
var silenceDetector = {
  name: "unilateral_silence",
  detect: (operatorId, relationships, config) => detectUnilateralSilence(operatorId, relationships, config)
};

// src/relationship-cadence/detectors/escalation-needed-detector.ts
var DEFAULTS3 = {
  lookbackDays: 90,
  sentimentFloor: 0,
  sentimentDrop: 0.4,
  frequencyDrop: 0.3,
  minTotal: 4,
  maxPatterns: 25
};
function detectEscalationNeeded(operatorId, relationships, config = {}) {
  const cfg = { ...DEFAULTS3, ...config };
  const now = config.now ?? /* @__PURE__ */ new Date();
  const windowStart = new Date(now.getTime() - days(cfg.lookbackDays));
  const midpoint = new Date(now.getTime() - days(cfg.lookbackDays / 2));
  const patterns = [];
  for (const rel of relationships) {
    if (patterns.length >= cfg.maxPatterns) break;
    const inWindow = contactsInWindow(rel.contacts, windowStart, now);
    if (inWindow.length < cfg.minTotal) continue;
    const baseline = [];
    const current = [];
    for (const c of inWindow) {
      if (c.occurredAt.getTime() < midpoint.getTime()) baseline.push(c);
      else current.push(c);
    }
    if (baseline.length === 0 || current.length === 0) continue;
    const baselineSentiment = meanSentiment(baseline);
    const currentSentiment = meanSentiment(current);
    const sentimentDelta = currentSentiment - baselineSentiment;
    if (currentSentiment > cfg.sentimentFloor) continue;
    if (sentimentDelta > -cfg.sentimentDrop) continue;
    const freqDelta = (current.length - baseline.length) / Math.max(1, baseline.length);
    if (freqDelta > -cfg.frequencyDrop) continue;
    const evidenceIds = inWindow.slice(-10).map((c) => c.id);
    const confidence = computeEscalationConfidence(sentimentDelta, freqDelta);
    patterns.push({
      id: patternId(),
      type: "escalation_needed",
      operatorId,
      relationshipId: rel.id,
      displayName: rel.displayName,
      description: `${rel.displayName} health is deteriorating \u2014 sentiment fell from ${fmtScore(baselineSentiment)} to ${fmtScore(currentSentiment)} and contact frequency dropped ${Math.round(Math.abs(freqDelta) * 100)}%. Consider a personal escalation touch.`,
      evidenceContactIds: evidenceIds,
      confidence,
      metrics: {
        baselineSentiment,
        currentSentiment,
        sentimentDelta,
        frequencyDelta: freqDelta,
        lookbackDays: cfg.lookbackDays
      },
      windowStart,
      windowEnd: now,
      detectedAt: now
    });
  }
  return patterns;
}
function meanSentiment(contacts) {
  if (contacts.length === 0) return 0;
  let sum = 0;
  let n = 0;
  for (const c of contacts) {
    if (c.sentiment === void 0) continue;
    sum += sentimentScore(c.sentiment);
    n++;
  }
  return n === 0 ? 0 : sum / n;
}
function fmtScore(n) {
  return n.toFixed(2);
}
function computeEscalationConfidence(sentimentDelta, freqDelta) {
  const sentMag = clamp(Math.abs(sentimentDelta), 0, 2);
  const freqMag = clamp(Math.abs(freqDelta), 0, 2);
  return clamp(0.65 + (sentMag + freqMag) * 0.1, 0.65, 0.95);
}
var escalationNeededDetector = {
  name: "escalation_needed",
  detect: (operatorId, relationships, config) => detectEscalationNeeded(operatorId, relationships, config)
};

// src/relationship-cadence/detectors/index.ts
function runAllCadenceDetectors(operatorId, relationships, config) {
  const disabled = new Set(config.disable ?? []);
  const now = config.now;
  const out = [];
  if (!disabled.has("going_cold")) {
    const cfg = {
      policy: config.policy,
      ...config.phase !== void 0 ? { phase: config.phase } : {},
      ...now !== void 0 ? { now } : {},
      ...config.goingCold ?? {}
    };
    out.push(...detectGoingCold(operatorId, relationships, cfg));
  }
  if (!disabled.has("frequency_shift")) {
    out.push(
      ...detectFrequencyShift(operatorId, relationships, {
        ...now !== void 0 ? { now } : {},
        ...config.frequencyShift ?? {}
      })
    );
  }
  if (!disabled.has("unilateral_silence")) {
    out.push(
      ...detectUnilateralSilence(operatorId, relationships, {
        ...now !== void 0 ? { now } : {},
        ...config.unilateralSilence ?? {}
      })
    );
  }
  if (!disabled.has("escalation_needed")) {
    out.push(
      ...detectEscalationNeeded(operatorId, relationships, {
        ...now !== void 0 ? { now } : {},
        ...config.escalationNeeded ?? {}
      })
    );
  }
  return out;
}

// src/relationship-cadence/recommenders/next-touchpoint-recommender.ts
var DAY_MS2 = 24 * 60 * 60 * 1e3;
var URGENCY_ORDER2 = { high: 0, medium: 1, low: 2 };
var PolicyNextTouchpointRecommender = class {
  recommend(_operatorId, patterns, relationships, context) {
    const phase = context.phase ?? "nurture";
    const now = context.now ?? /* @__PURE__ */ new Date();
    const thresholds = resolvePhase(context.policy, phase);
    const relById = new Map(relationships.map((r) => [r.id, r]));
    const byRel = groupByRelationship(patterns);
    const recs = [];
    for (const [relId, relPatterns] of byRel) {
      const rel = relById.get(relId);
      if (!rel) continue;
      const urgency = resolveUrgency2(relPatterns);
      const delayDays = resolveDelayDays(urgency, thresholds.minDaysBetweenTouches);
      const proposedTouchAt = new Date(now.getTime() + delayDays * DAY_MS2);
      const channel = resolveChannel(rel, context.policy.preferredChannels, now);
      const rationale = buildRationale2(relPatterns, urgency, channel);
      recs.push({
        relationshipId: rel.id,
        displayName: rel.displayName,
        proposedTouchAt,
        channel,
        urgency,
        rationale,
        basedOnPatternIds: relPatterns.map((p) => p.id),
        recommendedAt: now
      });
    }
    recs.sort((a, b) => URGENCY_ORDER2[a.urgency] - URGENCY_ORDER2[b.urgency]);
    return recs;
  }
};
function groupByRelationship(patterns) {
  const map = /* @__PURE__ */ new Map();
  for (const p of patterns) {
    const list = map.get(p.relationshipId) ?? [];
    list.push(p);
    map.set(p.relationshipId, list);
  }
  return map;
}
function resolveUrgency2(patterns) {
  let urgency = "low";
  for (const p of patterns) {
    const u = patternUrgency(p);
    if (URGENCY_ORDER2[u] < URGENCY_ORDER2[urgency]) urgency = u;
  }
  return urgency;
}
function patternUrgency(pattern) {
  if (pattern.type === "escalation_needed") return "high";
  if (pattern.type === "unilateral_silence") {
    const days2 = pattern.metrics.daysSinceLastOutbound ?? 0;
    const threshold = pattern.metrics.silenceAfterDays ?? 7;
    return days2 >= threshold * 2 ? "high" : "medium";
  }
  if (pattern.type === "going_cold") {
    const heatScore = pattern.metrics.heatScore ?? 0;
    return heatScore < 0.2 ? "high" : "medium";
  }
  if (pattern.type === "frequency_shift") {
    const delta = pattern.metrics.delta ?? 0;
    return delta < 0 ? "medium" : "low";
  }
  return "low";
}
function resolveDelayDays(urgency, minDaysBetweenTouches) {
  if (urgency === "high") return 0;
  if (urgency === "medium") return Math.max(1, Math.ceil(minDaysBetweenTouches / 2));
  return Math.max(minDaysBetweenTouches, 1);
}
function resolveChannel(rel, preferred, now) {
  const fallback = preferred[0] ?? "email";
  const ninetyDaysAgo = new Date(now.getTime() - 90 * DAY_MS2);
  const recentPositive = rel.contacts.filter(
    (c) => c.occurredAt >= ninetyDaysAgo && c.direction === "inbound" && c.sentiment === "positive"
  ).at(-1);
  if (recentPositive) return recentPositive.channel;
  const recentInbound = rel.contacts.filter((c) => c.occurredAt >= ninetyDaysAgo && c.direction === "inbound").at(-1);
  if (recentInbound) return recentInbound.channel;
  return fallback;
}
function resolvePhase(policy, phase) {
  if (phase === "engagement_active") return policy.engagementActive;
  if (phase === "maintenance") return policy.maintenance;
  return policy.nurture;
}
function buildRationale2(patterns, urgency, channel) {
  const reasons = patterns.map((p) => p.type.replace(/_/g, " ")).join(", ");
  return `Urgency ${urgency}; channel ${channel}. Driven by: ${reasons}.`;
}

// src/relationship-cadence/recommenders/draft-suggester.ts
var ClaudeDraftSuggester = class {
  constructor(anthropic) {
    this.anthropic = anthropic;
  }
  anthropic;
  async suggest(recommendation, relationship, personality, context = {}) {
    const channel = context.channel ?? recommendationChannelToDraftChannel(recommendation.channel);
    const personalityChannel = draftChannelToPersonalityChannel2(channel);
    const systemPrompt = buildSystemPrompt({
      personality,
      channel: personalityChannel,
      task: `You are drafting a ${channelLabel2(channel)} outreach message on behalf of ${personality.name}. The draft is for operator approval \u2014 never auto-sent.`
    });
    const userPrompt = buildUserPrompt2(recommendation, relationship, channel);
    const response = await this.anthropic.messages.create({
      model: "claude-sonnet-4-6",
      max_tokens: 1024,
      system: systemPrompt,
      messages: [{ role: "user", content: userPrompt }]
    });
    const rawText = extractText2(response);
    const { subject, body } = parseResponse2(rawText, channel);
    return {
      relationshipId: relationship.id,
      displayName: relationship.displayName,
      channel,
      subject,
      body,
      requiresApproval: true,
      draftedAt: /* @__PURE__ */ new Date(),
      recommendation
    };
  }
};
function recommendationChannelToDraftChannel(channel) {
  if (channel === "email") return "email";
  if (channel === "linkedin") return "linkedin";
  if (channel === "phone" || channel === "in_person" || channel === "conference") {
    return "call_script";
  }
  return "email";
}
function draftChannelToPersonalityChannel2(channel) {
  if (channel === "email") return "email";
  if (channel === "linkedin") return "telegram";
  return "voice";
}
function channelLabel2(channel) {
  if (channel === "email") return "email";
  if (channel === "linkedin") return "LinkedIn message";
  return "call script";
}
function buildUserPrompt2(rec, relationship, channel) {
  const lastContact = relationship.contacts.at(-1);
  const lastContactStr = lastContact ? `Last contact: ${lastContact.occurredAt.toISOString().split("T")[0]} via ${lastContact.channel} (${lastContact.direction})${lastContact.sentiment ? `, sentiment ${lastContact.sentiment}` : ""}.` : "No prior contact history recorded.";
  const lines = [
    `Contact: ${rec.displayName}`,
    lastContactStr,
    `Recommended channel: ${rec.channel}`,
    `Urgency: ${rec.urgency}`,
    `Rationale: ${rec.rationale}`,
    ""
  ];
  if (channel === "email") {
    lines.push(
      "Draft a warm, natural outreach email. Format your response as:",
      "Subject: <subject line>",
      "",
      "<email body>"
    );
  } else if (channel === "linkedin") {
    lines.push(
      "Draft a short, warm LinkedIn message (under 300 characters). Reply with the message body only \u2014 no subject line."
    );
  } else {
    lines.push(
      "Draft a brief call script: an opening, 2\u20133 talking points, and a clear call to action."
    );
  }
  return lines.join("\n");
}
function extractText2(response) {
  for (const block of response.content) {
    if (block.type === "text" && block.text) return block.text;
  }
  return "";
}
function parseResponse2(raw, channel) {
  if (channel !== "email") return { body: raw.trim() };
  const subjectMatch = /^Subject:\s*(.+)$/im.exec(raw);
  if (!subjectMatch) return { body: raw.trim() };
  const subject = subjectMatch[1]?.trim();
  const body = raw.replace(/^Subject:.*$/im, "").trim();
  return { subject, body };
}

// src/relationship-cadence/drafters/shared.ts
async function callDrafter(args) {
  const systemPrompt = buildSystemPrompt({
    personality: args.personality,
    channel: args.personalityChannel,
    task: args.systemTask
  });
  const response = await args.anthropic.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: args.maxTokens,
    system: systemPrompt,
    messages: [{ role: "user", content: args.userPrompt }]
  });
  return extractText3(response);
}
function extractText3(response) {
  for (const block of response.content) {
    if (block.type === "text" && block.text) return block.text;
  }
  return "";
}
function buildContactBlock(rec, rel, intent, context) {
  const lines = [
    `Contact: ${rec.displayName}`
  ];
  const lastContact = rel.contacts.at(-1);
  if (lastContact) {
    const sentiment = lastContact.sentiment ? `, sentiment ${lastContact.sentiment}` : "";
    lines.push(
      `Last raw contact: ${formatDate(lastContact.occurredAt)} via ${lastContact.channel} (${lastContact.direction})${sentiment}.`
    );
  } else {
    lines.push("No prior contact history recorded.");
  }
  lines.push(
    `Recommended outreach channel: ${rec.channel}`,
    `Urgency: ${rec.urgency}`,
    `Rationale: ${rec.rationale}`
  );
  if (intent) {
    lines.push(`Intent: ${intent.replace(/_/g, " ")}.`);
  }
  if (context?.intentNote) {
    lines.push(`Specific reason: ${context.intentNote}`);
  }
  const history = context?.interactionHistory ?? [];
  if (history.length > 0) {
    lines.push("", "Recent interactions (most recent first):");
    for (const t of recentFirst(history).slice(0, 5)) {
      lines.push(formatTouchpoint(t));
    }
  }
  const facts = context?.personalFacts ?? [];
  if (facts.length > 0) {
    lines.push("", "Known personal facts (use sparingly, never copy verbatim):");
    for (const f of facts.slice(0, 8)) {
      lines.push(`- [${f.category}] ${f.fact}`);
    }
  }
  const commitments = (context?.outstandingCommitments ?? []).filter(
    (c) => c.status === "open"
  );
  if (commitments.length > 0) {
    lines.push("", "Outstanding commitments:");
    for (const c of commitments.slice(0, 6)) {
      lines.push(formatCommitment(c));
    }
  }
  return lines.join("\n");
}
function recentFirst(touches) {
  return [...touches].sort((a, b) => b.occurredAt.getTime() - a.occurredAt.getTime());
}
function formatTouchpoint(t) {
  const topics = t.topicsDiscussed && t.topicsDiscussed.length > 0 ? ` [topics: ${t.topicsDiscussed.join(", ")}]` : "";
  return `- ${formatDate(t.occurredAt)} ${t.channel} ${t.direction}: ${t.summary}${topics}`;
}
function formatCommitment(c) {
  const owner = c.direction === "operator_to_contact" ? "I owe them" : "they owe me";
  const due = c.dueAt ? ` (due ${formatDate(c.dueAt)})` : "";
  return `- ${owner}: ${c.description}${due}`;
}
function formatDate(d) {
  return d.toISOString().split("T")[0] ?? d.toISOString();
}
function draftChannelToPersonalityChannel3(channel) {
  if (channel === "email") return "email";
  if (channel === "call_script") return "voice";
  return "telegram";
}

// src/relationship-cadence/drafters/email-drafter.ts
var EmailDrafter = class {
  constructor(anthropic) {
    this.anthropic = anthropic;
  }
  anthropic;
  channel = "email";
  async draft(args) {
    const userPrompt = buildEmailPrompt(
      args.recommendation,
      args.relationship,
      args.intent,
      args.context
    );
    const raw = await callDrafter({
      anthropic: this.anthropic,
      personality: args.personality,
      personalityChannel: draftChannelToPersonalityChannel3(this.channel),
      systemTask: `You are drafting an outreach email on behalf of ${args.personality.name}. The draft is for operator approval \u2014 NEVER auto-sent. Match the operator's voice precisely.`,
      userPrompt,
      maxTokens: 1024
    });
    const { subject, body } = parseEmail(raw);
    return {
      relationshipId: args.relationship.id,
      displayName: args.relationship.displayName,
      channel: this.channel,
      subject,
      body,
      requiresApproval: true,
      draftedAt: /* @__PURE__ */ new Date(),
      recommendation: args.recommendation
    };
  }
};
function buildEmailPrompt(rec, rel, intent, context) {
  return [
    buildContactBlock(rec, rel, intent, context),
    "",
    "Draft a warm, natural outreach email. Reference shared history when",
    "the personal facts or interaction history above clearly support it,",
    "but never invent details. Keep the body under ~150 words unless the",
    "intent demands more.",
    "",
    "Format your response exactly as:",
    "Subject: <subject line>",
    "",
    "<email body>"
  ].join("\n");
}
function parseEmail(raw) {
  const subjectMatch = /^Subject:\s*(.+)$/im.exec(raw);
  if (!subjectMatch) return { body: raw.trim() };
  const subject = subjectMatch[1]?.trim();
  const body = raw.replace(/^Subject:.*$/im, "").trim();
  return { subject, body };
}

// src/relationship-cadence/drafters/slack-drafter.ts
var SLACK_CHAR_LIMIT = 500;
var SlackDrafter = class {
  constructor(anthropic) {
    this.anthropic = anthropic;
  }
  anthropic;
  channel = "slack";
  async draft(args) {
    const userPrompt = [
      buildContactBlock(args.recommendation, args.relationship, args.intent, args.context),
      "",
      `Draft a short Slack message (under ${SLACK_CHAR_LIMIT} characters).`,
      "Casual tone, no greeting/sign-off boilerplate, no subject line.",
      "Reply with the message body only."
    ].join("\n");
    const raw = await callDrafter({
      anthropic: this.anthropic,
      personality: args.personality,
      personalityChannel: draftChannelToPersonalityChannel3(this.channel),
      systemTask: `You are drafting a Slack DM on behalf of ${args.personality.name}. The draft is for operator approval \u2014 NEVER auto-sent. Short, warm, and in the operator's voice.`,
      userPrompt,
      maxTokens: 512
    });
    return {
      relationshipId: args.relationship.id,
      displayName: args.relationship.displayName,
      channel: this.channel,
      body: raw.trim(),
      requiresApproval: true,
      draftedAt: /* @__PURE__ */ new Date(),
      recommendation: args.recommendation
    };
  }
};

// src/relationship-cadence/drafters/text-drafter.ts
var SMS_CHAR_LIMIT = 160;
var TextDrafter = class {
  constructor(anthropic) {
    this.anthropic = anthropic;
  }
  anthropic;
  channel = "sms";
  async draft(args) {
    const userPrompt = [
      buildContactBlock(args.recommendation, args.relationship, args.intent, args.context),
      "",
      `Draft a single-segment SMS, ideally under ${SMS_CHAR_LIMIT} characters.`,
      "One thought only. No greeting boilerplate, no sign-off \u2014 first-name only at most.",
      "Reply with the message body only."
    ].join("\n");
    const raw = await callDrafter({
      anthropic: this.anthropic,
      personality: args.personality,
      personalityChannel: draftChannelToPersonalityChannel3(this.channel),
      systemTask: `You are drafting an SMS on behalf of ${args.personality.name}. The draft is for operator approval \u2014 NEVER auto-sent. SMS-tight, warm, in the operator's voice.`,
      userPrompt,
      maxTokens: 256
    });
    return {
      relationshipId: args.relationship.id,
      displayName: args.relationship.displayName,
      channel: this.channel,
      body: raw.trim(),
      requiresApproval: true,
      draftedAt: /* @__PURE__ */ new Date(),
      recommendation: args.recommendation
    };
  }
};

// src/relationship-cadence/drafters/linkedin-drafter.ts
var LINKEDIN_CHAR_LIMIT = 300;
var LinkedInDrafter = class {
  constructor(anthropic) {
    this.anthropic = anthropic;
  }
  anthropic;
  channel = "linkedin";
  async draft(args) {
    const userPrompt = [
      buildContactBlock(args.recommendation, args.relationship, args.intent, args.context),
      "",
      `Draft a LinkedIn message, ideally under ${LINKEDIN_CHAR_LIMIT} characters.`,
      "Professional but warm. No subject line.",
      "Reply with the message body only."
    ].join("\n");
    const raw = await callDrafter({
      anthropic: this.anthropic,
      personality: args.personality,
      personalityChannel: draftChannelToPersonalityChannel3(this.channel),
      systemTask: `You are drafting a LinkedIn DM on behalf of ${args.personality.name}. The draft is for operator approval \u2014 NEVER auto-sent. Short, professional, in the operator's voice.`,
      userPrompt,
      maxTokens: 384
    });
    return {
      relationshipId: args.relationship.id,
      displayName: args.relationship.displayName,
      channel: this.channel,
      body: raw.trim(),
      requiresApproval: true,
      draftedAt: /* @__PURE__ */ new Date(),
      recommendation: args.recommendation
    };
  }
};

// src/relationship-cadence/sub-agents/nyx.ts
var DAY_MS3 = 24 * 60 * 60 * 1e3;
var Nyx = class {
  tracker;
  policy;
  phase;
  anthropic;
  touchpointRecommender;
  draftSuggester;
  approvalGate;
  detectorConfig;
  // Phase C
  drafters;
  interactionHistory;
  personalFacts;
  commitments;
  constructor(tracker, config) {
    this.tracker = tracker;
    this.policy = config.policy;
    this.phase = config.defaultPhase ?? "nurture";
    if (config.anthropic) this.anthropic = config.anthropic;
    this.touchpointRecommender = config.touchpointRecommender ?? new PolicyNextTouchpointRecommender();
    if (config.draftSuggester) {
      this.draftSuggester = config.draftSuggester;
    } else if (config.anthropic) {
      this.draftSuggester = new ClaudeDraftSuggester(config.anthropic);
    }
    if (config.approvalGate) this.approvalGate = config.approvalGate;
    this.detectorConfig = config.detectorConfig ?? {};
    this.drafters = { ...config.drafters ?? {} };
    if (config.interactionHistory) this.interactionHistory = config.interactionHistory;
    if (config.personalFacts) this.personalFacts = config.personalFacts;
    if (config.commitments) this.commitments = config.commitments;
  }
  /**
   * Run the full sweep: detect, score health, recommend touchpoints. Returns
   * everything Nyx knows about the operator's relationships at this moment.
   */
  async scan(operatorId, phase, now = /* @__PURE__ */ new Date()) {
    const effectivePhase = phase ?? this.phase;
    const relationships = await this.tracker.listRelationships(operatorId);
    const patterns = runAllCadenceDetectors(operatorId, relationships, {
      policy: this.policy,
      phase: effectivePhase,
      now,
      ...this.detectorConfig
    });
    const states = relationships.map(
      (r) => computeCadenceState(r, this.policy, effectivePhase, now)
    );
    const health = rollupHealth(relationships, patterns, now);
    const recommendations = this.touchpointRecommender.recommend(
      operatorId,
      patterns,
      relationships,
      { policy: this.policy, phase: effectivePhase, now }
    );
    return { patterns, health, states, recommendations };
  }
  /**
   * Focused per-relationship recommendation. Returns the single best outreach
   * action for this contact, or null when nothing is due.
   */
  async recommendNextAction(operatorId, relationshipId, phase, now = /* @__PURE__ */ new Date()) {
    const rel = await this.tracker.getRelationship(operatorId, relationshipId);
    if (!rel) return null;
    const effectivePhase = phase ?? this.phase;
    const patterns = runAllCadenceDetectors(operatorId, [rel], {
      policy: this.policy,
      phase: effectivePhase,
      now,
      ...this.detectorConfig
    });
    if (patterns.length === 0) return null;
    const recs = this.touchpointRecommender.recommend(operatorId, patterns, [rel], {
      policy: this.policy,
      phase: effectivePhase,
      now
    });
    return recs[0] ?? null;
  }
  /**
   * Draft an outreach message for an in-flight recommendation. Never sends.
   *
   * If a channel-specific Phase C drafter is registered for the resolved
   * channel, Nyx uses it (and pulls in interaction history + personal facts +
   * outstanding commitments from the memory stores when available). Otherwise
   * Nyx falls back to the Phase B `DraftSuggester`.
   *
   * If an `approvalGate` was provided in NyxConfig, this also forwards the
   * draft to that gate (which typically calls F0.6 createProposal).
   *
   * Throws if Nyx was constructed without any drafter route or Anthropic client.
   */
  async draftOutreach(operatorId, relationshipId, personality, channelOrOptions, now = /* @__PURE__ */ new Date()) {
    const options = typeof channelOrOptions === "string" ? { channel: channelOrOptions } : channelOrOptions ?? {};
    const rec = await this.recommendNextAction(operatorId, relationshipId, void 0, now);
    if (!rec) {
      throw new Error(
        `No outreach is currently recommended for relationship ${relationshipId}. Nothing to draft.`
      );
    }
    const rel = await this.tracker.getRelationship(operatorId, relationshipId);
    const resolvedChannel = options.channel ?? this.defaultChannelFor(rec);
    const drafter = await this.resolveDrafter(resolvedChannel);
    let drafted;
    if (drafter) {
      const context = await this.buildDrafterContext(
        operatorId,
        relationshipId,
        options.intentNote
      );
      drafted = await drafter.draft({
        recommendation: rec,
        relationship: rel,
        personality,
        ...options.intent ? { intent: options.intent } : {},
        ...context ? { context } : {}
      });
    } else {
      if (!this.draftSuggester) {
        throw new Error(
          "Nyx.draftOutreach requires an Anthropic client, a custom draftSuggester, or a channel-specific drafter in NyxConfig."
        );
      }
      drafted = await this.draftSuggester.suggest(
        rec,
        rel,
        personality,
        { channel: resolvedChannel }
      );
    }
    if (this.approvalGate) {
      await this.approvalGate.submit({
        drafted,
        title: `Re-engage ${drafted.displayName}`,
        rationale: rec.rationale
      });
    }
    return drafted;
  }
  /**
   * Phase C — surface the top-N OutreachRecommendations across all
   * relationships for an operator. Wraps `scan` and trims the result so
   * dashboards can ask for "the top 5 things to do today".
   */
  async surfaceRecommendations(operatorId, args = {}) {
    const now = args.now ?? /* @__PURE__ */ new Date();
    const result = await this.scan(operatorId, args.phase, now);
    const limit = args.limit ?? 10;
    return result.recommendations.slice(0, limit);
  }
  /**
   * Phase C — record an interaction touchpoint into the configured
   * `InteractionHistoryStore`. Optionally also records the underlying
   * cadence `ContactPoint` on the tracker.
   */
  async recordInteraction(operatorId, args) {
    if (!this.interactionHistory) {
      throw new Error(
        "Nyx.recordInteraction requires `interactionHistory` to be configured."
      );
    }
    const stored = await this.interactionHistory.recordTouchpoint(
      operatorId,
      args.touchpoint
    );
    if (args.alsoTrackContact) {
      await this.tracker.recordContact(operatorId, stored.relationshipId, {
        channel: stored.channel,
        direction: stored.direction,
        occurredAt: stored.occurredAt,
        ...stored.summary ? { note: stored.summary } : {}
      });
    }
    return stored;
  }
  /**
   * Phase C — fetch known personal facts about a contact for downstream
   * context (drafting, surfacing, dashboards). Returns [] when no facts store
   * is configured or none are known.
   */
  async queryPersonalFacts(operatorId, relationshipId) {
    if (!this.personalFacts) return [];
    return this.personalFacts.listForRelationship(operatorId, relationshipId);
  }
  defaultChannelFor(rec) {
    if (rec.channel === "email") return "email";
    if (rec.channel === "linkedin") return "linkedin";
    if (rec.channel === "slack") return "slack";
    if (rec.channel === "sms") return "sms";
    if (rec.channel === "phone" || rec.channel === "in_person" || rec.channel === "conference") {
      return "call_script";
    }
    return "email";
  }
  async resolveDrafter(channel) {
    const existing = this.drafters[channel];
    if (existing) return existing;
    if (!this.anthropic) return void 0;
    let created;
    if (channel === "email") created = new EmailDrafter(this.anthropic);
    else if (channel === "slack") created = new SlackDrafter(this.anthropic);
    else if (channel === "sms") created = new TextDrafter(this.anthropic);
    else if (channel === "linkedin") created = new LinkedInDrafter(this.anthropic);
    if (created) this.drafters[channel] = created;
    return created;
  }
  async buildDrafterContext(operatorId, relationshipId, intentNote) {
    const interactionHistory = this.interactionHistory ? await this.interactionHistory.listForRelationship(operatorId, relationshipId, 10) : void 0;
    const personalFacts = this.personalFacts ? await this.personalFacts.listForRelationship(operatorId, relationshipId) : void 0;
    const outstandingCommitments = this.commitments ? await this.commitments.listForRelationship(operatorId, relationshipId, {
      status: "open"
    }) : void 0;
    const ctx = {};
    if (interactionHistory && interactionHistory.length > 0) {
      ctx.interactionHistory = interactionHistory;
    }
    if (personalFacts && personalFacts.length > 0) {
      ctx.personalFacts = personalFacts;
    }
    if (outstandingCommitments && outstandingCommitments.length > 0) {
      ctx.outstandingCommitments = outstandingCommitments;
    }
    if (intentNote) ctx.intentNote = intentNote;
    return Object.keys(ctx).length > 0 ? ctx : void 0;
  }
  /**
   * Phase A intervention recommender — exposed for callers who want the
   * legacy heat-only flow alongside the Phase B pipeline.
   */
  async recommendInterventions(operatorId, phase, now = /* @__PURE__ */ new Date()) {
    const rels = await this.tracker.listRelationships(operatorId);
    return new PolicyInterventionRecommender().recommend(
      operatorId,
      rels,
      this.policy,
      phase ?? this.phase,
      now
    );
  }
};
function computeCadenceState(rel, policy, phase, now) {
  const heat = computeHeatScore(rel, policy, phase, now);
  const ninetyDaysAgo = new Date(now.getTime() - 90 * DAY_MS3);
  let inbound = 0;
  let outbound = 0;
  let lastInbound2;
  let lastOutbound2;
  for (const c of rel.contacts) {
    if (c.occurredAt < ninetyDaysAgo) continue;
    if (c.direction === "inbound") {
      inbound++;
      lastInbound2 = c.occurredAt;
    } else {
      outbound++;
      lastOutbound2 = c.occurredAt;
    }
  }
  return {
    relationshipId: rel.id,
    displayName: rel.displayName,
    heat,
    phase,
    inboundCount90d: inbound,
    outboundCount90d: outbound,
    ...lastInbound2 ? { lastInboundAt: lastInbound2 } : {},
    ...lastOutbound2 ? { lastOutboundAt: lastOutbound2 } : {},
    computedAt: now
  };
}
function rollupHealth(relationships, patterns, now) {
  const byRel = /* @__PURE__ */ new Map();
  for (const p of patterns) {
    const list = byRel.get(p.relationshipId) ?? [];
    list.push(p);
    byRel.set(p.relationshipId, list);
  }
  const out = [];
  for (const rel of relationships) {
    const relPatterns = byRel.get(rel.id) ?? [];
    const { level, score, rationale } = scoreHealth(relPatterns, rel.contacts);
    out.push({
      relationshipId: rel.id,
      displayName: rel.displayName,
      level,
      score,
      patterns: relPatterns,
      rationale,
      assessedAt: now
    });
  }
  return out;
}
function scoreHealth(patterns, contacts) {
  if (patterns.length === 0) {
    const rationale = contacts.length === 0 ? "No contact history yet \u2014 relationship considered healthy until a pattern fires." : "No cadence patterns detected.";
    return { level: "healthy", score: 1, rationale };
  }
  const types = new Set(patterns.map((p) => p.type));
  if (types.has("escalation_needed")) {
    return {
      level: "critical",
      score: 0.15,
      rationale: "Escalation pattern present \u2014 sentiment and frequency both deteriorating."
    };
  }
  const hasNegativeFreq = patterns.some(
    (p) => p.type === "frequency_shift" && (p.metrics.delta ?? 0) < 0
  );
  const hasSilence = types.has("unilateral_silence");
  const hasColdHeat = patterns.some(
    (p) => p.type === "going_cold" && (p.metrics.heatScore ?? 0) < 0.2
  );
  if (hasSilence && (hasNegativeFreq || hasColdHeat)) {
    return {
      level: "at_risk",
      score: 0.35,
      rationale: "Unilateral silence combined with declining cadence or cold heat."
    };
  }
  if (hasSilence || hasNegativeFreq || hasColdHeat) {
    return {
      level: "at_risk",
      score: 0.5,
      rationale: "One material cadence signal \u2014 worth a proactive touch."
    };
  }
  return {
    level: "watching",
    score: 0.7,
    rationale: "Minor cadence drift \u2014 relationship still warm overall."
  };
}

// src/relationship-cadence/memory/interaction-history.ts
function makeId() {
  return `tp_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
}
var InMemoryInteractionHistory = class {
  store = /* @__PURE__ */ new Map();
  bucket(operatorId) {
    let list = this.store.get(operatorId);
    if (!list) {
      list = [];
      this.store.set(operatorId, list);
    }
    return list;
  }
  async recordTouchpoint(operatorId, touchpoint) {
    const stored = {
      ...touchpoint,
      id: makeId(),
      createdAt: /* @__PURE__ */ new Date()
    };
    this.bucket(operatorId).push(stored);
    return stored;
  }
  async listForRelationship(operatorId, relationshipId, limit) {
    const sorted = this.bucket(operatorId).filter((t) => t.relationshipId === relationshipId).sort((a, b) => b.occurredAt.getTime() - a.occurredAt.getTime());
    return limit ? sorted.slice(0, limit) : sorted;
  }
  async listRecent(operatorId, limit = 20) {
    return this.bucket(operatorId).slice().sort((a, b) => b.occurredAt.getTime() - a.occurredAt.getTime()).slice(0, limit);
  }
  async getTouchpoint(operatorId, touchpointId) {
    return this.bucket(operatorId).find((t) => t.id === touchpointId) ?? null;
  }
};
function makeTouchpoint(args) {
  return { ...args };
}

// src/relationship-cadence/memory/personal-facts.ts
function makeFactId() {
  return `pf_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
}
function makeAuditId() {
  return `pfa_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
}
var InMemoryPersonalFacts = class {
  facts = /* @__PURE__ */ new Map();
  audit = /* @__PURE__ */ new Map();
  factsFor(operatorId) {
    let list = this.facts.get(operatorId);
    if (!list) {
      list = [];
      this.facts.set(operatorId, list);
    }
    return list;
  }
  auditFor(operatorId) {
    let list = this.audit.get(operatorId);
    if (!list) {
      list = [];
      this.audit.set(operatorId, list);
    }
    return list;
  }
  writeAudit(operatorId, args) {
    const entry = {
      id: makeAuditId(),
      occurredAt: /* @__PURE__ */ new Date(),
      ...args
    };
    this.auditFor(operatorId).push(entry);
    return entry;
  }
  async addFact(operatorId, args) {
    const now = /* @__PURE__ */ new Date();
    const stored = {
      id: makeFactId(),
      relationshipId: args.relationshipId,
      category: args.category,
      fact: args.fact,
      source: args.source,
      ...args.confidence !== void 0 ? { confidence: args.confidence } : {},
      createdAt: now,
      updatedAt: now
    };
    this.factsFor(operatorId).push(stored);
    this.writeAudit(operatorId, {
      factId: stored.id,
      relationshipId: stored.relationshipId,
      action: "created",
      previousFact: null,
      nextFact: stored.fact,
      actor: args.actor,
      ...args.reason !== void 0 ? { reason: args.reason } : {}
    });
    return stored;
  }
  async updateFact(operatorId, factId, args) {
    const list = this.factsFor(operatorId);
    const existing = list.find((f) => f.id === factId);
    if (!existing) return null;
    const previousFact = existing.fact;
    existing.fact = args.fact;
    existing.updatedAt = /* @__PURE__ */ new Date();
    this.writeAudit(operatorId, {
      factId: existing.id,
      relationshipId: existing.relationshipId,
      action: "updated",
      previousFact,
      nextFact: existing.fact,
      actor: args.actor,
      ...args.reason !== void 0 ? { reason: args.reason } : {}
    });
    return existing;
  }
  async deleteFact(operatorId, factId, args) {
    const list = this.factsFor(operatorId);
    const idx = list.findIndex((f) => f.id === factId);
    if (idx === -1) return false;
    const removed = list.splice(idx, 1)[0];
    this.writeAudit(operatorId, {
      factId: removed.id,
      relationshipId: removed.relationshipId,
      action: "deleted",
      previousFact: removed.fact,
      nextFact: null,
      actor: args.actor,
      ...args.reason !== void 0 ? { reason: args.reason } : {}
    });
    return true;
  }
  async listForRelationship(operatorId, relationshipId) {
    return this.factsFor(operatorId).filter(
      (f) => f.relationshipId === relationshipId
    );
  }
  async getFact(operatorId, factId) {
    return this.factsFor(operatorId).find((f) => f.id === factId) ?? null;
  }
  async listAuditEntries(operatorId, args) {
    let entries = this.auditFor(operatorId).slice();
    if (args?.relationshipId) {
      entries = entries.filter((e) => e.relationshipId === args.relationshipId);
    }
    if (args?.factId) {
      entries = entries.filter((e) => e.factId === args.factId);
    }
    return entries.sort((a, b) => b.occurredAt.getTime() - a.occurredAt.getTime());
  }
};

// src/relationship-cadence/memory/commitment-tracker.ts
function makeId2() {
  return `cm_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
}
var InMemoryCommitmentTracker = class {
  store = /* @__PURE__ */ new Map();
  bucket(operatorId) {
    let list = this.store.get(operatorId);
    if (!list) {
      list = [];
      this.store.set(operatorId, list);
    }
    return list;
  }
  async createCommitment(operatorId, args) {
    const now = /* @__PURE__ */ new Date();
    const stored = {
      id: makeId2(),
      relationshipId: args.relationshipId,
      direction: args.direction,
      description: args.description,
      ...args.dueAt ? { dueAt: args.dueAt } : {},
      ...args.sourceTouchpointId ? { sourceTouchpointId: args.sourceTouchpointId } : {},
      status: "open",
      createdAt: now,
      updatedAt: now
    };
    this.bucket(operatorId).push(stored);
    return stored;
  }
  async updateStatus(operatorId, commitmentId, status, now = /* @__PURE__ */ new Date()) {
    const c = this.bucket(operatorId).find((x) => x.id === commitmentId);
    if (!c) return null;
    c.status = status;
    c.updatedAt = now;
    if (status === "fulfilled") c.fulfilledAt = now;
    return c;
  }
  async listForRelationship(operatorId, relationshipId, args) {
    return this.bucket(operatorId).filter((c) => c.relationshipId === relationshipId).filter((c) => args?.status ? c.status === args.status : true).sort((a, b) => (a.dueAt?.getTime() ?? Infinity) - (b.dueAt?.getTime() ?? Infinity));
  }
  async listOverdue(operatorId, now = /* @__PURE__ */ new Date()) {
    return this.bucket(operatorId).filter((c) => c.status === "open" && c.dueAt && c.dueAt.getTime() < now.getTime()).sort((a, b) => (a.dueAt?.getTime() ?? 0) - (b.dueAt?.getTime() ?? 0));
  }
  async getCommitment(operatorId, commitmentId) {
    return this.bucket(operatorId).find((c) => c.id === commitmentId) ?? null;
  }
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  ClaudeDraftSuggester,
  ClaudeInterventionDrafter,
  DEFAULT_POLICIES,
  EmailDrafter,
  InMemoryCommitmentTracker,
  InMemoryInteractionHistory,
  InMemoryPersonalFacts,
  InMemoryRelationshipTracker,
  LINKEDIN_CHAR_LIMIT,
  LinkedInDrafter,
  Nyx,
  PolicyInterventionRecommender,
  PolicyNextTouchpointRecommender,
  SMS_CHAR_LIMIT,
  SlackDrafter,
  TextDrafter,
  cadenceDetector,
  computeHeatScore,
  detectEscalationNeeded,
  detectFrequencyShift,
  detectGoingCold,
  detectUnilateralSilence,
  escalationNeededDetector,
  frequencyShiftDetector,
  getCadencePolicy,
  makeTouchpoint,
  runAllCadenceDetectors,
  silenceDetector
});
//# sourceMappingURL=index.js.map