/** * LLM Council Tool — Ask multiple models, synthesize with a chairman. * * Spawns member models in parallel, then a chairman model * that synthesizes the answers into a final response. * Progress streams inline via renderResult. * * Rendering follows styled-outputs visual vocabulary: * ✓/✗ prefix, └─ branch lines, · indent, expand hints. * All labels, colors, and symbols are configurable via * ~/.pi/agent/configs/llm-council.json */ import * as path from 'node:path'; import type { ExtensionAPI, ExtensionContext, Theme } from '@earendil-works/pi-coding-agent'; import { BorderedLoader, buildSessionContext, convertToLlm, getMarkdownTheme, serializeConversation, } from '@earendil-works/pi-coding-agent'; import { Container, Markdown, Text } from '@earendil-works/pi-tui'; import { createSubagentRuntime, resolveContainedAgentResource, type SpawnUsage, type SubagentRuntime, } from '@nicknisi/pi-shared'; import { Type } from 'typebox'; import { CONFIG, councilConfigPath, loadCouncil, saveCouncilDefaults, type ResolvedCouncil } from './config.js'; import { CouncilPicker, type PickerResult } from './picker.js'; import { applySelection, resolveOverrides, restoreSelection, selectionFromCouncil, SELECTION_ENTRY, updateCouncilStatus, } from './selection.js'; import { applyColor, formatElapsed, getExpandToggleKey, getVisibleWidth } from './utils.js'; // ── Derived constants (computed once from CONFIG) ──────────────────────── const SPINNER_FRAMES = [...CONFIG.shared.spinner.prefixChars, ...[...CONFIG.shared.spinner.prefixChars].reverse()]; const INDENT_WIDTH = getVisibleWidth(CONFIG.shared.branch.prefix) + 1; // ── Styling helpers ────────────────────────────────────────────────────── function successPrefix(theme: Theme): string { return `${applyColor(theme, CONFIG.shared.successPrefix.color, CONFIG.shared.successPrefix.prefix)} `; } function errorPrefix(theme: Theme): string { return `${applyColor(theme, CONFIG.shared.errorPrefix.color, CONFIG.shared.errorPrefix.prefix)} `; } function branchLine(text: string, theme: Theme): string { return `${applyColor(theme, CONFIG.shared.branch.color, CONFIG.shared.branch.prefix)} ${text}`; } function indentLine(text: string): string { return `${' '.repeat(INDENT_WIDTH)}${text}`; } function expandHint(theme: Theme): string { return applyColor(theme, CONFIG.shared.expandHint.color, ` • ${getExpandToggleKey()} to expand`); } function toolHeader(label: string, summary: string, theme: Theme, dot?: string, isError?: boolean): string { const d = dot ?? (isError ? errorPrefix(theme) : successPrefix(theme)); const title = applyColor(theme, CONFIG.shared.toolHeader.titleColor, theme.bold(label)); return `${d}${title} ${summary}`; } /** Status icon: ✓ / ✗ / spinner frame / waiting glyph. */ function statusIcon(status: MemberResult['status'], theme: Theme, spinnerFrame: number): string { switch (status) { case 'done': return applyColor(theme, CONFIG.shared.status.doneColor, CONFIG.shared.successPrefix.prefix); case 'error': return applyColor(theme, CONFIG.shared.status.errorColor, CONFIG.shared.errorPrefix.prefix); case 'working': return applyColor(theme, CONFIG.shared.spinner.color, SPINNER_FRAMES[spinnerFrame % SPINNER_FRAMES.length]!); default: return applyColor(theme, CONFIG.shared.status.waitingIconColor, CONFIG.shared.status.waitingIcon); } } /** `