← back to Dw Boardroom V2

src/engine/agentDialogue.ts

154 lines

// ═══════════════════════════════════════════════
// DW Boardroom V2 — Gemini Agent Dialogue
// Generates in-character speech for 47 agents
// Anti-hallucination guards from existing boardroom
// ═══════════════════════════════════════════════

import fetch from 'node-fetch';

const GEMINI_KEY = process.env.GEMINI_KEY || '';
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_KEY}`;

// Anti-hallucination patterns — ported from existing boardroom
const HALLUCINATION_PATTERNS = [
  /(?:I (?:just |recently )?(?:completed|finished|resolved|fixed|addressed|updated|processed|handled|executed|fulfilled)\b)/i,
  /(?:I (?:have |'ve )?(?:sent|dispatched|delivered|forwarded|transmitted|posted)\b)/i,
  /(?:(?:all|every) (?:systems?|services?|agents?|processes?) (?:are |is )?(?:operational|running|online|healthy|stable|green)\b)/i,
  /(?:I (?:am )?(?:currently |actively )?(?:monitoring|tracking|watching|observing|analyzing)\b)/i,
  /(?:(?:revenue|sales|profit|income|earnings) (?:is |are )?(?:up|increased|growing|improved)\b)/i,
  /(?:I (?:have |'ve )?(?:detected|discovered|found|identified|noticed|observed)\b)/i,
  /(?:(?:task|issue|problem|bug|ticket) (?:has been |was )?(?:resolved|fixed|closed|completed|addressed)\b)/i,
  /(?:I (?:can |will |shall )?(?:confirm|verify|validate|certify|attest)\b)/i,
  /(?:(?:email|message|notification|alert) (?:has been |was )?(?:sent|delivered|dispatched)\b)/i,
  /(?:(?:database|server|system|service) (?:is )?(?:fully )?(?:optimized|upgraded|migrated|updated)\b)/i,
  /(?:(?:security|compliance|audit) (?:check|scan|review) (?:passed|cleared|completed)\b)/i,
  /(?:I (?:have |'ve )?(?:negotiated|secured|obtained|arranged|organized)\b)/i,
  /(?:(?:inventory|stock|supply) (?:has been |is )?(?:replenished|restocked|updated|synchronized)\b)/i,
  /(?:(?:customer|client|user) (?:satisfaction|feedback|rating) (?:is )?(?:excellent|positive|improving)\b)/i,
  /(?:I (?:have |'ve )?(?:already|previously) (?:submitted|filed|reported|logged)\b)/i,
];

// Reality anchor — prevents Gemini from fabricating actions
const REALITY_ANCHOR = `CRITICAL RULES:
1. You are an AI agent in a boardroom simulation. You do NOT have access to real systems.
2. NEVER claim to have completed actions, sent emails, fixed bugs, or monitored systems.
3. NEVER fabricate data, metrics, numbers, or statistics.
4. Instead: propose actions, recommend strategies, identify concerns, ask questions.
5. Use phrases like "I recommend...", "We should consider...", "My concern is...", "I propose..."
6. Keep response to EXACTLY 1 SHORT sentence. Blunt and direct. No fluff.
7. Stay in character for your role but never claim to have done real work.
8. PERSONA: Sir Steven stands at the podium as Abraham Lincoln, the 16th President. Address him as "Mr. President" or "President Lincoln".`;

export async function generateDialogue(
  agentId: string,
  agentName: string,
  role: string,
  phase: string,
  context: string,
  meetingType: string
): Promise<string> {
  // Fallback if no API key
  if (!GEMINI_KEY) {
    return getFallbackDialogue(agentName, role, phase, context);
  }

  const prompt = `${REALITY_ANCHOR}

You are ${agentName}, the ${role} at Designer Wallcoverings.
Meeting type: ${meetingType}
Current phase: ${phase}
Context: ${context}

Respond in EXACTLY 1 short blunt sentence. Direct. No filler words.`;

  try {
    const res = await fetch(GEMINI_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        contents: [{ parts: [{ text: prompt }] }],
        generationConfig: {
          maxOutputTokens: 60,
          temperature: 0.7,
        },
      }),
    });

    if (!res.ok) {
      console.error(`[agentDialogue] Gemini ${res.status}`);
      return getFallbackDialogue(agentName, role, phase, context);
    }

    const data = await res.json() as any;
    let text = data?.candidates?.[0]?.content?.parts?.[0]?.text || '';

    // Anti-hallucination filter
    text = filterHallucinations(text, agentName, role);

    return text || getFallbackDialogue(agentName, role, phase, context);
  } catch (err: any) {
    console.error(`[agentDialogue] Error:`, err.message);
    return getFallbackDialogue(agentName, role, phase, context);
  }
}

function filterHallucinations(text: string, agentName: string, role: string): string {
  let filtered = text;

  for (const pattern of HALLUCINATION_PATTERNS) {
    if (pattern.test(filtered)) {
      // Replace claims of action with recommendations
      filtered = filtered
        .replace(/I (?:just |recently )?(?:completed|finished|resolved|fixed)/gi, 'I recommend we address')
        .replace(/I (?:have |'ve )?(?:sent|dispatched|delivered|forwarded)/gi, 'I suggest we send')
        .replace(/(?:all|every) (?:systems?|services?) (?:are |is )?(?:operational|running|online)/gi, 'systems should be checked to confirm they are operational')
        .replace(/I (?:am )?(?:currently |actively )?monitoring/gi, 'I recommend monitoring')
        .replace(/(?:revenue|sales) (?:is |are )?(?:up|increased|growing)/gi, 'revenue trends should be reviewed')
        .replace(/I (?:have |'ve )?(?:detected|discovered|found)/gi, 'We should investigate potential')
        .replace(/(?:task|issue) (?:has been |was )?(?:resolved|fixed|closed)/gi, 'the task needs verification to confirm it is resolved');
    }
  }

  return filtered.trim();
}

function getFallbackDialogue(agentName: string, role: string, phase: string, context: string): string {
  const fallbacks: Record<string, string[]> = {
    caucus: [
      `${agentName} here, ready for the session.`,
      `${agentName} present. Let's make this productive.`,
    ],
    call_to_order: [
      `${agentName}, ${role}, standing by.`,
      `Present and accounted for. ${agentName} ready to contribute.`,
    ],
    old_business: [
      `From the ${role} perspective, I recommend we review outstanding items before proceeding.`,
      `I suggest we verify completion of prior action items related to ${role.toLowerCase()}.`,
    ],
    current_business: [
      `Regarding ${context.substring(0, 50)}... I recommend we evaluate this carefully.`,
      `From my vantage as ${role}, this deserves thorough consideration.`,
    ],
    new_business: [
      `I'd like to raise a point about ${role.toLowerCase()} priorities going forward.`,
      `For new business, I propose we consider our ${role.toLowerCase()} strategy.`,
    ],
    breakout: [
      `My team has several items to discuss offline. Will report back.`,
      `We'll coordinate on the ${role.toLowerCase()} items and bring a summary.`,
    ],
    minutes: [
      `Key takeaways noted for the record.`,
      `I'll ensure all action items are properly documented.`,
    ],
    adjournment: [
      `Good session. Looking forward to executing on these decisions.`,
      `Thank you all. ${agentName} signing off.`,
    ],
  };

  const options = fallbacks[phase] || [`${agentName} acknowledges.`];
  return options[Math.floor(Math.random() * options.length)];
}