{"version":3,"sources":["../../src/chat-protocol/frames.ts","../../src/chat-protocol/events.ts","../../src/chat-protocol/encode.ts","../../src/chat-protocol/leading-frames.ts","../../src/chat-protocol/decode.ts","../../src/components/chat/types/message.types.ts","../../src/chat-protocol/nats-decoder.ts","../../src/chat-protocol/confirm-tool.ts","../../src/chat-protocol/ip-normalize.ts","../../src/chat-protocol/env-flag.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * SSE wire-protocol frame shapes — the byte-level contract between the\n * hub's chat stream emitter (`multi-platform-hub/lib/utils/claude-util.ts`)\n * and the lib's SSE decoder (`./decode.ts`).\n *\n * Wire format (custom byte framing, NOT standard SSE `data:` lines):\n *\n *   [JSON frame]\\0 [JSON frame]\\0 …  \\x1E  <raw UTF-8 answer deltas>  \\x1F [JSON usage trailer]\n *\n *   1. LEADING frames — each a JSON object terminated by `\\0`\n *      (FRAME_TERMINATOR). Status, metadata, routing, live thinking\n *      deltas, usage:start, tool approval frames.\n *   2. ONE `\\x1E` byte (END_OF_LEADING) — flips the stream into raw\n *      UTF-8 answer-text mode.\n *   3. `\\x1F` (TRAILER_SENTINEL) + one JSON usage frame running to\n *      stream end (no terminator).\n *\n * Server-safe: no React, no browser APIs beyond TextEncoder/TextDecoder.\n */\n\n// =============================================================================\n// Sentinel constants\n// =============================================================================\n\n/** Terminates every leading JSON frame. */\nexport const FRAME_TERMINATOR = '\\0'\n/** Record Separator — end of leading frames, start of raw answer text. */\nexport const END_OF_LEADING = '\\x1E'\n/** Unit Separator — start of the trailing usage JSON frame. */\nexport const TRAILER_SENTINEL = '\\x1F'\n\n// =============================================================================\n// Routing types (moved verbatim from the hub's `lib/chat/wire-frames.ts`;\n// `RouteComplexity` is lib-local so the lib never imports hub code)\n// =============================================================================\n\n/** Server-side routing complexity tiers. Mirror of the hub's\n *  `lib/constants/ai-models.ts` union — kept as a lib-local literal type\n *  so the wire SSOT has zero hub imports. The DECODER accepts any string\n *  (older/newer servers may ship values outside this union). */\nexport type RouteComplexity = 'trivial' | 'default' | 'complex' | 'deep'\n\n/**\n * Routing decision frame. Emitted once per turn as a LEADING frame\n * (between metadata and the `\\x1E` end-of-leading-frames sentinel) when\n * the server's `decideRoute()` produces a `Route`.\n *\n * Field names use the `routed*` prefix on PURPOSE — the client's\n * catch-all leading-frame branch inspects `meta.model` / `meta.modelLabel`.\n * A bare `model` field on this frame would OVERWRITE the metadata frame's\n * modelLabel and break the <ModelDisplay> badge. The `routed` prefix\n * sidesteps that collision.\n *\n * Older clients that don't recognize `kind: 'routing'` silently drop\n * the frame (the catch-all only matches frames carrying known fields\n * like `sources` / `modelLabel`).\n */\nexport interface RoutingFrame {\n  kind: 'routing'\n  routedComplexity: RouteComplexity\n  routedModel: string\n  routedThinkingBudget: number\n}\n\n/**\n * Optional field on the trailing usage frame's `breakdown`. Surfaces\n * per-route attribution to the admin test console so an operator can\n * see \"this turn ran on Haiku/trivial/$0.0005\" without re-parsing the\n * stream. Additive: clients that ignore unknown keys keep working.\n */\nexport interface RoutedAnswerBreakdown {\n  model: string\n  complexity: RouteComplexity\n  thinkingBudget: number\n}\n\n// =============================================================================\n// Leading frames\n// =============================================================================\n\n/** First frame on every stream — lets the shell render \"Thinking…\"\n *  within ~200ms, before any upstream byte arrives. */\nexport interface StatusThinkingFrame {\n  status: 'thinking'\n}\n\n/** Per-turn metadata frame. The emitter sends `options.metadata`\n *  verbatim. All fields optional — the decoder's catch-all treats ANY\n *  frame that didn't match a `kind` discriminant as metadata-ish. */\nexport interface ChatMetadataFrame {\n  /** Retrieval sources for the source-chip strip. Host-defined row\n   *  shape (`ChatSource` on the adapter side) — opaque at wire level. */\n  sources?: unknown[]\n  /** Raw model id (e.g. 'claude-sonnet-x'). Presence alone triggers the\n   *  client's meta merge; the value itself is not stored. */\n  model?: string\n  modelLabel?: string\n  provider?: string\n  contextWindowMaxTokens?: number\n  /** Per-message viewport-positioning hint ('top' | 'bottom'). */\n  scrollAnchor?: string\n  /** SERVER-minted conversation trace id (`chat_conversations.id`),\n   *  echoed on every turn's leading metadata frame. The client never\n   *  generates ids — the first turn of a session goes out id-less, the\n   *  server mints one and echoes it here, and the adapter persists the\n   *  echoed id as its ONLY local state (see\n   *  `chat-conversation-storage.ts` on the consumer side). */\n  conversationId?: string\n}\n\n/** Live adaptive-thinking delta. WIRE IS ALREADY DELTA (Anthropic\n *  thinking_delta slices) — decoders must NOT diff or re-accumulate. */\nexport interface ThinkingDeltaFrame {\n  kind: 'thinking-delta'\n  text: string\n}\n\n/** Usage frame emitted as a LEADING frame right after Anthropic's\n *  message_start (stage='start'). */\nexport interface UsageStartFrame {\n  kind: 'usage'\n  stage: 'start'\n  input_tokens: number\n  cache_read_input_tokens?: number\n  cache_creation_input_tokens?: number\n}\n\n/** Model preamble prose written BEFORE a tool_use block — emitted as a\n *  standalone leading frame so it isn't stranded behind the approval\n *  card (the `\\x1E` sentinel is suppressed on tool turns). */\nexport interface TextLeadingFrame {\n  kind: 'text-leading'\n  text: string\n}\n\n/** Tool invocation failed server-side (proposal persist error,\n *  validation, ownership denial …). */\nexport interface ToolErrorFrame {\n  kind: 'tool_error'\n  toolName?: string\n  toolUseId?: string\n  message: string\n}\n\nexport interface ApprovalRequestField {\n  label: string\n  value: string\n}\n\n/** The model called a write tool; the server persisted a proposal and\n *  ships a card-ready payload. Shape mirrors the hub's `onToolUse`\n *  return in `lib/data/doc-chat-utils.ts`. */\nexport interface ApprovalRequestFrame {\n  kind: 'approval_request'\n  proposalId: string\n  toolName: string\n  title?: string\n  fields?: ApprovalRequestField[]\n  preamble?: string | null\n  args?: Record<string, unknown>\n  expiresAt?: string\n  ttlSeconds?: number\n}\n\n/** A single write-tool turn produced MULTIPLE proposals (batch\n *  operations — \"delete both\", \"close all three\"). The SERVER is the\n *  authority on the grouping: it collects the turn's proposals and\n *  ships ONE batch frame instead of N `approval_request` frames, so\n *  clients render the bulk-approval component without any adjacency\n *  heuristics. Single-proposal turns keep the classic\n *  `approval_request` frame. */\nexport interface ApprovalBatchFrame {\n  kind: 'approval_batch'\n  /** Stable batch anchor — client-side only (locates the batch segment\n   *  for status flips). MUST NOT equal any row's `proposalId`: the\n   *  click-time optimistic status flip keys on this id, and an anchor\n   *  that doubles as a row id would falsely tick that row's execution\n   *  as succeeded before its confirm ran (masking a failed write). The\n   *  server emits `batch:<first proposalId>`. */\n  batchId: string\n  proposals: Array<{\n    proposalId: string\n    toolName: string\n    title?: string\n    fields?: ApprovalRequestField[]\n    expiresAt?: string\n    ttlSeconds?: number\n  }>\n}\n\n/** Server-driven post-approve / post-reject frame (confirm-tool route).\n *  Mirror of the hub's `DecisionResolvedFrame` in\n *  `lib/data/chat-agent-utils.ts` (tool_name widened to string — the\n *  known-write-tool whitelist is host-side knowledge). */\nexport interface DecisionResolvedFrame {\n  kind: 'decision_resolved'\n  /** The proposal this decision resolved — locates the SOURCE approval\n   *  card so the shell can flip its status + append the receipt. */\n  proposalId: string\n  ok: boolean\n  action: 'approved' | 'rejected'\n  tool_name?: string\n  result?: {\n    ticket_id?: string\n    status?: string | null\n    mirror_synced?: boolean\n  }\n  /** True when the server WILL pipe an auto-continuation turn after\n   *  this frame. */\n  willAutoContinue: boolean\n  /** Pre-rendered receipt copy from the source strategy's\n   *  `receiptRenderer` — client renders verbatim. */\n  receiptText?: string\n}\n\n/** Every leading frame the emitter can produce. */\nexport type SseLeadingFrame =\n  | StatusThinkingFrame\n  | ChatMetadataFrame\n  | RoutingFrame\n  | ThinkingDeltaFrame\n  | UsageStartFrame\n  | TextLeadingFrame\n  | ToolErrorFrame\n  | ApprovalRequestFrame\n  | ApprovalBatchFrame\n  | DecisionResolvedFrame\n\n// =============================================================================\n// Trailing usage frame\n// =============================================================================\n\n/** Card/chip emission counts for admin adoption tooling. */\nexport interface UsageTelemetry {\n  cards: number\n  chips: number\n  sentences: number\n  answerLen: number\n}\n\n/** Trailing usage frame — `\\x1F` + JSON, runs to stream end (no\n *  terminator). `stage: 'display'` is a legacy alias the decoder treats\n *  identically to `'end'`. */\nexport interface SseTrailingUsageFrame {\n  kind: 'usage'\n  stage: 'end' | 'display'\n  input_tokens?: number\n  output_tokens?: number\n  cache_read_input_tokens?: number\n  cache_creation_input_tokens?: number\n  hit_rate_pct?: number\n  telemetry?: UsageTelemetry\n  /** Cross-call usage breakdown (Haiku rewriter / classifier /\n   *  summarizer + routed-answer attribution). */\n  breakdown?: {\n    haikuRewriter?: { input: number; output: number }\n    haikuClassifier?: { input: number; output: number }\n    haikuSummarizer?: { input: number; output: number }\n    routedAnswer?: RoutedAnswerBreakdown\n  }\n  /** Gated deep-debug payload (only present when the request carried\n   *  the privileged debug secret). */\n  debug?: Record<string, unknown>\n}\n","/**\n * Normalized chat stream events — the transport-agnostic event union BOTH\n * decoders (SSE byte framing in `./decode.ts`, NATS chunks in\n * `./nats-decoder.ts`) emit. Phase 3 of the chat unification makes the\n * reducer consume these directly; today the SSE adapter maps them back to\n * legacy `MessageSegment` yields.\n *\n * Server-safe: no React, no browser APIs.\n */\n\n// The wire-frame shapes these events carry through are defined ONCE in\n// `./frames.ts` — reuse them here rather than restating their fields.\nimport type {\n  ApprovalRequestField,\n  DecisionResolvedFrame,\n  UsageTelemetry,\n} from './frames'\n// The ask card's option shape is the SEGMENT's shape — the decoder hands the\n// rows straight to the accumulator, so restating them here would be two\n// declarations of one wire contract. Type-only import from a React-free\n// module; `nats-decoder.ts` already depends on the same file for MESSAGE_TYPE.\nimport type { AskOptionData } from '../components/chat/types/message.types'\n\n/** Optional envelope on every event. `seq` carries the transport's\n *  stream sequence (JetStream `streamSeq` on NATS; unused on SSE). */\ninterface ChatStreamEventBase {\n  seq?: number\n}\n\n/** Stream entered answer-text mode (SSE: the `\\x1E` sentinel or the\n *  JSON-parse-failure fallback; NATS: MESSAGE_START).\n *  `implicit` is true ONLY for the SSE fallback path where a\n *  non-JSON leading buffer flips straight into text mode — consumers\n *  that flush coalesced thinking on turn-start must SKIP the flush for\n *  implicit starts (legacy adapter parity: the fallback path never\n *  flushed pending thinking before the text). */\nexport interface TurnStartEvent extends ChatStreamEventBase {\n  type: 'turn-start'\n  implicit?: boolean\n}\n\nexport interface TurnEndEvent extends ChatStreamEventBase {\n  type: 'turn-end'\n}\n\n/** Answer text delta. `leading: true` marks SSE `text-leading` frames\n *  (model preamble prose emitted BEFORE the tool approval card). */\nexport interface TextDeltaEvent extends ChatStreamEventBase {\n  type: 'text-delta'\n  text: string\n  leading?: boolean\n}\n\n/** Thinking delta — APPEND-ONLY contract: each event carries the next\n *  verbatim slice; consumers accumulate. Decoders never diff. */\nexport interface ThinkingDeltaEvent extends ChatStreamEventBase {\n  type: 'thinking-delta'\n  text: string\n}\n\n/** Guide-body delta (NATS `GUIDE` chunk) — the assistant's how-to /\n *  documentation answer, rendered as a titled \"OpenFrame Guide\" card rather\n *  than bare prose. Same APPEND-ONLY contract as `text-delta` /\n *  `thinking-delta`: each event carries the next verbatim slice and consumers\n *  coalesce into the trailing `guide` segment. NATS-only today — the SSE\n *  frame grammar has no guide frame — but it lives in the shared union\n *  because the reducer is transport-agnostic. */\nexport interface GuideDeltaEvent extends ChatStreamEventBase {\n  type: 'guide-delta'\n  text: string\n}\n\n/** Clarification card (NATS `ASK` chunk) — the assistant asking which reading\n *  of an ambiguous question to answer. NOT a delta: the chunk carries the\n *  finished card, so consumers push it whole instead of coalescing. `text` is\n *  the intro sentence the same chunk rides along with; consumers render it as\n *  ordinary answer text BEFORE the card. NATS-only — the SSE frame grammar has\n *  no ask frame — but it lives in the shared union because the reducer is\n *  transport-agnostic. */\nexport interface AskEvent extends ChatStreamEventBase {\n  type: 'ask'\n  text?: string\n  question: string\n  options: AskOptionData[]\n}\n\nexport interface StatusEvent extends ChatStreamEventBase {\n  type: 'status'\n  phase: 'thinking'\n}\n\n/** NATS tool execution progress (EXECUTING_TOOL / EXECUTED_TOOL). */\nexport interface ToolExecutionEvent extends ChatStreamEventBase {\n  type: 'tool-execution'\n  data: {\n    type: 'EXECUTING_TOOL' | 'EXECUTED_TOOL'\n    integratedToolType: string\n    toolFunction: string\n    toolTitle?: string\n    /**\n     * Human-readable \"why this tool is running\" line, rendered as the body of\n     * the tool card. Carried by the EXECUTING chunk ONLY — the EXECUTED one\n     * never repeats it, and the accumulator's merge keeps whatever the\n     * EXECUTING segment held (`toolData.toolExplanation ?? existing`). Omitting\n     * it from this contract blanks the card for the whole run, live and on\n     * replay, which is exactly what happened while it was missing here.\n     */\n    toolExplanation?: string\n    parameters?: Record<string, unknown>\n    result?: string\n    success?: boolean\n    toolExecutionRequestId?: string\n  }\n}\n\n/** Single tool call inside a batch approval request (NATS). */\nexport interface ApprovalToolCall {\n  toolExecutionRequestId: string\n  toolName: string\n  toolTitle?: string\n  toolExplanation?: string\n  toolType?: string\n  requiresApproval: boolean\n  approvalType?: string | null\n  toolCallArguments?: Record<string, unknown> | null\n}\n\n/** A tool call awaits user approval. SSE fills `command`/`fields`\n *  (card-ready payload); NATS fills `command`/`explanation` or\n *  `toolCalls` (batch form). */\nexport interface ApprovalRequestEvent extends ChatStreamEventBase {\n  type: 'approval-request'\n  requestId: string\n  /** SSE: the write tool's name. NATS: approval tier (USER/ADMIN/…). */\n  approvalType?: string\n  command?: string\n  explanation?: string\n  fields?: ApprovalRequestField[]\n  toolCalls?: ApprovalToolCall[]\n  status?: 'pending'\n  /** Set when the card came from a Product Guide frame — see {@link GuideOrigin}. */\n  origin?: GuideOrigin\n}\n\n/**\n * Marks an event whose payload is a Product Guide frame, whatever transport\n * carried it. It exists because ONE stream can now mix both worlds: the agent\n * re-streams the hub's frames into a NATS dialog, so a card typed the hub's way\n * (`approvalType` = the tool name, resolved through the hub's confirm route)\n * travels beside cards typed the agent's way (`approvalType` = an approval TIER\n * routed to human escalation).\n *\n * Consumers read it to keep the guide half behaving exactly as it does in the\n * hub's own chat — NOT to give it special treatment. Without it the NATS kernel\n * would have to guess from `approvalType`, and every tool the hub adds would\n * silently fall into the escalation path.\n */\nexport type GuideOrigin = 'guide'\n\n/** The only value of {@link GuideOrigin}. Lives beside the type, and beside the\n *  predicate below, because both decoders and every consumer that branches on\n *  provenance must compare against the same token — a bare `'guide'` literal\n *  typo silently disables the branch instead of failing to compile. */\nexport const GUIDE_ORIGIN: GuideOrigin = 'guide'\n\n/** True for anything stamped as coming from the Product Guide — a stream event\n *  or the `data` of a segment built from one. */\nexport function isGuideOrigin(\n  source: { origin?: GuideOrigin | string } | null | undefined,\n): boolean {\n  return source?.origin === GUIDE_ORIGIN\n}\n\n/** An approval request was resolved (SSE `decision_resolved` frame /\n *  NATS APPROVAL_RESULT chunk). */\nexport interface ApprovalResolvedEvent extends ChatStreamEventBase {\n  type: 'approval-resolved'\n  requestId?: string\n  status: 'approved' | 'rejected'\n  ok?: boolean\n  toolName?: string\n  approvalType?: string\n  resolvedByName?: string | null\n  receiptText?: string\n  result?: DecisionResolvedFrame['result']\n  willAutoContinue?: boolean\n  /** Set when the resolution came from a Product Guide frame — see {@link GuideOrigin}. */\n  origin?: GuideOrigin\n}\n\n/** The client is offered a handoff of this ticket to a human technician\n *  (NATS `ESCALATION_OFFER` chunk in state PENDING). Distinct from\n *  `approval-request`: it resolves through the ticket-escalation mutations,\n *  never the tool-approval flow, and it is raised by four different origins\n *  (Fae's own tool call, the client's header button, a deterministic\n *  trigger, or a deferred surfacing at turn end) that all render alike. */\nexport interface EscalationOfferEvent extends ChatStreamEventBase {\n  type: 'escalation-offer'\n  offerId: string\n  text: string\n  origin?: string\n}\n\n/** Wire vocabulary of `EscalationOfferData.state`. */\nexport const ESCALATION_STATE = {\n  PENDING: 'PENDING',\n  APPROVED: 'APPROVED',\n  DECLINED: 'DECLINED',\n  SUPERSEDED: 'SUPERSEDED',\n} as const\n\n/** Terminal wire state → the shared `ChatApprovalStatus` vocabulary; `null`\n *  for PENDING or anything unrecognized. Shared by BOTH decoders (live\n *  chunks and persisted rows) so a state can never mean two things. */\nexport function escalationResolvedStatus(\n  state: unknown,\n): EscalationOfferResolvedEvent['status'] | null {\n  switch (state) {\n    case ESCALATION_STATE.APPROVED:\n      return 'approved'\n    case ESCALATION_STATE.DECLINED:\n      return 'rejected'\n    case ESCALATION_STATE.SUPERSEDED:\n      return 'cancelled'\n    default:\n      return null\n  }\n}\n\n/** An escalation offer reached a terminal state. The wire's SUPERSEDED\n *  (the client typed over a pending offer) maps onto `cancelled` so the\n *  whole stack keeps ONE status vocabulary (`ChatApprovalStatus`). The\n *  resolved chunk carries no text — consumers flip the existing block. */\nexport interface EscalationOfferResolvedEvent extends ChatStreamEventBase {\n  type: 'escalation-offer-resolved'\n  offerId: string\n  status: 'approved' | 'rejected' | 'cancelled'\n  resolvedByName?: string | null\n}\n\n/** The conversation was handed off to a human technician (`TICKET_ESCALATED`).\n *  A first-class block rather than something inferred from an offer's state,\n *  so paths that raise no offer at all — the inactivity auto-escalation — still\n *  produce a receipt. `text` is nullable on the wire; consumers supply the\n *  fallback copy. */\nexport interface TicketEscalatedEvent extends ChatStreamEventBase {\n  type: 'ticket-escalated'\n  ticketId: string\n  ticketNumber?: number\n  reason: string\n  text?: string\n}\n\n/** Ticket lifecycle receipt (`TICKET_EVENT`) — the ticket was resolved,\n *  reopened, etc. `kind` is an OPEN vocabulary (RESOLVED/REOPENED today):\n *  an unknown kind still decodes and renders as a neutral line rather than\n *  being dropped, so the backend can add kinds without a client release.\n *  Arrives standalone (outside MESSAGE_START/END), like `ticket-escalated`. */\nexport interface TicketEventEvent extends ChatStreamEventBase {\n  type: 'ticket-event'\n  kind: string\n  actorId?: string\n  actorName?: string\n  /** Who acted — e.g. an AI agent vs a human technician. Open string. */\n  actorType?: string\n  reason?: string\n  /** Kind-token the ticket reopened INTO (AI_ASSISTANCE / TECH_REQUIRED / ...). */\n  targetStatusKind?: string\n}\n\n/** Per-turn metadata. Raw wire values pass through UNVALIDATED — the\n *  consumer replicates the legacy truthiness/typeof gates (so a\n *  malformed frame degrades identically to the pre-SSOT parser). */\nexport interface ChatMetadataEvent extends ChatStreamEventBase {\n  type: 'metadata'\n  provider?: string | null\n  modelLabel?: string | null\n  /** Raw model id (SSE `meta.model`; NATS `modelName`). */\n  modelName?: string | null\n  contextWindowMaxTokens?: number | null\n  sources?: unknown\n  scrollAnchor?: unknown\n  /** Server-minted conversation id (`ChatMetadataFrame.conversationId`),\n   *  passed through raw like every other catch-all field — the consumer\n   *  applies the `typeof === 'string' && truthy` gate. */\n  conversationId?: string | null\n  routing?: {\n    routedComplexity: string\n    routedModel?: string\n    routedThinkingBudget: number | null\n  }\n  /**\n   * Set when the metadata came from a Product Guide frame — see\n   * {@link GuideOrigin}. Such an event carries ONLY `conversationId`: it exists\n   * to record the hub's conversation id (every confirm-tool call must quote it\n   * back), NOT to describe the dialog's model, which stays the agent's.\n   */\n  origin?: GuideOrigin\n}\n\n/** SSE usage frames — raw wire keys (snake_case) preserved. */\nexport interface UsageEvent extends ChatStreamEventBase {\n  type: 'usage'\n  stage: 'start' | 'end'\n  input_tokens?: number\n  output_tokens?: number\n  cache_read_input_tokens?: number\n  cache_creation_input_tokens?: number\n  hit_rate_pct?: number\n  telemetry?: UsageTelemetry\n  /** Left OPAQUE on purpose: the consumer (`chat-stream-reducer`'s\n   *  `applySseUsage`) re-validates every nested field with the legacy\n   *  truthiness/typeof gates, so a malformed frame degrades identically\n   *  to the pre-SSOT parser. Typing it would imply guarantees the wire\n   *  does not make. */\n  breakdown?: unknown\n  debug?: unknown\n}\n\n/** NATS TOKEN_USAGE chunk (camelCase backend shape). */\nexport interface TokenUsageEvent extends ChatStreamEventBase {\n  type: 'token-usage'\n  inputTokensSize: number\n  outputTokensSize: number\n  totalTokensSize: number\n  contextSize: number\n}\n\nexport interface CompactionEvent extends ChatStreamEventBase {\n  type: 'compaction'\n  phase: 'start' | 'end'\n  summary?: string\n}\n\nexport interface ErrorEvent extends ChatStreamEventBase {\n  type: 'error'\n  title: string\n  details?: string\n}\n\n/** Non-assistant message on the stream (NATS): the user's own message\n *  echo (`message-request`), an operator direct message, or a system\n *  line. */\n/**\n * Dialog participant owner type. The two members below are the ones clients\n * BRANCH ON (admin-vs-not decides echo dedup and author styling); the union\n * stays open (`string & {}`) because the wire may carry other roles that the\n * client treats as \"not admin\". Always compare against `CHAT_OWNER_ADMIN`,\n * never a bare `'ADMIN'` literal — a typo in a literal silently disables\n * dedup instead of failing to compile.\n */\nexport type ChatOwnerType = 'ADMIN' | 'CLIENT' | (string & {})\n\n/** Canonical ADMIN owner-type token (see `ChatOwnerType`). */\nexport const CHAT_OWNER_ADMIN = 'ADMIN'\n\nexport interface ParticipantEvent extends ChatStreamEventBase {\n  type: 'participant'\n  kind: 'message-request' | 'direct-message' | 'system'\n  text: string\n  ownerType?: ChatOwnerType\n  displayName?: string\n  userId?: string\n  contextItems?: Array<{ type: string; id: string; label: string }>\n}\n\nexport interface DialogClosedEvent extends ChatStreamEventBase {\n  type: 'dialog-closed'\n}\n\nexport type ChatStreamEvent =\n  | TurnStartEvent\n  | TurnEndEvent\n  | TextDeltaEvent\n  | ThinkingDeltaEvent\n  | GuideDeltaEvent\n  | AskEvent\n  | StatusEvent\n  | ToolExecutionEvent\n  | ApprovalRequestEvent\n  | ApprovalResolvedEvent\n  | EscalationOfferEvent\n  | EscalationOfferResolvedEvent\n  | TicketEscalatedEvent\n  | TicketEventEvent\n  | ChatMetadataEvent\n  | UsageEvent\n  | TokenUsageEvent\n  | CompactionEvent\n  | ErrorEvent\n  | ParticipantEvent\n  | DialogClosedEvent\n","/**\n * SSE wire-protocol encoders — the emit side of the framing contract in\n * `./frames.ts`. Server-safe (TextEncoder only). The hub's stream route\n * consumes these so emitter and decoder can never drift.\n */\n\nimport {\n  FRAME_TERMINATOR,\n  END_OF_LEADING,\n  TRAILER_SENTINEL,\n  type SseLeadingFrame,\n  type SseTrailingUsageFrame,\n} from './frames'\n\nconst encoder = new TextEncoder()\n\n/** Matches every framing sentinel byte: `\\0`, `\\x1E`, `\\x1F`. THE one\n *  regex — `stripSentinelBytes` is its only consumer. */\nconst SENTINEL_BYTES_RE = /[\\u0000\\u001E\\u001F]/g\n\n/**\n * Remove the SSE framing sentinel bytes (`\\0`, `\\x1E`, `\\x1F`) from model\n * text. Exported as a STANDALONE helper — not just folded into\n * `encodeTextDelta` — because producers must strip at the point where they\n * first take custody of the text, BEFORE any local accumulation.\n *\n * Concretely: the hub's `claude-util.ts` accumulates `state.answerText` for\n * persistence + telemetry. If stripping only happened at encode time, the\n * persisted/telemetered answer would diverge, character for character, from\n * the bytes the user actually received whenever the model echoes a control\n * byte. Both sides call this, so there is exactly ONE definition of \"which\n * bytes are framing\" in the whole system — do not re-declare the regex.\n *\n * Idempotent and per-character, so it distributes over concatenation:\n * `strip(a + b) === strip(a) + strip(b)` — safe to apply per-delta or on the\n * accumulated string interchangeably.\n */\nexport function stripSentinelBytes(text: string): string {\n  return text.replace(SENTINEL_BYTES_RE, '')\n}\n\n/** Encode one leading frame: JSON + `\\0` terminator. */\nexport function encodeLeadingFrame(frame: SseLeadingFrame): Uint8Array {\n  return encoder.encode(JSON.stringify(frame) + FRAME_TERMINATOR)\n}\n\n/** Encode the single `\\x1E` end-of-leading-frames sentinel. */\nexport function encodeEndOfLeading(): Uint8Array {\n  return encoder.encode(END_OF_LEADING)\n}\n\n/**\n * Encode a raw answer-text delta. STRIPS the framing sentinel bytes via\n * `stripSentinelBytes` so model-echoed control bytes can no longer\n * mis-frame the stream (a literal `\\x1F` in the answer used to flip the\n * client into trailer mode and swallow the rest of the answer PLUS the real\n * usage trailer — characterized by golden fixture (d)). Forward-looking\n * encoder fix only; the decoder's behavior is unchanged.\n *\n * Producers that ALSO accumulate the text locally (persistence, telemetry)\n * must call `stripSentinelBytes` on their own copy — see its docblock.\n */\nexport function encodeTextDelta(text: string): Uint8Array {\n  return encoder.encode(stripSentinelBytes(text))\n}\n\n/** Encode the trailing usage frame: `\\x1F` + JSON, no terminator\n *  (runs to stream end). */\nexport function encodeTrailingUsageFrame(frame: SseTrailingUsageFrame): Uint8Array {\n  return encoder.encode(TRAILER_SENTINEL + JSON.stringify(frame))\n}\n","/**\n * Leading-frame → `ChatStreamEvent` table — the ONE mapping both transports read.\n *\n * The SSE decoder (`./decode.ts`) reaches it while walking the hub's byte\n * framing. The NATS decoder (`./nats-decoder.ts`) reaches it for `GUIDE` chunks\n * carrying a `payload`: the saas-ai-agent re-streams the Product Guide's hub\n * frames VERBATIM into the Mingo chunk stream (`GuideStreamingService`), so the\n * bytes a guide answer is made of are the same frames on both transports — only\n * the envelope differs. Decoding them twice would drift the moment the hub adds\n * a frame kind, which is why this table lives outside either decoder.\n *\n * The NATS side does NOT take every event this table can produce; see\n * `guideFrameEvent` in `./nats-decoder.ts` for which ones cross over and why.\n *\n * Server-safe: no React, no browser APIs.\n */\n\nimport type { ChatStreamEvent } from './events'\n\n/**\n * Escape `<` so markdown renderers that pass HTML through (rehypeRaw)\n * don't treat XML-like tokens in Claude's thinking output as elements.\n * `<` → `&lt;` preserves the visible character without breaking\n * blockquote `>` markers. Per-character, so it distributes over\n * concatenation: escape(a + b) === escape(a) + escape(b) — callers may\n * apply it per-delta or on the accumulated string interchangeably.\n */\nexport function escapeThinkingTags(text: string): string {\n  return text.replace(/</g, '&lt;')\n}\n\n/**\n * Map one parsed leading frame to normalized events, replicating the\n * legacy parser's else-if chain ORDER and its exact truthiness/typeof\n * gates. Frames that matched a branch but failed its inner validation\n * (e.g. `routing` without a string `routedComplexity`) produce NO event,\n * exactly like the legacy no-op.\n *\n * Every branch pushes AT MOST ONE event — `guideFrameEvent` relies on that\n * to hand a single event back to `decodeNatsChunk`'s per-chunk contract.\n */\nexport function mapLeadingFrame(meta: any, out: ChatStreamEvent[]): void {\n  if (meta.status === 'thinking') {\n    out.push({ type: 'status', phase: 'thinking' })\n  } else if (meta.kind === 'thinking-delta' && typeof meta.text === 'string') {\n    // Wire is ALREADY delta — emit verbatim, append-only contract.\n    out.push({ type: 'thinking-delta', text: meta.text })\n  } else if (meta.kind === 'usage' && meta.stage === 'start') {\n    out.push({\n      type: 'usage',\n      stage: 'start',\n      input_tokens: meta.input_tokens,\n      cache_read_input_tokens: meta.cache_read_input_tokens,\n      cache_creation_input_tokens: meta.cache_creation_input_tokens,\n    })\n  } else if (meta.kind === 'decision_resolved' && typeof meta.action === 'string') {\n    const status = meta.action === 'rejected' ? 'rejected' : 'approved'\n    const toolName = typeof meta.tool_name === 'string' ? meta.tool_name : undefined\n    const result = meta.result ?? null\n    out.push({\n      type: 'approval-resolved',\n      status,\n      ok: meta.ok === true,\n      willAutoContinue: meta.willAutoContinue === true,\n      ...(toolName ? { toolName } : {}),\n      ...(result ? { result } : {}),\n      ...(typeof meta.receiptText === 'string' ? { receiptText: meta.receiptText } : {}),\n      requestId: typeof meta.proposalId === 'string' ? meta.proposalId : undefined,\n    })\n  } else if (meta.kind === 'approval_batch' && meta.batchId && Array.isArray(meta.proposals)) {\n    // Server-grouped multi-proposal turn → ONE batch event carrying a\n    // tool-call row per proposal. `toolExecutionRequestId` is the row's\n    // PROPOSAL id (each row resolves through its own per-proposal\n    // confirm); the batch's `requestId` is the stable anchor the shell\n    // uses for status flips. Field rows ride as the row's expandable\n    // args so per-proposal detail stays reachable inside the batch.\n    const toolCalls = (meta.proposals as Array<Record<string, any>>)\n      .filter((p) => p && typeof p.proposalId === 'string')\n      .map((p) => {\n        const rawFields = Array.isArray(p.fields)\n          ? (p.fields as Array<{ label?: string; value?: string }>).filter(\n              (f) => f && f.label && f.value,\n            )\n          : []\n        // Prefer human-readable identity labels for the row's\n        // disambiguator; opaque-id labels (\"Task\", \"Ticket\") only as\n        // the last-resort first field.\n        const detail =\n          rawFields.find((f) => /^(title|subject|name)$/i.test(String(f.label))) ??\n          rawFields[0]\n        const base =\n          typeof p.title === 'string' && p.title.length > 0 ? p.title : String(p.toolName ?? 'Tool call')\n        return {\n          toolExecutionRequestId: String(p.proposalId),\n          toolName: String(p.toolName ?? 'tool'),\n          toolTitle: detail ? `${base} — ${detail.value}` : base,\n          requiresApproval: true,\n          toolCallArguments:\n            rawFields.length > 0\n              ? Object.fromEntries(rawFields.map((f) => [String(f.label), String(f.value)]))\n              : null,\n        }\n      })\n    if (toolCalls.length > 0) {\n      out.push({\n        type: 'approval-request',\n        requestId: String(meta.batchId),\n        approvalType: 'chat',\n        toolCalls,\n        status: 'pending',\n      })\n    }\n  } else if (meta.kind === 'approval_request' && meta.proposalId) {\n    const proposalId = String(meta.proposalId)\n    const toolName = String(meta.toolName ?? 'tool')\n    const headline =\n      typeof meta.title === 'string' && meta.title.length > 0 ? meta.title : toolName\n    const rawFields = Array.isArray(meta.fields)\n      ? (meta.fields as Array<{ label?: string; value?: string }>)\n      : []\n    const fields: Array<{ label: string; value: string }> = []\n    for (const f of rawFields) {\n      if (!f || !f.label || !f.value) continue\n      fields.push({ label: f.label, value: f.value })\n    }\n    out.push({\n      type: 'approval-request',\n      requestId: proposalId,\n      approvalType: toolName,\n      command: headline,\n      fields,\n      status: 'pending',\n    })\n  } else if (meta.kind === 'text-leading' && typeof meta.text === 'string') {\n    out.push({ type: 'text-delta', text: meta.text, leading: true })\n  } else if (meta.kind === 'tool_error') {\n    const msg =\n      typeof meta.message === 'string' && meta.message.length > 0\n        ? meta.message\n        : 'Could not complete the requested action right now.'\n    out.push({ type: 'error', title: msg })\n  } else if (meta.kind === 'routing') {\n    if (typeof meta.routedComplexity === 'string') {\n      out.push({\n        type: 'metadata',\n        routing: {\n          routedComplexity: meta.routedComplexity,\n          ...(typeof meta.routedModel === 'string' ? { routedModel: meta.routedModel } : {}),\n          routedThinkingBudget:\n            typeof meta.routedThinkingBudget === 'number' ? meta.routedThinkingBudget : null,\n        },\n      })\n    }\n  } else {\n    // Catch-all metadata-ish frame. Raw values pass through UNVALIDATED\n    // (possibly undefined) so the consumer can replicate the legacy\n    // presence/truthiness gates exactly — including the `model`-presence\n    // trigger whose value is never stored.\n    out.push({\n      type: 'metadata',\n      sources: meta.sources,\n      provider: meta.provider,\n      modelLabel: meta.modelLabel,\n      modelName: meta.model,\n      contextWindowMaxTokens: meta.contextWindowMaxTokens,\n      scrollAnchor: meta.scrollAnchor,\n      conversationId: meta.conversationId,\n    })\n  }\n}\n","/**\n * SSE wire-protocol decoder — a timer-free synchronous state machine,\n * mechanically extracted from `useSseChatAdapter`'s inline\n * `createDocStreamFn` parser. Byte-for-byte behavior parity with the\n * legacy parser is the contract (the golden fixtures in\n * `src/components/chat/hooks/__tests__/sse-stream-golden.test.ts` pin it\n * through the full hook path), including its quirks:\n *\n *   - A leading `\\0`-terminated block that fails JSON.parse flips the\n *     stream into text mode and the WHOLE buffer (including the `\\0`)\n *     is emitted as answer text (legacy no-frame stream fallback). This\n *     transition is marked `turn-start { implicit: true }`.\n *   - In text mode only `\\x1F` is scanned — literal `\\0` / `\\x1E` bytes\n *     pass through into the answer; the FIRST literal `\\x1F` flips into\n *     trailer mode and everything after is captured as the trailer\n *     (golden fixture (d) characterizes this mis-framing; the fix lives\n *     in `encode.ts`'s sentinel stripping, NOT here).\n *   - The `\\x1E` sentinel and the `\\x1F` trailer can arrive in ONE TCP\n *     chunk (fixed-answer responses) — the post-sentinel slice is\n *     re-scanned for the trailer sentinel.\n *   - Multi-byte UTF-8 across chunk boundaries survives via a single\n *     streaming TextDecoder (`{ stream: true }` on every push; no final\n *     flush — a trailing partial code point is dropped, as legacy did).\n *   - `end()` parses the accumulated trailer (malformed → silently\n *     ignored) and drops any un-terminated leading buffer, as legacy did.\n *     It is IDEMPOTENT (a deliberate deviation from legacy): repeat calls\n *     emit nothing, so an adapter that ends in both its completion path\n *     and its `finally` cannot double-count the usage frame.\n *\n * State flow: leading → (sentinel | parse-failure) → text → (\\x1F) →\n * trailer-accumulate → end().\n *\n * Server-safe: no React, no timers, TextDecoder only.\n */\n\nimport { FRAME_TERMINATOR, END_OF_LEADING, TRAILER_SENTINEL } from './frames'\nimport type { ChatStreamEvent } from './events'\n// The frame table is SHARED with the NATS decoder — guide answers re-stream the\n// hub's frames verbatim inside `GUIDE` chunks — so it lives in its own module.\nimport { mapLeadingFrame } from './leading-frames'\n\nexport interface SseFrameDecoder {\n  /** Feed raw response bytes; returns the events they produced. */\n  push(bytes: Uint8Array): ChatStreamEvent[]\n  /**\n   * Signal end-of-stream; parses the trailing usage frame if present.\n   * IDEMPOTENT — every call after the first returns `[]`. Adapters\n   * routinely call this from BOTH their completion path and a `finally`,\n   * and a re-emitted `usage`/`stage:'end'` event would double-count token\n   * usage (displayed cost doubles).\n   */\n  end(): ChatStreamEvent[]\n}\n\n// `escapeThinkingTags` moved to `./leading-frames` alongside the frame table,\n// and is re-exported here: consumers have imported it from this module since it\n// was written, and the `chat-protocol` barrel re-exports it from here.\nexport { escapeThinkingTags } from './leading-frames'\n\nexport function createSseFrameDecoder(): SseFrameDecoder {\n  const textDecoder = new TextDecoder()\n  let buffer = ''\n  let inText = false\n  let inTrailer = false\n  let trailerBuffer = ''\n  let ended = false\n\n  function push(bytes: Uint8Array): ChatStreamEvent[] {\n    const out: ChatStreamEvent[] = []\n    const chunk = textDecoder.decode(bytes, { stream: true })\n\n    if (!inText) {\n      buffer += chunk\n      while (!inText) {\n        const recIdx = buffer.indexOf(END_OF_LEADING)\n        const nullIdx = buffer.indexOf(FRAME_TERMINATOR)\n        if (recIdx !== -1 && (nullIdx === -1 || recIdx < nullIdx)) {\n          inText = true\n          out.push({ type: 'turn-start' })\n          const after = buffer.slice(recIdx + 1)\n          buffer = ''\n          if (after) {\n            // The `after` slice may ALSO contain the `\\x1F` trailing-\n            // usage sentinel — common for fixed-answer responses where\n            // the whole frame sequence arrives in ONE TCP chunk.\n            const unitIdx = after.indexOf(TRAILER_SENTINEL)\n            if (unitIdx === -1) {\n              out.push({ type: 'text-delta', text: after })\n            } else {\n              const textBefore = after.slice(0, unitIdx)\n              const trailerAfter = after.slice(unitIdx + 1)\n              if (textBefore) {\n                out.push({ type: 'text-delta', text: textBefore })\n              }\n              inTrailer = true\n              trailerBuffer = trailerAfter\n            }\n          }\n          break\n        }\n        if (nullIdx === -1) break // need more bytes\n        const metaStr = buffer.slice(0, nullIdx)\n        const remaining = buffer.slice(nullIdx + 1)\n        let meta: any\n        try {\n          meta = JSON.parse(metaStr)\n        } catch {\n          // Not JSON — start of answer body. The WHOLE buffer (including\n          // the `\\0`) becomes answer text; `implicit` tells consumers\n          // this was the fallback path, not the `\\x1E` sentinel.\n          inText = true\n          out.push({ type: 'turn-start', implicit: true })\n          if (buffer.length > 0) {\n            out.push({ type: 'text-delta', text: buffer })\n            buffer = ''\n          }\n          break\n        }\n        mapLeadingFrame(meta, out)\n        buffer = remaining\n      }\n    } else if (inTrailer) {\n      trailerBuffer += chunk\n    } else {\n      // Text mode: only the `\\x1F` trailer sentinel is scanned. The\n      // unconditional emit (even for an empty decode of a partial\n      // multi-byte code point) mirrors the legacy per-chunk yield.\n      const sepIdx = chunk.indexOf(TRAILER_SENTINEL)\n      if (sepIdx === -1) {\n        out.push({ type: 'text-delta', text: chunk })\n      } else {\n        const before = chunk.slice(0, sepIdx)\n        const after = chunk.slice(sepIdx + 1)\n        if (before) out.push({ type: 'text-delta', text: before })\n        inTrailer = true\n        trailerBuffer = after\n      }\n    }\n    return out\n  }\n\n  function end(): ChatStreamEvent[] {\n    // Idempotency guard: a second end() must emit NOTHING. The trailer is\n    // also cleared so no later push()/end() pair can replay it.\n    if (ended) return []\n    ended = true\n    const out: ChatStreamEvent[] = []\n    if (trailerBuffer.length > 0) {\n      const raw = trailerBuffer\n      trailerBuffer = ''\n      try {\n        const meta = JSON.parse(raw)\n        if (meta.kind === 'usage' && (meta.stage === 'end' || meta.stage === 'display')) {\n          out.push({\n            type: 'usage',\n            stage: 'end',\n            input_tokens: meta.input_tokens,\n            output_tokens: meta.output_tokens,\n            cache_read_input_tokens: meta.cache_read_input_tokens,\n            cache_creation_input_tokens: meta.cache_creation_input_tokens,\n            hit_rate_pct: meta.hit_rate_pct,\n            telemetry: meta.telemetry,\n            breakdown: meta.breakdown,\n            debug: meta.debug,\n          })\n        }\n      } catch {\n        // Malformed trailer — silently ignore (legacy parity).\n      }\n    }\n    return out\n  }\n\n  return { push, end }\n}\n","/**\n * Message-related types\n * Contains all message structures, segments, and content types\n */\n\nimport type { AssistantType, AuthorType, ChatApprovalStatus, MessageOwner } from './chat.types'\n\n// ========== Message Type Definitions ==========\n\nexport const MESSAGE_TYPE = {\n  TEXT: 'TEXT',\n  THINKING: 'THINKING',\n  GUIDE: 'GUIDE',\n  ASK: 'ASK',\n  EXECUTING_TOOL: 'EXECUTING_TOOL',\n  EXECUTED_TOOL: 'EXECUTED_TOOL',\n  APPROVAL_REQUEST: 'APPROVAL_REQUEST',\n  APPROVAL_RESULT: 'APPROVAL_RESULT',\n  ESCALATION_OFFER: 'ESCALATION_OFFER',\n  TICKET_ESCALATED: 'TICKET_ESCALATED',\n  TICKET_EVENT: 'TICKET_EVENT',\n  ERROR: 'ERROR',\n  MESSAGE_START: 'MESSAGE_START',\n  MESSAGE_END: 'MESSAGE_END',\n  MESSAGE_REQUEST: 'MESSAGE_REQUEST',\n  AI_METADATA: 'AI_METADATA',\n  TOKEN_USAGE: 'TOKEN_USAGE',\n  CONTEXT_COMPACTION_START: 'CONTEXT_COMPACTION_START',\n  CONTEXT_COMPACTION_END: 'CONTEXT_COMPACTION_END',\n  DIRECT_MESSAGE: 'DIRECT_MESSAGE',\n  SYSTEM: 'SYSTEM',\n  DIALOG_CLOSED: 'DIALOG_CLOSED',\n} as const\n\nexport type MessageType = typeof MESSAGE_TYPE[keyof typeof MESSAGE_TYPE]\n\n// ========== Scroll Anchor (per-message render hint) ==========\n\n/** Per-message viewport-positioning hint sent on the per-turn metadata\n *  leading frame at the START of every assistant response. The chat\n *  message-list reads it to override the default `use-stick-to-bottom`\n *  tail behaviour for a single message. Field is OPTIONAL — when omitted\n *  (or set to `'bottom'`) the chat tails as today. Only `'top'` opts in\n *  to the alternative behaviour (used by display-action answers whose\n *  body is a long article and should be read top-down). */\nexport const SCROLL_ANCHOR = { TOP: 'top', BOTTOM: 'bottom' } as const\n\nexport type ScrollAnchor = typeof SCROLL_ANCHOR[keyof typeof SCROLL_ANCHOR]\n\n// ========== Tool Execution Types ==========\n\nexport interface ToolExecutionData {\n  type: 'EXECUTING_TOOL' | 'EXECUTED_TOOL'\n  integratedToolType: string\n  toolFunction: string\n  /** Backend-issued human-readable title (mirrors `PendingToolCallData.toolTitle`). */\n  toolTitle?: string\n  /**\n   * Backend-issued human-readable explanation of what the tool is doing and why\n   * (mirrors `PendingToolCallData.toolExplanation`). Only sent on `EXECUTING_TOOL`;\n   * the accumulator restores it onto the merged `EXECUTED_TOOL` segment.\n   */\n  toolExplanation?: string\n  parameters?: Record<string, any>\n  result?: string\n  success?: boolean\n  /**\n   * Backend-issued id (matches `PendingToolCallData.toolExecutionRequestId`).\n   * When present, lets the accumulator merge this execution event into the\n   * matching approval batch row instead of emitting a standalone segment.\n   */\n  toolExecutionRequestId?: string\n}\n\n/**\n * Snapshot of an in-flight tool kept between the `EXECUTING_TOOL` and\n * `EXECUTED_TOOL` events. The backend only sends `toolTitle` on\n * `EXECUTING_TOOL`; carrying this state lets the accumulator restore it onto\n * the merged `EXECUTED_TOOL` segment instead of falling back to the raw\n * `toolFunction`.\n */\nexport interface ExecutingToolState {\n  integratedToolType: string\n  toolFunction: string\n  /** Mirrors {@link ToolExecutionData.toolTitle}; absent on `EXECUTED_TOOL`. */\n  toolTitle?: string\n  /** Mirrors {@link ToolExecutionData.toolExplanation}; absent on `EXECUTED_TOOL`. */\n  toolExplanation?: string\n  parameters?: Record<string, any>\n}\n\n// ========== Approval Request Types ==========\n\nexport interface ApprovalRequestField {\n  /** Short label — e.g. \"Subject\", \"Priority\". Rendered in a muted\n   *  caps style above the value. */\n  label: string\n  /** Free-text value. Wraps and line-breaks are preserved\n   *  (`whitespace-pre-wrap`). */\n  value: string\n}\n\nexport interface ApprovalRequestData {\n  command: string\n  /** Structured field list — preferred over `explanation`. When set,\n   *  the approval card renders a vertical label/value stack with\n   *  proper spacing. Falls back to `explanation` (a single paragraph)\n   *  when omitted. Keep BOTH when you want hosts on older lib\n   *  versions to still see the prose; new hosts should send only\n   *  `fields`. */\n  fields?: ApprovalRequestField[]\n  explanation?: string\n  icon?: React.ReactNode\n  requestId?: string\n  approvalRequestId?: string\n  approvalType?: string\n  /** `'guide'` when the card came from a Product Guide frame (see `GuideOrigin`\n   *  in `chat-protocol/events`). Carried onto the segment because hosts route\n   *  and filter approvals by where they came from: mingo lifts its OWN pending\n   *  cards into a sticky footer, and a guide card must stay inline in the turn\n   *  the way it does in the hub's chat. */\n  origin?: 'guide'\n}\n\nexport interface ApprovalResultData {\n  approvalRequestId: string\n  approved: boolean\n  approvalType?: string\n  /** Display name of the user who resolved the request; null/absent for system actions. */\n  resolvedByName?: string | null\n}\n\n// ========== Escalation Offer Types ==========\n\nexport interface EscalationOfferData {\n  offerId: string\n  /** Backend-fixed card copy; the client never composes it. */\n  text: string\n  /** Why the offer was raised (`TOOL`, `MANUAL`, a trigger reason). Carried\n   *  for telemetry — every origin renders the same card. */\n  origin?: string\n}\n\n// ========== Ticket Escalated Types ==========\n\n/** Open union: the wire currently defines only `INACTIVITY`, and the block\n *  renders from `text` rather than branching on this, so a reason added\n *  server-side needs no client change. */\nexport type TicketEscalationReason = 'INACTIVITY' | (string & {})\n\n/** The handoff receipt — a real block on the wire, not something the client\n *  infers from an escalation offer's state. */\nexport interface TicketEscalatedData {\n  ticketId: string\n  ticketNumber?: number\n  reason: TicketEscalationReason\n  /** Backend-authored explanation; the card falls back to generic copy when\n   *  the wire omits it (the field is nullable). */\n  text?: string\n}\n\n// ========== Ticket Event Types ==========\n\n/** Known `TicketEventData.kind` values. The vocabulary is OPEN on the wire:\n *  an unknown kind still renders (as a neutral info line) rather than being\n *  dropped, so the backend can add lifecycle kinds without a client release. */\nexport const TICKET_EVENT_KIND = {\n  RESOLVED: 'RESOLVED',\n  REOPENED: 'REOPENED',\n} as const\n\nexport type TicketEventKind =\n  | typeof TICKET_EVENT_KIND[keyof typeof TICKET_EVENT_KIND]\n  | (string & {})\n\n/** Ticket lifecycle receipt (`TICKET_EVENT`) — the ticket was resolved,\n *  reopened, etc. Same field names live (NATS chunk) and persisted\n *  (GraphQL `TicketEventData` inside `messageData`), so ONE mapper covers\n *  both paths. All actor fields are optional: the event stays renderable\n *  when the backend can't attribute it. */\nexport interface TicketEventData {\n  kind: TicketEventKind\n  actorId?: string\n  actorName?: string\n  /** Who acted — e.g. an AI agent vs a human. Open string like `kind`. */\n  actorType?: string\n  reason?: string\n  /** Kind-token of the status the ticket reopened INTO (AI_ASSISTANCE /\n   *  TECH_REQUIRED / CUSTOM / ...) — same open vocabulary the lifecycle\n   *  statuses use. Picks the REOPENED card's subtitle deterministically;\n   *  absent on RESOLVED events and older backends. */\n  targetStatusKind?: string\n}\n\n/**\n * Single tool call inside a batch approval request.\n * Mirrors backend PendingToolCallDto.\n */\nexport interface PendingToolCallData {\n  toolExecutionRequestId: string\n  toolName: string\n  toolTitle?: string\n  toolExplanation?: string\n  toolType?: string\n  requiresApproval: boolean\n  approvalType?: string | null\n  toolCallArguments?: Record<string, any> | null\n}\n\n/**\n * Per-tool execution state inside an approval batch.\n * Populated by EXECUTING_TOOL / EXECUTED_TOOL chunks that carry a\n * `toolExecutionRequestId` matching one of the batch's tool calls.\n */\nexport interface ApprovalBatchExecutionState {\n  status: 'executing' | 'done'\n  result?: string\n  success?: boolean\n}\n\nexport interface ApprovalBatchData {\n  approvalRequestId: string\n  /** Highest approval type required across the batch (e.g. ADMIN beats CLIENT). */\n  approvalType: string\n  toolCalls: PendingToolCallData[]\n  /**\n   * Keyed by `PendingToolCallData.toolExecutionRequestId`. Absent before\n   * approval; rows without an entry render as \"queued\" (loader) once the\n   * batch itself is approved.\n   */\n  executions?: Record<string, ApprovalBatchExecutionState>\n  /** `'guide'` when the batch came from a Product Guide `approval_batch` frame.\n   *  Same contract and the same reasons as `ApprovalRequestData.origin` — the\n   *  hub groups multiple proposals into one card, and that card resolves\n   *  through the hub exactly like a single one. */\n  origin?: 'guide'\n}\n\n/** Approve/reject handler stamped onto approval segments. MAY resolve a\n *  boolean success flag — `false` means the confirm FAILED (expired\n *  proposal, network error); batch approve-all loops use it to tick the\n *  row's failure cross. `void` (legacy transports) is treated as\n *  success. Single source of truth for this signature — component\n *  props, accumulator callbacks, and adapters all reference it. */\nexport type ApprovalResolutionHandler = (\n  requestId?: string,\n) => void | boolean | Promise<void | boolean>\n\n// ========== Message Segment Types ==========\n\nexport type TextSegment = {\n  type: 'text'\n  text: string\n}\n\nexport type ThinkingSegment = {\n  type: 'thinking'\n  text: string\n}\n\n/** Guide answer body — the assistant's how-to/documentation reply, rendered as\n *  a titled \"OpenFrame Guide\" card instead of a bare paragraph. `text` is\n *  markdown, streamed in fragments like a `text` segment and coalesced by the\n *  accumulator. */\nexport type GuideSegment = {\n  type: 'guide'\n  text: string\n}\n\n/** One reading the assistant offers in an `ask` card. `label` is BOTH the row's\n *  headline and the exact text sent back when the row is picked — the backend's\n *  guide classifier resolves the user's next message against the labels it\n *  offered, so the reply must be the label verbatim. `description` is a short\n *  clarifying line rendered under it. */\nexport type AskOptionData = {\n  label: string\n  description?: string\n}\n\n/** Clarification card — the assistant asking WHICH reading of an ambiguous\n *  question it should answer, rendered as a heading plus a list of clickable\n *  options instead of prose bullets. NATS-only (the `ASK` chunk); the intro\n *  sentence riding the same chunk becomes an ordinary `text` segment in front\n *  of the card, so it goes through the normal markdown body pipeline. Unlike\n *  the three delta streams an ask arrives whole — it is never coalesced. */\nexport type AskSegment = {\n  type: 'ask'\n  question: string\n  options: AskOptionData[]\n}\n\nexport type ToolExecutionSegment = {\n  type: 'tool_execution'\n  data: ToolExecutionData\n}\n\nexport type ApprovalRequestSegment = {\n  type: 'approval_request'\n  data: ApprovalRequestData & { approvalType?: string }\n  status?: ChatApprovalStatus\n  /** Display name of the user who resolved the request; baked into the client\n   *  variant's full-text status pill (\"Approved by {name}\"). */\n  resolvedByName?: string | null\n  onApprove?: ApprovalResolutionHandler\n  onReject?: ApprovalResolutionHandler\n}\n\nexport type ApprovalBatchSegment = {\n  type: 'approval_batch'\n  data: ApprovalBatchData\n  status?: ChatApprovalStatus\n  /** Display name of the user who resolved the request; set when the batch is resolved (null/absent for system actions). */\n  resolvedByName?: string | null\n  onApprove?: ApprovalResolutionHandler\n  onReject?: ApprovalResolutionHandler\n}\n\n/**\n * Ticket-escalation offer block. Visually the client approval card (same\n * Figma component), but a SEPARATE segment type on purpose: it resolves\n * through the ticket-escalation GraphQL mutations rather than the tool\n * approval endpoint, and hosts that hide pending tool approvals from the\n * thread must still render this one inline.\n *\n * `cancelled` is the wire's SUPERSEDED — the client typed over the offer.\n */\nexport type EscalationOfferSegment = {\n  type: 'escalation_offer'\n  data: EscalationOfferData\n  status?: ChatApprovalStatus\n  resolvedByName?: string | null\n  onApprove?: ApprovalResolutionHandler\n  onReject?: ApprovalResolutionHandler\n}\n\n/**\n * The conversation was handed off to a human technician. Decoded from the\n * `TICKET_ESCALATED` block, so it appears for every escalation path the\n * backend emits it for — including the inactivity auto-escalation, which\n * raises no offer and therefore has no offer state to infer from.\n */\nexport type TicketEscalatedSegment = {\n  type: 'ticket_escalated'\n  data: TicketEscalatedData\n}\n\n/**\n * Ticket lifecycle receipt (resolved / reopened / an unknown future kind).\n * Arrives as a standalone `TICKET_EVENT` chunk outside MESSAGE_START/END —\n * same delivery shape as `ticket_escalated`.\n *\n * `streamSeq` is the chunk's JetStream sequence, kept ON the segment because\n * it is the event's only stable identity: the accumulator upserts on it so a\n * redelivered chunk (catch-up over hydrated history) replaces rather than\n * stacks a second card, while two genuinely distinct events of the same kind\n * (resolve → reopen → resolve) keep distinct sequences and both render.\n */\nexport type TicketEventSegment = {\n  type: 'ticket_event'\n  data: TicketEventData\n  streamSeq?: number\n  /** When THIS event happened (history: the persisted row's `createdAt`;\n   *  live: chunk arrival). Kept beside `streamSeq`, not in `data`: the\n   *  upsert's payload-equality fallback must keep matching a redelivered\n   *  chunk to its hydrated twin, and their times come from different clocks.\n   *  Without it the card inherits the surrounding assistant bubble's\n   *  timestamp — the FIRST row of the whole turn, so every lifecycle card\n   *  reads the same stale time. */\n  occurredAt?: Date\n}\n\nexport type ErrorSegment = {\n  type: 'error'\n  title: string\n  details?: string\n}\n\nexport type ContextCompactionSegment = {\n  type: 'context_compaction'\n  status: 'started' | 'completed'\n  summary?: string\n}\n\nexport type MessageSegment = TextSegment | ThinkingSegment | GuideSegment | AskSegment | ToolExecutionSegment | ApprovalRequestSegment | ApprovalBatchSegment | EscalationOfferSegment | TicketEscalatedSegment | TicketEventSegment | ErrorSegment | ContextCompactionSegment\n\nexport type MessageContent = string | MessageSegment[]\n\n// ========== Message Data Types (from GraphQL/API) ==========\n\nexport interface MessageDataBase {\n  type: MessageType\n}\n\nexport interface TextMessageData extends MessageDataBase {\n  type: 'TEXT'\n  text?: string\n}\n\nexport interface ThinkingMessageData extends MessageDataBase {\n  type: 'THINKING'\n  text?: string\n}\n\nexport interface GuideMessageData extends MessageDataBase {\n  type: 'GUIDE'\n  text?: string\n  /** Persisted Product Guide frame (GraphQL `GuideData.payload`, a JSON scalar)\n   *  for rows that carry a card instead of answer text — replayed through the\n   *  live path's `guideFrameEvent`. */\n  payload?: Record<string, unknown>\n}\n\n/** Persisted `ASK` row (GraphQL `AskData`). `text` is the intro sentence, which\n *  history replays as a text segment ahead of the card — same split the live\n *  `ASK` chunk carries. */\nexport interface AskMessageData extends MessageDataBase {\n  type: 'ASK'\n  text?: string\n  question?: string\n  options?: AskOptionData[]\n}\n\nexport interface ExecutingToolMessageData extends MessageDataBase {\n  type: 'EXECUTING_TOOL'\n  integratedToolType?: string\n  toolFunction?: string\n  /** Backend-issued human-readable title (wire field, mirrors `ChunkData.title`). */\n  title?: string\n  /** Backend-issued human-readable explanation (what/why) of the tool call. */\n  toolExplanation?: string\n  parameters?: Record<string, any>\n  toolExecutionRequestId?: string\n}\n\nexport interface ExecutedToolMessageData extends MessageDataBase {\n  type: 'EXECUTED_TOOL'\n  integratedToolType?: string\n  toolFunction?: string\n  /** Backend-issued human-readable title (wire field, mirrors `ChunkData.title`). */\n  title?: string\n  parameters?: Record<string, any>\n  result?: string\n  success?: boolean\n  toolExecutionRequestId?: string\n}\n\nexport interface ApprovalRequestMessageData extends MessageDataBase {\n  type: 'APPROVAL_REQUEST'\n  approvalRequestId?: string\n  approvalType?: string\n  command?: string\n  explanation?: string\n  /** Present when the approval is a batch of tool calls (new format). */\n  toolCalls?: PendingToolCallData[]\n}\n\nexport interface ApprovalResultMessageData extends MessageDataBase {\n  type: 'APPROVAL_RESULT'\n  approvalRequestId?: string\n  approved?: boolean\n  approvalType?: string\n  /** Display name of the user who resolved the request; null/absent for system actions. */\n  resolvedByName?: string | null\n}\n\n/**\n * Persisted escalation-offer row. The PENDING row carries `text`/`origin`;\n * the resolution is a SECOND row with the same `offerId`, state\n * APPROVED/DECLINED/SUPERSEDED and no text — matched on replay by id.\n */\nexport interface EscalationOfferMessageData extends MessageDataBase {\n  type: 'ESCALATION_OFFER'\n  offerId?: string\n  state?: string\n  text?: string\n  origin?: string\n  resolvedByUserId?: string | null\n  resolvedByName?: string | null\n}\n\nexport interface TicketEscalatedMessageData extends MessageDataBase {\n  type: 'TICKET_ESCALATED'\n  ticketId?: string\n  ticketNumber?: number\n  reason?: TicketEscalationReason\n  text?: string\n}\n\n/** Persisted ticket lifecycle row — same field names as the live chunk\n *  (see `TicketEventData`), decoded by the SAME mapper on replay. Fields are\n *  wire-shaped (`| null`): every one is a nullable GraphQL scalar. */\nexport interface TicketEventMessageData extends MessageDataBase {\n  type: 'TICKET_EVENT'\n  kind?: string | null\n  actorId?: string | null\n  actorName?: string | null\n  actorType?: string | null\n  reason?: string | null\n  targetStatusKind?: string | null\n}\n\nexport interface ErrorMessageData extends MessageDataBase {\n  type: 'ERROR'\n  error?: string\n  details?: string\n}\n\nexport interface AIMetadataMessageData extends MessageDataBase {\n  type: 'AI_METADATA'\n  modelName?: string\n  providerName?: string\n  provider?: string\n  contextWindow?: number\n}\n\nexport interface TokenUsageData {\n  inputTokensSize: number\n  outputTokensSize: number\n  totalTokensSize: number\n  contextSize: number\n}\n\nexport interface SystemMessageData extends MessageDataBase {\n  type: 'SYSTEM'\n  text?: string\n}\n\nexport interface ContextCompactionStartMessageData extends MessageDataBase {\n  type: 'CONTEXT_COMPACTION_START'\n}\n\nexport interface ContextCompactionEndMessageData extends MessageDataBase {\n  type: 'CONTEXT_COMPACTION_END'\n  summary?: string\n}\n\nexport type MessageData =\n  | TextMessageData\n  | ThinkingMessageData\n  | GuideMessageData\n  | AskMessageData\n  | ExecutingToolMessageData\n  | ExecutedToolMessageData\n  | ApprovalRequestMessageData\n  | ApprovalResultMessageData\n  | EscalationOfferMessageData\n  | TicketEscalatedMessageData\n  | TicketEventMessageData\n  | ErrorMessageData\n  | AIMetadataMessageData\n  | SystemMessageData\n  | ContextCompactionStartMessageData\n  | ContextCompactionEndMessageData\n\n// ========== Historical Message Types ==========\n\nexport interface HistoricalMessage {\n  id: string\n  dialogId?: string\n  chatType?: string\n  createdAt: string\n  owner?: MessageOwner\n  messageData?: MessageData | MessageData[]\n  /** Persisted stream sequence of this row's last chunk (the backend's\n   *  `lastChunkStreamSeq`). Passed through to the processed message's\n   *  `streamSeq` so `mergeHistoryWithRealtime` can decide coverage per-role\n   *  (a synthetic is covered only by a persisted row of its OWN role reaching\n   *  its seq). Optional — absent for rows the backend doesn't stamp (e.g. user\n   *  MESSAGE_REQUEST rows), which then don't participate in seq coverage. */\n  lastChunkStreamSeq?: number | null\n}\n\n// ========== Processed Message Types ==========\n\nexport interface ProcessedMessage {\n  id: string\n  role: 'user' | 'assistant' | 'error'  // Limited to display roles\n  content: MessageContent\n  name?: string\n  assistantType?: AssistantType\n  authorType?: AuthorType\n  timestamp: Date\n  avatar?: string\n  /** Persisted last-chunk stream sequence carried through from\n   *  `HistoricalMessage.lastChunkStreamSeq` (for assistant turns: the MAX\n   *  across the grouped rows). Hosts stamp it onto the rendered message's\n   *  `streamSeq` so the history/realtime merge can do per-role seq coverage.\n   *  Absent when the source row(s) carried no seq. */\n  streamSeq?: number\n}\n\n// ========== Base Message Interface ==========\n\nimport type { ChatContextItem } from './context-item.types'\n\nexport interface Message {\n  id: string\n  role: 'user' | 'assistant' | 'error'  // Limited to display roles\n  content: MessageContent\n  name?: string\n  assistantType?: AssistantType\n  authorType?: AuthorType\n  timestamp?: Date\n  avatar?: string | null\n  /** Highest CONTENT chunk streamSeq that composed this message. Stamped on\n   *  realtime synthetics so `mergeHistoryWithRealtime` can decide history\n   *  coverage per-message (see `MergeableChatMessage.streamSeq`). */\n  streamSeq?: number\n  /** Entity-context items attached to this (user) message via the composer's\n   *  context picker. When present the message bubble renders the context\n   *  chips beneath its text (Figma node 31:28709). Optional — omitted for\n   *  assistant messages and turns sent without context. */\n  contextItems?: ChatContextItem[]\n  /** Per-message viewport-positioning hint. OPTIONAL — when omitted (the\n   *  default for every LLM Q&A / browse / search / find / Discuss path)\n   *  the chat tails as today via `use-stick-to-bottom`. Only `'top'` opts\n   *  in to the alternative top-anchor behaviour (display-action answers\n   *  whose body is a long article). The server is the sole decision-\n   *  maker — set on the metadata leading frame. */\n  scrollAnchor?: ScrollAnchor\n  /** When true the message is part of the API conversation history (sent\n   *  to the LLM so it has context) but is NOT rendered in the chat UI.\n   *\n   *  Used for \"synthetic continuation\" turns: when the user clicks Approve\n   *  on a tool proposal, the host auto-fires a follow-up `sendMessage`\n   *  with `hidden: true` carrying a directive like \"the user just\n   *  approved <tool>; ask follow-up questions per protocol\". The LLM's\n   *  response IS rendered (as a normal assistant message); only the\n   *  trigger prompt is suppressed so the chat reads naturally:\n   *\n   *    user: \"open a ticket\"\n   *    assistant: preamble + approval card\n   *    [user clicks Approve]\n   *    assistant: \"Now to triage faster, can you share...\"   ← auto-fires\n   *\n   *  Without this flag the trigger prompt would surface as a confusing\n   *  bubble like \"(continue per protocol)\" between the approval card\n   *  and the AI's follow-up. */\n  hidden?: boolean\n}","/**\n * NATS chunk → normalized `ChatStreamEvent` decoder.\n *\n * THE live NATS reading path — the ONLY chunk parser in the codebase. Every\n * consumer (this lib's `useNatsChatAdapter`, the hub, the product app) feeds\n * raw chunks through `decodeNatsChunk` into `createChatStreamReducer`. The\n * legacy `components/chat/utils/chunk-parser.ts` (`parseChunkToAction`) that\n * this module superseded was DELETED — do not reintroduce a second decoder.\n *\n * Chunks map onto the transport-agnostic event union, with the JetStream\n * `streamSeq` lifted into the `seq` envelope (the reducer's idempotency gate\n * keys off it).\n *\n * Behavior is pinned by `__tests__/nats-decoder-golden.test.ts`.\n *\n * Server-safe: no React, no browser APIs.\n */\n\nimport { MESSAGE_TYPE } from '../components/chat/types/message.types'\nimport type { AskOptionData } from '../components/chat/types/message.types'\nimport { ESCALATION_STATE, GUIDE_ORIGIN, escalationResolvedStatus } from './events'\nimport type { ApprovalToolCall, ChatStreamEvent } from './events'\nimport { escapeThinkingTags, mapLeadingFrame } from './leading-frames'\n\n/** Minimal structural view of a NATS chunk (see `ChunkData` in\n *  `src/components/chat/types/network.types.ts`). */\ntype NatsChunk = Record<string, any>\n\n// `GUIDE_ORIGIN` moved to `./events`, beside the `GuideOrigin` type it is the\n// only value of — the SSE half reads it too, so it never belonged to the NATS\n// decoder. Re-exported here because that is the import path consumers know.\nexport { GUIDE_ORIGIN } from './events'\n\n/** Coerce the wire's `toolCalls[]` into the batch-approval shape, dropping\n *  non-object entries and defaulting every field. */\nfunction normalizeToolCalls(raw: unknown): ApprovalToolCall[] {\n  if (!Array.isArray(raw)) return []\n  return raw\n    .filter((item): item is Record<string, any> => !!item && typeof item === 'object')\n    .map((item) => ({\n      toolExecutionRequestId: String(item.toolExecutionRequestId ?? ''),\n      toolName: String(item.toolName ?? ''),\n      toolTitle: typeof item.toolTitle === 'string' ? item.toolTitle : undefined,\n      toolExplanation:\n        typeof item.toolExplanation === 'string' ? item.toolExplanation : undefined,\n      toolType: typeof item.toolType === 'string' ? item.toolType : undefined,\n      requiresApproval: item.requiresApproval === true,\n      approvalType: typeof item.approvalType === 'string' ? item.approvalType : null,\n      toolCallArguments:\n        item.toolCallArguments && typeof item.toolCallArguments === 'object'\n          ? (item.toolCallArguments as Record<string, any>)\n          : null,\n    }))\n}\n\n/** Coerce the wire's ask `options[]` into the card's row shape. Rows without a\n *  usable `label` are dropped: the label is what gets SENT when the row is\n *  picked, so a blank one would post an empty reply. */\nexport function normalizeAskOptions(raw: unknown): AskOptionData[] {\n  if (!Array.isArray(raw)) return []\n  return raw\n    .filter((item): item is Record<string, any> => !!item && typeof item === 'object')\n    .map((item) => ({\n      label: typeof item.label === 'string' ? item.label.trim() : '',\n      description: typeof item.description === 'string' ? item.description : undefined,\n    }))\n    .filter((option) => option.label.length > 0)\n}\n\n/**\n * Events whose meaning belongs to the AGENT's dialog rather than to the guide\n * turn embedded inside it. The hub emits its own copies of these, and the Mingo\n * dialog already has authoritative ones: letting the hub's through would\n * double-count the dialog's tokens or fight the agent's own phase chunks.\n *\n * Note this is a DENYLIST, and that is the point. `./leading-frames` is the ONE\n * place a hub frame kind is taught to the client; whatever it learns to emit —\n * including kinds that do not exist yet — must reach a Mingo dialog without a\n * second edit here. An allowlist would silently swallow every card the hub adds\n * next, and the guide half would quietly lag the guide chat.\n */\nconst AGENT_OWNED_EVENTS: ReadonlySet<ChatStreamEvent['type']> = new Set([\n  'usage',\n  'token-usage',\n  'status',\n  'compaction',\n  'participant',\n  'dialog-closed',\n])\n\n/** Event types that declare `origin` (see `GuideOrigin`). Data rather than a\n *  branch per type, so a future hub card opts in by adding the field to its\n *  interface and its name here — the pass-through below needs no edit. */\nconst ORIGIN_BEARING_EVENTS: ReadonlySet<ChatStreamEvent['type']> = new Set([\n  'approval-request',\n  'approval-resolved',\n])\n\n/**\n * Adapt ONE Product Guide event to the NATS kernel.\n *\n * The guide half of a Mingo dialog is the hub's own stream, so it is decoded by\n * the shared table in `./leading-frames` and then passed through here — the ONE\n * place that reconciles the two kernels. Both entry points use it: frames\n * re-streamed by the agent inside `GUIDE` chunks, and the SSE response of the\n * hub's confirm-tool route, which a host replays into the same dialog.\n *\n * Pass-through is the DEFAULT; only these rules alter an event, and each exists\n * because the two kernels genuinely disagree:\n *\n *   - `text-delta` becomes `guide-delta`: the body of a guide turn belongs\n *     inside the \"OpenFrame Guide\" card, and a `text` segment would strand part\n *     of the same answer outside it.\n *   - thinking is escaped HERE. The SSE kernel escapes `<` on the way in, the\n *     NATS kernel does not, and the guide's thinking is full of XML-ish tokens\n *     that would otherwise render as markup.\n *   - `metadata` survives ONLY to carry `conversationId`, which the hub mints\n *     and every confirm-tool call must quote back. Everything else on that\n *     frame (the hub's model, routing) is dropped: `applyNats` rebuilds the\n *     dialog's live model from a metadata event, so letting it through would\n *     relabel a Mingo turn with the hub's model, and a routing frame — which\n *     carries no model at all — would blank the badge mid-answer.\n *   - dialog-level events stop here (`AGENT_OWNED_EVENTS`).\n *\n * Everything else crosses over as the hub typed it, gaining only `origin`.\n * `approvalType` in particular stays the TOOL NAME, exactly as in the hub's own\n * chat: the NATS kernel gates approvals on approval TIER and escalates the rest,\n * which is an agent-side concept a hub proposal has no tier for. Rewriting the\n * tool name into a fake tier to slip past that gate would make the guide half of\n * the stream diverge from the guide chat itself.\n */\nexport function guideEventForNats(event: ChatStreamEvent): ChatStreamEvent | null {\n  switch (event.type) {\n    case 'text-delta':\n      return { type: 'guide-delta', text: event.text }\n    case 'thinking-delta':\n      return { type: 'thinking-delta', text: escapeThinkingTags(event.text) }\n    case 'metadata':\n      return typeof event.conversationId === 'string' && event.conversationId\n        ? { type: 'metadata', conversationId: event.conversationId, origin: GUIDE_ORIGIN }\n        : null\n    default:\n      if (AGENT_OWNED_EVENTS.has(event.type)) return null\n      return ORIGIN_BEARING_EVENTS.has(event.type)\n        ? ({ ...event, origin: GUIDE_ORIGIN } as ChatStreamEvent)\n        : event\n  }\n}\n\n/**\n * Decode one re-streamed Product Guide frame (a `GUIDE` chunk's `payload`) into\n * a NATS event. The frame grammar is decoded by the shared table, never by a\n * second copy here; the kernel reconciliation is `guideEventForNats`.\n */\nexport function guideFrameEvent(frame: Record<string, unknown>): ChatStreamEvent | null {\n  const events: ChatStreamEvent[] = []\n  // Every branch of the table pushes at most one event, so the first is the one.\n  mapLeadingFrame(frame, events)\n  const event = events[0]\n  return event ? guideEventForNats(event) : null\n}\n\n/**\n * Parse one raw NATS chunk into a normalized event. Returns `null` for\n * unknown/malformed chunks — an unrecognized `type`, a missing required\n * field, or a non-object payload are all tolerated as no-ops rather than\n * throwing, so a backend that adds a chunk type can't break the stream.\n */\nexport function decodeNatsChunk(chunk: unknown): ChatStreamEvent | null {\n  if (!chunk || typeof chunk !== 'object') return null\n\n  const data = chunk as NatsChunk\n  const type = String(data.type || '')\n  // JetStream stream sequence → the generic `seq` envelope.\n  const seq: { seq?: number } =\n    typeof data.streamSeq === 'number' ? { seq: data.streamSeq } : {}\n\n  switch (type) {\n    case MESSAGE_TYPE.MESSAGE_START:\n      return { type: 'turn-start', ...seq }\n\n    case MESSAGE_TYPE.MESSAGE_END:\n      return { type: 'turn-end', ...seq }\n\n    case MESSAGE_TYPE.AI_METADATA: {\n      const providerName = data.providerName || data.provider\n      if (typeof data.modelName === 'string' && typeof providerName === 'string') {\n        return {\n          type: 'metadata',\n          modelLabel: data.modelDisplayName,\n          modelName: data.modelName,\n          provider: providerName,\n          contextWindowMaxTokens:\n            typeof data.contextWindow === 'number' ? data.contextWindow : 0,\n          ...seq,\n        }\n      }\n      return null\n    }\n\n    case MESSAGE_TYPE.TEXT:\n      if (typeof data.text === 'string') {\n        return { type: 'text-delta', text: data.text, ...seq }\n      }\n      return null\n\n    case MESSAGE_TYPE.THINKING:\n      if (typeof data.text === 'string') {\n        return { type: 'thinking-delta', text: data.text, ...seq }\n      }\n      return null\n\n    // Two shapes share this chunk type. `text` is the answer body the agent\n    // streams (and persists). `payload` is a Product Guide frame re-streamed\n    // verbatim from the hub — decoded through the shared frame table, with the\n    // narrowing `guideFrameEvent` documents.\n    case MESSAGE_TYPE.GUIDE: {\n      if (typeof data.text === 'string') {\n        return { type: 'guide-delta', text: data.text, ...seq }\n      }\n      const frame = data.payload\n      if (!frame || typeof frame !== 'object' || Array.isArray(frame)) return null\n      const event = guideFrameEvent(frame as Record<string, unknown>)\n      if (!event) return null\n      return { ...event, ...seq }\n    }\n\n    // An ask card is only an ask card with something to pick: a question and at\n    // least one option. Anything less is dropped rather than rendered as an\n    // empty card the user can't answer — the backend already falls back to a\n    // complete hardcoded card when the model returns a partial one.\n    case MESSAGE_TYPE.ASK: {\n      const question = typeof data.question === 'string' ? data.question.trim() : ''\n      const options = normalizeAskOptions(data.options)\n      if (!question || options.length === 0) return null\n      return {\n        type: 'ask',\n        ...(typeof data.text === 'string' && data.text ? { text: data.text } : {}),\n        question,\n        options,\n        ...seq,\n      }\n    }\n\n    case MESSAGE_TYPE.EXECUTING_TOOL:\n      return {\n        type: 'tool-execution',\n        data: {\n          type: 'EXECUTING_TOOL',\n          integratedToolType: data.integratedToolType || '',\n          toolFunction: data.toolFunction || '',\n          toolTitle: typeof data.title === 'string' ? data.title : undefined,\n          // The human-readable \"why this tool is running\" line rendered inside\n          // the tool card. It rides ONLY the EXECUTING chunk — the EXECUTED one\n          // never carries it, and the accumulator's merge preserves whatever\n          // the EXECUTING segment held. Dropping it here therefore blanks the\n          // card for the whole lifetime of the run.\n          toolExplanation: typeof data.toolExplanation === 'string' ? data.toolExplanation : undefined,\n          parameters: data.parameters,\n          toolExecutionRequestId:\n            typeof data.toolExecutionRequestId === 'string'\n              ? data.toolExecutionRequestId\n              : undefined,\n        },\n        ...seq,\n      }\n\n    case MESSAGE_TYPE.EXECUTED_TOOL:\n      return {\n        type: 'tool-execution',\n        data: {\n          type: 'EXECUTED_TOOL',\n          integratedToolType: data.integratedToolType || '',\n          toolFunction: data.toolFunction || '',\n          toolTitle: typeof data.title === 'string' ? data.title : undefined,\n          parameters: data.parameters,\n          result: data.result,\n          success: data.success,\n          toolExecutionRequestId:\n            typeof data.toolExecutionRequestId === 'string'\n              ? data.toolExecutionRequestId\n              : undefined,\n        },\n        ...seq,\n      }\n\n    case MESSAGE_TYPE.APPROVAL_REQUEST: {\n      const requestId = data.approvalRequestId || data.approval_request_id || ''\n      const approvalType = data.approvalType || 'USER'\n      const toolCalls = normalizeToolCalls(data.toolCalls)\n\n      if (toolCalls.length > 0) {\n        return { type: 'approval-request', requestId, approvalType, toolCalls, ...seq }\n      }\n\n      return {\n        type: 'approval-request',\n        requestId,\n        approvalType,\n        command: data.command || '',\n        explanation: data.explanation,\n        ...seq,\n      }\n    }\n\n    case MESSAGE_TYPE.APPROVAL_RESULT: {\n      // Realtime chunks carry the resolver's name as `displayName`; the\n      // persisted GraphQL message exposes the same value as\n      // `resolvedByName`. Accept either so realtime and history-replay\n      // render \"by {name}\" identically.\n      const resolvedByName =\n        typeof data.resolvedByName === 'string'\n          ? data.resolvedByName\n          : typeof data.displayName === 'string'\n            ? data.displayName\n            : undefined\n      return {\n        type: 'approval-resolved',\n        requestId: data.approvalRequestId || data.approval_request_id || '',\n        status: data.approved === true ? 'approved' : 'rejected',\n        approvalType: data.approvalType || 'CLIENT',\n        resolvedByName,\n        ...seq,\n      }\n    }\n\n    case MESSAGE_TYPE.ESCALATION_OFFER: {\n      const offerId = typeof data.offerId === 'string' ? data.offerId : ''\n      if (!offerId) return null\n      if (data.state === ESCALATION_STATE.PENDING) {\n        return {\n          type: 'escalation-offer',\n          offerId,\n          text: typeof data.text === 'string' ? data.text : '',\n          origin: typeof data.origin === 'string' ? data.origin : undefined,\n          ...seq,\n        }\n      }\n      const status = escalationResolvedStatus(data.state)\n      if (!status) return null\n      // Same realtime/history field split as APPROVAL_RESULT: the chunk names\n      // the resolver `displayName`, the persisted row `resolvedByName`.\n      return {\n        type: 'escalation-offer-resolved',\n        offerId,\n        status,\n        resolvedByName:\n          typeof data.resolvedByName === 'string'\n            ? data.resolvedByName\n            : typeof data.displayName === 'string'\n              ? data.displayName\n              : undefined,\n        ...seq,\n      }\n    }\n\n    case MESSAGE_TYPE.TICKET_ESCALATED: {\n      const ticketId = typeof data.ticketId === 'string' ? data.ticketId : ''\n      const reason = typeof data.reason === 'string' ? data.reason : ''\n      // Both are non-null on the wire; a payload missing either is malformed\n      // rather than a variant to render.\n      if (!ticketId || !reason) return null\n      return {\n        type: 'ticket-escalated',\n        ticketId,\n        reason,\n        ticketNumber: typeof data.ticketNumber === 'number' ? data.ticketNumber : undefined,\n        text: typeof data.text === 'string' ? data.text : undefined,\n        ...seq,\n      }\n    }\n\n    case MESSAGE_TYPE.TICKET_EVENT: {\n      // `kind` is the only required field, and deliberately an OPEN string:\n      // unknown kinds must render (as a neutral line), not be dropped.\n      const kind = typeof data.kind === 'string' ? data.kind.trim() : ''\n      if (!kind) return null\n      // This chunk names its own JetStream sequence `sequenceId` in the\n      // payload (the persisted row's `lastChunkStreamSeq` equals it — the\n      // dedupe key). Prefer the transport-stamped `streamSeq` envelope like\n      // every other chunk; fall back to the payload copy when absent.\n      const eventSeq: { seq?: number } =\n        seq.seq !== undefined\n          ? seq\n          : typeof data.sequenceId === 'number'\n            ? { seq: data.sequenceId }\n            : {}\n      return {\n        type: 'ticket-event',\n        kind,\n        actorId: typeof data.actorId === 'string' ? data.actorId : undefined,\n        actorName: typeof data.actorName === 'string' ? data.actorName : undefined,\n        actorType: typeof data.actorType === 'string' ? data.actorType : undefined,\n        reason:\n          typeof data.reason === 'string' && data.reason.trim() ? data.reason : undefined,\n        targetStatusKind:\n          typeof data.targetStatusKind === 'string' && data.targetStatusKind.trim()\n            ? data.targetStatusKind\n            : undefined,\n        ...eventSeq,\n      }\n    }\n\n    case MESSAGE_TYPE.ERROR:\n      return {\n        type: 'error',\n        title: data.error || 'An error occurred',\n        details: data.details,\n        ...seq,\n      }\n\n    case MESSAGE_TYPE.MESSAGE_REQUEST:\n      return {\n        type: 'participant',\n        kind: 'message-request',\n        text: String(data.text || ''),\n        ownerType: typeof data.ownerType === 'string' ? data.ownerType : undefined,\n        displayName: typeof data.displayName === 'string' ? data.displayName : undefined,\n        userId: typeof data.userId === 'string' ? data.userId : undefined,\n        // Wire shape carries no label; fall back to the id (parity with\n        // the history path in `process-historical-messages.ts`).\n        contextItems: Array.isArray(data.contextItems)\n          ? (data.contextItems as Array<{ type?: unknown; id?: unknown }>)\n              .filter((it) => typeof it?.type === 'string' && typeof it?.id === 'string')\n              .map((it) => ({\n                type: it.type as string,\n                id: it.id as string,\n                label: it.id as string,\n              }))\n          : undefined,\n        ...seq,\n      }\n\n    case MESSAGE_TYPE.TOKEN_USAGE:\n      return {\n        type: 'token-usage',\n        inputTokensSize: data.inputTokensSize ?? 0,\n        outputTokensSize: data.outputTokensSize ?? 0,\n        totalTokensSize: data.totalTokensSize ?? 0,\n        contextSize: data.contextSize ?? 0,\n        ...seq,\n      }\n\n    case MESSAGE_TYPE.CONTEXT_COMPACTION_START:\n      return { type: 'compaction', phase: 'start', ...seq }\n\n    case MESSAGE_TYPE.CONTEXT_COMPACTION_END:\n      return {\n        type: 'compaction',\n        phase: 'end',\n        summary: typeof data.text === 'string' ? data.text : undefined,\n        ...seq,\n      }\n\n    case MESSAGE_TYPE.SYSTEM:\n      if (typeof data.text === 'string') {\n        return { type: 'participant', kind: 'system', text: data.text, ...seq }\n      }\n      return null\n\n    case MESSAGE_TYPE.DIRECT_MESSAGE:\n      if (typeof data.text === 'string') {\n        return {\n          type: 'participant',\n          kind: 'direct-message',\n          text: data.text,\n          ownerType: typeof data.ownerType === 'string' ? data.ownerType : undefined,\n          displayName:\n            typeof data.displayName === 'string' ? data.displayName : undefined,\n          userId: typeof data.userId === 'string' ? data.userId : undefined,\n          ...seq,\n        }\n      }\n      return null\n\n    case MESSAGE_TYPE.DIALOG_CLOSED:\n      return { type: 'dialog-closed', ...seq }\n\n    default:\n      return null\n  }\n}\n","/**\n * The hub's confirm-tool contract — request shape and error envelope.\n *\n * Resolving a Product Guide proposal is a POST to `endpoints.approvalToolUrl`\n * whose response is an ordinary guide stream (a `decision_resolved` frame, then\n * the hub's auto-continuation). Two callers make that call today: the SSE chat\n * adapter, and any host replaying the same route into a different transport's\n * conversation (the product app resolves guide cards inside a NATS Mingo\n * dialog). They MUST send byte-identical bodies — a proposal is single-use, so a\n * caller that drifts on a field name does not degrade, it fails the write — and\n * they must surface the same server copy, because the hub's errors are written\n * for the end user (\"This approval expired — ask again to get a fresh one\").\n *\n * Hence both live here rather than at either call site.\n *\n * Server-safe: no React, no browser APIs.\n */\n\n/** What the user did with the card. */\nexport type ApprovalToolAction = 'approve' | 'reject'\n\n/** Client-side shape of a confirm; `buildConfirmToolBody` renames it to the\n *  wire's own casing so no caller hand-writes `proposal_id`. */\nexport interface ConfirmToolRequest {\n  /** The hub-minted proposal id carried by the card. */\n  proposalId: string\n  action: ApprovalToolAction\n  /**\n   * The hub's conversation id, quoted back verbatim. The hub rejects a confirm\n   * without it: a proposal only means something inside the conversation that\n   * minted it.\n   */\n  conversationId: string | null | undefined\n}\n\n/** Build the confirm-tool request body. The ONE place that knows the wire\n *  spells the id `proposal_id` while everything else is camelCase. */\nexport function buildConfirmToolBody(request: ConfirmToolRequest): Record<string, unknown> {\n  return {\n    proposal_id: request.proposalId,\n    action: request.action,\n    conversationId: request.conversationId,\n  }\n}\n\n/**\n * Pull the user-facing copy out of a failed chat/confirm response.\n *\n * The route-base envelope is `{error, code}`; `error` is written for the end\n * user, so it beats any status-code copy the client could invent. Returns null\n * when the body is not that envelope (HTML error page, empty body, gateway\n * response) — the caller then supplies its own generic line.\n *\n * Consumes the body: call it once, on a response you have already decided is a\n * failure.\n */\nexport async function readServerErrorMessage(response: {\n  json: () => Promise<unknown>\n}): Promise<string | null> {\n  try {\n    const body = (await response.json()) as { error?: unknown } | null\n    if (typeof body?.error === 'string' && body.error.length > 0) return body.error\n  } catch {\n    /* non-JSON error body — the caller falls through to its generic copy */\n  }\n  return null\n}\n","/**\n * `normalizeIpForBucketKey` — THE cross-repo normalizer for turning a raw\n * client-IP candidate (`x-forwarded-for` hop, socket remote address, proxy\n * header) into a stable rate-limit / dedup bucket key.\n *\n * WHY IT LIVES HERE: producer and consumer sit in DIFFERENT repos (the app\n * emits the candidate, the hub buckets it) and each had grown its own\n * normalizer. They disagreed — one unwrapped `::ffff:1.2.3.4` to the bare\n * IPv4 and stripped `%zone`, the other kept the zone and only lower-cased the\n * mapped form — so ONE visitor could land in TWO buckets depending on which\n * side normalized. Both repos already depend on this package, and\n * `./chat-protocol` is its server-safe subpath (no React, no browser APIs),\n * so this is the one place both sides can share.\n *\n * Contract:\n *   - input longer than `IP_BUCKET_KEY_MAX_LENGTH` → `null` (an unbounded\n *     header must never become an unbounded map key);\n *   - `[…]` brackets and a trailing `]:port` are stripped, as is a bare\n *     `:port` on an IPv4 (`203.0.113.4:8080`) — an unbracketed IPv6 with a\n *     bare port stays ambiguous and is not stripped;\n *   - a `%zone` suffix is stripped (link-local scope is per-host, not part of\n *     the peer's identity);\n *   - IPv4-mapped IPv6 (`::ffff:203.0.113.4`) collapses to the bare IPv4, so\n *     the same peer buckets identically over either stack;\n *   - IPv6 is lower-cased;\n *   - IPv4 octets are range-checked and REJECTED when zero-padded (`.04`\n *     would otherwise bucket separately from `.4`), and IPv6 is\n *     charset/shape-checked (one `::` at most, ≤8 groups, an embedded IPv4\n *     only in the last group, no dangling separator); anything else returns\n *     `null` rather than a junk bucket key.\n *\n * It does NOT canonicalize IPv6 zero-compression (`2001:db8::1` vs\n * `2001:0db8:0:0:0:0:0:1`) — peers do not spell their own address two ways\n * within one deployment, and a full expander is more surface than the bucket\n * key warrants.\n */\n\n/** Longest accepted candidate. A full IPv6 + zone fits well inside this. */\nexport const IP_BUCKET_KEY_MAX_LENGTH = 64\n\nfunction isIpv4(value: string): boolean {\n  const parts = value.split('.')\n  if (parts.length !== 4) return false\n  for (const part of parts) {\n    if (!/^\\d{1,3}$/.test(part)) return false\n    // Leading zeros are a SECOND spelling of the same address (`203.0.113.04`),\n    // so accepting them verbatim splits one peer across two buckets. Reject\n    // rather than canonicalize: no legitimate emitter zero-pads, and some\n    // resolvers read a `0`-prefixed octet as octal, i.e. it is not even\n    // reliably the same address.\n    if (part.length > 1 && part.startsWith('0')) return false\n    if (Number(part) > 255) return false\n  }\n  return true\n}\n\nfunction isIpv6(value: string): boolean {\n  if (!value.includes(':')) return false\n  // Charset gate first: hex digits, separators, and the dots of a trailing\n  // embedded IPv4. Anything else (a hostname, a header injection) is out.\n  if (!/^[0-9a-f:.]+$/.test(value)) return false\n  if (value.includes(':::')) return false\n  // A dangling separator (`2001:db8::1:`) leaves an EMPTY trailing group that\n  // is not part of a `::` compression — malformed, not an address.\n  if (value.endsWith(':') && !value.endsWith('::')) return false\n  if (value.startsWith(':') && !value.startsWith('::')) return false\n  const compressions = value.split('::').length - 1\n  if (compressions > 1) return false\n  const groups = value.split(':')\n  if (groups.length > 8) return false\n  for (let i = 0; i < groups.length; i += 1) {\n    const group = groups[i]\n    if (group === '') continue\n    if (group.includes('.')) {\n      // An embedded IPv4 occupies the LAST two groups; anywhere else\n      // (`1.2.3.4::1`) it is malformed.\n      if (i !== groups.length - 1) return false\n      if (!isIpv4(group)) return false\n      continue\n    }\n    if (!/^[0-9a-f]{1,4}$/.test(group)) return false\n  }\n  // Uncompressed form must be exactly 8 groups (a trailing embedded IPv4\n  // occupies two, so 7 written groups is also complete).\n  if (compressions === 0) {\n    const embedsIpv4 = groups[groups.length - 1]?.includes('.') ?? false\n    if (groups.length !== (embedsIpv4 ? 7 : 8)) return false\n  }\n  return true\n}\n\n/**\n * Normalize one IP candidate to its canonical bucket key, or `null` when the\n * value is not a usable address.\n */\nexport function normalizeIpForBucketKey(value: string): string | null {\n  if (typeof value !== 'string') return null\n  let candidate = value.trim()\n  if (candidate === '' || candidate.length > IP_BUCKET_KEY_MAX_LENGTH) return null\n\n  // `[2001:db8::1]` / `[::1]:443` — the bracketed forms a proxy emits so the\n  // port is unambiguous.\n  if (candidate.startsWith('[')) {\n    const close = candidate.indexOf(']')\n    if (close === -1) return null\n    const trailer = candidate.slice(close + 1)\n    if (trailer !== '' && !/^:\\d{1,5}$/.test(trailer)) return null\n    candidate = candidate.slice(1, close)\n  }\n\n  // `%eth0` / `%25eth0` scope id — host-local, never part of peer identity.\n  const zone = candidate.indexOf('%')\n  if (zone !== -1) candidate = candidate.slice(0, zone)\n  if (candidate === '') return null\n\n  // UNBRACKETED `203.0.113.4:8080` — Azure App Service / Front Door and several\n  // CDNs write the client hop that way. Rejecting it left the caller with NO\n  // candidate at all, collapsing every visitor into the single egress bucket:\n  // exactly the silent regression this module exists to prevent. Stripped ONLY\n  // when the remainder is a valid IPv4; an unbracketed IPv6 with a bare port\n  // (`::1:443`) is genuinely ambiguous — `:443` is equally a final group — so\n  // that shape is left to the IPv6 path.\n  const portSplit = candidate.lastIndexOf(':')\n  if (portSplit > 0 && /^\\d{1,5}$/.test(candidate.slice(portSplit + 1))) {\n    const head = candidate.slice(0, portSplit)\n    if (isIpv4(head)) candidate = head\n  }\n\n  if (isIpv4(candidate)) return candidate\n\n  const lowered = candidate.toLowerCase()\n\n  // IPv4-mapped IPv6 → the bare IPv4 (same peer, one bucket). EVERY spelling\n  // is handled: the compressed `::ffff:1.2.3.4` that dual-stack listeners\n  // actually emit, the written-out `0:0:0:0:0:ffff:1.2.3.4` that a hop doing\n  // its own (non-compressing) formatting can produce, and the ZERO-PADDED\n  // `0000:0000:0000:0000:0000:ffff:1.2.3.4` that a fixed-width formatter\n  // produces — leading zeros in an IPv6 group are purely cosmetic, so all\n  // three name the same peer. Matching only some of them would split one peer\n  // across two buckets — the exact failure this module exists to prevent.\n  const mapped = /^(?:(?:0{1,4}:){5}|::)ffff:(\\d{1,3}(?:\\.\\d{1,3}){3})$/.exec(lowered)\n  if (mapped) return isIpv4(mapped[1]) ? mapped[1] : null\n\n  if (isIpv6(lowered)) return lowered\n  return null\n}\n","/**\n * `envFlagEnabled` — THE cross-repo predicate for reading a boolean env flag.\n *\n * WHY IT LIVES HERE: the same flag is read on BOTH sides of the guide-chat\n * seam and each side had grown its own reading of it. The hub parsed the\n * value (`'0'` / `'false'` / `'off'` mean OFF); the producing app used bare\n * `Boolean(process.env.X)`, under which every non-empty string — including\n * `'0'` and `'false'` — is TRUE.\n *\n * That divergence failed OPEN on the one flag that gates a security boundary.\n * `TRUSTED_INGRESS_SETS_REAL_IP` is an operator's assertion that a reverse\n * proxy OVERWRITES the client-IP headers; without it those headers are\n * attacker-writable and must not be read. An operator writing `=0` to mean\n * \"off\" got the hub honoring that while the producer read `x-real-ip` /\n * `x-forwarded-for` anyway and forwarded the value as `x-chat-ip`, which the\n * hub then trusts verbatim under the service token. A rate-limit-bucket\n * spoof, reachable through a plausible operator typo.\n *\n * Both repos already depend on this package and `./chat-protocol` is its\n * server-safe subpath (no React, no browser APIs), so this is the one place\n * both sides can share — same reasoning that moved `normalizeIpForBucketKey`\n * here. Keep it as ONE definition: a second copy is what caused the drift.\n *\n * Contract:\n *   - unset, empty, or whitespace-only  → `false`;\n *   - `'0'`, `'false'`, `'off'`         → `false` (case- and\n *     surrounding-whitespace-insensitive);\n *   - any other non-empty value         → `true`.\n *\n * `process.env` is read PER CALL, never module-hoisted, so tests, preview and\n * prod all observe the live value rather than whatever was present at\n * module-eval time.\n */\nexport function envFlagEnabled(name: string): boolean {\n  const raw = process.env[name]\n  if (!raw) return false\n  const v = raw.trim().toLowerCase()\n  return v !== '' && v !== '0' && v !== 'false' && v !== 'off'\n}\n"]}