{"version":3,"sources":["../../src/kroki/fetch.ts","../../src/renderers/ascii.ts","../../src/types.ts","../../src/utils/timing.ts","../../src/renderers/colors.ts","../../src/renderers/mermaid.ts","../../src/performance-analyzer.ts","../../src/kroki/encoder.ts","../../src/kroki/url.ts"],"sourcesContent":["/**\n * Kroki Fetch (Node-only)\n *\n * Downloads rendered diagrams from Kroki as SVG or PNG.\n * This is a separate subpath entry to avoid bundling Node-only code in browser builds.\n */\n\nimport { ok, err, type AsyncResult } from \"awaitly\";\nimport type { WorkflowIR } from \"../types\";\nimport { toKrokiSvgUrl, toKrokiPngUrl, type UrlGeneratorOptions, type UrlGenerator } from \"./url\";\n\n/**\n * Error types for Kroki fetch operations.\n */\nexport type KrokiError = \"FETCH_ERROR\" | \"TIMEOUT\" | \"INVALID_RESPONSE\";\n\n/**\n * Options for fetching from Kroki.\n */\nexport interface FetchKrokiOptions extends UrlGeneratorOptions {\n  /** Optional custom URL generator */\n  generator?: UrlGenerator;\n  /** Request timeout in milliseconds (default: 30000) */\n  timeout?: number;\n}\n\n/**\n * Fetch SVG content from Kroki.\n *\n * @param ir - Workflow intermediate representation\n * @param options - Fetch options\n * @returns Result with SVG content or KrokiError\n *\n * @example\n * ```typescript\n * import { fetchKrokiSvg } from 'awaitly-visualizer/kroki-fetch';\n *\n * const result = await fetchKrokiSvg(workflowIR);\n * if (result.ok) {\n *   // Write to file or embed in HTML\n *   console.log(result.value);\n * } else {\n *   console.error('Failed:', result.error);\n * }\n * ```\n */\nexport async function fetchKrokiSvg(\n  ir: WorkflowIR,\n  options: FetchKrokiOptions = {}\n): AsyncResult<string, KrokiError> {\n  const { generator, timeout = 30000, ...urlOptions } = options;\n\n  const url = generator\n    ? generator.toSvgUrl(ir)\n    : toKrokiSvgUrl(ir, urlOptions);\n\n  const controller = new AbortController();\n  const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n  try {\n    const response = await fetch(url, {\n      signal: controller.signal,\n      headers: {\n        Accept: \"image/svg+xml\",\n      },\n    });\n\n    if (!response.ok) {\n      return err(\"INVALID_RESPONSE\");\n    }\n\n    const text = await response.text();\n    return ok(text);\n  } catch (error) {\n    if (error instanceof Error && error.name === \"AbortError\") {\n      return err(\"TIMEOUT\");\n    }\n    return err(\"FETCH_ERROR\");\n  } finally {\n    clearTimeout(timeoutId);\n  }\n}\n\n/**\n * Fetch PNG content from Kroki.\n *\n * @param ir - Workflow intermediate representation\n * @param options - Fetch options\n * @returns Result with PNG content as Buffer or KrokiError\n *\n * @example\n * ```typescript\n * import { fetchKrokiPng } from 'awaitly-visualizer/kroki-fetch';\n * import fs from 'node:fs';\n *\n * const result = await fetchKrokiPng(workflowIR);\n * if (result.ok) {\n *   fs.writeFileSync('workflow.png', result.value);\n * }\n * ```\n */\nexport async function fetchKrokiPng(\n  ir: WorkflowIR,\n  options: FetchKrokiOptions = {}\n): AsyncResult<Buffer, KrokiError> {\n  const { generator, timeout = 30000, ...urlOptions } = options;\n\n  const url = generator\n    ? generator.toPngUrl(ir)\n    : toKrokiPngUrl(ir, urlOptions);\n\n  const controller = new AbortController();\n  const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n  try {\n    const response = await fetch(url, {\n      signal: controller.signal,\n      headers: {\n        Accept: \"image/png\",\n      },\n    });\n\n    if (!response.ok) {\n      return err(\"INVALID_RESPONSE\");\n    }\n\n    const arrayBuffer = await response.arrayBuffer();\n    return ok(Buffer.from(arrayBuffer));\n  } catch (error) {\n    if (error instanceof Error && error.name === \"AbortError\") {\n      return err(\"TIMEOUT\");\n    }\n    return err(\"FETCH_ERROR\");\n  } finally {\n    clearTimeout(timeoutId);\n  }\n}\n\n/**\n * Fetch PDF content from Kroki.\n *\n * @param ir - Workflow intermediate representation\n * @param options - Fetch options\n * @returns Result with PDF content as Buffer or KrokiError\n *\n * @example\n * ```typescript\n * import { fetchKrokiPdf } from 'awaitly-visualizer/kroki-fetch';\n * import fs from 'node:fs';\n *\n * const result = await fetchKrokiPdf(workflowIR);\n * if (result.ok) {\n *   fs.writeFileSync('workflow.pdf', result.value);\n * }\n * ```\n */\nexport async function fetchKrokiPdf(\n  ir: WorkflowIR,\n  options: FetchKrokiOptions = {}\n): AsyncResult<Buffer, KrokiError> {\n  const { generator, timeout = 30000, ...urlOptions } = options;\n\n  // When generator is provided, use its toPdfUrl directly\n  // Otherwise, build PDF URL by modifying the PNG URL path\n  const pdfUrl = generator\n    ? generator.toPdfUrl(ir)\n    : toKrokiPngUrl(ir, urlOptions).replace(\"/png/\", \"/pdf/\");\n\n  const controller = new AbortController();\n  const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n  try {\n    const response = await fetch(pdfUrl, {\n      signal: controller.signal,\n      headers: {\n        Accept: \"application/pdf\",\n      },\n    });\n\n    if (!response.ok) {\n      return err(\"INVALID_RESPONSE\");\n    }\n\n    const arrayBuffer = await response.arrayBuffer();\n    return ok(Buffer.from(arrayBuffer));\n  } catch (error) {\n    if (error instanceof Error && error.name === \"AbortError\") {\n      return err(\"TIMEOUT\");\n    }\n    return err(\"FETCH_ERROR\");\n  } finally {\n    clearTimeout(timeoutId);\n  }\n}\n","/**\n * ASCII Terminal Renderer\n *\n * Renders the workflow IR as ASCII art with box-drawing characters\n * and ANSI colors for terminal display.\n */\n\nimport { ok, err, type Result } from \"awaitly\";\nimport type {\n  FlowNode,\n  ParallelNode,\n  RaceNode,\n  DecisionNode,\n  StreamNode,\n  Renderer,\n  RenderOptions,\n  StepNode,\n  WorkflowIR,\n  EnhancedRenderOptions,\n  HeatLevel,\n  WorkflowHooks,\n  HookExecution,\n} from \"../types\";\nimport { isParallelNode, isRaceNode, isStepNode, isDecisionNode, isStreamNode } from \"../types\";\nimport { formatDuration } from \"../utils/timing\";\nimport {\n  bold,\n  colorByState,\n  colorize,\n  defaultColorScheme,\n  dim,\n  getColoredSymbol,\n  stripAnsi,\n} from \"./colors\";\n\n/**\n * Error types for stringify operations.\n */\nexport type StringifyError = \"STRINGIFY_ERROR\";\n\n// =============================================================================\n// Box Drawing Characters\n// =============================================================================\n\nconst BOX = {\n  topLeft: \"┌\",\n  topRight: \"┐\",\n  bottomLeft: \"└\",\n  bottomRight: \"┘\",\n  horizontal: \"─\",\n  vertical: \"│\",\n  teeRight: \"├\",\n  teeLeft: \"┤\",\n  teeDown: \"┬\",\n  teeUp: \"┴\",\n  cross: \"┼\",\n} as const;\n\n// =============================================================================\n// Heatmap Colors (ANSI)\n// =============================================================================\n\n/**\n * ANSI color codes for heatmap visualization.\n */\nconst HEAT_COLORS: Record<HeatLevel, string> = {\n  cold: \"\\x1b[34m\",      // Blue\n  cool: \"\\x1b[36m\",      // Cyan\n  neutral: \"\",           // Default (no color)\n  warm: \"\\x1b[33m\",      // Yellow\n  hot: \"\\x1b[31m\",       // Red\n  critical: \"\\x1b[41m\",  // Red background\n};\n\nconst RESET = \"\\x1b[0m\";\n\n/**\n * Get ANSI color code for a heat level.\n */\nfunction getHeatColor(heat: number): string {\n  if (heat < 0.2) return HEAT_COLORS.cold;\n  if (heat < 0.4) return HEAT_COLORS.cool;\n  if (heat < 0.6) return HEAT_COLORS.neutral;\n  if (heat < 0.8) return HEAT_COLORS.warm;\n  if (heat < 0.95) return HEAT_COLORS.hot;\n  return HEAT_COLORS.critical;\n}\n\n/**\n * Apply heat coloring to a string.\n */\nfunction applyHeatColor(text: string, heat: number): string {\n  const color = getHeatColor(heat);\n  if (!color) return text;\n  return `${color}${text}${RESET}`;\n}\n\n/**\n * Safely stringify a value, handling circular references and BigInt.\n * Returns Result with either the stringified value or an error.\n */\nfunction safeStringify(value: unknown): Result<string, StringifyError> {\n  try {\n    const replacer = (_key: string, v: unknown): unknown => {\n      if (typeof v !== \"bigint\") return v;\n      const n = Number(v);\n      return Number.isSafeInteger(n) ? n : v.toString();\n    };\n    return ok(JSON.stringify(value, replacer));\n  } catch {\n    return err(\"STRINGIFY_ERROR\");\n  }\n}\n\n/**\n * Get stringified value or fallback for unserializable values.\n */\nfunction getStringified(value: unknown): string {\n  const result = safeStringify(value);\n  return result.ok ? result.value : \"[unserializable]\";\n}\n\n// =============================================================================\n// Sparkline Characters\n// =============================================================================\n\nconst SPARK_CHARS = \"▁▂▃▄▅▆▇█\";\n\n/**\n * Render a sparkline from an array of values.\n *\n * @param values Array of numeric values\n * @param width Maximum characters to use (default: 10)\n * @returns Sparkline string\n */\nexport function renderSparkline(values: number[], width = 10): string {\n  if (values.length === 0) return \"\";\n\n  // Take last N values\n  const subset = values.slice(-width);\n  const min = Math.min(...subset);\n  const max = Math.max(...subset);\n  const range = max - min || 1;\n\n  return subset\n    .map((v) => {\n      const normalized = (v - min) / range;\n      const index = Math.floor(normalized * (SPARK_CHARS.length - 1));\n      return SPARK_CHARS[index];\n    })\n    .join(\"\");\n}\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Pad a string to a fixed width, accounting for ANSI codes.\n */\nfunction padEnd(str: string, width: number): string {\n  const visibleLen = stripAnsi(str).length;\n  const padding = Math.max(0, width - visibleLen);\n  return str + \" \".repeat(padding);\n}\n\n/**\n * Create a horizontal line with optional title.\n */\nfunction horizontalLine(width: number, title?: string): string {\n  if (!title) {\n    return BOX.horizontal.repeat(width);\n  }\n\n  const titleText = ` ${title} `;\n  // Use visible length (strip ANSI codes) for width calculation\n  const visibleTitleLen = stripAnsi(titleText).length;\n  const remainingWidth = width - visibleTitleLen;\n  if (remainingWidth < 4) {\n    return BOX.horizontal.repeat(width);\n  }\n\n  const leftPad = 2;\n  const rightPad = remainingWidth - leftPad;\n\n  return (\n    BOX.horizontal.repeat(leftPad) + titleText + BOX.horizontal.repeat(rightPad)\n  );\n}\n\n// =============================================================================\n// Hook Rendering\n// =============================================================================\n\n/**\n * Render a single hook execution.\n */\nfunction renderHookExecution(\n  hook: HookExecution,\n  label: string,\n  colors: ReturnType<typeof Object.assign>\n): string {\n  const symbol = hook.state === \"success\"\n    ? colorize(\"⚙\", colors.success)\n    : colorize(\"⚠\", colors.error);\n\n  const timing = hook.durationMs !== undefined\n    ? dim(` [${formatDuration(hook.durationMs)}]`)\n    : \"\";\n\n  let context = \"\";\n  if (hook.type === \"shouldRun\" && hook.context?.skipped) {\n    context = dim(\" → workflow skipped\");\n  } else if (hook.type === \"shouldRun\" && hook.context?.result === true) {\n    context = dim(\" → proceed\");\n  } else if (hook.type === \"onBeforeStart\" && hook.context?.skipped) {\n    context = dim(\" → workflow skipped\");\n  } else if (hook.type === \"onAfterStep\" && hook.context?.stepKey) {\n    context = dim(` (${hook.context.stepKey})`);\n  }\n\n  const error = hook.state === \"error\" && hook.error\n    ? dim(` error: ${String(hook.error)}`)\n    : \"\";\n\n  return `${symbol} ${dim(label)}${context}${timing}${error}`;\n}\n\n/**\n * Render workflow hooks section.\n */\nfunction renderHooks(\n  hooks: WorkflowHooks,\n  colors: ReturnType<typeof Object.assign>\n): string[] {\n  const lines: string[] = [];\n\n  // Render shouldRun hook\n  if (hooks.shouldRun) {\n    lines.push(renderHookExecution(hooks.shouldRun, \"shouldRun\", colors));\n  }\n\n  // Render onBeforeStart hook\n  if (hooks.onBeforeStart) {\n    lines.push(renderHookExecution(hooks.onBeforeStart, \"onBeforeStart\", colors));\n  }\n\n  // We don't render onAfterStep hooks here - they're shown inline with steps\n  // But if there are any, add a separator\n  if (lines.length > 0) {\n    lines.push(dim(\"────────────────────\")); // Separator between hooks and steps\n  }\n\n  return lines;\n}\n\n// =============================================================================\n// ASCII Renderer\n// =============================================================================\n\n/**\n * Create the ASCII terminal renderer.\n */\nexport function asciiRenderer(): Renderer {\n  return {\n    name: \"ascii\",\n    supportsLive: true,\n\n    render(ir: WorkflowIR, options: RenderOptions): string {\n      const colors = { ...defaultColorScheme, ...options.colors };\n      // Ensure minimum width to prevent negative repeat counts\n      const width = Math.max(options.terminalWidth ?? 60, 5);\n      const innerWidth = width - 4; // Account for borders\n\n      const lines: string[] = [];\n\n      // Header\n      const workflowName = ir.root.name ?? \"workflow\";\n      const headerTitle = bold(workflowName);\n      lines.push(\n        `${BOX.topLeft}${horizontalLine(width - 2, headerTitle)}${BOX.topRight}`\n      );\n      lines.push(`${BOX.vertical}${\" \".repeat(width - 2)}${BOX.vertical}`);\n\n      // Render hooks (if any)\n      if (ir.hooks) {\n        const hookLines = renderHooks(ir.hooks, colors);\n        for (const line of hookLines) {\n          lines.push(\n            `${BOX.vertical}  ${padEnd(line, innerWidth)}${BOX.vertical}`\n          );\n        }\n      }\n\n      // Render children\n      const childLines = renderNodes(ir.root.children, options, colors, 0, ir.hooks);\n      for (const line of childLines) {\n        lines.push(\n          `${BOX.vertical}  ${padEnd(line, innerWidth)}${BOX.vertical}`\n        );\n      }\n\n      // Footer with timing\n      lines.push(`${BOX.vertical}${\" \".repeat(width - 2)}${BOX.vertical}`);\n\n      if (ir.root.durationMs !== undefined && options.showTimings) {\n        const status =\n          ir.root.state === \"success\"\n            ? \"Completed\"\n            : ir.root.state === \"aborted\"\n              ? \"Cancelled\"\n              : \"Failed\";\n        const statusColored = colorByState(status, ir.root.state, colors);\n        const footer = `${statusColored} in ${formatDuration(ir.root.durationMs)}`;\n        lines.push(\n          `${BOX.vertical}  ${padEnd(footer, innerWidth)}${BOX.vertical}`\n        );\n        lines.push(`${BOX.vertical}${\" \".repeat(width - 2)}${BOX.vertical}`);\n      }\n\n      lines.push(\n        `${BOX.bottomLeft}${BOX.horizontal.repeat(width - 2)}${BOX.bottomRight}`\n      );\n\n      return lines.join(\"\\n\");\n    },\n  };\n}\n\n/**\n * Render a list of nodes.\n */\nfunction renderNodes(\n  nodes: FlowNode[],\n  options: RenderOptions,\n  colors: ReturnType<typeof Object.assign>,\n  depth: number,\n  hooks?: WorkflowHooks\n): string[] {\n  const lines: string[] = [];\n\n  for (const node of nodes) {\n    if (isStepNode(node)) {\n      lines.push(renderStepNode(node, options, colors, hooks));\n    } else if (isParallelNode(node)) {\n      lines.push(...renderParallelNode(node, options, colors, depth, hooks));\n    } else if (isRaceNode(node)) {\n      lines.push(...renderRaceNode(node, options, colors, depth, hooks));\n    } else if (isDecisionNode(node)) {\n      lines.push(...renderDecisionNode(node, options, colors, depth, hooks));\n    } else if (isStreamNode(node)) {\n      lines.push(renderStreamNode(node, options, colors));\n    }\n  }\n\n  return lines;\n}\n\n/**\n * Render a single step node.\n */\nfunction renderStepNode(\n  node: StepNode,\n  options: RenderOptions,\n  colors: ReturnType<typeof Object.assign>,\n  hooks?: WorkflowHooks\n): string {\n  const symbol = getColoredSymbol(node.state, colors);\n  const name = node.name ?? node.key ?? \"step\";\n\n  // Check for enhanced options\n  const enhanced = options as EnhancedRenderOptions;\n  // Heatmap lookup order matches PerformanceAnalyzer: key ?? name ?? id\n  const heat = enhanced.showHeatmap && enhanced.heatmapData\n    ? enhanced.heatmapData.heat.get(node.key ?? \"\") ??\n      enhanced.heatmapData.heat.get(node.name ?? \"\") ??\n      enhanced.heatmapData.heat.get(node.id)\n    : undefined;\n\n  // Apply heat coloring or default state coloring\n  let nameColored: string;\n  if (heat !== undefined) {\n    nameColored = applyHeatColor(name, heat);\n  } else {\n    nameColored = colorByState(name, node.state, colors);\n  }\n\n  let line = `${symbol} ${nameColored}`;\n\n  // Add key if requested (only when step has a name, so key is not already the label)\n  if (options.showKeys && node.key && node.name) {\n    line += dim(` [key: ${node.key}]`);\n  }\n\n  // Add input/output if available (for decision understanding)\n  if (node.input !== undefined) {\n    const inputStr = typeof node.input === \"string\"\n      ? node.input\n      : getStringified(node.input).slice(0, 30);\n    line += dim(` [in: ${inputStr}${inputStr.length >= 30 ? \"...\" : \"\"}]`);\n  }\n  if (node.output !== undefined && node.state === \"success\") {\n    const outputStr = typeof node.output === \"string\"\n      ? node.output\n      : getStringified(node.output).slice(0, 30);\n    line += dim(` [out: ${outputStr}${outputStr.length >= 30 ? \"...\" : \"\"}]`);\n  }\n\n  // Add timing if available and requested\n  if (options.showTimings && node.durationMs !== undefined) {\n    // Apply heat coloring to timing if enabled\n    const timingStr = formatDuration(node.durationMs);\n    const timingDisplay = heat !== undefined\n      ? applyHeatColor(`[${timingStr}]`, heat)\n      : dim(`[${timingStr}]`);\n    line += ` ${timingDisplay}`;\n  }\n\n  // Add sparkline if enabled and history available (lookup order: key ?? name ?? id, like analyzer)\n  if (enhanced.showSparklines && enhanced.timingHistory) {\n    const history =\n      enhanced.timingHistory.get(node.key ?? \"\") ??\n      enhanced.timingHistory.get(node.name ?? \"\") ??\n      enhanced.timingHistory.get(node.id);\n    if (history && history.length > 1) {\n      line += ` ${dim(renderSparkline(history))}`;\n    }\n  }\n\n  // Add retry indicator if retries occurred\n  if (node.retryCount !== undefined && node.retryCount > 0) {\n    line += dim(` [${node.retryCount} ${node.retryCount === 1 ? \"retry\" : \"retries\"}]`);\n  }\n\n  // Add timeout indicator if step timed out\n  if (node.timedOut) {\n    const timeoutInfo = node.timeoutMs !== undefined ? ` ${node.timeoutMs}ms` : \"\";\n    line += dim(` [timeout${timeoutInfo}]`);\n  }\n\n  // Add onAfterStep hook indicator if present (check by key first, then by id)\n  const hookKey = node.key ?? node.id;\n  if (hooks && hookKey && hooks.onAfterStep.has(hookKey)) {\n    const hookExec = hooks.onAfterStep.get(hookKey)!;\n    const hookSymbol = hookExec.state === \"success\"\n      ? colorize(\"⚙\", colors.success)\n      : colorize(\"⚠\", colors.error);\n    const hookTiming = hookExec.durationMs !== undefined\n      ? dim(` ${formatDuration(hookExec.durationMs)}`)\n      : \"\";\n    line += ` ${hookSymbol}${hookTiming}`;\n  }\n\n  return line;\n}\n\n/**\n * Render a stream node.\n */\nfunction renderStreamNode(\n  node: StreamNode,\n  options: RenderOptions,\n  colors: ReturnType<typeof Object.assign>\n): string {\n  // Use stream-specific symbol\n  const stateSymbol = node.streamState === \"active\"\n    ? colorize(\"⟳\", colors.running)\n    : node.streamState === \"closed\"\n      ? colorize(\"✓\", colors.success)\n      : colorize(\"✗\", colors.error);\n\n  const name = `stream:${node.namespace}`;\n  const nameColored = colorByState(name, node.state, colors);\n\n  // Show write/read counts\n  const counts = dim(`[W:${node.writeCount} R:${node.readCount}]`);\n\n  let line = `${stateSymbol} ${nameColored} ${counts}`;\n\n  // Add timing if available and requested\n  if (options.showTimings && node.durationMs !== undefined) {\n    line += ` ${dim(`[${formatDuration(node.durationMs)}]`)}`;\n  }\n\n  // Add backpressure indicator if occurred\n  if (node.backpressureOccurred) {\n    line += dim(\" [backpressure]\");\n  }\n\n  // Add final position\n  if (node.streamState === \"closed\") {\n    line += dim(` pos:${node.finalPosition}`);\n  }\n\n  return line;\n}\n\n/**\n * Render a parallel node (allAsync).\n */\nfunction renderParallelNode(\n  node: ParallelNode,\n  options: RenderOptions,\n  colors: ReturnType<typeof Object.assign>,\n  depth: number,\n  hooks?: WorkflowHooks\n): string[] {\n  const lines: string[] = [];\n  const indent = \"  \".repeat(depth);\n\n  // Header\n  const symbol = getColoredSymbol(node.state, colors);\n  const name = node.name ?? \"parallel\";\n  const mode = node.mode === \"allSettled\" ? \" (allSettled)\" : \"\";\n  lines.push(`${indent}${BOX.teeRight}${BOX.teeDown}${BOX.horizontal} ${symbol} ${bold(name)}${mode}`);\n\n  // Children\n  if (node.children.length === 0) {\n    // Empty parallel scope - operations inside allAsync/anyAsync weren't tracked as steps\n    lines.push(`${indent}${BOX.vertical} ${dim(\"(operations not individually tracked)\")}`);\n    lines.push(`${indent}${BOX.vertical} ${dim(\"(wrap each operation with step() to see individual steps)\")}`);\n  } else {\n    for (let i = 0; i < node.children.length; i++) {\n      const child = node.children[i];\n      const isLast = i === node.children.length - 1;\n      const prefix = isLast ? `${indent}${BOX.vertical} ${BOX.bottomLeft}` : `${indent}${BOX.vertical} ${BOX.teeRight}`;\n\n      if (isStepNode(child)) {\n        lines.push(`${prefix} ${renderStepNode(child, options, colors, hooks)}`);\n      } else {\n        // Nested structure - recurse\n        const nestedLines = renderNodes([child], options, colors, depth + 1, hooks);\n        for (const line of nestedLines) {\n          lines.push(`${indent}${BOX.vertical}   ${line}`);\n        }\n      }\n    }\n  }\n\n  // Timing footer\n  if (options.showTimings && node.durationMs !== undefined) {\n    lines.push(`${indent}${BOX.bottomLeft}${BOX.horizontal}${BOX.horizontal} ${dim(`[${formatDuration(node.durationMs)}]`)}`);\n  }\n\n  return lines;\n}\n\n/**\n * Render a race node (anyAsync).\n */\nfunction renderRaceNode(\n  node: RaceNode,\n  options: RenderOptions,\n  colors: ReturnType<typeof Object.assign>,\n  depth: number,\n  hooks?: WorkflowHooks\n): string[] {\n  const lines: string[] = [];\n  const indent = \"  \".repeat(depth);\n\n  // Header with lightning bolt for race\n  const symbol = getColoredSymbol(node.state, colors);\n  const name = node.name ?? \"race\";\n  lines.push(`${indent}${BOX.teeRight}⚡ ${symbol} ${bold(name)}`);\n\n  // Children\n  if (node.children.length === 0) {\n    // Empty race scope - operations inside anyAsync weren't tracked as steps\n    lines.push(`${indent}${BOX.vertical} ${dim(\"(operations not individually tracked)\")}`);\n    lines.push(`${indent}${BOX.vertical} ${dim(\"(wrap each operation with step() to see individual steps)\")}`);\n  } else {\n    for (let i = 0; i < node.children.length; i++) {\n      const child = node.children[i];\n      const isLast = i === node.children.length - 1;\n      const prefix = isLast ? `${indent}${BOX.vertical} ${BOX.bottomLeft}` : `${indent}${BOX.vertical} ${BOX.teeRight}`;\n\n      // Mark winner\n      const isWinner = node.winnerId && child.id === node.winnerId;\n      const winnerSuffix = isWinner ? dim(\" (winner)\") : \"\";\n\n      if (isStepNode(child)) {\n        lines.push(`${prefix} ${renderStepNode(child, options, colors, hooks)}${winnerSuffix}`);\n      } else {\n        const nestedLines = renderNodes([child], options, colors, depth + 1, hooks);\n        for (const line of nestedLines) {\n          lines.push(`${indent}${BOX.vertical}   ${line}`);\n        }\n      }\n    }\n  }\n\n  // Timing footer\n  if (options.showTimings && node.durationMs !== undefined) {\n    lines.push(`${indent}${BOX.bottomLeft}${BOX.horizontal}${BOX.horizontal} ${dim(`[${formatDuration(node.durationMs)}]`)}`);\n  }\n\n  return lines;\n}\n\n/**\n * Render a decision node (conditional branch).\n */\nfunction renderDecisionNode(\n  node: DecisionNode,\n  options: RenderOptions,\n  colors: ReturnType<typeof Object.assign>,\n  depth: number,\n  hooks?: WorkflowHooks\n): string[] {\n  const lines: string[] = [];\n  const indent = \"  \".repeat(depth);\n\n  // Header with decision info\n  const symbol = getColoredSymbol(node.state, colors);\n  const name = node.name ?? \"decision\";\n  const condition = node.condition\n    ? dim(` (${node.condition})`)\n    : \"\";\n  const decisionValue = node.decisionValue !== undefined\n    ? dim(` = ${String(node.decisionValue)}`)\n    : \"\";\n  const branchTaken = node.branchTaken !== undefined\n    ? dim(` → ${String(node.branchTaken)}`)\n    : \"\";\n\n  lines.push(\n    `${indent}${BOX.teeRight}${BOX.teeDown}${BOX.horizontal} ${symbol} ${bold(name)}${condition}${decisionValue}${branchTaken}`\n  );\n\n  // Render branches\n  for (let i = 0; i < node.branches.length; i++) {\n    const branch = node.branches[i];\n    const isLast = i === node.branches.length - 1;\n    const prefix = isLast\n      ? `${indent}${BOX.vertical} ${BOX.bottomLeft}`\n      : `${indent}${BOX.vertical} ${BOX.teeRight}`;\n\n    // Branch label with taken/skipped indicator\n    const branchSymbol = branch.taken ? \"✓\" : \"⊘\";\n    const branchColor = branch.taken ? colors.success : colors.skipped;\n    const branchLabel = colorize(\n      `${branchSymbol} ${branch.label}`,\n      branchColor\n    );\n    const branchCondition = branch.condition\n      ? dim(` (${branch.condition})`)\n      : \"\";\n\n    lines.push(`${prefix} ${branchLabel}${branchCondition}`);\n\n    // Render children of this branch\n    if (branch.children.length > 0) {\n      const childLines = renderNodes(branch.children, options, colors, depth + 1, hooks);\n      for (const line of childLines) {\n        lines.push(`${indent}${BOX.vertical}   ${line}`);\n      }\n    } else if (!branch.taken) {\n      // Show that this branch was skipped\n      lines.push(\n        `${indent}${BOX.vertical}   ${dim(\"(skipped)\")}`\n      );\n    }\n  }\n\n  // Timing footer\n  if (options.showTimings && node.durationMs !== undefined) {\n    lines.push(\n      `${indent}${BOX.bottomLeft}${BOX.horizontal}${BOX.horizontal} ${dim(`[${formatDuration(node.durationMs)}]`)}`\n    );\n  }\n\n  return lines;\n}\n\nexport { defaultColorScheme };\n","/**\n * Workflow Visualization - Intermediate Representation Types\n *\n * The IR (Intermediate Representation) is a DSL that represents workflow\n * execution structure. Events are converted to IR, which can then be\n * rendered to various output formats (ASCII, Mermaid, JSON, etc.).\n */\n\n// =============================================================================\n// Step States\n// =============================================================================\n\n/**\n * Execution state of a step with semantic meaning for visualization.\n *\n * Color mapping:\n * - pending  → white/clear (not started)\n * - running  → yellow (currently executing)\n * - success  → green (completed successfully)\n * - error    → red (failed with error)\n * - aborted  → gray (cancelled, e.g., in race)\n * - cached   → blue (served from cache)\n * - skipped  → dim gray (not executed due to conditional logic)\n */\nexport type StepState =\n  | \"pending\"\n  | \"running\"\n  | \"success\"\n  | \"error\"\n  | \"aborted\"\n  | \"cached\"\n  | \"skipped\";\n\n// =============================================================================\n// Node Types\n// =============================================================================\n\n/**\n * Base properties shared by all IR nodes.\n */\nexport interface BaseNode {\n  /** Unique identifier for this node */\n  id: string;\n  /** Human-readable name (from step options or inferred) */\n  name?: string;\n  /** Cache key if this is a keyed step */\n  key?: string;\n  /** Current execution state */\n  state: StepState;\n  /** Timestamp when execution started */\n  startTs?: number;\n  /** Timestamp when execution ended */\n  endTs?: number;\n  /** Duration in milliseconds */\n  durationMs?: number;\n  /** Error value if state is 'error' */\n  error?: unknown;\n  /** Input value that triggered this step (for decision understanding) */\n  input?: unknown;\n  /** Output value from this step (for decision understanding) */\n  output?: unknown;\n  /** Number of retry attempts made (0 = no retries, 1 = one retry, etc.) */\n  retryCount?: number;\n  /** Whether this step experienced a timeout (may have retried after) */\n  timedOut?: boolean;\n  /** Timeout duration in ms (if timed out) */\n  timeoutMs?: number;\n  /** Agent metadata from step options (domain, intent, owner, etc.) */\n  metadata?: import(\"awaitly\").StepMetadata;\n  /** Error diagnostics (tag, classification, origin) */\n  errorDiagnostics?: import(\"awaitly\").StepErrorDiagnostics;\n}\n\n/**\n * A single step execution node.\n */\nexport interface StepNode extends BaseNode {\n  type: \"step\";\n}\n\n/**\n * Sequential execution - steps run one after another.\n * This is the implicit structure when steps are awaited in sequence.\n */\nexport interface SequenceNode extends BaseNode {\n  type: \"sequence\";\n  children: FlowNode[];\n}\n\n/**\n * Parallel execution - all branches run simultaneously.\n * Created by allAsync() or allSettledAsync().\n */\nexport interface ParallelNode extends BaseNode {\n  type: \"parallel\";\n  children: FlowNode[];\n  /**\n   * Execution mode:\n   * - 'all': Fails on first error (allAsync)\n   * - 'allSettled': Collects all results (allSettledAsync)\n   */\n  mode: \"all\" | \"allSettled\";\n}\n\n/**\n * Race execution - first to complete wins.\n * Created by anyAsync().\n */\nexport interface RaceNode extends BaseNode {\n  type: \"race\";\n  children: FlowNode[];\n  /** ID of the winning branch (first to succeed) */\n  winnerId?: string;\n}\n\n/**\n * Stream operation node.\n * Tracks streaming events (write, read, close, error, backpressure).\n */\nexport interface StreamNode extends BaseNode {\n  type: \"stream\";\n  /** Stream namespace identifier */\n  namespace: string;\n  /** Total number of write operations */\n  writeCount: number;\n  /** Total number of read operations */\n  readCount: number;\n  /** Final position when stream closed */\n  finalPosition: number;\n  /** Current stream state */\n  streamState: \"active\" | \"closed\" | \"error\";\n  /** Whether backpressure was encountered during streaming */\n  backpressureOccurred: boolean;\n}\n\n/**\n * Decision point - conditional branch (if/switch).\n * Shows which branch was taken and why.\n */\nexport interface DecisionNode extends BaseNode {\n  type: \"decision\";\n  /** Condition that was evaluated (e.g., \"user.role === 'admin'\") */\n  condition?: string;\n  /** Value that was evaluated (the input to the decision) */\n  decisionValue?: unknown;\n  /** Which branch was taken (true/false, or the matched case) */\n  branchTaken?: string | boolean;\n  /** All possible branches (including skipped ones) */\n  branches: DecisionBranch[];\n}\n\n/**\n * A branch in a decision node.\n */\nexport interface DecisionBranch {\n  /** Label for this branch (e.g., \"if\", \"else\", \"case 'admin'\") */\n  label: string;\n  /** Condition that would trigger this branch */\n  condition?: string;\n  /** Whether this branch was taken */\n  taken: boolean;\n  /** Steps in this branch */\n  children: FlowNode[];\n}\n\n/**\n * Union of all flow node types.\n */\nexport type FlowNode = StepNode | SequenceNode | ParallelNode | RaceNode | DecisionNode | StreamNode;\n\n/**\n * Root node representing the entire workflow.\n */\nexport interface WorkflowNode extends BaseNode {\n  type: \"workflow\";\n  /** Correlation ID from the workflow execution */\n  workflowId: string;\n  /** Child nodes (steps, parallel blocks, etc.) */\n  children: FlowNode[];\n}\n\n// =============================================================================\n// Workflow IR\n// =============================================================================\n\n/**\n * Complete workflow intermediate representation.\n * This is the main data structure produced by the IR builder.\n */\nexport interface WorkflowIR {\n  /** Root workflow node */\n  root: WorkflowNode;\n  /** Metadata about the IR */\n  metadata: {\n    /** When the IR was first created */\n    createdAt: number;\n    /** When the IR was last updated */\n    lastUpdatedAt: number;\n  };\n  /** Hook executions (if any hooks are configured) */\n  hooks?: WorkflowHooks;\n}\n\n// =============================================================================\n// Scope Events (for parallel/race detection)\n// =============================================================================\n\n// Re-export ScopeType from awaitly for consistency\nexport type { ScopeType } from \"awaitly\";\nimport type { ScopeType, WorkflowEvent, WorkflowOptions } from \"awaitly\";\nimport type { UnexpectedError } from \"awaitly\";\n\n/**\n * Event emitted when entering a parallel/race scope.\n * This matches the scope_start event in WorkflowEvent.\n */\nexport interface ScopeStartEvent {\n  type: \"scope_start\";\n  workflowId: string;\n  scopeId: string;\n  scopeType: ScopeType;\n  name?: string;\n  ts: number;\n}\n\n/**\n * Event emitted when exiting a parallel/race scope.\n */\nexport interface ScopeEndEvent {\n  type: \"scope_end\";\n  workflowId: string;\n  scopeId: string;\n  ts: number;\n  durationMs: number;\n  /** For race scopes, the ID of the winning branch */\n  winnerId?: string;\n}\n\n/**\n * Event emitted when a decision point is encountered.\n * Use this to track conditional logic (if/switch).\n */\nexport interface DecisionStartEvent {\n  type: \"decision_start\";\n  workflowId: string;\n  decisionId: string;\n  /** Condition being evaluated (e.g., \"user.role === 'admin'\") */\n  condition?: string;\n  /** Value being evaluated */\n  decisionValue?: unknown;\n  /** Name/label for this decision point */\n  name?: string;\n  ts: number;\n}\n\n/**\n * Event emitted when a decision branch is taken.\n */\nexport interface DecisionBranchEvent {\n  type: \"decision_branch\";\n  workflowId: string;\n  decisionId: string;\n  /** Label for this branch (e.g., \"if\", \"else\", \"case 'admin'\") */\n  branchLabel: string;\n  /** Condition for this branch */\n  condition?: string;\n  /** Whether this branch was taken */\n  taken: boolean;\n  ts: number;\n}\n\n/**\n * Event emitted when a decision point completes.\n */\nexport interface DecisionEndEvent {\n  type: \"decision_end\";\n  workflowId: string;\n  decisionId: string;\n  /** Which branch was taken */\n  branchTaken?: string | boolean;\n  ts: number;\n  durationMs: number;\n}\n\n/**\n * Event emitted when a step is skipped due to conditional logic.\n */\nexport interface StepSkippedEvent {\n  type: \"step_skipped\";\n  workflowId: string;\n  stepKey?: string;\n  name?: string;\n  /** Reason why this step was skipped (e.g., \"condition was false\") */\n  reason?: string;\n  /** The decision that caused this skip */\n  decisionId?: string;\n  ts: number;\n}\n\n/**\n * Union of scope-related events.\n */\nexport type ScopeEvent = ScopeStartEvent | ScopeEndEvent;\n\n/**\n * Union of decision-related events.\n */\nexport type DecisionEvent = DecisionStartEvent | DecisionBranchEvent | DecisionEndEvent;\n\n// =============================================================================\n// Renderer Types\n// =============================================================================\n\n/**\n * Color scheme for rendering step states.\n */\nexport interface ColorScheme {\n  pending: string;\n  running: string;\n  success: string;\n  error: string;\n  aborted: string;\n  cached: string;\n  skipped: string;\n}\n\n/**\n * Options passed to renderers.\n */\nexport interface RenderOptions {\n  /** Show timing information (duration) */\n  showTimings: boolean;\n  /** Show step cache keys */\n  showKeys: boolean;\n  /** Terminal width for ASCII renderer */\n  terminalWidth?: number;\n  /** Color scheme */\n  colors: ColorScheme;\n}\n\n/**\n * Extended options for Mermaid renderer.\n * Controls how edges are displayed for retries, errors, and timeouts.\n */\nexport interface MermaidRenderOptions extends RenderOptions {\n  /** Show retry as self-loop edge (default: true) */\n  showRetryEdges?: boolean;\n  /** Show error flow to error node (default: true) */\n  showErrorEdges?: boolean;\n  /** Show timeout as alternative path (default: true) */\n  showTimeoutEdges?: boolean;\n}\n\n/**\n * Renderer interface - transforms IR to output format.\n */\nexport interface Renderer {\n  /** Unique identifier for this renderer */\n  readonly name: string;\n  /** Render IR to string output */\n  render(ir: WorkflowIR, options: RenderOptions): string;\n  /** Whether this renderer supports live (incremental) updates */\n  supportsLive?: boolean;\n  /** Render incremental update (optional) */\n  renderUpdate?(\n    ir: WorkflowIR,\n    changedNodes: FlowNode[],\n    options: RenderOptions\n  ): string;\n}\n\n// =============================================================================\n// Visualizer Types\n// =============================================================================\n\n/**\n * Output format for rendering.\n */\nexport type OutputFormat = \"ascii\" | \"mermaid\" | \"json\" | \"logger\" | \"flowchart\";\n\n/**\n * Options for creating a visualizer.\n */\nexport interface VisualizerOptions {\n  /** Name for the workflow in visualizations */\n  workflowName?: string;\n  /** Enable parallel detection heuristics (default: true) */\n  detectParallel?: boolean;\n  /** Show timing information (default: true) */\n  showTimings?: boolean;\n  /** Show step keys (default: false) */\n  showKeys?: boolean;\n  /** Custom color scheme */\n  colors?: Partial<ColorScheme>;\n  /**\n   * Export configuration for URL generation methods.\n   * Note: Treated as immutable after creation - do not mutate.\n   */\n  export?: {\n    /** Default export provider (opt-in). If not set, export methods require explicit provider. */\n    default?: ExportOptions;\n  };\n}\n\n/**\n * Options for createVisualizingWorkflow convenience factory.\n * Combines WorkflowOptions with VisualizerOptions.\n *\n * @example\n * ```typescript\n * const { workflow, visualizer } = createVisualizingWorkflow(deps, {\n *   workflowName: 'checkout',\n *   showTimings: true,\n *   forwardTo: (event) => console.log(event.type),\n * });\n * ```\n */\nexport interface VisualizingWorkflowOptions<E, C = void>\n  extends Omit<WorkflowOptions<E, C>, \"onEvent\">,\n    VisualizerOptions {\n  /** Forward events to additional handler (runs after visualization) */\n  forwardTo?: (event: WorkflowEvent<E | UnexpectedError, C>, ctx: C) => void;\n}\n\n/**\n * Options for live visualization.\n */\nexport interface LiveVisualizerOptions extends VisualizerOptions {\n  /** Output stream (default: process.stdout) */\n  stream?: NodeJS.WriteStream;\n  /** Update interval in ms (default: 100) */\n  updateInterval?: number;\n}\n\n// =============================================================================\n// Type Guards\n// =============================================================================\n\n/**\n * Check if a node is a StepNode.\n */\nexport function isStepNode(node: FlowNode): node is StepNode {\n  return node.type === \"step\";\n}\n\n/**\n * Check if a node is a SequenceNode.\n */\nexport function isSequenceNode(node: FlowNode): node is SequenceNode {\n  return node.type === \"sequence\";\n}\n\n/**\n * Check if a node is a ParallelNode.\n */\nexport function isParallelNode(node: FlowNode): node is ParallelNode {\n  return node.type === \"parallel\";\n}\n\n/**\n * Check if a node is a RaceNode.\n */\nexport function isRaceNode(node: FlowNode): node is RaceNode {\n  return node.type === \"race\";\n}\n\n/**\n * Check if a node is a DecisionNode.\n */\nexport function isDecisionNode(node: FlowNode): node is DecisionNode {\n  return node.type === \"decision\";\n}\n\n/**\n * Check if a node is a StreamNode.\n */\nexport function isStreamNode(node: FlowNode): node is StreamNode {\n  return node.type === \"stream\";\n}\n\n/**\n * Check if a node has children.\n */\nexport function hasChildren(\n  node: FlowNode\n): node is SequenceNode | ParallelNode | RaceNode | DecisionNode {\n  return \"children\" in node || (node.type === \"decision\" && \"branches\" in node);\n}\n\n// =============================================================================\n// Time Travel Types\n// =============================================================================\n\n/**\n * Snapshot of an active step's state at a point in time.\n */\nexport interface ActiveStepSnapshot {\n  id: string;\n  name?: string;\n  key?: string;\n  startTs: number;\n  retryCount: number;\n  timedOut: boolean;\n  timeoutMs?: number;\n}\n\n/**\n * A snapshot of the complete IR state at a specific point in time.\n * Used for time-travel debugging - each event creates a snapshot.\n */\nexport interface IRSnapshot {\n  /** Unique identifier for this snapshot */\n  id: string;\n  /** Index in the event sequence (0-based) */\n  eventIndex: number;\n  /** The event that triggered this snapshot */\n  event: unknown; // WorkflowEvent - avoid circular import\n  /** Complete IR state at this moment */\n  ir: WorkflowIR;\n  /** Timestamp when snapshot was taken */\n  timestamp: number;\n  /** Active step states at this moment (for debugging) */\n  activeSteps: Map<string, ActiveStepSnapshot>;\n}\n\n/**\n * State of the time-travel controller.\n */\nexport interface TimeTravelState {\n  /** All recorded snapshots */\n  snapshots: IRSnapshot[];\n  /** Current snapshot index (for playback position) */\n  currentIndex: number;\n  /** Whether playback is active */\n  isPlaying: boolean;\n  /** Playback speed multiplier (1.0 = realtime, 2.0 = 2x speed) */\n  playbackSpeed: number;\n  /** Whether recording is active */\n  isRecording: boolean;\n}\n\n// =============================================================================\n// Performance Analysis Types\n// =============================================================================\n\n/**\n * Performance metrics for a single node across multiple runs.\n */\nexport interface NodePerformance {\n  /** Node identifier (name or step ID) */\n  nodeId: string;\n  /** Average duration across all samples */\n  avgDurationMs: number;\n  /** Minimum duration observed */\n  minDurationMs: number;\n  /** Maximum duration observed */\n  maxDurationMs: number;\n  /** Standard deviation of durations */\n  stdDevMs: number;\n  /** Number of timing samples collected */\n  samples: number;\n  /** Retry frequency (0-1, where 1 = always retries) */\n  retryRate: number;\n  /** Timeout frequency (0-1) */\n  timeoutRate: number;\n  /** Error rate (0-1) */\n  errorRate: number;\n  /** Percentile data for distribution analysis */\n  percentiles: {\n    p50: number;\n    p90: number;\n    p95: number;\n    p99: number;\n  };\n}\n\n/**\n * Heatmap data for visualizing performance across nodes.\n */\nexport interface HeatmapData {\n  /** Map of node ID to heat level (0-1, where 1 is hottest/slowest) */\n  heat: Map<string, number>;\n  /** The metric used for heat calculation */\n  metric: \"duration\" | \"retryRate\" | \"errorRate\";\n  /** Statistics used to compute heat values */\n  stats: {\n    /** Minimum value in the dataset */\n    min: number;\n    /** Maximum value in the dataset */\n    max: number;\n    /** Mean value */\n    mean: number;\n    /** Threshold above which a node is considered \"hot\" */\n    threshold: number;\n  };\n}\n\n/**\n * Heat level for visual styling.\n */\nexport type HeatLevel = \"cold\" | \"cool\" | \"neutral\" | \"warm\" | \"hot\" | \"critical\";\n\n// =============================================================================\n// HTML Renderer Types\n// =============================================================================\n\n/**\n * Theme for the HTML visualizer.\n */\nexport type HTMLTheme = \"light\" | \"dark\" | \"auto\";\n\n/**\n * Layout direction for the workflow diagram.\n */\nexport type LayoutDirection = \"TB\" | \"LR\" | \"BT\" | \"RL\";\n\n/**\n * Options for the HTML renderer.\n */\nexport interface HTMLRenderOptions extends RenderOptions {\n  /** Enable interactive features (click to inspect, zoom/pan) */\n  interactive: boolean;\n  /** Include time-travel controls */\n  timeTravel: boolean;\n  /** Include performance heatmap overlay */\n  heatmap: boolean;\n  /** Animation duration for transitions (ms) */\n  animationDuration: number;\n  /** Color theme */\n  theme: HTMLTheme;\n  /** Diagram layout direction */\n  layout: LayoutDirection;\n  /** Heatmap data (if heatmap is enabled) */\n  heatmapData?: HeatmapData;\n  /** WebSocket URL for live updates (if streaming) */\n  wsUrl?: string;\n}\n\n/**\n * Message sent from the web visualizer to the dev server.\n */\nexport interface WebVisualizerMessage {\n  type:\n    | \"time_travel_seek\"\n    | \"time_travel_play\"\n    | \"time_travel_pause\"\n    | \"time_travel_step_forward\"\n    | \"time_travel_step_backward\"\n    | \"request_snapshots\"\n    | \"toggle_heatmap\"\n    | \"set_heatmap_metric\";\n  payload?: unknown;\n}\n\n/**\n * Message sent from the dev server to the web visualizer.\n */\nexport interface ServerMessage {\n  type:\n    | \"ir_update\"\n    | \"snapshot\"\n    | \"snapshots_list\"\n    | \"performance_data\"\n    | \"workflow_complete\"\n    | \"time_travel_state\";\n  payload: unknown;\n}\n\n// =============================================================================\n// Enhanced ASCII Renderer Types\n// =============================================================================\n\n/**\n * Extended render options for the enhanced ASCII renderer.\n */\nexport interface EnhancedRenderOptions extends RenderOptions {\n  /** Show performance heatmap coloring */\n  showHeatmap?: boolean;\n  /** Heatmap data for coloring nodes */\n  heatmapData?: HeatmapData;\n  /** Show timing sparklines (requires historical data) */\n  showSparklines?: boolean;\n  /** Historical timing data for sparklines: nodeId → array of durations */\n  timingHistory?: Map<string, number[]>;\n}\n\n/**\n * Options for the flowchart ASCII renderer.\n * Renders workflow as a proper flowchart with boxes and arrows.\n */\nexport interface FlowchartRenderOptions extends EnhancedRenderOptions {\n  /** Show start and end nodes (default: true) */\n  showStartEnd?: boolean;\n  /** Reduce vertical spacing between nodes (default: false) */\n  compact?: boolean;\n  /** Box border style (default: 'single') */\n  boxStyle?: \"single\" | \"double\" | \"rounded\";\n}\n\n// =============================================================================\n// Hook Execution Types\n// =============================================================================\n\n/**\n * State of a hook execution.\n */\nexport type HookState = \"pending\" | \"running\" | \"success\" | \"error\";\n\n/**\n * Execution record for a workflow hook.\n */\nexport interface HookExecution {\n  /** Hook type identifier */\n  type: \"shouldRun\" | \"onBeforeStart\" | \"onAfterStep\";\n  /** Execution state */\n  state: HookState;\n  /** Timestamp when hook started */\n  ts: number;\n  /** Duration in milliseconds */\n  durationMs?: number;\n  /** Error if hook failed */\n  error?: unknown;\n  /** Additional context (e.g., stepKey for onAfterStep) */\n  context?: {\n    /** Step key for onAfterStep hooks */\n    stepKey?: string;\n    /** Result of shouldRun hook */\n    result?: boolean;\n    /** Whether workflow was skipped due to shouldRun returning false */\n    skipped?: boolean;\n  };\n}\n\n/**\n * Hook execution summary for the workflow.\n */\nexport interface WorkflowHooks {\n  /** shouldRun hook execution (if configured) */\n  shouldRun?: HookExecution;\n  /** onBeforeStart hook execution (if configured) */\n  onBeforeStart?: HookExecution;\n  /** onAfterStep hook executions (keyed by stepKey) */\n  onAfterStep: Map<string, HookExecution>;\n}\n\n// =============================================================================\n// Export Types\n// =============================================================================\n\n/**\n * Export format for diagram URLs.\n */\nexport type ExportFormat = \"svg\" | \"png\" | \"pdf\";\n\n/**\n * Diagram source - future-proof union for multiple diagram types.\n * Uses \"kind\" internally, maps to \"diagramType\" for Kroki API.\n */\nexport type DiagramSource =\n  | { kind: \"mermaid\"; source: string }\n  | { kind: \"graphviz\"; source: string }\n  | { kind: \"plantuml\"; source: string };\n\n/**\n * Kroki-specific export options.\n * Note: No background/scale - Kroki doesn't support them for mermaid diagrams.\n */\nexport interface KrokiExportOptions {\n  /** Provider identifier */\n  provider: \"kroki\";\n  /** Base URL for self-hosted Kroki (default: https://kroki.io) */\n  baseUrl?: string;\n}\n\n/**\n * Mermaid.ink-specific export options.\n * Supports additional styling options like background, scale, and theme.\n */\nexport interface MermaidInkExportOptions {\n  /** Provider identifier */\n  provider: \"mermaid-ink\";\n  /** Mermaid theme */\n  mermaidTheme?: \"default\" | \"dark\" | \"forest\" | \"neutral\";\n  /** Background color: \"transparent\" or hex color (e.g., \"1b1b1f\") */\n  background?: \"transparent\" | string;\n  /** Image scale (1-3) */\n  scale?: number;\n  /** Fit PDF to diagram size */\n  fit?: boolean;\n  /** Image width in pixels */\n  width?: number;\n  /** Image height in pixels */\n  height?: number;\n  /** Paper size for PDF */\n  paper?: \"a4\" | \"letter\";\n}\n\n/**\n * Discriminated union of export options.\n * Provider is the discriminant - no implicit defaults.\n */\nexport type ExportOptions = KrokiExportOptions | MermaidInkExportOptions;\n","/**\n * Timing utilities for workflow visualization.\n */\n\n/**\n * Format duration in milliseconds to a human-readable string.\n *\n * @example\n * formatDuration(23) // \"23ms\"\n * formatDuration(1500) // \"1.5s\"\n * formatDuration(65000) // \"1m 5s\"\n */\nexport function formatDuration(ms: number): string {\n  if (ms < 1000) {\n    return `${Math.round(ms)}ms`;\n  }\n\n  if (ms < 60000) {\n    const seconds = ms / 1000;\n    // Show one decimal for seconds\n    return `${seconds.toFixed(1).replace(/\\.0$/, \"\")}s`;\n  }\n\n  let minutes = Math.floor(ms / 60000);\n  let seconds = Math.round((ms % 60000) / 1000);\n  if (seconds >= 60) {\n    minutes += 1;\n    seconds = 0;\n  }\n\n  if (seconds === 0) {\n    return `${minutes}m`;\n  }\n\n  return `${minutes}m ${seconds}s`;\n}\n\n/**\n * Generate a unique ID for nodes.\n */\nexport function generateId(): string {\n  return `node_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n}\n","/**\n * ANSI color utilities for terminal output.\n */\n\nimport type { ColorScheme, StepState } from \"../types\";\n\n// =============================================================================\n// ANSI Escape Codes\n// =============================================================================\n\nconst RESET = \"\\x1b[0m\";\nconst BOLD = \"\\x1b[1m\";\nconst DIM = \"\\x1b[2m\";\n\n// Foreground colors\nconst FG_RED = \"\\x1b[31m\";\nconst FG_GREEN = \"\\x1b[32m\";\nconst FG_YELLOW = \"\\x1b[33m\";\nconst FG_BLUE = \"\\x1b[34m\";\nconst FG_GRAY = \"\\x1b[90m\";\nconst FG_WHITE = \"\\x1b[37m\";\n\n// =============================================================================\n// Color Functions\n// =============================================================================\n\n/**\n * Apply ANSI color to text.\n */\nexport function colorize(text: string, color: string): string {\n  if (!color) return text;\n  return `${color}${text}${RESET}`;\n}\n\n/**\n * Make text bold.\n */\nexport function bold(text: string): string {\n  return `${BOLD}${text}${RESET}`;\n}\n\n/**\n * Make text dim.\n */\nexport function dim(text: string): string {\n  return `${DIM}${text}${RESET}`;\n}\n\n// =============================================================================\n// Default Color Scheme\n// =============================================================================\n\n/**\n * Default ANSI color scheme for step states.\n */\nexport const defaultColorScheme: ColorScheme = {\n  pending: FG_WHITE,\n  running: FG_YELLOW,\n  success: FG_GREEN,\n  error: FG_RED,\n  aborted: FG_GRAY,\n  cached: FG_BLUE,\n  skipped: DIM + FG_GRAY, // Dim gray for skipped steps\n};\n\n// =============================================================================\n// State Symbols\n// =============================================================================\n\n/**\n * Get the symbol for a step state.\n */\nexport function getStateSymbol(state: StepState): string {\n  switch (state) {\n    case \"pending\":\n      return \"○\"; // Empty circle\n    case \"running\":\n      return \"⟳\"; // Rotating arrows\n    case \"success\":\n      return \"✓\"; // Check mark\n    case \"error\":\n      return \"✗\"; // X mark\n    case \"aborted\":\n      return \"⊘\"; // Circled slash\n    case \"cached\":\n      return \"↺\"; // Cached/replay\n    case \"skipped\":\n      return \"⊘\"; // Circled slash (same as aborted, but different color)\n  }\n}\n\n/**\n * Get the colored symbol for a step state.\n */\nexport function getColoredSymbol(state: StepState, colors: ColorScheme): string {\n  const symbol = getStateSymbol(state);\n  return colorize(symbol, colors[state]);\n}\n\n/**\n * Get colored text based on step state.\n */\nexport function colorByState(\n  text: string,\n  state: StepState,\n  colors: ColorScheme\n): string {\n  return colorize(text, colors[state]);\n}\n\n// =============================================================================\n// Strip ANSI\n// =============================================================================\n\n/**\n * Strip ANSI escape codes from a string.\n * Useful for calculating visible string length.\n */\nexport function stripAnsi(str: string): string {\n  // eslint-disable-next-line no-control-regex\n  return str.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n}\n\n/**\n * Get the visible length of a string (without ANSI codes).\n */\nexport function visibleLength(str: string): string {\n  return stripAnsi(str);\n}\n","/**\n * Mermaid Diagram Renderer\n *\n * Renders the workflow IR as a Mermaid flowchart diagram.\n * Supports sequential flows, parallel (subgraph), and race patterns.\n */\n\nimport { ok, err, type Result } from \"awaitly\";\nimport type {\n  FlowNode,\n  ParallelNode,\n  RaceNode,\n  DecisionNode,\n  StreamNode,\n  Renderer,\n  RenderOptions,\n  MermaidRenderOptions,\n  StepNode,\n  StepState,\n  WorkflowIR,\n  EnhancedRenderOptions,\n  HeatLevel,\n  WorkflowHooks,\n} from \"../types\";\nimport { isParallelNode, isRaceNode, isStepNode, isDecisionNode, isStreamNode } from \"../types\";\nimport { formatDuration } from \"../utils/timing\";\nimport { getHeatLevel } from \"../performance-analyzer\";\n\n/**\n * Error types for stringify operations.\n */\nexport type StringifyError = \"STRINGIFY_ERROR\";\n\n// =============================================================================\n// Mermaid Style Definitions\n// =============================================================================\n\n/**\n * Get Mermaid class definition for step states.\n * Colors inspired by AWS Step Functions and XState visualizers for professional appearance.\n */\nfunction getStyleDefinitions(): string[] {\n  return [\n    // Pending - light gray, subtle\n    \"    classDef pending fill:#f3f4f6,stroke:#9ca3af,stroke-width:2px,color:#374151\",\n    // Running - amber/yellow, indicates active execution\n    \"    classDef running fill:#fef3c7,stroke:#f59e0b,stroke-width:3px,color:#92400e\",\n    // Success - green, clear positive indicator\n    \"    classDef success fill:#d1fae5,stroke:#10b981,stroke-width:3px,color:#065f46\",\n    // Error - red, clear negative indicator\n    \"    classDef error fill:#fee2e2,stroke:#ef4444,stroke-width:3px,color:#991b1b\",\n    // Aborted - gray, indicates cancellation\n    \"    classDef aborted fill:#f3f4f6,stroke:#6b7280,stroke-width:2px,color:#4b5563,stroke-dasharray: 5 5\",\n    // Cached - blue, indicates cache hit\n    \"    classDef cached fill:#dbeafe,stroke:#3b82f6,stroke-width:3px,color:#1e40af\",\n    // Skipped - light gray with dashed border\n    \"    classDef skipped fill:#f9fafb,stroke:#d1d5db,stroke-width:2px,color:#6b7280,stroke-dasharray: 5 5\",\n    // Stream - purple/violet, indicates streaming operation\n    \"    classDef stream fill:#ede9fe,stroke:#8b5cf6,stroke-width:3px,color:#5b21b6\",\n    // Stream active - purple with animation indicator\n    \"    classDef streamActive fill:#ddd6fe,stroke:#7c3aed,stroke-width:3px,color:#4c1d95\",\n    // Stream error - purple-red for stream errors\n    \"    classDef streamError fill:#fce7f3,stroke:#db2777,stroke-width:3px,color:#9d174d\",\n  ];\n}\n\n/**\n * Get Mermaid class definitions for heatmap visualization.\n */\nfunction getHeatmapStyleDefinitions(): string[] {\n  return [\n    // Heatmap colors - cold to hot\n    \"    classDef heat_cold fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e40af\",\n    \"    classDef heat_cool fill:#ccfbf1,stroke:#14b8a6,stroke-width:2px,color:#0f766e\",\n    \"    classDef heat_neutral fill:#f3f4f6,stroke:#6b7280,stroke-width:2px,color:#374151\",\n    \"    classDef heat_warm fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#92400e\",\n    \"    classDef heat_hot fill:#fed7aa,stroke:#f97316,stroke-width:3px,color:#c2410c\",\n    \"    classDef heat_critical fill:#fecaca,stroke:#ef4444,stroke-width:3px,color:#b91c1c\",\n  ];\n}\n\n/**\n * Get the Mermaid class name for a heat level.\n */\nfunction getHeatClass(level: HeatLevel): string {\n  return `heat_${level}`;\n}\n\n/**\n * Get the Mermaid class name for a step state.\n */\nfunction getStateClass(state: StepState): string {\n  return state;\n}\n\n/**\n * Get Mermaid class definitions for hook visualization.\n */\nfunction getHookStyleDefinitions(): string[] {\n  return [\n    // Hook styles - gear icon aesthetic\n    \"    classDef hook_success fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e\",\n    \"    classDef hook_error fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#7f1d1d\",\n  ];\n}\n\n/**\n * Safely stringify a value, handling circular references and BigInt.\n * Returns Result with either the stringified value or an error.\n */\nfunction safeStringify(value: unknown): Result<string, StringifyError> {\n  try {\n    const replacer = (_key: string, v: unknown): unknown => {\n      if (typeof v !== \"bigint\") return v;\n      const n = Number(v);\n      return Number.isSafeInteger(n) ? n : v.toString();\n    };\n    return ok(JSON.stringify(value, replacer));\n  } catch {\n    return err(\"STRINGIFY_ERROR\");\n  }\n}\n\n/**\n * Get stringified value or fallback for unserializable values.\n */\nfunction getStringified(value: unknown): string {\n  const result = safeStringify(value);\n  return result.ok ? result.value : \"[unserializable]\";\n}\n\n/**\n * Render hooks as nodes before the workflow starts.\n * Returns the ID of the last hook node (to connect to workflow start).\n */\nfunction renderHooks(\n  hooks: WorkflowHooks,\n  lines: string[],\n  options: RenderOptions\n): { lastHookId: string | undefined } {\n  let lastHookId: string | undefined;\n\n  // Render shouldRun hook\n  if (hooks.shouldRun) {\n    const hookId = \"hook_shouldRun\";\n    const state = hooks.shouldRun.state === \"success\" ? \"hook_success\" : \"hook_error\";\n    const icon = hooks.shouldRun.state === \"success\" ? \"⚙\" : \"⚠\";\n    const timing = options.showTimings && hooks.shouldRun.durationMs !== undefined\n      ? ` ${formatDuration(hooks.shouldRun.durationMs)}`\n      : \"\";\n    const context = hooks.shouldRun.context?.skipped\n      ? \"\\\\nskipped workflow\"\n      : hooks.shouldRun.context?.result === true\n        ? \"\\\\nproceed\"\n        : \"\";\n\n    lines.push(`    ${hookId}[[\"${icon} shouldRun${context}${timing}\"]]:::${state}`);\n    lastHookId = hookId;\n  }\n\n  // Render onBeforeStart hook\n  if (hooks.onBeforeStart) {\n    const hookId = \"hook_beforeStart\";\n    const state = hooks.onBeforeStart.state === \"success\" ? \"hook_success\" : \"hook_error\";\n    const icon = hooks.onBeforeStart.state === \"success\" ? \"⚙\" : \"⚠\";\n    const timing = options.showTimings && hooks.onBeforeStart.durationMs !== undefined\n      ? ` ${formatDuration(hooks.onBeforeStart.durationMs)}`\n      : \"\";\n    const context = hooks.onBeforeStart.context?.skipped\n      ? \"\\\\nskipped workflow\"\n      : \"\";\n\n    lines.push(`    ${hookId}[[\"${icon} onBeforeStart${context}${timing}\"]]:::${state}`);\n\n    // Connect from previous hook if exists\n    if (lastHookId) {\n      lines.push(`    ${lastHookId} --> ${hookId}`);\n    }\n    lastHookId = hookId;\n  }\n\n  return { lastHookId };\n}\n\n// =============================================================================\n// Node ID Generation\n// =============================================================================\n\nlet nodeCounter = 0;\nconst usedDecisionIds = new Set<string>();\nconst usedStepIds = new Set<string>();\n\nfunction generateNodeId(prefix: string = \"node\"): string {\n  return `${prefix}_${++nodeCounter}`;\n}\n\nfunction resetNodeCounter(): void {\n  nodeCounter = 0;\n  usedDecisionIds.clear();\n  usedStepIds.clear();\n}\n\n// =============================================================================\n// Mermaid Text Escaping\n// =============================================================================\n\n/**\n * Escape text for use in Mermaid diagrams.\n * Only escapes characters that break quoted strings in Mermaid.\n *\n * With bracket-quote syntax (e.g., `nodeId[\"label\"]`), special characters\n * like {}[]() are allowed inside the quoted label.\n *\n * @param text - Text to escape\n * @returns Escaped text safe for Mermaid quoted labels\n */\nfunction escapeMermaidText(text: string): string {\n  return text\n    .replace(/\"/g, \"#quot;\")  // Escape double quotes for Mermaid\n    .replace(/</g, \"&lt;\")\n    .replace(/>/g, \"&gt;\")\n    .trim();\n}\n\n/**\n * Escape text for use in Mermaid subgraph names.\n * Subgraph names need special handling - brackets and braces must be removed.\n *\n * @param text - Text to escape for subgraph name\n * @returns Escaped text safe for subgraph names\n */\nfunction escapeSubgraphName(text: string): string {\n  return escapeMermaidText(text)\n    .replace(/[{}[\\]()]/g, \"\"); // Remove brackets, braces, and parentheses from subgraph names\n}\n\n// =============================================================================\n// Mermaid Renderer\n// =============================================================================\n\n/**\n * Create the Mermaid diagram renderer.\n */\nexport function mermaidRenderer(): Renderer {\n  return {\n    name: \"mermaid\",\n    supportsLive: false,\n\n    render(ir: WorkflowIR, options: RenderOptions): string {\n      resetNodeCounter();\n      const lines: string[] = [];\n\n      // Check for enhanced options (heatmap)\n      const enhanced = options as EnhancedRenderOptions;\n\n      // Diagram header\n      lines.push(\"flowchart TD\");\n\n      // Render hooks first (if any)\n      let hookExitId: string | undefined;\n      if (ir.hooks) {\n        const hookResult = renderHooks(ir.hooks, lines, options);\n        hookExitId = hookResult.lastHookId;\n      }\n\n      // Start node - more visually distinctive\n      const startId = \"start\";\n      lines.push(`    ${startId}((\"▶ Start\"))`);\n\n      // Connect hooks to start node\n      if (hookExitId) {\n        lines.push(`    ${hookExitId} --> ${startId}`);\n      }\n\n      // Track the last node for connections\n      let prevNodeId = startId;\n\n      // Render children (passing hooks for onAfterStep annotations)\n      for (const child of ir.root.children) {\n        const result = renderNode(child, options, lines, enhanced, ir.hooks);\n        lines.push(`    ${prevNodeId} --> ${result.entryId}`);\n        prevNodeId = result.exitId;\n      }\n\n      // End node (if workflow reached a terminal state) - more visually distinctive\n      const terminalStates = [\"success\", \"error\", \"aborted\"] as const;\n      if (terminalStates.includes(ir.root.state as (typeof terminalStates)[number])) {\n        const endId = \"finish\";\n        const endIcon =\n          ir.root.state === \"success\" ? \"✓\"\n            : ir.root.state === \"error\" ? \"✗\"\n              : \"⊘\";\n        const endLabel =\n          ir.root.state === \"success\" ? \"Done\"\n            : ir.root.state === \"error\" ? \"Failed\"\n              : \"Cancelled\";\n        const endShape = `((\"${endIcon} ${endLabel}\"))`;\n        const endClass =\n          ir.root.state === \"success\" ? \":::success\"\n            : ir.root.state === \"error\" ? \":::error\"\n              : \":::aborted\";\n        lines.push(`    ${endId}${endShape}${endClass}`);\n        lines.push(`    ${prevNodeId} --> ${endId}`);\n      }\n\n      // Add style definitions\n      lines.push(\"\");\n      lines.push(...getStyleDefinitions());\n\n      // Add heatmap styles if enabled\n      if (enhanced.showHeatmap) {\n        lines.push(...getHeatmapStyleDefinitions());\n      }\n\n      // Add hook styles if hooks were rendered\n      if (ir.hooks) {\n        lines.push(...getHookStyleDefinitions());\n      }\n\n      return lines.join(\"\\n\");\n    },\n  };\n}\n\n/**\n * Render result with entry and exit node IDs.\n */\ninterface RenderResult {\n  entryId: string;\n  exitId: string;\n}\n\n/**\n * Render a node and return its entry/exit IDs.\n */\nfunction renderNode(\n  node: FlowNode,\n  options: RenderOptions,\n  lines: string[],\n  enhanced?: EnhancedRenderOptions,\n  hooks?: WorkflowHooks\n): RenderResult {\n  if (isStepNode(node)) {\n    return renderStepNode(node, options, lines, enhanced, hooks);\n  } else if (isParallelNode(node)) {\n    return renderParallelNode(node, options, lines, enhanced, hooks);\n  } else if (isRaceNode(node)) {\n    return renderRaceNode(node, options, lines, enhanced, hooks);\n  } else if (isDecisionNode(node)) {\n    return renderDecisionNode(node, options, lines, enhanced, hooks);\n  } else if (isStreamNode(node)) {\n    return renderStreamNode(node, options, lines);\n  }\n\n  // Fallback for sequence or unknown nodes\n  const id = generateNodeId(\"unknown\");\n  lines.push(`    ${id}[\"Unknown Node\"]`);\n  return { entryId: id, exitId: id };\n}\n\n/**\n * Render a step node.\n */\nfunction renderStepNode(\n  node: StepNode,\n  options: RenderOptions,\n  lines: string[],\n  enhanced?: EnhancedRenderOptions,\n  hooks?: WorkflowHooks\n): RenderResult {\n  // Cast to MermaidRenderOptions to access extended options\n  const mermaidOpts = options as MermaidRenderOptions;\n  const showRetryEdges = mermaidOpts.showRetryEdges ?? true;\n  const showErrorEdges = mermaidOpts.showErrorEdges ?? true;\n  const showTimeoutEdges = mermaidOpts.showTimeoutEdges ?? true;\n\n  // Generate step ID, ensuring uniqueness even with duplicate keys\n  let id = node.key\n    ? `step_${node.key.replace(/[^a-zA-Z0-9]/g, \"_\")}`\n    : generateNodeId(\"step\");\n\n  // Ensure uniqueness by appending suffix if collision\n  if (usedStepIds.has(id)) {\n    let suffix = 2;\n    while (usedStepIds.has(`${id}_${suffix}`)) {\n      suffix++;\n    }\n    id = `${id}_${suffix}`;\n  }\n  usedStepIds.add(id);\n\n  const baseLabel = node.name ?? node.key ?? \"Step\";\n  const labelText = options.showKeys && node.key && node.name\n    ? `${baseLabel} [${node.key}]`\n    : baseLabel;\n  const label = escapeMermaidText(labelText);\n\n  // Format timing - use space instead of parentheses to avoid Mermaid parse errors\n  const timing =\n    options.showTimings && node.durationMs !== undefined\n      ? ` ${formatDuration(node.durationMs)}`\n      : \"\";\n\n  // Add visual indicators based on state (like XState/AWS Step Functions)\n  let stateIcon = \"\";\n  switch (node.state) {\n    case \"success\":\n      stateIcon = \"✓ \";\n      break;\n    case \"error\":\n      stateIcon = \"✗ \";\n      break;\n    case \"cached\":\n      stateIcon = \"💾 \";\n      break;\n    case \"running\":\n      stateIcon = \"⏳ \";\n      break;\n    case \"skipped\":\n      stateIcon = \"⊘ \";\n      break;\n  }\n\n  // Add input/output info if available\n  // Use newlines for multi-line labels, but escape special characters\n  let ioInfo = \"\";\n  if (node.input !== undefined) {\n    const inputStr = typeof node.input === \"string\"\n      ? escapeMermaidText(node.input)\n      : escapeMermaidText(getStringified(node.input).slice(0, 20));\n    ioInfo += `\\\\nin: ${inputStr}`;\n  }\n  if (node.output !== undefined && node.state === \"success\") {\n    const outputStr = typeof node.output === \"string\"\n      ? escapeMermaidText(node.output)\n      : escapeMermaidText(getStringified(node.output).slice(0, 20));\n    ioInfo += `\\\\nout: ${outputStr}`;\n  }\n\n  // Add onAfterStep hook info if present (check by key first, then by id)\n  let hookInfo = \"\";\n  const hookKey = node.key ?? node.id;\n  if (hooks && hookKey && hooks.onAfterStep.has(hookKey)) {\n    const hookExec = hooks.onAfterStep.get(hookKey)!;\n    const hookIcon = hookExec.state === \"success\" ? \"⚙\" : \"⚠\";\n    const hookTiming = options.showTimings && hookExec.durationMs !== undefined\n      ? ` ${formatDuration(hookExec.durationMs)}`\n      : \"\";\n    hookInfo = `\\\\n${hookIcon} hook${hookTiming}`;\n  }\n\n  // Combine all label parts with icon (retry/timeout info moved to edges)\n  const escapedLabel = (stateIcon + label + ioInfo + hookInfo + timing).trim();\n\n  // Determine class: use heatmap if enabled and data available, otherwise use state\n  // Lookup order matches PerformanceAnalyzer: key ?? name ?? id\n  let nodeClass: string;\n  const heat = enhanced?.showHeatmap && enhanced.heatmapData\n    ? enhanced.heatmapData.heat.get(node.key ?? \"\") ??\n      enhanced.heatmapData.heat.get(node.name ?? \"\") ??\n      enhanced.heatmapData.heat.get(node.id)\n    : undefined;\n\n  if (heat !== undefined) {\n    const level = getHeatLevel(heat);\n    nodeClass = getHeatClass(level);\n  } else {\n    nodeClass = getStateClass(node.state);\n  }\n\n  // Use different shapes based on state (like AWS Step Functions)\n  let shape: string;\n  switch (node.state) {\n    case \"error\":\n      // Hexagon for errors (more distinctive)\n      shape = `{{\"${escapedLabel}\"}}`;\n      break;\n    case \"cached\":\n      // Rounded rectangle with double border for cached\n      shape = `[(\"${escapedLabel}\")]`;\n      break;\n    case \"skipped\":\n      // Dashed border via class (applied once in lines.push below)\n      shape = `[\"${escapedLabel}\"]`;\n      break;\n    default:\n      // Standard rectangle for normal steps\n      shape = `[\"${escapedLabel}\"]`;\n  }\n\n  lines.push(`    ${id}${shape}:::${nodeClass}`);\n\n  // NEW: Add retry loop edge (self-loop showing retries)\n  if (showRetryEdges && node.retryCount !== undefined && node.retryCount > 0) {\n    const retryLabel = `↻ ${node.retryCount} retr${node.retryCount === 1 ? \"y\" : \"ies\"}`;\n    lines.push(`    ${id} -.->|\"${retryLabel}\"| ${id}`);\n  }\n\n  // NEW: Add error path edge (flow to error node)\n  if (showErrorEdges && node.state === \"error\" && node.error !== undefined) {\n    const errorNodeId = `ERR_${id}`;\n    const errorLabel = escapeMermaidText(String(node.error)).slice(0, 30);\n    lines.push(`    ${errorNodeId}{{\"${errorLabel}\"}}`);\n    lines.push(`    ${id} -->|error| ${errorNodeId}`);\n    lines.push(`    style ${errorNodeId} fill:#fee2e2,stroke:#dc2626`);\n  }\n\n  // NEW: Add timeout edge (alternative timeout path)\n  if (showTimeoutEdges && node.timedOut) {\n    const timeoutNodeId = `TO_${id}`;\n    const timeoutMs = node.timeoutMs !== undefined ? `${node.timeoutMs}ms` : \"\";\n    lines.push(`    ${timeoutNodeId}{{\"⏱ Timeout ${timeoutMs}\"}}`);\n    lines.push(`    ${id} -.->|timeout| ${timeoutNodeId}`);\n    lines.push(`    style ${timeoutNodeId} fill:#fef3c7,stroke:#f59e0b`);\n  }\n\n  return { entryId: id, exitId: id };\n}\n\n/**\n * Render a parallel node as a subgraph with fork/join.\n */\nfunction renderParallelNode(\n  node: ParallelNode,\n  options: RenderOptions,\n  lines: string[],\n  enhanced?: EnhancedRenderOptions,\n  hooks?: WorkflowHooks\n): RenderResult {\n  const subgraphId = generateNodeId(\"parallel\");\n  const forkId = `${subgraphId}_fork`;\n  const joinId = `${subgraphId}_join`;\n  const name = escapeSubgraphName(node.name ?? \"Parallel\");\n  const modeLabel = node.mode === \"allSettled\" ? \" (allSettled)\" : \"\";\n\n  // If no children, render as a simple step-like node with note\n  if (node.children.length === 0) {\n    const id = subgraphId;\n    const label = escapeMermaidText(`${name}${modeLabel}`);\n    const note = \"operations not individually tracked\";\n    const timing = options.showTimings && node.durationMs !== undefined\n      ? ` ${formatDuration(node.durationMs)}`\n      : \"\";\n\n    // Use a rounded rectangle to indicate it's a parallel operation\n    lines.push(`    ${id}[\"${label}${timing}\\\\n${note}\"]:::${getStateClass(node.state)}`);\n    return { entryId: id, exitId: id };\n  }\n\n  // Subgraph for parallel block with proper visual hierarchy\n  lines.push(`    subgraph ${subgraphId}[\"${name}${modeLabel}\"]`);\n  lines.push(`    direction TB`);\n\n  // Fork node (diamond) - more visually distinct\n  lines.push(`    ${forkId}{\"⚡ Fork\"}`);\n\n  // Child branches - render in parallel columns\n  const childExitIds: string[] = [];\n  for (const child of node.children) {\n    const result = renderNode(child, options, lines, enhanced, hooks);\n    lines.push(`    ${forkId} --> ${result.entryId}`);\n    childExitIds.push(result.exitId);\n  }\n\n  // Join node (diamond) - visually distinct\n  lines.push(`    ${joinId}{\"✓ Join\"}`);\n  for (const exitId of childExitIds) {\n    lines.push(`    ${exitId} --> ${joinId}`);\n  }\n\n  lines.push(`    end`);\n\n  // Apply state styling to subgraph\n  const stateClass = getStateClass(node.state);\n  lines.push(`    class ${subgraphId} ${stateClass}`);\n\n  return { entryId: forkId, exitId: joinId };\n}\n\n/**\n * Render a race node as a subgraph with racing indicator.\n */\nfunction renderRaceNode(\n  node: RaceNode,\n  options: RenderOptions,\n  lines: string[],\n  enhanced?: EnhancedRenderOptions,\n  hooks?: WorkflowHooks\n): RenderResult {\n  const subgraphId = generateNodeId(\"race\");\n  const startId = `${subgraphId}_start`;\n  const endId = `${subgraphId}_end`;\n  const name = escapeSubgraphName(node.name ?? \"Race\");\n\n  // If no children, render as a simple step-like node with note\n  if (node.children.length === 0) {\n    const id = subgraphId;\n    const label = escapeMermaidText(name);\n    const note = \"operations not individually tracked\";\n    const timing = options.showTimings && node.durationMs !== undefined\n      ? ` ${formatDuration(node.durationMs)}`\n      : \"\";\n\n    lines.push(`    ${id}[\"⚡ ${label}${timing}\\\\n${note}\"]:::${getStateClass(node.state)}`);\n    return { entryId: id, exitId: id };\n  }\n\n  // Subgraph for race block - escape name and emoji is safe in quoted strings\n  lines.push(`    subgraph ${subgraphId}[\"⚡ ${name}\"]`);\n  lines.push(`    direction TB`);\n\n  // Start node - use a more distinctive shape\n  lines.push(`    ${startId}((\"🏁 Start\"))`);\n\n  // Child branches\n  const childExitIds: Array<{ exitId: string; isWinner: boolean }> = [];\n  let winnerExitId: string | undefined;\n\n  for (const child of node.children) {\n    const result = renderNode(child, options, lines, enhanced, hooks);\n    const isWinner = node.winnerId === child.id;\n    lines.push(`    ${startId} --> ${result.entryId}`);\n\n    if (isWinner) {\n      winnerExitId = result.exitId;\n    }\n    childExitIds.push({ exitId: result.exitId, isWinner });\n  }\n\n  // End node - more distinctive\n  lines.push(`    ${endId}((\"✓ First\"))`);\n\n  // Connect winner with thick line, others with dashed (cancelled)\n  for (const { exitId, isWinner } of childExitIds) {\n    if (isWinner && winnerExitId) {\n      lines.push(`    ${exitId} ==>|🏆 Winner| ${endId}`);\n    } else if (node.winnerId) {\n      // Non-winner: show as cancelled\n      lines.push(`    ${exitId} -. cancelled .-> ${endId}`);\n    } else {\n      // No winner determined, normal connection\n      lines.push(`    ${exitId} --> ${endId}`);\n    }\n  }\n\n  lines.push(`    end`);\n\n  const stateClass = getStateClass(node.state);\n  lines.push(`    class ${subgraphId} ${stateClass}`);\n\n  return { entryId: startId, exitId: endId };\n}\n\n/**\n * Render a decision node as a diamond with branches.\n */\nfunction renderDecisionNode(\n  node: DecisionNode,\n  options: RenderOptions,\n  lines: string[],\n  enhanced?: EnhancedRenderOptions,\n  hooks?: WorkflowHooks\n): RenderResult {\n  // Generate decision ID, ensuring uniqueness even with duplicate keys\n  let decisionId = node.key\n    ? `decision_${node.key.replace(/[^a-zA-Z0-9]/g, \"_\")}`\n    : generateNodeId(\"decision\");\n\n  // Ensure uniqueness by appending suffix if collision\n  if (usedDecisionIds.has(decisionId)) {\n    let suffix = 2;\n    while (usedDecisionIds.has(`${decisionId}_${suffix}`)) {\n      suffix++;\n    }\n    decisionId = `${decisionId}_${suffix}`;\n  }\n  usedDecisionIds.add(decisionId);\n\n  // Escape condition and decision value - remove characters that break Mermaid\n  const condition = escapeMermaidText(node.condition ?? \"condition\");\n  const decisionValue = node.decisionValue !== undefined\n    ? ` = ${escapeMermaidText(String(node.decisionValue)).slice(0, 30)}`\n    : \"\";\n\n  // Decision diamond - ensure no invalid characters\n  const decisionLabel = `${condition}${decisionValue}`.trim();\n  lines.push(`    ${decisionId}{\"${decisionLabel}\"}`);\n\n  // Render branches\n  const branchExitIds: string[] = [];\n  let takenBranchExitId: string | undefined;\n  const usedBranchIds = new Set<string>();\n\n  for (const branch of node.branches) {\n    // Generate base branch ID from sanitized label\n    let branchId = `${decisionId}_${branch.label.replace(/[^a-zA-Z0-9]/g, \"_\")}`;\n    // Ensure uniqueness by appending index if collision\n    if (usedBranchIds.has(branchId)) {\n      let suffix = 2;\n      while (usedBranchIds.has(`${branchId}_${suffix}`)) {\n        suffix++;\n      }\n      branchId = `${branchId}_${suffix}`;\n    }\n    usedBranchIds.add(branchId);\n    // Escape branch label - remove parentheses and other special chars\n    const branchLabelText = escapeMermaidText(branch.label);\n    const branchLabel = branch.taken\n      ? `${branchLabelText} ✓`\n      : `${branchLabelText} skipped`;\n    const branchClass = branch.taken ? \":::success\" : \":::skipped\";\n\n    // Branch label node\n    lines.push(`    ${branchId}[\"${branchLabel}\"]${branchClass}`);\n\n    // Connect decision to branch\n    // Mermaid edge labels must be simple text - escape special characters\n    // Also remove pipe character as it's used for edge label syntax\n    const edgeLabel = branch.condition\n      ? `|${escapeMermaidText(branch.condition).replace(/\\|/g, \"\")}|`\n      : \"\";\n    lines.push(`    ${decisionId} -->${edgeLabel} ${branchId}`);\n\n    // Render children of this branch\n    if (branch.children.length > 0) {\n      let prevId = branchId;\n      for (const child of branch.children) {\n        const result = renderNode(child, options, lines, enhanced, hooks);\n        lines.push(`    ${prevId} --> ${result.entryId}`);\n        prevId = result.exitId;\n      }\n      branchExitIds.push(prevId);\n      if (branch.taken) {\n        takenBranchExitId = prevId;\n      }\n    } else {\n      branchExitIds.push(branchId);\n      if (branch.taken) {\n        takenBranchExitId = branchId;\n      }\n    }\n  }\n\n  // Join point (if we have a taken branch)\n  if (takenBranchExitId) {\n    return { entryId: decisionId, exitId: takenBranchExitId };\n  }\n\n  // If no branch was taken, return decision as exit\n  return { entryId: decisionId, exitId: decisionId };\n}\n\n/**\n * Render a stream node.\n * Uses hexagonal shape to distinguish from regular steps.\n */\nfunction renderStreamNode(\n  node: StreamNode,\n  options: RenderOptions,\n  lines: string[]\n): RenderResult {\n  const id = `stream_${node.namespace.replace(/[^a-zA-Z0-9]/g, \"_\")}_${generateNodeId(\"\")}`;\n\n  // Format counts\n  const counts = `W:${node.writeCount} R:${node.readCount}`;\n\n  // Add state icon\n  let stateIcon = \"\";\n  switch (node.streamState) {\n    case \"active\":\n      stateIcon = \"⟳ \";\n      break;\n    case \"closed\":\n      stateIcon = \"✓ \";\n      break;\n    case \"error\":\n      stateIcon = \"✗ \";\n      break;\n  }\n\n  // Format timing\n  const timing =\n    options.showTimings && node.durationMs !== undefined\n      ? ` ${formatDuration(node.durationMs)}`\n      : \"\";\n\n  // Backpressure indicator\n  const backpressure = node.backpressureOccurred ? \"\\\\nbackpressure\" : \"\";\n\n  // Combine label parts - use hexagon shape for streams\n  const label = `${stateIcon}stream:${escapeMermaidText(node.namespace)}\\\\n${counts}${backpressure}${timing}`;\n\n  // Determine class based on stream state\n  let nodeClass: string;\n  if (node.streamState === \"error\") {\n    nodeClass = \"streamError\";\n  } else if (node.streamState === \"active\") {\n    nodeClass = \"streamActive\";\n  } else {\n    nodeClass = \"stream\";\n  }\n\n  // Hexagonal shape for streams: {{\"label\"}}\n  lines.push(`    ${id}{{\"${label}\"}}:::${nodeClass}`);\n\n  return { entryId: id, exitId: id };\n}\n\nexport { mermaidRenderer as default };\n","/**\n * Performance Analyzer\n *\n * Analyzes workflow execution data to identify:\n * - Slow steps (bottlenecks)\n * - Retry patterns\n * - Error-prone steps\n * - Timing anomalies\n *\n * Aggregates metrics across multiple workflow runs to provide\n * statistical insights and heatmap visualization data.\n */\n\nimport type { WorkflowEvent } from \"awaitly\";\nimport type {\n  NodePerformance,\n  HeatmapData,\n  WorkflowIR,\n  FlowNode,\n  HeatLevel,\n} from \"./types\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * A recorded workflow run for analysis.\n */\nexport interface WorkflowRun {\n  /** Unique identifier for this run */\n  id: string;\n  /** Workflow start timestamp */\n  startTime: number;\n  /** All events from the workflow execution */\n  events: WorkflowEvent<unknown>[];\n}\n\n/**\n * Performance analyzer interface.\n */\nexport interface PerformanceAnalyzer {\n  /** Add a completed workflow run for analysis */\n  addRun: (run: WorkflowRun) => void;\n\n  /** Add events incrementally (alternative to addRun) */\n  addEvent: (event: WorkflowEvent<unknown>) => void;\n\n  /** Finalize current run (when using addEvent) */\n  finalizeRun: (runId: string) => void;\n\n  /** Get performance stats for a specific node */\n  getNodePerformance: (nodeId: string) => NodePerformance | undefined;\n\n  /** Get heatmap data for an IR */\n  getHeatmap: (\n    ir: WorkflowIR,\n    metric?: \"duration\" | \"retryRate\" | \"errorRate\"\n  ) => HeatmapData;\n\n  /** Get slowest nodes */\n  getSlowestNodes: (limit?: number) => NodePerformance[];\n\n  /** Get error-prone nodes */\n  getErrorProneNodes: (limit?: number) => NodePerformance[];\n\n  /** Get retry-prone nodes */\n  getRetryProneNodes: (limit?: number) => NodePerformance[];\n\n  /** Get all performance data */\n  getAllPerformance: () => Map<string, NodePerformance>;\n\n  /** Export performance data as JSON */\n  exportData: () => string;\n\n  /** Import performance data from JSON */\n  importData: (json: string) => void;\n\n  /** Clear all collected data */\n  clear: () => void;\n}\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Flatten all nodes from an IR tree.\n */\nfunction flattenNodes(nodes: FlowNode[]): FlowNode[] {\n  const result: FlowNode[] = [];\n  for (const node of nodes) {\n    result.push(node);\n    if (\"children\" in node && Array.isArray(node.children)) {\n      result.push(...flattenNodes(node.children));\n    }\n    if (\"branches\" in node) {\n      for (const branch of node.branches) {\n        result.push(...flattenNodes(branch.children));\n      }\n    }\n  }\n  return result;\n}\n\n/**\n * Calculate percentile value from sorted array.\n */\nfunction percentile(sortedValues: number[], p: number): number {\n  if (sortedValues.length === 0) return 0;\n  const index = Math.floor(sortedValues.length * p);\n  return sortedValues[Math.min(index, sortedValues.length - 1)];\n}\n\n/**\n * Get heat level from normalized value (0-1).\n */\nexport function getHeatLevel(heat: number): HeatLevel {\n  if (heat < 0.2) return \"cold\";\n  if (heat < 0.4) return \"cool\";\n  if (heat < 0.6) return \"neutral\";\n  if (heat < 0.8) return \"warm\";\n  if (heat < 0.95) return \"hot\";\n  return \"critical\";\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/**\n * Create a performance analyzer for workflow metrics.\n *\n * @example\n * ```typescript\n * const analyzer = createPerformanceAnalyzer();\n *\n * // Add completed runs\n * analyzer.addRun({ id: 'run-1', startTime: Date.now(), events });\n *\n * // Get insights\n * const slowest = analyzer.getSlowestNodes(5);\n * const heatmap = analyzer.getHeatmap(ir, 'duration');\n * ```\n */\nexport function createPerformanceAnalyzer(): PerformanceAnalyzer {\n  // Timing data: nodeId → array of durations (ms)\n  const timingData = new Map<string, number[]>();\n\n  // Retry data: nodeId → { retried runs, total runs }\n  const retryData = new Map<string, { retried: number; total: number }>();\n\n  // Error data: nodeId → { error runs, total runs }\n  const errorData = new Map<string, { errors: number; total: number }>();\n\n  // Timeout data: nodeId → { timed out, total }\n  const timeoutData = new Map<string, { timedOut: number; total: number }>();\n\n  // Current run state (for incremental event adding)\n  let currentRunEvents: WorkflowEvent<unknown>[] = [];\n\n  /**\n   * Get node ID from event (for grouping and metrics).\n   * Prioritizes stepKey (cache/instance identity), then stepId (step label), then name\n   * so distinct steps (e.g. same id in a loop, different key) are not merged.\n   * With the current awaitly API, stepId is always set; stepKey is set when key option is used.\n   */\n  function getNodeId(event: {\n    stepId?: string;\n    stepKey?: string;\n    name?: string;\n  }): string {\n    return event.stepKey ?? event.stepId ?? event.name ?? \"unknown\";\n  }\n\n  /**\n   * Process events from a workflow run.\n   */\n  function processEvents(events: WorkflowEvent<unknown>[]): void {\n    // Track step state during processing\n    const stepState = new Map<\n      string,\n      {\n        retried: boolean;\n        timedOut: boolean;\n      }\n    >();\n\n    for (const event of events) {\n      switch (event.type) {\n        case \"step_start\": {\n          const id = getNodeId(event);\n          stepState.set(id, { retried: false, timedOut: false });\n          break;\n        }\n\n        case \"step_retry\": {\n          const id = getNodeId(event);\n          const state = stepState.get(id);\n          if (state) {\n            state.retried = true;\n          }\n          break;\n        }\n\n        case \"step_timeout\": {\n          const id = getNodeId(event);\n          const state = stepState.get(id);\n          if (state) {\n            state.timedOut = true;\n          }\n          // Don't update counts here - wait for step completion\n          // to get accurate total count across all runs\n          break;\n        }\n\n        case \"step_success\": {\n          const id = getNodeId(event);\n          const state = stepState.get(id);\n\n          // Record timing\n          const timings = timingData.get(id) ?? [];\n          timings.push(event.durationMs);\n          timingData.set(id, timings);\n\n          // Record retry status\n          const retry = retryData.get(id) ?? { retried: 0, total: 0 };\n          retry.total++;\n          if (state?.retried) retry.retried++;\n          retryData.set(id, retry);\n\n          // Record timeout status\n          const timeout = timeoutData.get(id) ?? { timedOut: 0, total: 0 };\n          timeout.total++;\n          if (state?.timedOut) timeout.timedOut++;\n          timeoutData.set(id, timeout);\n\n          // Record success (no error)\n          const error = errorData.get(id) ?? { errors: 0, total: 0 };\n          error.total++;\n          errorData.set(id, error);\n\n          stepState.delete(id);\n          break;\n        }\n\n        case \"step_error\": {\n          const id = getNodeId(event);\n          const state = stepState.get(id);\n\n          // Record timing\n          const timings = timingData.get(id) ?? [];\n          timings.push(event.durationMs);\n          timingData.set(id, timings);\n\n          // Record retry status\n          const retry = retryData.get(id) ?? { retried: 0, total: 0 };\n          retry.total++;\n          if (state?.retried) retry.retried++;\n          retryData.set(id, retry);\n\n          // Record timeout status\n          const timeout = timeoutData.get(id) ?? { timedOut: 0, total: 0 };\n          timeout.total++;\n          if (state?.timedOut) timeout.timedOut++;\n          timeoutData.set(id, timeout);\n\n          // Record error\n          const error = errorData.get(id) ?? { errors: 0, total: 0 };\n          error.total++;\n          error.errors++;\n          errorData.set(id, error);\n\n          stepState.delete(id);\n          break;\n        }\n      }\n    }\n  }\n\n  /**\n   * Add a completed workflow run.\n   */\n  function addRun(run: WorkflowRun): void {\n    processEvents(run.events);\n  }\n\n  /**\n   * Add an event incrementally.\n   */\n  function addEvent(event: WorkflowEvent<unknown>): void {\n    currentRunEvents.push(event);\n  }\n\n  /**\n   * Finalize current run (process accumulated events).\n   */\n  function finalizeRun(_runId: string): void {\n    if (currentRunEvents.length > 0) {\n      processEvents(currentRunEvents);\n      currentRunEvents = [];\n    }\n  }\n\n  /**\n   * Compute performance metrics for a node.\n   */\n  function computePerformance(nodeId: string): NodePerformance | undefined {\n    const timings = timingData.get(nodeId);\n    if (!timings || timings.length === 0) return undefined;\n\n    const sorted = [...timings].sort((a, b) => a - b);\n    const sum = sorted.reduce((a, b) => a + b, 0);\n    const mean = sum / sorted.length;\n    const variance =\n      sorted.reduce((acc, t) => acc + (t - mean) ** 2, 0) / sorted.length;\n\n    const retry = retryData.get(nodeId) ?? { retried: 0, total: 1 };\n    const error = errorData.get(nodeId) ?? { errors: 0, total: 1 };\n    const timeout = timeoutData.get(nodeId) ?? { timedOut: 0, total: 1 };\n\n    return {\n      nodeId,\n      avgDurationMs: mean,\n      minDurationMs: sorted[0],\n      maxDurationMs: sorted[sorted.length - 1],\n      stdDevMs: Math.sqrt(variance),\n      samples: sorted.length,\n      retryRate: retry.total > 0 ? retry.retried / retry.total : 0,\n      timeoutRate: timeout.total > 0 ? timeout.timedOut / timeout.total : 0,\n      errorRate: error.total > 0 ? error.errors / error.total : 0,\n      percentiles: {\n        p50: percentile(sorted, 0.5),\n        p90: percentile(sorted, 0.9),\n        p95: percentile(sorted, 0.95),\n        p99: percentile(sorted, 0.99),\n      },\n    };\n  }\n\n  /**\n   * Get performance stats for a specific node.\n   */\n  function getNodePerformance(nodeId: string): NodePerformance | undefined {\n    return computePerformance(nodeId);\n  }\n\n  /**\n   * Get heatmap data for an IR.\n   */\n  function getHeatmap(\n    ir: WorkflowIR,\n    metric: \"duration\" | \"retryRate\" | \"errorRate\" = \"duration\"\n  ): HeatmapData {\n    const heat = new Map<string, number>();\n    const allNodes = flattenNodes(ir.root.children);\n\n    // Compute values for all nodes\n    const values: Array<{ id: string; value: number }> = [];\n    for (const node of allNodes) {\n      // Use same lookup order as getNodeId: stepKey ?? stepId ?? name (IR has key, id, name)\n      const lookupKey =\n        (\"key\" in node ? node.key : undefined) ?? node.id ?? node.name;\n      const perf = computePerformance(lookupKey);\n      if (perf) {\n        let value: number;\n        switch (metric) {\n          case \"duration\":\n            value = perf.avgDurationMs;\n            break;\n          case \"retryRate\":\n            value = perf.retryRate;\n            break;\n          case \"errorRate\":\n            value = perf.errorRate;\n            break;\n        }\n        values.push({ id: node.id, value });\n      }\n    }\n\n    if (values.length === 0) {\n      return {\n        heat,\n        metric,\n        stats: { min: 0, max: 0, mean: 0, threshold: 0 },\n      };\n    }\n\n    // Compute statistics\n    const vals = values.map((v) => v.value);\n    const min = Math.min(...vals);\n    const max = Math.max(...vals);\n    const mean = vals.reduce((a, b) => a + b, 0) / vals.length;\n    const range = max - min || 1;\n\n    // Normalize to 0-1 heat values\n    for (const { id, value } of values) {\n      heat.set(id, (value - min) / range);\n    }\n\n    return {\n      heat,\n      metric,\n      stats: {\n        min,\n        max,\n        mean,\n        threshold: mean + (max - mean) * 0.5, // 50% above mean is \"hot\"\n      },\n    };\n  }\n\n  /**\n   * Get all performance data.\n   */\n  function getAllPerformance(): Map<string, NodePerformance> {\n    const result = new Map<string, NodePerformance>();\n    for (const nodeId of timingData.keys()) {\n      const perf = computePerformance(nodeId);\n      if (perf) result.set(nodeId, perf);\n    }\n    return result;\n  }\n\n  /**\n   * Get slowest nodes by average duration.\n   */\n  function getSlowestNodes(limit = 10): NodePerformance[] {\n    const all = getAllPerformance();\n    return [...all.values()]\n      .sort((a, b) => b.avgDurationMs - a.avgDurationMs)\n      .slice(0, limit);\n  }\n\n  /**\n   * Get error-prone nodes by error rate.\n   */\n  function getErrorProneNodes(limit = 10): NodePerformance[] {\n    const all = getAllPerformance();\n    return [...all.values()]\n      .filter((p) => p.errorRate > 0)\n      .sort((a, b) => b.errorRate - a.errorRate)\n      .slice(0, limit);\n  }\n\n  /**\n   * Get retry-prone nodes by retry rate.\n   */\n  function getRetryProneNodes(limit = 10): NodePerformance[] {\n    const all = getAllPerformance();\n    return [...all.values()]\n      .filter((p) => p.retryRate > 0)\n      .sort((a, b) => b.retryRate - a.retryRate)\n      .slice(0, limit);\n  }\n\n  /**\n   * Export performance data as JSON.\n   */\n  function exportData(): string {\n    return JSON.stringify({\n      timingData: Object.fromEntries(timingData),\n      retryData: Object.fromEntries(retryData),\n      errorData: Object.fromEntries(errorData),\n      timeoutData: Object.fromEntries(timeoutData),\n    });\n  }\n\n  /**\n   * Import performance data from JSON.\n   */\n  function importData(json: string): void {\n    const data = JSON.parse(json) as {\n      timingData?: Record<string, number[]>;\n      retryData?: Record<string, { retried: number; total: number }>;\n      errorData?: Record<string, { errors: number; total: number }>;\n      timeoutData?: Record<string, { timedOut: number; total: number }>;\n    };\n\n    // Clear existing data\n    timingData.clear();\n    retryData.clear();\n    errorData.clear();\n    timeoutData.clear();\n\n    // Import timing data\n    for (const [k, v] of Object.entries(data.timingData ?? {})) {\n      timingData.set(k, v);\n    }\n\n    // Import retry data\n    for (const [k, v] of Object.entries(data.retryData ?? {})) {\n      retryData.set(k, v);\n    }\n\n    // Import error data\n    for (const [k, v] of Object.entries(data.errorData ?? {})) {\n      errorData.set(k, v);\n    }\n\n    // Import timeout data\n    for (const [k, v] of Object.entries(data.timeoutData ?? {})) {\n      timeoutData.set(k, v);\n    }\n  }\n\n  /**\n   * Clear all collected data.\n   */\n  function clear(): void {\n    timingData.clear();\n    retryData.clear();\n    errorData.clear();\n    timeoutData.clear();\n    currentRunEvents = [];\n  }\n\n  return {\n    addRun,\n    addEvent,\n    finalizeRun,\n    getNodePerformance,\n    getHeatmap,\n    getSlowestNodes,\n    getErrorProneNodes,\n    getRetryProneNodes,\n    getAllPerformance,\n    exportData,\n    importData,\n    clear,\n  };\n}\n","/**\n * Kroki Encoder\n *\n * Encodes Mermaid diagram text for Kroki URLs using pako deflate + base64url.\n * Uses Buffer in Node (btoa/atob not available) and btoa/atob in browsers.\n */\n\nimport pako from \"pako\";\n\n/** True when Buffer is available (Node). */\nconst hasBuffer = typeof globalThis !== \"undefined\" && \"Buffer\" in globalThis && typeof (globalThis as { Buffer?: unknown }).Buffer === \"function\";\n\n/**\n * Base64URL encode bytes (URL-safe base64).\n * Uses `-` and `_` instead of `+` and `/`, and omits padding.\n * Node-safe: uses Buffer when available, otherwise btoa.\n */\nfunction base64UrlEncode(bytes: Uint8Array): string {\n  let base64: string;\n  if (hasBuffer) {\n    const B = (globalThis as unknown as { Buffer: { from: (u: Uint8Array) => { toString: (enc: string) => string } } }).Buffer;\n    base64 = B.from(bytes).toString(\"base64\");\n  } else {\n    let binary = \"\";\n    for (let i = 0; i < bytes.length; i++) {\n      binary += String.fromCharCode(bytes[i]);\n    }\n    base64 = btoa(binary);\n  }\n  return base64\n    .replace(/\\+/g, \"-\")\n    .replace(/\\//g, \"_\")\n    .replace(/=+$/, \"\"); // Remove padding\n}\n\n/**\n * Decode standard base64 to bytes.\n * Node-safe: uses Buffer when available, otherwise atob.\n */\nfunction base64ToBytes(base64: string): Uint8Array {\n  if (hasBuffer) {\n    const B = (globalThis as unknown as { Buffer: { from: (s: string, enc: string) => Uint8Array } }).Buffer;\n    return B.from(base64, \"base64\");\n  }\n  const binary = atob(base64);\n  const bytes = new Uint8Array(binary.length);\n  for (let i = 0; i < binary.length; i++) {\n    bytes[i] = binary.charCodeAt(i);\n  }\n  return bytes;\n}\n\n/**\n * Encode text for Kroki URL.\n * Uses pako deflate compression + base64url encoding.\n *\n * @param text - The text to encode (e.g., Mermaid diagram)\n * @returns URL-safe encoded string\n *\n * @example\n * ```typescript\n * const encoded = encodeForKroki('flowchart TD\\n  A-->B');\n * // => \"eNpLzs8tyc9NTgQADsMDmA\"\n * ```\n */\nexport function encodeForKroki(text: string): string {\n  // Convert string to UTF-8 bytes\n  const textEncoder = new TextEncoder();\n  const textBytes = textEncoder.encode(text);\n\n  // Compress with deflate\n  const compressed = pako.deflate(textBytes);\n\n  // Base64URL encode\n  return base64UrlEncode(compressed);\n}\n\n/**\n * Decode Kroki URL payload back to text.\n * Uses base64url decoding + pako inflate.\n *\n * @param encoded - The encoded string from a Kroki URL\n * @returns The original text\n *\n * @example\n * ```typescript\n * const text = decodeFromKroki('eNpLzs8tyc9NTgQADsMDmA');\n * // => \"flowchart TD\\n  A-->B\"\n * ```\n */\nexport function decodeFromKroki(encoded: string): string {\n  // Convert URL-safe base64 to standard base64\n  let base64 = encoded.replace(/-/g, \"+\").replace(/_/g, \"/\");\n\n  // Add padding if needed\n  const padding = 4 - (base64.length % 4);\n  if (padding !== 4) {\n    base64 += \"=\".repeat(padding);\n  }\n\n  const bytes = base64ToBytes(base64);\n  const decompressed = pako.inflate(bytes);\n  const textDecoder = new TextDecoder();\n  return textDecoder.decode(decompressed);\n}\n","/**\n * Kroki URL Generation\n *\n * Generates shareable URLs for Kroki diagram rendering service.\n * Works in both browser and Node.js environments.\n */\n\nimport type { WorkflowIR, RenderOptions, KrokiExportOptions } from \"../types\";\nimport { mermaidRenderer, defaultColorScheme } from \"../renderers\";\nimport { encodeForKroki } from \"./encoder\";\n\n/**\n * Supported Kroki output formats.\n */\nexport type KrokiFormat = \"svg\" | \"png\" | \"pdf\" | \"jpeg\";\n\n/**\n * Options for URL generator.\n */\nexport interface UrlGeneratorOptions {\n  /** Base URL for Kroki service (default: https://kroki.io) */\n  baseUrl?: string;\n}\n\n/**\n * Default Kroki base URL.\n */\nconst DEFAULT_KROKI_URL = \"https://kroki.io\";\n\n/**\n * Build a Kroki URL for the given diagram.\n *\n * @param diagramType - Diagram type (e.g., \"mermaid\", \"plantuml\", \"graphviz\")\n * @param format - Output format (svg, png, pdf, jpeg)\n * @param text - The diagram text\n * @param options - URL generator options (KrokiExportOptions or legacy UrlGeneratorOptions)\n * @returns The Kroki URL\n *\n * @example\n * ```typescript\n * const url = buildKrokiUrl('mermaid', 'svg', 'flowchart TD\\n  A-->B');\n * // => \"https://kroki.io/mermaid/svg/eNpLzs8tyc9NTgQADsMDmA\"\n *\n * // With explicit KrokiExportOptions\n * const url2 = buildKrokiUrl('mermaid', 'svg', 'flowchart TD\\n  A-->B', {\n *   provider: 'kroki',\n *   baseUrl: 'https://kroki.internal'\n * });\n * ```\n */\nexport function buildKrokiUrl(\n  diagramType: string,\n  format: KrokiFormat,\n  text: string,\n  options: KrokiExportOptions | UrlGeneratorOptions = {}\n): string {\n  const baseUrl = options.baseUrl ?? DEFAULT_KROKI_URL;\n  const encoded = encodeForKroki(text);\n  return `${baseUrl}/${diagramType}/${format}/${encoded}`;\n}\n\n/**\n * Generate a Kroki URL from workflow IR.\n *\n * @param ir - Workflow intermediate representation\n * @param format - Output format (default: 'svg')\n * @param options - Optional URL generator options\n * @returns The Kroki URL\n *\n * @example\n * ```typescript\n * const url = toKrokiUrl(workflowIR, 'svg');\n * // Share this URL - image renders when viewed\n * ```\n */\nexport function toKrokiUrl(\n  ir: WorkflowIR,\n  format: KrokiFormat = \"svg\",\n  options: UrlGeneratorOptions = {}\n): string {\n  const renderer = mermaidRenderer();\n  const renderOptions: RenderOptions = {\n    showTimings: true,\n    showKeys: false,\n    terminalWidth: 80,\n    colors: defaultColorScheme,\n  };\n\n  const mermaidText = renderer.render(ir, renderOptions);\n  return buildKrokiUrl(\"mermaid\", format, mermaidText, options);\n}\n\n/**\n * Generate a Kroki SVG URL from workflow IR.\n *\n * @param ir - Workflow intermediate representation\n * @param options - Optional URL generator options\n * @returns The Kroki SVG URL\n *\n * @example\n * ```typescript\n * const svgUrl = toKrokiSvgUrl(workflowIR);\n * // => \"https://kroki.io/mermaid/svg/eNp...\"\n * ```\n */\nexport function toKrokiSvgUrl(\n  ir: WorkflowIR,\n  options: UrlGeneratorOptions = {}\n): string {\n  return toKrokiUrl(ir, \"svg\", options);\n}\n\n/**\n * Generate a Kroki PNG URL from workflow IR.\n *\n * @param ir - Workflow intermediate representation\n * @param options - Optional URL generator options\n * @returns The Kroki PNG URL\n *\n * @example\n * ```typescript\n * const pngUrl = toKrokiPngUrl(workflowIR);\n * // => \"https://kroki.io/mermaid/png/eNp...\"\n * ```\n */\nexport function toKrokiPngUrl(\n  ir: WorkflowIR,\n  options: UrlGeneratorOptions = {}\n): string {\n  return toKrokiUrl(ir, \"png\", options);\n}\n\n/**\n * URL Generator with configured base URL.\n */\nexport interface UrlGenerator {\n  /** Generate URL with specified format */\n  toUrl(ir: WorkflowIR, format: KrokiFormat): string;\n  /** Generate SVG URL */\n  toSvgUrl(ir: WorkflowIR): string;\n  /** Generate PNG URL */\n  toPngUrl(ir: WorkflowIR): string;\n  /** Generate PDF URL */\n  toPdfUrl(ir: WorkflowIR): string;\n  /** Get the configured base URL */\n  getBaseUrl(): string;\n}\n\n/**\n * Create a URL generator with a custom base URL.\n * Useful for self-hosted Kroki instances.\n *\n * @param options - URL generator options\n * @returns A URL generator instance\n *\n * @example\n * ```typescript\n * // Use self-hosted Kroki\n * const generator = createUrlGenerator({ baseUrl: 'https://my-kroki.internal' });\n * const url = generator.toSvgUrl(workflowIR);\n *\n * // Default public Kroki\n * const defaultGenerator = createUrlGenerator();\n * const publicUrl = defaultGenerator.toSvgUrl(workflowIR);\n * ```\n */\nexport function createUrlGenerator(options: UrlGeneratorOptions = {}): UrlGenerator {\n  const baseUrl = options.baseUrl ?? DEFAULT_KROKI_URL;\n\n  return {\n    toUrl(ir: WorkflowIR, format: KrokiFormat): string {\n      return toKrokiUrl(ir, format, { baseUrl });\n    },\n\n    toSvgUrl(ir: WorkflowIR): string {\n      return toKrokiUrl(ir, \"svg\", { baseUrl });\n    },\n\n    toPngUrl(ir: WorkflowIR): string {\n      return toKrokiUrl(ir, \"png\", { baseUrl });\n    },\n\n    toPdfUrl(ir: WorkflowIR): string {\n      return toKrokiUrl(ir, \"pdf\", { baseUrl });\n    },\n\n    getBaseUrl(): string {\n      return baseUrl;\n    },\n  };\n}\n"],"mappings":"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","names":["fetch_exports","__export","fetchKrokiPdf","fetchKrokiPng","fetchKrokiSvg","__toCommonJS","import_awaitly","import_awaitly","isStepNode","node","isParallelNode","node","isRaceNode","isDecisionNode","isStreamNode","formatDuration","ms","minutes","seconds","DIM","FG_RED","FG_GREEN","FG_YELLOW","FG_BLUE","FG_GRAY","FG_WHITE","defaultColorScheme","FG_WHITE","FG_YELLOW","FG_GREEN","FG_RED","FG_GRAY","FG_BLUE","DIM","import_awaitly","getHeatLevel","heat","getStyleDefinitions","getHeatmapStyleDefinitions","getHeatClass","level","getHookStyleDefinitions","safeStringify","value","_key","v","n","getStringified","result","renderHooks","hooks","lines","options","lastHookId","hookId","state","icon","timing","formatDuration","context","nodeCounter","usedDecisionIds","usedStepIds","generateNodeId","prefix","resetNodeCounter","escapeMermaidText","text","escapeSubgraphName","mermaidRenderer","ir","enhanced","hookExitId","startId","prevNodeId","child","renderNode","endId","endIcon","endLabel","endShape","endClass","getStyleDefinitions","getHeatmapStyleDefinitions","node","isStepNode","renderStepNode","isParallelNode","renderParallelNode","isRaceNode","renderRaceNode","isDecisionNode","renderDecisionNode","isStreamNode","renderStreamNode","id","mermaidOpts","showRetryEdges","showErrorEdges","showTimeoutEdges","suffix","baseLabel","labelText","label","stateIcon","ioInfo","inputStr","outputStr","hookInfo","hookKey","hookExec","hookIcon","hookTiming","escapedLabel","nodeClass","heat","level","getHeatLevel","getHeatClass","shape","retryLabel","errorNodeId","errorLabel","timeoutNodeId","timeoutMs","subgraphId","forkId","joinId","name","modeLabel","note","childExitIds","exitId","stateClass","winnerExitId","isWinner","decisionId","condition","decisionValue","decisionLabel","branchExitIds","takenBranchExitId","usedBranchIds","branch","branchId","branchLabelText","branchLabel","branchClass","edgeLabel","prevId","counts","backpressure","import_pako","hasBuffer","base64UrlEncode","bytes","base64","binary","i","encodeForKroki","text","textBytes","compressed","pako","base64UrlEncode","DEFAULT_KROKI_URL","buildKrokiUrl","diagramType","format","text","options","baseUrl","encoded","encodeForKroki","toKrokiUrl","ir","renderer","mermaidRenderer","renderOptions","defaultColorScheme","mermaidText","toKrokiSvgUrl","toKrokiPngUrl","fetchKrokiSvg","ir","options","generator","timeout","urlOptions","url","toKrokiSvgUrl","controller","timeoutId","response","text","error","fetchKrokiPng","toKrokiPngUrl","arrayBuffer","fetchKrokiPdf","pdfUrl"]}