src/voice/voiceOperationalMessaging.ts

import { getVoiceConversationPolicy } from "../settings/agentStack.ts";

/**

  • Operational message verbosity tiers.
  • "user_response" — direct reply to a user request (join, leave, status, music command).
  •               Always sent regardless of verbosity (except "none").
    
  • "lifecycle" — autonomous session lifecycle (session end, connection drop,
  •               music started/paused/stopped, stream watch state changes).
    
  •               Sent at "all", LLM-skippable at "essential", suppressed at "minimal"/"none".
    
  • "error" — runtime errors, crashes, failures.
  •               Sent at "all"/"essential", LLM-skippable at "minimal", suppressed at "none".
    

*/ type OperationalMessageTier = "user_response" | "lifecycle" | "error";

const VALID_VERBOSITY_LEVELS = ["all", "essential", "minimal", "none"] as const; type OperationalMessageVerbosity = (typeof VALID_VERBOSITY_LEVELS)[number];

/** Map (event, reason) to a tier so the verbosity setting can decide what to suppress. */ function classifyOperationalMessageTier( event: string, reason: string | null ): OperationalMessageTier { const e = String(event || "").trim().toLowerCase(); const r = String(reason || "").trim().toLowerCase();

// --- user_response: the user explicitly asked for something --- if (e === "voice_join_request") return "user_response"; if (e === "voice_leave_request") return "user_response"; if (e === "voice_status_request") return "user_response"; if (e === "voice_screen_share_offer") return "user_response";

// Music: commands that come from a user request if (e === "voice_music_request") { // "started" / "paused" / "stopped" / "already_stopped" are the result of the user's // command — still a user_response. Failures too (user asked, we owe them an answer). // "disambiguation_required" / "disambiguation_cancelled" — user interaction. return "user_response"; }

// Stream-watch: user-issued commands if (e === "voice_stream_watch_request") { // watching_started, watching_stopped, status, already_stopped — user asked. // offline, requester_not_in_same_vc, disabled, unavailable — user asked but denied. return "user_response"; }

// --- error: runtime failures, crashes --- if (e === "voice_session_end") { const errorReasons = [ "realtime_runtime_error", "realtime_socket_closed", "connection_lost", "subprocess_crashed", "response_stalled" ]; if (errorReasons.includes(r)) return "error"; // Everything else is lifecycle (inactivity, max_duration, nl_leave, assistant_leave_directive, etc.) return "lifecycle"; }

// Catch-all: treat unknown events as lifecycle return "lifecycle"; }

/**

  • Given the per-call mustNotify flag, the event tier, and the verbosity setting,
  • resolve whether the message should be force-sent, LLM-skippable, or suppressed entirely.
  • Returns:
  • "send" — compose + send (mustNotify=true to LLM, no [SKIP] allowed)
  • "allow_skip" — compose + send but LLM may return [SKIP]
  • "suppress" — skip entirely, no LLM call, no channel message (just log) */ function resolveMessageDisposition( mustNotify: boolean, tier: OperationalMessageTier, verbosity: OperationalMessageVerbosity ): "send" | "allow_skip" | "suppress" { if (verbosity === "none") return "suppress";

if (verbosity === "all") { // Original behavior: respect the per-call mustNotify flag exactly. return mustNotify ? "send" : "allow_skip"; }

if (verbosity === "essential") { // user_response: always send if (tier === "user_response") return mustNotify ? "send" : "allow_skip"; // error: always send if (tier === "error") return "send"; // lifecycle: LLM decides return "allow_skip"; }

// verbosity === "minimal" // user_response: always send if (tier === "user_response") return mustNotify ? "send" : "allow_skip"; // error: LLM decides if (tier === "error") return "allow_skip"; // lifecycle: suppress entirely return "suppress"; }

function normalizeVerbosity(raw: unknown): OperationalMessageVerbosity { const s = String(raw || "").trim().toLowerCase(); if ((VALID_VERBOSITY_LEVELS as readonly string[]).includes(s)) return s as OperationalMessageVerbosity; return "all"; }

export async function sendOperationalMessage(manager, { channel, settings = null, guildId = null, channelId = null, userId = null, messageId = null, event = "voice_runtime", reason = null, details = {}, mustNotify = false }) { const resolvedSettings = settings || (typeof manager.store?.getSettings === "function" ? manager.store.getSettings() : null); const detailsPayload = details && typeof details === "object" && !Array.isArray(details) ? details : { detail: String(details || "") };

// --- Verbosity gate --- const verbosity = normalizeVerbosity(getVoiceConversationPolicy(resolvedSettings).operationalMessages); const tier = classifyOperationalMessageTier(String(event || ""), reason); const disposition = resolveMessageDisposition(mustNotify, tier, verbosity);

if (disposition === "suppress") { manager.store.logAction({ kind: "voice_info", guildId: guildId || null, channelId: channelId || channel?.id || null, messageId: messageId || null, userId: userId || manager.client.user?.id || null, content: "voice_message_suppressed_by_verbosity", metadata: { event, reason, verbosity, tier } }); return true; }

const effectiveAllowSkip = disposition === "allow_skip";

const resolvedChannel = await resolveOperationalChannel(manager, channel, channelId, { guildId, userId, messageId, event, reason }); if (!resolvedChannel) { manager.store.logAction({ kind: "voice_error", guildId: guildId || null, channelId: channelId || channel?.id || null, messageId: messageId || null, userId: userId || manager.client.user?.id || null, content: "voice_message_channel_unavailable", metadata: { event, reason } }); return false; }

let composedText = ""; if (!manager.composeOperationalMessage || !resolvedSettings) { manager.store.logAction({ kind: "voice_error", guildId: guildId || null, channelId: channelId || channel?.id || null, messageId: messageId || null, userId: userId || manager.client.user?.id || null, content: "voice_message_compose_unavailable", metadata: { event, reason, hasComposeOperationalMessage: Boolean(manager.composeOperationalMessage), hasResolvedSettings: Boolean(resolvedSettings) } }); return false; }

try { composedText = String( (await manager.composeOperationalMessage({ settings: resolvedSettings, guildId: guildId || null, channelId: channelId || channel?.id || null, userId: userId || null, messageId: messageId || null, event: String(event || "voice_runtime"), reason: reason ? String(reason) : null, details: detailsPayload, allowSkip: effectiveAllowSkip })) || "" ).trim(); } catch (error) { manager.store.logAction({ kind: "voice_error", guildId: guildId || null, channelId: channelId || channel?.id || null, messageId: messageId || null, userId: userId || manager.client.user?.id || null, content: voice_message_compose_failed: ${String(error?.message || error)}, metadata: { event, reason } }); return false; }

const normalizedComposedText = String(composedText || "").trim(); const skipRequested = /^[SKIP]$/i.test(normalizedComposedText); if (skipRequested) return true;

if (!normalizedComposedText) { manager.store.logAction({ kind: "voice_error", guildId: guildId || null, channelId: channelId || resolvedChannel?.id || channel?.id || null, messageId: messageId || null, userId: userId || manager.client.user?.id || null, content: "voice_message_model_empty", metadata: { event, reason } }); return false; }

return await sendToChannel(manager, resolvedChannel, normalizedComposedText, { guildId, channelId: channelId || resolvedChannel?.id || null, userId, messageId, event, reason }); }

export async function resolveOperationalChannel( manager, channel, channelId, { guildId = null, userId = null, messageId = null, event = null, reason = null } = {} ) { if (channel && typeof channel.send === "function") return channel;

const resolvedChannelId = String(channelId || channel?.id || "").trim(); if (!resolvedChannelId) return null;

try { const fetched = await manager.client.channels.fetch(resolvedChannelId); if (fetched && typeof fetched.send === "function") return fetched; return null; } catch (error) { manager.store.logAction({ kind: "voice_error", guildId: guildId || null, channelId: resolvedChannelId || null, messageId: messageId || null, userId: userId || manager.client.user?.id || null, content: voice_message_channel_fetch_failed: ${String(error?.message || error)}, metadata: { event, reason } }); return null; } }

export async function sendToChannel( manager, channel, text, { guildId = null, channelId = null, userId = null, messageId = null, event = null, reason = null } = {} ) { if (!channel || typeof channel.send !== "function") return false; const content = String(text || "").trim(); if (!content) return false;

try { await channel.send(content); return true; } catch (error) { manager.store.logAction({ kind: "voice_error", guildId: guildId || null, channelId: channelId || channel?.id || null, messageId: messageId || null, userId: userId || manager.client.user?.id || null, content: voice_message_send_failed: ${String(error?.message || error)}, metadata: { event, reason } }); return false; } }