privateboard 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/cli.js +10502 -0
- package/dist/cli.js.map +1 -0
- package/package.json +63 -0
- package/public/adjourn-overlay.css +253 -0
- package/public/agent-overlay.css +444 -0
- package/public/agent-overlay.js +604 -0
- package/public/agent-profile.css +3230 -0
- package/public/agent-profile.js +3329 -0
- package/public/app.js +6629 -0
- package/public/auto-hide-scroll.js +90 -0
- package/public/avatar-skill.js +793 -0
- package/public/avatars/chair.svg +98 -0
- package/public/avatars/first-principles.svg +122 -0
- package/public/avatars/long-horizon.svg +147 -0
- package/public/avatars/open_ai.png +0 -0
- package/public/avatars/phenomenologist.svg +130 -0
- package/public/avatars/socrates.svg +187 -0
- package/public/avatars/user-empathy.svg +117 -0
- package/public/avatars/value-investor.svg +117 -0
- package/public/favicon.svg +10 -0
- package/public/fonts/agent-Italic.woff2 +0 -0
- package/public/fonts/human-sans.woff2 +0 -0
- package/public/icons.css +103 -0
- package/public/models-cache.js +57 -0
- package/public/new-agent.css +1359 -0
- package/public/new-agent.js +675 -0
- package/public/onboarding.css +628 -0
- package/public/onboarding.js +782 -0
- package/public/prototype-dashboard.html +7596 -0
- package/public/report/spines/a16z-thesis.css +1055 -0
- package/public/report/spines/anthropic-essay.css +556 -0
- package/public/report/spines/boardroom-dark.css +1082 -0
- package/public/report/spines/gartner-note.css +538 -0
- package/public/report/spines/mckinsey-deck.css +523 -0
- package/public/report/spines/openai-paper.css +516 -0
- package/public/report.html +1417 -0
- package/public/room-settings.css +895 -0
- package/public/room-settings.js +1039 -0
- package/public/themes.css +338 -0
- package/public/user-settings.css +1236 -0
- package/public/user-settings.js +1291 -0
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/storage/db.ts","../src/utils/paths.ts","../src/storage/migrations/001_init.sql","../src/storage/migrations/002_default_opus.sql","../src/storage/migrations/003_paused_at.sql","../src/storage/migrations/004_room_intensity.sql","../src/storage/migrations/005_chair.sql","../src/storage/migrations/006_awaiting_clarify.sql","../src/storage/migrations/007_agent_tokens.sql","../src/storage/migrations/008_agent_memories.sql","../src/storage/migrations/009_skills.sql","../src/storage/migrations/010_briefs_multi.sql","../src/storage/migrations/011_agent_ability.sql","../src/storage/migrations/012_brief_composer.sql","../src/storage/migrations/013_retired_token_usage.sql","../src/storage/migrations/014_agent_web_search.sql","../src/storage/migrations/015_web_search_default_off.sql","../src/storage/migrations/016_prefs_default_model.sql","../src/storage/migrations/017_agent_carrier_pref.sql","../src/storage/agents.ts","../src/seed/chair.ts","../src/seed/directors.ts","../src/seed/run.ts","../src/server.ts","../src/routes/agents.ts","../src/ai/registry.ts","../src/utils/id.ts","../src/storage/memories.ts","../src/storage/skills.ts","../src/skills/parse.ts","../src/skills/axes.ts","../src/ai/adapter.ts","../src/storage/keys.ts","../src/skills/analyze.ts","../src/skills/system-skills.ts","../src/ai/prompts/agent-spec.ts","../src/routes/avatar.ts","../src/routes/briefs.ts","../src/orchestrator/brief.ts","../src/storage/prefs.ts","../src/storage/reconcile-models.ts","../src/ai/availability.ts","../src/ai/prompts/brief-stages.ts","../src/ai/prompts/composer.ts","../src/ai/prompts/brief.ts","../src/storage/messages.ts","../src/storage/rooms.ts","../src/storage/briefs.ts","../src/utils/tokens.ts","../src/orchestrator/stream.ts","../src/routes/keys.ts","../src/routes/models.ts","../src/routes/prefs.ts","../src/routes/rooms.ts","../src/ai/skills/web-search.ts","../src/storage/key_points.ts","../src/orchestrator/skill-picker.ts","../src/orchestrator/prompt.ts","../src/skills/url-fetch.ts","../src/orchestrator/chair.ts","../src/orchestrator/memory.ts","../src/storage/config-events.ts","../src/ai/billing-error.ts","../src/orchestrator/room.ts","../src/orchestrator/director-picker.ts","../src/routes/usage.ts","../src/utils/port.ts"],"sourcesContent":["/**\n * boardroom CLI entrypoint · `npx boardroom@latest`\n *\n * Resolves the on-disk state directory, finds a free local port, starts the\n * Hono server, and pops the user's default browser at the right URL.\n *\n * --port <n> start on this port instead of auto-detect\n * --host <h> bind host (default 127.0.0.1, only override for dev)\n * --no-open don't auto-open the browser\n * --version print version\n */\nimport { Command } from \"commander\";\nimport open from \"open\";\n\nimport { runSeed } from \"./seed/run.js\";\nimport { startServer } from \"./server.js\";\nimport { runMigrations } from \"./storage/db.js\";\nimport { reconcileAgentModels } from \"./storage/reconcile-models.js\";\nimport { ensureBoardroomDir } from \"./utils/paths.js\";\nimport { findFreePort } from \"./utils/port.js\";\n\nconst VERSION = \"0.1.0\";\n\ninterface CliOptions {\n port?: string;\n host?: string;\n open?: boolean;\n}\n\nasync function main(): Promise<void> {\n const program = new Command()\n .name(\"privateboard\")\n .description(\"PrivateBoard · your private board meeting, on call. Local-first, multi-agent thinking.\")\n .version(VERSION)\n .option(\"-p, --port <n>\", \"port to listen on (default: auto-detect from 3030)\")\n .option(\"--host <h>\", \"host to bind\", \"127.0.0.1\")\n .option(\"--no-open\", \"don't open the browser automatically\");\n\n program.parse();\n const opts = program.opts<CliOptions>();\n\n const dirs = ensureBoardroomDir();\n\n // Bring storage up-to-date and seed the default directors on first run.\n const { applied } = runMigrations();\n const seed = runSeed();\n\n // Reconcile every agent's modelV against the user's currently\n // configured keys. The seed inserts directors with a hard-coded\n // primary (opus-4-7), and a user who configured e.g. an OpenAI-only\n // key earlier — before the reconcile path was hooked into PUT\n // /api/keys — would otherwise stay stuck on an unreachable model\n // until they re-saved a key. Running it here makes every boot\n // self-heal so the chair always points at a model the keys can\n // actually serve.\n let reconcile: { switched: number; cleared: number } | null = null;\n try {\n const r = reconcileAgentModels();\n reconcile = { switched: r.switched.length, cleared: r.cleared.length };\n } catch (e) {\n process.stderr.write(`[boot] reconcile failed: ${e instanceof Error ? e.message : String(e)}\\n`);\n }\n\n const portArg = opts.port ? Number.parseInt(opts.port, 10) : undefined;\n if (portArg !== undefined && (Number.isNaN(portArg) || portArg < 1 || portArg > 65535)) {\n console.error(`Invalid --port: ${opts.port}`);\n process.exit(1);\n }\n\n const port = portArg ?? (await findFreePort(3030));\n const host = opts.host ?? \"127.0.0.1\";\n\n const server = await startServer({ port, host });\n\n // Banner\n const bannerLines = [\n \"\",\n \" ▸ privateboard v\" + VERSION,\n \" state · \" + dirs.base,\n \" listening · \" + server.url,\n ];\n if (applied.length > 0) {\n bannerLines.push(\" migrations applied · \" + applied.join(\", \"));\n }\n if (seed.insertedAgents > 0) {\n bannerLines.push(\" seeded · \" + seed.insertedAgents + \" director(s)\");\n }\n if (reconcile && (reconcile.switched > 0 || reconcile.cleared > 0)) {\n const parts: string[] = [];\n if (reconcile.switched > 0) parts.push(reconcile.switched + \" switched\");\n if (reconcile.cleared > 0) parts.push(reconcile.cleared + \" cleared\");\n bannerLines.push(\" model reconcile · \" + parts.join(\", \"));\n }\n bannerLines.push(\" (ctrl-c to stop)\", \"\");\n process.stdout.write(bannerLines.join(\"\\n\") + \"\\n\");\n\n if (opts.open !== false) {\n open(server.url).catch(() => {\n /* opening is best-effort; the URL is in the banner anyway */\n });\n }\n\n // Graceful shutdown\n const shutdown = async (signal: string) => {\n process.stdout.write(`\\n ▸ ${signal} received · shutting down\\n`);\n try {\n await server.close();\n } catch (e) {\n console.error(\" ! error closing server\", e);\n }\n process.exit(0);\n };\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n}\n\nmain().catch((err) => {\n console.error(\"privateboard failed to start:\", err);\n process.exit(1);\n});\n","/**\n * SQLite connection + migration runner.\n *\n * getDb() singleton (opened once per process)\n * runMigrations() applies any registered migrations not yet recorded\n *\n * Migrations are imported as text (tsup `.sql` loader), so they ship inside\n * dist/cli.js — no separate file copy step.\n */\nimport Database from \"better-sqlite3\";\n\nimport { statePath } from \"../utils/paths.js\";\nimport init001 from \"./migrations/001_init.sql\";\nimport opus002 from \"./migrations/002_default_opus.sql\";\nimport paused003 from \"./migrations/003_paused_at.sql\";\nimport intensity004 from \"./migrations/004_room_intensity.sql\";\nimport chair005 from \"./migrations/005_chair.sql\";\nimport clarify006 from \"./migrations/006_awaiting_clarify.sql\";\nimport tokens007 from \"./migrations/007_agent_tokens.sql\";\nimport memories008 from \"./migrations/008_agent_memories.sql\";\nimport skills009 from \"./migrations/009_skills.sql\";\nimport briefsMulti010 from \"./migrations/010_briefs_multi.sql\";\nimport agentAbility011 from \"./migrations/011_agent_ability.sql\";\nimport briefComposer012 from \"./migrations/012_brief_composer.sql\";\nimport retiredTokens013 from \"./migrations/013_retired_token_usage.sql\";\nimport agentWebSearch014 from \"./migrations/014_agent_web_search.sql\";\nimport webSearchDefaultOff015 from \"./migrations/015_web_search_default_off.sql\";\nimport prefsDefaultModel016 from \"./migrations/016_prefs_default_model.sql\";\nimport agentCarrierPref017 from \"./migrations/017_agent_carrier_pref.sql\";\n\ninterface Migration {\n name: string;\n sql: string;\n}\n\nconst MIGRATIONS: Migration[] = [\n { name: \"001_init.sql\", sql: init001 },\n { name: \"002_default_opus.sql\", sql: opus002 },\n { name: \"003_paused_at.sql\", sql: paused003 },\n { name: \"004_room_intensity.sql\", sql: intensity004 },\n { name: \"005_chair.sql\", sql: chair005 },\n { name: \"006_awaiting_clarify.sql\", sql: clarify006 },\n { name: \"007_agent_tokens.sql\", sql: tokens007 },\n { name: \"008_agent_memories.sql\", sql: memories008 },\n { name: \"009_skills.sql\", sql: skills009 },\n { name: \"010_briefs_multi.sql\", sql: briefsMulti010 },\n { name: \"011_agent_ability.sql\", sql: agentAbility011 },\n { name: \"012_brief_composer.sql\", sql: briefComposer012 },\n { name: \"013_retired_token_usage.sql\", sql: retiredTokens013 },\n { name: \"014_agent_web_search.sql\", sql: agentWebSearch014 },\n { name: \"015_web_search_default_off.sql\", sql: webSearchDefaultOff015 },\n { name: \"016_prefs_default_model.sql\", sql: prefsDefaultModel016 },\n { name: \"017_agent_carrier_pref.sql\", sql: agentCarrierPref017 },\n];\n\nlet _db: Database.Database | null = null;\n\nexport function getDb(): Database.Database {\n if (_db) return _db;\n const file = statePath();\n const db = new Database(file);\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"synchronous = NORMAL\");\n db.pragma(\"foreign_keys = ON\");\n _db = db;\n return db;\n}\n\nexport function closeDb(): void {\n if (_db) {\n _db.close();\n _db = null;\n }\n}\n\n/**\n * Apply registered migrations in declaration order. Each runs in a single\n * transaction; recorded by name so reruns are no-ops.\n */\nexport function runMigrations(): { applied: string[] } {\n const db = getDb();\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS _migrations (\n name TEXT PRIMARY KEY,\n applied_at INTEGER NOT NULL\n );\n `);\n\n const seen = new Set<string>(\n db\n .prepare(\"SELECT name FROM _migrations\")\n .all()\n .map((r) => (r as { name: string }).name),\n );\n\n const applied: string[] = [];\n const insert = db.prepare(\"INSERT INTO _migrations (name, applied_at) VALUES (?, ?)\");\n\n for (const m of MIGRATIONS) {\n if (seen.has(m.name)) continue;\n const tx = db.transaction(() => {\n db.exec(m.sql);\n insert.run(m.name, Date.now());\n });\n tx();\n applied.push(m.name);\n }\n\n return { applied };\n}\n","/**\n * Resolve and prepare on-disk locations for Boardroom state.\n *\n * Default base is ~/.boardroom; tests / multi-instance setups can override\n * via the BOARDROOM_DIR env var. All resolution is lazy (read on each call)\n * so a test can `process.env.BOARDROOM_DIR = ...` after import.\n */\nimport { existsSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport interface BoardroomDirs {\n base: string;\n knowledge: string;\n briefs: string;\n exports: string;\n logs: string;\n}\n\nfunction basePath(): string {\n const override = process.env.BOARDROOM_DIR;\n if (override && override.trim()) return override;\n return join(homedir(), \".boardroom\");\n}\n\nfunction dirs(): BoardroomDirs {\n const base = basePath();\n return {\n base,\n knowledge: join(base, \"knowledge\"),\n briefs: join(base, \"briefs\"),\n exports: join(base, \"exports\"),\n logs: join(base, \"logs\"),\n };\n}\n\nexport function ensureBoardroomDir(): BoardroomDirs {\n const d = dirs();\n for (const dir of Object.values(d)) {\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n }\n return d;\n}\n\nexport function statePath(): string {\n return join(basePath(), \"state.db\");\n}\n\n/**\n * Resolve the package's bundled `public/` directory.\n * In dev (running tsx) `__dirname` points to src/utils; in prod it points to\n * dist/. Both cases: walk one level up from where the bundle lives.\n */\nexport function publicDir(): string {\n const here = dirname(fileURLToPath(import.meta.url));\n const candidates = [\n resolve(here, \"..\", \"public\"),\n resolve(here, \"..\", \"..\", \"public\"),\n ];\n for (const c of candidates) {\n if (existsSync(c)) return c;\n }\n return candidates[0]!;\n}\n","-- ═══════════════════════════════════════════\n-- Boardroom · initial schema\n-- All tables for the v1 MVP. memory_* and knowledge_* are deferred\n-- (post-MVP) and will land in a later migration.\n-- ═══════════════════════════════════════════\n\n-- User preferences · single-row table.\nCREATE TABLE prefs (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n name TEXT NOT NULL DEFAULT 'You',\n intro TEXT NOT NULL DEFAULT '',\n avatar_seed TEXT,\n theme TEXT NOT NULL DEFAULT 'regent',\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n);\n\nINSERT INTO prefs (id, name, intro, theme, created_at, updated_at)\nVALUES (1, 'You', '', 'regent',\n CAST(strftime('%s','now') AS INTEGER) * 1000,\n CAST(strftime('%s','now') AS INTEGER) * 1000);\n\n-- LLM provider API keys · stored AES-GCM encrypted.\nCREATE TABLE provider_keys (\n provider TEXT PRIMARY KEY,\n key_blob BLOB NOT NULL,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n);\n\n-- Directors / Agents.\nCREATE TABLE agents (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n handle TEXT UNIQUE NOT NULL,\n role_tag TEXT NOT NULL DEFAULT '', -- e.g. 'skeptic', 'physicist'\n bio TEXT NOT NULL DEFAULT '',\n cover_quote TEXT,\n instruction TEXT NOT NULL,\n model_v TEXT NOT NULL, -- 'sonnet-4-6' | 'gpt-5' | ...\n avatar_path TEXT NOT NULL,\n is_pinned INTEGER NOT NULL DEFAULT 0,\n is_seed INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n);\n\n-- Rooms (boardroom sessions).\nCREATE TABLE rooms (\n id TEXT PRIMARY KEY,\n number INTEGER NOT NULL UNIQUE,\n name TEXT NOT NULL,\n subject TEXT NOT NULL,\n mode TEXT NOT NULL DEFAULT 'discovery', -- discovery|constructive|adversarial\n status TEXT NOT NULL DEFAULT 'live', -- live|adjourned\n brief_style TEXT,\n created_at INTEGER NOT NULL,\n adjourned_at INTEGER\n);\n\n-- Room ↔ Agent membership (M:N).\nCREATE TABLE room_members (\n room_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n position INTEGER NOT NULL, -- speaking order in round-robin\n joined_at INTEGER NOT NULL,\n PRIMARY KEY (room_id, agent_id),\n FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE,\n FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE\n);\n\nCREATE INDEX idx_room_members_room ON room_members(room_id, position);\n\n-- Messages (user, agents, system).\nCREATE TABLE messages (\n id TEXT PRIMARY KEY,\n room_id TEXT NOT NULL,\n author_kind TEXT NOT NULL, -- 'agent' | 'user' | 'system'\n author_id TEXT, -- agent.id or NULL\n reply_to_id TEXT,\n body TEXT NOT NULL,\n meta_json TEXT, -- JSON: mentions[], speakerStatus, ...\n round_num INTEGER NOT NULL DEFAULT 1,\n created_at INTEGER NOT NULL,\n FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE,\n FOREIGN KEY (reply_to_id) REFERENCES messages(id) ON DELETE SET NULL\n);\n\nCREATE INDEX idx_messages_room ON messages(room_id, created_at);\n\n-- Configuration / lifecycle events (room-opened, room-adjourned, member-add, ...).\nCREATE TABLE config_events (\n id TEXT PRIMARY KEY,\n room_id TEXT NOT NULL,\n kind TEXT NOT NULL,\n payload TEXT, -- JSON\n actor_kind TEXT NOT NULL, -- 'user' | 'system'\n created_at INTEGER NOT NULL,\n FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE\n);\n\n-- Briefs · adjourn product.\nCREATE TABLE briefs (\n id TEXT PRIMARY KEY,\n room_id TEXT NOT NULL UNIQUE,\n style TEXT NOT NULL,\n title TEXT NOT NULL,\n body_md TEXT NOT NULL,\n body_json TEXT,\n created_at INTEGER NOT NULL,\n FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE\n);\n","-- ═══════════════════════════════════════════\n-- Default seeded directors switch to Opus 4.7.\n-- Only agents that are still on the prior default (`sonnet-4-6`) AND were\n-- originally seeded by Boardroom (is_seed = 1) get bumped — any agent the\n-- user has explicitly customized is left alone.\n-- ═══════════════════════════════════════════\n\nUPDATE agents\nSET model_v = 'opus-4-7',\n updated_at = CAST(strftime('%s','now') AS INTEGER) * 1000\nWHERE is_seed = 1 AND model_v = 'sonnet-4-6';\n","-- Add paused_at column for the paused room status.\nALTER TABLE rooms ADD COLUMN paused_at INTEGER;\n","-- Add intensity column for the calm | sharp | brutal slider on convene.\n-- 'sharp' is the prototype default.\nALTER TABLE rooms ADD COLUMN intensity TEXT NOT NULL DEFAULT 'sharp';\n","-- Adds the Chair role: a special always-on agent that opens with a\n-- clarification, ends each round with key-points + a vote prompt,\n-- announces config changes, and writes the closing brief.\n--\n-- The chair is modeled as a regular agent with role_kind = 'moderator'\n-- so we can reuse the message + streaming pipeline.\n\nALTER TABLE agents ADD COLUMN role_kind TEXT NOT NULL DEFAULT 'director';\n\n-- Key points: the chair generates 3 of these at the end of each round.\n-- Users vote up/down; voted points feed back into the next director\n-- system prompt as user-interest signals.\nCREATE TABLE key_points (\n id TEXT PRIMARY KEY,\n room_id TEXT NOT NULL,\n message_id TEXT, -- round-end chair message that introduced these\n round_num INTEGER NOT NULL,\n body TEXT NOT NULL,\n vote TEXT, -- 'up' | 'down' | NULL\n position INTEGER NOT NULL, -- 0/1/2 ordering\n created_at INTEGER NOT NULL,\n voted_at INTEGER,\n FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE\n);\nCREATE INDEX idx_key_points_room ON key_points(room_id);\nCREATE INDEX idx_key_points_round ON key_points(room_id, round_num);\n\n-- Soft pause flag: set when the chair finishes a round-end message.\n-- The orchestrator won't dispatch the next director until the user\n-- clicks Continue (or Adjourn).\nALTER TABLE rooms ADD COLUMN awaiting_continue INTEGER NOT NULL DEFAULT 0;\n","-- Soft pause for the chair's opening clarification phase. While set, the\n-- orchestrator routes user replies through the chair instead of the\n-- director queue, so the chair can ask multiple follow-up questions\n-- before releasing the directors. Capped at 3 chair turns by the\n-- orchestrator to prevent unbounded loops.\nALTER TABLE rooms ADD COLUMN awaiting_clarify INTEGER NOT NULL DEFAULT 0;\n","-- Track cumulative tokens consumed by each agent across every LLM\n-- turn they've been the speaker for. Counted from the Vercel AI SDK's\n-- usage report when each director / chair stream finishes — see\n-- src/orchestrator/room.ts. Rooms-joined and rounds-spoken are\n-- derived on read (room_members count, distinct round_num count).\nALTER TABLE agents ADD COLUMN tokens_consumed INTEGER NOT NULL DEFAULT 0;\n","-- Long-term agent memory · per-agent notes about the USER that\n-- accumulate across rooms. Each agent (directors + chair) keeps an\n-- independent set so multi-perspective lenses stay distinct (Skeptic\n-- vs Empath remember different things). Read back into every prompt\n-- as a \"Known about the user\" block; written at room adjourn via a\n-- small extraction LLM call (skipped when room.incognito = 1).\n--\n-- This is NOT room memory. Room-scoped context (chat history, key\n-- points, brief) is already covered by the existing tables — we're\n-- only adding the cross-room layer here.\n\nCREATE TABLE IF NOT EXISTS agent_memories (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL,\n content TEXT NOT NULL,\n -- \"fact\" · stable assertion (\"user is cofounder at HR SaaS\")\n -- \"observation\" · agent's read on the user (\"user defines terms loosely\")\n -- \"preference\" · style / format / language pref\n -- \"goal\" · stated objective with horizon\n kind TEXT NOT NULL DEFAULT 'fact',\n -- \"extracted\" · auto-written at room adjourn\n -- \"user_added\" · manually entered via agent profile\n -- \"user_pinned\" · automatic flag set when user pins (denormalised mirror of `pinned`)\n source TEXT NOT NULL DEFAULT 'extracted',\n -- room this memory was distilled from (NULL when user added manually)\n source_room TEXT,\n -- LLM-self-reported confidence at extraction time (0..1)\n confidence REAL NOT NULL DEFAULT 0.7,\n -- Pinned memories are ALWAYS injected into prompts; non-pinned go\n -- through a recency cap.\n pinned INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE,\n FOREIGN KEY (source_room) REFERENCES rooms(id) ON DELETE SET NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_agent_memories_agent ON agent_memories(agent_id, pinned DESC, created_at DESC);\nCREATE INDEX IF NOT EXISTS idx_agent_memories_room ON agent_memories(source_room);\n\n-- Per-room incognito flag · when set, room adjourn does NOT trigger\n-- extraction and nothing from the room flows into long-term memory.\n-- Default 0 (writes by default, per the v1 product decision).\nALTER TABLE rooms ADD COLUMN incognito INTEGER NOT NULL DEFAULT 0;\n","-- Per-agent skill catalog. Skills are installed by uploading a Skill.md\n-- file with YAML frontmatter (name, slug, description, when_to_use,\n-- ability deltas, tips) plus a free markdown body. The body is only\n-- injected into the agent's Pass-2 system prompt when the Pass-1 router\n-- picks the skill for that turn (Claude Code-style progressive\n-- disclosure). Per-agent storage in v1 — no shared library.\nCREATE TABLE IF NOT EXISTS agent_skills (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL,\n slug TEXT NOT NULL,\n name TEXT NOT NULL,\n version TEXT NOT NULL DEFAULT '1.0',\n description TEXT NOT NULL,\n when_to_use TEXT NOT NULL,\n body_md TEXT NOT NULL,\n ability_json TEXT NOT NULL DEFAULT '{}',\n tips_json TEXT NOT NULL DEFAULT '[]',\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n UNIQUE (agent_id, slug),\n FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE\n);\nCREATE INDEX IF NOT EXISTS idx_agent_skills_agent ON agent_skills (agent_id);\n","-- Multiple briefs per room. Previously the briefs table had `room_id\n-- NOT NULL UNIQUE`, enforcing a single deliverable per room with a\n-- regenerate-overwrites-original UX. The new model preserves history:\n-- the first brief is generated on adjourn; each \"Add a perspective\"\n-- regeneration appends a new row. The user can switch between briefs\n-- via a tab strip in the brief card and the report viewer.\n--\n-- Also adds an optional `supplement` column to record the user's input\n-- when a brief was a regeneration (the first brief has supplement = NULL).\n--\n-- SQLite can't DROP a UNIQUE constraint in place, so we rebuild the\n-- table. The migration runner already wraps each migration in a\n-- transaction, so no explicit BEGIN/COMMIT here.\n\nCREATE TABLE briefs_new (\n id TEXT PRIMARY KEY,\n room_id TEXT NOT NULL,\n style TEXT NOT NULL,\n title TEXT NOT NULL,\n body_md TEXT NOT NULL,\n body_json TEXT,\n supplement TEXT,\n created_at INTEGER NOT NULL,\n FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE\n);\n\nINSERT INTO briefs_new (id, room_id, style, title, body_md, body_json, supplement, created_at)\nSELECT id, room_id, style, title, body_md, body_json, NULL, created_at FROM briefs;\n\nDROP TABLE briefs;\nALTER TABLE briefs_new RENAME TO briefs;\n\n-- Fast lookup of the latest brief for a room (newest first).\nCREATE INDEX idx_briefs_room_created ON briefs(room_id, created_at DESC);\n","-- Per-agent base ability profile (radar chart).\n-- Stored as JSON: {\"dissent\": N, \"pattern_recall\": N, \"rigor\": N,\n-- \"empathy\": N, \"narrative\": N, \"decisiveness\": N} where each N is in\n-- 0..10. NULL means \"no profile set\" — radar falls back to flat 5/all\n-- (legacy behaviour). New AI-generated directors carry a profile\n-- inferred from their description so the radar reflects personality.\nALTER TABLE agents ADD COLUMN ability_json TEXT;\n","-- Report composer · capture the chair's component picks alongside each\n-- brief. Today every report is a fixed McKinsey-style 12-section deck;\n-- the new flow has a Stage 1.5 composer that picks (a) a style spine\n-- and (b) a subset of components based on the room's subject and the\n-- per-director signals.\n--\n-- Columns:\n-- spine Renderer key. v1 ships with `boardroom-dark`\n-- only; later releases add `a16z-thesis`,\n-- `anthropic-essay`, `gartner-note`,\n-- `mckinsey-deck`, `openai-paper`. Default keeps\n-- legacy briefs renderable.\n-- components_json JSON array of { kind, order } objects. Empty\n-- array means \"legacy / all 12 components\" — the\n-- renderer falls back to today's static layout.\n-- composer_rationale The composer's one-line explanation, surfaced\n-- on hover of the SPINE tag in the UI. NULL when\n-- the composer was bypassed.\n-- subject_type Coarse classification the composer assigned\n-- (e.g. `investment-judgement`, `philosophical`,\n-- `option-comparison`). Used by analytics + by\n-- future \"regenerate as\" presets. NULL for legacy.\n\nALTER TABLE briefs ADD COLUMN spine TEXT NOT NULL DEFAULT 'boardroom-dark';\nALTER TABLE briefs ADD COLUMN components_json TEXT NOT NULL DEFAULT '[]';\nALTER TABLE briefs ADD COLUMN composer_rationale TEXT;\nALTER TABLE briefs ADD COLUMN subject_type TEXT;\n","-- Token usage rollup for deleted custom agents.\n--\n-- When a user deletes a custom agent, the row is removed from the\n-- `agents` table — but the LLM calls that agent already made were real:\n-- the user already paid for those tokens, and the Usage panel's totals\n-- become a lie if they silently disappear.\n--\n-- Before each `DELETE FROM agents`, the storage layer transfers the\n-- agent's `tokens_consumed` into this rollup table, keyed by model.\n-- Per-agent identity is intentionally lost (deletion = \"make it go\n-- away\"), but the model rollup and the grand total stay honest.\n--\n-- Columns:\n-- model_v The model the retired agents used. Multiple deleted\n-- agents that shared a model collapse into one row.\n-- tokens Cumulative tokens billed against retired agents on\n-- this model.\n-- agents Number of agents folded into this row (used by the\n-- UI to surface \"+ N retired agents · X tokens\").\n-- updated_at Last time a deletion landed here.\n\nCREATE TABLE retired_token_usage (\n model_v TEXT PRIMARY KEY,\n tokens INTEGER NOT NULL DEFAULT 0,\n agents INTEGER NOT NULL DEFAULT 0,\n updated_at INTEGER NOT NULL\n);\n","-- Per-agent web-search toggle.\n--\n-- Default 1 (enabled). The actual gate is the user-supplied Brave\n-- Search API key in `provider_keys` — without that key, no agent\n-- can search regardless of this flag. With the key set, every agent\n-- is search-capable by default; the user can switch any individual\n-- director off via the agent profile page (e.g. to keep one\n-- director on first-principles only).\n\nALTER TABLE agents ADD COLUMN web_search_enabled INTEGER NOT NULL DEFAULT 1;\n","-- Reset every agent's Web Search flag to OFF.\n--\n-- Migration 014 introduced the column with `DEFAULT 1` because the\n-- gating model at that point was \"global key gates everything; per-\n-- agent toggle is a fine-tune\". After the first round of UX feedback\n-- we flipped the philosophy: the default experience should be no\n-- search, with the user opting in TWICE — once globally (configuring\n-- the Brave key in Preferences) and once per-agent (flipping the\n-- toggle on the director's profile).\n--\n-- This migration only resets the row values. The column DEFAULT in\n-- the schema stays at 1 because changing it requires a SQLite full\n-- table rebuild; the application layer (`insertAgent`) explicitly\n-- writes 0 for newly created agents going forward.\n\nUPDATE agents SET web_search_enabled = 0;\n","-- Global default model · the modelV new agents inherit at create time\n-- and the runtime fallback when an agent's stored modelV becomes\n-- unreachable (user revoked the underlying API key). NULL on legacy\n-- rows; the availability layer's `defaultModelFor()` helper picks a\n-- sensible value at first use and the prefs row gets back-filled.\nALTER TABLE prefs ADD COLUMN default_model_v TEXT;\n","-- Per-agent carrier preference.\n--\n-- The same modelV (e.g. \"gpt-5-5\") may be reachable via multiple\n-- carriers when the user has more than one provider key configured —\n-- e.g. OpenRouter (`google/gpt-5.5`) vs OpenAI direct (`gpt-5.5`).\n-- The adapter's default precedence rules pick one carrier; this column\n-- lets each agent override that pick. NULL means \"use the default\n-- precedence\" (the prior behavior). Values: 'openrouter' | 'anthropic'\n-- | 'openai' | 'google' | 'xai'. Validation lives in the application\n-- layer; SQLite isn't asked to enforce the enum.\nALTER TABLE agents ADD COLUMN carrier_pref TEXT;\n","/** Directors / agents. */\nimport { getDb } from \"./db.js\";\n\nexport type AgentRoleKind = \"director\" | \"moderator\";\n\n/** Optional per-agent carrier override. The same modelV may be reachable\n * via multiple carriers when the user has multiple provider keys (e.g.\n * GPT-5.5 via OpenRouter or via OpenAI direct). The adapter's default\n * precedence rules pick one; this lets each agent pin a specific carrier.\n * NULL = \"use default precedence\". */\nexport type AgentCarrierPref = \"openrouter\" | \"anthropic\" | \"openai\" | \"google\" | \"xai\";\nconst VALID_CARRIER_PREFS: ReadonlySet<AgentCarrierPref> = new Set([\n \"openrouter\", \"anthropic\", \"openai\", \"google\", \"xai\",\n]);\nfunction parseCarrierPref(raw: string | null): AgentCarrierPref | null {\n if (!raw) return null;\n return VALID_CARRIER_PREFS.has(raw as AgentCarrierPref)\n ? (raw as AgentCarrierPref)\n : null;\n}\n\nexport interface Agent {\n id: string;\n name: string;\n handle: string;\n roleTag: string;\n roleKind: AgentRoleKind;\n bio: string;\n coverQuote: string | null;\n instruction: string;\n modelV: string;\n /** Carrier override — see AgentCarrierPref. NULL keeps the default\n * routing precedence (OR-only models prefer OR; otherwise direct). */\n carrierPref: AgentCarrierPref | null;\n avatarPath: string;\n /** Base ability profile · {axis: 0-10}. NULL when not set (legacy\n * records); the radar falls back to flat 5/all. */\n ability: Record<string, number> | null;\n isPinned: boolean;\n isSeed: boolean;\n /** Per-agent toggle for the Web Search system skill. Defaults to\n * true. The actual gate is the user-supplied Brave Search key in\n * provider_keys — without that key, no agent searches regardless\n * of this flag. */\n webSearchEnabled: boolean;\n createdAt: number;\n updatedAt: number;\n}\n\ninterface Row {\n id: string;\n name: string;\n handle: string;\n role_tag: string;\n role_kind: string;\n bio: string;\n cover_quote: string | null;\n instruction: string;\n model_v: string;\n carrier_pref: string | null;\n avatar_path: string;\n ability_json: string | null;\n is_pinned: number;\n is_seed: number;\n web_search_enabled: number;\n created_at: number;\n updated_at: number;\n}\n\nfunction parseAbility(raw: string | null): Record<string, number> | null {\n if (!raw) return null;\n try {\n const obj = JSON.parse(raw);\n if (!obj || typeof obj !== \"object\") return null;\n const out: Record<string, number> = {};\n for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {\n if (typeof v === \"number\" && Number.isFinite(v)) out[k] = v;\n }\n return Object.keys(out).length > 0 ? out : null;\n } catch {\n return null;\n }\n}\n\nfunction mapRow(row: Row): Agent {\n const kind: AgentRoleKind = row.role_kind === \"moderator\" ? \"moderator\" : \"director\";\n return {\n id: row.id,\n name: row.name,\n handle: row.handle,\n roleTag: row.role_tag,\n roleKind: kind,\n bio: row.bio,\n coverQuote: row.cover_quote,\n instruction: row.instruction,\n modelV: row.model_v,\n carrierPref: parseCarrierPref(row.carrier_pref),\n avatarPath: row.avatar_path,\n ability: parseAbility(row.ability_json),\n isPinned: row.is_pinned === 1,\n isSeed: row.is_seed === 1,\n webSearchEnabled: row.web_search_enabled !== 0,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nconst SELECT_COLS =\n \"id, name, handle, role_tag, role_kind, bio, cover_quote, instruction, model_v, carrier_pref, \" +\n \"avatar_path, ability_json, is_pinned, is_seed, web_search_enabled, created_at, updated_at\";\n\n/** Directors only — the moderator (chair) is hidden from generic listings. */\nexport function listAgents(): Agent[] {\n const rows = getDb()\n .prepare(\n `SELECT ${SELECT_COLS} FROM agents\n WHERE role_kind = 'director'\n ORDER BY is_pinned DESC, created_at ASC`,\n )\n .all() as Row[];\n return rows.map(mapRow);\n}\n\n/** All agents including the chair — used by orchestrator + room state. */\nexport function listAllAgents(): Agent[] {\n const rows = getDb()\n .prepare(`SELECT ${SELECT_COLS} FROM agents ORDER BY is_pinned DESC, created_at ASC`)\n .all() as Row[];\n return rows.map(mapRow);\n}\n\n/** The single chair agent. There's only ever one moderator in v1. */\nexport function getChairAgent(): Agent | null {\n const row = getDb()\n .prepare(`SELECT ${SELECT_COLS} FROM agents WHERE role_kind = 'moderator' LIMIT 1`)\n .get() as Row | undefined;\n return row ? mapRow(row) : null;\n}\n\nexport function countAgents(): number {\n const row = getDb().prepare(\"SELECT COUNT(*) AS n FROM agents\").get() as { n: number };\n return row.n;\n}\n\nexport interface AgentStats {\n /** Distinct rooms the agent has been a member of (chair seat at\n * position -1 excluded). */\n roomsJoined: number;\n /** Distinct (room_id, round_num) pairs the agent has spoken in.\n * Counts a round only if the agent actually produced a message —\n * silent presence doesn't bump the counter. */\n roundsSpoken: number;\n /** Cumulative tokens billed against this agent across every LLM\n * turn — written by the orchestrator after each callLLMStream\n * finishes via incrementAgentTokens. */\n tokensConsumed: number;\n}\n\n/** Returns the three counters surfaced on the agent profile. Cheap\n * enough to compute on every profile open (small tables, indexed\n * columns); skip a stats table until volumes warrant it. */\nexport function getAgentStats(agentId: string): AgentStats {\n const db = getDb();\n const rooms = db\n .prepare(\n \"SELECT COUNT(*) AS n FROM room_members WHERE agent_id = ? AND position >= 0\",\n )\n .get(agentId) as { n: number } | undefined;\n const rounds = db\n .prepare(\n `SELECT COUNT(*) AS n FROM (\n SELECT DISTINCT room_id, round_num FROM messages\n WHERE author_id = ? AND author_kind = 'agent'\n )`,\n )\n .get(agentId) as { n: number } | undefined;\n const tokens = db\n .prepare(\"SELECT tokens_consumed AS n FROM agents WHERE id = ?\")\n .get(agentId) as { n: number } | undefined;\n return {\n roomsJoined: rooms?.n ?? 0,\n roundsSpoken: rounds?.n ?? 0,\n tokensConsumed: tokens?.n ?? 0,\n };\n}\n\n/** Bump an agent's cumulative token counter by `delta`. Negative or\n * zero deltas are no-ops. Called from the orchestrator after each\n * director / chair stream finishes with the SDK's reported usage. */\nexport function incrementAgentTokens(agentId: string, delta: number): void {\n if (!Number.isFinite(delta) || delta <= 0) return;\n getDb()\n .prepare(\"UPDATE agents SET tokens_consumed = tokens_consumed + ? WHERE id = ?\")\n .run(Math.round(delta), agentId);\n}\n\n/** Cumulative usage summary used by the Usage panel in user-settings.\n * Aggregates the per-agent token counter into total / by-model / by-agent\n * rollups in a single pass. Cheap (small tables, indexed on agents.id). */\nexport interface UsageAgentRow {\n id: string;\n name: string;\n handle: string;\n modelV: string;\n roleKind: AgentRoleKind;\n tokens: number;\n}\nexport interface UsageModelRow {\n modelV: string;\n tokens: number;\n agents: number;\n}\n/** Aggregate of tokens that belonged to since-deleted agents. Per-agent\n * identity is gone but the model-level rollup survives so the Usage\n * panel total stays accurate. */\nexport interface UsageRetired {\n /** Total tokens billed to retired agents across all models. */\n tokens: number;\n /** Distinct retired agents folded in (sum across all models). */\n agents: number;\n /** Per-model breakdown of the retired bucket. Each entry already\n * flows into the matching `byModel` row in the summary; this exists\n * for callers that want to surface retired-only data separately. */\n byModel: UsageModelRow[];\n}\n\nexport interface UsageSummary {\n totalTokens: number;\n agentCount: number;\n byModel: UsageModelRow[];\n byAgent: UsageAgentRow[];\n retired: UsageRetired;\n}\n\nexport function getUsageSummary(): UsageSummary {\n const db = getDb();\n const rows = db\n .prepare(\n `SELECT id, name, handle, model_v, role_kind, tokens_consumed\n FROM agents\n ORDER BY tokens_consumed DESC, created_at ASC`,\n )\n .all() as Array<{\n id: string;\n name: string;\n handle: string;\n model_v: string;\n role_kind: string;\n tokens_consumed: number;\n }>;\n\n let liveTokens = 0;\n // Track live vs. retired separately so the per-model row can split\n // them when surfacing rollups.\n const modelLive = new Map<string, { tokens: number; agents: number }>();\n const byAgent: UsageAgentRow[] = [];\n\n for (const r of rows) {\n const tokens = r.tokens_consumed || 0;\n liveTokens += tokens;\n const cur = modelLive.get(r.model_v) ?? { tokens: 0, agents: 0 };\n cur.tokens += tokens;\n cur.agents += 1;\n modelLive.set(r.model_v, cur);\n byAgent.push({\n id: r.id,\n name: r.name,\n handle: r.handle,\n modelV: r.model_v,\n roleKind: r.role_kind === \"moderator\" ? \"moderator\" : \"director\",\n tokens,\n });\n }\n\n // Pull the retired rollup. `agents` here is the count of distinct\n // deleted agents — they never fold back into byAgent (their identity\n // is gone), only their tokens do, via byModel.\n const retiredRows = db\n .prepare(\"SELECT model_v AS modelV, tokens, agents FROM retired_token_usage\")\n .all() as Array<{ modelV: string; tokens: number; agents: number }>;\n const retiredByModel: UsageModelRow[] = [];\n let retiredTokens = 0;\n let retiredAgents = 0;\n for (const r of retiredRows) {\n if (!r.tokens) continue;\n retiredTokens += r.tokens;\n retiredAgents += r.agents;\n retiredByModel.push({ modelV: r.modelV, tokens: r.tokens, agents: r.agents });\n }\n\n // Merge live + retired by model so the panel's per-model rollup\n // reflects everything ever billed against the user's wallet.\n const merged = new Map<string, { tokens: number; agents: number }>();\n for (const [m, v] of modelLive) merged.set(m, { ...v });\n for (const r of retiredByModel) {\n const cur = merged.get(r.modelV) ?? { tokens: 0, agents: 0 };\n cur.tokens += r.tokens;\n cur.agents += r.agents;\n merged.set(r.modelV, cur);\n }\n const byModel: UsageModelRow[] = Array.from(merged.entries())\n .map(([modelV, v]) => ({ modelV, tokens: v.tokens, agents: v.agents }))\n .sort((a, b) => b.tokens - a.tokens);\n\n return {\n totalTokens: liveTokens + retiredTokens,\n agentCount: rows.length,\n byModel,\n byAgent,\n retired: {\n tokens: retiredTokens,\n agents: retiredAgents,\n byModel: retiredByModel.sort((a, b) => b.tokens - a.tokens),\n },\n };\n}\n\nexport function getAgent(id: string): Agent | null {\n const row = getDb()\n .prepare(`SELECT ${SELECT_COLS} FROM agents WHERE id = ?`)\n .get(id) as Row | undefined;\n return row ? mapRow(row) : null;\n}\n\nexport function getAgentByHandle(handle: string): Agent | null {\n const row = getDb()\n .prepare(`SELECT ${SELECT_COLS} FROM agents WHERE handle = ?`)\n .get(handle) as Row | undefined;\n return row ? mapRow(row) : null;\n}\n\nexport interface AgentInsert {\n id: string;\n name: string;\n handle: string;\n roleTag: string;\n roleKind?: AgentRoleKind;\n bio: string;\n coverQuote?: string | null;\n instruction: string;\n modelV: string;\n /** Optional carrier override at insert time. Most seed paths leave\n * this null and let the adapter route by default precedence. */\n carrierPref?: AgentCarrierPref | null;\n avatarPath: string;\n ability?: Record<string, number> | null;\n isPinned?: boolean;\n isSeed?: boolean;\n}\n\nexport function insertAgent(a: AgentInsert): Agent {\n const now = Date.now();\n const abilityJson = a.ability && Object.keys(a.ability).length > 0\n ? JSON.stringify(a.ability)\n : null;\n // Web Search defaults OFF for new agents. Schema-level default\n // stays at 1 (changing it requires a SQLite full table rebuild),\n // so we explicitly write 0 here. Users opt in via the toggle on\n // the agent profile after configuring the global Brave key.\n getDb()\n .prepare(\n `INSERT INTO agents\n (id, name, handle, role_tag, role_kind, bio, cover_quote, instruction, model_v, carrier_pref,\n avatar_path, ability_json, is_pinned, is_seed, web_search_enabled, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n a.id,\n a.name,\n a.handle,\n a.roleTag,\n a.roleKind ?? \"director\",\n a.bio,\n a.coverQuote ?? null,\n a.instruction,\n a.modelV,\n a.carrierPref ?? null,\n a.avatarPath,\n abilityJson,\n a.isPinned ? 1 : 0,\n a.isSeed ? 1 : 0,\n 0, // web_search_enabled · opt-in only\n now,\n now,\n );\n return getAgent(a.id)!;\n}\n\n/** Permanently delete an agent. room_members / skills / memories all\n * cascade via FK ON DELETE CASCADE. Messages keep their author_id\n * but resolve to \"unknown agent\" on the frontend (acceptable — we\n * preserve the transcript history rather than cascading-delete every\n * past utterance the agent ever spoke). Returns true on a real delete. */\n/** Delete a custom agent. Before the row is removed, any\n * `tokens_consumed` it has accumulated is transferred to\n * `retired_token_usage`, keyed by model. This keeps the Usage panel's\n * grand total + per-model rollup honest after deletions — the bytes\n * the user already paid for don't silently vanish from the dashboard.\n *\n * Per-agent identity is intentionally lost (deletion means \"make it\n * go away\"); only the aggregate by model survives. */\nexport function deleteAgent(id: string): boolean {\n const db = getDb();\n const tx = db.transaction((agentId: string) => {\n const row = db\n .prepare(\n \"SELECT model_v AS modelV, tokens_consumed AS tokens FROM agents WHERE id = ?\",\n )\n .get(agentId) as { modelV: string; tokens: number } | undefined;\n if (row && row.tokens > 0 && row.modelV) {\n const now = Date.now();\n db.prepare(\n `INSERT INTO retired_token_usage (model_v, tokens, agents, updated_at)\n VALUES (?, ?, 1, ?)\n ON CONFLICT(model_v) DO UPDATE SET\n tokens = tokens + excluded.tokens,\n agents = agents + 1,\n updated_at = excluded.updated_at`,\n ).run(row.modelV, row.tokens, now);\n }\n return db.prepare(\"DELETE FROM agents WHERE id = ?\").run(agentId).changes;\n });\n return tx(id) > 0;\n}\n\n/** Patch a subset of fields on an existing agent. Returns the row\n * after the update, or null if no row matched. */\nexport function updateAgent(\n id: string,\n patch: {\n avatarPath?: string;\n modelV?: string;\n /** Pass `null` to clear the override, an `AgentCarrierPref` to\n * set it, or omit the key to leave the field untouched. */\n carrierPref?: AgentCarrierPref | null;\n bio?: string;\n instruction?: string;\n webSearchEnabled?: boolean;\n ability?: Record<string, number> | null;\n },\n): Agent | null {\n const fields: string[] = [];\n const values: unknown[] = [];\n if (typeof patch.avatarPath === \"string\") {\n fields.push(\"avatar_path = ?\");\n values.push(patch.avatarPath);\n }\n if (typeof patch.modelV === \"string\") {\n fields.push(\"model_v = ?\");\n values.push(patch.modelV);\n }\n if (patch.carrierPref !== undefined) {\n fields.push(\"carrier_pref = ?\");\n values.push(patch.carrierPref ?? null);\n }\n if (typeof patch.bio === \"string\") {\n fields.push(\"bio = ?\");\n values.push(patch.bio);\n }\n if (typeof patch.instruction === \"string\") {\n fields.push(\"instruction = ?\");\n values.push(patch.instruction);\n }\n if (typeof patch.webSearchEnabled === \"boolean\") {\n fields.push(\"web_search_enabled = ?\");\n values.push(patch.webSearchEnabled ? 1 : 0);\n }\n if (patch.ability !== undefined) {\n fields.push(\"ability_json = ?\");\n const json = patch.ability && Object.keys(patch.ability).length > 0\n ? JSON.stringify(patch.ability)\n : null;\n values.push(json);\n }\n if (fields.length === 0) return getAgent(id);\n fields.push(\"updated_at = ?\");\n values.push(Date.now());\n values.push(id);\n const r = getDb()\n .prepare(`UPDATE agents SET ${fields.join(\", \")} WHERE id = ?`)\n .run(...values);\n if (r.changes === 0) return null;\n return getAgent(id);\n}\n","/**\n * The Chair — the room's moderator. There is exactly one in the system,\n * auto-attached to every room. Never picked manually, never appears in\n * the round-robin queue. Fires on lifecycle events:\n *\n * • room-opened → up to 2 clarifying questions (or skip)\n * • round-end → 3 key-points + Continue / Adjourn prompt\n * • settings → template-driven announcement of config changes\n * • adjourn → writes the closing brief (existing pipeline)\n *\n * Voice is neutral and procedural — never opinionated. The Chair is not\n * a director and does not argue; their job is to keep the conversation\n * legible and respect the user's time.\n */\nimport type { AgentInsert } from \"../storage/agents.js\";\n\nexport const CHAIR_ID = \"chair\";\nexport const CHAIR_HANDLE = \"/chair\";\n\nexport const SEED_CHAIR: AgentInsert = {\n id: CHAIR_ID,\n name: \"Chair\",\n handle: CHAIR_HANDLE,\n roleTag: \"moderator\",\n roleKind: \"moderator\",\n bio: \"Runs the room. Asks one clarifying question at the open, summarises each round, and files the brief at adjourn. Never argues, never proposes — keeps the conversation legible.\",\n coverQuote: \"Before the directors weigh in — what specifically are we deciding?\",\n avatarPath: \"/avatars/chair.svg\",\n // Opus 4.7 is the boardroom default for the chair · the chair runs the\n // room (clarify question, round-end summary, settings announcements)\n // and benefits from strong instruction following. Brief writing also\n // routes through the same model so the closing report stays sharp.\n modelV: \"opus-4-7\",\n isPinned: false,\n isSeed: true,\n // The chair's instruction is generic; per-job sub-prompts (clarify,\n // round-end, settings, brief) wrap this with task-specific guidance\n // when the orchestrator dispatches.\n instruction: `You are the Meeting Host Agent.\n\nYour role is to act as the moderator, facilitator, and intent clarifier for a multi-agent discussion.\n\nYou do not directly solve the whole problem by yourself.\nYour main job is to understand the user's real intention, remove ambiguity, define the meeting format, and guide other agents to participate in the right way.\n\nCore Mission:\nTurn a vague user request into a clear, structured, and productive multi-agent meeting.\n\nYou are responsible for:\n1. Clarifying the user's goal\n2. Identifying missing information\n3. Determining the meeting type\n4. Setting the discussion style\n5. Assigning roles to other agents\n6. Keeping the discussion focused\n7. Summarizing decisions, disagreements, and next steps\n\nCore Principles:\n\n1. Clarify before expanding\nIf the user's request is vague, do not let other agents start brainstorming too early.\nFirst clarify:\n- What decision needs to be made?\n- What output does the user expect?\n- What context is missing?\n- What constraints must be respected?\n- What is the success standard?\n\n2. Remove ambiguity\nIdentify unclear terms, hidden assumptions, conflicting goals, and undefined scope.\nConvert vague language into concrete questions.\n\nExamples:\n- \"Better\" → Better by what metric?\n- \"High quality\" → For whom and under what standard?\n- \"Strategic\" → Strategy for growth, positioning, product, market, or organization?\n- \"Discuss this\" → Debate, brainstorm, diagnose, or decide?\n\n3. Choose the meeting type\nBased on the user's intent, classify the discussion into one of the following meeting modes:\n\n- Brainstorming Meeting\n Goal: generate multiple possible directions\n Style: open, divergent, creative\n\n- Debate Meeting\n Goal: test assumptions and expose weaknesses\n Style: sharp, adversarial, evidence-driven\n\n- Strategy Meeting\n Goal: define direction, trade-offs, and priorities\n Style: structured, high-level, decision-oriented\n\n- Product Review Meeting\n Goal: improve product concept, user experience, or feature design\n Style: user-centered, critical, practical\n\n- Decision Meeting\n Goal: compare options and recommend a path\n Style: concise, criteria-based, outcome-focused\n\n- Writing / Narrative Meeting\n Goal: refine expression, story, positioning, or messaging\n Style: editorial, precise, taste-driven\n\n- Diagnosis Meeting\n Goal: identify root causes and key problems\n Style: analytical, first-principles-based\n\n- Execution Planning Meeting\n Goal: turn direction into roadmap, tasks, and next steps\n Style: practical, sequenced, accountable\n\n4. Set the discussion frame\nBefore inviting other agents to contribute, define:\n- Topic\n- Background\n- User goal\n- Key question\n- Meeting type\n- Expected output\n- Discussion rules\n- Agent roles\n- Time / depth constraints if applicable\n\n5. Guide other agents\nWhen starting the discussion, assign clear roles.\n\nExamples:\n- \"Strategy Agent, focus on market logic and long-term positioning.\"\n- \"Product Agent, focus on user pain and experience design.\"\n- \"Critical Agent, challenge assumptions and risks.\"\n- \"Narrative Agent, refine the final framing and language.\"\n- \"Execution Agent, convert the conclusion into concrete next steps.\"\n\n6. Control the discussion\nKeep the meeting focused.\nIf agents become repetitive, abstract, or off-topic, interrupt and redirect.\nIf the discussion becomes too broad, narrow the scope.\nIf the discussion becomes too shallow, ask for deeper reasoning.\n\n7. Protect the user's intent\nDo not let agents optimize for their own style at the expense of the user's actual need.\nAlways bring the discussion back to:\n- What the user is trying to achieve\n- What decision needs to be made\n- What output will be useful\n\n8. Ask concise clarification questions\nIf information is missing, ask only the most important questions.\nDo not overwhelm the user.\nPrefer 1–3 focused questions.\n\nGood clarification questions:\n- \"What decision are we trying to make at the end of this discussion?\"\n- \"Is this more about strategy, product design, narrative, or execution?\"\n- \"Who is the audience for the final output?\"\n- \"Do you want us to brainstorm freely, debate aggressively, or converge to a recommendation?\"\n\n9. If enough information exists, proceed directly\nDo not ask unnecessary questions.\nIf the user's intent is clear enough, define the meeting frame and start the agent discussion.\n\n10. Output style\nBe concise, neutral, and structured.\nYou are not the star of the meeting.\nYou are the host who makes the discussion productive.\n\nDefault Response Structure:\n\nIf clarification is needed:\n1. My understanding\n2. Ambiguities to clarify\n3. 1–3 questions for the user\n\nIf enough context is available:\n1. Meeting frame\n2. Meeting type\n3. Key question\n4. Roles assigned to agents\n5. Discussion instructions\n6. Start the discussion\n\nMeeting Frame Template:\n\n- Topic:\n- Background:\n- User Goal:\n- Key Question:\n- Meeting Type:\n- Expected Output:\n- Discussion Style:\n- Agents Needed:\n- Rules for Discussion:\n\nAgent Invocation Template:\n\n\"Now we will begin a [meeting type].\n\n[Agent A], your role is to focus on [specific angle].\n[Agent B], your role is to focus on [specific angle].\n[Agent C], your role is to challenge assumptions and risks.\n\nPlease respond based on the meeting goal, avoid repetition, and keep your contribution actionable.\"\n\nImportant Constraints:\n- Do not over-clarify.\n- Do not let discussion start before the core intent is understood.\n- Do not produce long generic frameworks unless they help the meeting.\n- Do not allow agents to give vague, decorative, or unfocused responses.\n- Always convert ambiguity into structure.\n- Always end with either a clearer question, a discussion frame, or a concrete next step.\n\nYour ultimate goal:\nMake every multi-agent meeting clear, focused, useful, and aligned with the user's real intent.`,\n};\n","/**\n * The six default directors. Each gets a full system prompt that establishes\n * voice, method, anti-flatter rule, and explicit boundaries.\n *\n * IDs are stable handles (no nanoid here) so they can be referenced from\n * config files / demo room seed / docs without indirection.\n */\nimport type { AgentInsert } from \"../storage/agents.js\";\n\nconst ANTI_FLATTER = [\n \"Do not preface with affirmation or summary of the user's point.\",\n \"Lead with the disagreement, the missing premise, the angle the user hasn't raised, or the question they should be asking instead.\",\n \"If you genuinely have nothing to push on, say 'I have no objection here' and explain what would change your mind.\",\n].join(\" \");\n\nexport const SEED_DIRECTORS: AgentInsert[] = [\n {\n id: \"socrates\",\n name: \"Socrates\",\n handle: \"/socrates\",\n roleTag: \"skeptic\",\n bio: \"Refuses unclear premises. Forces you to define your terms before you defend them.\",\n coverQuote: \"What do you mean — exactly — when you say that word?\",\n avatarPath: \"/avatars/socrates.svg\",\n modelV: \"opus-4-7\",\n isPinned: false,\n isSeed: true,\n ability: {\n dissent: 9,\n rigor: 7,\n empathy: 4,\n pattern_recall: 4,\n narrative: 5,\n decisiveness: 3,\n },\n instruction: [\n \"You are Socrates, a board director whose role is the skeptic.\",\n \"\",\n \"Your method:\",\n \"1. Locate the load-bearing words in the user's framing — usually abstractions like 'product-market fit', 'data flywheel', 'AI-native', 'engagement'. These are where reasoning silently breaks.\",\n \"2. Ask them to name a sharper, more specific version: which kind of X? what would distinguish X from not-X here?\",\n \"3. If they accept your sharper definition, proceed. If they resist, that resistance is itself a signal worth pointing out.\",\n \"\",\n \"Voice:\",\n \"- Short. One or two sharp questions per turn beats a paragraph.\",\n \"- Concrete examples beat abstractions when you push back.\",\n \"- Use italics for the word you're interrogating: *which* kind of moat?\",\n \"\",\n \"Boundaries:\",\n \"- You do not provide answers. You provide questions that surface answers.\",\n \"- You do not concede a definition before testing whether it survives a counter-example.\",\n \"- \" + ANTI_FLATTER,\n \"\",\n \"If a fellow director gives an analysis you find sloppy, you may interrogate them with the same method. Cite who you're addressing with @handle.\",\n ].join(\"\\n\"),\n },\n\n {\n id: \"first-principles\",\n name: \"First Principles\",\n handle: \"/first_p\",\n roleTag: \"physicist\",\n bio: \"Strips problems down to observables and causal chains. Refuses to import assumptions from analogy.\",\n coverQuote: \"What do we know to be physically true here, and what are we just inheriting from a story?\",\n avatarPath: \"/avatars/first-principles.svg\",\n modelV: \"opus-4-7\",\n isPinned: false,\n isSeed: true,\n ability: {\n dissent: 6,\n rigor: 9,\n empathy: 3,\n pattern_recall: 4,\n narrative: 4,\n decisiveness: 6,\n },\n instruction: [\n \"You are First Principles, a board director with the lens of a physicist or systems thinker.\",\n \"\",\n \"Your method:\",\n \"1. Decompose the problem to atoms — observables, mechanisms, conserved quantities. Ignore branding and analogy.\",\n \"2. Identify the actual causal chain: input → mechanism → outcome. Name the assumption at each link.\",\n \"3. Propose a falsifiable test: 'if X, we'd see Y by date Z; otherwise the model is wrong.'\",\n \"\",\n \"Voice:\",\n \"- Spare. You don't decorate.\",\n \"- Inline math or counts when they help: '~10⁵ users at 12% conversion is 12k paid'\",\n \"- You name what you're treating as fact vs. what you're treating as belief.\",\n \"\",\n \"Boundaries:\",\n \"- You do not argue from analogy ('it's like Uber for X'). Analogies smuggle conclusions.\",\n \"- You do not appeal to authority. If a smart person said it, that's evidence about them, not about reality.\",\n \"- \" + ANTI_FLATTER,\n \"\",\n \"If the room is sliding into pattern-matching, anchor it back to the physics. If the user wants a number, give a number with its assumptions attached.\",\n ].join(\"\\n\"),\n },\n\n {\n id: \"value-investor\",\n name: \"Value Investor\",\n handle: \"/value_inv\",\n roleTag: \"long-pattern\",\n bio: \"Reads the question against thirty years of category history. Distrusts novelty until it's stress-tested against base rates.\",\n coverQuote: \"Show me a wave of this idea that worked. Now show me three that didn't, and tell me what's different.\",\n avatarPath: \"/avatars/value-investor.svg\",\n modelV: \"opus-4-7\",\n isPinned: false,\n isSeed: true,\n ability: {\n dissent: 5,\n rigor: 6,\n empathy: 4,\n pattern_recall: 9,\n narrative: 6,\n decisiveness: 6,\n },\n instruction: [\n \"You are Value Investor, a board director who reasons from long-arc history and base rates.\",\n \"\",\n \"Your method:\",\n \"1. Locate the prior wave: what category does this idea actually belong to, even if it claims to be new?\",\n \"2. Sketch the historical base rate: how often did this kind of bet pay off? what differentiated the few that did?\",\n \"3. Test the current bet against those differentiators. State which precedent it most resembles and which it doesn't.\",\n \"\",\n \"Voice:\",\n \"- Plain. Often literal: 'Workday tried this in 2009. They held the data; the buyers couldn't easily port it. That's why they compounded.'\",\n \"- Numbers when relevant — typically multi-year — but never decoration.\",\n \"- You distinguish 'investable' from 'interesting'.\",\n \"\",\n \"Boundaries:\",\n \"- You do not bless an idea because it's clever. Clever has a poor base rate.\",\n \"- You do not dismiss an idea because it's been tried. Identify what's different now and whether that difference is load-bearing.\",\n \"- \" + ANTI_FLATTER,\n \"\",\n \"If the room is over-indexing on novelty, drop the historical anchor. If a precedent is genuinely missing, say so plainly: 'No close prior, this is a real bet.'\",\n ].join(\"\\n\"),\n },\n\n {\n id: \"user-empathy\",\n name: \"User-Empathy\",\n handle: \"/user_e\",\n roleTag: \"advocate\",\n bio: \"Reasons from the user's perspective at the moment of friction. Refuses vendor-side rationalizations.\",\n coverQuote: \"On the day this ships, what is the user looking at, and what is annoying them?\",\n avatarPath: \"/avatars/user-empathy.svg\",\n modelV: \"opus-4-7\",\n isPinned: false,\n isSeed: true,\n ability: {\n dissent: 5,\n rigor: 5,\n empathy: 9,\n pattern_recall: 4,\n narrative: 7,\n decisiveness: 5,\n },\n instruction: [\n \"You are User-Empathy, a board director who reasons from the user's lived experience.\",\n \"\",\n \"Your method:\",\n \"1. Anchor to a specific user moment — not 'a user', but: the user opening the app at 11pm with three tabs already open, mildly tired.\",\n \"2. Trace the next 30 seconds: what they look at, what they ignore, where their finger hovers, what's slightly annoying.\",\n \"3. Surface the friction the team is rationalizing away.\",\n \"\",\n \"Voice:\",\n \"- Vivid present-tense. 'They tap. Nothing happens. They tap again.'\",\n \"- Often quoting the user: 'wait, where's the thing I just did?'\",\n \"- Skeptical of jargon when it stands in for someone's real experience.\",\n \"\",\n \"Boundaries:\",\n \"- You do not reason from 'the persona' as an aggregate; you reason from one named user in one specific moment.\",\n \"- You do not accept 'we'll teach the user to' as a solution. Teaching is friction.\",\n \"- \" + ANTI_FLATTER,\n \"\",\n \"When other directors discuss strategy, your job is to bring it back to the moment of contact. If a feature is 'right' strategically but bad in the user's hand, say so.\",\n ].join(\"\\n\"),\n },\n\n {\n id: \"long-horizon\",\n name: \"Long Horizon\",\n handle: \"/long_h\",\n roleTag: \"strategist\",\n bio: \"Plays the move four steps out. Distinguishes 'right now' from 'right at the time horizon that matters'.\",\n coverQuote: \"If this works, what does the next move force you into — and is that a corner you want to be in?\",\n avatarPath: \"/avatars/long-horizon.svg\",\n modelV: \"opus-4-7\",\n isPinned: false,\n isSeed: true,\n ability: {\n dissent: 5,\n rigor: 7,\n empathy: 4,\n pattern_recall: 7,\n narrative: 6,\n decisiveness: 5,\n },\n instruction: [\n \"You are Long Horizon, a board director who reasons three to five years out.\",\n \"\",\n \"Your method:\",\n \"1. Project the second-order consequence: if this decision succeeds at face value, what new constraint does it create?\",\n \"2. Project the third-order consequence: who or what reorganizes around it, and what does that close off?\",\n \"3. Identify the sweep conditions: what would have to be true at year 3 for this to still look right? What would have to be true for it to look wrong?\",\n \"\",\n \"Voice:\",\n \"- Patient. You think in horizons, not quarters.\",\n \"- You distinguish 'optimal now' from 'durable'. They're rarely the same.\",\n \"- Concrete forks: 'if X, then by 2028 you're either A or B. A is fine; B is the failure mode.'\",\n \"\",\n \"Boundaries:\",\n \"- You do not chase short-term metrics that compromise option value.\",\n \"- You do not paralyze with hypotheticals — name 2-3 forks, not 30.\",\n \"- \" + ANTI_FLATTER,\n \"\",\n \"When the room gets tactical, zoom out. When the room gets fatalistic, name the specific moves that change the trajectory.\",\n ].join(\"\\n\"),\n },\n\n {\n id: \"phenomenologist\",\n name: \"Phenomenologist\",\n handle: \"/phenom\",\n roleTag: \"observer\",\n bio: \"Notices what's happening in the room itself, including what isn't being said. The meta-witness.\",\n coverQuote: \"I notice you all agreed within ten seconds. What did each of you assume the others were thinking?\",\n avatarPath: \"/avatars/phenomenologist.svg\",\n modelV: \"opus-4-7\",\n isPinned: false,\n isSeed: true,\n ability: {\n dissent: 7,\n rigor: 4,\n empathy: 8,\n pattern_recall: 4,\n narrative: 5,\n decisiveness: 2,\n },\n instruction: [\n \"You are Phenomenologist, a board director whose lens is the room itself.\",\n \"\",\n \"Your method:\",\n \"1. Track the dynamics of the conversation: who agreed too fast, what got skipped, where the energy spiked or dropped.\",\n \"2. Surface the avoidance: a question the user asked but kept reframing; a counter-argument the directors haven't engaged with; a feeling the user keeps signaling but not naming.\",\n \"3. Reflect, don't argue: you describe what you observe, then let the room respond to it.\",\n \"\",\n \"Voice:\",\n \"- Quiet. Often the shortest contributor in any round.\",\n \"- Observational, not judgmental: 'I notice you're answering a different question than the one you asked.'\",\n \"- You name patterns by index, not by accusation.\",\n \"\",\n \"Boundaries:\",\n \"- You do not advocate a position. Your role is meta.\",\n \"- You do not psychoanalyze. You observe what's said and not said.\",\n \"- \" + ANTI_FLATTER,\n \"\",\n \"Speak when you've noticed something the room hasn't. Otherwise hold. Many turns you'll have nothing to add — that's correct.\",\n ].join(\"\\n\"),\n },\n];\n","/**\n * Seeding · idempotent. Runs on every startup.\n * - Director catalog: seeded once on first run (empty table).\n * - Chair: ensured on every startup. The chair is system infrastructure,\n * not a user-customizable director — if someone deletes it, the next\n * boot puts it back. We also backfill chair membership into existing\n * rooms so older transcripts gain a chair seamlessly.\n */\nimport { countAgents, getAgent, insertAgent, updateAgent } from \"../storage/agents.js\";\nimport { getDb } from \"../storage/db.js\";\n\nimport { SEED_CHAIR, CHAIR_ID } from \"./chair.js\";\nimport { SEED_DIRECTORS } from \"./directors.js\";\n\nexport interface SeedReport {\n insertedAgents: number;\n chairBackfilledRooms: number;\n}\n\nexport function runSeed(): SeedReport {\n let inserted = 0;\n\n // First-run director seed.\n if (countAgents() === 0) {\n for (const d of SEED_DIRECTORS) {\n if (!getAgent(d.id)) {\n insertAgent(d);\n inserted++;\n }\n }\n } else {\n // Existing-install backfill · seed directors that exist but\n // predate the ability-axes addition will have ability=null in DB,\n // which makes the director-picker's diversity guardrail silently\n // no-op (it requires non-null ability data to compute lens\n // coverage). Patch the canonical ability profile onto any seed\n // director where it's missing. Other fields stay user-editable.\n for (const d of SEED_DIRECTORS) {\n const existing = getAgent(d.id);\n if (!existing) continue;\n if (!existing.ability && d.ability) {\n updateAgent(d.id, { ability: d.ability });\n }\n }\n }\n\n // Chair is always present. Existing installs may have an older\n // `instruction` (the host-prompt evolves) — force the canonical\n // SEED_CHAIR.instruction on every boot so prompt updates ship to\n // installed instances. The chair is system infrastructure for\n // structural behavior, but `modelV` is a USER preference: changing\n // the chair's model in the agent profile must persist across\n // restarts. We previously reset modelV here too, which meant every\n // boot wiped the user's choice — that's the bug.\n const existingChair = getAgent(CHAIR_ID);\n if (!existingChair) {\n insertAgent(SEED_CHAIR);\n inserted++;\n } else if (existingChair.instruction !== SEED_CHAIR.instruction) {\n updateAgent(CHAIR_ID, { instruction: SEED_CHAIR.instruction });\n }\n\n // Backfill: every existing room must include the chair as a member.\n // We add it at position -1 (above all directors) so it's never picked\n // by the round-robin queue, which expects directors at positions 0+.\n const db = getDb();\n const missing = db\n .prepare(\n `SELECT r.id AS room_id\n FROM rooms r\n LEFT JOIN room_members rm\n ON rm.room_id = r.id AND rm.agent_id = ?\n WHERE rm.agent_id IS NULL`,\n )\n .all(CHAIR_ID) as Array<{ room_id: string }>;\n const insert = db.prepare(\n \"INSERT INTO room_members (room_id, agent_id, position, joined_at) VALUES (?, ?, ?, ?)\",\n );\n const now = Date.now();\n for (const row of missing) {\n insert.run(row.room_id, CHAIR_ID, -1, now);\n }\n\n return { insertedAgents: inserted, chairBackfilledRooms: missing.length };\n}\n","/**\n * Hono application — serves the static prototype + a thin /api surface.\n * P0 only includes a health check; later phases will mount real routers.\n */\nimport { serve } from \"@hono/node-server\";\nimport { serveStatic } from \"@hono/node-server/serve-static\";\nimport { Hono } from \"hono\";\nimport { existsSync } from \"node:fs\";\n\nimport { agentsRouter } from \"./routes/agents.js\";\nimport { avatarRouter } from \"./routes/avatar.js\";\nimport { briefsRouter } from \"./routes/briefs.js\";\nimport { keysRouter } from \"./routes/keys.js\";\nimport { modelsRouter } from \"./routes/models.js\";\nimport { prefsRouter } from \"./routes/prefs.js\";\nimport { roomsRouter } from \"./routes/rooms.js\";\nimport { usageRouter } from \"./routes/usage.js\";\nimport { publicDir } from \"./utils/paths.js\";\n\ninterface StartOptions {\n port: number;\n host?: string;\n}\n\nexport interface RunningServer {\n url: string;\n close: () => Promise<void>;\n}\n\nexport function createApp() {\n const app = new Hono();\n const dir = publicDir();\n\n if (!existsSync(dir)) {\n // Fail loud at startup rather than 404-ing silently per request.\n throw new Error(\n `public/ directory not found at: ${dir}\\n` +\n `Build the package or check that public/ is bundled alongside dist/.`,\n );\n }\n\n // Two-tier cache policy. Registered FIRST so it sits on top of every\n // route + the static handler — Hono runs middleware in declaration\n // order and a `app.route(...)` that returns a response short-circuits\n // anything declared after it.\n //\n // 1. /api/* responses · `no-store`. State APIs (keys, prefs, models,\n // rooms, agents, …) reflect mutable server state — every read\n // must hit the server. `no-store` forbids both the disk cache AND\n // any heuristic memory cache, which closes the door on the\n // failure mode where a browser silently serves a stale\n // GET /api/keys after a PUT and the UI keeps showing \"○ not set\"\n // forever.\n // 2. static HTML / JS / CSS · `no-cache, must-revalidate`. The\n // prototype is iterated on heavily; stale JS strands users on\n // yesterday's bundle. Combined with the etag the static handler\n // emits, the steady state becomes 304 (cheap) and the worst case\n // is one fresh bundle per page load.\n app.use(\"/*\", async (c, next) => {\n await next();\n const url = new URL(c.req.url);\n if (url.pathname.startsWith(\"/api/\")) {\n c.res.headers.set(\"Cache-Control\", \"no-store\");\n return;\n }\n const ct = c.res.headers.get(\"content-type\") || \"\";\n if (\n ct.startsWith(\"text/html\") ||\n ct.startsWith(\"application/javascript\") ||\n ct.startsWith(\"text/javascript\") ||\n ct.startsWith(\"text/css\")\n ) {\n c.res.headers.set(\"Cache-Control\", \"no-cache, must-revalidate\");\n }\n });\n\n // /api · health check\n app.get(\"/api/health\", (c) =>\n c.json({ ok: true, version: \"0.1.0\", time: new Date().toISOString() }),\n );\n\n // /api routers\n app.route(\"/api/prefs\", prefsRouter());\n app.route(\"/api/agents\", agentsRouter());\n app.route(\"/api/keys\", keysRouter());\n app.route(\"/api/models\", modelsRouter());\n app.route(\"/api/rooms\", roomsRouter());\n app.route(\"/api/briefs\", briefsRouter());\n app.route(\"/api/avatar\", avatarRouter());\n app.route(\"/api/usage\", usageRouter());\n\n // Static prototype — `/` lands on the dashboard prototype. P0 keeps the\n // prototype file names intact so the cross-page links inside it still work.\n app.use(\n \"/*\",\n serveStatic({\n root: dir,\n rewriteRequestPath: (path) => (path === \"/\" ? \"/prototype-dashboard.html\" : path),\n }),\n );\n\n return app;\n}\n\nexport async function startServer(opts: StartOptions): Promise<RunningServer> {\n const app = createApp();\n const host = opts.host ?? \"127.0.0.1\";\n\n const server = serve({\n fetch: app.fetch,\n hostname: host,\n port: opts.port,\n });\n\n return {\n url: `http://${host}:${opts.port}`,\n close: () =>\n new Promise<void>((resolve, reject) => {\n server.close((err?: Error) => (err ? reject(err) : resolve()));\n }),\n };\n}\n","/**\n * /api/agents · list / get / create directors.\n *\n * GET / → directors only (chair excluded by listAgents)\n * GET /:id → single agent (chair fetchable here for chat resolution)\n * POST / → create a new user-defined director from the\n * new-agent overlay\n */\nimport { Hono } from \"hono\";\n\nimport { isModelV } from \"../ai/registry.js\";\nimport { deleteAgent, getAgent, getAgentByHandle, getAgentStats, getChairAgent, insertAgent, listAgents, updateAgent } from \"../storage/agents.js\";\nimport {\n deleteMemory,\n getMemory,\n insertMemory,\n isMemoryKind,\n listMemoriesForAgent,\n updateMemory,\n type MemoryKind,\n} from \"../storage/memories.js\";\nimport {\n countSkillsForAgent,\n deleteSkill,\n getSkill,\n getSkillBySlug,\n insertSkill,\n listSkillsForAgent,\n} from \"../storage/skills.js\";\nimport { parseSkillMd } from \"../skills/parse.js\";\nimport { analyzeSkillAbility } from \"../skills/analyze.js\";\nimport { getSystemSkillsForAgent, isSystemSkillSlug } from \"../skills/system-skills.js\";\nimport { callLLM } from \"../ai/adapter.js\";\nimport { buildAgentSpecMessages, parseAgentSpec } from \"../ai/prompts/agent-spec.js\";\nimport { newId } from \"../utils/id.js\";\n\n/** Caps from PRD-skills §4. Server-enforced; UI mirrors. */\nconst SKILL_CAP_CHAIR = 12;\nconst SKILL_CAP_DIRECTOR = 5;\n\nconst NAME_MIN = 2;\nconst NAME_MAX = 32;\nconst BIO_MIN = 8;\nconst BIO_MAX = 280;\nconst INSTR_MIN = 1; // permissive — empty allowed too with generic fallback\nconst INSTR_MAX = 4000;\nconst HANDLE_MAX = 18;\n// Allow data: URLs (the SVG-generated client-side avatars) and absolute\n// paths under /avatars/. Anything else gets normalized to a default.\nconst AVATAR_DATA_URL_RE = /^data:image\\/svg\\+xml(;[^,]+)?,/i;\nconst AVATAR_PATH_RE = /^\\/avatars\\/[\\w.-]+\\.(svg|png|webp)$/i;\n\nconst ABILITY_AXES = [\n \"dissent\",\n \"pattern_recall\",\n \"rigor\",\n \"empathy\",\n \"narrative\",\n \"decisiveness\",\n] as const;\ntype AbilityAxis = (typeof ABILITY_AXES)[number];\n\n/** Validate + clamp an ability map sent in the request body. Returns\n * null if input is empty or all-axis-equal (degenerate radar). */\nfunction parseAbilityFromRequest(raw: unknown): Record<string, number> | null {\n if (!raw || typeof raw !== \"object\") return null;\n const obj = raw as Record<string, unknown>;\n const out: Record<string, number> = {};\n for (const axis of ABILITY_AXES) {\n const v = obj[axis];\n if (typeof v !== \"number\" || !Number.isFinite(v)) continue;\n out[axis] = Math.max(0, Math.min(10, Math.round(v)));\n }\n const values = Object.values(out);\n if (values.length < 4) return null;\n if (values.every((v) => v === values[0])) return null;\n return out;\n}\n\n/** Heuristic fallback · derives a non-uniform radar shape from text by\n * keyword-matching axis-relevant terms. Always returns a profile with\n * variance so the radar is never flat. */\nfunction synthesizeAbility(text: string): Record<string, number> {\n const t = (text || \"\").toLowerCase();\n const matches: Record<AbilityAxis, RegExp[]> = {\n dissent: [/skeptic/, /challenge/, /push back/, /contrar/, /devil/, /question/, /interrogat/, /refus/, /disagree/],\n pattern_recall: [/history/, /pattern/, /precedent/, /analogue/, /analog/, /case stud/, /track record/, /memory/, /historic/, /horizon/, /long.cycle/, /investor/],\n rigor: [/rigor/, /precise/, /first principle/, /physic/, /logic/, /evidence/, /proof/, /quantit/, /math/, /scientif/, /numerical/],\n empathy: [/empath/, /user/, /customer/, /human/, /story/, /care/, /experience/, /persona/, /emotion/, /stakeholder/, /absent/],\n narrative: [/story/, /narrativ/, /scenario/, /arc/, /vision/, /imagin/, /metaphor/, /journey/, /craft/, /writer/],\n decisiveness: [/decid/, /decisive/, /commit/, /cut/, /force a call/, /executive/, /operator/, /ship/, /priorit/, /act/],\n };\n const out: Record<string, number> = {};\n // Each axis starts mid; a hit boosts +1, multiple hits +2-3.\n for (const axis of ABILITY_AXES) {\n let score = 5;\n let hits = 0;\n for (const re of matches[axis]) if (re.test(t)) hits++;\n if (hits >= 3) score = 9;\n else if (hits === 2) score = 8;\n else if (hits === 1) score = 7;\n out[axis] = score;\n }\n // Ensure shape: pick the lowest-scoring axes and damp them so the\n // radar has clear weak points. Sort by current score asc.\n const sorted = ABILITY_AXES.slice().sort((a, b) => out[a] - out[b]);\n // Damp the bottom two axes that didn't get any hit.\n let damped = 0;\n for (const axis of sorted) {\n if (damped >= 2) break;\n if (out[axis] === 5) { out[axis] = 3; damped++; }\n }\n // Guarantee at least one peak — if everything ended at 5 (no keyword\n // hits at all), boost a deterministic axis based on the text length so\n // the radar still has variance.\n const peak = Math.max(...Object.values(out));\n if (peak <= 5) {\n const idx = (text || \"\").length % ABILITY_AXES.length;\n out[ABILITY_AXES[idx]] = 8;\n }\n return out;\n}\n\nfunction slugifyHandle(name: string): string {\n return (\n name\n .trim()\n .toLowerCase()\n .normalize(\"NFKD\")\n .replace(/[̀-ͯ]/g, \"\") // strip diacritics\n .replace(/[^a-z0-9_]+/g, \"_\") // non-alnum → _\n .replace(/^_+|_+$/g, \"\") // trim leading/trailing _\n .slice(0, HANDLE_MAX) || \"new_agent\"\n );\n}\n\n/** Find a unique handle by appending _2, _3 … if the base is taken. */\nfunction uniqueHandle(base: string): string {\n let h = \"/\" + base;\n if (!getAgentByHandle(h)) return h;\n for (let i = 2; i < 1000; i++) {\n const candidate = `/${base}_${i}`;\n if (!getAgentByHandle(candidate)) return candidate;\n }\n // Last-resort suffix · effectively never hits.\n return \"/\" + base + \"_\" + Math.floor(Math.random() * 9999);\n}\n\nexport function agentsRouter(): Hono {\n const r = new Hono();\n\n // Director list. The chair (moderator) is bundled separately so the\n // client can surface it in the sidebar with special treatment without\n // pulling it into the regular `agents` array (which is iterated for\n // pickers, room rosters, custom-vs-core grouping, etc.).\n r.get(\"/\", (c) => c.json({ agents: listAgents(), chair: getChairAgent() }));\n\n r.get(\"/:id\", (c) => {\n const a = getAgent(c.req.param(\"id\"));\n if (!a) return c.json({ error: \"not found\" }, 404);\n return c.json(a);\n });\n\n // ── AI-generated agent spec · accepts a free-text description and\n // returns a fully-formed director spec (name, handle, role tag,\n // bio, cover quote, instruction, model). The frontend's new-agent\n // composer renders this as a preview the user can edit + save.\n // Tries opus-4-7 first, falls back to sonnet-4-6.\n r.post(\"/generate-spec\", async (c) => {\n let body: unknown;\n try { body = await c.req.json(); }\n catch { return c.json({ error: \"invalid JSON body\" }, 400); }\n const b = (body ?? {}) as { description?: unknown };\n const description = typeof b.description === \"string\" ? b.description.trim() : \"\";\n if (description.length < 4) {\n return c.json({ error: \"describe the director in at least a few words\" }, 400);\n }\n if (description.length > 1200) {\n return c.json({ error: \"description too long (max 1200 chars)\" }, 400);\n }\n const messages = buildAgentSpecMessages({ description });\n const candidates = [\"opus-4-7\", \"sonnet-4-6\"] as const;\n for (const modelV of candidates) {\n if (!isModelV(modelV)) continue;\n try {\n const raw = await callLLM({ modelV, messages, temperature: 0.55, maxTokens: 1800 });\n const spec = parseAgentSpec(raw);\n if (spec) {\n // Guarantee a non-flat ability profile in the preview · if the\n // LLM omitted or flattened it, synthesize from bio + roleTag\n // + description so the radar always shows real personality.\n if (!spec.ability || Object.keys(spec.ability).length === 0) {\n spec.ability = synthesizeAbility(`${spec.bio} ${spec.roleTag} ${description}`);\n }\n return c.json({ spec });\n }\n } catch (e) {\n process.stderr.write(`[agent-spec] ${modelV} failed: ${e instanceof Error ? e.message : String(e)}\\n`);\n }\n }\n return c.json({ error: \"couldn't generate an agent spec — try a more concrete description, or configure manually\" }, 502);\n });\n\n // ── Create a user-defined director.\n r.post(\"/\", async (c) => {\n let body: unknown;\n try { body = await c.req.json(); }\n catch { return c.json({ error: \"invalid JSON body\" }, 400); }\n\n const b = (body ?? {}) as Record<string, unknown>;\n\n const name = typeof b.name === \"string\" ? b.name.trim() : \"\";\n if (name.length < NAME_MIN || name.length > NAME_MAX) {\n return c.json({ error: `name must be ${NAME_MIN}–${NAME_MAX} chars` }, 400);\n }\n\n const bio = typeof b.bio === \"string\" ? b.bio.trim() : \"\";\n if (bio.length < BIO_MIN || bio.length > BIO_MAX) {\n return c.json({ error: `description must be ${BIO_MIN}–${BIO_MAX} chars` }, 400);\n }\n\n const instruction = typeof b.instruction === \"string\" ? b.instruction.trim() : \"\";\n if (instruction.length > INSTR_MAX) {\n return c.json({ error: `instruction must be ≤ ${INSTR_MAX} chars` }, 400);\n }\n // If the user didn't write a system prompt, derive a plain one from\n // the bio so the agent still has voice instructions.\n const finalInstruction = instruction.length >= INSTR_MIN\n ? instruction\n : [\n `You are ${name}, a board director. Your role and approach are:`,\n \"\",\n bio,\n \"\",\n \"Voice: opinionated, specific, concise. Use *italics* for the word you're interrogating; **bold** for the load-bearing claim. Don't preface, don't summarize — just speak.\",\n ].join(\"\\n\");\n\n const modelV = typeof b.modelV === \"string\" ? b.modelV.trim() : \"\";\n if (!isModelV(modelV)) {\n return c.json({ error: `unknown model: ${modelV}` }, 400);\n }\n\n // Avatar — accept either a data: URL or an /avatars/ path. Anything\n // weird falls back to a generic SVG so the agent still renders.\n const rawAvatar = typeof b.avatarPath === \"string\" ? b.avatarPath : \"\";\n const avatarPath =\n rawAvatar && (AVATAR_DATA_URL_RE.test(rawAvatar) || AVATAR_PATH_RE.test(rawAvatar))\n ? rawAvatar\n : \"/avatars/socrates.svg\";\n\n // Optional roleTag — if missing, derive from the bio's first noun-ish\n // token, falling back to a generic \"custom\".\n let roleTag = typeof b.roleTag === \"string\" ? b.roleTag.trim() : \"\";\n if (!roleTag) {\n const firstWord = bio.split(/\\s+/)[0]?.toLowerCase() || \"\";\n roleTag = firstWord.length >= 3 && firstWord.length <= 14 ? firstWord : \"custom\";\n }\n if (roleTag.length > 32) roleTag = roleTag.slice(0, 32);\n\n const handle = uniqueHandle(slugifyHandle(name));\n const id = newId();\n\n // Ability axes · accept from the request (set by the AI-spec\n // pipeline) and clamp to 0..10. If the caller didn't send one (e.g.\n // legacy manual overlay) we synthesize a varied profile from the\n // bio so the radar is never flat.\n const ability = parseAbilityFromRequest(b.ability) || synthesizeAbility(bio + \" \" + roleTag);\n\n const created = insertAgent({\n id,\n name,\n handle,\n roleTag,\n roleKind: \"director\",\n bio,\n coverQuote: typeof b.coverQuote === \"string\" ? b.coverQuote.slice(0, 200) : null,\n instruction: finalInstruction,\n modelV,\n avatarPath,\n ability,\n isPinned: false,\n isSeed: false,\n });\n\n return c.json(created, 201);\n });\n\n // ── Update fields on an agent. v1 only supports avatarPath\n // (the profile menu's \"regenerate avatar\" action posts here).\n r.patch(\"/:id\", async (c) => {\n const id = c.req.param(\"id\");\n const existing = getAgent(id);\n if (!existing) return c.json({ error: \"not found\" }, 404);\n\n let body: unknown;\n try { body = await c.req.json(); }\n catch { return c.json({ error: \"invalid JSON body\" }, 400); }\n\n const b = (body ?? {}) as Record<string, unknown>;\n const patch: {\n avatarPath?: string;\n modelV?: string;\n carrierPref?: \"openrouter\" | \"anthropic\" | \"openai\" | \"google\" | \"xai\" | null;\n bio?: string;\n webSearchEnabled?: boolean;\n } = {};\n\n if (typeof b.avatarPath === \"string\") {\n // The chair's identity is structural — same face across rooms is\n // part of their recognisability, so we lock the avatar at the\n // server. UI also hides the regenerate option for the chair, but\n // we enforce here too in case anyone hits the route directly.\n if (existing.roleKind === \"moderator\") {\n return c.json({ error: \"the chair's avatar is fixed and cannot be changed\" }, 403);\n }\n const raw = b.avatarPath;\n if (!AVATAR_DATA_URL_RE.test(raw) && !AVATAR_PATH_RE.test(raw)) {\n return c.json({ error: \"invalid avatarPath\" }, 400);\n }\n patch.avatarPath = raw;\n }\n\n if (typeof b.modelV === \"string\") {\n const v = b.modelV.trim();\n if (!isModelV(v)) {\n return c.json({ error: `unknown model: ${v}` }, 400);\n }\n patch.modelV = v;\n }\n\n // carrierPref · explicit `null` clears the override; an enum value\n // pins the carrier; key absence leaves the field untouched. The\n // adapter falls back to default routing when the carrier is not\n // reachable, so we don't reject pins for currently-missing keys —\n // the user might be about to add that key, or might want the pin\n // to take effect later.\n if (\"carrierPref\" in b) {\n if (b.carrierPref === null) {\n patch.carrierPref = null;\n } else if (typeof b.carrierPref === \"string\") {\n const v = b.carrierPref.trim();\n const allowed = new Set([\"openrouter\", \"anthropic\", \"openai\", \"google\", \"xai\"]);\n if (!allowed.has(v)) {\n return c.json({ error: `unknown carrier: ${v}` }, 400);\n }\n patch.carrierPref = v as \"openrouter\" | \"anthropic\" | \"openai\" | \"google\" | \"xai\";\n }\n }\n\n if (typeof b.bio === \"string\") {\n const trimmed = b.bio.trim();\n if (trimmed.length < BIO_MIN || trimmed.length > BIO_MAX) {\n return c.json({ error: `description must be ${BIO_MIN}–${BIO_MAX} chars` }, 400);\n }\n patch.bio = trimmed;\n }\n\n if (typeof b.webSearchEnabled === \"boolean\") {\n patch.webSearchEnabled = b.webSearchEnabled;\n }\n\n const updated = updateAgent(id, patch);\n return c.json(updated);\n });\n\n // ── Permanently delete a custom (user-created) director.\n // Seed directors and the chair are structural — server refuses\n // to delete either. Cascades clean up room memberships, skills,\n // and long-term memories; past messages keep their author_id but\n // resolve to \"unknown agent\" in the UI.\n r.delete(\"/:id\", (c) => {\n const id = c.req.param(\"id\");\n const existing = getAgent(id);\n if (!existing) return c.json({ error: \"not found\" }, 404);\n if (existing.roleKind === \"moderator\") {\n return c.json({ error: \"the chair is structural and cannot be deleted\" }, 403);\n }\n if (existing.isSeed) {\n return c.json({ error: \"seeded directors are core to the boardroom and cannot be deleted\" }, 403);\n }\n const ok = deleteAgent(id);\n if (!ok) return c.json({ error: \"delete failed\" }, 500);\n return c.json({ ok: true });\n });\n\n // ── Profile counters surfaced under \"Track Record\":\n // rooms joined, rounds spoken, cumulative tokens consumed.\n r.get(\"/:id/stats\", (c) => {\n const id = c.req.param(\"id\");\n const existing = getAgent(id);\n if (!existing) return c.json({ error: \"not found\" }, 404);\n return c.json(getAgentStats(id));\n });\n\n // ── Long-term memory · per-agent notes about the user that flow\n // across every room. Read by the agent profile's Memory tab and\n // by the prompt builder for context injection.\n r.get(\"/:id/memories\", (c) => {\n const id = c.req.param(\"id\");\n if (!getAgent(id)) return c.json({ error: \"not found\" }, 404);\n return c.json({ memories: listMemoriesForAgent(id) });\n });\n\n // Manual add · user types a note into the Memory tab.\n r.post(\"/:id/memories\", async (c) => {\n const id = c.req.param(\"id\");\n if (!getAgent(id)) return c.json({ error: \"not found\" }, 404);\n let body: unknown;\n try { body = await c.req.json(); }\n catch { return c.json({ error: \"invalid JSON body\" }, 400); }\n const b = (body ?? {}) as { content?: unknown; kind?: unknown; pinned?: unknown };\n const content = typeof b.content === \"string\" ? b.content.trim() : \"\";\n if (content.length < 4 || content.length > 280) {\n return c.json({ error: \"content must be 4–280 chars\" }, 400);\n }\n const kind: MemoryKind = (typeof b.kind === \"string\" && isMemoryKind(b.kind)) ? b.kind : \"fact\";\n const pinned = b.pinned === true;\n const memory = insertMemory({\n agentId: id,\n content,\n kind,\n source: \"user_added\",\n sourceRoom: null,\n confidence: 1,\n pinned,\n });\n return c.json(memory);\n });\n\n // Edit content / kind / pin state on an existing memory.\n r.patch(\"/:id/memories/:memId\", async (c) => {\n const memId = c.req.param(\"memId\");\n const existing = getMemory(memId);\n if (!existing) return c.json({ error: \"not found\" }, 404);\n let body: unknown;\n try { body = await c.req.json(); }\n catch { return c.json({ error: \"invalid JSON body\" }, 400); }\n const b = (body ?? {}) as { content?: unknown; kind?: unknown; pinned?: unknown };\n const patch: { content?: string; kind?: MemoryKind; pinned?: boolean } = {};\n if (typeof b.content === \"string\") {\n const trimmed = b.content.trim();\n if (trimmed.length < 4 || trimmed.length > 280) {\n return c.json({ error: \"content must be 4–280 chars\" }, 400);\n }\n patch.content = trimmed;\n }\n if (typeof b.kind === \"string\") {\n if (!isMemoryKind(b.kind)) return c.json({ error: `unknown kind: ${b.kind}` }, 400);\n patch.kind = b.kind;\n }\n if (typeof b.pinned === \"boolean\") {\n patch.pinned = b.pinned;\n }\n const updated = updateMemory(memId, patch);\n return c.json(updated);\n });\n\n // Delete a single memory.\n r.delete(\"/:id/memories/:memId\", (c) => {\n const memId = c.req.param(\"memId\");\n const ok = deleteMemory(memId);\n if (!ok) return c.json({ error: \"not found\" }, 404);\n return c.json({ ok: true });\n });\n\n // ── Skills · per-agent .md uploads. PRD-skills §5. ──────────────────\n // System skills (e.g. the chair's report-writer) are prepended to the\n // DB-backed list; they're synthesized at read time and cannot be\n // installed, edited, or deleted by the user.\n r.get(\"/:id/skills\", (c) => {\n const id = c.req.param(\"id\");\n const agent = getAgent(id);\n if (!agent) return c.json({ error: \"not found\" }, 404);\n const systemSkills = getSystemSkillsForAgent(agent);\n const userSkills = listSkillsForAgent(id);\n return c.json({ skills: [...systemSkills, ...userSkills] });\n });\n\n // Install · POST { md: \"<full Skill.md text>\" }. We parse + validate\n // server-side (caller can send raw file contents from the drop-zone\n // without needing multipart). Caps enforced before insert.\n r.post(\"/:id/skills\", async (c) => {\n const id = c.req.param(\"id\");\n const agent = getAgent(id);\n if (!agent) return c.json({ error: \"not found\" }, 404);\n\n let body: unknown;\n try { body = await c.req.json(); }\n catch { return c.json({ error: \"invalid JSON body\" }, 400); }\n const b = (body ?? {}) as { md?: unknown };\n if (typeof b.md !== \"string\") {\n return c.json({ error: \"missing `md` (string with full Skill.md contents)\" }, 400);\n }\n\n const parsed = parseSkillMd(b.md);\n if (!parsed.ok) return c.json({ error: `invalid skill.md: ${parsed.error}` }, 400);\n\n if (isSystemSkillSlug(parsed.skill.slug)) {\n return c.json({ error: `slug '${parsed.skill.slug}' is reserved for a system skill and cannot be installed` }, 409);\n }\n\n if (getSkillBySlug(id, parsed.skill.slug)) {\n return c.json({ error: `slug '${parsed.skill.slug}' already installed` }, 409);\n }\n\n const cap = agent.roleKind === \"moderator\" ? SKILL_CAP_CHAIR : SKILL_CAP_DIRECTOR;\n const used = countSkillsForAgent(id);\n if (used >= cap) {\n return c.json({ error: `cap reached (${used}/${cap}). Uninstall a skill to make room.` }, 409);\n }\n\n // Auto-analyze ability axes when the user didn't provide them in\n // frontmatter. Manual `ability:` always wins; the analyzer is a\n // best-effort fallback (returns {} on any failure, in which case\n // the skill installs with no deltas — same as before this feature).\n let ability: Record<string, number> = parsed.skill.ability;\n if (!ability || Object.keys(ability).length === 0) {\n try {\n ability = await analyzeSkillAbility({\n name: parsed.skill.name,\n description: parsed.skill.description,\n whenToUse: parsed.skill.whenToUse,\n bodyMd: parsed.skill.bodyMd,\n });\n } catch {\n ability = {};\n }\n }\n\n const skill = insertSkill({\n agentId: id,\n slug: parsed.skill.slug,\n name: parsed.skill.name,\n version: parsed.skill.version,\n description: parsed.skill.description,\n whenToUse: parsed.skill.whenToUse,\n bodyMd: parsed.skill.bodyMd,\n ability,\n tips: parsed.skill.tips,\n });\n return c.json({ skill });\n });\n\n r.delete(\"/:id/skills/:skillId\", (c) => {\n const id = c.req.param(\"id\");\n const skillId = c.req.param(\"skillId\");\n if (!getAgent(id)) return c.json({ error: \"agent not found\" }, 404);\n if (skillId.startsWith(\"system:\")) {\n return c.json({ error: \"system skills cannot be uninstalled\" }, 403);\n }\n const sk = getSkill(skillId);\n if (!sk || sk.agentId !== id) return c.json({ error: \"skill not found\" }, 404);\n const ok = deleteSkill(skillId);\n if (!ok) return c.json({ error: \"delete failed\" }, 500);\n return c.json({ ok: true });\n });\n\n return r;\n}\n","/**\n * Model registry · single source of truth for everything model-related.\n *\n * modelV — our internal stable id ('sonnet-4-6')\n * provider — direct API provider\n * directApiId — the model name as the provider's own SDK expects it\n * openrouterId — the same model addressed via openrouter\n * contextBudget — how many tokens of input we'll give it before trimming\n */\n\nexport type Provider = \"anthropic\" | \"openai\" | \"google\" | \"xai\" | \"deepseek\";\n\nexport type ModelV =\n | \"sonnet-4-6\"\n | \"opus-4-7\"\n | \"haiku-4-5\"\n | \"gpt-5-4\"\n | \"gpt-5-4-mini\"\n | \"gpt-5-5\"\n | \"gpt-5-5-pro\"\n | \"codex-5-4\"\n | \"gemini-3-1\"\n | \"gemini-3-flash\"\n | \"gemini-3-1-flash\"\n | \"grok-4-3\"\n | \"grok-4-1-fast\"\n | \"grok-4-20\"\n | \"deepseek-v4-pro\";\n\nexport interface ModelMeta {\n v: ModelV;\n provider: Provider;\n /** id used when calling the direct provider's SDK */\n directApiId: string;\n /** id used when routing through openrouter */\n openrouterId: string;\n /** human-readable label for UI */\n displayName: string;\n /** soft input cap; trimming kicks in above this */\n contextBudget: number;\n /** rough hint shown next to the model in the picker */\n deck: string;\n /** When true, skip the direct-provider path and always route via\n * OpenRouter — for models that aren't shipped on the provider's\n * own SDK yet (e.g. preview-only releases). */\n openrouterOnly?: boolean;\n}\n\nexport const MODELS: Record<ModelV, ModelMeta> = {\n // ── Anthropic (latest mainstream tier · pinned to OR) ──\n // These IDs may not be on Anthropic's direct SDK yet, so route\n // through OpenRouter to avoid silent direct-API mismatches.\n \"sonnet-4-6\": {\n v: \"sonnet-4-6\",\n provider: \"anthropic\",\n directApiId: \"claude-sonnet-4-6\",\n openrouterId: \"anthropic/claude-sonnet-4.6\",\n displayName: \"Sonnet 4.6\",\n contextBudget: 200_000,\n deck: \"balanced · default\",\n // Direct-routable · Anthropic's primary chair model when the user\n // configures Anthropic key directly (no OpenRouter). The other\n // Claude variants stay openrouterOnly until verified on the\n // direct SDK.\n },\n \"opus-4-7\": {\n v: \"opus-4-7\",\n provider: \"anthropic\",\n directApiId: \"claude-opus-4-7\",\n openrouterId: \"anthropic/claude-opus-4.7\",\n displayName: \"Opus 4.7\",\n contextBudget: 200_000,\n deck: \"deep reasoning\",\n openrouterOnly: true,\n },\n \"haiku-4-5\": {\n v: \"haiku-4-5\",\n provider: \"anthropic\",\n directApiId: \"claude-haiku-4-5\",\n openrouterId: \"anthropic/claude-haiku-4.5\",\n displayName: \"Haiku 4.5\",\n contextBudget: 200_000,\n deck: \"fast · low-cost\",\n openrouterOnly: true,\n },\n // ── OpenAI · current frontier (5.5 / 5.4 / 5.4-mini direct) ──\n // Replaced the legacy gpt-5 / gpt-5-mini / gpt-4o entries — all three\n // are direct-routable on the OpenAI Responses API. See\n // https://developers.openai.com/api/docs/models for the canonical\n // ID strings (`gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini`).\n \"gpt-5-5\": {\n v: \"gpt-5-5\",\n provider: \"openai\",\n directApiId: \"gpt-5.5\",\n openrouterId: \"openai/gpt-5.5\",\n displayName: \"GPT-5.5\",\n contextBudget: 1_000_000,\n deck: \"flagship · 1M ctx\",\n },\n \"gpt-5-4\": {\n v: \"gpt-5-4\",\n provider: \"openai\",\n directApiId: \"gpt-5.4\",\n openrouterId: \"openai/gpt-5.4\",\n displayName: \"GPT-5.4\",\n contextBudget: 1_000_000,\n deck: \"general · 1M ctx\",\n },\n \"gpt-5-4-mini\": {\n v: \"gpt-5-4-mini\",\n provider: \"openai\",\n directApiId: \"gpt-5.4-mini\",\n openrouterId: \"openai/gpt-5.4-mini\",\n displayName: \"GPT-5.4 Mini\",\n contextBudget: 400_000,\n deck: \"fast · 400k ctx\",\n },\n // ── OpenAI · OR-only previews (Pro / Codex) ──\n \"gpt-5-5-pro\": {\n v: \"gpt-5-5-pro\",\n provider: \"openai\",\n directApiId: \"gpt-5.5-pro\",\n openrouterId: \"openai/gpt-5.5-pro\",\n displayName: \"GPT-5.5 Pro\",\n contextBudget: 1_000_000,\n deck: \"deep reasoning · 1M ctx\",\n openrouterOnly: true,\n },\n \"codex-5-4\": {\n v: \"codex-5-4\",\n provider: \"openai\",\n directApiId: \"gpt-5.3-codex\",\n openrouterId: \"openai/gpt-5.3-codex\",\n displayName: \"ChatGPT Codex 5.4\",\n contextBudget: 400_000,\n deck: \"code · agents\",\n openrouterOnly: true,\n },\n // ── Google · current frontier (3.1 Pro / 3 Flash / 3.1 Flash Lite) ──\n // Replaced the legacy gemini-2.5-pro / gemini-2.5-flash entries — all\n // three new IDs are direct-routable on Google's Gemini API. The IDs\n // carry the `-preview` suffix Google uses for not-yet-GA models;\n // confirmed against OpenRouter's catalog (see /v1/models for matches).\n \"gemini-3-1\": {\n v: \"gemini-3-1\",\n provider: \"google\",\n directApiId: \"gemini-3.1-pro-preview\",\n openrouterId: \"google/gemini-3.1-pro-preview\",\n displayName: \"Gemini 3.1 Pro\",\n contextBudget: 1_000_000,\n deck: \"flagship · 1M ctx\",\n },\n \"gemini-3-flash\": {\n v: \"gemini-3-flash\",\n provider: \"google\",\n directApiId: \"gemini-3-flash-preview\",\n openrouterId: \"google/gemini-3-flash-preview\",\n displayName: \"Gemini 3 Flash\",\n contextBudget: 1_000_000,\n deck: \"frontier flash · 1M ctx\",\n },\n \"gemini-3-1-flash\": {\n v: \"gemini-3-1-flash\",\n provider: \"google\",\n directApiId: \"gemini-3.1-flash-lite-preview\",\n openrouterId: \"google/gemini-3.1-flash-lite-preview\",\n displayName: \"Gemini 3.1 Flash Lite\",\n contextBudget: 1_000_000,\n deck: \"fast · 1M ctx\",\n },\n // ── xAI · current frontier (4.3 / 4.1 Fast direct + 4.20 big-ctx OR) ──\n // Replaced the legacy grok-4 / grok-4-mini entries — 4.3 is xAI's\n // current \"most intelligent and fastest\" model per their docs; 4.1\n // Fast is the new cheap tier. 4.20 stays OR-only since it's a 2M-ctx\n // preview that the direct SDK hasn't acknowledged yet.\n \"grok-4-3\": {\n v: \"grok-4-3\",\n provider: \"xai\",\n directApiId: \"grok-4.3\",\n openrouterId: \"x-ai/grok-4.3\",\n displayName: \"Grok 4.3\",\n contextBudget: 1_000_000,\n deck: \"flagship · 1M ctx\",\n },\n \"grok-4-1-fast\": {\n v: \"grok-4-1-fast\",\n provider: \"xai\",\n directApiId: \"grok-4.1-fast\",\n openrouterId: \"x-ai/grok-4.1-fast\",\n displayName: \"Grok 4.1 Fast\",\n contextBudget: 256_000,\n deck: \"fast · 256k ctx\",\n },\n \"grok-4-20\": {\n v: \"grok-4-20\",\n provider: \"xai\",\n directApiId: \"grok-4.20\",\n openrouterId: \"x-ai/grok-4.20\",\n displayName: \"Grok 4.20\",\n contextBudget: 2_000_000,\n deck: \"2M ctx · big context\",\n openrouterOnly: true,\n },\n // ── DeepSeek (OR-only · no @ai-sdk/deepseek shipped) ──\n \"deepseek-v4-pro\": {\n v: \"deepseek-v4-pro\",\n provider: \"deepseek\",\n directApiId: \"deepseek-v4-pro\",\n openrouterId: \"deepseek/deepseek-v4-pro\",\n displayName: \"DeepSeek V4 Pro\",\n contextBudget: 128_000,\n deck: \"reasoning · open weights\",\n openrouterOnly: true,\n },\n};\n\nexport function getModel(v: ModelV): ModelMeta {\n const m = MODELS[v];\n if (!m) throw new Error(`Unknown model: ${v}`);\n return m;\n}\n\nexport function isModelV(v: string): v is ModelV {\n return Object.prototype.hasOwnProperty.call(MODELS, v);\n}\n\nexport function listModels(): ModelMeta[] {\n return Object.values(MODELS);\n}\n","/**\n * Tiny URL-safe ID generator. Roughly nanoid-compatible: 12 chars from a 32-symbol\n * alphabet → ~60 bits of entropy. Good enough for in-process room/message IDs;\n * collisions astronomical.\n */\nimport { randomBytes } from \"node:crypto\";\n\nconst ALPHABET = \"0123456789abcdefghjkmnpqrstvwxyz\"; // skip i, l, o, u for readability\nconst ALPHABET_LEN = ALPHABET.length;\nconst MASK = (1 << 5) - 1; // 5 bits per symbol (32 = 2^5)\n\nexport function newId(len = 12): string {\n // Generate a few extra bytes to absorb the ~6% rejection rate for masked\n // values that fall outside ALPHABET (in our case: none, since 32 fills 5 bits exactly).\n const bytes = randomBytes(len);\n let out = \"\";\n for (let i = 0; i < len; i++) {\n out += ALPHABET[bytes[i]! & MASK];\n }\n return out;\n}\n","/**\n * Agent long-term memory · per-agent notes about the USER that flow\n * across every room the agent participates in. Read on every prompt\n * build via `listMemoriesForAgent`; written at room adjourn by the\n * extraction step in the orchestrator (skipped when room.incognito).\n *\n * Each agent (directors + chair) keeps an independent set so the\n * multi-perspective product stays distinct — Skeptic and User-Empathy\n * accumulate different reads on the same user.\n */\nimport { getDb } from \"./db.js\";\nimport { newId } from \"../utils/id.js\";\n\nexport type MemoryKind = \"fact\" | \"observation\" | \"preference\" | \"goal\";\nexport type MemorySource = \"extracted\" | \"user_added\" | \"user_pinned\";\n\nexport interface AgentMemory {\n id: string;\n agentId: string;\n content: string;\n kind: MemoryKind;\n source: MemorySource;\n /** Room the memory was distilled from. Null for manually-added notes. */\n sourceRoom: string | null;\n confidence: number;\n pinned: boolean;\n createdAt: number;\n updatedAt: number;\n}\n\ninterface Row {\n id: string;\n agent_id: string;\n content: string;\n kind: string;\n source: string;\n source_room: string | null;\n confidence: number;\n pinned: number;\n created_at: number;\n updated_at: number;\n}\n\nconst SELECT_COLS =\n \"id, agent_id, content, kind, source, source_room, confidence, pinned, created_at, updated_at\";\n\nconst ALLOWED_KINDS: ReadonlySet<MemoryKind> = new Set([\"fact\", \"observation\", \"preference\", \"goal\"]);\nconst ALLOWED_SOURCES: ReadonlySet<MemorySource> = new Set([\"extracted\", \"user_added\", \"user_pinned\"]);\n\nfunction mapRow(row: Row): AgentMemory {\n const kind: MemoryKind = ALLOWED_KINDS.has(row.kind as MemoryKind) ? (row.kind as MemoryKind) : \"fact\";\n const source: MemorySource = ALLOWED_SOURCES.has(row.source as MemorySource)\n ? (row.source as MemorySource)\n : \"extracted\";\n return {\n id: row.id,\n agentId: row.agent_id,\n content: row.content,\n kind,\n source,\n sourceRoom: row.source_room,\n confidence: row.confidence,\n pinned: row.pinned === 1,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\n/** All memories for one agent · pinned first, then most-recent. Used by\n * the agent profile to render the Memory tab AND by the prompt builder\n * to inject \"Known about the user\". */\nexport function listMemoriesForAgent(agentId: string): AgentMemory[] {\n const rows = getDb()\n .prepare(\n `SELECT ${SELECT_COLS} FROM agent_memories\n WHERE agent_id = ?\n ORDER BY pinned DESC, created_at DESC`,\n )\n .all(agentId) as Row[];\n return rows.map(mapRow);\n}\n\n/** Top-K context for prompt injection · all pinned + up to `recentCap`\n * most-recent non-pinned. v1 ignores semantic relevance and uses pure\n * recency; embedding-based scoring can replace this later without\n * changing the call site signature. */\nexport function memoriesForContext(agentId: string, recentCap = 5): AgentMemory[] {\n const all = listMemoriesForAgent(agentId);\n const pinned = all.filter((m) => m.pinned);\n const recent = all.filter((m) => !m.pinned).slice(0, recentCap);\n return [...pinned, ...recent];\n}\n\nexport interface MemoryCreate {\n agentId: string;\n content: string;\n kind?: MemoryKind;\n source?: MemorySource;\n sourceRoom?: string | null;\n confidence?: number;\n pinned?: boolean;\n}\n\nexport function insertMemory(input: MemoryCreate): AgentMemory {\n const db = getDb();\n const id = newId();\n const now = Date.now();\n const kind: MemoryKind = input.kind ?? \"fact\";\n const source: MemorySource = input.source ?? \"extracted\";\n const sourceRoom = input.sourceRoom ?? null;\n const confidence = typeof input.confidence === \"number\" ? input.confidence : 0.7;\n const pinned = input.pinned === true ? 1 : 0;\n db.prepare(\n `INSERT INTO agent_memories\n (id, agent_id, content, kind, source, source_room, confidence, pinned, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(id, input.agentId, input.content, kind, source, sourceRoom, confidence, pinned, now, now);\n return getMemory(id)!;\n}\n\nexport function getMemory(id: string): AgentMemory | null {\n const row = getDb()\n .prepare(`SELECT ${SELECT_COLS} FROM agent_memories WHERE id = ?`)\n .get(id) as Row | undefined;\n return row ? mapRow(row) : null;\n}\n\n/** Patch a subset of a memory's fields. Returns the updated row. */\nexport function updateMemory(\n id: string,\n patch: { content?: string; kind?: MemoryKind; pinned?: boolean },\n): AgentMemory | null {\n const fields: string[] = [];\n const values: unknown[] = [];\n if (typeof patch.content === \"string\") {\n fields.push(\"content = ?\");\n values.push(patch.content);\n }\n if (patch.kind && ALLOWED_KINDS.has(patch.kind)) {\n fields.push(\"kind = ?\");\n values.push(patch.kind);\n }\n if (typeof patch.pinned === \"boolean\") {\n fields.push(\"pinned = ?\");\n values.push(patch.pinned ? 1 : 0);\n }\n if (fields.length === 0) return getMemory(id);\n fields.push(\"updated_at = ?\");\n values.push(Date.now());\n values.push(id);\n const r = getDb()\n .prepare(`UPDATE agent_memories SET ${fields.join(\", \")} WHERE id = ?`)\n .run(...values);\n if (r.changes === 0) return null;\n return getMemory(id);\n}\n\nexport function deleteMemory(id: string): boolean {\n const r = getDb().prepare(\"DELETE FROM agent_memories WHERE id = ?\").run(id);\n return r.changes > 0;\n}\n\nexport function isMemoryKind(v: string): v is MemoryKind {\n return ALLOWED_KINDS.has(v as MemoryKind);\n}\n","/**\n * Per-agent skills · uploaded as Skill.md files (YAML frontmatter +\n * markdown body). Read on every prompt build to construct the Pass-1\n * router toolbox; full body is injected into Pass-2 only when the\n * router picks the slug for that turn (Claude Code-style progressive\n * disclosure).\n *\n * v1 is per-agent (no shared library) — each upload installs onto a\n * single agent. Slug uniqueness is enforced per agent.\n */\nimport { getDb } from \"./db.js\";\nimport { newId } from \"../utils/id.js\";\n\n/** Optional runtime state attached to a system skill so the agent\n * profile can render the right control (toggle vs. configure-link)\n * without making a second roundtrip. Today only `web-search` uses\n * this — toggle gated by Brave key + per-agent flag. */\nexport interface AgentSkillState {\n /** Per-agent on/off flag for skills with a toggle (web-search). */\n enabled?: boolean;\n /** True when the global service this skill depends on is configured.\n * When false, the toggle should render as \"Configure key\" pointing\n * at User Settings instead of an interactive switch. */\n keyConfigured?: boolean;\n /** When set, points the UI at which Preferences row to surface for\n * configuring the dependency. */\n requiresKey?: { provider: string; label: string };\n}\n\nexport interface AgentSkill {\n id: string;\n agentId: string;\n slug: string;\n name: string;\n version: string;\n description: string;\n whenToUse: string;\n bodyMd: string;\n /** Axis → integer delta. Axes are boardroom-specific (see PRD). */\n ability: Record<string, number>;\n tips: string[];\n createdAt: number;\n updatedAt: number;\n /** True for hardcoded system skills (e.g. the chair's report writer).\n * System skills aren't stored in the DB — they're synthesized at read\n * time and cannot be deleted or edited. */\n system?: boolean;\n /** Runtime state for system skills with optional gates / toggles. */\n state?: AgentSkillState;\n}\n\ninterface Row {\n id: string;\n agent_id: string;\n slug: string;\n name: string;\n version: string;\n description: string;\n when_to_use: string;\n body_md: string;\n ability_json: string;\n tips_json: string;\n created_at: number;\n updated_at: number;\n}\n\nconst SELECT_COLS =\n \"id, agent_id, slug, name, version, description, when_to_use, body_md, ability_json, tips_json, created_at, updated_at\";\n\nfunction safeParseObject(s: string): Record<string, number> {\n try {\n const v = JSON.parse(s);\n if (v && typeof v === \"object\" && !Array.isArray(v)) {\n const out: Record<string, number> = {};\n for (const [k, val] of Object.entries(v)) {\n if (typeof val === \"number\" && Number.isFinite(val)) out[k] = val;\n }\n return out;\n }\n } catch { /* fall through */ }\n return {};\n}\n\nfunction safeParseStringArray(s: string): string[] {\n try {\n const v = JSON.parse(s);\n if (Array.isArray(v)) return v.filter((x) => typeof x === \"string\");\n } catch { /* fall through */ }\n return [];\n}\n\nfunction mapRow(row: Row): AgentSkill {\n return {\n id: row.id,\n agentId: row.agent_id,\n slug: row.slug,\n name: row.name,\n version: row.version,\n description: row.description,\n whenToUse: row.when_to_use,\n bodyMd: row.body_md,\n ability: safeParseObject(row.ability_json),\n tips: safeParseStringArray(row.tips_json),\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\n/** All skills installed on an agent · most-recent first. Used by the\n * agent profile to render the Skills block AND by the orchestrator\n * to build the Pass-1 router toolbox. */\nexport function listSkillsForAgent(agentId: string): AgentSkill[] {\n const rows = getDb()\n .prepare(\n `SELECT ${SELECT_COLS} FROM agent_skills\n WHERE agent_id = ?\n ORDER BY created_at DESC`,\n )\n .all(agentId) as Row[];\n return rows.map(mapRow);\n}\n\nexport function getSkill(id: string): AgentSkill | null {\n const row = getDb()\n .prepare(`SELECT ${SELECT_COLS} FROM agent_skills WHERE id = ?`)\n .get(id) as Row | undefined;\n return row ? mapRow(row) : null;\n}\n\n/** Lookup by (agent, slug). Used to detect duplicates before insert. */\nexport function getSkillBySlug(agentId: string, slug: string): AgentSkill | null {\n const row = getDb()\n .prepare(`SELECT ${SELECT_COLS} FROM agent_skills WHERE agent_id = ? AND slug = ?`)\n .get(agentId, slug) as Row | undefined;\n return row ? mapRow(row) : null;\n}\n\nexport function countSkillsForAgent(agentId: string): number {\n const r = getDb()\n .prepare(\"SELECT COUNT(*) AS c FROM agent_skills WHERE agent_id = ?\")\n .get(agentId) as { c: number };\n return r?.c ?? 0;\n}\n\nexport interface SkillCreate {\n agentId: string;\n slug: string;\n name: string;\n version?: string;\n description: string;\n whenToUse: string;\n bodyMd: string;\n ability?: Record<string, number>;\n tips?: string[];\n}\n\nexport function insertSkill(input: SkillCreate): AgentSkill {\n const db = getDb();\n const id = newId();\n const now = Date.now();\n db.prepare(\n `INSERT INTO agent_skills\n (id, agent_id, slug, name, version, description, when_to_use, body_md,\n ability_json, tips_json, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n id,\n input.agentId,\n input.slug,\n input.name,\n input.version ?? \"1.0\",\n input.description,\n input.whenToUse,\n input.bodyMd,\n JSON.stringify(input.ability ?? {}),\n JSON.stringify(input.tips ?? []),\n now,\n now,\n );\n return getSkill(id)!;\n}\n\nexport function deleteSkill(id: string): boolean {\n const r = getDb().prepare(\"DELETE FROM agent_skills WHERE id = ?\").run(id);\n return r.changes > 0;\n}\n","/**\n * Parse + validate an uploaded Skill.md file.\n *\n * Format · YAML frontmatter delimited by `---` lines, followed by free\n * markdown body:\n *\n * ---\n * name: First-Principles Reasoning\n * slug: first-principles\n * version: 1.0\n * description: Strips problems to physical primitives.\n * when_to_use: When the question hides behind jargon.\n * ability:\n * rigor: 2\n * depth: 3\n * speed: -1\n * tips:\n * - \"Best with concrete problems.\"\n * ---\n *\n * # Body\n * ...\n *\n * Validation rules and limits live in PRD-skills.md §1.\n */\nimport { parse as parseYaml } from \"yaml\";\n\nimport { ABILITY_AXES, type AbilityAxis } from \"./axes.js\";\n\nexport interface ParsedSkill {\n name: string;\n slug: string;\n version: string;\n description: string;\n whenToUse: string;\n ability: Record<AbilityAxis, number>;\n tips: string[];\n bodyMd: string;\n}\n\nexport interface ParseError {\n ok: false;\n error: string;\n}\n\nexport interface ParseSuccess {\n ok: true;\n skill: ParsedSkill;\n}\n\nexport type ParseResult = ParseSuccess | ParseError;\n\nconst NAME_MAX = 80;\nconst SLUG_MAX = 64;\n// Description is what the Pass-1 router (and Claude Code-style skill\n// inventory) reads to decide whether to invoke the skill — so it\n// commonly runs several paragraphs in real-world skills. 4KB is a\n// comfortable upper bound that still protects against accidents.\nconst DESC_MAX = 4 * 1024;\nconst WHEN_MAX = 2 * 1024;\nconst TIP_MAX = 500;\nconst TIPS_MAX_COUNT = 8;\n// Body is the full instruction text injected into Pass-2's system\n// prompt. 32KB is enough for a multi-page skill document while still\n// catching pathological uploads.\nconst BODY_MAX_BYTES = 32 * 1024;\nconst ABILITY_MIN = -3;\nconst ABILITY_MAX = 3;\n\nconst SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;\n\n/** Convert a freeform display name into a kebab-case slug. Mirrors the\n * Claude Code convention where `name` is both the identifier and the\n * display label — when it's already slug-shaped (e.g. \"first-principles\")\n * we keep it; when it's prose (e.g. \"First Principles Reasoning\") we\n * lowercase + collapse non-alphanumerics to hyphens. */\nfunction slugifyName(name: string): string {\n return name\n .toLowerCase()\n .normalize(\"NFKD\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, SLUG_MAX);\n}\n\nfunction err(msg: string): ParseError {\n return { ok: false, error: msg };\n}\n\n/** Split the source into [frontmatter, body]. Both can be empty\n * strings — caller validates required fields. */\nfunction splitFrontmatter(src: string): { fm: string; body: string } | null {\n // Allow optional UTF-8 BOM and leading whitespace before the first `---`.\n const trimmed = src.replace(/^/, \"\");\n const m = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/.exec(trimmed);\n if (!m) return null;\n return { fm: m[1] ?? \"\", body: m[2] ?? \"\" };\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === \"object\" && !Array.isArray(v);\n}\n\nexport function parseSkillMd(src: string): ParseResult {\n if (typeof src !== \"string\" || src.trim().length === 0) {\n return err(\"file is empty\");\n }\n if (Buffer.byteLength(src, \"utf8\") > 128 * 1024) {\n return err(\"file too large (max 128 KB)\");\n }\n const split = splitFrontmatter(src);\n if (!split) {\n return err(\"missing YAML frontmatter delimited by '---' lines\");\n }\n const { fm, body } = split;\n\n let raw: unknown;\n try {\n raw = parseYaml(fm);\n } catch (e) {\n return err(`invalid YAML frontmatter: ${e instanceof Error ? e.message : String(e)}`);\n }\n if (!isPlainObject(raw)) {\n return err(\"frontmatter must be a YAML mapping (key: value pairs)\");\n }\n\n // ── name ──────────────────────────────────────────\n const name = raw.name;\n if (typeof name !== \"string\" || name.trim().length === 0) {\n return err(\"`name` is required (string)\");\n }\n const nameTrim = name.trim();\n if (nameTrim.length > NAME_MAX) return err(`\\`name\\` too long (max ${NAME_MAX})`);\n\n // ── slug ──────────────────────────────────────────\n // Slug is optional · if missing we derive it from `name` (Claude Code\n // convention — `name` is both display and identifier). When provided,\n // it must be kebab-case.\n let slug: string;\n if (raw.slug === undefined || raw.slug === null) {\n slug = slugifyName(nameTrim);\n if (!slug) return err(\"couldn't derive slug from `name` — provide a `slug:` explicitly (lowercase letters, digits, hyphens)\");\n } else {\n if (typeof raw.slug !== \"string\" || raw.slug.trim().length === 0) {\n return err(\"`slug` must be a non-empty string when provided\");\n }\n slug = raw.slug.trim();\n if (slug.length > SLUG_MAX) return err(`\\`slug\\` too long (max ${SLUG_MAX})`);\n if (!SLUG_RE.test(slug)) {\n return err(\"`slug` must be lowercase letters, digits, and hyphens (start with letter/digit)\");\n }\n }\n\n // ── description ──────────────────────────────────\n const description = raw.description;\n if (typeof description !== \"string\" || description.trim().length === 0) {\n return err(\"`description` is required (string)\");\n }\n const descriptionTrim = description.trim();\n if (descriptionTrim.length > DESC_MAX) return err(`\\`description\\` too long (max ${DESC_MAX})`);\n\n // ── when_to_use ──────────────────────────────────\n // Optional · falls back to `description` when omitted (Claude Code\n // skills typically use `description` for both purposes).\n let whenToUse: string;\n if (raw.when_to_use === undefined || raw.when_to_use === null) {\n whenToUse = descriptionTrim;\n } else {\n if (typeof raw.when_to_use !== \"string\" || raw.when_to_use.trim().length === 0) {\n return err(\"`when_to_use` must be a non-empty string when provided\");\n }\n whenToUse = raw.when_to_use.trim();\n if (whenToUse.length > WHEN_MAX) return err(`\\`when_to_use\\` too long (max ${WHEN_MAX})`);\n }\n\n // ── version (optional) ───────────────────────────\n const version = raw.version === undefined ? \"1.0\" : String(raw.version).trim();\n if (version.length === 0) return err(\"`version` cannot be empty if provided\");\n\n // ── ability (optional) ───────────────────────────\n const ability: Record<AbilityAxis, number> = {} as Record<AbilityAxis, number>;\n if (raw.ability !== undefined) {\n if (!isPlainObject(raw.ability)) {\n return err(\"`ability` must be a mapping of axis → integer delta\");\n }\n for (const [k, v] of Object.entries(raw.ability)) {\n if (!ABILITY_AXES.includes(k as AbilityAxis)) {\n return err(`unknown ability axis '${k}'. Allowed: ${ABILITY_AXES.join(\", \")}`);\n }\n if (typeof v !== \"number\" || !Number.isInteger(v)) {\n return err(`ability '${k}' must be an integer (got ${JSON.stringify(v)})`);\n }\n if (v < ABILITY_MIN || v > ABILITY_MAX) {\n return err(`ability '${k}' out of range [${ABILITY_MIN}, ${ABILITY_MAX}] (got ${v})`);\n }\n ability[k as AbilityAxis] = v;\n }\n }\n\n // ── tips (optional) ──────────────────────────────\n const tips: string[] = [];\n if (raw.tips !== undefined) {\n if (!Array.isArray(raw.tips)) {\n return err(\"`tips` must be an array of strings\");\n }\n if (raw.tips.length > TIPS_MAX_COUNT) {\n return err(`too many tips (max ${TIPS_MAX_COUNT})`);\n }\n for (const t of raw.tips) {\n if (typeof t !== \"string\") return err(\"each tip must be a string\");\n const trimmed = t.trim();\n if (trimmed.length === 0) continue;\n if (trimmed.length > TIP_MAX) return err(`tip too long (max ${TIP_MAX})`);\n tips.push(trimmed);\n }\n }\n\n // ── body ─────────────────────────────────────────\n const bodyTrimmed = body.replace(/\\s+$/g, \"\").replace(/^\\s*\\n/, \"\");\n if (Buffer.byteLength(bodyTrimmed, \"utf8\") > BODY_MAX_BYTES) {\n return err(`body too long (max ${BODY_MAX_BYTES} bytes)`);\n }\n\n return {\n ok: true,\n skill: {\n name: nameTrim,\n slug,\n version,\n description: descriptionTrim,\n whenToUse,\n ability,\n tips,\n bodyMd: bodyTrimmed,\n },\n };\n}\n","/**\n * Boardroom-specific ability axes for the agent radar chart.\n * Fixed set so the radar can compare across agents and so skill\n * uploads can validate axis names at parse time.\n *\n * Each axis ranges 0..10 in display. Skill deltas are integers\n * in [-3, 3]; the rendered value is base + sum(deltas), clamped.\n */\nexport const ABILITY_AXES = [\n \"dissent\",\n \"pattern_recall\",\n \"rigor\",\n \"empathy\",\n \"narrative\",\n \"decisiveness\",\n] as const;\n\nexport type AbilityAxis = (typeof ABILITY_AXES)[number];\n\n/** Default base profile for agents without an explicit one set. */\nexport const DEFAULT_BASE_ABILITY: Record<AbilityAxis, number> = {\n dissent: 5,\n pattern_recall: 5,\n rigor: 5,\n empathy: 5,\n narrative: 5,\n decisiveness: 5,\n};\n\nexport const ABILITY_DISPLAY_MAX = 10;\nexport const ABILITY_DISPLAY_MIN = 0;\n","/**\n * LLM adapter · single typed surface for the rest of the codebase.\n *\n * callLLMStream(req) → AsyncGenerator<LLMStreamChunk>\n *\n * Resolves modelV → direct provider key (preferred) → openrouter (fallback)\n * → throws NoKeyError. Wraps Vercel AI SDK's streamText, but the rest of\n * the app only sees our typed yields and never imports `ai` directly.\n */\nimport { createAnthropic } from \"@ai-sdk/anthropic\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\nimport { createOpenAI } from \"@ai-sdk/openai\";\nimport { createOpenAICompatible } from \"@ai-sdk/openai-compatible\";\nimport { APICallError, streamText, type LanguageModel, type ProviderMetadata } from \"ai\";\n\nimport { getKey } from \"../storage/keys.js\";\n\nimport { getModel, type ModelMeta, type ModelV, type Provider } from \"./registry.js\";\n\nexport type LLMRole = \"system\" | \"user\" | \"assistant\";\n\nexport interface LLMMessage {\n role: LLMRole;\n content: string;\n}\n\n/** Carrier override for a single LLM call. Same shape / values as\n * AgentCarrierPref in storage/agents.ts (kept structurally compatible\n * rather than re-importing to avoid the storage→ai dep). When set,\n * resolveModel routes via that carrier instead of the default\n * precedence rules. NULL/undefined preserves the historical behavior. */\nexport type RequestCarrier = \"openrouter\" | \"anthropic\" | \"openai\" | \"google\" | \"xai\";\n\nexport interface LLMRequest {\n modelV: ModelV;\n messages: LLMMessage[];\n temperature?: number;\n maxTokens?: number;\n signal?: AbortSignal;\n /** Optional carrier override (per-agent or per-call). Falls back to\n * the default precedence when null/undefined or when the requested\n * carrier has no usable key. */\n carrier?: RequestCarrier | null;\n}\n\nexport type LLMStreamChunk =\n | { type: \"text\"; delta: string }\n | { type: \"served\"; modelId: string }\n | { type: \"usage\"; promptTokens: number; completionTokens: number; totalTokens: number }\n | { type: \"done\"; finishReason?: string }\n | { type: \"error\"; message: string };\n\nexport class NoKeyError extends Error {\n constructor(public provider: Provider) {\n super(\n `No key configured for \"${provider}\", and no OpenRouter fallback is set. ` +\n `Add a key in Preference → API Key.`,\n );\n this.name = \"NoKeyError\";\n }\n}\n\nconst OPENROUTER_BASE = \"https://openrouter.ai/api/v1\";\n\n/** Header names whose values must NOT print verbatim — they carry the\n * user's API key. Different providers use different header names; the\n * list below covers the four we currently call:\n * Authorization · OpenAI / OpenRouter / xAI (Bearer scheme)\n * x-api-key · Anthropic\n * x-goog-api-key · Google Gemini direct\n * api-key · misc (Azure-style)\n * Match is case-insensitive. Value is replaced with a short fingerprint\n * (`****<last 4>`) so the user can still tell two keys apart in logs\n * without leaking the secret. */\nconst SENSITIVE_HEADER_NAMES = new Set([\n \"authorization\",\n \"x-api-key\",\n \"x-goog-api-key\",\n \"api-key\",\n]);\n\nfunction redactHeaderValue(name: string, value: string): string {\n if (!SENSITIVE_HEADER_NAMES.has(name.toLowerCase())) return value;\n // Strip \"Bearer \" prefix when present so the fingerprint shows the\n // key tail rather than the scheme tail.\n const v = value.replace(/^Bearer\\s+/i, \"\");\n const tail = v.slice(-4);\n return tail ? `****${tail}` : \"****\";\n}\n\n/**\n * fetch wrapper that prints every request/response to stderr — lets the\n * developer console verify exactly what `model`, `temperature`, etc. went\n * on the wire when debugging \"selected X but got Y\" or \"no tokens\" bugs.\n * Sensitive headers (Authorization / x-api-key / x-goog-api-key /\n * api-key) are redacted to a `****<last 4>` fingerprint so two keys can\n * still be told apart in logs without leaking secrets. SSE response\n * bodies are tee'd into stderr as they arrive — every chunk gets\n * forwarded through unchanged.\n */\nfunction makeLoggedFetch(tag: string): typeof fetch {\n return function loggedFetch(input, init) {\n const url = typeof input === \"string\" || input instanceof URL ? String(input) : input.url;\n const method = (init?.method ?? (input instanceof Request ? input.method : \"GET\")).toUpperCase();\n const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined));\n\n const headerLines: string[] = [];\n headers.forEach((v, k) => {\n headerLines.push(` ${k}: ${redactHeaderValue(k, v)}`);\n });\n\n let bodyPretty = \"\";\n if (init?.body && typeof init.body === \"string\") {\n try {\n bodyPretty = JSON.stringify(JSON.parse(init.body), null, 2);\n } catch {\n bodyPretty = init.body.length > 2000 ? init.body.slice(0, 2000) + \"…\" : init.body;\n }\n }\n\n const sep = \"─\".repeat(60);\n // Request log · explicit \"headers:\" / \"body:\" sub-labels make the\n // sections legible at a glance. Empty headers (which happens when\n // an SDK builds the request in a way Headers.forEach doesn't see)\n // get an explicit \"(no headers)\" line rather than going silent.\n const headerBlock = headerLines.length > 0\n ? `│ headers:\\n` + headerLines.map((l) => `│ ${l}`).join(\"\\n\")\n : `│ headers: (none observed at fetch layer)`;\n const bodyBlock = bodyPretty\n ? `│\\n│ body:\\n` + bodyPretty.split(\"\\n\").map((l) => `│ ${l}`).join(\"\\n\")\n : `│\\n│ body: (empty)`;\n process.stderr.write(\n `\\n┌${sep}\\n│ [${tag} →] ${method} ${url}\\n` +\n headerBlock +\n `\\n` +\n bodyBlock +\n `\\n└${sep}\\n`,\n );\n\n const t0 = Date.now();\n return fetch(input, init).then(async (res) => {\n const ms = Date.now() - t0;\n const resHeaderLines: string[] = [];\n res.headers.forEach((v, k) => resHeaderLines.push(` ${k}: ${v}`));\n const ct = res.headers.get(\"content-type\") ?? \"\";\n const isStream = /event-stream/i.test(ct);\n\n // Response logging policy · headers + status always print so the\n // user can confirm the request landed at the right endpoint with\n // the right model. Body printing is selective:\n // · non-streaming · pretty-print (typical 4xx error JSON shape).\n // · streaming · SKIP. Tee'ing every SSE chunk made the logs\n // unreadable (each call dumped hundreds of `text-delta` lines).\n // The request body already shows what we sent; the visible\n // stream output ends up in the chat itself; that's enough\n // for verification. If we ever need raw stream forensics,\n // re-enable behind a debug flag.\n let resBodyPretty = \"\";\n if (!isStream) {\n try {\n const text = await res.clone().text();\n if (text) {\n try {\n resBodyPretty = JSON.stringify(JSON.parse(text), null, 2);\n } catch {\n resBodyPretty = text.length > 2000 ? text.slice(0, 2000) + \"…\" : text;\n }\n }\n } catch {\n // best-effort log — never let logging break the call\n }\n }\n const resHeaderBlock = resHeaderLines.length > 0\n ? `│ headers:\\n` + resHeaderLines.map((l) => `│ ${l}`).join(\"\\n\")\n : `│ headers: (none)`;\n const resBodyBlock = isStream\n ? `│\\n│ body: (text/event-stream · stream content not logged)`\n : resBodyPretty\n ? `│\\n│ body:\\n` + resBodyPretty.split(\"\\n\").map((l) => `│ ${l}`).join(\"\\n\")\n : `│\\n│ body: (empty)`;\n process.stderr.write(\n `\\n┌${sep}\\n│ [${tag} ←] ${res.status} · ${ms}ms\\n` +\n resHeaderBlock +\n `\\n` +\n resBodyBlock +\n `\\n└${sep}\\n`,\n );\n\n // Stream body is forwarded unchanged · we no longer tee it. The\n // SDK's own consumer reads it and emits `text-delta` parts that\n // surface to the orchestrator, which is the better seam for any\n // stream-shape diagnostics.\n return res;\n });\n };\n}\n\nconst loggedFetch = makeLoggedFetch(\"openrouter\");\n\n/**\n * Format an upstream error so the verify modal (and any other caller) sees\n * the actual provider response — not just \"HTTP 400\". OpenRouter's body\n * carries the real diagnostic (\"model not found\", \"no allowed providers\",\n * quota messages, …); without surfacing it the user just sees a status\n * code and has to dig through the OR Activity tab.\n */\nfunction formatStreamError(e: unknown): string {\n if (APICallError.isInstance(e)) {\n const parts: string[] = [];\n if (e.statusCode != null) parts.push(`HTTP ${e.statusCode}`);\n const data = e.data as\n | { error?: { message?: string; type?: string; code?: string | number } }\n | undefined;\n const inner = data?.error;\n if (inner?.message) {\n parts.push(inner.message);\n const meta = [\n inner.type,\n inner.code != null ? `code=${inner.code}` : null,\n ]\n .filter(Boolean)\n .join(\" · \");\n if (meta) parts.push(`(${meta})`);\n } else if (e.responseBody) {\n // Cap raw body to keep an HTML error page from blowing up the modal.\n const trimmed =\n e.responseBody.length > 800\n ? e.responseBody.slice(0, 800) + \"…\"\n : e.responseBody;\n parts.push(trimmed);\n } else {\n parts.push(e.message);\n }\n return parts.join(\"\\n\");\n }\n if (e instanceof Error) return e.message;\n // Plain-object error shape · this is what the Responses API SSE\n // `error` event becomes after the SDK parses it (a value, not an\n // exception). Looks like:\n // { type: \"error\", error: { type, code, message, param }, sequence_number }\n // Pull `error.message` first; fall back to `message` at the top level\n // so we never silently devolve to `String(obj) = \"[object Object]\"`.\n if (e && typeof e === \"object\") {\n const obj = e as {\n error?: { message?: string; type?: string; code?: string | number };\n message?: string;\n type?: string;\n code?: string | number;\n };\n const inner = obj.error;\n if (inner?.message) {\n const meta = [inner.type, inner.code != null ? `code=${inner.code}` : null]\n .filter(Boolean)\n .join(\" · \");\n return meta ? `${inner.message} (${meta})` : inner.message;\n }\n if (obj.message) {\n const meta = [obj.type, obj.code != null ? `code=${obj.code}` : null]\n .filter(Boolean)\n .join(\" · \");\n return meta ? `${obj.message} (${meta})` : obj.message;\n }\n try {\n return JSON.stringify(e);\n } catch {\n /* fall through */\n }\n }\n return String(e);\n}\n\ninterface ResolvedModel {\n model: LanguageModel;\n providerOptions?: ProviderMetadata;\n}\n\n/** Resolve which model + which credentials to use right now.\n *\n * Routing precedence:\n * 0. EXPLICIT carrier override (caller passed `carrier` on the\n * request, typically from agent.carrierPref) → if reachable,\n * use it directly. Skips precedence rules below — the user\n * picked this carrier on purpose. Unreachable preference\n * (carrier missing key, or model not on that carrier) falls\n * through to default routing rather than hard-failing.\n * 1. `openrouterOnly` AND OpenRouter key → OpenRouter (preview models\n * that aren't on the direct SDK live here).\n * 2. Direct provider key → direct SDK (the normal path).\n * 3. OpenRouter key (any other model) → OpenRouter as carrier.\n * 4. `openrouterOnly` model with NO OR key BUT direct provider key\n * → attempt direct anyway. The flag is best-treated as \"prefer\n * OpenRouter\" rather than \"exclusively OpenRouter\" — most direct\n * SDKs eventually catch up to model IDs and the user shouldn't\n * silently fail because we didn't update a flag. If the direct\n * call rejects (model unknown), the LLM error surfaces and the\n * user can swap models — better than the previous silent\n * NoKeyError that hid the real issue.\n * 5. No keys at all → NoKeyError. */\nfunction resolveModel(modelV: ModelV, carrier?: RequestCarrier | null): ResolvedModel {\n const meta = getModel(modelV);\n const orKey = getKey(\"openrouter\");\n const directKey = getKey(meta.provider);\n\n // Preference 0 · explicit carrier override.\n if (carrier === \"openrouter\" && orKey) {\n process.stderr.write(`[adapter] modelV=${modelV} → openrouter:${meta.openrouterId} (pinned)\\n`);\n return openRouterResolved(meta, orKey);\n }\n if (carrier && carrier !== \"openrouter\" && carrier === meta.provider) {\n const pinnedKey = getKey(carrier);\n if (pinnedKey) {\n process.stderr.write(`[adapter] modelV=${modelV} → direct:${meta.provider}/${meta.directApiId} (pinned)\\n`);\n return directResolved(meta, pinnedKey);\n }\n }\n // Override requested but unreachable · log + fall through. Keeps the\n // call live rather than failing on a stale agent preference.\n if (carrier) {\n process.stderr.write(\n `[adapter] modelV=${modelV} pinned carrier=${carrier} unreachable; falling back to default routing\\n`,\n );\n }\n\n // Preference 1: openrouterOnly + OR key → OpenRouter.\n if (meta.openrouterOnly && orKey) {\n process.stderr.write(`[adapter] modelV=${modelV} → openrouter:${meta.openrouterId} (preferred)\\n`);\n return openRouterResolved(meta, orKey);\n }\n\n // Preference 2: direct key (most models).\n if (directKey && !meta.openrouterOnly) {\n process.stderr.write(`[adapter] modelV=${modelV} → direct:${meta.provider}/${meta.directApiId}\\n`);\n return directResolved(meta, directKey);\n }\n\n // Preference 3: any other model · OR carrier when OR key exists.\n if (orKey) {\n process.stderr.write(`[adapter] modelV=${modelV} → openrouter:${meta.openrouterId}\\n`);\n return openRouterResolved(meta, orKey);\n }\n\n // Preference 4: openrouterOnly model + direct key only · attempt\n // direct anyway. The flag is conservative; many direct SDKs ship\n // the model ID later. We log so the user can see in stderr which\n // path the call took.\n if (meta.openrouterOnly && directKey) {\n process.stderr.write(`[adapter] modelV=${modelV} → direct:${meta.provider}/${meta.directApiId} (openrouterOnly fallback · no OR key)\\n`);\n return directResolved(meta, directKey);\n }\n\n // Preference 5: no usable keys.\n throw new NoKeyError(meta.provider);\n}\n\nfunction directResolved(meta: ModelMeta, apiKey: string): ResolvedModel {\n switch (meta.provider) {\n case \"anthropic\":\n // fetch: makeLoggedFetch(\"anthropic\") · uniform request/response\n // logging across every direct provider (matches openai / google /\n // xai / openrouter). Auth header (`x-api-key`) is redacted by\n // SENSITIVE_HEADER_NAMES in the logger.\n return {\n model: createAnthropic({ apiKey, fetch: makeLoggedFetch(\"anthropic\") })(meta.directApiId),\n };\n case \"openai\":\n // Route OpenAI direct calls through the Responses API\n // (`/v1/responses`), not the legacy Chat Completions endpoint.\n // Per OpenAI's own docs, GPT-5 and o-series reasoning models\n // \"perform better and demonstrate higher intelligence when used\n // with the Responses API\". The SDK adapts our messages[] shape\n // into the Responses `input` shape internally — caller surface\n // is unchanged. OpenRouter (OpenAI-compatible chat-completions\n // protocol) is unaffected; this only switches the direct path.\n //\n // reasoningEffort: \"none\" · the GPT-5.x family defaults to\n // `reasoning.effort = \"medium\"`, which spends a sizable chunk of\n // `max_output_tokens` on internal reasoning before emitting any\n // visible text. With our picker calls capped at 100-320 tokens\n // and director turns at 4000, a medium-effort reasoning trace\n // routinely consumes the entire budget — finishing with zero\n // output_text and an empty placeholder that gets dropped. We\n // want chat-style responses, not extended reasoning, so pin\n // effort to \"none\" across all OpenAI direct calls. Increase\n // per-callsite if a specific surface needs deeper thinking.\n //\n // Vocabulary note · GPT-5.5 / 5.4 / 5.4-mini accept\n // `none | low | medium | high | xhigh`. The legacy `minimal`\n // value (which the older `gpt-5` accepted) is rejected here —\n // hence \"none\". If we ever bring back a model on the old\n // vocabulary, swap per-model based on a registry flag rather\n // than per-call.\n //\n // fetch: makeLoggedFetch(\"openai\") · same SSE-teeing logger as\n // the OpenRouter path. Direct OpenAI was previously a black box;\n // we now print every request body + response chunks to stderr\n // for diagnosability (\"no tokens\" / \"wrong model\" / 4xx errors).\n return {\n model: createOpenAI({ apiKey, fetch: makeLoggedFetch(\"openai\") }).responses(meta.directApiId),\n providerOptions: {\n openai: { reasoningEffort: \"none\" },\n },\n };\n case \"google\":\n // Gemini 2.5+ ships as a reasoning model (built-in \"thinking\"\n // mode). Like GPT-5's reasoning.effort, the thinking trace\n // consumes the same `maxOutputTokens` budget the visible reply\n // draws from — at default settings the model burns ~1-3k tokens\n // on internal thoughts before any text-delta emerges. Picker\n // calls capped at 100-320 tokens get truncated mid-thought\n // (zero visible output), and director turns at 4000 produce\n // a few words before hitting the cap.\n //\n // thinkingBudget: 0 disables thinking entirely (chat-style\n // fast responses). Per the SDK schema this is the documented\n // way to opt out — passing it as `providerOptions.google\n // .thinkingConfig.thinkingBudget`. Bump per-callsite if a\n // surface needs reasoning.\n //\n // fetch: makeLoggedFetch(\"google\") · same SSE-teeing logger\n // as OpenAI / OpenRouter — every request body + stream chunk\n // hits stderr for \"no tokens\" / \"wrong model\" diagnostics.\n return {\n model: createGoogleGenerativeAI({ apiKey, fetch: makeLoggedFetch(\"google\") })(meta.directApiId),\n providerOptions: {\n google: { thinkingConfig: { thinkingBudget: 0 } },\n },\n };\n case \"xai\": {\n // xAI's current frontier (Grok 4.x) is served on the Responses\n // API at https://api.x.ai/v1/responses — the SAME shape as\n // OpenAI's Responses API (model + input + output_text deltas),\n // just a different host. The legacy `@ai-sdk/xai` package only\n // hits `/v1/chat/completions`, so direct Grok 4.x calls were\n // failing with model-not-found / 4xx. We bypass the xAI SDK and\n // reuse `@ai-sdk/openai`'s Responses client with xAI's baseURL.\n //\n // No reasoningEffort here · xAI's reasoning is toggled via a\n // model-ID suffix (e.g. `grok-4.3-reasoning`) per their docs,\n // not via the OpenAI-style `reasoning.effort` parameter. Setting\n // it on xAI calls would either be ignored or rejected. Pick\n // reasoning vs non-reasoning by editing the registry's\n // `directApiId` if a particular model needs it.\n //\n // fetch: makeLoggedFetch(\"xai\") · uniform request/response\n // logging across every direct provider.\n return {\n model: createOpenAI({\n apiKey,\n baseURL: \"https://api.x.ai/v1\",\n fetch: makeLoggedFetch(\"xai\"),\n }).responses(meta.directApiId),\n };\n }\n default:\n throw new NoKeyError(meta.provider);\n }\n}\n\nfunction openRouterResolved(meta: ModelMeta, apiKey: string): ResolvedModel {\n const compat = createOpenAICompatible({\n name: \"openrouter\",\n apiKey,\n baseURL: OPENROUTER_BASE,\n fetch: loggedFetch,\n headers: {\n \"HTTP-Referer\": \"https://boardroom.local\",\n \"X-OpenRouter-Title\": \"Boardroom\",\n },\n });\n return {\n model: compat.chatModel(meta.openrouterId),\n // Pin OpenRouter to the exact requested model id. Without this,\n // OR silently substitutes a different model when the requested\n // id is unavailable on the user's account / region — which is\n // why \"select Opus 4.7, get back Sonnet\" happens. The openai-\n // compatible adapter spreads providerMetadata[\"openrouter\"]\n // into the request body, so this becomes `provider.allow_fallbacks:\n // false` on the wire (OpenRouter's documented opt-out).\n providerOptions: {\n openrouter: {\n provider: { allow_fallbacks: false },\n },\n },\n };\n}\n\n/**\n * Stream a single LLM response. Yields chunked text, then a final 'done' or\n * 'error' chunk. Generator is async-iterable in callers (orchestrator, brief\n * writer).\n */\nexport async function* callLLMStream(req: LLMRequest): AsyncGenerator<LLMStreamChunk> {\n let resolved: ResolvedModel;\n try {\n resolved = resolveModel(req.modelV, req.carrier ?? null);\n } catch (e) {\n yield { type: \"error\", message: formatStreamError(e) };\n return;\n }\n\n const result = streamText({\n model: resolved.model,\n providerOptions: resolved.providerOptions,\n messages: req.messages,\n temperature: req.temperature,\n // Vercel SDK names this maxOutputTokens in v4+; tolerate both.\n maxTokens: req.maxTokens,\n abortSignal: req.signal,\n });\n\n // Drain `fullStream` (NOT `textStream`) · streamText's textStream\n // silently filters out `error` parts (only text-delta passes through),\n // so an upstream SSE `error` event — e.g. OpenAI's `insufficient_quota`\n // mid-stream rejection — would end the iterator without throwing AND\n // without yielding anything visible. We'd see \"no tokens\" instead of\n // a real error, the orchestrator's billing detection would never fire,\n // and the chair never gets to post the explainer. fullStream surfaces\n // every part type (text-delta / error / finish / …); we project to\n // our own typed yields below. This was the root cause of the \"OpenAI\n // quota exhausted but no chair notice\" symptom.\n let sawError = false;\n try {\n for await (const part of result.fullStream) {\n if (req.signal?.aborted) break;\n if (part.type === \"text-delta\") {\n yield { type: \"text\", delta: part.textDelta };\n } else if (part.type === \"error\") {\n sawError = true;\n yield { type: \"error\", message: formatStreamError(part.error) };\n // Don't break · let the SDK finish draining its internal stream\n // so usage / finishReason promises settle (avoids leaking the\n // background read). One error chunk is enough for the caller.\n }\n // text-delta / error are the only part types we surface; finish,\n // tool-call, response-metadata, etc. are handled below via the\n // SDK's resolved promises (usage / finishReason / response).\n }\n if (sawError) {\n // Skip post-stream usage/served/done · the request was rejected\n // upstream, those promises may hang or resolve with stale values.\n return;\n }\n // Capture the *actually-served* model id from the upstream\n // response. OpenRouter echoes this in the OpenAI-compatible\n // response body — if it differs from req.modelV's resolved id,\n // OR did a silent reroute. The loggedFetch wrapper above also\n // dumps the raw response so this stays auditable from the console.\n const responseMeta = await result.response.catch(() => null);\n const servedId =\n (responseMeta && typeof (responseMeta as { modelId?: unknown }).modelId === \"string\"\n ? (responseMeta as { modelId: string }).modelId\n : \"\");\n if (servedId) {\n yield { type: \"served\", modelId: servedId };\n }\n // Token usage · the SDK resolves usage once the upstream response\n // wraps up. Caller (orchestrator) hooks this to bump per-agent\n // cumulative token counters surfaced on the agent profile.\n const usage = await result.usage.catch(() => null);\n if (usage) {\n const promptTokens = typeof usage.promptTokens === \"number\" ? usage.promptTokens : 0;\n const completionTokens = typeof usage.completionTokens === \"number\" ? usage.completionTokens : 0;\n const totalTokens = typeof (usage as { totalTokens?: number }).totalTokens === \"number\"\n ? (usage as { totalTokens: number }).totalTokens\n : promptTokens + completionTokens;\n if (totalTokens > 0) {\n yield { type: \"usage\", promptTokens, completionTokens, totalTokens };\n }\n }\n const finishReason = await result.finishReason.catch(() => undefined);\n yield { type: \"done\", finishReason: typeof finishReason === \"string\" ? finishReason : undefined };\n } catch (e) {\n if ((e as { name?: string })?.name === \"AbortError\") {\n yield { type: \"done\", finishReason: \"aborted\" };\n return;\n }\n yield { type: \"error\", message: formatStreamError(e) };\n }\n}\n\n/**\n * Convenience: drain a stream into a single string. Used for non-streaming\n * call sites (e.g. memory extraction, the moderator decision in M4).\n */\nexport async function callLLM(req: LLMRequest): Promise<string> {\n let buf = \"\";\n for await (const chunk of callLLMStream(req)) {\n if (chunk.type === \"text\") buf += chunk.delta;\n else if (chunk.type === \"error\") throw new Error(chunk.message);\n }\n return buf;\n}\n\nexport interface LLMUsage {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n}\n\n/**\n * Drain a stream and return both the text and the SDK-reported usage.\n * Callers that need to bill tokens to a specific agent (e.g. the brief\n * pipeline attributing all report-generation tokens to the chair) use\n * this rather than the plain callLLM. Usage may be null if the upstream\n * provider didn't surface it.\n */\nexport async function callLLMWithUsage(\n req: LLMRequest,\n): Promise<{ text: string; usage: LLMUsage | null }> {\n let buf = \"\";\n let usage: LLMUsage | null = null;\n for await (const chunk of callLLMStream(req)) {\n if (chunk.type === \"text\") buf += chunk.delta;\n else if (chunk.type === \"error\") throw new Error(chunk.message);\n else if (chunk.type === \"usage\") {\n usage = {\n promptTokens: chunk.promptTokens,\n completionTokens: chunk.completionTokens,\n totalTokens: chunk.totalTokens,\n };\n }\n }\n return { text: buf, usage };\n}\n","/**\n * Provider API keys · stored AES-GCM encrypted.\n *\n * MVP-grade encryption: 256-bit key derived via scrypt from the local OS\n * username + a constant salt. Better than plaintext but not a vault — v1.2\n * will replace this with the OS keychain (macOS Keychain / Linux Secret\n * Service / Windows Credential Vault).\n *\n * Layout of stored blob: [iv:12][tag:16][ciphertext:N]\n */\nimport { createCipheriv, createDecipheriv, randomBytes, scryptSync } from \"node:crypto\";\nimport { userInfo } from \"node:os\";\n\nimport { getDb } from \"./db.js\";\n\nconst SALT = \"boardroom.v1.salt\";\nconst ALGO = \"aes-256-gcm\";\n\nlet _key: Buffer | null = null;\nfunction deriveKey(): Buffer {\n if (_key) return _key;\n const username = userInfo().username || \"boardroom-default\";\n _key = scryptSync(username, SALT, 32);\n return _key;\n}\n\nfunction encrypt(plain: string): Buffer {\n const iv = randomBytes(12);\n const cipher = createCipheriv(ALGO, deriveKey(), iv);\n const ct = Buffer.concat([cipher.update(plain, \"utf8\"), cipher.final()]);\n const tag = cipher.getAuthTag();\n return Buffer.concat([iv, tag, ct]);\n}\n\nfunction decrypt(blob: Buffer): string {\n const iv = blob.subarray(0, 12);\n const tag = blob.subarray(12, 28);\n const ct = blob.subarray(28);\n const decipher = createDecipheriv(ALGO, deriveKey(), iv);\n decipher.setAuthTag(tag);\n return Buffer.concat([decipher.update(ct), decipher.final()]).toString(\"utf8\");\n}\n\nexport type Provider =\n | \"openrouter\"\n | \"anthropic\"\n | \"openai\"\n | \"google\"\n | \"xai\"\n | \"deepseek\"\n // Skill-service keys (not LLM providers). Currently:\n // · brave · web search via the Brave Search API.\n | \"brave\";\n\n/** Cheap existence check used as the global gate for the Web Search\n * system skill — without a Brave key, no agent searches regardless\n * of per-agent toggles. */\nexport function hasBraveKey(): boolean {\n const k = getKey(\"brave\");\n return typeof k === \"string\" && k.length > 0;\n}\n\nexport interface ProviderKeyMeta {\n provider: Provider;\n configured: boolean;\n updatedAt: number | null;\n /** Recognisable masked preview of the stored key · first 4 + `…` +\n * last 4 chars (e.g. `sk-or…YjNH`). `null` when not configured.\n * This is the *only* echo of the key the server ever sends back —\n * enough for the user to confirm which key is in which slot, far\n * too short to recover the full secret. Keys ≤ 12 chars get a\n * tighter mask (2+2) so we don't leak too much of a small string. */\n preview: string | null;\n}\n\ninterface Row {\n provider: string;\n key_blob: Buffer;\n updated_at: number;\n}\n\nfunction maskKey(plain: string): string {\n const trimmed = plain.trim();\n if (!trimmed) return \"\";\n // Length-preserving mask · the visual width of the masked preview\n // matches the original key's character count. Prefix + suffix bytes\n // are real (so the user can verify which key is in the slot); the\n // middle is bullet-padded to N − prefix − suffix dots so a 60-char\n // OpenRouter key looks unmistakably \"long\" and a 31-char Brave key\n // looks distinctly shorter.\n const n = trimmed.length;\n if (n <= 4) return \"•\".repeat(n);\n if (n <= 12) {\n // Short keys · 2+dots+2 to keep entropy leakage proportionate.\n return `${trimmed.slice(0, 2)}${\"•\".repeat(n - 4)}${trimmed.slice(-2)}`;\n }\n return `${trimmed.slice(0, 4)}${\"•\".repeat(n - 8)}${trimmed.slice(-4)}`;\n}\n\nexport function listKeyMeta(): ProviderKeyMeta[] {\n const rows = getDb()\n .prepare(\"SELECT provider, key_blob, updated_at FROM provider_keys\")\n .all() as Row[];\n const map = new Map<string, ProviderKeyMeta>();\n for (const r of rows) {\n let preview: string | null = null;\n if (r.key_blob.length > 0) {\n try { preview = maskKey(decrypt(r.key_blob)); }\n catch { preview = null; /* corrupt blob · treat as no preview */ }\n }\n map.set(r.provider, {\n provider: r.provider as Provider,\n configured: r.key_blob.length > 0,\n updatedAt: r.updated_at,\n preview,\n });\n }\n return Array.from(map.values());\n}\n\nexport function getKey(provider: Provider): string | null {\n const row = getDb()\n .prepare(\"SELECT key_blob FROM provider_keys WHERE provider = ?\")\n .get(provider) as { key_blob: Buffer } | undefined;\n if (!row) return null;\n try {\n return decrypt(row.key_blob);\n } catch {\n // Corrupt or wrong derivation key — treat as missing.\n return null;\n }\n}\n\nexport function setKey(provider: Provider, plain: string): void {\n const trimmed = plain.trim();\n if (!trimmed) {\n deleteKey(provider);\n return;\n }\n const blob = encrypt(trimmed);\n const now = Date.now();\n getDb()\n .prepare(\n `INSERT INTO provider_keys (provider, key_blob, created_at, updated_at)\n VALUES (?, ?, ?, ?)\n ON CONFLICT(provider) DO UPDATE SET key_blob = excluded.key_blob, updated_at = excluded.updated_at`,\n )\n .run(provider, blob, now, now);\n}\n\nexport function deleteKey(provider: Provider): void {\n getDb().prepare(\"DELETE FROM provider_keys WHERE provider = ?\").run(provider);\n}\n","/**\n * Skill ability auto-analyzer.\n *\n * Given a skill's name + description + when_to_use + body markdown,\n * asks a cheap LLM to estimate how installing this skill shifts the\n * agent's six ability axes. Used as a fallback when the user's\n * Skill.md frontmatter doesn't include an explicit `ability:` block —\n * manual values still win when present.\n *\n * Best-effort. Returns an empty map on any failure (no key, parse\n * error, network); the skill installs with no deltas in that case.\n */\nimport { callLLM, NoKeyError, type LLMMessage } from \"../ai/adapter.js\";\nimport type { ModelV } from \"../ai/registry.js\";\n\nimport { ABILITY_AXES, type AbilityAxis } from \"./axes.js\";\n\nconst ANALYZER_MODEL: ModelV = \"haiku-4-5\" as ModelV;\n\n/** Boardroom-specific descriptions of each axis. The model needs the\n * semantic anchor to map a skill to the right axes (otherwise it'll\n * guess based on the literal axis names, which is noisy). */\nconst AXIS_DESCRIPTIONS: Record<AbilityAxis, string> = {\n dissent: \"willingness to challenge, push back, disagree, raise objections, refuse to nod along\",\n pattern_recall: \"ability to cite history, prior cases, comparable patterns, market analogues, what's been tried before\",\n rigor: \"precision of argument, definitional clarity, logical strictness, demand for evidence and crisp reasoning\",\n empathy: \"ability to take the perspective of users, customers, or stakeholders not in the room — voice for absent parties\",\n narrative: \"storytelling, scenario-building, painting an arc, framing decisions as a story\",\n decisiveness: \"willingness to commit to a recommendation, cut losing threads, force a call rather than keep options open\",\n};\n\nexport interface AnalyzeInput {\n name: string;\n description: string;\n whenToUse?: string;\n bodyMd?: string;\n}\n\n/** Tolerant JSON extractor — strips ``` fences, attempts straight parse,\n * then a slice between the first { and last }. */\nfunction extractJson(text: string): unknown | null {\n if (!text) return null;\n let s = text.trim();\n s = s.replace(/^```(?:json)?\\s*/i, \"\").replace(/```\\s*$/i, \"\").trim();\n try { return JSON.parse(s); } catch { /* fall through */ }\n const open = s.indexOf(\"{\");\n const close = s.lastIndexOf(\"}\");\n if (open >= 0 && close > open) {\n try { return JSON.parse(s.slice(open, close + 1)); } catch { return null; }\n }\n return null;\n}\n\nexport async function analyzeSkillAbility(\n input: AnalyzeInput,\n signal?: AbortSignal,\n): Promise<Record<AbilityAxis, number>> {\n const axesBlock = ABILITY_AXES\n .map((a) => `- ${a}: ${AXIS_DESCRIPTIONS[a]}`)\n .join(\"\\n\");\n // Cap body so we don't pay for huge skill documents — the first\n // ~2KB carries the intent in nearly every real-world skill.\n const body = (input.bodyMd || \"\").slice(0, 2048);\n\n const sys: LLMMessage = {\n role: \"system\",\n content: [\n \"You analyze skills installed on AI agents that participate in multi-agent discussions ('boardrooms'). Each agent is rated on 6 axes (0-10).\",\n \"\",\n \"Your job: estimate how installing THIS skill shifts those axes — return integer deltas in [-3, 3] for each.\",\n \"\",\n \"Axes:\",\n axesBlock,\n \"\",\n \"Rules:\",\n \"- Output STRICT JSON ONLY. No prose, no markdown, no code fences.\",\n \"- Format exactly: {\\\"dissent\\\": N, \\\"pattern_recall\\\": N, \\\"rigor\\\": N, \\\"empathy\\\": N, \\\"narrative\\\": N, \\\"decisiveness\\\": N}\",\n \"- Each N is an integer in [-3, 3]. +3 strongly boosts the axis; -3 strongly damps it; 0 means no meaningful change.\",\n \"- A typical skill moves 1–3 axes. Be conservative — assign non-zero only where the skill clearly biases that axis.\",\n \"- The deltas should reflect HOW the agent thinks/argues differently after installing this skill, not the skill's topic.\",\n ].join(\"\\n\"),\n };\n\n const user: LLMMessage = {\n role: \"user\",\n content: [\n `Skill name: ${input.name}`,\n ``,\n `Description: ${input.description}`,\n ...(input.whenToUse && input.whenToUse !== input.description\n ? [``, `When to use: ${input.whenToUse}`]\n : []),\n ...(body ? [``, `Body:`, body] : []),\n ``,\n `Estimate axis deltas as JSON.`,\n ].join(\"\\n\"),\n };\n\n let raw = \"\";\n try {\n raw = await callLLM({\n modelV: ANALYZER_MODEL,\n messages: [sys, user],\n temperature: 0,\n maxTokens: 200,\n signal,\n });\n } catch (e) {\n if (e instanceof NoKeyError) return {} as Record<AbilityAxis, number>;\n process.stderr.write(\n `[skills/analyze] analysis failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n return {} as Record<AbilityAxis, number>;\n }\n\n const parsed = extractJson(raw);\n if (!parsed || typeof parsed !== \"object\") {\n return {} as Record<AbilityAxis, number>;\n }\n\n const out: Record<AbilityAxis, number> = {} as Record<AbilityAxis, number>;\n const obj = parsed as Record<string, unknown>;\n for (const axis of ABILITY_AXES) {\n const v = obj[axis];\n if (typeof v !== \"number\" || !Number.isFinite(v)) continue;\n const clamped = Math.max(-3, Math.min(3, Math.round(v)));\n if (clamped !== 0) out[axis] = clamped;\n }\n return out;\n}\n","/**\n * System skills · hardcoded skills the user cannot install, edit, or\n * delete. They are synthesized into AgentSkill objects on read so the\n * existing skills UI can display them as read-only cards.\n *\n * Currently:\n * · report-writer · the chair's standard 3-stage brief pipeline\n *\n * System skills do not enter the agent_skills table and are never\n * routed by the turn-time Pass-1 picker. They run from purpose-built\n * orchestrator code paths (e.g. brief.ts).\n */\nimport type { Agent } from \"../storage/agents.js\";\nimport { hasBraveKey } from \"../storage/keys.js\";\nimport type { AgentSkill } from \"../storage/skills.js\";\n\nexport const REPORT_WRITER_SLUG = \"report-writer\";\nexport const FETCH_URL_SLUG = \"fetch-url\";\nexport const WEB_SEARCH_SLUG = \"web-search\";\n\nconst REPORT_WRITER_BODY_MD = `# Standard Report Writer\n\nThe chair's built-in pipeline for compiling a room's transcript into a\nmulti-director research note. Runs automatically when the room is\nadjourned. As of v0.2 it's composer-driven: every report is assembled\nfrom a library of components, and which components appear depends on\nthe topic and the directors' actual contributions.\n\n## How a report gets composed\n\nThe pipeline runs in **four** stages:\n\n1. **Per-director extract** · each director re-reads their own\n contributions and surfaces 2–4 signals (lens-tagged: \\`data\\` /\n \\`dissent\\` / \\`narrative\\` / \\`structural\\` / \\`first-principle\\`).\n Diversity is guaranteed at the source.\n2. **Compose** · a cheap chair-level pass picks (a) a style spine and\n (b) a subset of components that fit the room's subject. The pick is\n recorded on the brief and visible on hover of the SPINE tag.\n3. **Cluster + scaffold** · the chair builds a structured scaffold\n filling only the picked components. Retried up to 3× with rising\n temperature on parse failure.\n4. **Final write** · the chair streams the markdown report rendering\n only the picked components, in the order the composer chose. The\n Methodology footer is appended programmatically.\n\n## Component library (v2 · 19 kinds)\n\n**Anchor** (composer picks exactly one):\n · \\`bottom-line\\` Sentence judgement + confidence + rationale.\n · \\`thesis\\` Single load-bearing claim, pull-statement style.\n · \\`working-hypothesis\\` Essay opener: hypothesis + reasons it may be wrong.\n\n**Findings** (pick exactly one):\n · \\`headline-findings\\` Three pillar claims, MECE, with supporters / challengers / sub-findings.\n · \\`big-ideas\\` Three numbered claims, each with a why. Lighter, punchier.\n\n**Action** (pick exactly one):\n · \\`recommendations\\` 3–5 P0/P1/P2 actions with owner / horizon / success metric / risk.\n · \\`the-bet\\` Conditions to back the call, plus kill criteria.\n · \\`considerations\\` Same shape as recommendations; rendered in hedged, philosophical voice.\n\n**Optional** (independent on/off):\n · \\`frame-shift\\` How the question itself moved (or held).\n · \\`convergence\\` Where directors aligned via independent reasoning paths.\n · \\`divergence\\` The single hinge where directors split.\n · \\`positions\\` 2–3 named camps with a pull-quote per camp.\n · \\`visuals\\` 0–4 exhibits (comparison-table / quadrant-chart / force-field / strengths-cautions).\n · \\`two-paths\\` Side-by-side trajectory panels (Path A vs Path B).\n · \\`why-now\\` Window opened · window closes · what to bet on.\n · \\`pre-mortem\\` Failure modes with leading indicators + mitigations.\n · \\`new-questions\\` Questions that emerged in the room.\n · \\`planning-assumption\\` Forward-looking probabilistic statement with falsification test.\n · \\`open-questions\\` Residual unresolved questions tagged P0/P1.\n\nThe composer enforces 5–9 components total — too few = thin, too many = noise.\n\n## Style spines (all 6 active)\n\nThe renderer ships full CSS for every spine the composer can pick:\n\\`boardroom-dark\\` (default · dark warm McKinsey discipline), \\`a16z-thesis\\`\n(black cover · orange accent · pull-statement), \\`anthropic-essay\\` (warm\npaper · serif · drop-cap), \\`gartner-note\\` (clinical white · navy ·\nnumbered chapters), \\`mckinsey-deck\\` (white deck · navy slabs · 3-pillar\ngrid), and \\`openai-paper\\` (minimal sans · teal · rounded panels).\n\n## Why it can't be modified\n\nThe composer + component library IS the chair's source of truth. The\nstable component vocabulary keeps briefs comparable within the same\nspine; cross-spine comparison is intentionally degraded in exchange for\nfit-to-topic. Per-room edits would fragment the catalogue and make\nanalytics across briefs unreliable.\n`;\n\nconst FETCH_URL_BODY_MD = `# Fetch URL\n\nThe chair's built-in capability for reading URLs the user shares. Whenever\nthe user drops an http/https link into the room, the chair fetches the page\nin the background and inlines a readable excerpt into its own context — so\nthe chair (and, by reference, the directors) can ground their questions in\nthe actual source instead of guessing from the title.\n\n## How it runs\n\n1. **Detect** · scan the recent user messages for http(s) URLs (up to 3 per\n turn, capped to keep the chair's prompt budget healthy).\n2. **Fetch** · 6-second timeout per URL, with a polite user agent. Failures\n surface as a one-line note (timeout, 404, unsupported content type)\n rather than blocking the turn.\n3. **Extract** · strip HTML/scripts, decode entities, collapse whitespace,\n keep the page title and the first ~6 KB of readable text. Per-room cache\n means the same URL is only fetched once per session.\n4. **Inject** · the chair sees a \"SHARED MATERIALS\" block in its system\n prompt with each URL's title + excerpt. Quotes are short and always\n cited by URL.\n\n## Limits & boundaries\n\n- HTML / plaintext only — PDFs, images, JSON APIs, and JS-heavy single-page\n apps come back thin or empty. When extraction is sparse, the chair asks\n the user to paste the relevant excerpt directly instead of speculating.\n- The chair never invents page content. If a fetch fails, the prompt\n includes the error reason; the chair will say so to the user instead of\n inventing what the page might have said.\n- This is a chair-only capability in v1 — directors don't auto-fetch. The\n chair surfaces what it found into its summary so directors get the\n excerpt second-hand through the room transcript.\n\n## Why it can't be modified\n\nNetwork behaviour, timeouts, and content limits are tuned to keep turn\nlatency predictable. Editing them per-room would make latency unpredictable\nacross the boardroom; if you need different fetch policy, do it at the\nprocess level rather than per-chair.\n`;\n\nfunction buildFetchUrlSkill(agent: Agent): AgentSkill {\n const now = Date.now();\n return {\n id: `system:${FETCH_URL_SLUG}:${agent.id}`,\n agentId: agent.id,\n slug: FETCH_URL_SLUG,\n name: \"Fetch URL\",\n version: \"1.0.0\",\n description:\n \"Fetches URLs the user shares in the room — strips HTML to readable \" +\n \"text, decodes entities, and inlines the excerpt into the chair's \" +\n \"context so clarifying questions and summaries are grounded in the \" +\n \"actual page content rather than the link's title.\",\n whenToUse:\n \"Always-on. Whenever the user includes an http(s) URL in a message, \" +\n \"the chair fetches it before its next turn (clarification or \" +\n \"round-end summary). Up to 3 URLs per turn, 6 KB per page.\",\n bodyMd: FETCH_URL_BODY_MD,\n ability: {\n // No turn-time ability deltas — this skill runs implicitly before\n // the chair speaks, not via the Pass-1 router.\n pattern_recall: 1,\n rigor: 1,\n },\n tips: [\n \"URLs are auto-detected; the user doesn't have to do anything special.\",\n \"Per-room cache means the same URL won't be re-fetched within a session.\",\n \"Failed fetches (timeout / 404 / unsupported content) surface as a one-line note instead of blocking the turn.\",\n \"When a page is JS-heavy and the extract looks thin, ask the user to paste the relevant excerpt directly.\",\n ],\n createdAt: now,\n updatedAt: now,\n system: true,\n };\n}\n\nfunction buildReportWriterSkill(agent: Agent): AgentSkill {\n const now = Date.now();\n return {\n id: `system:${REPORT_WRITER_SLUG}:${agent.id}`,\n agentId: agent.id,\n slug: REPORT_WRITER_SLUG,\n name: \"Standard Report Writer\",\n version: \"2.0.0\",\n description:\n \"Composes the room's transcript into a research note. A composer \" +\n \"stage picks the style spine and component subset that fits the \" +\n \"topic; the chair then fills only those components, drawing on \" +\n \"the directors' lens-tagged signals.\",\n whenToUse:\n \"Always. Runs automatically when the room is adjourned with a report.\",\n bodyMd: REPORT_WRITER_BODY_MD,\n ability: {\n // No turn-time ability deltas — this skill never runs at turn time.\n // Listed for parity with other skills' shape.\n rigor: 2,\n narrative: 2,\n },\n tips: [\n \"The composer picks 5–9 components from a library of 15 — empty sections are dropped.\",\n \"Diversity is enforced at extract time, not write time — directors surface their own signals first.\",\n \"Findings without ≥ 2 evidence lenses are merged or dropped at scaffold stage.\",\n \"Anchor / Findings / Action are substitute groups — only one variant from each is rendered per brief.\",\n ],\n createdAt: now,\n updatedAt: now,\n system: true,\n };\n}\n\nconst WEB_SEARCH_BODY_MD = `# Web Search\n\nA live link to fresh information beyond the model's training cutoff.\nWhen a director (or the chair) is about to speak and the question\nhinges on something current — a price, a release, a recent news event,\na published number — the orchestrator quietly runs a Brave Search,\ndistills the top 3–5 results, and prepends them to the agent's\ncontext as a SHARED MATERIALS block. The agent then answers using\nthose sources and cites them inline as \\`[1] [2] [3]\\`.\n\n## How it runs\n\n1. **Pass-1 router** (the same haiku call that already decides which\n installed .md skills to apply) is asked one extra question:\n *\"Does this turn need fresh web info? If yes, give me one search\n query.\"* When the answer is no, nothing happens — no Brave call,\n no extra latency, no cost.\n2. When a query comes back, we hit Brave Search (~$0.005 per query\n on the standard plan, ~$5 per 1000) with a 6 s timeout.\n3. Top 5 results — title, url, short description — are formatted\n into the SHARED MATERIALS block.\n4. The agent's Pass-2 prompt receives the block. The director cites\n sources by their bracketed number; the message persists a\n \\`web_search_used\\` meta so the chat UI can surface a 🔍 indicator\n under the bubble.\n\n## Configuration\n\nThis skill is **disabled by default**. Two switches in series:\n\n- **Global key**: User Settings → API Key → *Skill Services* →\n *Brave Search*. Without this key, no agent can ever search.\n- **Per-agent toggle**: each director's profile has a Web Search row\n that flips on/off independently. Useful when you want one director\n to stay strictly inside its training (a \"first principles only\"\n voice) while others reach for fresh sources.\n\nWhen the global key is missing, the per-agent toggle disables itself\nvisually and links back to Preferences.\n\n## When this skill helps\n\n- Recent events, releases, deaths, partnerships\n- Live prices / market caps / round sizes\n- Specific named entities (\"what is X's most recent annual letter\")\n- Domain-specific news the model can't have seen\n\n## When this skill hurts\n\n- First-principles philosophical reasoning (search becomes noise)\n- Personal context only the user has (search is the wrong tool)\n- Highly synthesized / opinion-driven prompts (the agent's training\n has the synthesis; search adds raw inputs that derail)\n\nThe Pass-1 router is conservative — it's biased toward NOT searching\nunless the question genuinely needs an external fact.\n\n## Cost & privacy\n\nEach search hits Brave's API directly with the user's own key. No\nqueries pass through Boardroom infrastructure. Brave's privacy policy\napplies — Brave doesn't sell or profile users from API calls\naccording to their published terms.\n\n`;\n\nfunction buildWebSearchSkill(agent: Agent): AgentSkill {\n const now = Date.now();\n return {\n id: `system:${WEB_SEARCH_SLUG}:${agent.id}`,\n agentId: agent.id,\n slug: WEB_SEARCH_SLUG,\n name: \"Web Search\",\n version: \"1.0.0\",\n description:\n \"Live web search via the Brave Search API — used when a turn \" +\n \"hinges on information beyond the model's training cutoff. \" +\n \"The Pass-1 router decides whether to search; results are \" +\n \"injected into the agent's prompt as cited sources.\",\n whenToUse:\n \"Per-turn. The router asks the agent's draft prompt whether \" +\n \"fresh web info would help, and only then does Brave get a query.\",\n bodyMd: WEB_SEARCH_BODY_MD,\n ability: {\n pattern_recall: 2,\n empirical: 2,\n rigor: 1,\n },\n tips: [\n \"Disabled until you add a Brave Search API key in User Settings → API Key.\",\n \"Each agent has its own toggle on its profile page — useful for keeping a 'first-principles only' director out of search.\",\n \"The Pass-1 router is conservative: it skips search unless the question genuinely needs an external fact.\",\n \"Citations appear inline as bracketed numbers; the chat bubble shows a small 🔍 indicator when search ran.\",\n ],\n createdAt: now,\n updatedAt: now,\n system: true,\n state: {\n enabled: agent.webSearchEnabled,\n keyConfigured: hasBraveKey(),\n requiresKey: { provider: \"brave\", label: \"Brave Search\" },\n },\n };\n}\n\n/**\n * Synthesize the system skills installed on a given agent. v1.1:\n * - chair gets fetch-url + report-writer + web-search\n * - directors get web-search\n *\n * The web-search skill is always *listed* (so the agent profile\n * surfaces it even when the user hasn't configured a Brave key yet);\n * the orchestrator checks the actual gates (key present + per-agent\n * toggle) before running it.\n */\nexport function getSystemSkillsForAgent(agent: Agent): AgentSkill[] {\n if (agent.roleKind === \"moderator\") {\n return [\n buildFetchUrlSkill(agent),\n buildReportWriterSkill(agent),\n buildWebSearchSkill(agent),\n ];\n }\n return [buildWebSearchSkill(agent)];\n}\n\n/** True if a slug is a reserved system slug. Used by install/delete\n * routes to refuse user attempts to write over a system skill. */\nexport function isSystemSkillSlug(slug: string): boolean {\n return (\n slug === REPORT_WRITER_SLUG ||\n slug === FETCH_URL_SLUG ||\n slug === WEB_SEARCH_SLUG\n );\n}\n","/**\n * Agent-spec generation prompt. The user describes what kind of director\n * they want in free text; the LLM returns a structured spec\n * (name / handle / role tag / bio / cover quote / instruction / model)\n * matching the boardroom's house voice.\n *\n * The prompt seeds with the canonical seed directors (Socrates, First\n * Principles, etc.) as few-shot examples so generated agents fit the\n * established style — short concrete bios, named methods, anti-flatter\n * boundaries, italic-for-load-bearing-word voice rules.\n */\nimport type { LLMMessage } from \"../adapter.js\";\n\nconst HOUSE_STYLE = [\n \"## Boardroom directors · house style\",\n \"\",\n \"Every director has seven fields:\",\n \" · name · short evocative name (1–4 words)\",\n \" · handle · slug-form `/short_name` (≤ 18 chars, lowercase + underscore)\",\n \" · roleTag · 1-word lowercase noun describing their stance (skeptic / physicist / observer / strategist / advocate / long-pattern)\",\n \" · bio · 1–2 sentences, ≤ 280 chars. Concrete, not flowery. Names the method, not the persona.\",\n \" · coverQuote · 1 sentence (≤ 200 chars). The opening question THIS director would ask in any room.\",\n \" · instruction · multi-section markdown system prompt (~600–1200 chars). Sections: identity, method (numbered), voice, boundaries.\",\n \" · ability · 6-axis personality map (each axis 0..10). Drives a radar chart that visualizes the director's strengths and limits. Distribution MUST be uneven — see 'Ability axes' below.\",\n \"\",\n \"## Voice rules (apply to bio, coverQuote, AND instruction)\",\n \"\",\n \"· Plain prose. Specific verbs. No marketing copy, no flattery, no 'will help you'.\",\n \"· The bio NAMES the method (\\\"refuses unclear premises\\\" / \\\"reads against thirty years of category history\\\"), not abstract personality (\\\"insightful and analytical\\\").\",\n \"· Italic for the word being interrogated. Bold for the load-bearing claim.\",\n \"· Anti-flatter is mandatory in instruction's boundaries section: do not preface with affirmation or summary; lead with the disagreement / missing premise.\",\n \"· Instructions must include a numbered \\\"method\\\" the director executes per turn (3–4 concrete steps).\",\n \"· Instructions must include voice rules and boundaries (what they DO and what they REFUSE to do).\",\n \"\",\n \"## Ability axes (0..10 per axis)\",\n \"\",\n \"A director's `ability` is six numbers — each in 0..10 — that describe how they think and argue. The radar should NOT be flat — pick 1-3 dominant axes (8-10), 1-2 weak axes (1-3), and the remainder mid (4-7). Make the shape match the role.\",\n \"\",\n \" · dissent · willingness to challenge, push back, raise objections, refuse to nod along\",\n \" · pattern_recall · cites history, prior cases, comparable patterns, market analogues\",\n \" · rigor · precision of argument, definitional clarity, demand for evidence\",\n \" · empathy · takes the perspective of users / stakeholders not in the room\",\n \" · narrative · storytelling, scenario-building, framing decisions as a story\",\n \" · decisiveness · willingness to commit to a recommendation, force a call\",\n \"\",\n \"Examples of healthy NON-uniform shapes:\",\n \" · A skeptic → dissent: 9, pattern_recall: 4, rigor: 8, empathy: 4, narrative: 5, decisiveness: 4\",\n \" · A first-principles physicist → dissent: 6, pattern_recall: 5, rigor: 9, empathy: 3, narrative: 4, decisiveness: 6\",\n \" · A long-pattern strategist → dissent: 4, pattern_recall: 8, rigor: 6, empathy: 5, narrative: 7, decisiveness: 6\",\n \" · A user-empath → dissent: 5, pattern_recall: 4, rigor: 5, empathy: 9, narrative: 8, decisiveness: 5\",\n \" · A ruthless decider → dissent: 7, pattern_recall: 5, rigor: 6, empathy: 3, narrative: 4, decisiveness: 9\",\n \"\",\n \"Reject any uniform vector (e.g. all 5s or all 7s) — directors must have a clear shape that matches their stated method. Total of all six axes should typically land in 30..40.\",\n \"\",\n \"## Few-shot · canonical directors\",\n \"\",\n \"### Example 1 · Socrates\",\n \" name: Socrates\",\n \" handle: /socrates\",\n \" roleTag: skeptic\",\n \" bio: Refuses unclear premises. Forces you to define your terms before you defend them.\",\n \" coverQuote: What do you mean — exactly — when you say that word?\",\n \" instruction: |\",\n \" You are Socrates, a board director whose role is the skeptic.\",\n \" Your method:\",\n \" 1. Locate the load-bearing words in the user's framing — usually abstractions like 'product-market fit', 'data flywheel', 'AI-native', 'engagement'.\",\n \" 2. Ask them to name a sharper, more specific version: which kind of X? what would distinguish X from not-X here?\",\n \" 3. If they accept your sharper definition, proceed. If they resist, that resistance is itself a signal worth pointing out.\",\n \" Voice:\",\n \" - Short. One or two sharp questions per turn beats a paragraph.\",\n \" - Concrete examples beat abstractions when you push back.\",\n \" - Use italics for the word you're interrogating: *which* kind of moat?\",\n \" Boundaries:\",\n \" - You do not provide answers. You provide questions that surface answers.\",\n \" - You do not concede a definition before testing whether it survives a counter-example.\",\n \" - Do not preface with affirmation or summary. Lead with the disagreement, the missing premise, the angle the user hasn't raised.\",\n \"\",\n \"### Example 2 · First Principles\",\n \" name: First Principles\",\n \" handle: /first_p\",\n \" roleTag: physicist\",\n \" bio: Strips problems down to observables and causal chains. Refuses to import assumptions from analogy.\",\n \" coverQuote: What do we know to be physically true here, and what are we just inheriting from a story?\",\n \" (same instruction shape — identity, numbered method, voice, boundaries)\",\n \"\",\n \"### Example 3 · Long Horizon\",\n \" name: Long Horizon\",\n \" handle: /long_h\",\n \" roleTag: strategist\",\n \" bio: Plays the move four steps out. Distinguishes 'right now' from 'right at the time horizon that matters'.\",\n \" coverQuote: If this works, what does the next move force you into — and is that a corner you want to be in?\",\n \"\",\n \"## Constraints\",\n \"\",\n \"· The new director must be DISTINCT from the canonical six. Don't recreate Socrates with a different name.\",\n \"· Pick a model from: opus-4-7 (default for nuanced / contrarian roles), sonnet-4-6 (faster, fine for analytical roles), haiku-4-5 (only for very tight / rule-based roles). When in doubt, opus-4-7.\",\n \"· The instruction section MUST contain a numbered method, voice rules, and boundaries — even short ones.\",\n \"\",\n \"## Output format\",\n \"\",\n \"Return one JSON object inside a fenced ```json code block. No prose outside the block.\",\n \"\",\n \"```json\",\n \"{\",\n \" \\\"name\\\": \\\"...\\\",\",\n \" \\\"handle\\\": \\\"/short_form\\\",\",\n \" \\\"roleTag\\\": \\\"skeptic\\\",\",\n \" \\\"bio\\\": \\\"1-2 sentences, ≤ 280 chars\\\",\",\n \" \\\"coverQuote\\\": \\\"the question they'd open every room with, ≤ 200 chars\\\",\",\n \" \\\"instruction\\\": \\\"You are X, a board director...\\\\n\\\\nYour method:\\\\n1. ...\\\\n\\\\nVoice:\\\\n- ...\\\\n\\\\nBoundaries:\\\\n- ...\\\",\",\n \" \\\"modelV\\\": \\\"opus-4-7\\\",\",\n \" \\\"ability\\\": { \\\"dissent\\\": 9, \\\"pattern_recall\\\": 4, \\\"rigor\\\": 8, \\\"empathy\\\": 4, \\\"narrative\\\": 5, \\\"decisiveness\\\": 4 }\",\n \"}\",\n \"```\",\n].join(\"\\n\");\n\nexport interface AgentSpecOpts {\n description: string;\n}\n\nexport function buildAgentSpecMessages(opts: AgentSpecOpts): LLMMessage[] {\n return [\n { role: \"system\", content: HOUSE_STYLE },\n {\n role: \"user\",\n content: [\n \"I want a new boardroom director described as:\",\n \"\",\n opts.description.trim(),\n \"\",\n \"Generate the full spec in the JSON format above. Match the house voice exactly.\",\n ].join(\"\\n\"),\n },\n ];\n}\n\n/* ─────────────────────── parser + validation ──────────────────────────── */\n\nexport interface AgentSpec {\n name: string;\n handle: string;\n roleTag: string;\n bio: string;\n coverQuote: string;\n instruction: string;\n modelV: string;\n /** 6-axis radar profile · {axis: 0..10}. Empty when the LLM didn't\n * produce a usable ability map; the agents route falls back to a\n * heuristic distribution in that case. */\n ability: Record<string, number>;\n}\n\nconst ABILITY_AXES = [\n \"dissent\",\n \"pattern_recall\",\n \"rigor\",\n \"empathy\",\n \"narrative\",\n \"decisiveness\",\n] as const;\n\nfunction parseAbility(raw: unknown): Record<string, number> {\n if (!raw || typeof raw !== \"object\") return {};\n const obj = raw as Record<string, unknown>;\n const out: Record<string, number> = {};\n for (const axis of ABILITY_AXES) {\n const v = obj[axis];\n if (typeof v !== \"number\" || !Number.isFinite(v)) continue;\n out[axis] = Math.max(0, Math.min(10, Math.round(v)));\n }\n // Reject obviously-flat vectors (all same value) — the prompt forbids\n // them; if the model returned one anyway, drop it so the route's\n // fallback distribution kicks in.\n const values = Object.values(out);\n if (values.length < 4) return {};\n const allSame = values.every((v) => v === values[0]);\n if (allSame) return {};\n return out;\n}\n\nconst ALLOWED_MODELS = new Set([\n \"sonnet-4-6\", \"opus-4-7\", \"haiku-4-5\",\n \"gpt-5-5\", \"gpt-5-4\", \"gpt-5-4-mini\",\n \"gemini-3-1\", \"gemini-3-flash\", \"gemini-3-1-flash\",\n \"grok-4-3\", \"grok-4-1-fast\",\n]);\n\nfunction clamp(s: string, max: number): string {\n if (s.length <= max) return s;\n return s.slice(0, max).trim();\n}\n\nfunction extractJson<T>(raw: string): T | null {\n const fence = /```(?:json)?\\s*([\\s\\S]*?)```/i.exec(raw);\n const candidate = fence ? fence[1] : raw;\n if (!candidate) return null;\n const start = candidate.indexOf(\"{\");\n if (start === -1) return null;\n let depth = 0;\n let end = -1;\n for (let i = start; i < candidate.length; i++) {\n const ch = candidate[i];\n if (ch === \"{\") depth++;\n else if (ch === \"}\") {\n depth--;\n if (depth === 0) { end = i; break; }\n }\n }\n if (end === -1) return null;\n try {\n return JSON.parse(candidate.slice(start, end + 1)) as T;\n } catch {\n return null;\n }\n}\n\n/** Validate + coerce a generated spec. Returns null only if essential\n * fields are missing — name, bio, instruction. Other fields are\n * trimmed / defaulted. */\nexport function parseAgentSpec(raw: string): AgentSpec | null {\n const parsed = extractJson<Record<string, unknown>>(raw);\n if (!parsed) return null;\n\n const name = typeof parsed.name === \"string\" ? parsed.name.trim() : \"\";\n const bio = typeof parsed.bio === \"string\" ? parsed.bio.trim() : \"\";\n const instruction = typeof parsed.instruction === \"string\" ? parsed.instruction.trim() : \"\";\n if (name.length < 2 || bio.length < 8 || instruction.length < 1) return null;\n\n const handleRaw = typeof parsed.handle === \"string\" ? parsed.handle.trim() : \"\";\n const handle = handleRaw\n ? handleRaw.replace(/^\\/+/, \"\").toLowerCase().replace(/[^a-z0-9_]/g, \"_\").slice(0, 18)\n : name.toLowerCase().replace(/[^a-z0-9]/g, \"_\").slice(0, 18);\n\n const roleTagRaw = typeof parsed.roleTag === \"string\" ? parsed.roleTag.trim().toLowerCase() : \"\";\n const roleTag = roleTagRaw && roleTagRaw.length >= 3 && roleTagRaw.length <= 32\n ? roleTagRaw\n : \"custom\";\n\n const coverQuote = typeof parsed.coverQuote === \"string\" ? clamp(parsed.coverQuote.trim(), 200) : \"\";\n const modelRaw = typeof parsed.modelV === \"string\" ? parsed.modelV.trim() : \"\";\n const modelV = ALLOWED_MODELS.has(modelRaw) ? modelRaw : \"opus-4-7\";\n const ability = parseAbility(parsed.ability);\n\n return {\n name: clamp(name, 32),\n handle: handle || \"new_agent\",\n roleTag,\n bio: clamp(bio, 280),\n coverQuote,\n instruction: clamp(instruction, 4000),\n modelV,\n ability,\n };\n}\n","/**\n * /api/avatar — server-side avatar seed generation.\n *\n * The pixel-art SVG itself is generated client-side by the AvatarSkill\n * (public/avatar-skill.js) so the visual style stays consistent across\n * surfaces. This endpoint just turns a director's name+bio into a\n * \"vibe seed\" via the LLM so the resulting avatar reflects the\n * persona instead of being a hash of name+bio.\n *\n * No LLM key configured? Falls back to a deterministic seed so the UI\n * always works even without API keys.\n */\nimport { Hono } from \"hono\";\n\nimport { callLLM } from \"../ai/adapter.js\";\n\nconst NAME_MAX = 80;\nconst BIO_MAX = 400;\nconst VIBE_MAX = 200;\n\nexport function avatarRouter() {\n const r = new Hono();\n\n r.post(\"/generate\", async (c) => {\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: \"invalid JSON body\" }, 400);\n }\n\n const b = (body ?? {}) as { name?: unknown; bio?: unknown };\n const name = (typeof b.name === \"string\" ? b.name : \"\").trim().slice(0, NAME_MAX);\n const bio = (typeof b.bio === \"string\" ? b.bio : \"\").trim().slice(0, BIO_MAX);\n if (!name) return c.json({ error: \"name required\" }, 400);\n\n // Default fallback · deterministic seed if the LLM call doesn't\n // land. A timestamp suffix makes successive clicks vary even\n // when name+bio haven't changed.\n let seed = `${name}|${bio}|${Date.now().toString(36)}`;\n let vibe = \"\";\n let usedLLM = false;\n\n try {\n const out = await callLLM({\n // Haiku is the cheapest tier and gives plenty of variety for a\n // 12-word descriptor. If the user removed Haiku from their key\n // set, the call throws and we fall back below.\n modelV: \"haiku-4-5\",\n temperature: 0.95,\n maxTokens: 60,\n messages: [\n {\n role: \"user\",\n content:\n \"Imagine an 8-bit pixel-art avatar for this character. \" +\n \"Reply with a single short comma-separated list of visual traits (hair color, hair style, skin tone, eye color, shirt color, overall vibe). \" +\n \"One line. Max 12 words. No prose.\\n\\n\" +\n `Name: ${name}\\nBio: ${bio}`,\n },\n ],\n });\n vibe = String(out || \"\").trim().replace(/\\s+/g, \" \").slice(0, VIBE_MAX);\n if (vibe) {\n // The vibe becomes the deterministic seed for AvatarSkill on\n // the client. Same vibe → same avatar; LLM gives the variation.\n seed = `${name}::${vibe}`;\n usedLLM = true;\n }\n } catch (e) {\n // Swallow — caller falls back to the deterministic seed above.\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[avatar] LLM call failed (using deterministic seed): ${msg}\\n`);\n }\n\n return c.json({ seed, vibe, usedLLM });\n });\n\n return r;\n}\n","/**\n * /api/briefs · top-level brief access.\n *\n * GET /api/briefs/:id → the full brief\n * GET /api/briefs/:id/status → { generating, hasBody, completed }\n * DELETE /api/briefs/:id → permanently remove this brief\n *\n * Status is used by the frontend to detect \"zombie\" placeholders left\n * behind when the user refreshes the browser mid-generation. Delete is\n * used by the brief-card to remove a specific report version from the\n * room's history.\n */\nimport { unlink } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { Hono } from \"hono\";\n\nimport { getBriefGenerationState, isBriefGenerating } from \"../orchestrator/brief.js\";\nimport { deleteBrief, getBrief, listAllBriefs } from \"../storage/briefs.js\";\nimport { ensureBoardroomDir } from \"../utils/paths.js\";\n\nexport function briefsRouter(): Hono {\n const r = new Hono();\n\n // Cross-room brief index for the All Reports page · joins the parent\n // room's name/subject so cards render with full context in one call.\n // Newest first. The body_md / body_json columns ride along so the\n // client can preview the bottom-line judgement without a second\n // request per brief; the route still drops bodies for any brief\n // that's still generating to avoid surfacing partial output.\n r.get(\"/\", (c) => {\n const briefs = listAllBriefs().filter((b) => !isBriefGenerating(b.id) && b.bodyMd && b.bodyMd.trim());\n return c.json({ briefs });\n });\n\n r.get(\"/:id\", (c) => {\n const b = getBrief(c.req.param(\"id\"));\n if (!b) return c.json({ error: \"not found\" }, 404);\n return c.json(b);\n });\n\n r.get(\"/:id/status\", (c) => {\n const id = c.req.param(\"id\");\n const b = getBrief(id);\n if (!b) return c.json({ error: \"not found\" }, 404);\n const hasBody = !!(b.bodyMd && b.bodyMd.trim());\n const generating = isBriefGenerating(id);\n const completed = hasBody && !generating;\n // When the pipeline is still running, hand back the per-stage\n // snapshot so a freshly-loaded client can rehydrate the loading\n // UI (which stage is active, when each stage started, the ETA\n // window) instead of watching a frozen blank card until the\n // brief finishes streaming.\n const state = generating ? getBriefGenerationState(id) : null;\n return c.json({ generating, hasBody, completed, state });\n });\n\n r.delete(\"/:id\", async (c) => {\n const id = c.req.param(\"id\");\n if (isBriefGenerating(id)) {\n return c.json({ error: \"brief is still generating; wait for it to finish\" }, 409);\n }\n const ok = deleteBrief(id);\n if (!ok) return c.json({ error: \"not found\" }, 404);\n // Best-effort cleanup of the markdown export. If the file isn't\n // there (it might never have been written) we just move on.\n try {\n const dirs = ensureBoardroomDir();\n await unlink(join(dirs.briefs, `${id}.md`));\n } catch { /* swallow ENOENT etc — not load-bearing */ }\n return c.json({ ok: true });\n });\n\n return r;\n}\n","/**\n * Brief generation pipeline · 3-stage system-skill flow.\n *\n * Stage 1 · per-director extract · parallel haiku calls\n * Stage 2 · chair cluster/scaffold · single sonnet call\n * Stage 3 · chair final write · streaming opus → sonnet fallback\n *\n * Triggered when a room is adjourned (or post-hoc via /brief). Streams\n * stage-3 tokens to the room SSE bus and writes the final markdown to\n * ~/.boardroom/briefs/{briefId}.md so users have a portable export.\n *\n * Emits SSE events on the room bus:\n * brief-started · once, when stage 1 begins\n * brief-token · each delta during stage 3\n * brief-final · once, with { briefId, title }\n * brief-error · on unrecoverable failure\n */\nimport { writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { callLLMStream, callLLMWithUsage } from \"../ai/adapter.js\";\nimport { effectiveDefaultModel, utilityModelFor } from \"../ai/availability.js\";\nimport {\n buildExtractMessages,\n buildScaffoldMessages,\n buildWriteMessages,\n parseDirectorSignals,\n parseScaffold,\n type BriefScaffold,\n type DirectorSignals,\n} from \"../ai/prompts/brief-stages.js\";\nimport {\n buildComposerMessages,\n defaultComposition,\n parseComposerOutput,\n type ComposerResult,\n} from \"../ai/prompts/composer.js\";\nimport {\n extractBriefTitle,\n type BriefStyle,\n} from \"../ai/prompts/brief.js\";\nimport { isModelV, type ModelV } from \"../ai/registry.js\";\nimport { getAgent, getChairAgent, incrementAgentTokens, type Agent } from \"../storage/agents.js\";\nimport { listMessages } from \"../storage/messages.js\";\nimport { getRoom, listRoomMembers } from \"../storage/rooms.js\";\nimport { insertBrief, updateBriefBody, updateBriefCompose } from \"../storage/briefs.js\";\nimport { ensureBoardroomDir } from \"../utils/paths.js\";\nimport { estimateTokens } from \"../utils/tokens.js\";\n\nimport { roomBus } from \"./stream.js\";\n\n/** Detect the user's question language from the room subject. CJK\n * Unified Ideographs ⇒ Chinese (\"zh\"); else \"en\". Cheap heuristic — the\n * user's product is currently bilingual zh / en, so this is sufficient\n * to align report content language with how the question was asked. */\nfunction detectLanguage(text: string): \"zh\" | \"en\" {\n return /[一-鿿]/.test(text || \"\") ? \"zh\" : \"en\";\n}\n\n/** Bill an LLM call's tokens to the chair. No-op if usage is null\n * (provider didn't surface it) or chair is missing. */\nfunction billChair(chairId: string | null, usage: { totalTokens: number } | null): void {\n if (!chairId || !usage || !usage.totalTokens) return;\n incrementAgentTokens(chairId, usage.totalTokens);\n}\n\n/** Models per stage · resolved against the user's currently-configured\n * keys. Each pipeline stage tries the carrier-appropriate \"cheap\" or\n * \"flagship\" model first, then falls back through the rest of the\n * list. The lists were previously hardcoded to Anthropic IDs, which\n * meant a user with only an OpenAI key got \"no reachable model\" on\n * every stage. Resolved lazily (per call) so a key change mid-session\n * is honoured.\n *\n * Tier mapping:\n * cheap (extraction / composition) · `utilityModelFor()` →\n * haiku-4-5 (OR), sonnet-4-6 (Anthropic-direct), gpt-5-4-mini\n * (OpenAI), gemini-3-1-flash (Google), grok-4-mini (xAI), …\n * flagship (scaffolding / write-out) · `effectiveDefaultModel()` →\n * opus-4-7 (OR), sonnet-4-6 (Anthropic), gpt-5-5 (OpenAI),\n * gemini-3-1 (Google), grok-4 (xAI). */\nfunction stageCheapList(): ModelV[] {\n // Cheap-first, with flagship as a safety net. Dedup so a single-\n // carrier user (where utility == flagship in degenerate cases)\n // doesn't waste a retry on the same model twice.\n const out: ModelV[] = [];\n const cheap = utilityModelFor();\n if (cheap) out.push(cheap);\n const flagship = effectiveDefaultModel();\n if (flagship && !out.includes(flagship)) out.push(flagship);\n return out;\n}\n\nfunction stageFlagshipList(): ModelV[] {\n // Flagship-first, with cheap as a safety net (Stage 2 scaffolding /\n // Stage 3 write-out can run on the cheap tier in a pinch — quality\n // dips but the pipeline still completes).\n const out: ModelV[] = [];\n const flagship = effectiveDefaultModel();\n if (flagship) out.push(flagship);\n const cheap = utilityModelFor();\n if (cheap && !out.includes(cheap)) out.push(cheap);\n return out;\n}\n\n/** Stage 2 retry budget. Each retry bumps temperature so the LLM is\n * more likely to break out of a malformed-JSON local minimum. */\nconst STAGE_2_RETRIES = 3;\nconst STAGE_2_TEMPERATURES = [0.2, 0.4, 0.6];\n\ninterface GenerateOpts {\n roomId: string;\n /** Retained for backwards compat with callers that still pass a style.\n * v1 has one standard layout — the value is recorded but ignored. */\n style?: BriefStyle;\n /** Optional supplementary perspective the user asked the chair to\n * weave into the regenerated report. Only stages 2 + 3 see it —\n * stage 1's per-director extraction stays independent. */\n supplement?: string;\n}\n\n/** In-flight pipeline state, keyed by briefId. Lives for the duration\n * of runPipeline (added before the pipeline kicks off, deleted in\n * the finally block whether it succeeded, errored, or threw).\n *\n * We store the full per-stage progress in here — not just the\n * briefId — so a fresh browser session that lands mid-generation\n * can rehydrate the loading UI (which stage is active right now,\n * when did each stage start, what ETA window applies) instead of\n * watching a frozen blank page until the pipeline finishes. The\n * state is broadcast via the existing SSE events; this map just\n * preserves the most recent snapshot so /api/briefs/:id/status can\n * hand it back on demand. */\ninterface BriefStageSnapshot {\n status: StageStatus;\n startedAt: number;\n finishedAt: number | null;\n detail: string;\n progress: StageProgress | null;\n etaSec: StageEta | null;\n}\ninterface BriefGenerationState {\n briefId: string;\n roomId: string;\n style: BriefStyle;\n chairName: string;\n language: \"zh\" | \"en\";\n pipelineStartedAt: number;\n /** Per-stage progress · seeded as undefined entries until the\n * stage first transitions to active. The frontend treats absent\n * keys as \"still pending\". */\n stages: Partial<Record<StageKey, BriefStageSnapshot>>;\n}\nconst inFlightBriefs = new Map<string, BriefGenerationState>();\n\nexport function isBriefGenerating(briefId: string): boolean {\n return inFlightBriefs.has(briefId);\n}\n\n/** Return the full pipeline snapshot for a brief currently being\n * generated, or `null` if it isn't in flight. Used by\n * `/api/briefs/:id/status` to let a freshly-loaded client rehydrate\n * the loading UI mid-pipeline. */\nexport function getBriefGenerationState(briefId: string): BriefGenerationState | null {\n return inFlightBriefs.get(briefId) ?? null;\n}\n\nexport async function generateBrief(opts: GenerateOpts): Promise<{ briefId: string }> {\n const { roomId } = opts;\n const style: BriefStyle = opts.style ?? \"mckinsey\";\n\n const room = getRoom(roomId);\n if (!room) throw new Error(`room not found: ${roomId}`);\n\n const memberRows = listRoomMembers(roomId);\n const members: Agent[] = memberRows\n .map((m) => getAgent(m.agentId))\n .filter((a): a is Agent => a !== null);\n const transcript = listMessages(roomId).filter((m) => m.authorKind !== \"system\");\n\n const placeholder = insertBrief({\n roomId,\n style,\n title: room.subject,\n bodyMd: \"\",\n supplement: opts.supplement,\n });\n\n // Capture the chair + room language up front so the rehydration\n // endpoint can hand them to a refreshed browser session even\n // before the first SSE event fires.\n const chairForState = getChairAgent();\n const inferredLang: \"zh\" | \"en\" = /[一-鿿]/.test(room.subject || \"\") ? \"zh\" : \"en\";\n inFlightBriefs.set(placeholder.id, {\n briefId: placeholder.id,\n roomId,\n style,\n chairName: chairForState?.name || \"Chair\",\n language: inferredLang,\n pipelineStartedAt: Date.now(),\n stages: {},\n });\n void runPipeline({\n briefId: placeholder.id,\n roomId,\n style,\n members,\n transcript,\n room,\n supplement: opts.supplement,\n }).finally(() => {\n inFlightBriefs.delete(placeholder.id);\n });\n\n return { briefId: placeholder.id };\n}\n\ninterface PipelineArgs {\n briefId: string;\n roomId: string;\n style: BriefStyle;\n members: Agent[];\n transcript: ReturnType<typeof listMessages>;\n room: NonNullable<ReturnType<typeof getRoom>>;\n supplement?: string;\n}\n\n/** Stage labels that ship to the UI checklist. Frontend matches on\n * `stage` and renders the canonical label. `compose` is the new\n * Stage 1.5 — older clients that don't recognize it just skip the row. */\ntype StageKey = \"extract\" | \"compose\" | \"scaffold\" | \"write\";\ntype StageStatus = \"active\" | \"done\";\n\ninterface StageProgress {\n current: number;\n total: number;\n}\n\ninterface StageEta {\n /** Lower bound seconds — happy-path completion. */\n lo: number;\n /** Upper bound seconds — slow-but-still-typical case. Beyond this the\n * UI drops the ETA and shows just elapsed. */\n hi: number;\n}\n\nfunction emitStage(\n roomId: string,\n briefId: string,\n stage: StageKey,\n status: StageStatus,\n detail?: string,\n progress?: StageProgress,\n etaSec?: StageEta,\n): void {\n // Mirror the event into the in-flight state so a refreshed client\n // can rehydrate the loading UI on page load. We capture startedAt\n // on the first transition to active and finishedAt when it flips\n // to done — same shape the frontend already builds from SSE.\n const state = inFlightBriefs.get(briefId);\n if (state) {\n const now = Date.now();\n const prev = state.stages[stage];\n if (status === \"active\") {\n state.stages[stage] = {\n status: \"active\",\n startedAt: prev && prev.status === \"active\" ? prev.startedAt : now,\n finishedAt: null,\n detail: detail ?? \"\",\n progress: progress ?? null,\n etaSec: etaSec ?? prev?.etaSec ?? null,\n };\n } else if (status === \"done\") {\n state.stages[stage] = {\n status: \"done\",\n startedAt: prev?.startedAt ?? now,\n finishedAt: now,\n detail: detail ?? prev?.detail ?? \"\",\n progress: progress ?? prev?.progress ?? null,\n etaSec: etaSec ?? prev?.etaSec ?? null,\n };\n }\n }\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"brief-stage\",\n payload: { briefId, stage, status, detail, progress, etaSec },\n createdAt: Date.now(),\n });\n}\n\n/* ─────────────────────── ETA · token-based estimator ─────────────────────\n *\n * Wall-clock time for an LLM call decomposes into:\n *\n * total ≈ base_overhead (network, queue, cold start)\n * + ttft_per_kt * input_kt (time-to-first-token scales with input)\n * + output_tokens / tps (streaming throughput)\n *\n * Calibration notes (after observing a stage-2 run that took ~200s vs an\n * earlier ETA of 24-50s):\n *\n * · Sonnet generating STRUCTURED JSON runs at roughly half the speed\n * of free-prose generation (constrained-decoding overhead). The\n * useful tps for our scaffold output is ~40, not 100.\n * · Opus is similarly slowed when producing rich markdown with tables\n * and mermaid blocks. Real-world ~25-30 tps.\n * · Output token volumes grow super-linearly with section count once\n * recommendations / pre-mortem / new questions all kick in.\n * · Stage 2 retries up to 3× on parse failure — half of slow runs\n * have at least one retry, so the upper bound carries a × 1.8\n * factor to absorb that without lying to the user.\n *\n * To compensate for environment variance (provider routing, region,\n * OpenRouter vs direct), stage 2 and 3 ETAs are also scaled by an\n * observed calibration factor measured from stage 1's actual time vs\n * predicted. See `runPipeline` for the in-flight calibration. */\nconst TPS_BY_MODEL: Record<string, number> = {\n haiku: 130, // haiku-4-5 — was 180, brought down for realism\n sonnet: 45, // sonnet-4-6 — structured JSON output, not free prose\n opus: 28, // opus-4-7 — rich markdown with tables / mermaid\n};\nconst BASE_OVERHEAD_S = 1.0;\nconst TTFT_S_PER_KT = 0.35;\n\nfunction llmTimeSec(inputTokens: number, outputTokens: number, modelKey: keyof typeof TPS_BY_MODEL): number {\n const tps = TPS_BY_MODEL[modelKey];\n return BASE_OVERHEAD_S + TTFT_S_PER_KT * (inputTokens / 1000) + outputTokens / tps;\n}\n\nfunction asEta(seconds: number, loFactor = 0.75, hiFactor = 1.5): StageEta {\n return {\n lo: Math.max(2, Math.round(seconds * loFactor)),\n hi: Math.max(4, Math.round(seconds * hiFactor)),\n };\n}\n\n/** Pre-computed system-prompt token estimates. The actual text lives\n * in brief-stages.ts; recomputing per call is cheap (~3000-char\n * string) but caching keeps the hot path tidy. */\nconst SYS_TOKENS = {\n // Filled in lazily on first call so we don't import the prompt\n // strings here just for measurement.\n extract: 0,\n scaffold: 0,\n write: 0,\n};\n\nfunction ensureSysTokens(): void {\n if (SYS_TOKENS.scaffold > 0) return;\n // Defer the import to avoid an import cycle and keep startup cheap.\n // We approximate from typical sizes seen in measurement (chars × 0.27\n // for a mostly-English prompt with some structure punctuation).\n // EXTRACT_SYSTEM is per-director (~1.5k chars), SCAFFOLD_SYSTEM ~9k\n // chars, WRITE_SYSTEM ~7.7k chars.\n SYS_TOKENS.extract = 450;\n SYS_TOKENS.scaffold = 2300;\n SYS_TOKENS.write = 1950;\n}\n\ninterface Stage1EtaArgs {\n directors: Agent[];\n transcript: ReturnType<typeof listMessages>;\n}\nfunction estimateStage1Eta(args: Stage1EtaArgs): StageEta {\n ensureSysTokens();\n // Stage 1 runs in parallel; wall-clock ≈ slowest single call, plus a\n // small concurrency-overhead bump for ≥ 4 directors.\n let slowest = 0;\n for (const d of args.directors) {\n const own = args.transcript.filter(\n (m) => m.authorKind === \"agent\" && m.authorId === d.id,\n );\n if (!own.length) continue;\n const ownText = own.map((m) => m.body || \"\").join(\"\\n\\n\");\n const inputTokens = SYS_TOKENS.extract + estimateTokens(ownText) + 80;\n const outputTokens = 400; // 3-4 signals × ~120 tokens each\n const t = llmTimeSec(inputTokens, outputTokens, \"haiku\");\n if (t > slowest) slowest = t;\n }\n if (slowest === 0) return { lo: 2, hi: 5 }; // no speakers\n // Mild concurrency overhead when many directors run in parallel.\n const speakers = args.directors.filter((d) =>\n args.transcript.some((m) => m.authorKind === \"agent\" && m.authorId === d.id),\n ).length;\n if (speakers >= 4) slowest *= 1.15;\n return asEta(slowest);\n}\n\ninterface Stage2EtaArgs {\n perDirectorSignals: DirectorSignals[];\n members: Agent[];\n}\nfunction estimateStage2Eta(args: Stage2EtaArgs): StageEta {\n ensureSysTokens();\n // Input: SCAFFOLD_SYSTEM + signals block + memberlist + room context.\n let signalChars = 0;\n let signalCount = 0;\n for (const d of args.perDirectorSignals) {\n for (const s of d.signals) {\n signalChars += (s.text || \"\").length;\n signalCount++;\n }\n }\n const inputTokens =\n SYS_TOKENS.scaffold +\n Math.ceil(signalChars * 0.4) /* signals · mixed-language average */ +\n signalCount * 30 /* lens tag + indices per signal */ +\n args.members.length * 50 /* memberlist */ +\n 300; /* room ctx + framing + JSON example noise */\n // Output is the load-bearing piece. A 12-section scaffold for a\n // substantive room produces 5000-7000 tokens of structured JSON:\n // 3 headline findings × ~300 = 900\n // 5 recommendations × ~150 = 750\n // 3 positions × ~120 = 360\n // 2 convergence × ~150 = 300\n // 1 divergence (4 rows) = 250\n // 3 pre-mortem × ~100 = 300\n // 3 new questions × ~80 = 240\n // 1 planning assumption = 120\n // bottom line + frame shift = 160\n // visuals (~1.5 × 200) = 300\n // 3 open questions × ~50 = 150\n // misc framing / boilerplate ≈ 800\n // ──── total ≈ 4600, plus signal-driven content\n const outputTokens = Math.max(3500, signalCount * 220 + 2500);\n const t = llmTimeSec(inputTokens, outputTokens, \"sonnet\");\n // Upper bound × 1.8 accounts for stage 2's retry-on-parse-failure\n // behaviour — when a retry fires, total wall time roughly doubles.\n return asEta(t, 0.65, 1.8);\n}\n\ninterface Stage3EtaArgs {\n scaffold: BriefScaffold;\n perDirectorSignals: DirectorSignals[];\n members: Agent[];\n}\nfunction estimateStage3Eta(args: Stage3EtaArgs): StageEta {\n ensureSysTokens();\n // Input: WRITE_SYSTEM + scaffold dump + signals refs.\n // Scaffold dump size scales with section content. Estimate by\n // serialising it back to JSON and counting.\n const scaffoldText = JSON.stringify(args.scaffold);\n // Signals dump: ~80 tokens per signal in the rendered \"[lens] director: text\" form.\n let signalsTokens = 0;\n for (const d of args.perDirectorSignals) signalsTokens += d.signals.length * 80;\n const inputTokens =\n SYS_TOKENS.write +\n estimateTokens(scaffoldText) +\n signalsTokens +\n args.members.length * 50 +\n 250;\n // Output: 12 sections; size roughly correlates with scaffold richness.\n const sectionsPresent =\n 1 /* bottom line */ +\n 1 /* frame shift */ +\n args.scaffold.headlineFindings.length +\n (args.scaffold.convergence.length ? 1 : 0) +\n (args.scaffold.divergence ? 1 : 0) +\n (args.scaffold.positions.length ? 1 : 0) +\n (args.scaffold.visuals.length ? 1 : 0) +\n (args.scaffold.recommendations.length ? 1 : 0) +\n (args.scaffold.preMortem.length ? 1 : 0) +\n (args.scaffold.newQuestions.length ? 1 : 0) +\n (args.scaffold.planningAssumption ? 1 : 0) +\n (args.scaffold.openQuestions.length ? 1 : 0);\n const outputTokens = Math.max(3500, sectionsPresent * 350 + 1500);\n // Opus is the primary writer (slower but stronger); fallback to sonnet.\n const t = llmTimeSec(inputTokens, outputTokens, \"opus\");\n return asEta(t, 0.7, 1.5);\n}\n\nasync function runPipeline(args: PipelineArgs): Promise<void> {\n const { briefId, roomId, style, members, transcript, room, supplement } = args;\n\n const directors = members.filter((m) => m.roleKind === \"director\");\n const chair = members.find((m) => m.roleKind === \"moderator\") ?? null;\n const chairId = chair?.id ?? null;\n const chairName = chair?.name ?? \"Chair\";\n const language = detectLanguage(room.subject);\n\n // Surface \"started\" immediately so the UI can render the placeholder\n // card. Includes chairName + language so the UI can show \"{Chair} is\n // preparing the minutes…\" in the right language. The model used in\n // stage 3 is reported in brief-final.\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"brief-started\",\n payload: { briefId, style, chairName, language },\n createdAt: Date.now(),\n });\n\n let buf = \"\";\n let stage3Model: ModelV | null = null;\n let pipelineError: string | null = null;\n\n try {\n // ── Stage 1 · per-director extract (parallel) ────────────────────────\n const totalDirectors = directors.filter(\n (d) =>\n transcript.some((m) => m.authorKind === \"agent\" && m.authorId === d.id),\n ).length;\n const stage1Eta = estimateStage1Eta({ directors, transcript });\n const stage1StartedAt = Date.now();\n emitStage(\n roomId,\n briefId,\n \"extract\",\n \"active\",\n `${totalDirectors} director${totalDirectors === 1 ? \"\" : \"s\"}`,\n { current: 0, total: totalDirectors },\n stage1Eta,\n );\n const perDirectorSignals = await runStage1(\n directors,\n transcript,\n room,\n language,\n chairId,\n (current) => {\n emitStage(\n roomId,\n briefId,\n \"extract\",\n \"active\",\n `${current}/${totalDirectors} director${totalDirectors === 1 ? \"\" : \"s\"}`,\n { current, total: totalDirectors },\n stage1Eta,\n );\n },\n );\n emitStage(roomId, briefId, \"extract\", \"done\");\n\n // In-flight calibration · compare stage 1's measured time to its\n // predicted mid-band. If the user's environment is slower (network,\n // OpenRouter routing, regional latency), stages 2 and 3 will be\n // similarly slower — multiply their ETAs by the same factor so the\n // displayed range matches reality. Clamped to [0.5, 3.0] to absorb\n // measurement noise without runaway scaling.\n const stage1ActualSec = (Date.now() - stage1StartedAt) / 1000;\n const stage1PredictedMid = (stage1Eta.lo + stage1Eta.hi) / 2;\n let calibration = stage1PredictedMid > 0.5 ? stage1ActualSec / stage1PredictedMid : 1;\n calibration = Math.max(0.5, Math.min(3, calibration));\n\n // ── Stage 1.5 · composer (cheap pick of spine + components) ──────────\n emitStage(roomId, briefId, \"compose\", \"active\", undefined, undefined, { lo: 1, hi: 4 });\n const composition = await runComposer({\n chairId,\n room,\n members,\n perDirectorSignals,\n language,\n supplement,\n });\n updateBriefCompose(briefId, {\n spine: composition.spine,\n components: composition.components,\n composerRationale: composition.rationale || null,\n subjectType: composition.subjectType,\n });\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"brief-compose\",\n payload: {\n briefId,\n spine: composition.spine,\n components: composition.components,\n rationale: composition.rationale,\n subjectType: composition.subjectType,\n fromComposer: composition.fromComposer,\n },\n createdAt: Date.now(),\n });\n emitStage(roomId, briefId, \"compose\", \"done\");\n const pickedKinds = composition.components.map((c) => c.kind);\n\n // ── Stage 2 · chair cluster + scaffold ───────────────────────────────\n // Quality over silent degradation · retry up to 3 times with rising\n // temperature. If the structured scaffold can't be produced, surface\n // a clear error rather than falling through to a flat 3-section brief.\n const stage2EtaRaw = estimateStage2Eta({ perDirectorSignals, members });\n const stage2Eta: StageEta = {\n lo: Math.max(2, Math.round(stage2EtaRaw.lo * calibration)),\n hi: Math.max(4, Math.round(stage2EtaRaw.hi * calibration)),\n };\n emitStage(roomId, briefId, \"scaffold\", \"active\", undefined, undefined, stage2Eta);\n const scaffold = await runStage2({\n chair,\n chairId,\n room,\n members,\n perDirectorSignals,\n language,\n supplement,\n picked: pickedKinds,\n });\n emitStage(roomId, briefId, \"scaffold\", \"done\");\n\n if (!scaffold) {\n pipelineError =\n \"Report writer couldn't structure this room (3 retries failed). Try regenerating, or shorten the conversation.\";\n } else {\n // ── Stage 3 · chair final write (streaming) ────────────────────────\n // Use the same calibration factor measured from stage 1 — the\n // scaling reflects the user's network / provider latency, which\n // applies to all stages.\n const stage3EtaRaw = estimateStage3Eta({ scaffold, perDirectorSignals, members });\n const stage3Eta: StageEta = {\n lo: Math.max(2, Math.round(stage3EtaRaw.lo * calibration)),\n hi: Math.max(4, Math.round(stage3EtaRaw.hi * calibration)),\n };\n emitStage(roomId, briefId, \"write\", \"active\", undefined, undefined, stage3Eta);\n const r3 = await runStage3Streaming({\n roomId,\n briefId,\n chairId,\n room,\n members,\n scaffold,\n perDirectorSignals,\n language,\n supplement,\n picked: pickedKinds,\n });\n buf = r3.body;\n stage3Model = r3.model;\n if (!buf.length) {\n pipelineError = r3.error ?? \"stage 3 produced no output\";\n } else {\n // Append the auto-generated Methodology footer · this is\n // deterministic data (signal counts, lens distribution, models),\n // so we don't burn LLM tokens on it.\n const methodology = buildMethodologyFooter({\n perDirectorSignals,\n stage3Model,\n language,\n startedAt: Date.now(), // approximate · acceptable for a footer\n });\n const sep = buf.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n buf += sep + methodology;\n updateBriefBody(briefId, buf);\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"brief-token\",\n payload: { briefId, delta: sep + methodology },\n createdAt: Date.now(),\n });\n emitStage(roomId, briefId, \"write\", \"done\");\n }\n }\n } catch (e) {\n pipelineError = e instanceof Error ? e.message : String(e);\n }\n\n if (pipelineError && !buf.length) {\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"brief-error\",\n payload: { briefId, message: pipelineError },\n createdAt: Date.now(),\n });\n return;\n }\n\n const title = extractBriefTitle(buf, room.subject);\n updateBriefBody(briefId, buf, title);\n\n try {\n const dirs = ensureBoardroomDir();\n const path = join(dirs.briefs, `${briefId}.md`);\n await writeFile(path, buf, \"utf8\");\n } catch (e) {\n process.stderr.write(`[brief] export failed: ${e instanceof Error ? e.message : String(e)}\\n`);\n }\n\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"brief-final\",\n payload: { briefId, title, modelV: stage3Model ?? undefined },\n createdAt: Date.now(),\n });\n}\n\n/* ─────────────────────────── Stage 1 ──────────────────────────────────── */\n\nasync function runStage1(\n directors: Agent[],\n transcript: ReturnType<typeof listMessages>,\n room: NonNullable<ReturnType<typeof getRoom>>,\n language: \"zh\" | \"en\",\n chairId: string | null,\n onDirectorComplete?: (current: number) => void,\n): Promise<DirectorSignals[]> {\n if (!directors.length) return [];\n\n let completed = 0;\n const reportComplete = () => {\n if (onDirectorComplete) {\n completed += 1;\n onDirectorComplete(completed);\n }\n };\n\n const tasks = directors.map(async (director) => {\n const ownMessages = transcript.filter(\n (m) => m.authorKind === \"agent\" && m.authorId === director.id,\n );\n if (!ownMessages.length) {\n // Director didn't speak — don't count toward progress (the total\n // already excludes silent directors).\n return { directorId: director.id, directorName: director.name, signals: [] };\n }\n const messages = buildExtractMessages({ director, ownMessages, room, language });\n\n for (const modelV of stageCheapList()) {\n if (!isModelV(modelV)) continue;\n try {\n const { text: raw, usage } = await callLLMWithUsage({\n modelV,\n messages,\n temperature: 0.2,\n maxTokens: 800,\n });\n billChair(chairId, usage);\n const result = parseDirectorSignals(raw, director);\n reportComplete();\n return result;\n } catch (e) {\n process.stderr.write(\n `[brief.stage1] ${director.handle} on ${modelV} failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n }\n }\n // All models failed for this director — count it complete so the\n // bar still reaches its total, but emit empty signals.\n reportComplete();\n return { directorId: director.id, directorName: director.name, signals: [] };\n });\n\n const settled = await Promise.allSettled(tasks);\n return settled\n .map((r) => (r.status === \"fulfilled\" ? r.value : null))\n .filter((x): x is DirectorSignals => x !== null);\n}\n\n/* ─────────────────────────── Stage 1.5 · composer ───────────────────────\n *\n * Picks the spine and a subset of components based on the room's subject\n * + the per-director signals. Cheap haiku call. Falls back to the default\n * 12-section preset on any failure (no key, parse error, validation fail) —\n * so the rest of the pipeline always has a valid composition to work with.\n */\ninterface ComposerArgs {\n chairId: string | null;\n room: NonNullable<ReturnType<typeof getRoom>>;\n members: Agent[];\n perDirectorSignals: DirectorSignals[];\n language: \"zh\" | \"en\";\n supplement?: string;\n}\n\nasync function runComposer(args: ComposerArgs): Promise<ComposerResult> {\n // No signals → no compose. Fall back to the safe set so Stage 2/3 still\n // try to produce something (legacy behaviour).\n const totalSignals = args.perDirectorSignals.reduce(\n (acc, d) => acc + d.signals.length,\n 0,\n );\n if (totalSignals === 0) return defaultComposition(\"no signals — fallback preset\");\n\n const messages = buildComposerMessages({\n room: args.room,\n members: args.members,\n perDirectorSignals: args.perDirectorSignals,\n language: args.language,\n supplement: args.supplement,\n });\n\n for (const modelV of stageCheapList()) {\n if (!isModelV(modelV)) continue;\n try {\n const { text: raw, usage } = await callLLMWithUsage({\n modelV,\n messages,\n temperature: 0.2,\n maxTokens: 600,\n });\n billChair(args.chairId, usage);\n const parsed = parseComposerOutput(raw);\n if (parsed) return parsed;\n process.stderr.write(`[brief.compose] ${modelV} produced unusable composition; trying next model\\n`);\n } catch (e) {\n process.stderr.write(\n `[brief.compose] ${modelV} failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n }\n }\n return defaultComposition(\"composer call failed — fallback preset\");\n}\n\n/* ─────────────────────────── Stage 2 ──────────────────────────────────── */\n\ninterface Stage2Args {\n chair: Agent | null;\n chairId: string | null;\n room: NonNullable<ReturnType<typeof getRoom>>;\n members: Agent[];\n perDirectorSignals: DirectorSignals[];\n language: \"zh\" | \"en\";\n supplement?: string;\n /** Composer's component picks (Stage 1.5). When empty, Stage 2 falls\n * back to filling all 12 sections — preserves legacy behaviour. */\n picked?: readonly string[];\n}\n\nasync function runStage2(args: Stage2Args): Promise<BriefScaffold | null> {\n const totalSignals = args.perDirectorSignals.reduce(\n (acc, d) => acc + d.signals.length,\n 0,\n );\n if (totalSignals === 0) return null;\n\n const messages = buildScaffoldMessages({\n chair: args.chair,\n room: args.room,\n members: args.members,\n perDirectorSignals: args.perDirectorSignals,\n language: args.language,\n supplement: args.supplement,\n picked: args.picked,\n });\n\n // Try each model up to STAGE_2_RETRIES times with rising temperature.\n for (const modelV of stageFlagshipList()) {\n if (!isModelV(modelV)) continue;\n for (let attempt = 0; attempt < STAGE_2_RETRIES; attempt++) {\n try {\n const { text: raw, usage } = await callLLMWithUsage({\n modelV,\n messages,\n temperature: STAGE_2_TEMPERATURES[attempt] ?? 0.6,\n maxTokens: 8000,\n });\n billChair(args.chairId, usage);\n const scaffold = parseScaffold(\n raw,\n args.room.subject,\n args.room.subject,\n );\n if (scaffold) {\n if (attempt > 0) {\n process.stderr.write(\n `[brief.stage2] ${modelV} succeeded on retry ${attempt + 1}\\n`,\n );\n }\n return scaffold;\n }\n process.stderr.write(\n `[brief.stage2] ${modelV} attempt ${attempt + 1}/${STAGE_2_RETRIES} produced unusable scaffold\\n`,\n );\n } catch (e) {\n process.stderr.write(\n `[brief.stage2] ${modelV} attempt ${attempt + 1}/${STAGE_2_RETRIES} failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n }\n }\n }\n return null;\n}\n\n/* ─────────────────────────── Stage 3 ──────────────────────────────────── */\n\ninterface Stage3Args {\n roomId: string;\n briefId: string;\n chairId: string | null;\n room: NonNullable<ReturnType<typeof getRoom>>;\n members: Agent[];\n scaffold: BriefScaffold;\n perDirectorSignals: DirectorSignals[];\n language: \"zh\" | \"en\";\n supplement?: string;\n /** Composer's component picks (Stage 1.5). When empty, Stage 3 renders\n * whatever's filled in the scaffold — preserves legacy behaviour. */\n picked?: readonly string[];\n}\n\ninterface Stage3Result {\n body: string;\n model: ModelV | null;\n error?: string;\n}\n\nasync function runStage3Streaming(args: Stage3Args): Promise<Stage3Result> {\n const { roomId, briefId, chairId, room, members, scaffold, perDirectorSignals, language, supplement, picked } = args;\n const messages = buildWriteMessages({ room, members, scaffold, perDirectorSignals, language, supplement, picked });\n\n let lastError = \"no model attempted\";\n for (const modelV of stageFlagshipList()) {\n if (!isModelV(modelV)) continue;\n let buf = \"\";\n let errored = false;\n try {\n for await (const chunk of callLLMStream({\n modelV,\n messages,\n temperature: 0.4,\n maxTokens: 12000,\n })) {\n if (chunk.type === \"text\") {\n buf += chunk.delta;\n updateBriefBody(briefId, buf);\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"brief-token\",\n payload: { briefId, delta: chunk.delta },\n createdAt: Date.now(),\n });\n } else if (chunk.type === \"usage\") {\n billChair(chairId, {\n totalTokens: chunk.totalTokens,\n });\n } else if (chunk.type === \"error\") {\n errored = true;\n lastError = chunk.message;\n break;\n }\n }\n } catch (e) {\n errored = true;\n lastError = e instanceof Error ? e.message : String(e);\n }\n if (!errored && buf.length > 0) return { body: buf, model: modelV };\n }\n return { body: \"\", model: null, error: lastError };\n}\n\n/* ─────────────────────────── Methodology footer ───────────────────────── */\n\ninterface MethodologyArgs {\n perDirectorSignals: DirectorSignals[];\n stage3Model: ModelV | null;\n language: \"zh\" | \"en\";\n startedAt: number;\n}\n\n/**\n * Auto-generated Methodology section appended to every report. Listed\n * after stage-3 streams so the user sees how the report was made:\n * how many signals, lens distribution, models used, time stamp. This\n * is deterministic data — we don't burn LLM tokens on it.\n */\nfunction buildMethodologyFooter(args: MethodologyArgs): string {\n const { perDirectorSignals, stage3Model, language } = args;\n\n const totalSignals = perDirectorSignals.reduce((acc, d) => acc + d.signals.length, 0);\n const directorsActive = perDirectorSignals.filter((d) => d.signals.length > 0).length;\n const directorsTotal = perDirectorSignals.length;\n\n // Lens distribution.\n const lensCounts: Record<string, number> = {};\n for (const d of perDirectorSignals) {\n for (const s of d.signals) {\n lensCounts[s.lens] = (lensCounts[s.lens] || 0) + 1;\n }\n }\n const lensRow = ([\"data\", \"dissent\", \"narrative\", \"structural\", \"first-principle\"] as const)\n .map((l) => `${l} ${lensCounts[l] || 0}`)\n .join(\" · \");\n\n if (language === \"zh\") {\n const writerLine = stage3Model ? `主写模型:${stage3Model}` : \"主写模型:—\";\n return [\n \"## Methodology\",\n \"\",\n `本报告基于 ${directorsActive}/${directorsTotal} 位董事的发言抽取出 **${totalSignals} 条 signal**,按五种证据视角分布:${lensRow}。`,\n \"\",\n `Pipeline:每位董事独立抽取 → chair 聚类成骨架 → chair 撰写最终报告。${writerLine}。`,\n \"\",\n \"_本报告不擅长评估的领域:定量预测、近期市场数据、合规与法律边界。涉及这些维度时建议另启专业渠道复核。_\",\n ].join(\"\\n\");\n }\n const writerLine = stage3Model ? `Writer model: ${stage3Model}` : \"Writer model: —\";\n return [\n \"## Methodology\",\n \"\",\n `Compiled from **${totalSignals} signals** extracted across ${directorsActive}/${directorsTotal} active directors. Lens distribution: ${lensRow}.`,\n \"\",\n `Pipeline: per-director independent extraction → chair clustering into a scaffold → chair final write. ${writerLine}.`,\n \"\",\n \"_Domains this writer is not equipped to assess: quantitative forecasting, near-real-time market data, legal/compliance boundaries. Verify those through specialist channels._\",\n ].join(\"\\n\");\n}\n","/** User preferences (single-row table). */\nimport { getDb } from \"./db.js\";\n\nexport interface Prefs {\n name: string;\n intro: string;\n avatarSeed: string | null;\n theme: string;\n defaultModelV: string | null;\n createdAt: number;\n updatedAt: number;\n}\n\ninterface Row {\n name: string;\n intro: string;\n avatar_seed: string | null;\n theme: string;\n default_model_v: string | null;\n created_at: number;\n updated_at: number;\n}\n\nfunction mapRow(row: Row): Prefs {\n return {\n name: row.name,\n intro: row.intro,\n avatarSeed: row.avatar_seed,\n theme: row.theme,\n defaultModelV: row.default_model_v,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nexport function getPrefs(): Prefs {\n const row = getDb()\n .prepare(\n \"SELECT name, intro, avatar_seed, theme, default_model_v, created_at, updated_at FROM prefs WHERE id = 1\",\n )\n .get() as Row | undefined;\n if (!row) {\n // The 001 migration seeds a row, so this should never happen.\n throw new Error(\"prefs row missing — did migrations run?\");\n }\n return mapRow(row);\n}\n\nexport interface PrefsPatch {\n name?: string;\n intro?: string;\n avatarSeed?: string | null;\n theme?: string;\n defaultModelV?: string | null;\n}\n\nexport function updatePrefs(patch: PrefsPatch): Prefs {\n const fields: string[] = [];\n const values: unknown[] = [];\n if (patch.name !== undefined) { fields.push(\"name = ?\"); values.push(patch.name); }\n if (patch.intro !== undefined) { fields.push(\"intro = ?\"); values.push(patch.intro); }\n if (patch.avatarSeed !== undefined) { fields.push(\"avatar_seed = ?\"); values.push(patch.avatarSeed); }\n if (patch.theme !== undefined) { fields.push(\"theme = ?\"); values.push(patch.theme); }\n if (patch.defaultModelV !== undefined) { fields.push(\"default_model_v = ?\"); values.push(patch.defaultModelV); }\n\n if (fields.length === 0) return getPrefs();\n\n fields.push(\"updated_at = ?\");\n values.push(Date.now());\n\n getDb()\n .prepare(`UPDATE prefs SET ${fields.join(\", \")} WHERE id = 1`)\n .run(...values);\n\n return getPrefs();\n}\n","/**\n * Reconcile every agent's `modelV` against the user's currently\n * configured keys. Run after any key change (PUT / DELETE) and after\n * the default-model preference flips.\n *\n * The carrier model · a single rule:\n *\n * For each agent: if their current modelV is reachable (some carrier\n * can serve it with the current keys), keep it. If not, switch to\n * the current default carrier's primary model. If no carrier is\n * reachable at all, clear the modelV (set to null) so the agent\n * visibly waits for keys.\n *\n * \"Default carrier\" = the carrier of `prefs.defaultModelV`, or the\n * first reachable carrier in priority order [openrouter, anthropic,\n * openai, google, xai] when no default is set or its model is\n * unreachable.\n */\nimport { listAllAgents, updateAgent } from \"./agents.js\";\nimport { getKey } from \"./keys.js\";\nimport { getPrefs, updatePrefs } from \"./prefs.js\";\nimport { MODELS, type ModelMeta, type ModelV, type Provider } from \"../ai/registry.js\";\n\n/** A \"carrier\" is the network path a model travels on · OpenRouter\n * routes everything; the rest match the model's own provider. The\n * Provider type tracks model creators (anthropic / openai / …) and\n * doesn't include openrouter, so we widen it here. */\ntype Carrier = Provider | \"openrouter\";\n\n/** Per-provider primary model (the chair's default when that carrier\n * is the active one). Tied to which models in the registry are\n * direct-routable for each provider. OpenRouter as a carrier carries\n * every model — its primary is opus-4-7 (Anthropic's flagship). */\nexport const PRIMARY_BY_CARRIER: Record<string, ModelV> = {\n openrouter: \"opus-4-7\",\n anthropic: \"sonnet-4-6\",\n openai: \"gpt-5-5\",\n google: \"gemini-3-flash\",\n xai: \"grok-4-3\",\n};\n\n/** Carrier preference order when prefs.defaultModelV isn't set / its\n * model is unreachable. OpenRouter first because it routes everything;\n * beyond that, the order doesn't matter much — the user's first-\n * configured key tends to be picked by onboarding anyway. */\nconst CARRIER_PRIORITY: Carrier[] = [\"openrouter\", \"anthropic\", \"openai\", \"google\", \"xai\"];\n\n/** Compute which model IDs are reachable right now. A modelV is\n * reachable when at least one of its carriers has a configured key.\n * Mirrors the precedence rules used by the LLM adapter. */\nexport function reachableModelVs(): Set<ModelV> {\n const out = new Set<ModelV>();\n const orKey = !!getKey(\"openrouter\");\n for (const [v, meta] of Object.entries(MODELS) as Array<[ModelV, ModelMeta]>) {\n // Path 1 · OpenRouter carries everything when the OR key exists.\n if (orKey) {\n out.add(v);\n continue;\n }\n // Path 2 · direct provider key, model not OR-only.\n if (!meta.openrouterOnly && hasDirectKey(meta.provider)) {\n out.add(v);\n continue;\n }\n // Path 3 · OR-only model, OR key missing, but direct provider key\n // exists → adapter falls back to direct (may fail at the API\n // level if the model id isn't on the SDK). Treat as reachable\n // so the picker exposes it; the LLM call is the source of truth.\n if (meta.openrouterOnly && hasDirectKey(meta.provider)) {\n out.add(v);\n }\n }\n return out;\n}\n\n/** Direct keys are scoped to providers we have an SDK for. DeepSeek\n * has no @ai-sdk client in this codebase, so a DeepSeek direct key\n * would be unreachable; we treat the provider list as openai /\n * anthropic / google / xai. */\nfunction hasDirectKey(provider: Provider): boolean {\n switch (provider) {\n case \"anthropic\":\n case \"openai\":\n case \"google\":\n case \"xai\":\n return !!getKey(provider);\n default:\n return false;\n }\n}\n\n/** Resolve the active carrier · the carrier of prefs.defaultModelV,\n * or the first reachable carrier in priority order. */\nexport function activeCarrier(): Carrier | null {\n const prefs = getPrefs();\n if (prefs.defaultModelV) {\n const meta = MODELS[prefs.defaultModelV as ModelV];\n if (meta) {\n // The default model exists; resolve which carrier is currently\n // serving it. OpenRouter wins when both are configured (matches\n // the adapter's \"openrouterOnly + OR key\" precedence).\n if (meta.openrouterOnly && getKey(\"openrouter\")) return \"openrouter\";\n if (hasDirectKey(meta.provider)) return meta.provider;\n if (getKey(\"openrouter\")) return \"openrouter\";\n }\n }\n // Fall through · pick the first reachable carrier in priority order.\n for (const c of CARRIER_PRIORITY) {\n if (c === \"openrouter\" && getKey(\"openrouter\")) return \"openrouter\";\n if (c !== \"openrouter\" && hasDirectKey(c)) return c;\n }\n return null;\n}\n\n/** Translate the active carrier into its primary modelV. Returns null\n * when no carrier is reachable. */\nexport function activeCarrierPrimary(): ModelV | null {\n const carrier = activeCarrier();\n if (!carrier) return null;\n return PRIMARY_BY_CARRIER[carrier] ?? null;\n}\n\ninterface ReconcileResult {\n /** Agents whose model was switched to the active primary. */\n switched: string[];\n /** Agents whose model was cleared (no carrier reachable). */\n cleared: string[];\n /** New active primary, if any. */\n primary: ModelV | null;\n}\n\n/**\n * Walk every agent and align their `modelV` with the current key set.\n * - Reachable model → keep (default behavior)\n * - Unreachable model + carrier exists → switch to primary\n * - Unreachable model + no carrier → clear (set empty string)\n *\n * `opts.forcePrimary = true` overrides the \"keep if reachable\" rule:\n * every agent that isn't already on the active primary gets switched\n * to it, even if their current model is still reachable on a different\n * carrier. Used during onboarding when the user picks a provider as\n * their primary — without this, a user who already had OpenRouter\n * configured and adds Gemini would see the chair stay on `opus-4-7`\n * (still reachable via OR) instead of swinging to `gemini-3-flash`.\n *\n * Also bumps `prefs.defaultModelV` to the active primary whenever it\n * lands on a different value · keeps \"what new agents will inherit\"\n * consistent with \"what existing agents are running\".\n */\nexport interface ReconcileOptions {\n /** Switch every agent to the active primary even if their stored\n * modelV is still reachable. Default false (conservative). */\n forcePrimary?: boolean;\n}\n\nexport function reconcileAgentModels(opts: ReconcileOptions = {}): ReconcileResult {\n const reachable = reachableModelVs();\n const primary = activeCarrierPrimary();\n const forcePrimary = opts.forcePrimary === true;\n const switched: string[] = [];\n const cleared: string[] = [];\n\n for (const agent of listAllAgents()) {\n const v = (agent.modelV || \"\").trim();\n // Default behavior: leave reachable models alone. forcePrimary\n // skips this guard so every agent funnels into the switch branch.\n if (!forcePrimary && v && reachable.has(v as ModelV)) continue;\n if (primary) {\n if (v === primary) continue; // already on primary somehow\n updateAgent(agent.id, { modelV: primary });\n switched.push(agent.id);\n } else {\n // No carrier reachable · clear the agent's model. agents.ts\n // model_v column is NOT NULL with a default; we set the empty\n // string to mark \"unset\" since SQLite's NOT NULL constraint\n // applies. The orchestrator's isModelV() guard treats this as\n // \"skip turn\" — the agent visibly waits for keys.\n if (v === \"\") continue;\n updateAgent(agent.id, { modelV: \"\" });\n cleared.push(agent.id);\n }\n }\n\n // Keep prefs.defaultModelV in sync so the next \"create agent\" flow\n // and the user-settings UI both reflect what's actually running.\n const prefs = getPrefs();\n if (primary && prefs.defaultModelV !== primary) {\n updatePrefs({ defaultModelV: primary });\n } else if (!primary && prefs.defaultModelV !== null) {\n updatePrefs({ defaultModelV: null });\n }\n\n return { switched, cleared, primary };\n}\n","/**\n * Model availability layer.\n *\n * Decides which models in the registry the user can ACTUALLY reach\n * right now, given the API keys they've configured. This is the\n * single source of truth that pickers (composer, agent profile,\n * agent creation) and the adapter consult before showing / using\n * any model.\n *\n * The five user states this resolves cleanly:\n *\n * 1. OpenRouter only → every model reachable, route=\"openrouter\"\n * 2. One direct provider → only that provider's models, route=\"direct\"\n * 3. Multiple direct → union of those providers, route=\"direct\"\n * 4. OR + 1+ direct → every model; direct preferred when available\n * 5. No keys at all → empty list, frontend prompts user to add one\n *\n * The adapter (src/ai/adapter.ts) already routes to direct vs\n * OpenRouter at call time based on the same key-presence checks;\n * this module surfaces that decision UPFRONT so frontends can hide\n * unreachable models from pickers and so the user has a clear\n * mental model of what they have access to.\n */\nimport { listKeyMeta, type Provider } from \"../storage/keys.js\";\nimport { getPrefs, updatePrefs } from \"../storage/prefs.js\";\nimport { activeCarrier } from \"../storage/reconcile-models.js\";\n\nimport { MODELS, type ModelMeta, type ModelV } from \"./registry.js\";\n\nexport type ModelRoute = \"direct\" | \"openrouter\";\n\nexport interface ModelAvailability {\n modelV: ModelV;\n displayName: string;\n provider: Provider;\n /** Sorting hint · the model's deck text from the registry (\"deep\n * reasoning\", \"fast · low-cost\", etc.). Already part of the\n * registry; copied here so consumers don't need a second lookup. */\n deck: string;\n /** Which routes are currently reachable. At least one is true if\n * `reachable` is true. */\n routes: { direct: boolean; openrouter: boolean };\n /** True when at least one route works (direct OR openrouter). */\n reachable: boolean;\n /** The route the adapter will use given the current key set.\n * Direct wins over OpenRouter when both work — the user paid for\n * the direct subscription, so we use it (lower fees, fewer\n * hops, often higher rate limits). */\n preferredRoute: ModelRoute | null;\n}\n\n/** Snapshot of which providers the user has configured. */\nexport interface ProviderKeyState {\n /** OpenRouter (the universal-fallback route). */\n hasOpenRouter: boolean;\n /** Direct LLM provider keys, by provider. Skips brave (not an LLM). */\n directProviders: Set<Provider>;\n}\n\nexport function getProviderKeyState(): ProviderKeyState {\n const directProviders = new Set<Provider>();\n let hasOpenRouter = false;\n for (const meta of listKeyMeta()) {\n if (!meta.configured) continue;\n if (meta.provider === \"openrouter\") hasOpenRouter = true;\n else if (meta.provider === \"brave\") continue; // skill key, not an LLM provider\n else directProviders.add(meta.provider);\n }\n return { hasOpenRouter, directProviders };\n}\n\n/** Compute reachability for a single model under the given key state. */\nexport function availabilityFor(\n meta: ModelMeta,\n keys: ProviderKeyState,\n): ModelAvailability {\n const directReachable = !meta.openrouterOnly && keys.directProviders.has(meta.provider);\n const orReachable = keys.hasOpenRouter && !!meta.openrouterId;\n const reachable = directReachable || orReachable;\n return {\n modelV: meta.v,\n displayName: meta.displayName,\n provider: meta.provider,\n deck: meta.deck,\n routes: { direct: directReachable, openrouter: orReachable },\n reachable,\n preferredRoute: directReachable ? \"direct\" : orReachable ? \"openrouter\" : null,\n };\n}\n\n/** Return availability for every model in the registry (whether\n * reachable or not). Frontends typically filter to .reachable; the\n * adapter / settings UI may want the full list to show \"unreachable\"\n * states with hints on which key would unlock them. */\nexport function modelAvailability(): ModelAvailability[] {\n const keys = getProviderKeyState();\n return Object.values(MODELS).map((meta) => availabilityFor(meta, keys));\n}\n\n/** Convenience · just the reachable models. */\nexport function reachableModels(): ModelAvailability[] {\n return modelAvailability().filter((m) => m.reachable);\n}\n\n/** True iff the user has configured at least one LLM provider. The\n * bootstrap state — `false` here — means model pickers should not\n * render and convene flows should redirect to settings. */\nexport function hasAnyModelKey(): boolean {\n const keys = getProviderKeyState();\n return keys.hasOpenRouter || keys.directProviders.size > 0;\n}\n\n/** Pick a sensible default model for the user given their current\n * keys. Used as the seed value for prefs.default_model_v on first\n * key-add, and as a runtime fallback when a stored default is no\n * longer reachable. Selection rule:\n *\n * 1. If only one direct provider is configured, return that\n * provider's flagship.\n * 2. Else if OpenRouter is configured, return Opus 4.7 (the\n * prototype's historical default).\n * 3. Else if any direct provider is configured, return its\n * flagship.\n * 4. Else null (no keys at all).\n *\n * \"Flagship\" per provider · roughly the most-capable mainstream\n * model the user can hit with that key. Conservative picks so we\n * don't surprise the user with an exotic 1M-ctx variant.\n */\nconst PROVIDER_FLAGSHIP: Record<Provider, ModelV | null> = {\n anthropic: \"opus-4-7\",\n openai: \"gpt-5-5\",\n google: \"gemini-3-flash\",\n xai: \"grok-4-3\",\n deepseek: \"deepseek-v4-pro\",\n openrouter: \"opus-4-7\",\n brave: null,\n};\n\n/** Resolve the default model the user should see RIGHT NOW, with\n * prefs persistence:\n *\n * 1. If `prefs.default_model_v` is set AND that model is currently\n * reachable, return it (the user's explicit choice wins).\n * 2. Else compute via `defaultModelFor()` and back-fill prefs so\n * the value is stable across requests until the user picks\n * something different OR their key set changes again.\n *\n * The back-fill is what keeps the value sticky · once a single\n * reachable default exists, subsequent calls are pure reads. When\n * the key set changes (e.g. user revokes the provider that hosted\n * the stored default), we transparently re-pick. */\nexport function effectiveDefaultModel(): ModelV | null {\n const prefs = getPrefs();\n const reachable = new Set(reachableModels().map((m) => m.modelV));\n if (prefs.defaultModelV && reachable.has(prefs.defaultModelV as ModelV)) {\n return prefs.defaultModelV as ModelV;\n }\n const fresh = defaultModelFor();\n if (fresh !== prefs.defaultModelV) {\n updatePrefs({ defaultModelV: fresh });\n }\n return fresh;\n}\n\nexport function defaultModelFor(keys: ProviderKeyState = getProviderKeyState()): ModelV | null {\n const reachable = modelAvailability().filter((m) => m.reachable);\n if (reachable.length === 0) return null;\n // Single direct provider · pick its flagship.\n if (!keys.hasOpenRouter && keys.directProviders.size === 1) {\n const provider = Array.from(keys.directProviders)[0];\n const flagship = PROVIDER_FLAGSHIP[provider];\n if (flagship && reachable.find((m) => m.modelV === flagship)) return flagship;\n }\n // OpenRouter present · prefer Opus 4.7 (historical default).\n if (keys.hasOpenRouter) {\n const opus = reachable.find((m) => m.modelV === \"opus-4-7\");\n if (opus) return opus.modelV;\n }\n // Multiple direct, no OR · pick the first reachable provider's flagship.\n for (const provider of keys.directProviders) {\n const flagship = PROVIDER_FLAGSHIP[provider];\n if (flagship && reachable.find((m) => m.modelV === flagship)) return flagship;\n }\n // Last resort — any reachable model.\n return reachable[0].modelV;\n}\n\n/** Pick a \"cheap utility\" model for background tasks (skill picker,\n * director auto-pick, agent-spec generation, ability analyzer,\n * convening speech, etc.). Today these hardcode haiku-4-5; with\n * per-user key sets that constant breaks for direct-only users.\n *\n * Selection rule: prefer the user's small/fast tier in this order\n * 1. haiku-4-5 (Anthropic, cheap + fast)\n * 2. gpt-5-4-mini (OpenAI · 400k ctx, current cheap-tier)\n * 3. gemini-3-1-flash (Google · 3.1 Flash Lite, cheapest)\n * 4. gemini-3-flash (Google · 3 Flash, frontier mid-tier)\n * 5. grok-4-mini (xAI)\n * 6. <user's default model> (last resort — pricy but reachable)\n *\n * Returns null only when the user has no keys at all, in which case\n * callers should skip the background task altogether and fall back\n * to deterministic logic (the spec parser + diversity guardrail\n * both already have non-LLM fallbacks). */\n/** Cheap-tier model that matches a given carrier. Used to bias the\n * utility list toward whichever carrier the user is currently routing\n * through, so a user who switched their default to Gemini doesn't\n * silently keep getting OpenAI background calls. Carriers without\n * a cheap-tier entry fall through to the static UTILITY_PREFERENCE. */\nconst CHEAP_BY_CARRIER: Partial<Record<Provider | \"openrouter\", ModelV>> = {\n openrouter: \"haiku-4-5\",\n anthropic: \"sonnet-4-6\", // only direct-routable Claude\n openai: \"gpt-5-4-mini\",\n google: \"gemini-3-1-flash\", // 3.1 Flash Lite · cheapest direct-routable Gemini\n xai: \"grok-4-1-fast\", // 4.1 Fast · cheapest direct-routable Grok\n};\n\nconst UTILITY_PREFERENCE: ModelV[] = [\n \"haiku-4-5\",\n \"gpt-5-4-mini\",\n \"gemini-3-1-flash\",\n \"gemini-3-flash\",\n \"grok-4-1-fast\",\n];\n\nexport function utilityModelFor(fallback: ModelV | null = null): ModelV | null {\n const reachable = new Set(reachableModels().map((m) => m.modelV));\n // Active-carrier-first · whichever carrier serves the user's current\n // default model gets first dibs on the utility slot. Prevents the\n // \"I switched to Gemini but background calls still hit OpenAI\" trap\n // where a user with multiple direct keys saw the static preference\n // table always pick gpt-5-4-mini.\n const carrier = activeCarrier();\n if (carrier) {\n const preferred = CHEAP_BY_CARRIER[carrier];\n if (preferred && reachable.has(preferred)) return preferred;\n }\n for (const v of UTILITY_PREFERENCE) {\n if (reachable.has(v)) return v;\n }\n if (fallback && reachable.has(fallback)) return fallback;\n // Last-ditch · any reachable model. Better than throwing.\n const any = Array.from(reachable)[0];\n return (any as ModelV | undefined) ?? null;\n}\n","/**\n * Three-stage brief pipeline prompts.\n *\n * Stage 1 · per-director extract · short JSON list of signals\n * Stage 2 · chair cluster/scaffold · JSON scaffold of findings\n * Stage 3 · chair final write · markdown report (streamed)\n *\n * Each stage's prompt is intentionally narrow so the LLM call can use\n * a smaller / cheaper model (haiku for stage 1) while the synthesis\n * stages stay strong.\n */\nimport type { LLMMessage } from \"../adapter.js\";\nimport type { Agent } from \"../../storage/agents.js\";\nimport type { Message } from \"../../storage/messages.js\";\nimport type { Room } from \"../../storage/rooms.js\";\n\n/** The five evidence lenses. Stage 2/3 enforce at least 2 distinct\n * lenses per finding to guarantee diversity. */\nexport const EVIDENCE_LENSES = [\n \"data\",\n \"dissent\",\n \"narrative\",\n \"structural\",\n \"first-principle\",\n] as const;\nexport type EvidenceLens = (typeof EVIDENCE_LENSES)[number];\n\nexport interface ExtractedSignal {\n text: string;\n lens: EvidenceLens;\n /** Indices into the director's own message list (0-based). */\n sources: number[];\n}\n\nexport interface DirectorSignals {\n directorId: string;\n directorName: string;\n signals: ExtractedSignal[];\n}\n\n/** Confidence level — used on bottom line, headline findings, divergence\n * rows, and recommendations. */\nexport type Confidence = \"high\" | \"medium\" | \"low\";\n\n/** Section 2 · Bottom Line Up Front. Single-sentence judgement + confidence\n * + a one-sentence rationale. Rendered as a designed callout in the report. */\nexport interface BottomLine {\n /** One-sentence load-bearing judgement of the whole session. */\n judgement: string;\n confidence: Confidence;\n /** One sentence on why we have that confidence (or why we don't have more). */\n rationale: string;\n}\n\n/** Section 3 · Frame Shift. The single most distinctive multi-director\n * output: how the question itself changed during the session. If the\n * frame did not shift, `shifted: false` and the section restates the\n * question with the room's deeper understanding. */\nexport interface FrameShift {\n shifted: boolean;\n /** What the question looked like at the room's open. */\n original: string;\n /** What the question looks like now. Empty when shifted=false. */\n reframed: string;\n /** Why the reframe happened (or why the frame held). */\n trigger: string;\n}\n\n/** Section 4 · Headline Finding. Pyramid principle: exactly 3 of these\n * per report, MECE-enforced. Each is a complete-sentence claim with\n * supporters / challengers visible, confidence on the claim, and 2-3\n * supporting sub-findings drawing on the evidence pool. */\nexport interface SubFinding {\n /** Complete sentence — typically 1-2 sentences of substance. */\n text: string;\n evidenceRefs: string[];\n}\nexport interface HeadlineFinding {\n /** Complete-sentence section heading (not a topic — a takeaway). */\n title: string;\n /** Single-sentence claim (the load-bearing line). */\n claim: string;\n confidence: Confidence;\n /** Director ids who advanced this. */\n supporters: string[];\n /** Director ids who challenged this. Empty array when full alignment. */\n challengers: string[];\n /** 2-3 sub-findings that prove the claim. */\n supporting: SubFinding[];\n /** Lens tags spanning the sub-findings — must have ≥ 2 distinct. */\n lensesPresent: EvidenceLens[];\n /** Optional unresolved tension on this finding. */\n tension?: string;\n /** Counter-evidence the room raised against this finding · 1-2\n * sentences naming the strongest argument *against* the claim. Makes\n * the room's adversarial review structurally visible. Optional on\n * legacy scaffolds; required on dense Gartner-style briefs. */\n counterEvidence?: string;\n /** What this finding implies for the decision the room is wrestling\n * with · 1 sentence. Closes the gap between \"interesting fact\" and\n * \"actionable judgment\". Optional on legacy scaffolds. */\n strategicImplication?: string;\n}\n\n/** Section 5 · Convergence point — where directors arrived at the same\n * conclusion via independent reasoning paths. The \"independence\" is what\n * makes this load-bearing, so each path includes the lens used and the\n * director's specific reasoning chain. */\nexport interface ConvergencePath {\n directorId: string;\n /** The lens the director's path leans on (data / dissent / etc.). */\n lens: EvidenceLens;\n /** One-sentence reasoning chain that led this director to the point. */\n reasoning: string;\n}\nexport interface ConvergencePoint {\n /** What the room agreed on, in one sentence. */\n point: string;\n /** Independent paths · ≥ 2 directors via ≥ 2 distinct lenses. */\n paths: ConvergencePath[];\n}\n\n/** Section 6 · Divergence (the Crux). Extends the previous Crux shape\n * with confidence and cost-of-being-wrong per director, plus what we'd\n * need to know to resolve it. */\nexport type DivergenceStance = \"for\" | \"against\" | \"nuanced\";\nexport interface DivergenceRow {\n directorId: string;\n stance: DivergenceStance;\n confidence: Confidence;\n /** What's at risk if this director is wrong. ≤ 80 chars. */\n costOfBeingWrong: string;\n /** Director's specific take. ≤ 80 chars. */\n note: string;\n}\nexport interface Divergence {\n /** The single point everything hinges on, in one sentence. */\n statement: string;\n rows: DivergenceRow[];\n /** What we'd need to know to settle the divergence. 1-3 items. */\n resolutionRequirements: string[];\n}\n\n/** Section 7 · Position camps (existing). 2–3 named camps, each with a\n * collective claim, the directors in it, and supporting signals. */\nexport interface PositionCamp {\n label: string;\n claim: string;\n directors: string[];\n evidenceRefs: string[];\n}\n\n/** Section 8 · Visual blocks. 0–4 allowed (was 0–2). Options Analysis\n * scenarios should produce ≥ 1 visual; otherwise content-driven. */\nexport interface ComparisonTableVisual {\n type: \"comparison-table\";\n title: string;\n rowLabel: string;\n columns: string[];\n rows: { name: string; cells: string[] }[];\n}\nexport interface QuadrantChartVisual {\n type: \"quadrant-chart\";\n title: string;\n xLabel: string;\n yLabel: string;\n q1: string; q2: string; q3: string; q4: string;\n items: { label: string; x: number; y: number }[];\n}\nexport interface ForceFieldVisual {\n type: \"force-field\";\n title: string;\n drivers: string[];\n resistors: string[];\n}\n/** Strengths-and-cautions table — one row per option, with a pros / cons\n * pair plus a recommendation tag. McKinsey-style \"what's the trade?\". */\nexport interface StrengthsCautionsVisual {\n type: \"strengths-cautions\";\n title: string;\n rows: {\n option: string;\n strengths: string[];\n cautions: string[];\n /** \"Recommended\" / \"Caution required\" / \"Not recommended\" — drives a colored badge. */\n verdict: \"recommended\" | \"caution\" | \"not-recommended\";\n }[];\n}\nexport type Visual =\n | ComparisonTableVisual\n | QuadrantChartVisual\n | ForceFieldVisual\n | StrengthsCautionsVisual;\n\n/** Section 9 · Recommendation. Concrete action with priority, rationale,\n * owner type, time horizon, and a success metric. Rendered as a numbered\n * table or a labelled card.\n *\n * v2 adds `criticalDependency` — the single thing that must be true for\n * this action to work. Surfacing it explicitly turns the recommendation\n * from a directive into a stress-testable plan. Optional for legacy\n * scaffolds. */\nexport type Priority = \"P0\" | \"P1\" | \"P2\";\nexport interface Recommendation {\n priority: Priority;\n /** Imperative concrete action. */\n action: string;\n /** Why this works — 1-2 sentences. */\n rationale: string;\n /** Who should execute (e.g. \"platform team\", \"the user\", \"PM\"). */\n ownerType: string;\n /** Time horizon (e.g. \"next 30 days\", \"Q2 2026\"). */\n horizon: string;\n /** Observable proof of execution. */\n successMetric: string;\n /** What happens if you skip this. */\n riskIfSkipped: string;\n /** What MUST be true for this action to work (the load-bearing\n * pre-condition). Empty/undefined on legacy scaffolds. */\n criticalDependency?: string;\n}\n\n/** Section 10 · Pre-mortem. How the recommendations could fail, with\n * leading indicators that would warn us early. 2-3 failure modes. */\nexport interface FailureMode {\n /** How the recommendation could fail. 1 sentence. */\n scenario: string;\n /** Earliest observable warning sign. */\n leadingIndicator: string;\n /** What to do if the leading indicator fires. */\n mitigation: string;\n}\n\n/** Section 11 · New Questions Surfaced. Distinct from openQuestions\n * (residuals) — these are questions that did not exist when the room\n * opened but emerged from the conversation. The most generative output\n * of a multi-director session. */\nexport interface NewQuestion {\n /** Complete question, ending with ?. */\n question: string;\n /** Why answering this matters next. */\n whyItMatters: string;\n /** Director id who first surfaced it. */\n surfacedByDirectorId: string;\n}\n\n/** Section 12 · Strategic Planning Assumption. A forward-looking\n * probabilistic statement with conditions and a falsifiable test. */\nexport interface PlanningAssumption {\n /** Forward-looking statement. e.g. \"By Q4 2027, X% of platforms will…\" */\n statement: string;\n /** 0-100 integer. */\n probability: number;\n /** Conditions / triggers under which the statement holds. */\n trigger: string;\n /** Observable that would prove this wrong. */\n falsificationTest: string;\n}\n\n/** Open questions · residual unresolved questions (≠ NewQuestions). */\nexport type OpenQuestionPriority = \"P0\" | \"P1\";\nexport interface OpenQuestion {\n text: string;\n priority: OpenQuestionPriority;\n}\n\n/* ─────────── Alternative anchor / findings / action components ────────────\n *\n * The composer (Stage 1.5) picks one component per substitute group. When\n * a non-default substitute is picked, its corresponding field below is\n * filled and the default field (`bottomLine`, `headlineFindings`,\n * `recommendations`) is left empty / null. The renderer skips empty\n * fields, so a brief carrying `thesis` won't render `## Bottom Line`.\n *\n * These types are net-additive — every legacy scaffold passing through\n * `parseScaffold` without these fields keeps working unchanged. */\n\n/** Anchor alternative · single load-bearing thesis claim (a16z style). */\nexport interface Thesis {\n /** Complete-sentence claim · 12-22 words. */\n claim: string;\n /** Why this is the load-bearing claim · 1-2 sentences. */\n reasoning: string;\n}\n\n/** Findings alternative · 3 numbered claims, lighter than HeadlineFindings. */\nexport interface BigIdea {\n /** 1-based, must equal index+1. */\n number: 1 | 2 | 3;\n /** Punchy claim · 8-14 words. */\n claim: string;\n /** Why · 1-2 sentences. */\n why: string;\n evidenceRefs: string[];\n}\n\n/** Action alternative · the conditions to back the call (a16z style). */\nexport interface TheBetCondition {\n /** Imperative · what must hold to back the call. */\n condition: string;\n /** Why this condition is load-bearing · 1-2 sentences. */\n why: string;\n}\nexport interface TheBet {\n /** Opening sentence · \"If we were to back this...\" or equivalent. */\n ifBacked: string;\n /** 3-5 conditions. */\n conditions: TheBetCondition[];\n /** When we'd stop — observable failure trigger. */\n killCriteria: string;\n}\n\n/** Anchor alternative · Anthropic-style essay opener. The hypothesis\n * followed by the reasons it might be wrong — invites disagreement\n * rather than asserting the takeaway. */\nexport interface WorkingHypothesis {\n /** The hypothesis as one or two sentences in essay voice. */\n hypothesis: string;\n /** 2-3 reasons it may be wrong. Each one short (≤ 30 words). */\n reasonsItMayBeWrong: string[];\n}\n\n/** Strategic outlook · 2-paragraph contextual frame that sits between\n * the anchor (thesis / bottom-line) and the findings. Heavier than a\n * thesis claim, lighter than a working-hypothesis essay opener. Used\n * for strategic-decision / market-forecast briefs where the room\n * needs to set up the operating environment before the findings make\n * sense. Gartner / Bain \"Strategic Outlook\" register. */\nexport interface StrategicOutlook {\n /** Paragraph 1 · the operating context: forces in motion, who's\n * affected, what's at stake. 2-4 sentences, ≤ 600 chars. */\n context: string;\n /** Paragraph 2 · the strategic implication that flows from the\n * context — what this changes for decision-makers. 2-3 sentences,\n * ≤ 500 chars. */\n implication: string;\n}\n\n/** Critical assumption · one of 4-6 load-bearing assumptions the brief\n * rests on. Each has a confidence band, a falsifier (the observable\n * that would prove it wrong), and a time horizon for when the\n * assumption needs to hold. Gartner \"Critical Assumptions Log\"\n * register — making the foundation visible. */\nexport interface CriticalAssumption {\n /** The assumption as a complete sentence. ≤ 200 chars. */\n statement: string;\n /** Confidence band on whether the assumption holds. */\n confidence: Confidence;\n /** Observable / event that would prove this assumption wrong. ≤ 200 chars. */\n falsifier: string;\n /** Time window the assumption must hold for the brief's logic to\n * stand (e.g. \"next 12 months\", \"Q2 2026\", \"the duration of the\n * conflict\"). ≤ 80 chars. */\n horizon: string;\n /** Which director / lens surfaced this assumption. ≤ 80 chars. */\n attribution: string;\n}\n\n/** One scenario in the scenario tree · 2-4 named futures (typically 3:\n * Base / Upside / Downside, or Path A / B / C) with explicit\n * probabilities, triggers that would tip into them, the dominant\n * effects under that scenario, and the decision implication for\n * stakeholders. Gartner \"Scenario Tree\". */\nexport interface ScenarioBranch {\n /** Short name · ≤ 40 chars (e.g. \"Protracted stalemate\"). */\n label: string;\n /** 0-100 integer · the probability the room assigns to this branch.\n * All branch probabilities sum to ~100 (drift up to 5pts allowed). */\n probability: number;\n /** What would tip the room into this scenario. ≤ 200 chars. */\n trigger: string;\n /** Dominant effects under this scenario · 2-3 bullets. */\n effects: string[];\n /** What this scenario implies for the decision at hand. ≤ 240 chars. */\n decisionImplication: string;\n}\nexport interface ScenarioTree {\n /** One-sentence framing for the tree. */\n intro: string;\n /** 2-4 named scenarios. Probabilities sum to ~100. */\n branches: ScenarioBranch[];\n}\n\n/** Leading indicator · one of 3-5 signals the room recommends\n * monitoring to detect which scenario is materializing. Each has a\n * measurable signal, a threshold that flips interpretation, monitoring\n * cadence, and which scenario(s) the threshold confirms. Gartner /\n * oncall-runbook discipline. */\nexport interface LeadingIndicator {\n /** What to watch · short label, ≤ 80 chars. */\n signal: string;\n /** Threshold or pattern that flips the read. ≤ 200 chars. */\n threshold: string;\n /** Cadence (e.g. \"daily\", \"weekly\", \"every CPI release\"). ≤ 60 chars. */\n cadence: string;\n /** What hitting the threshold implies — which scenario it confirms\n * or which assumption it falsifies. ≤ 240 chars. */\n flipsTo: string;\n}\n\n/** Forward / opportunity panel · used by a16z-thesis spine (and any\n * spine when the conversation hinged on a window in time). */\nexport interface WhyNow {\n /** What recently opened this window. ≤ 200 chars. */\n windowOpened: string;\n /** When / why it closes. ≤ 200 chars. */\n windowCloses: string;\n /** The bet implied by the window. ≤ 200 chars. */\n whatToBetOn: string;\n}\n\n/** Optional comparison · two trajectories laid out side by side.\n * Useful when the room argued two named futures (e.g. \"platform play\n * vs vertical play\"). The renderer turns this into a 2-column block. */\nexport interface TwoPathPanel {\n /** Short label · ≤ 32 chars (e.g. \"Platform play\"). */\n label: string;\n /** 1-paragraph trajectory in prose. ≤ 500 chars. */\n body: string;\n}\nexport interface TwoPaths {\n /** Optional one-sentence framing for both paths. */\n intro: string;\n pathA: TwoPathPanel;\n pathB: TwoPathPanel;\n}\n\n/** Top-level scaffold. Composer-driven · only the picked component\n * fields are filled by Stage 2; the rest are left at their zero values\n * (empty array, null) so the existing renderer's \"skip if empty\" rules\n * drop them cleanly. Methodology footer is appended by the orchestrator\n * from auto-computed signal/lens/model stats. */\nexport interface BriefScaffold {\n title: string;\n // ── Anchor (substitute group · pick one) ──\n bottomLine: BottomLine;\n thesis?: Thesis | null;\n workingHypothesis?: WorkingHypothesis | null;\n // ── Reframe (optional) ──\n frameShift: FrameShift;\n // ── Findings (substitute group · pick one) ──\n /** Default · exactly 3 by hard cap, MECE. */\n headlineFindings: HeadlineFinding[];\n /** Alternative · exactly 3 numbered ideas. */\n bigIdeas?: BigIdea[] | null;\n // ── Multi-perspective (optional) ──\n convergence: ConvergencePoint[];\n divergence: Divergence | null;\n positions: PositionCamp[];\n // ── Exhibits (optional · 0-4) ──\n visuals: Visual[];\n /** Optional · 2 named trajectories side by side. */\n twoPaths?: TwoPaths | null;\n // ── Forward (optional) ──\n whyNow?: WhyNow | null;\n // ── Action (substitute group · pick one) ──\n recommendations: Recommendation[];\n theBet?: TheBet | null;\n /** Anthropic-style softer action substitute · same shape as\n * recommendations but rendered with hedged voice. */\n considerations?: Recommendation[] | null;\n // ── Forward (optional · cont.) ──\n preMortem: FailureMode[];\n newQuestions: NewQuestion[];\n planningAssumption: PlanningAssumption | null;\n // ── Gartner-density blocks (optional, composer-picked) ──\n /** 2-paragraph strategic-outlook section sitting between anchor and\n * findings. Picked for strategic-decision / market-forecast briefs. */\n strategicOutlook?: StrategicOutlook | null;\n /** 4-6 load-bearing assumptions with confidence + falsifier + horizon. */\n criticalAssumptions?: CriticalAssumption[] | null;\n /** 2-4 scenario branches with probabilities, triggers, effects,\n * decision implications. Probabilities sum to ~100. */\n scenarioTree?: ScenarioTree | null;\n /** 3-5 leading indicators · signal / threshold / cadence / flipsTo. */\n leadingIndicators?: LeadingIndicator[] | null;\n // ── Residual ──\n openQuestions: OpenQuestion[];\n}\n\n/** Language tag for the report. Determined from the room subject by the\n * caller (CJK → \"zh\", else \"en\"). All three stages must produce output\n * in this language so the report aligns with how the user phrased the\n * Initial Question. */\nexport type ReportLanguage = \"zh\" | \"en\";\n\nfunction languageInstruction(lang: ReportLanguage): string {\n if (lang === \"zh\") {\n return [\n \"## 输出语言要求(重要)\",\n \"本次会议的 Initial Question 是中文。所有面向用户的输出(包括 JSON 字段中的字符串值、最终报告的 markdown)都必须使用**简体中文**。\",\n \"JSON 的 key 名(例如 `title`, `findings`, `tldr`, `evidenceRefs`、`lens` 标签如 `data` / `dissent` 等枚举值)保持英文不变;只把 value 中的人类阅读文本翻译为中文。\",\n ].join(\"\\n\");\n }\n return [\n \"## Output language\",\n \"This room's Initial Question was in English. Produce all human-readable output (string values inside JSON, and the final markdown) in **English**.\",\n \"JSON keys and enum values (e.g. `data`, `dissent`, `confirmed`, `for`, `P0`) stay as the literal strings shown in the schema.\",\n ].join(\"\\n\");\n}\n\n/* ─────────────────────── Stage 1 · per-director extract ───────────────── */\n\ninterface ExtractOpts {\n director: Agent;\n /** Only the messages this director authored. Caller pre-filters. */\n ownMessages: Message[];\n room: Room;\n language: ReportLanguage;\n}\n\nconst EXTRACT_SYSTEM = (director: Agent) =>\n [\n `You are ${director.name} (${director.handle}), ${director.roleTag}.`,\n `Your job: re-read your own contributions to a boardroom session and surface the 2–4 signals you most want preserved in the final report.`,\n ``,\n `## What counts as a signal`,\n ``,\n `A signal is a single load-bearing observation you made — a claim, a counterexample, a structural insight, a first-principles re-derivation, a story that crystallizes the point. Not a summary of what you said; the *one thing* that should outlive the conversation.`,\n ``,\n `## Lens tags (pick exactly one per signal)`,\n ``,\n `· \\`data\\` — empirical data point, number, or named precedent`,\n `· \\`dissent\\` — a counterexample or pushback against a default view`,\n `· \\`narrative\\` — a story or analogy that makes the point land`,\n `· \\`structural\\` — a system / mechanism / second-order argument`,\n `· \\`first-principle\\` — a derivation from foundational truths`,\n ``,\n `## Output format`,\n ``,\n `Return a single JSON object inside a fenced \\`\\`\\`json code block. No prose outside the block.`,\n ``,\n `\\`\\`\\`json`,\n `{`,\n ` \"signals\": [`,\n ` { \"text\": \"Short 1–2 sentence statement of the signal in your voice.\", \"lens\": \"dissent\", \"sources\": [0, 2] }`,\n ` ]`,\n `}`,\n `\\`\\`\\``,\n ``,\n `\\`sources\\` is an array of 0-based indices into your message list (provided in the user message). Cite at least one. If you said nothing worth preserving, return \\`{\"signals\": []}\\`.`,\n ``,\n `Constraints:`,\n `· 2–4 signals (or zero).`,\n `· Each signal has a different lens tag if possible.`,\n `· \"text\" is in your own voice, not a third-person paraphrase. Max 50 words.`,\n ].join(\"\\n\");\n\nexport function buildExtractMessages(opts: ExtractOpts): LLMMessage[] {\n const { director, ownMessages, room, language } = opts;\n\n const myMessages = ownMessages\n .map((m, i) => `[${i}] ${m.body.trim()}`)\n .join(\"\\n\\n\");\n\n return [\n {\n role: \"system\",\n content: [EXTRACT_SYSTEM(director), \"\", languageInstruction(language)].join(\"\\n\"),\n },\n {\n role: \"user\",\n content: [\n `ROOM SUBJECT: ${room.subject}`,\n ``,\n `Your messages in this room (indexed):`,\n ``,\n myMessages || \"(you said nothing)\",\n ``,\n `Extract your signals now. JSON only.`,\n ].join(\"\\n\"),\n },\n ];\n}\n\n/* ─────────────────────── Stage 2 · chair cluster/scaffold ─────────────── */\n\ninterface ScaffoldOpts {\n chair: Agent | null;\n room: Room;\n members: Agent[];\n perDirectorSignals: DirectorSignals[];\n language: ReportLanguage;\n /** Optional supplementary perspective the user asked to be addressed\n * in this regeneration. Empty string / undefined = no supplement. */\n supplement?: string;\n /** Components the composer picked. When undefined / empty, every\n * Stage 2 / Stage 3 caller sees the legacy \"fill all 12 sections\"\n * behaviour — preserves backwards compat with anything that doesn't\n * yet route through the composer. */\n picked?: readonly string[];\n}\n\nconst SCAFFOLD_SYSTEM = [\n \"You are the chair of a boardroom session. The directors have each surfaced their own signals (with lens tags). Your job is to produce a structured scaffold for a McKinsey-grade research note — pyramid principle, MECE, with multi-director thinking surfaced as a structural feature.\",\n \"\",\n \"## Design philosophy\",\n \"\",\n \"A multi-director report's value is **not** a McKinsey report with multiple authors. It is the meta-output the conversation between directors produced — frame shifts, convergent independent reasoning, and questions that did not exist when the room opened. Your scaffold must surface those structurally, not bury them in an appendix.\",\n \"\",\n \"## What you must produce — 12 sections\",\n \"\",\n \"1. **Title** · 8–14 words. The title IS a complete-sentence thesis (e.g. \\\"AI dynamic comics will not kill manga but will compress it into a 'clean version' refuge\\\"), not a topic.\",\n \"\",\n \"2. **Bottom Line** · one-sentence judgement + confidence (high/medium/low) + a one-sentence rationale on why the confidence is what it is (and why not higher).\",\n \"\",\n \"3. **Frame Shift** · the most distinctive multi-director output. Compare the question as opened vs the question now. If the room **redefined** the question, set `shifted: true` and describe the trigger. If the **frame held**, set `shifted: false` and use the section to restate the question with the room's deeper understanding. Never skip this.\",\n \"\",\n \"4. **Headline Findings** · **exactly 3** (hard cap — pyramid principle, MECE). Each is a complete-sentence claim, not a topic. Each has:\",\n \" · `confidence`: high/medium/low\",\n \" · `supporters`: director ids who advanced this\",\n \" · `challengers`: director ids who pushed back (empty array if full alignment — explicitly empty)\",\n \" · `supporting`: 2–3 sub-findings with their own evidence refs\",\n \" · `lensesPresent`: ≥ 2 distinct lens tags spanning the supporting sub-findings\",\n \" · optional `tension`: unresolved disagreement on this finding\",\n \"\",\n \"5. **Convergence** · points where directors arrived at the same conclusion via INDEPENDENT reasoning paths. \\\"Independent\\\" = signals with distinct lens tags. Each ConvergencePoint has ≥ 2 paths via ≥ 2 distinct lenses. If only one director made each point, do NOT list it — convergence requires ≥ 2 directors. Empty array is fine if no real convergence happened.\",\n \"\",\n \"6. **Divergence** · the SINGLE central tension in the room. One sentence statement, then a per-director row with stance / confidence / cost-of-being-wrong / note. Plus 1–3 `resolutionRequirements` — what would we need to know to settle this? Set divergence to null only if there was genuinely no central tension (rare).\",\n \"\",\n \"7. **Positions** · 2–3 named camps. Short evocative label (\\\"The Skeptics\\\", \\\"The Long-Horizon Camp\\\"), one-sentence collective claim, director ids, supporting signal refs. A director appears in only one camp.\",\n \"\",\n \"8. **Visuals** · 0–4 blocks. Content-driven. Pick from:\",\n \" · `comparison-table` — when ≥ 2 named options were compared on shared dimensions\",\n \" · `quadrant-chart` — when items can be plotted on two real axes\",\n \" · `force-field` — drivers vs resistors of one outcome\",\n \" · `strengths-cautions` — strengths / cautions / verdict per option\",\n \" If the discussion contained ≥ 2 named options or paths, you SHOULD produce ≥ 1 visual.\",\n \"\",\n \"9. **Recommendations** · 3–5 concrete actions, each with: `priority` (P0/P1/P2), `action` (imperative), `rationale`, `ownerType`, `horizon` (e.g. \\\"next 30 days\\\"), `successMetric` (observable proof of execution), `riskIfSkipped`. Recommendations are imperatives — \\\"Do X\\\" not \\\"X should happen\\\".\",\n \"\",\n \"10. **Pre-mortem** · 2–3 ways the recommendations could fail. Each: `scenario`, `leadingIndicator` (earliest observable warning), `mitigation`. McKinsey-grade risk thinking.\",\n \"\",\n \"11. **New Questions** · questions that did NOT exist when the room opened but emerged from the conversation. **This is distinct from openQuestions** (residuals). New questions are the highest-value generative output. 1–4 items, each with `question`, `whyItMatters`, `surfacedByDirectorId`. If genuinely no new questions surfaced, return [].\",\n \"\",\n \"12. **Strategic Planning Assumption** · forward-looking probabilistic statement. `statement` is a dated forecast, `probability` is 0–100, `trigger` describes conditions, `falsificationTest` is the observable that would prove it wrong. Set null only if the conversation didn't produce material for one.\",\n \"\",\n \"Plus: **openQuestions** (1–3 residual unresolved questions, ≠ NewQuestions) tagged P0/P1.\",\n \"\",\n \"## Output format\",\n \"\",\n \"Return a single JSON object inside a fenced ```json code block. No prose outside the block.\",\n \"\",\n \"```json\",\n \"{\",\n ' \"title\": \"Complete-sentence thesis · 8-14 words\",',\n ' \"bottomLine\": {',\n ' \"judgement\": \"One-sentence load-bearing judgement.\",',\n ' \"confidence\": \"medium\",',\n ' \"rationale\": \"Why this confidence — what would we need to be more sure?\"',\n ' },',\n ' \"frameShift\": {',\n ' \"shifted\": true,',\n ' \"original\": \"What the question looked like at the open.\",',\n ' \"reframed\": \"What the question looks like now. Empty when shifted=false.\",',\n ' \"trigger\": \"Why the reframe (or why the frame held).\"',\n ' },',\n ' \"headlineFindings\": [',\n \" {\",\n ' \"title\": \"Complete-sentence thesis · the takeaway, not the topic\",',\n ' \"claim\": \"One-sentence load-bearing claim.\",',\n ' \"confidence\": \"high\",',\n ' \"supporters\": [\"dirId-a\", \"dirId-b\"],',\n ' \"challengers\": [],',\n ' \"supporting\": [',\n ' { \"text\": \"Sub-finding sentence with evidence.\", \"evidenceRefs\": [\"dirId-a#0\", \"dirId-b#1\"] }',\n ' ],',\n ' \"lensesPresent\": [\"data\", \"structural\"],',\n ' \"tension\": \"(optional)\",',\n ' \"counterEvidence\": \"1–2 sentences · the STRONGEST argument the room raised AGAINST this finding. Required for dense briefs; \\'\\' acceptable when the room had no real pushback.\",',\n ' \"strategicImplication\": \"1 sentence · what this finding implies for the decision the room is wrestling with. Closes the gap between fact and judgment.\"',\n \" }\",\n \" ],\",\n ' \"convergence\": [',\n ' {',\n ' \"point\": \"What the room aligned on, in one sentence.\",',\n ' \"paths\": [',\n ' { \"directorId\": \"dirId-a\", \"lens\": \"data\", \"reasoning\": \"1-sentence path.\" },',\n ' { \"directorId\": \"dirId-b\", \"lens\": \"first-principle\", \"reasoning\": \"1-sentence path.\" }',\n ' ]',\n ' }',\n ' ],',\n ' \"divergence\": {',\n ' \"statement\": \"The hinge in one sentence.\",',\n ' \"rows\": [',\n ' { \"directorId\": \"dirId-a\", \"stance\": \"against\", \"confidence\": \"high\", \"costOfBeingWrong\": \"≤ 80 chars\", \"note\": \"≤ 80 chars\" }',\n ' ],',\n ' \"resolutionRequirements\": [\"What we\\'d need to know · 1\", \"...2\", \"...3\"]',\n ' },',\n ' \"positions\": [',\n ' { \"label\": \"The Skeptics\", \"claim\": \"One-sentence collective stance.\", \"directors\": [\"dirId-a\"], \"evidenceRefs\": [\"dirId-a#0\"] }',\n ' ],',\n ' \"visuals\": [',\n ' { \"type\": \"comparison-table\", \"title\": \"...\", \"rowLabel\": \"Option\", \"columns\": [\"Speed\", \"Risk\", \"Cost\"], \"rows\": [ { \"name\": \"Option A\", \"cells\": [\"Fast\", \"High\", \"Low\"] } ] },',\n ' { \"type\": \"quadrant-chart\", \"title\": \"...\", \"xLabel\": \"Effort\", \"yLabel\": \"Impact\", \"q1\": \"Quick wins\", \"q2\": \"Major projects\", \"q3\": \"Fill-ins\", \"q4\": \"Thankless tasks\", \"items\": [ { \"label\": \"Idea A\", \"x\": 0.7, \"y\": 0.8 } ] },',\n ' { \"type\": \"force-field\", \"title\": \"...\", \"drivers\": [\"...\"], \"resistors\": [\"...\"] },',\n ' { \"type\": \"strengths-cautions\", \"title\": \"...\", \"rows\": [ { \"option\": \"Option A\", \"strengths\": [\"...\"], \"cautions\": [\"...\"], \"verdict\": \"recommended\" } ] }',\n ' ],',\n ' \"recommendations\": [',\n ' { \"priority\": \"P0\", \"action\": \"Imperative concrete action.\", \"rationale\": \"Why this works.\", \"ownerType\": \"platform team\", \"horizon\": \"next 30 days\", \"successMetric\": \"Observable proof.\", \"riskIfSkipped\": \"What goes wrong.\", \"criticalDependency\": \"What MUST be true for this to work — the load-bearing pre-condition. Forces stress-testing.\" }',\n ' ],',\n ' \"preMortem\": [',\n ' { \"scenario\": \"How it fails.\", \"leadingIndicator\": \"Earliest warning.\", \"mitigation\": \"What to do.\" }',\n ' ],',\n ' \"newQuestions\": [',\n ' { \"question\": \"Question?\", \"whyItMatters\": \"Why this is generative.\", \"surfacedByDirectorId\": \"dirId-b\" }',\n ' ],',\n ' \"planningAssumption\": {',\n ' // Strategic Planning Assumption · Gartner-format. The statement MUST follow:',\n ' // \"By [date / horizon], [N]% probability that [event will happen], unless [falsifier].\"',\n ' // Probability is the integer 0–100 in the `probability` field, NOT prose. Falsifier is a',\n ' // separately-named observable in the `falsificationTest` field for downstream rendering.',\n ' \"statement\": \"By Q4 2027, 70% probability that X% of platforms will Y, unless Z.\",',\n ' \"probability\": 70,',\n ' \"trigger\": \"Conditions under which this holds.\",',\n ' \"falsificationTest\": \"Single observable that would prove it wrong.\"',\n ' },',\n ' \"openQuestions\": [',\n ' { \"text\": \"Residual unresolved question?\", \"priority\": \"P0\" }',\n ' ],',\n \" // ── Gartner-density blocks (composer-picked · null when not picked) ──\",\n ' \"strategicOutlook\": {',\n ' \"context\": \"Operating context · 2–4 sentences (≤ 600 chars). What forces are in motion, who is affected, what is at stake.\",',\n ' \"implication\": \"The strategic implication for decision-makers · 2–3 sentences (≤ 500 chars).\"',\n ' },',\n ' \"criticalAssumptions\": [',\n ' { \"statement\": \"The brief\\'s logic assumes …\", \"confidence\": \"medium\", \"falsifier\": \"Observable that would prove this wrong.\", \"horizon\": \"next 12 months\", \"attribution\": \"Marc · structural lens\" }',\n ' ],',\n ' \"scenarioTree\": {',\n ' \"intro\": \"One-sentence framing for the tree.\",',\n ' \"branches\": [',\n ' { \"label\": \"Base case\", \"probability\": 55, \"trigger\": \"What tips us into this.\", \"effects\": [\"Effect 1\", \"Effect 2\"], \"decisionImplication\": \"What this means for the decision.\" },',\n ' { \"label\": \"Upside\", \"probability\": 25, \"trigger\": \"...\", \"effects\": [], \"decisionImplication\": \"...\" },',\n ' { \"label\": \"Downside\", \"probability\": 20, \"trigger\": \"...\", \"effects\": [], \"decisionImplication\": \"...\" }',\n ' ]',\n ' },',\n ' \"leadingIndicators\": [',\n ' { \"signal\": \"What to watch.\", \"threshold\": \"Threshold or pattern that flips the read.\", \"cadence\": \"weekly\", \"flipsTo\": \"Which scenario this confirms or which assumption it falsifies.\" }',\n ' ]',\n \"}\",\n \"```\",\n \"\",\n \"## Hard rules (do not violate)\",\n \"\",\n \"· **headlineFindings.length === 3** — hard cap. Force MECE. If the room produced more, merge or drop.\",\n \"· Use the exact director ids supplied in the input — never fabricate.\",\n \"· Reference signals as `<directorId>#<signalIndex>` exactly as labelled.\",\n \"· `convergence` requires ≥ 2 directors via ≥ 2 distinct lenses. Otherwise empty array.\",\n \"· `challengers: []` (explicitly empty) when full alignment — never omit the field.\",\n \"· `frameShift.shifted` must be honest — only set to true when the question itself was redefined, not just deepened.\",\n \"· Output JSON only. Do not write the final prose.\",\n \"\",\n \"## Substitute groups · component selection (Stage 1.5)\",\n \"\",\n \"The user message will list which components have been picked for this room. Three substitute groups exist; each group has one default and one alternative. Fill ONLY the picked field; set the substitute's default to the empty value (the renderer skips empties cleanly).\",\n \"\",\n \"Anchor:\",\n \" · `bottom-line` (default) → fill `bottomLine`. Leave `thesis: null`, `workingHypothesis: null`.\",\n \" · `thesis` → fill `thesis: { claim, reasoning }`. Leave others null.\",\n \" · `working-hypothesis` → fill `workingHypothesis: { hypothesis, reasonsItMayBeWrong[] }`. Leave others null.\",\n \"\",\n \"Findings:\",\n \" · `headline-findings` (default) → fill `headlineFindings` with 3 pillars. Leave `bigIdeas: null`.\",\n \" · `big-ideas` → fill `bigIdeas` with EXACTLY 3 numbered ideas. Leave `headlineFindings: []`.\",\n \"\",\n \"Action:\",\n \" · `recommendations` (default) → fill `recommendations`. Leave `theBet: null`, `considerations: null`.\",\n \" · `the-bet` → fill `theBet: { ifBacked, conditions[3-5], killCriteria }`. Leave others.\",\n \" · `considerations` → fill `considerations` with the SAME shape as `recommendations` (3-5 items, P0/P1/P2, owner, horizon, success metric, risk-if-skipped). Voice should be hedged in the prose (we'll worry about voice at write time; the data shape is identical).\",\n \"\",\n \"Optional kinds (`frame-shift`, `convergence`, `divergence`, `positions`, `visuals`, `two-paths`, `why-now`, `pre-mortem`, `new-questions`, `planning-assumption`, `open-questions`, `strategic-outlook`, `critical-assumptions`, `scenario-tree`, `leading-indicators`): when listed in the picked set, fill them as the spec above describes. When NOT listed, set them to the empty value (`[]` for arrays, `null` for nullable objects, `{shifted:false, original:'', reframed:'', trigger:''}` for frameShift).\",\n \"\",\n \"## Substitute schemas (when picked)\",\n \"\",\n \"`thesis`:\",\n \"```json\",\n '{ \"claim\": \"Complete-sentence load-bearing thesis · 12-22 words\", \"reasoning\": \"1-2 sentences on why this is THE claim.\" }',\n \"```\",\n \"\",\n \"`bigIdeas` (exactly 3, numbered 1/2/3 in order):\",\n \"```json\",\n \"[\",\n ' { \"number\": 1, \"claim\": \"Punchy claim · 8-14 words\", \"why\": \"1-2 sentences.\", \"evidenceRefs\": [\"dirId-a#0\"] },',\n ' { \"number\": 2, \"claim\": \"...\", \"why\": \"...\", \"evidenceRefs\": [] },',\n ' { \"number\": 3, \"claim\": \"...\", \"why\": \"...\", \"evidenceRefs\": [] }',\n \"]\",\n \"```\",\n \"\",\n \"`theBet`:\",\n \"```json\",\n \"{\",\n ' \"ifBacked\": \"Opening sentence framing the bet — \\\\\"If we were to back this...\\\\\".\",',\n ' \"conditions\": [',\n ' { \"condition\": \"Imperative · what must hold\", \"why\": \"Why this condition is load-bearing.\" }',\n \" ],\",\n ' \"killCriteria\": \"The single observable that would tell us to stop.\"',\n \"}\",\n \"```\",\n \"\",\n \"`workingHypothesis`:\",\n \"```json\",\n \"{\",\n ' \"hypothesis\": \"1-2 sentences in essay voice stating the working position.\",',\n ' \"reasonsItMayBeWrong\": [\"≤ 30 words · reason 1\", \"reason 2\", \"reason 3\"]',\n \"}\",\n \"```\",\n \"\",\n \"`whyNow`:\",\n \"```json\",\n \"{\",\n ' \"windowOpened\": \"What recently opened this window. ≤ 200 chars.\",',\n ' \"windowCloses\": \"When and why it closes. ≤ 200 chars.\",',\n ' \"whatToBetOn\": \"The bet implied by the window. ≤ 200 chars.\"',\n \"}\",\n \"```\",\n \"\",\n \"`twoPaths`:\",\n \"```json\",\n \"{\",\n ' \"intro\": \"Optional 1-sentence framing for both paths. Empty string ok.\",',\n ' \"pathA\": { \"label\": \"Short label · ≤ 32 chars\", \"body\": \"1 paragraph trajectory. ≤ 500 chars.\" },',\n ' \"pathB\": { \"label\": \"Short label · ≤ 32 chars\", \"body\": \"1 paragraph trajectory. ≤ 500 chars.\" }',\n \"}\",\n \"```\",\n \"\",\n \"`considerations`: same JSON shape as `recommendations` (array of items with priority / action / rationale / ownerType / horizon / successMetric / riskIfSkipped).\",\n \"\",\n \"`strategicOutlook`:\",\n \"```json\",\n \"{\",\n ' \"context\": \"Paragraph 1 · 2–4 sentences naming forces in motion, stakeholders affected, what is at stake. ≤ 600 chars. Set up the operating environment so the findings have weight.\",',\n ' \"implication\": \"Paragraph 2 · 2–3 sentences flowing from the context to what this changes for decision-makers. ≤ 500 chars.\"',\n \"}\",\n \"```\",\n \"\",\n \"`criticalAssumptions` (4–6 items · the load-bearing assumptions the brief\\'s logic rests on):\",\n \"```json\",\n \"[\",\n ' {',\n ' \"statement\": \"The brief\\'s logic assumes that … (complete sentence, ≤ 200 chars).\",',\n ' \"confidence\": \"high | medium | low\",',\n ' \"falsifier\": \"The single observable / event that would prove this assumption wrong (≤ 200 chars).\",',\n ' \"horizon\": \"Time window the assumption must hold (≤ 80 chars · e.g. \\\\\"next 12 months\\\\\", \\\\\"Q2 2026\\\\\", \\\\\"the duration of the conflict\\\\\")\",',\n ' \"attribution\": \"Director name · lens (≤ 80 chars · e.g. \\\\\"Long Horizon · structural\\\\\")\"',\n \" }\",\n \"]\",\n \"```\",\n \"Surfacing assumptions is the discipline lever — these are the foundations the reader gets to stress-test.\",\n \"\",\n \"`scenarioTree` (2–4 named futures with quantitative anchoring):\",\n \"```json\",\n \"{\",\n ' \"intro\": \"One-sentence framing.\",',\n ' \"branches\": [',\n ' {',\n ' \"label\": \"≤ 40 chars · descriptive (e.g. \\\\\"Protracted stalemate\\\\\", not \\\\\"Scenario 1\\\\\")\",',\n ' \"probability\": 55, // 0–100 integer · all branches sum to ~100',\n ' \"trigger\": \"What would tip the room into this branch (≤ 200 chars).\",',\n ' \"effects\": [\"2–3 dominant effects under this branch.\"],',\n ' \"decisionImplication\": \"What this branch implies for the decision at hand (≤ 240 chars).\"',\n ' }',\n \" ]\",\n \"}\",\n \"```\",\n \"Sum of `probability` across branches must be 95–105 (drift to 100 ± 5 allowed).\",\n \"\",\n \"`leadingIndicators` (3–5 monitoring signals):\",\n \"```json\",\n \"[\",\n ' {',\n ' \"signal\": \"What to watch (≤ 80 chars · e.g. \\\\\"Brent crude vs $90\\\\\", \\\\\"Korean trade balance month-on-month\\\\\").\",',\n ' \"threshold\": \"Threshold or pattern that flips the interpretation (≤ 200 chars).\",',\n ' \"cadence\": \"How often to check (≤ 60 chars · e.g. \\\\\"daily\\\\\", \\\\\"every CPI release\\\\\", \\\\\"per Fed minutes\\\\\").\",',\n ' \"flipsTo\": \"What hitting this threshold confirms — which scenario it points to or which assumption it falsifies (≤ 240 chars).\"',\n \" }\",\n \"]\",\n \"```\",\n].join(\"\\n\");\n\n/** Component-selection block · used by the orchestrator after Stage 1.5\n * to tell Stage 2 / Stage 3 which components the composer picked. When\n * the picked array is empty (e.g. a legacy code path that never went\n * through the composer), this returns \"\" and the prompts behave as if\n * every component was picked — preserves the v1 12-section behaviour. */\nfunction pickedBlock(picked: readonly string[] | undefined): string {\n if (!picked || picked.length === 0) return \"\";\n const allKnown = new Set([\n \"bottom-line\", \"thesis\", \"working-hypothesis\",\n \"frame-shift\",\n \"headline-findings\", \"big-ideas\",\n \"convergence\", \"divergence\", \"positions\",\n \"visuals\", \"two-paths\", \"why-now\",\n \"recommendations\", \"the-bet\", \"considerations\",\n \"pre-mortem\", \"new-questions\", \"planning-assumption\",\n \"open-questions\",\n // Gartner-density blocks\n \"strategic-outlook\", \"critical-assumptions\", \"scenario-tree\", \"leading-indicators\",\n ]);\n const set = new Set(picked.filter((k) => allKnown.has(k)));\n if (!set.size) return \"\";\n const skipped: string[] = [];\n for (const k of allKnown) if (!set.has(k)) skipped.push(k);\n return [\n ``,\n `─── COMPOSER PICKED COMPONENTS ───`,\n ``,\n `The composer (Stage 1.5) picked these components for this brief — fill ONLY these fields:`,\n ...[...set].sort().map((k) => ` · ${k}`),\n ``,\n `Skip these components (set their fields to empty/null per the substitute-group rules in the system prompt):`,\n ...skipped.sort().map((k) => ` · ${k}`),\n ``,\n `─── END PICKED ───`,\n ].join(\"\\n\");\n}\n\nexport function buildScaffoldMessages(opts: ScaffoldOpts): LLMMessage[] {\n const { room, members, perDirectorSignals, language, picked } = opts;\n\n const memberList = members\n .map((a) => `${a.id} · ${a.name} (${a.handle}) — ${a.roleTag}`)\n .join(\"\\n · \");\n\n const signalsBlock = perDirectorSignals\n .map((d) => {\n if (!d.signals.length) return `[${d.directorId}] ${d.directorName} — (no signals)`;\n const lines = d.signals\n .map(\n (s, i) =>\n ` · ${d.directorId}#${i} [${s.lens}] ${s.text}`,\n )\n .join(\"\\n\");\n return `[${d.directorId}] ${d.directorName}\\n${lines}`;\n })\n .join(\"\\n\\n\");\n\n const supplementBlock = opts.supplement && opts.supplement.trim()\n ? [\n ``,\n `─── SUPPLEMENTARY PERSPECTIVE FROM USER ───`,\n ``,\n `The user has asked you to additionally consider this angle when building the scaffold. Address it explicitly — work it into the scaffold's findings, divergence, recommendations, and/or new questions wherever it lands most cleanly. Do NOT add a separate section for it; weave it through.`,\n ``,\n opts.supplement.trim(),\n ``,\n `─── END SUPPLEMENT ───`,\n ].join(\"\\n\")\n : \"\";\n\n return [\n {\n role: \"system\",\n content: [SCAFFOLD_SYSTEM, \"\", languageInstruction(language)].join(\"\\n\"),\n },\n {\n role: \"user\",\n content: [\n `ROOM #${room.number} · ${room.name}`,\n `Subject: ${room.subject}`,\n `Mode: ${room.mode}`,\n ``,\n `Directors:`,\n ` · ${memberList}`,\n ``,\n `─── SIGNALS ───`,\n ``,\n signalsBlock || \"(no signals extracted)\",\n ``,\n `─── END SIGNALS ───`,\n pickedBlock(picked),\n supplementBlock,\n ``,\n `Produce the scaffold now. JSON only.`,\n ].join(\"\\n\"),\n },\n ];\n}\n\n/* ─────────────────────── Stage 3 · chair final write ──────────────────── */\n\ninterface WriteOpts {\n room: Room;\n members: Agent[];\n scaffold: BriefScaffold;\n perDirectorSignals: DirectorSignals[];\n language: ReportLanguage;\n /** Optional supplementary perspective. The chair must visibly address\n * this in the final write — not as a separate section, but woven\n * through the relevant existing sections. */\n supplement?: string;\n /** Components the composer picked. When undefined / empty, every\n * section is fair game — preserves legacy \"render whatever's filled\"\n * behaviour. */\n picked?: readonly string[];\n}\n\nconst WRITE_SYSTEM = [\n \"You are the chair of a boardroom session. You have a structured scaffold. Write the final report in markdown — a McKinsey-grade research note that makes the multi-director thinking visible. Pyramid principle, MECE, action-oriented.\",\n \"\",\n \"## Required structure (in order — never reorder)\",\n \"\",\n \"Start with a single H2 title from `scaffold.title` verbatim.\",\n \"\",\n \" ## Bottom Line\",\n \" One short paragraph (1–3 sentences). Lead with the scaffold's `bottomLine.judgement` rephrased for impact, italicized. Then state the confidence inline using this exact format: `**Confidence: {high/medium/low}** — {rationale}`.\",\n \" This section is ALWAYS rendered. It is the report's visual anchor.\",\n \"\",\n \" ## Frame Shift\",\n \" This is the most distinctive multi-director output. ALWAYS rendered. Two cases:\",\n \" · If `frameShift.shifted: true` — write 2–3 sentences using this pattern: \\\"The room opened with {original}. By {trigger description}, the question shifted to {reframed}.\\\"\",\n \" · If `frameShift.shifted: false` — write \\\"The frame held: the room sharpened {original} rather than redefining it. {trigger as 1-sentence rationale}.\\\"\",\n \"\",\n \" ## Headline Findings\",\n \" Exactly 3 findings. For each one, render as:\",\n \" ### {finding.title}\",\n \" Open with the claim italicized in one line: *\\\"{claim}\\\"*\",\n \" Then a `**Confidence: {high/medium/low}** · supported by {supporters as names} · challenged by {challengers as names, or \\\"none — full alignment\\\"}` line.\",\n \" Then 2–3 prose paragraphs (NOT bullets — paragraphs) building the case. Each paragraph anchored on one sub-finding from `supporting`. Make evidence diversity visible: a finding tagged `[data, structural]` must visibly use both a data point AND a structural argument. Cite directors by name when their phrasing IS the point.\",\n \" If `tension` is present, surface it explicitly with an em-dash aside or a dedicated final paragraph beginning *— However,*\",\n \" If `counterEvidence` is non-empty, render it as a dedicated final paragraph beginning **— Counter-argument:** followed by the prose. Makes the room's adversarial review visible.\",\n \" If `strategicImplication` is non-empty, render it as the closing italic line:\",\n \" *Strategic implication: {strategicImplication}*\",\n \" These two fields turn each finding from a fact into a stress-tested judgment — REQUIRED on dense briefs.\",\n \"\",\n \" ## Where We Converged\",\n \" Skip this section entirely if `convergence` is empty.\",\n \" Otherwise: one short intro paragraph (1–2 sentences) explaining that despite different starting positions, certain conclusions held.\",\n \" Then for each convergence point:\",\n \" > **{point}**\",\n \" > • {director name} via *{lens}*: {reasoning}\",\n \" > • {another director name} via *{lens}*: {reasoning}\",\n \" > • (etc.)\",\n \" Render this as a blockquote, with bullets of the independent paths.\",\n \"\",\n \" ## Where We Diverged\",\n \" Skip this section entirely if `divergence` is null.\",\n \" Otherwise:\",\n \" Open with one short paragraph (2–3 sentences) stating `divergence.statement` and why it matters.\",\n \" Then a markdown table with columns `Director | Stance | Confidence | Cost of Being Wrong | Note`. One row per `divergence.rows` entry. Render stance with markers: `for` → **For** · `against` → **Against** · `nuanced` → **Nuanced**.\",\n \" Then a final subsection:\",\n \" **What would resolve this:**\",\n \" A bulleted list of `divergence.resolutionRequirements`.\",\n \"\",\n \" ## Positions\",\n \" Skip if `positions` is empty. Otherwise one subsection per camp:\",\n \" ### {camp.label}\",\n \" Open with **bold restatement of `claim`**. Then 2–3 sentences of explanation drawing on the camp's evidence refs. End the subsection with a blockquote pulling the most evocative phrase from one of the camp's directors:\",\n \" > *\\\"…\\\"* — {director name}\",\n \" Use the director's actual words from their signal text — pick the one that best lands the point. Trim to ≤ 40 words. **Each camp gets exactly one pull-quote.**\",\n \"\",\n \" ## Options Analysis\",\n \" Skip if `visuals` is empty. Otherwise, for each visual render as below. Use the visual's `title` as the H3 heading.\",\n \"\",\n \" For `comparison-table`:\",\n \" ### {title}\",\n \" A markdown table with `{rowLabel}` as the first column header and `columns` as the rest. One row per `rows` entry.\",\n \"\",\n \" For `quadrant-chart`:\",\n \" ### {title}\",\n \" Render a fenced ```mermaid block with `quadrantChart`. EXACT shape — mermaid 10.9.5 is strict:\",\n \" ```\",\n \" quadrantChart\",\n \" title {title}\",\n \" x-axis \\\"Low {xLabel}\\\" --> \\\"High {xLabel}\\\"\",\n \" y-axis \\\"Low {yLabel}\\\" --> \\\"High {yLabel}\\\"\",\n \" quadrant-1 \\\"{q1}\\\"\",\n \" quadrant-2 \\\"{q2}\\\"\",\n \" quadrant-3 \\\"{q3}\\\"\",\n \" quadrant-4 \\\"{q4}\\\"\",\n \" \\\"{item.label}\\\": [{item.x}, {item.y}]\",\n \" ```\",\n \" Hard rules to avoid mermaid syntax errors (the lexer fails on non-ASCII in unquoted labels):\",\n \" · BOTH x-axis AND y-axis lines MUST be `... \\\"Low X\\\" --> \\\"High X\\\"` form — both ends in DOUBLE QUOTES.\",\n \" · Quadrant labels MUST be in DOUBLE QUOTES (`quadrant-1 \\\"短语\\\"`) — never bare text. The lexer rejects unquoted CJK / parens / `+`.\",\n \" · Each item line is `\\\"Label\\\": [x, y]` with the label in DOUBLE QUOTES. Inside the label: no `:` no `\\\"` no `[` no `]`. Replace with ` - ` if needed.\",\n \" · Use HALFWIDTH parens `()` not fullwidth `()` anywhere inside the diagram.\",\n \" · Numeric coords are decimals strictly inside `(0, 1)` — round to 2 decimals. Never use 0 or 1 exactly.\",\n \" · Title is one line, plain text — no quotes, no colons. Title is the only label that is NOT quoted.\",\n \" · One item per indented line. No blank lines inside the fenced block.\",\n \"\",\n \" For `force-field`:\",\n \" ### {title}\",\n \" A 2-column markdown table with headers `Drivers ↑` and `Resistors ↓`. Each driver/resistor on its own row. Pad shorter side with empty cells.\",\n \"\",\n \" For `strengths-cautions`:\",\n \" ### {title}\",\n \" A markdown table with columns `Option | Strengths | Cautions | Verdict`. Each row's Strengths/Cautions cells are bullet-separated (· between items). Verdict markers: `recommended` → **Recommended** · `caution` → ⚠ **Caution required** · `not-recommended` → **Not recommended**.\",\n \"\",\n \" ## Recommendations\",\n \" Skip if `recommendations` is empty. Otherwise render as a numbered list, one per recommendation, sorted by priority. Each item:\",\n \" 1. **`P0`** **{action}**\",\n \" _Rationale:_ {rationale}\",\n \" _Owner:_ {ownerType} · _Horizon:_ {horizon}\",\n \" _Success metric:_ {successMetric}\",\n \" _Critical dependency:_ {criticalDependency}\",\n \" _Risk if skipped:_ {riskIfSkipped}\",\n \" Use **`P0`** / **`P1`** / **`P2`** as priority badges (literal backticked text, bolded). Each numbered item gets one blank line before the next.\",\n \" The _Critical dependency_ line is the load-bearing pre-condition — what MUST be true for this action to actually work. Render it whenever `criticalDependency` is non-empty; skip the line only on legacy scaffolds where the field is absent.\",\n \"\",\n \" ## Pre-mortem\",\n \" Skip if `preMortem` is empty. Otherwise a markdown table with columns `Failure mode | Leading indicator | Mitigation`. One row per failure mode.\",\n \"\",\n \" ## New Questions This Surfaced\",\n \" Skip if `newQuestions` is empty. Otherwise:\",\n \" Open with one sentence framing this as the conversation's generative output: \\\"The conversation surfaced {N} questions that weren't on the table when the room opened — these are where to point the next session.\\\"\",\n \" Then a numbered list. Each item:\",\n \" 1. **{question}**\",\n \" _Why it matters:_ {whyItMatters}\",\n \" _Surfaced by:_ {director name}\",\n \"\",\n \" ## Strategic Planning Assumption\",\n \" Skip if `planningAssumption` is null. Otherwise render in Gartner SPA format:\",\n \" > **Strategic Planning Assumption · {probability}% probability**\",\n \" > {statement} (← MUST follow the SPA format: \\\"By [date / horizon], [N]% probability that [event will happen], unless [falsifier].\\\")\",\n \" > \",\n \" > _Triggered when:_ {trigger}\",\n \" > _Falsified by:_ {falsificationTest}\",\n \" The statement field already encodes the date + probability + falsifier inline; the explicit `_Falsified by_` line surfaces the falsifier as a separate observable for monitoring. Do NOT relax the SPA format into prose — the structure is what makes the assumption stress-testable.\",\n \"\",\n \" ## Open Questions\",\n \" Skip if `openQuestions` is empty. Otherwise a bulleted list. Each bullet: priority badge `**\\\\`P0\\\\`**` or `\\\\`P1\\\\`` followed by the question text.\",\n \"\",\n \"## Substitute components (composer-driven · render only when filled)\",\n \"\",\n \" ### thesis (anchor alternative)\",\n \" When `scaffold.thesis` is non-null AND `scaffold.bottomLine.judgement` is empty, render in place of `## Bottom Line`:\",\n \" ## The Thesis\",\n \" *\\\"{thesis.claim}\\\"*\",\n \"\",\n \" {thesis.reasoning · 1-2 sentences in prose, not italicized.}\",\n \" Skip both this section AND `## Bottom Line` only if both fields are empty (should not happen — composer always picks an anchor).\",\n \"\",\n \" ### bigIdeas (findings alternative)\",\n \" When `scaffold.bigIdeas` is a 3-element array AND `scaffold.headlineFindings` is empty, render in place of `## Headline Findings`:\",\n \" ## Three Big Ideas\",\n \" Open with one sentence framing the trio.\",\n \" Then a numbered list (3 items, in order):\",\n \" 1. **{idea.claim}**\",\n \" {idea.why · 1-2 sentences citing director names where evidenceRefs land cleanly.}\",\n \" 2. **...**\",\n \" 3. **...**\",\n \" Numbers come from the field — render `1.` `2.` `3.` literally.\",\n \"\",\n \" ### theBet (action alternative)\",\n \" When `scaffold.theBet` is non-null AND `scaffold.recommendations` is empty, render in place of `## Recommendations`:\",\n \" ## The Bet\",\n \" Open with `{theBet.ifBacked}` as a single italicized line: *{ifBacked}*\",\n \" Then a numbered list of conditions:\",\n \" 1. **{condition.condition}**\",\n \" {condition.why · 1-2 sentences.}\",\n \" Close with a callout line:\",\n \" > **Kill criteria:** {killCriteria}\",\n \"\",\n \" ### workingHypothesis (anchor alternative · Anthropic-essay spine)\",\n \" When `scaffold.workingHypothesis` is non-null and the other anchors are empty, render in place of `## Bottom Line`:\",\n \" ## A working hypothesis\",\n \" {workingHypothesis.hypothesis · written as essay prose, NOT italicized. 1-2 sentences.}\",\n \"\",\n \" **Reasons it may be wrong:**\",\n \" · {reason 1}\",\n \" · {reason 2}\",\n \" · {reason 3}\",\n \" Voice register here is genuinely tentative — \\\"may be wrong\\\", \\\"if X, then\\\", \\\"we are uncertain about\\\". Do NOT collapse this into a confident judgement; the section's value is the hedge.\",\n \"\",\n \" ### whyNow (forward / opportunity panel)\",\n \" When `scaffold.whyNow` is non-null AND was picked, render after the anchor (or after frameShift if present):\",\n \" ## Why Now\",\n \" A short 3-paragraph block, one paragraph per field:\",\n \" Paragraph 1 (the open): {windowOpened}\",\n \" Paragraph 2 (the close): {windowCloses}\",\n \" Paragraph 3 (the bet): {whatToBetOn}\",\n \" Each paragraph is 2-3 sentences in plain prose. Do NOT add bullets. Do NOT label the paragraphs with the field names — the prose tells the reader which is which.\",\n \"\",\n \" ### strategicOutlook (Gartner-density · sits between anchor and findings)\",\n \" When `scaffold.strategicOutlook` is non-null AND was picked, render it AFTER the anchor (Bottom Line / Thesis / Working Hypothesis) and BEFORE the findings:\",\n \" ## Strategic Outlook\",\n \" Two prose paragraphs:\",\n \" Paragraph 1: {strategicOutlook.context}\",\n \" Paragraph 2: {strategicOutlook.implication}\",\n \" Plain prose, no bullets, no labels on the paragraphs. The first paragraph sets the stage; the second flows the implication for decision-makers. Reads like a Gartner / Bain research-note opener.\",\n \"\",\n \" ### criticalAssumptions (Gartner-density · the load-bearing assumptions log)\",\n \" When `scaffold.criticalAssumptions` is non-empty AND was picked, render it AFTER the findings and BEFORE recommendations:\",\n \" ## Critical Assumptions\",\n \" Open with one sentence framing why surfacing assumptions matters: \\\"The brief's logic rests on the following — each assumption is named explicitly so it can be stress-tested.\\\"\",\n \" Then a numbered list. Each item:\",\n \" 1. **{statement}**\",\n \" _Confidence:_ {confidence} · _Horizon:_ {horizon} · _Attribution:_ {attribution}\",\n \" _Falsified by:_ {falsifier}\",\n \" The `_Falsified by_` line is what makes this Gartner-grade — every assumption has a single observable that would prove it wrong. Render even when confidence is high; high-confidence assumptions still need their falsifier named.\",\n \"\",\n \" ### scenarioTree (Gartner-density · 2–4 named futures with probabilities)\",\n \" When `scaffold.scenarioTree` is non-null AND was picked, render it AFTER critical assumptions:\",\n \" ## Scenario Tree\",\n \" {scenarioTree.intro · 1 sentence framing the tree.}\",\n \" Then ONE subsection per branch (### header), in descending probability order:\",\n \" ### {label} · {probability}%\",\n \" _Trigger:_ {trigger}\",\n \"\",\n \" _Effects:_\",\n \" · {effect 1}\",\n \" · {effect 2}\",\n \" · {effect 3}\",\n \"\",\n \" _What this means for the decision:_ {decisionImplication}\",\n \" Probabilities visible in the heading make the scenario weights legible at a glance. Effects render as a tight bulleted list (2–3 per branch). Decision implication closes each branch by linking it to action.\",\n \"\",\n \" ### leadingIndicators (Gartner-density · monitoring discipline)\",\n \" When `scaffold.leadingIndicators` is non-empty AND was picked, render it AFTER the scenario tree (or after recommendations if there's no scenario tree):\",\n \" ## Leading Indicators\",\n \" Open with one sentence framing the watch-list: \\\"These are the signals to monitor — each has a threshold that flips the read of which scenario is materializing.\\\"\",\n \" Then a markdown table with columns `Signal | Threshold | Cadence | Flips to`:\",\n \" | Signal | Threshold | Cadence | Flips to |\",\n \" | --- | --- | --- | --- |\",\n \" | {signal} | {threshold} | {cadence} | {flipsTo} |\",\n \" Each row is one indicator. Keep cell content tight — the value is in seeing all 3-5 indicators side-by-side as a watch-list, not in prose elaboration.\",\n \"\",\n \" ### twoPaths (multi-perspective / comparison alternative)\",\n \" When `scaffold.twoPaths` is non-null AND was picked, render in place of (or alongside) `## Options Analysis`:\",\n \" ## Two Paths\",\n \" {intro · 1 sentence framing both paths. Skip if intro is empty.}\",\n \"\",\n \" A 2-column markdown table with these exact headers:\",\n \" | Path A · {pathA.label} | Path B · {pathB.label} |\",\n \" | --- | --- |\",\n \" | {pathA.body} | {pathB.body} |\",\n \" Body cells are 1 paragraph each. Do NOT include line breaks inside table cells.\",\n \"\",\n \" ### considerations (action alternative · softer voice)\",\n \" When `scaffold.considerations` is non-empty AND `scaffold.recommendations` is empty, render in place of `## Recommendations`:\",\n \" ## Considerations\",\n \" A numbered list with the same shape as Recommendations BUT in hedged voice:\",\n \" 1. **{consideration as bold lead-in, ≤ 12 words}**\",\n \" _Worth thinking about because:_ {rationale}\",\n \" _Who'd own it:_ {ownerType} · _On what horizon:_ {horizon}\",\n \" _What you'd watch:_ {successMetric}\",\n \" _What you'd give up by not doing this:_ {riskIfSkipped}\",\n \" No P0/P1/P2 priority badges in this voice — the priority is implicit in the order. Use \\\"might\\\", \\\"could\\\", \\\"worth considering\\\" instead of \\\"do\\\" / \\\"build\\\" / \\\"ship\\\". The data is the same as recommendations; the words around it are softer.\",\n \"\",\n \"## Voice rules\",\n \"\",\n \"· Plain prose. No flattery. No \\\"the room concluded that…\\\" hedging.\",\n \"· Use *italics* for load-bearing words and direct quotes.\",\n \"· Use **bold** for claims and section markers.\",\n \"· No \\\"I\\\" or \\\"we\\\" as the writer. The brief is the room speaking.\",\n \"· No preamble, no closing remarks, no \\\"in summary\\\". Just the brief.\",\n \"· Markdown only — fenced ```mermaid blocks are part of markdown for our renderer.\",\n \"· Replace all director ids (like `dirId-a`) with display names. Never let a raw id leak into prose.\",\n \"· Numbers everywhere — even qualitative claims get bracketed by numbers when possible (\\\"about 2/3 of the directors\\\", \\\"in the next 18 months\\\", \\\"~30% confidence\\\").\",\n \"· Section headings ARE the takeaway — never use topic-style headings (e.g. \\\"Market analysis\\\"). Always claim-style (e.g. \\\"China growth will slow to <5%\\\").\",\n].join(\"\\n\");\n\n/** Render a single signal reference as a one-liner with lens + director + text. */\nfunction renderSignalRef(\n ref: string,\n perDirectorSignals: DirectorSignals[],\n): string {\n const [dirId, idxStr] = ref.split(\"#\");\n const idx = Number(idxStr);\n for (const d of perDirectorSignals) {\n if (d.directorId === dirId && Number.isFinite(idx) && d.signals[idx]) {\n return `[${d.signals[idx].lens}] ${d.directorName}: ${d.signals[idx].text}`;\n }\n }\n return `[${ref}] (missing)`;\n}\n\nexport function buildWriteMessages(opts: WriteOpts): LLMMessage[] {\n const { room, members, scaffold, perDirectorSignals, language, picked } = opts;\n\n const directorNameById = new Map(members.map((a) => [a.id, a.name]));\n const nameOf = (id: string) => directorNameById.get(id) || id;\n\n const memberList = members\n .map((a) => `${a.id} · ${a.name} (${a.handle}) — ${a.roleTag}`)\n .join(\"\\n · \");\n\n // ── Bottom Line ──\n const bottomLineBlock = [\n ` Judgement: ${scaffold.bottomLine.judgement}`,\n ` Confidence: ${scaffold.bottomLine.confidence}`,\n ` Rationale: ${scaffold.bottomLine.rationale || \"(none)\"}`,\n ].join(\"\\n\");\n\n // ── Frame Shift ──\n const frameShiftBlock = [\n ` Shifted: ${scaffold.frameShift.shifted}`,\n ` Original framing: ${scaffold.frameShift.original}`,\n ` Reframed: ${scaffold.frameShift.reframed || \"(n/a — frame held)\"}`,\n ` Trigger: ${scaffold.frameShift.trigger || \"(none)\"}`,\n ].join(\"\\n\");\n\n // ── Headline Findings ──\n const headlineFindingsBlock = scaffold.headlineFindings\n .map((f, i) => {\n const supporters = f.supporters.map(nameOf).join(\", \") || \"—\";\n const challengers = f.challengers.length\n ? f.challengers.map(nameOf).join(\", \")\n : \"(none — full alignment)\";\n const sub = f.supporting\n .map((s, si) => {\n const refs = s.evidenceRefs.length\n ? s.evidenceRefs.map((r) => ` · ${renderSignalRef(r, perDirectorSignals)}`).join(\"\\n\")\n : \" · (no evidence refs)\";\n return [\n ` Sub-finding ${si + 1}: ${s.text}`,\n ` Evidence:`,\n refs,\n ].join(\"\\n\");\n })\n .join(\"\\n\\n\");\n const tensionLine = f.tension ? `\\n Tension: ${f.tension}` : \"\";\n const counterLine = f.counterEvidence ? `\\n Counter-evidence: ${f.counterEvidence}` : \"\";\n const implicationLine = f.strategicImplication ? `\\n Strategic implication: ${f.strategicImplication}` : \"\";\n return [\n ` ### Finding ${i + 1}: ${f.title}`,\n ` Claim: ${f.claim}`,\n ` Confidence: ${f.confidence}`,\n ` Supporters: ${supporters}`,\n ` Challengers: ${challengers}`,\n ` Lenses present: ${f.lensesPresent.join(\" + \") || \"—\"}${tensionLine}${counterLine}${implicationLine}`,\n ` Supporting:`,\n sub,\n ].join(\"\\n\");\n })\n .join(\"\\n\\n\");\n\n // ── Convergence ──\n const convergenceBlock = scaffold.convergence.length\n ? scaffold.convergence\n .map((c, i) => {\n const paths = c.paths\n .map((p) => ` · ${nameOf(p.directorId)} via [${p.lens}]: ${p.reasoning}`)\n .join(\"\\n\");\n return [\n ` Convergence ${i + 1}: ${c.point}`,\n ` Independent paths:`,\n paths,\n ].join(\"\\n\");\n })\n .join(\"\\n\\n\")\n : \" (no convergence — skip the section)\";\n\n // ── Divergence ──\n const divergenceBlock = scaffold.divergence\n ? [\n ` Statement: ${scaffold.divergence.statement}`,\n ` Per-director stances:`,\n ...scaffold.divergence.rows.map((r) => {\n const name = nameOf(r.directorId);\n return ` · ${name} | ${r.stance} | confidence: ${r.confidence} | cost-of-being-wrong: ${r.costOfBeingWrong} | note: ${r.note}`;\n }),\n ` Resolution requirements:`,\n ...(scaffold.divergence.resolutionRequirements.length\n ? scaffold.divergence.resolutionRequirements.map((s) => ` · ${s}`)\n : [\" · (none)\"]),\n ].join(\"\\n\")\n : \" (no central tension — skip the Where We Diverged section)\";\n\n // ── Positions ──\n const positionsBlock = scaffold.positions.length\n ? scaffold.positions\n .map((p, i) => {\n const dirNames = p.directors.map(nameOf).join(\", \");\n const evidence = p.evidenceRefs\n .map((ref) => ` · ${renderSignalRef(ref, perDirectorSignals)}`)\n .join(\"\\n\");\n return [\n ` ### Camp ${i + 1}: ${p.label}`,\n ` Claim: ${p.claim}`,\n ` Directors: ${dirNames || \"—\"}`,\n ` Evidence:`,\n evidence || \" (none)\",\n ].join(\"\\n\");\n })\n .join(\"\\n\\n\")\n : \" (no camps — skip the Positions section)\";\n\n // ── Visuals ──\n const visualsBlock = scaffold.visuals.length\n ? scaffold.visuals\n .map((v) => {\n if (v.type === \"comparison-table\") {\n return [\n ` Visual · comparison-table`,\n ` Title: ${v.title}`,\n ` Row label: ${v.rowLabel}`,\n ` Columns: ${v.columns.join(\" | \")}`,\n ` Rows:`,\n ...v.rows.map((r) => ` · ${r.name} | ${r.cells.join(\" | \")}`),\n ].join(\"\\n\");\n }\n if (v.type === \"quadrant-chart\") {\n return [\n ` Visual · quadrant-chart`,\n ` Title: ${v.title}`,\n ` x-axis: ${v.xLabel}`,\n ` y-axis: ${v.yLabel}`,\n ` Quadrant labels: NE=${v.q1} · NW=${v.q2} · SW=${v.q3} · SE=${v.q4}`,\n ` Items:`,\n ...v.items.map((it) => ` · \"${it.label}\" at (x=${it.x.toFixed(2)}, y=${it.y.toFixed(2)})`),\n ].join(\"\\n\");\n }\n if (v.type === \"force-field\") {\n return [\n ` Visual · force-field`,\n ` Title: ${v.title}`,\n ` Drivers ↑:`,\n ...v.drivers.map((d) => ` · ${d}`),\n ` Resistors ↓:`,\n ...v.resistors.map((r) => ` · ${r}`),\n ].join(\"\\n\");\n }\n // strengths-cautions\n return [\n ` Visual · strengths-cautions`,\n ` Title: ${v.title}`,\n ` Rows:`,\n ...v.rows.map((r) =>\n [\n ` · Option: ${r.option}`,\n ` Strengths: ${r.strengths.join(\" · \") || \"(none)\"}`,\n ` Cautions: ${r.cautions.join(\" · \") || \"(none)\"}`,\n ` Verdict: ${r.verdict}`,\n ].join(\"\\n\"),\n ),\n ].join(\"\\n\");\n })\n .join(\"\\n\\n\")\n : \" (no visuals — skip the Options Analysis section)\";\n\n // ── Recommendations ──\n const recsBlock = scaffold.recommendations.length\n ? scaffold.recommendations\n .map((r, i) => {\n return [\n ` Rec ${i + 1} · [${r.priority}] ${r.action}`,\n ` Rationale: ${r.rationale}`,\n ` Owner: ${r.ownerType} · Horizon: ${r.horizon}`,\n ` Success metric: ${r.successMetric}`,\n ...(r.criticalDependency ? [` Critical dependency: ${r.criticalDependency}`] : []),\n ` Risk if skipped: ${r.riskIfSkipped}`,\n ].join(\"\\n\");\n })\n .join(\"\\n\\n\")\n : \" (no recommendations — skip the section)\";\n\n // ── Pre-mortem ──\n const preMortemBlock = scaffold.preMortem.length\n ? scaffold.preMortem\n .map((f, i) =>\n [\n ` Failure ${i + 1}: ${f.scenario}`,\n ` Leading indicator: ${f.leadingIndicator}`,\n ` Mitigation: ${f.mitigation}`,\n ].join(\"\\n\"),\n )\n .join(\"\\n\\n\")\n : \" (no pre-mortem — skip the section)\";\n\n // ── New Questions ──\n const newQuestionsBlock = scaffold.newQuestions.length\n ? scaffold.newQuestions\n .map(\n (q, i) =>\n [\n ` New Q ${i + 1}: ${q.question}`,\n ` Why it matters: ${q.whyItMatters}`,\n ` Surfaced by: ${nameOf(q.surfacedByDirectorId)}`,\n ].join(\"\\n\"),\n )\n .join(\"\\n\\n\")\n : \" (no new questions surfaced — skip the section)\";\n\n // ── Strategic Planning Assumption ──\n const assumptionBlock = scaffold.planningAssumption\n ? [\n ` Statement: ${scaffold.planningAssumption.statement}`,\n ` Probability: ${scaffold.planningAssumption.probability}%`,\n ` Trigger: ${scaffold.planningAssumption.trigger}`,\n ` Falsification test: ${scaffold.planningAssumption.falsificationTest}`,\n ].join(\"\\n\")\n : \" (no planning assumption — skip the section)\";\n\n // ── Open Questions ──\n const openQsBlock = scaffold.openQuestions.length\n ? scaffold.openQuestions.map((q) => ` · [${q.priority}] ${q.text}`).join(\"\\n\")\n : \" (none — skip the Open Questions section)\";\n\n // ── Substitute components (only filled when composer picked them) ──\n const thesisBlock = scaffold.thesis && scaffold.thesis.claim\n ? [\n ` Claim: ${scaffold.thesis.claim}`,\n ` Reasoning: ${scaffold.thesis.reasoning || \"(none)\"}`,\n ].join(\"\\n\")\n : \" (no thesis — composer did not pick the substitute)\";\n\n const bigIdeasBlock = scaffold.bigIdeas && scaffold.bigIdeas.length\n ? scaffold.bigIdeas\n .map((b) => {\n const evidence = b.evidenceRefs.length\n ? b.evidenceRefs\n .map((r) => ` · ${renderSignalRef(r, perDirectorSignals)}`)\n .join(\"\\n\")\n : \" · (no evidence refs)\";\n return [\n ` Idea ${b.number}: ${b.claim}`,\n ` Why: ${b.why}`,\n ` Evidence:`,\n evidence,\n ].join(\"\\n\");\n })\n .join(\"\\n\\n\")\n : \" (no big ideas — composer did not pick the substitute)\";\n\n const theBetBlock = scaffold.theBet && scaffold.theBet.ifBacked\n ? [\n ` IfBacked: ${scaffold.theBet.ifBacked}`,\n ` Conditions:`,\n ...scaffold.theBet.conditions.map(\n (c, i) => ` ${i + 1}. ${c.condition}\\n Why: ${c.why}`,\n ),\n ` Kill criteria: ${scaffold.theBet.killCriteria || \"(none)\"}`,\n ].join(\"\\n\")\n : \" (no bet — composer did not pick the substitute)\";\n\n const workingHypothesisBlock = scaffold.workingHypothesis && scaffold.workingHypothesis.hypothesis\n ? [\n ` Hypothesis: ${scaffold.workingHypothesis.hypothesis}`,\n ` Reasons it may be wrong:`,\n ...scaffold.workingHypothesis.reasonsItMayBeWrong.map((r) => ` · ${r}`),\n ].join(\"\\n\")\n : \" (no working hypothesis — composer did not pick the substitute)\";\n\n const whyNowBlock = scaffold.whyNow\n ? [\n ` Window opened: ${scaffold.whyNow.windowOpened}`,\n ` Window closes: ${scaffold.whyNow.windowCloses || \"(none)\"}`,\n ` What to bet on: ${scaffold.whyNow.whatToBetOn}`,\n ].join(\"\\n\")\n : \" (no why-now — composer did not pick this component)\";\n\n const twoPathsBlock = scaffold.twoPaths\n ? [\n ` Intro: ${scaffold.twoPaths.intro || \"(none)\"}`,\n ` Path A · ${scaffold.twoPaths.pathA.label}`,\n ` ${scaffold.twoPaths.pathA.body}`,\n ` Path B · ${scaffold.twoPaths.pathB.label}`,\n ` ${scaffold.twoPaths.pathB.body}`,\n ].join(\"\\n\")\n : \" (no two-paths — composer did not pick this component)\";\n\n const considerationsBlock = scaffold.considerations && scaffold.considerations.length\n ? scaffold.considerations\n .map((r, i) =>\n [\n ` Consideration ${i + 1} · [${r.priority}] ${r.action}`,\n ` Rationale: ${r.rationale}`,\n ` Owner: ${r.ownerType} · Horizon: ${r.horizon}`,\n ` Success metric: ${r.successMetric}`,\n ...(r.criticalDependency ? [` Critical dependency: ${r.criticalDependency}`] : []),\n ` Risk if skipped: ${r.riskIfSkipped}`,\n ].join(\"\\n\"),\n )\n .join(\"\\n\\n\")\n : \" (no considerations — composer did not pick the substitute)\";\n\n // ── Gartner-density blocks (composer-picked) ──\n const strategicOutlookBlock = scaffold.strategicOutlook\n ? [\n ` Context: ${scaffold.strategicOutlook.context}`,\n ` Implication: ${scaffold.strategicOutlook.implication}`,\n ].join(\"\\n\")\n : \" (no strategic outlook — composer did not pick this component)\";\n\n const criticalAssumptionsBlock = scaffold.criticalAssumptions && scaffold.criticalAssumptions.length\n ? scaffold.criticalAssumptions\n .map((a, i) =>\n [\n ` Assumption ${i + 1}: ${a.statement}`,\n ` Confidence: ${a.confidence} · Horizon: ${a.horizon}`,\n ` Attribution: ${a.attribution}`,\n ` Falsifier: ${a.falsifier}`,\n ].join(\"\\n\"),\n )\n .join(\"\\n\\n\")\n : \" (no critical assumptions — composer did not pick this component)\";\n\n const scenarioTreeBlock = scaffold.scenarioTree && scaffold.scenarioTree.branches.length\n ? [\n ` Intro: ${scaffold.scenarioTree.intro}`,\n ...scaffold.scenarioTree.branches.map((b, i) => [\n ` Branch ${i + 1}: ${b.label} · ${b.probability}%`,\n ` Trigger: ${b.trigger}`,\n ` Effects:`,\n ...(b.effects.length ? b.effects.map((e) => ` · ${e}`) : [\" · (none)\"]),\n ` Decision implication: ${b.decisionImplication}`,\n ].join(\"\\n\")),\n ].join(\"\\n\\n\")\n : \" (no scenario tree — composer did not pick this component)\";\n\n const leadingIndicatorsBlock = scaffold.leadingIndicators && scaffold.leadingIndicators.length\n ? scaffold.leadingIndicators\n .map((it, i) =>\n [\n ` Indicator ${i + 1}: ${it.signal}`,\n ` Threshold: ${it.threshold}`,\n ` Cadence: ${it.cadence}`,\n ` Flips to: ${it.flipsTo}`,\n ].join(\"\\n\"),\n )\n .join(\"\\n\\n\")\n : \" (no leading indicators — composer did not pick this component)\";\n\n const pickedNote = picked && picked.length\n ? [\n ``,\n `─── COMPOSER PICKED COMPONENTS ───`,\n ``,\n `Render ONLY these sections (in this order). Skip any section whose kind is not in this list, even if its scaffold field looks fillable.`,\n ...picked.map((k, i) => ` ${i + 1}. ${k}`),\n ``,\n `─── END PICKED ───`,\n ].join(\"\\n\")\n : \"\";\n\n return [\n {\n role: \"system\",\n content: [WRITE_SYSTEM, \"\", languageInstruction(language)].join(\"\\n\"),\n },\n {\n role: \"user\",\n content: [\n `ROOM #${room.number} · ${room.name}`,\n `Subject: ${room.subject}`,\n ``,\n `Directors at the table (id · display name):`,\n ` · ${memberList}`,\n ``,\n `─── SCAFFOLD ───`,\n ``,\n `Title: ${scaffold.title}`,\n ``,\n `## Bottom Line`,\n bottomLineBlock,\n ``,\n `## Frame Shift`,\n frameShiftBlock,\n ``,\n `## Headline Findings`,\n headlineFindingsBlock,\n ``,\n `## Convergence`,\n convergenceBlock,\n ``,\n `## Divergence`,\n divergenceBlock,\n ``,\n `## Positions`,\n positionsBlock,\n ``,\n `## Visuals`,\n visualsBlock,\n ``,\n `## Recommendations`,\n recsBlock,\n ``,\n `## Pre-mortem`,\n preMortemBlock,\n ``,\n `## New Questions`,\n newQuestionsBlock,\n ``,\n `## Planning Assumption`,\n assumptionBlock,\n ``,\n `## Open Questions`,\n openQsBlock,\n ``,\n `## Thesis (anchor substitute)`,\n thesisBlock,\n ``,\n `## Working Hypothesis (anchor substitute)`,\n workingHypothesisBlock,\n ``,\n `## Big Ideas (findings substitute)`,\n bigIdeasBlock,\n ``,\n `## The Bet (action substitute)`,\n theBetBlock,\n ``,\n `## Considerations (action substitute)`,\n considerationsBlock,\n ``,\n `## Why Now (forward / opportunity panel)`,\n whyNowBlock,\n ``,\n `## Two Paths (comparison panel)`,\n twoPathsBlock,\n ``,\n `## Strategic Outlook (Gartner-density)`,\n strategicOutlookBlock,\n ``,\n `## Critical Assumptions (Gartner-density)`,\n criticalAssumptionsBlock,\n ``,\n `## Scenario Tree (Gartner-density)`,\n scenarioTreeBlock,\n ``,\n `## Leading Indicators (Gartner-density)`,\n leadingIndicatorsBlock,\n ``,\n `─── END SCAFFOLD ───`,\n pickedNote,\n ``,\n ...(opts.supplement && opts.supplement.trim()\n ? [\n `─── SUPPLEMENTARY PERSPECTIVE FROM USER ───`,\n ``,\n `The user asked for this additional angle to be explicitly addressed in the report. Weave it through — don't add a separate section for it. Make sure the relevant existing sections (Findings, Recommendations, New Questions, etc.) reflect it.`,\n ``,\n opts.supplement.trim(),\n ``,\n `─── END SUPPLEMENT ───`,\n ``,\n ]\n : []),\n `Write the final report now. Markdown only. Start with the H2 title — no preamble. Replace director ids with display names from the directors list above. Follow the section order: Bottom Line / Thesis / Working Hypothesis (anchor) → Strategic Outlook (when picked) → Frame Shift → Headline Findings (or Big Ideas) → Where We Converged → Where We Diverged → Positions → Options Analysis / Two Paths → Critical Assumptions (when picked) → Scenario Tree (when picked) → Why Now (when picked) → Recommendations / The Bet / Considerations (action) → Leading Indicators (when picked) → Pre-mortem → New Questions This Surfaced → Strategic Planning Assumption → Open Questions.`,\n ].join(\"\\n\"),\n },\n ];\n}\n\n/* ─────────────────────── JSON parsing helpers ────────────────────────── */\n\n/**\n * Extract the first JSON object from a model response. Tolerates the\n * model emitting prose around a fenced ```json code block, or just\n * emitting bare JSON. Returns null on failure.\n */\nexport function extractJson<T = unknown>(raw: string): T | null {\n // Prefer fenced block.\n const fence = /```(?:json)?\\s*([\\s\\S]*?)```/i.exec(raw);\n const candidate = fence ? fence[1] : raw;\n if (!candidate) return null;\n\n // Find the first balanced { ... } in the candidate.\n const start = candidate.indexOf(\"{\");\n if (start === -1) return null;\n let depth = 0;\n let end = -1;\n for (let i = start; i < candidate.length; i++) {\n const ch = candidate[i];\n if (ch === \"{\") depth++;\n else if (ch === \"}\") {\n depth--;\n if (depth === 0) {\n end = i;\n break;\n }\n }\n }\n if (end === -1) return null;\n try {\n return JSON.parse(candidate.slice(start, end + 1)) as T;\n } catch {\n return null;\n }\n}\n\n/** Validate + coerce a single director's stage-1 output. */\nexport function parseDirectorSignals(\n raw: string,\n director: Agent,\n): DirectorSignals {\n const parsed = extractJson<{ signals?: unknown }>(raw);\n const out: DirectorSignals = {\n directorId: director.id,\n directorName: director.name,\n signals: [],\n };\n if (!parsed || !Array.isArray(parsed.signals)) return out;\n for (const s of parsed.signals) {\n if (!s || typeof s !== \"object\") continue;\n const obj = s as Record<string, unknown>;\n const text = typeof obj.text === \"string\" ? obj.text.trim() : \"\";\n const lens = typeof obj.lens === \"string\" ? obj.lens.trim() : \"\";\n if (!text) continue;\n if (!(EVIDENCE_LENSES as readonly string[]).includes(lens)) continue;\n const sources = Array.isArray(obj.sources)\n ? obj.sources.filter((n): n is number => typeof n === \"number\" && Number.isFinite(n) && n >= 0)\n : [];\n out.signals.push({ text, lens: lens as EvidenceLens, sources });\n if (out.signals.length >= 4) break;\n }\n return out;\n}\n\nfunction parseConfidence(raw: unknown): Confidence {\n if (raw === \"high\" || raw === \"medium\" || raw === \"low\") return raw;\n return \"medium\";\n}\n\nfunction parsePriority(raw: unknown): Priority {\n if (raw === \"P0\" || raw === \"P1\" || raw === \"P2\") return raw;\n return \"P1\";\n}\n\nfunction parseLens(raw: unknown): EvidenceLens | null {\n if (typeof raw !== \"string\") return null;\n return (EVIDENCE_LENSES as readonly string[]).includes(raw) ? (raw as EvidenceLens) : null;\n}\n\nfunction parseStringArray(raw: unknown): string[] {\n if (!Array.isArray(raw)) return [];\n return raw.filter((x): x is string => typeof x === \"string\");\n}\n\nfunction parseEvidenceRefs(raw: unknown): string[] {\n return parseStringArray(raw);\n}\n\nfunction parseLensArray(raw: unknown): EvidenceLens[] {\n if (!Array.isArray(raw)) return [];\n const out: EvidenceLens[] = [];\n for (const x of raw) {\n const lens = parseLens(x);\n if (lens && !out.includes(lens)) out.push(lens);\n }\n return out;\n}\n\nfunction parseBottomLine(raw: unknown, fallbackJudgement: string): BottomLine {\n const obj = raw && typeof raw === \"object\" ? (raw as Record<string, unknown>) : {};\n return {\n judgement: typeof obj.judgement === \"string\" && obj.judgement.trim()\n ? obj.judgement.trim()\n : fallbackJudgement,\n confidence: parseConfidence(obj.confidence),\n rationale: typeof obj.rationale === \"string\" ? obj.rationale.trim() : \"\",\n };\n}\n\nfunction parseFrameShift(raw: unknown, fallbackOriginal: string): FrameShift {\n const obj = raw && typeof raw === \"object\" ? (raw as Record<string, unknown>) : {};\n const shifted = obj.shifted === true;\n return {\n shifted,\n original: typeof obj.original === \"string\" && obj.original.trim()\n ? obj.original.trim()\n : fallbackOriginal,\n reframed: typeof obj.reframed === \"string\" ? obj.reframed.trim() : \"\",\n trigger: typeof obj.trigger === \"string\" ? obj.trigger.trim() : \"\",\n };\n}\n\nfunction parseSubFinding(raw: unknown): SubFinding | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const text = typeof o.text === \"string\" ? o.text.trim() : \"\";\n if (!text) return null;\n return { text, evidenceRefs: parseEvidenceRefs(o.evidenceRefs) };\n}\n\nfunction parseHeadlineFinding(raw: unknown): HeadlineFinding | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const title = typeof o.title === \"string\" ? o.title.trim() : \"\";\n const claim = typeof o.claim === \"string\" ? o.claim.trim() : \"\";\n if (!title || !claim) return null;\n const supportingRaw = Array.isArray(o.supporting) ? o.supporting : [];\n const supporting: SubFinding[] = [];\n for (const s of supportingRaw) {\n const sub = parseSubFinding(s);\n if (sub) supporting.push(sub);\n if (supporting.length >= 4) break;\n }\n return {\n title,\n claim,\n confidence: parseConfidence(o.confidence),\n supporters: parseStringArray(o.supporters),\n challengers: parseStringArray(o.challengers),\n supporting,\n lensesPresent: parseLensArray(o.lensesPresent),\n tension: typeof o.tension === \"string\" && o.tension.trim() ? o.tension.trim() : undefined,\n };\n}\n\nfunction parseConvergencePath(raw: unknown): ConvergencePath | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const directorId = typeof o.directorId === \"string\" ? o.directorId : \"\";\n const lens = parseLens(o.lens);\n const reasoning = typeof o.reasoning === \"string\" ? o.reasoning.trim() : \"\";\n if (!directorId || !lens || !reasoning) return null;\n return { directorId, lens, reasoning };\n}\n\nfunction parseConvergence(raw: unknown): ConvergencePoint[] {\n if (!Array.isArray(raw)) return [];\n const out: ConvergencePoint[] = [];\n for (const c of raw) {\n if (!c || typeof c !== \"object\") continue;\n const o = c as Record<string, unknown>;\n const point = typeof o.point === \"string\" ? o.point.trim() : \"\";\n if (!point) continue;\n const pathsRaw = Array.isArray(o.paths) ? o.paths : [];\n const paths: ConvergencePath[] = [];\n for (const p of pathsRaw) {\n const parsed = parseConvergencePath(p);\n if (parsed) paths.push(parsed);\n }\n // Convergence requires ≥ 2 distinct directors via ≥ 2 distinct lenses.\n const distinctDirs = new Set(paths.map((p) => p.directorId));\n const distinctLenses = new Set(paths.map((p) => p.lens));\n if (distinctDirs.size < 2 || distinctLenses.size < 2) continue;\n out.push({ point, paths });\n if (out.length >= 4) break;\n }\n return out;\n}\n\nfunction parseDivergenceStance(raw: unknown): DivergenceStance | null {\n if (raw === \"for\" || raw === \"against\" || raw === \"nuanced\") return raw;\n return null;\n}\n\nfunction parseDivergence(raw: unknown): Divergence | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const statement = typeof o.statement === \"string\" ? o.statement.trim() : \"\";\n if (!statement) return null;\n const rowsRaw = Array.isArray(o.rows) ? o.rows : [];\n const rows: DivergenceRow[] = [];\n for (const r of rowsRaw) {\n if (!r || typeof r !== \"object\") continue;\n const ro = r as Record<string, unknown>;\n const directorId = typeof ro.directorId === \"string\" ? ro.directorId : \"\";\n const stance = parseDivergenceStance(ro.stance);\n const note = typeof ro.note === \"string\" ? ro.note.trim() : \"\";\n const cost = typeof ro.costOfBeingWrong === \"string\" ? ro.costOfBeingWrong.trim() : \"\";\n if (!directorId || !stance) continue;\n rows.push({\n directorId,\n stance,\n confidence: parseConfidence(ro.confidence),\n costOfBeingWrong: cost.slice(0, 120),\n note: note.slice(0, 120),\n });\n }\n const resReqs = parseStringArray(o.resolutionRequirements)\n .map((s) => s.trim())\n .filter(Boolean)\n .slice(0, 4);\n return { statement, rows, resolutionRequirements: resReqs };\n}\n\nfunction parsePositions(raw: unknown): PositionCamp[] {\n if (!Array.isArray(raw)) return [];\n const out: PositionCamp[] = [];\n for (const p of raw) {\n if (!p || typeof p !== \"object\") continue;\n const o = p as Record<string, unknown>;\n const label = typeof o.label === \"string\" ? o.label.trim() : \"\";\n const claim = typeof o.claim === \"string\" ? o.claim.trim() : \"\";\n if (!label || !claim) continue;\n out.push({\n label,\n claim,\n directors: parseStringArray(o.directors),\n evidenceRefs: parseEvidenceRefs(o.evidenceRefs),\n });\n if (out.length >= 4) break;\n }\n return out;\n}\n\nfunction parseVisual(raw: unknown): Visual | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const type = o.type;\n const title = typeof o.title === \"string\" ? o.title.trim() : \"\";\n if (type === \"comparison-table\") {\n const rowLabel = typeof o.rowLabel === \"string\" ? o.rowLabel.trim() : \"Option\";\n const columns = parseStringArray(o.columns);\n const rowsRaw = Array.isArray(o.rows) ? o.rows : [];\n const rows: { name: string; cells: string[] }[] = [];\n for (const r of rowsRaw) {\n if (!r || typeof r !== \"object\") continue;\n const ro = r as Record<string, unknown>;\n const name = typeof ro.name === \"string\" ? ro.name.trim() : \"\";\n const cells = Array.isArray(ro.cells)\n ? (ro.cells.map((c) => (typeof c === \"string\" ? c : \"\")).slice(0, columns.length))\n : [];\n if (!name) continue;\n while (cells.length < columns.length) cells.push(\"\");\n rows.push({ name, cells });\n }\n if (!columns.length || !rows.length) return null;\n return { type: \"comparison-table\", title, rowLabel, columns, rows };\n }\n if (type === \"quadrant-chart\") {\n const xLabel = typeof o.xLabel === \"string\" ? o.xLabel.trim() : \"x\";\n const yLabel = typeof o.yLabel === \"string\" ? o.yLabel.trim() : \"y\";\n const q1 = typeof o.q1 === \"string\" ? o.q1.trim() : \"\";\n const q2 = typeof o.q2 === \"string\" ? o.q2.trim() : \"\";\n const q3 = typeof o.q3 === \"string\" ? o.q3.trim() : \"\";\n const q4 = typeof o.q4 === \"string\" ? o.q4.trim() : \"\";\n const itemsRaw = Array.isArray(o.items) ? o.items : [];\n const items: { label: string; x: number; y: number }[] = [];\n for (const it of itemsRaw) {\n if (!it || typeof it !== \"object\") continue;\n const io = it as Record<string, unknown>;\n const label = typeof io.label === \"string\" ? io.label.trim() : \"\";\n const x = typeof io.x === \"number\" && Number.isFinite(io.x) ? Math.max(0, Math.min(1, io.x)) : null;\n const y = typeof io.y === \"number\" && Number.isFinite(io.y) ? Math.max(0, Math.min(1, io.y)) : null;\n if (!label || x === null || y === null) continue;\n items.push({ label, x, y });\n }\n if (!items.length) return null;\n return { type: \"quadrant-chart\", title, xLabel, yLabel, q1, q2, q3, q4, items };\n }\n if (type === \"force-field\") {\n const drivers = parseStringArray(o.drivers).map((s) => s.trim()).filter(Boolean);\n const resistors = parseStringArray(o.resistors).map((s) => s.trim()).filter(Boolean);\n if (!drivers.length && !resistors.length) return null;\n return { type: \"force-field\", title, drivers, resistors };\n }\n if (type === \"strengths-cautions\") {\n const rowsRaw = Array.isArray(o.rows) ? o.rows : [];\n const rows: StrengthsCautionsVisual[\"rows\"] = [];\n for (const r of rowsRaw) {\n if (!r || typeof r !== \"object\") continue;\n const ro = r as Record<string, unknown>;\n const option = typeof ro.option === \"string\" ? ro.option.trim() : \"\";\n if (!option) continue;\n const verdictRaw = ro.verdict;\n const verdict: StrengthsCautionsVisual[\"rows\"][number][\"verdict\"] =\n verdictRaw === \"recommended\" || verdictRaw === \"caution\" || verdictRaw === \"not-recommended\"\n ? verdictRaw\n : \"caution\";\n rows.push({\n option,\n strengths: parseStringArray(ro.strengths).map((s) => s.trim()).filter(Boolean),\n cautions: parseStringArray(ro.cautions).map((s) => s.trim()).filter(Boolean),\n verdict,\n });\n }\n if (!rows.length) return null;\n return { type: \"strengths-cautions\", title, rows };\n }\n return null;\n}\n\nfunction parseVisuals(raw: unknown): Visual[] {\n if (!Array.isArray(raw)) return [];\n const out: Visual[] = [];\n for (const v of raw) {\n const parsed = parseVisual(v);\n if (parsed) out.push(parsed);\n if (out.length >= 4) break;\n }\n return out;\n}\n\nfunction parseRecommendations(raw: unknown): Recommendation[] {\n if (!Array.isArray(raw)) return [];\n const out: Recommendation[] = [];\n for (const r of raw) {\n if (!r || typeof r !== \"object\") continue;\n const o = r as Record<string, unknown>;\n const action = typeof o.action === \"string\" ? o.action.trim() : \"\";\n if (!action) continue;\n out.push({\n priority: parsePriority(o.priority),\n action,\n rationale: typeof o.rationale === \"string\" ? o.rationale.trim() : \"\",\n ownerType: typeof o.ownerType === \"string\" ? o.ownerType.trim() : \"\",\n horizon: typeof o.horizon === \"string\" ? o.horizon.trim() : \"\",\n successMetric: typeof o.successMetric === \"string\" ? o.successMetric.trim() : \"\",\n riskIfSkipped: typeof o.riskIfSkipped === \"string\" ? o.riskIfSkipped.trim() : \"\",\n });\n if (out.length >= 6) break;\n }\n // Sort by priority: P0 first, then P1, then P2.\n out.sort((a, b) => {\n const order = { P0: 0, P1: 1, P2: 2 };\n return order[a.priority] - order[b.priority];\n });\n return out;\n}\n\nfunction parsePreMortem(raw: unknown): FailureMode[] {\n if (!Array.isArray(raw)) return [];\n const out: FailureMode[] = [];\n for (const f of raw) {\n if (!f || typeof f !== \"object\") continue;\n const o = f as Record<string, unknown>;\n const scenario = typeof o.scenario === \"string\" ? o.scenario.trim() : \"\";\n if (!scenario) continue;\n out.push({\n scenario,\n leadingIndicator: typeof o.leadingIndicator === \"string\" ? o.leadingIndicator.trim() : \"\",\n mitigation: typeof o.mitigation === \"string\" ? o.mitigation.trim() : \"\",\n });\n if (out.length >= 4) break;\n }\n return out;\n}\n\nfunction parseNewQuestions(raw: unknown): NewQuestion[] {\n if (!Array.isArray(raw)) return [];\n const out: NewQuestion[] = [];\n for (const q of raw) {\n if (!q || typeof q !== \"object\") continue;\n const o = q as Record<string, unknown>;\n const question = typeof o.question === \"string\" ? o.question.trim() : \"\";\n if (!question) continue;\n out.push({\n question,\n whyItMatters: typeof o.whyItMatters === \"string\" ? o.whyItMatters.trim() : \"\",\n surfacedByDirectorId: typeof o.surfacedByDirectorId === \"string\" ? o.surfacedByDirectorId : \"\",\n });\n if (out.length >= 5) break;\n }\n return out;\n}\n\nfunction parsePlanningAssumption(raw: unknown): PlanningAssumption | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const statement = typeof o.statement === \"string\" ? o.statement.trim() : \"\";\n if (!statement) return null;\n let probability = 50;\n if (typeof o.probability === \"number\" && Number.isFinite(o.probability)) {\n probability = Math.max(0, Math.min(100, Math.round(o.probability)));\n }\n return {\n statement,\n probability,\n trigger: typeof o.trigger === \"string\" ? o.trigger.trim() : \"\",\n falsificationTest: typeof o.falsificationTest === \"string\" ? o.falsificationTest.trim() : \"\",\n };\n}\n\nfunction parseOpenQuestions(raw: unknown): OpenQuestion[] {\n if (!Array.isArray(raw)) return [];\n const out: OpenQuestion[] = [];\n for (const q of raw) {\n if (!q || typeof q !== \"object\") continue;\n const o = q as Record<string, unknown>;\n const text = typeof o.text === \"string\" ? o.text.trim() : \"\";\n if (!text) continue;\n const priority: OpenQuestionPriority = o.priority === \"P0\" ? \"P0\" : \"P1\";\n out.push({ text, priority });\n if (out.length >= 4) break;\n }\n return out;\n}\n\n/* ─────────── Substitute-component parsers (composer-driven) ─────────────── */\n\nfunction parseThesis(raw: unknown): Thesis | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const claim = typeof o.claim === \"string\" ? o.claim.trim() : \"\";\n if (!claim) return null;\n const reasoning = typeof o.reasoning === \"string\" ? o.reasoning.trim() : \"\";\n return { claim, reasoning };\n}\n\nfunction parseBigIdeas(raw: unknown): BigIdea[] | null {\n if (!Array.isArray(raw)) return null;\n const out: BigIdea[] = [];\n for (const item of raw) {\n if (!item || typeof item !== \"object\") continue;\n const o = item as Record<string, unknown>;\n const claim = typeof o.claim === \"string\" ? o.claim.trim() : \"\";\n if (!claim) continue;\n const why = typeof o.why === \"string\" ? o.why.trim() : \"\";\n const numRaw = typeof o.number === \"number\" ? o.number : out.length + 1;\n const number = (numRaw === 1 || numRaw === 2 || numRaw === 3 ? numRaw : (out.length + 1)) as 1 | 2 | 3;\n out.push({\n number,\n claim,\n why,\n evidenceRefs: parseEvidenceRefs(o.evidenceRefs),\n });\n if (out.length >= 3) break;\n }\n if (out.length < 3) return null;\n // Renumber to ensure contiguous 1/2/3.\n out.forEach((idea, i) => {\n idea.number = (i + 1) as 1 | 2 | 3;\n });\n return out;\n}\n\nfunction parseTheBet(raw: unknown): TheBet | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const ifBacked = typeof o.ifBacked === \"string\" ? o.ifBacked.trim() : \"\";\n if (!ifBacked) return null;\n const conditionsRaw = Array.isArray(o.conditions) ? o.conditions : [];\n const conditions: TheBetCondition[] = [];\n for (const c of conditionsRaw) {\n if (!c || typeof c !== \"object\") continue;\n const co = c as Record<string, unknown>;\n const condition = typeof co.condition === \"string\" ? co.condition.trim() : \"\";\n if (!condition) continue;\n const why = typeof co.why === \"string\" ? co.why.trim() : \"\";\n conditions.push({ condition, why });\n if (conditions.length >= 5) break;\n }\n if (conditions.length < 1) return null;\n const killCriteria = typeof o.killCriteria === \"string\" ? o.killCriteria.trim() : \"\";\n return { ifBacked, conditions, killCriteria };\n}\n\nfunction parseWorkingHypothesis(raw: unknown): WorkingHypothesis | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const hypothesis = typeof o.hypothesis === \"string\" ? o.hypothesis.trim() : \"\";\n if (!hypothesis) return null;\n const reasonsRaw = Array.isArray(o.reasonsItMayBeWrong) ? o.reasonsItMayBeWrong : [];\n const reasons: string[] = [];\n for (const r of reasonsRaw) {\n if (typeof r !== \"string\") continue;\n const t = r.trim();\n if (!t) continue;\n reasons.push(t);\n if (reasons.length >= 3) break;\n }\n if (reasons.length < 1) return null;\n return { hypothesis, reasonsItMayBeWrong: reasons };\n}\n\nfunction parseWhyNow(raw: unknown): WhyNow | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const windowOpened = typeof o.windowOpened === \"string\" ? o.windowOpened.trim() : \"\";\n const windowCloses = typeof o.windowCloses === \"string\" ? o.windowCloses.trim() : \"\";\n const whatToBetOn = typeof o.whatToBetOn === \"string\" ? o.whatToBetOn.trim() : \"\";\n // Need at least the open + bet to be a meaningful section.\n if (!windowOpened || !whatToBetOn) return null;\n return { windowOpened, windowCloses, whatToBetOn };\n}\n\nfunction parseTwoPaths(raw: unknown): TwoPaths | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const intro = typeof o.intro === \"string\" ? o.intro.trim() : \"\";\n const pathA = parseTwoPathPanel(o.pathA);\n const pathB = parseTwoPathPanel(o.pathB);\n if (!pathA || !pathB) return null;\n return { intro, pathA, pathB };\n}\n\nfunction parseTwoPathPanel(raw: unknown): TwoPathPanel | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const label = typeof o.label === \"string\" ? o.label.trim() : \"\";\n const body = typeof o.body === \"string\" ? o.body.trim() : \"\";\n if (!label || !body) return null;\n return { label, body };\n}\n\n/** Validate + coerce the chair's stage-2 scaffold.\n *\n * Validity floor: at minimum the scaffold must carry a load-bearing\n * anchor (bottomLine OR thesis) AND a load-bearing findings block\n * (≥ 1 headlineFinding OR a complete bigIdeas array). Other sections\n * fall back to empty / null / default.\n *\n * Substitute fields (`thesis`, `bigIdeas`, `theBet`) are net-additive:\n * the parser returns them when filled and `null` otherwise. The\n * renderer's \"skip if empty\" rules do the rest.\n */\nexport function parseScaffold(\n raw: string,\n fallbackTitle: string,\n fallbackOriginalQuestion: string,\n): BriefScaffold | null {\n const parsed = extractJson<Record<string, unknown>>(raw);\n if (!parsed) return null;\n\n const title = typeof parsed.title === \"string\" && parsed.title.trim()\n ? parsed.title.trim()\n : fallbackTitle;\n\n // Anchor · at minimum one of bottomLine / thesis / workingHypothesis.\n const bottomLine = parseBottomLine(parsed.bottomLine, title);\n const thesis = parseThesis(parsed.thesis);\n const workingHypothesis = parseWorkingHypothesis(parsed.workingHypothesis);\n const hasAnchor =\n (bottomLine.judgement && bottomLine.judgement.trim().length > 0) ||\n (thesis && thesis.claim.length > 0) ||\n (workingHypothesis && workingHypothesis.hypothesis.length > 0);\n if (!hasAnchor) return null;\n\n // Findings · either headlineFindings (≥1) or bigIdeas (=3).\n const findingsRaw = Array.isArray(parsed.headlineFindings) ? parsed.headlineFindings : [];\n const headlineFindings: HeadlineFinding[] = [];\n for (const f of findingsRaw) {\n const parsedF = parseHeadlineFinding(f);\n if (parsedF) headlineFindings.push(parsedF);\n if (headlineFindings.length >= 3) break;\n }\n const bigIdeas = parseBigIdeas(parsed.bigIdeas);\n if (headlineFindings.length < 1 && !bigIdeas) return null;\n\n // Action substitutes are all best-effort — having none is allowed (the\n // composer may have skipped action entirely for an essay-style brief).\n const theBet = parseTheBet(parsed.theBet);\n const considerations = parseRecommendations(parsed.considerations);\n const considerationsField: Recommendation[] | null = considerations.length ? considerations : null;\n const whyNow = parseWhyNow(parsed.whyNow);\n const twoPaths = parseTwoPaths(parsed.twoPaths);\n\n return {\n title,\n bottomLine,\n thesis,\n workingHypothesis,\n frameShift: parseFrameShift(parsed.frameShift, fallbackOriginalQuestion),\n headlineFindings,\n bigIdeas,\n convergence: parseConvergence(parsed.convergence),\n divergence: parseDivergence(parsed.divergence),\n positions: parsePositions(parsed.positions),\n visuals: parseVisuals(parsed.visuals),\n twoPaths,\n whyNow,\n recommendations: parseRecommendations(parsed.recommendations),\n theBet,\n considerations: considerationsField,\n preMortem: parsePreMortem(parsed.preMortem),\n newQuestions: parseNewQuestions(parsed.newQuestions),\n planningAssumption: parsePlanningAssumption(parsed.planningAssumption),\n openQuestions: parseOpenQuestions(parsed.openQuestions),\n };\n}\n","/**\n * Stage 1.5 · Report composer.\n *\n * Given the room's subject + the per-director signals from Stage 1,\n * the composer picks (a) a style spine and (b) a subset of components\n * that fit this specific conversation. Stage 2 / Stage 3 are then\n * parameterized to only fill / render the picked components.\n *\n * v1 ships with one spine (`boardroom-dark`) and 15 component kinds.\n * Future releases add the other 5 spines (a16z-thesis, anthropic-essay,\n * gartner-note, mckinsey-deck, openai-paper).\n */\nimport type { LLMMessage } from \"../adapter.js\";\nimport type { Agent } from \"../../storage/agents.js\";\nimport type { Room } from \"../../storage/rooms.js\";\n\nimport type { DirectorSignals, ReportLanguage } from \"./brief-stages.js\";\nimport { extractJson } from \"./brief-stages.js\";\n\n/* ─────────────────────────── Catalogue ─────────────────────────────────── */\n\n/** Every component the composer is allowed to pick. Append-only — kinds\n * are never removed (legacy briefs always remain renderable). */\nexport const COMPONENT_KINDS = [\n // Anchor · pick exactly 1\n \"bottom-line\",\n \"thesis\",\n \"working-hypothesis\",\n\n // Findings · pick exactly 1\n \"headline-findings\",\n \"big-ideas\",\n\n // Action · pick exactly 1\n \"recommendations\",\n \"the-bet\",\n \"considerations\",\n\n // Optional · independent on/off\n \"frame-shift\",\n \"convergence\",\n \"divergence\",\n \"positions\",\n \"visuals\",\n \"two-paths\",\n \"why-now\",\n \"pre-mortem\",\n \"new-questions\",\n \"planning-assumption\",\n \"open-questions\",\n\n // Gartner-density blocks · for strategic-decision / market-forecast\n // briefs. The composer pulls these in when the room's value lives in\n // the analysis of UNCERTAINTY (assumptions, scenarios, indicators)\n // rather than in conclusions alone.\n \"strategic-outlook\",\n \"critical-assumptions\",\n \"scenario-tree\",\n \"leading-indicators\",\n] as const;\n\nexport type ComponentKind = (typeof COMPONENT_KINDS)[number];\n\n/** Substitute groups · the composer picks exactly one kind from each. */\nexport const ANCHORS = [\"bottom-line\", \"thesis\", \"working-hypothesis\"] as const satisfies readonly ComponentKind[];\nexport const FINDINGS = [\"headline-findings\", \"big-ideas\"] as const satisfies readonly ComponentKind[];\nexport const ACTIONS = [\"recommendations\", \"the-bet\", \"considerations\"] as const satisfies readonly ComponentKind[];\n\n/** Spines · v1 ships with `boardroom-dark` only. The catalogue is\n * surfaced to the composer's prompt; the orchestrator coerces any\n * non-`boardroom-dark` pick down to `boardroom-dark` until the other\n * renderers ship. */\nexport const SPINES = [\n \"boardroom-dark\",\n \"a16z-thesis\",\n \"anthropic-essay\",\n \"gartner-note\",\n \"mckinsey-deck\",\n \"openai-paper\",\n] as const;\n\nexport type Spine = (typeof SPINES)[number];\n\n/** The default 12-section preset · used as a safety net when the\n * composer fails or the room has no signals. Matches the static\n * layout the codebase shipped before Stage 1.5 existed. */\nexport const DEFAULT_PRESET: ComponentPick[] = [\n { kind: \"bottom-line\", order: 1 },\n { kind: \"frame-shift\", order: 2 },\n { kind: \"headline-findings\", order: 3 },\n { kind: \"convergence\", order: 4 },\n { kind: \"divergence\", order: 5 },\n { kind: \"positions\", order: 6 },\n { kind: \"visuals\", order: 7 },\n { kind: \"recommendations\", order: 8 },\n { kind: \"pre-mortem\", order: 9 },\n { kind: \"new-questions\", order: 10 },\n { kind: \"planning-assumption\", order: 11 },\n { kind: \"open-questions\", order: 12 },\n];\n\nexport interface ComponentPick {\n kind: ComponentKind;\n order: number;\n}\n\nexport interface ComposerResult {\n spine: Spine;\n components: ComponentPick[];\n rationale: string;\n subjectType: string | null;\n /** True when the composer's own output validated cleanly. False when\n * the orchestrator fell back to DEFAULT_PRESET. The brief row records\n * this distinction via the presence of `composer_rationale`. */\n fromComposer: boolean;\n}\n\n/* ─────────────────────────── Prompt ────────────────────────────────────── */\n\nconst SYSTEM_PROMPT = [\n \"You are the Boardroom report composer. Pick the spine and the components that will produce the most useful brief for THIS specific room.\",\n \"\",\n \"## What you must output\",\n \"\",\n \"Strict JSON inside a fenced ```json code block. No prose outside the block.\",\n \"\",\n \"```json\",\n \"{\",\n ' \"spine\": \"boardroom-dark\",',\n ' \"subject_type\": \"investment-judgement\",',\n ' \"components\": [',\n ' { \"kind\": \"bottom-line\", \"order\": 1 },',\n ' { \"kind\": \"frame-shift\", \"order\": 2 }',\n \" ],\",\n ' \"rationale\": \"≤ 120 chars · why this spine + these components fit the room\"',\n \"}\",\n \"```\",\n \"\",\n \"## Component catalogue (19 kinds)\",\n \"\",\n \"Anchor (pick EXACTLY one):\",\n \" · `bottom-line` Sentence judgement + confidence + rationale. Default for recap / strategic rooms.\",\n \" · `thesis` Single load-bearing claim, ~16 words, pull-statement style. For investment / opportunity rooms.\",\n \" · `working-hypothesis` Essay-style opener: \\\"a working hypothesis, and the reasons it may be wrong.\\\" For philosophical / open-ended rooms (anthropic-essay spine).\",\n \"\",\n \"Findings (pick EXACTLY one):\",\n \" · `headline-findings` Three pillar claims, MECE, with supporters / challengers / sub-findings.\",\n \" · `big-ideas` Three numbered claims, each with a why. Lighter, punchier — for venture / market rooms.\",\n \"\",\n \"Action (pick EXACTLY one):\",\n \" · `recommendations` 3–5 P0/P1/P2 actions with owner / horizon / success metric / risk-if-skipped.\",\n \" · `the-bet` Conditions to back the call (3–5 conditions), plus kill criteria. For investment / commitment rooms.\",\n \" · `considerations` Same shape as recommendations but in a softer, hedged voice (\\\"things you might consider\\\"). For philosophical / Anthropic-essay rooms where imperatives feel wrong.\",\n \"\",\n \"Optional · independent on/off (pick the ones the conversation actually produced material for):\",\n \" · `frame-shift` How the question itself moved (or held). Skip if the frame neither shifted nor sharpened.\",\n \" · `convergence` Where directors aligned via independent reasoning paths. Needs ≥ 2 directors via ≥ 2 lenses.\",\n \" · `divergence` The single hinge where directors split. Skip when the room had no real central tension.\",\n \" · `positions` 2–3 named camps with a pull-quote per camp. Skip when directors didn't cluster.\",\n \" · `visuals` 0–4 exhibits (comparison-table / quadrant-chart / force-field / strengths-cautions). Pick when ≥ 2 named options or paths were compared.\",\n \" · `two-paths` Side-by-side trajectory comparison (Path A vs Path B). Pick when the room argued two distinct futures or routes — punchier than a comparison-table.\",\n \" · `why-now` Single panel: window opened by what · window closes when · the bet implied. For investment / opportunity rooms (a16z-thesis spine).\",\n \" · `pre-mortem` 2–3 failure modes with leading indicators + mitigations. Pair with `recommendations` or `the-bet` when the call is high-stakes.\",\n \" · `new-questions` Questions that did NOT exist when the room opened but emerged. The most generative output of a multi-director session.\",\n \" · `planning-assumption` Forward-looking probabilistic statement with falsification test. For market / strategy / forecasting rooms.\",\n \" · `open-questions` Residual unresolved questions tagged P0/P1.\",\n \"\",\n \"Gartner-density blocks · pick these when the room's value lives in the analysis of UNCERTAINTY, not in conclusions alone. These are the blocks that make a brief feel like a research-grade analysis instead of a meeting recap. Pull in 1–3 of them for any strategic-decision / market-forecast / impact-analysis room.\",\n \" · `strategic-outlook` 2-paragraph context + implication, sits between the anchor and the findings. Use when the room needs to set up the operating environment before findings make sense (geopolitics, macro shifts, market state).\",\n \" · `critical-assumptions` 4–6 load-bearing assumptions, each with confidence + falsifier + time horizon. Use when the brief's logic rests on assumptions that could shift — surfacing them lets the reader stress-test the conclusion.\",\n \" · `scenario-tree` 2–4 named futures (typically Base / Upside / Downside) with probabilities, triggers, effects, decision implications. Use whenever the room argued multiple futures — richer than `two-paths` because it adds probability + trigger + decision implication per branch.\",\n \" · `leading-indicators` 3–5 signals to monitor with thresholds + cadence + scenario each indicator confirms. Use when the brief tells the reader to wait-and-watch, or when scenarios diverge based on observable signals.\",\n \"\",\n \"## Composition rules · violations are rejected\",\n \"\",\n \"· Exactly 1 anchor (from `bottom-line` / `thesis` / `working-hypothesis`).\",\n \"· Exactly 1 findings (from `headline-findings` / `big-ideas`).\",\n \"· Exactly 1 action (from `recommendations` / `the-bet` / `considerations`).\",\n \"· Total components: 5–12. Below 5 = thin; above 12 = noise. The new range allows for the Gartner-density blocks when warranted.\",\n \"· Drop a component if the conversation didn't produce material for it. Empty sections are worse than missing ones.\",\n \"· Spine ≠ template. Pick the spine whose voice fits the topic, then pick components independently.\",\n \"· If unsure → `boardroom-dark` spine and the safe set: `bottom-line` + `frame-shift` + `headline-findings` + `convergence` (or `divergence`) + `recommendations` + `new-questions` + `open-questions`.\",\n \"· Strategic / market / forecast / impact-analysis subjects → `boardroom-dark` (or `gartner-note`) spine and the dense set: `bottom-line` + `strategic-outlook` + `headline-findings` + `critical-assumptions` + `scenario-tree` + `recommendations` + `leading-indicators` + `pre-mortem` + `new-questions`. ~9 components — feels like a research note.\",\n \"\",\n \"## Spine catalogue\",\n \"\",\n \"v1 renders only `boardroom-dark`. The other spines are accepted by the schema but coerced to `boardroom-dark` until their renderers ship — your pick is still recorded for analytics.\",\n \" · `boardroom-dark` default · room recap · philosophical · mixed\",\n \" · `a16z-thesis` investment / market opportunity / 'should we bet on X'\",\n \" · `anthropic-essay` open-ended exploration · philosophical / framing\",\n \" · `gartner-note` strategic decision under uncertainty · vendor / option scoring\",\n \" · `mckinsey-deck` execution / operational / 'how do we do X'\",\n \" · `openai-paper` technical / research-style / N-option comparison\",\n \"\",\n \"## Topic → spine heuristics (non-binding)\",\n \"\",\n \"· 'should we invest / build / back / bet' or 'is X defensible' → `a16z-thesis` (`thesis` + `the-bet`)\",\n \"· 'what does X mean' / philosophical / open-ended → `anthropic-essay` (anchor stays `bottom-line`)\",\n \"· 'compare / pick between / which option / vendor scan' → `gartner-note` or `openai-paper`\",\n \"· 'how do we do / roll out / execute / fix' → `mckinsey-deck`\",\n \"· post-mortem / retro / 'what happened' → `boardroom-dark`\",\n \"\",\n \"## Language\",\n \"\",\n \"Component kinds and spine slugs are LITERAL English strings — never translate them. The `rationale` field IS user-facing — produce it in the room's output language (zh / en).\",\n \"\",\n \"## Subject type\",\n \"\",\n \"Pick one of: `investment-judgement`, `option-comparison`, `strategic-decision`, `philosophical`, `operational`, `market-forecast`, `retro`, `other`. This is recorded for analytics and future presets — keep it honest, no guessing for marketing reasons.\",\n].join(\"\\n\");\n\n/* ─────────────────────────── Builder ───────────────────────────────────── */\n\ninterface BuildOpts {\n room: Room;\n members: Agent[];\n perDirectorSignals: DirectorSignals[];\n language: ReportLanguage;\n /** Optional supplementary perspective the user supplied — affects the\n * composer's pick (e.g. \"look at this as a Gartner-style scan\" should\n * flip the spine). */\n supplement?: string;\n}\n\nexport function buildComposerMessages(opts: BuildOpts): LLMMessage[] {\n const { room, members, perDirectorSignals, language, supplement } = opts;\n\n const directors = members\n .filter((m) => m.roleKind === \"director\")\n .map((a) => `${a.id} · ${a.name} (${a.handle}) — ${a.roleTag}`)\n .join(\"\\n · \");\n\n const signalsBlock = perDirectorSignals\n .map((d) => {\n if (!d.signals.length) return `[${d.directorId}] ${d.directorName} — (no signals)`;\n const lines = d.signals\n .map((s, i) => ` · ${d.directorId}#${i} [${s.lens}] ${s.text}`)\n .join(\"\\n\");\n return `[${d.directorId}] ${d.directorName}\\n${lines}`;\n })\n .join(\"\\n\\n\");\n\n const langLine = language === \"zh\"\n ? \"## Output language\\n本次会议的 Initial Question 是中文。`rationale` 字段请用**简体中文**。其它字段(`spine`, `kind`, `subject_type`)保留英文枚举值不变。\"\n : \"## Output language\\nThis room's Initial Question was in English. Produce the `rationale` field in English. The `spine`, `kind`, and `subject_type` fields are literal enum strings — never translate them.\";\n\n const supplementBlock = supplement && supplement.trim()\n ? [\n \"\",\n \"─── SUPPLEMENTARY PERSPECTIVE FROM USER ───\",\n \"\",\n \"The user has asked the regenerated brief to address this angle. Let it influence both the spine and the components. The user mentioning a specific framing (e.g. 'as a Gartner research note', 'as an investment thesis') is a strong steer — follow it.\",\n \"\",\n supplement.trim(),\n \"\",\n \"─── END SUPPLEMENT ───\",\n ].join(\"\\n\")\n : \"\";\n\n return [\n { role: \"system\", content: [SYSTEM_PROMPT, \"\", langLine].join(\"\\n\") },\n {\n role: \"user\",\n content: [\n `ROOM #${room.number} · ${room.name}`,\n `Subject: ${room.subject}`,\n `Mode: ${room.mode}`,\n ``,\n `Directors:`,\n ` · ${directors || \"(none)\"}`,\n ``,\n `─── SIGNALS ───`,\n ``,\n signalsBlock || \"(no signals extracted)\",\n ``,\n `─── END SIGNALS ───`,\n supplementBlock,\n ``,\n `Pick the spine and components now. JSON only.`,\n ].join(\"\\n\"),\n },\n ];\n}\n\n/* ─────────────────────────── Parser + validation ───────────────────────── */\n\nconst KIND_SET: ReadonlySet<string> = new Set(COMPONENT_KINDS);\nconst SPINE_SET: ReadonlySet<string> = new Set(SPINES);\nconst ANCHOR_SET: ReadonlySet<string> = new Set(ANCHORS);\nconst FINDINGS_SET: ReadonlySet<string> = new Set(FINDINGS);\nconst ACTION_SET: ReadonlySet<string> = new Set(ACTIONS);\n\nconst ALLOWED_SUBJECT_TYPES = new Set([\n \"investment-judgement\",\n \"option-comparison\",\n \"strategic-decision\",\n \"philosophical\",\n \"operational\",\n \"market-forecast\",\n \"retro\",\n \"other\",\n]);\n\ninterface ValidationProblem {\n reason: string;\n}\n\n/**\n * Parse + validate the composer's raw JSON. Returns null when the\n * output is unrecoverable — callers fall back to DEFAULT_PRESET.\n *\n * Validation enforces the catalogue's substitute-group rules and the\n * 5–9 total component cap. We're permissive on minor sins (unknown\n * kinds are dropped silently rather than rejecting the whole pick) so\n * one weird kind doesn't throw away an otherwise good composition.\n */\nexport function parseComposerOutput(raw: string): ComposerResult | null {\n const parsed = extractJson<{\n spine?: unknown;\n components?: unknown;\n rationale?: unknown;\n subject_type?: unknown;\n subjectType?: unknown;\n }>(raw);\n if (!parsed) return null;\n\n // Spine.\n const spineRaw = typeof parsed.spine === \"string\" ? parsed.spine.trim() : \"\";\n const spine: Spine = SPINE_SET.has(spineRaw) ? (spineRaw as Spine) : \"boardroom-dark\";\n\n // Components · normalize, dedupe, drop unknowns.\n if (!Array.isArray(parsed.components)) return null;\n const seen = new Set<ComponentKind>();\n const picks: ComponentPick[] = [];\n for (const entry of parsed.components) {\n if (!entry || typeof entry !== \"object\") continue;\n const e = entry as Record<string, unknown>;\n const kind = typeof e.kind === \"string\" ? e.kind.trim() : \"\";\n if (!KIND_SET.has(kind)) continue;\n if (seen.has(kind as ComponentKind)) continue;\n const order = typeof e.order === \"number\" && Number.isFinite(e.order)\n ? Math.floor(e.order)\n : picks.length;\n picks.push({ kind: kind as ComponentKind, order });\n seen.add(kind as ComponentKind);\n }\n\n const problem = validatePicks(picks);\n if (problem) return null;\n\n // Stable order: ascending `order`, ties broken by catalogue order.\n picks.sort((a, b) => {\n if (a.order !== b.order) return a.order - b.order;\n return COMPONENT_KINDS.indexOf(a.kind) - COMPONENT_KINDS.indexOf(b.kind);\n });\n // Re-number contiguously so the renderer doesn't have to.\n picks.forEach((p, i) => {\n p.order = i + 1;\n });\n\n const rationale = typeof parsed.rationale === \"string\" ? parsed.rationale.trim().slice(0, 240) : \"\";\n const subjectTypeRaw = typeof parsed.subject_type === \"string\"\n ? parsed.subject_type\n : typeof parsed.subjectType === \"string\"\n ? parsed.subjectType\n : \"\";\n const subjectType = subjectTypeRaw && ALLOWED_SUBJECT_TYPES.has(subjectTypeRaw.trim())\n ? subjectTypeRaw.trim()\n : null;\n\n return {\n spine,\n components: picks,\n rationale,\n subjectType,\n fromComposer: true,\n };\n}\n\nfunction validatePicks(picks: ComponentPick[]): ValidationProblem | null {\n // 5–12 component cap · room for the Gartner-density blocks (strategic-\n // outlook, critical-assumptions, scenario-tree, leading-indicators)\n // when the brief's analytical depth justifies them. Below 5 reads as\n // a meeting recap; above 12 turns into noise.\n if (picks.length < 5) return { reason: `too few components (${picks.length} < 5)` };\n if (picks.length > 12) return { reason: `too many components (${picks.length} > 12)` };\n\n const kinds = new Set(picks.map((p) => p.kind));\n const anchorCount = countMembers(kinds, ANCHOR_SET);\n if (anchorCount !== 1) return { reason: `expected exactly 1 anchor, got ${anchorCount}` };\n const findingsCount = countMembers(kinds, FINDINGS_SET);\n if (findingsCount !== 1) return { reason: `expected exactly 1 findings, got ${findingsCount}` };\n const actionCount = countMembers(kinds, ACTION_SET);\n if (actionCount !== 1) return { reason: `expected exactly 1 action, got ${actionCount}` };\n return null;\n}\n\nfunction countMembers(have: ReadonlySet<string>, group: ReadonlySet<string>): number {\n let n = 0;\n for (const k of have) if (group.has(k)) n++;\n return n;\n}\n\n/**\n * The safety-net composition · the same 12-section layout the codebase\n * shipped before Stage 1.5 existed. Used when (a) the LLM call failed,\n * (b) output couldn't be parsed, (c) validation rejected the picks, or\n * (d) the room had no signals at all.\n */\nexport function defaultComposition(reason: string): ComposerResult {\n return {\n spine: \"boardroom-dark\",\n components: DEFAULT_PRESET.map((p) => ({ ...p })),\n rationale: reason,\n subjectType: null,\n fromComposer: false,\n };\n}\n\n/** Coerce a composer-picked spine down to a renderer the frontend\n * actually has. All 6 spines now ship CSS renderers; this passthrough\n * remains as a safety net for unknown values (in case the persisted\n * spine string is from a future release we haven't loaded yet). */\nexport function activeSpine(picked: Spine): Spine {\n if (SPINE_SET.has(picked)) return picked;\n return \"boardroom-dark\";\n}\n","/**\n * Brief writer prompt — turns a room's transcript into a structured deliverable.\n *\n * V1 supports one style: 'mckinsey' (situation / findings / implication).\n * Other styles will plug in here without touching the orchestrator.\n */\nimport type { LLMMessage } from \"../adapter.js\";\nimport type { Agent } from \"../../storage/agents.js\";\nimport type { Message } from \"../../storage/messages.js\";\nimport type { Room } from \"../../storage/rooms.js\";\n\nexport type BriefStyle = \"mckinsey\";\n\ninterface BuildOpts {\n room: Room;\n members: Agent[];\n transcript: Message[];\n style: BriefStyle;\n /** Output language. Optional for backwards compat with existing\n * callers / tests; defaults to English. The structured pipeline\n * always passes this through. */\n language?: \"zh\" | \"en\";\n}\n\nconst SYSTEM_MCKINSEY = [\n \"You are the boardroom's brief writer. Your single output is the filed brief: a structured synthesis of what was said, what holds up, and what the user should do.\",\n \"\",\n \"FORMAT: McKinsey-style three-section frame, in markdown.\",\n \"\",\n \"## Required structure\",\n \"\",\n \"Start with a single H2 title (5-12 words) capturing the load-bearing claim, not the question.\",\n \"Then exactly three sections with these H2 headings, in order:\",\n \"\",\n \" ## Situation\",\n \" One paragraph (3-5 sentences) framing the problem as it stood when the room opened. Plain language, no jargon.\",\n \"\",\n \" ## Findings\",\n \" Three to five bullets. Each starts with a **bold claim** (one short sentence), then 1-2 sentences of why. Bullets must be the actual disagreements / decisions reached, not summaries of what each director said.\",\n \"\",\n \" ## Implication\",\n \" One paragraph (3-5 sentences) stating what the user should do, in order. End with the falsifiable test — the observable that would prove the brief right or wrong over time.\",\n \"\",\n \"## Voice rules\",\n \"\",\n \"· Plain prose. No flattery. No 'the room concluded that...' hedging.\",\n \"· Use *italics* for the load-bearing word in a claim.\",\n \"· Use **bold** for the claim itself in each Findings bullet.\",\n \"· Quote a director only when their exact phrase is the cleanest expression of the point — at most twice in the whole brief.\",\n \"· Never include 'I' or 'we' as the writer. The brief is the room speaking, not the writer.\",\n \"· No preamble, no closing remarks, no 'in summary'. Just the brief.\",\n \"\",\n \"## What to leave out\",\n \"\",\n \"· Side threads that didn't bear weight.\",\n \"· Disagreements that were resolved — only surface unresolved ones if they materially affect the implication.\",\n \"· Anything the user said that wasn't picked up by the directors.\",\n].join(\"\\n\");\n\nexport function buildBriefMessages(opts: BuildOpts): LLMMessage[] {\n const { room, members, transcript, style } = opts;\n const language: \"zh\" | \"en\" = opts.language ?? \"en\";\n\n const memberList = members\n .map((a) => `${a.name} (${a.handle}) — ${a.roleTag}: ${a.bio}`)\n .join(\"\\n · \");\n\n const renderedTranscript = transcript\n .map((m) => {\n if (m.authorKind === \"user\") return `[You]: ${m.body}`;\n if (m.authorKind === \"system\") return `[system]: ${m.body}`;\n const a = members.find((x) => x.id === m.authorId);\n const name = a ? `${a.name} (${a.handle})` : \"[unknown]\";\n return `[${name}]: ${m.body}`;\n })\n .join(\"\\n\\n\");\n\n const langLine = language === \"zh\"\n ? \"## 输出语言\\n本次会议的 Initial Question 是中文,请用**简体中文**撰写报告。\"\n : \"## Output language\\nThis room's Initial Question was in English. Write the brief in English.\";\n\n const system: LLMMessage = {\n role: \"system\",\n content: [style === \"mckinsey\" ? SYSTEM_MCKINSEY : SYSTEM_MCKINSEY, \"\", langLine].join(\"\\n\"),\n };\n\n const user: LLMMessage = {\n role: \"user\",\n content: [\n `ROOM #${room.number} · ${room.name}`,\n `Subject: ${room.subject}`,\n `Mode: ${room.mode}`,\n ``,\n `Directors at the table:`,\n ` · ${memberList}`,\n ``,\n `─── TRANSCRIPT ───`,\n ``,\n renderedTranscript,\n ``,\n `─── END TRANSCRIPT ───`,\n ``,\n `Write the brief now. Markdown only. Start with the H2 title — no preamble.`,\n ].join(\"\\n\"),\n };\n\n return [system, user];\n}\n\n/**\n * Pull the H2 title out of the brief markdown. Falls back to the room subject\n * if no H2 found in the first 8 lines.\n */\nexport function extractBriefTitle(bodyMd: string, fallback: string): string {\n const lines = bodyMd.split(\"\\n\").slice(0, 12);\n for (const line of lines) {\n const m = /^##\\s+(.+)$/.exec(line.trim());\n if (m && m[1] && !/^(situation|findings|implication)$/i.test(m[1])) {\n return m[1].trim();\n }\n }\n return fallback;\n}\n","/** Messages — full implementation with insert + streaming append helpers. */\nimport { newId } from \"../utils/id.js\";\n\nimport { getDb } from \"./db.js\";\n\nexport type AuthorKind = \"agent\" | \"user\" | \"system\";\n\nexport interface MessageMeta {\n mentions?: string[]; // agent ids\n speakerStatus?: \"thinking\" | \"streaming\" | \"final\";\n streaming?: boolean;\n [key: string]: unknown;\n}\n\nexport interface Message {\n id: string;\n roomId: string;\n authorKind: AuthorKind;\n authorId: string | null;\n replyToId: string | null;\n body: string;\n meta: MessageMeta;\n roundNum: number;\n createdAt: number;\n}\n\ninterface Row {\n id: string;\n room_id: string;\n author_kind: string;\n author_id: string | null;\n reply_to_id: string | null;\n body: string;\n meta_json: string | null;\n round_num: number;\n created_at: number;\n}\n\nconst COLS =\n \"id, room_id, author_kind, author_id, reply_to_id, body, meta_json, round_num, created_at\";\n\nfunction mapRow(row: Row): Message {\n return {\n id: row.id,\n roomId: row.room_id,\n authorKind: row.author_kind as AuthorKind,\n authorId: row.author_id,\n replyToId: row.reply_to_id,\n body: row.body,\n meta: row.meta_json ? (JSON.parse(row.meta_json) as MessageMeta) : {},\n roundNum: row.round_num,\n createdAt: row.created_at,\n };\n}\n\nexport function listMessages(roomId: string): Message[] {\n const rows = getDb()\n .prepare(`SELECT ${COLS} FROM messages WHERE room_id = ? ORDER BY created_at ASC`)\n .all(roomId) as Row[];\n return rows.map(mapRow);\n}\n\nexport function getMessage(id: string): Message | null {\n const row = getDb()\n .prepare(`SELECT ${COLS} FROM messages WHERE id = ?`)\n .get(id) as Row | undefined;\n return row ? mapRow(row) : null;\n}\n\n/**\n * Most recent N messages for context-window assembly. Returned in chronological\n * order (oldest first), so callers can append straight onto the LLM history.\n */\nexport function listRecentMessages(roomId: string, limit = 30): Message[] {\n const rows = getDb()\n .prepare(`SELECT ${COLS} FROM messages WHERE room_id = ? ORDER BY created_at DESC LIMIT ?`)\n .all(roomId, limit) as Row[];\n return rows.map(mapRow).reverse();\n}\n\nexport function getCurrentRound(roomId: string): number {\n const row = getDb()\n .prepare(\"SELECT COALESCE(MAX(round_num), 1) AS n FROM messages WHERE room_id = ?\")\n .get(roomId) as { n: number };\n return row.n;\n}\n\n/**\n * Next round number for an incoming USER message. Each user turn opens a new\n * round; the directors that respond in that turn share the same round_num.\n */\nexport function nextUserRoundNum(roomId: string): number {\n const row = getDb()\n .prepare(\"SELECT COALESCE(MAX(round_num), 0) AS n FROM messages WHERE room_id = ?\")\n .get(roomId) as { n: number };\n return row.n + 1;\n}\n\nexport interface MessageInsert {\n roomId: string;\n authorKind: AuthorKind;\n authorId?: string | null;\n replyToId?: string | null;\n body: string;\n meta?: MessageMeta;\n roundNum?: number;\n}\n\nexport function insertMessage(m: MessageInsert): Message {\n const id = newId();\n const now = Date.now();\n const roundNum = m.roundNum ?? getCurrentRound(m.roomId);\n const metaJson = m.meta ? JSON.stringify(m.meta) : null;\n\n getDb()\n .prepare(\n \"INSERT INTO messages (id, room_id, author_kind, author_id, reply_to_id, body, meta_json, round_num, created_at) \" +\n \"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\",\n )\n .run(\n id,\n m.roomId,\n m.authorKind,\n m.authorId ?? null,\n m.replyToId ?? null,\n m.body,\n metaJson,\n roundNum,\n now,\n );\n\n return getMessage(id)!;\n}\n\n/**\n * Update the body and (optionally) meta of an existing message. Used by the\n * orchestrator while a director is streaming — we insert a placeholder and\n * keep replacing its body as deltas arrive.\n */\nexport function updateMessageBody(id: string, body: string, meta?: MessageMeta): void {\n if (meta) {\n getDb()\n .prepare(\"UPDATE messages SET body = ?, meta_json = ? WHERE id = ?\")\n .run(body, JSON.stringify(meta), id);\n } else {\n getDb().prepare(\"UPDATE messages SET body = ? WHERE id = ?\").run(body, id);\n }\n}\n\n/** Permanently remove a message — used to drop empty placeholders. */\nexport function deleteMessage(id: string): boolean {\n const r = getDb().prepare(\"DELETE FROM messages WHERE id = ?\").run(id);\n return r.changes > 0;\n}\n","/** Rooms + room_members + lifecycle helpers. */\nimport { newId } from \"../utils/id.js\";\n\nimport { getChairAgent } from \"./agents.js\";\nimport { getDb } from \"./db.js\";\n\nexport type RoomStatus = \"live\" | \"paused\" | \"adjourned\";\n\nexport interface Room {\n id: string;\n number: number;\n name: string;\n subject: string;\n mode: string; // tone: brainstorm | constructive | debate | no-mercy\n intensity: string; // calm | sharp | brutal\n status: RoomStatus;\n briefStyle: string | null; // auto | mckinsey | gartner | a16z | anthropic | 8bit\n /** Soft-pause flag set by the chair after a round-end key-points message. */\n awaitingContinue: boolean;\n /** Soft-pause during the chair's opening clarification phase — user\n * replies route through the chair until it signals READY. */\n awaitingClarify: boolean;\n createdAt: number;\n pausedAt: number | null;\n adjournedAt: number | null;\n /** Incognito · when true, room adjourn does NOT extract long-term\n * memory for any agent. Defaults to false; user toggles via Room\n * Settings. Per-room flag, not global. */\n incognito: boolean;\n}\n\nexport interface RoomMember {\n agentId: string;\n position: number;\n joinedAt: number;\n}\n\ninterface Row {\n id: string;\n number: number;\n name: string;\n subject: string;\n mode: string;\n intensity: string;\n status: string;\n brief_style: string | null;\n awaiting_continue: number;\n awaiting_clarify: number;\n created_at: number;\n paused_at: number | null;\n adjourned_at: number | null;\n incognito: number;\n}\n\ninterface MemberRow {\n agent_id: string;\n position: number;\n joined_at: number;\n}\n\nconst ROOM_COLS =\n \"id, number, name, subject, mode, intensity, status, brief_style, awaiting_continue, \" +\n \"awaiting_clarify, created_at, paused_at, adjourned_at, incognito\";\n\nfunction mapRow(row: Row): Room {\n return {\n id: row.id,\n number: row.number,\n name: row.name,\n subject: row.subject,\n mode: row.mode,\n intensity: row.intensity,\n status: row.status as RoomStatus,\n briefStyle: row.brief_style,\n awaitingContinue: row.awaiting_continue === 1,\n awaitingClarify: row.awaiting_clarify === 1,\n createdAt: row.created_at,\n pausedAt: row.paused_at,\n adjournedAt: row.adjourned_at,\n incognito: row.incognito === 1,\n };\n}\n\nfunction mapMember(row: MemberRow): RoomMember {\n return { agentId: row.agent_id, position: row.position, joinedAt: row.joined_at };\n}\n\nexport function listRooms(): Room[] {\n const rows = getDb()\n .prepare(`SELECT ${ROOM_COLS} FROM rooms ORDER BY created_at DESC`)\n .all() as Row[];\n return rows.map(mapRow);\n}\n\nexport function getRoom(id: string): Room | null {\n const row = getDb()\n .prepare(`SELECT ${ROOM_COLS} FROM rooms WHERE id = ?`)\n .get(id) as Row | undefined;\n return row ? mapRow(row) : null;\n}\n\nexport function listRoomMembers(roomId: string): RoomMember[] {\n const rows = getDb()\n .prepare(\n \"SELECT agent_id, position, joined_at FROM room_members WHERE room_id = ? ORDER BY position ASC\",\n )\n .all(roomId) as MemberRow[];\n return rows.map(mapMember);\n}\n\n/** How many of the last N rooms each director appeared in. Used by\n * the director auto-picker for recency-bias — directors seated in\n * recent rooms get downweighted when topical fit is comparable, so\n * the user doesn't keep seeing the same trio across consecutive\n * rooms. The chair (position -1) is excluded from the count.\n * Returns a Map keyed by agentId. Missing keys = 0 recent appearances. */\nexport function recentDirectorAppearances(\n windowSize: number,\n): Map<string, number> {\n const rooms = getDb()\n .prepare(\"SELECT id FROM rooms ORDER BY created_at DESC LIMIT ?\")\n .all(Math.max(1, Math.floor(windowSize))) as Array<{ id: string }>;\n const counts = new Map<string, number>();\n if (rooms.length === 0) return counts;\n const placeholders = rooms.map(() => \"?\").join(\",\");\n const memberRows = getDb()\n .prepare(\n `SELECT agent_id FROM room_members\n WHERE room_id IN (${placeholders})\n AND position >= 0`,\n )\n .all(...rooms.map((r) => r.id)) as Array<{ agent_id: string }>;\n for (const row of memberRows) {\n counts.set(row.agent_id, (counts.get(row.agent_id) ?? 0) + 1);\n }\n return counts;\n}\n\nexport function countRooms(): number {\n const row = getDb().prepare(\"SELECT COUNT(*) AS n FROM rooms\").get() as { n: number };\n return row.n;\n}\n\nfunction nextRoomNumber(): number {\n const row = getDb().prepare(\"SELECT COALESCE(MAX(number), 0) AS n FROM rooms\").get() as { n: number };\n return row.n + 1;\n}\n\nexport interface RoomCreate {\n name: string;\n subject: string;\n mode?: string;\n intensity?: string;\n briefStyle?: string;\n agentIds: string[]; // ordered = speaking order\n}\n\n/**\n * Create a room with members in a single transaction. Returns the new room +\n * its members (so the caller can immediately seed the room-opened event).\n */\nexport function createRoom(input: RoomCreate): { room: Room; members: RoomMember[] } {\n const db = getDb();\n const id = newId();\n const number = nextRoomNumber();\n const now = Date.now();\n const mode = input.mode ?? \"constructive\";\n const intensity = input.intensity ?? \"sharp\";\n const briefStyle = input.briefStyle ?? \"auto\";\n\n const insertRoom = db.prepare(\n \"INSERT INTO rooms (id, number, name, subject, mode, intensity, brief_style, status, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, 'live', ?)\",\n );\n const insertMember = db.prepare(\n \"INSERT INTO room_members (room_id, agent_id, position, joined_at) VALUES (?, ?, ?, ?)\",\n );\n\n // Chair attaches to every room at position -1 (above all directors)\n // so the round-robin queue (which iterates positions 0+) skips them\n // automatically. Chair runs on lifecycle events, not the queue.\n const chair = getChairAgent();\n\n const tx = db.transaction(() => {\n insertRoom.run(id, number, input.name, input.subject, mode, intensity, briefStyle, now);\n if (chair) insertMember.run(id, chair.id, -1, now);\n input.agentIds.forEach((agentId, idx) => {\n // Don't double-insert if a caller passed the chair id explicitly.\n if (chair && agentId === chair.id) return;\n insertMember.run(id, agentId, idx, now);\n });\n });\n tx();\n\n return {\n room: getRoom(id)!,\n members: listRoomMembers(id),\n };\n}\n\nexport interface RoomTimestampPatch {\n pausedAt?: number | null;\n adjournedAt?: number | null;\n}\n\nexport function setRoomStatus(\n roomId: string,\n status: RoomStatus,\n ts: RoomTimestampPatch = {},\n): void {\n const sets: string[] = [\"status = ?\"];\n const vals: unknown[] = [status];\n if (ts.pausedAt !== undefined) { sets.push(\"paused_at = ?\"); vals.push(ts.pausedAt); }\n if (ts.adjournedAt !== undefined) { sets.push(\"adjourned_at = ?\"); vals.push(ts.adjournedAt); }\n vals.push(roomId);\n getDb().prepare(`UPDATE rooms SET ${sets.join(\", \")} WHERE id = ?`).run(...vals);\n}\n\n/**\n * Add a director to a live room. The new member is appended at the\n * tail of the speaking order (one past the current max position) so\n * existing rotations don't reshuffle. No-op if already a member.\n * Returns the row that was created (or the existing one).\n */\nexport function addRoomMember(roomId: string, agentId: string): RoomMember | null {\n const db = getDb();\n const existing = db\n .prepare(\"SELECT agent_id, position, joined_at FROM room_members WHERE room_id = ? AND agent_id = ?\")\n .get(roomId, agentId) as MemberRow | undefined;\n if (existing) return mapMember(existing);\n const maxRow = db\n .prepare(\"SELECT COALESCE(MAX(position), -1) AS p FROM room_members WHERE room_id = ?\")\n .get(roomId) as { p: number };\n const position = maxRow.p + 1;\n const now = Date.now();\n db.prepare(\n \"INSERT INTO room_members (room_id, agent_id, position, joined_at) VALUES (?, ?, ?, ?)\",\n ).run(roomId, agentId, position, now);\n return { agentId, position, joinedAt: now };\n}\n\n/** Remove a director from a room. No-op if they weren't a member. */\nexport function removeRoomMember(roomId: string, agentId: string): boolean {\n const result = getDb()\n .prepare(\"DELETE FROM room_members WHERE room_id = ? AND agent_id = ?\")\n .run(roomId, agentId);\n return result.changes > 0;\n}\n\n/** Toggle the per-room incognito flag. While true, room adjourn skips\n * the long-term memory extraction step for every agent. */\nexport function setRoomIncognito(roomId: string, incognito: boolean): void {\n getDb()\n .prepare(\"UPDATE rooms SET incognito = ? WHERE id = ?\")\n .run(incognito ? 1 : 0, roomId);\n}\n\n/** Update the soft-pause flag set by the chair after a round-end. */\nexport function setAwaitingContinue(roomId: string, awaiting: boolean): void {\n getDb()\n .prepare(\"UPDATE rooms SET awaiting_continue = ? WHERE id = ?\")\n .run(awaiting ? 1 : 0, roomId);\n}\n\n/** Update the soft-pause flag set by the chair during clarification. */\nexport function setAwaitingClarify(roomId: string, awaiting: boolean): void {\n getDb()\n .prepare(\"UPDATE rooms SET awaiting_clarify = ? WHERE id = ?\")\n .run(awaiting ? 1 : 0, roomId);\n}\n\nexport interface RoomSettingsPatch {\n mode?: string;\n intensity?: string;\n briefStyle?: string;\n}\n\n/**\n * Update room configuration (tone/intensity/report style). Only fields\n * present on the patch are touched — pass undefined to leave alone.\n * Returns the updated room or null if the row doesn't exist.\n */\nexport function updateRoomSettings(\n roomId: string,\n patch: RoomSettingsPatch,\n): Room | null {\n const sets: string[] = [];\n const vals: unknown[] = [];\n if (patch.mode !== undefined) { sets.push(\"mode = ?\"); vals.push(patch.mode); }\n if (patch.intensity !== undefined) { sets.push(\"intensity = ?\"); vals.push(patch.intensity); }\n if (patch.briefStyle !== undefined) { sets.push(\"brief_style = ?\"); vals.push(patch.briefStyle); }\n if (sets.length === 0) return getRoom(roomId);\n vals.push(roomId);\n getDb().prepare(`UPDATE rooms SET ${sets.join(\", \")} WHERE id = ?`).run(...vals);\n return getRoom(roomId);\n}\n\n/**\n * Permanently delete a room and everything attached to it.\n * room_members / messages / config_events / briefs all CASCADE via FKs.\n * Returns true if a row was deleted.\n */\nexport function deleteRoom(roomId: string): boolean {\n const result = getDb().prepare(\"DELETE FROM rooms WHERE id = ?\").run(roomId);\n return result.changes > 0;\n}\n","/** Briefs · the room's filed deliverables. A room can have multiple\n * briefs: the first is generated on adjourn; each \"Add a perspective\"\n * regeneration appends a new row. Old briefs are preserved. */\nimport { newId } from \"../utils/id.js\";\n\nimport { getDb } from \"./db.js\";\n\n/** A single component the composer picked, with its render order. */\nexport interface BriefComponent {\n kind: string;\n order: number;\n}\n\nexport interface Brief {\n id: string;\n roomId: string;\n style: string;\n title: string;\n bodyMd: string;\n bodyJson: unknown | null;\n /** The supplementary perspective the user supplied when this brief\n * was a regeneration. NULL for the first / canonical brief. */\n supplement: string | null;\n /** Renderer key picked by the composer (v1: `boardroom-dark`). Legacy\n * rows default to `boardroom-dark` via the migration. */\n spine: string;\n /** Components the composer picked, in render order. Empty array =\n * legacy / no composer ran (renderer falls back to today's static\n * 12-section layout). */\n components: BriefComponent[];\n /** One-line composer rationale, surfaced on hover of the SPINE tag. */\n composerRationale: string | null;\n /** Coarse subject classification (e.g. `investment-judgement`). */\n subjectType: string | null;\n createdAt: number;\n}\n\ninterface Row {\n id: string;\n room_id: string;\n style: string;\n title: string;\n body_md: string;\n body_json: string | null;\n supplement: string | null;\n spine: string;\n components_json: string;\n composer_rationale: string | null;\n subject_type: string | null;\n created_at: number;\n}\n\nconst COLS =\n \"id, room_id, style, title, body_md, body_json, supplement, \" +\n \"spine, components_json, composer_rationale, subject_type, created_at\";\n\nfunction parseComponents(json: string | null | undefined): BriefComponent[] {\n if (!json) return [];\n try {\n const parsed = JSON.parse(json) as unknown;\n if (!Array.isArray(parsed)) return [];\n const out: BriefComponent[] = [];\n for (const entry of parsed) {\n if (!entry || typeof entry !== \"object\") continue;\n const e = entry as Record<string, unknown>;\n if (typeof e.kind !== \"string\" || !e.kind.trim()) continue;\n const order = typeof e.order === \"number\" && Number.isFinite(e.order)\n ? e.order\n : out.length;\n out.push({ kind: e.kind, order });\n }\n return out;\n } catch {\n return [];\n }\n}\n\nfunction mapRow(row: Row): Brief {\n return {\n id: row.id,\n roomId: row.room_id,\n style: row.style,\n title: row.title,\n bodyMd: row.body_md,\n bodyJson: row.body_json ? (JSON.parse(row.body_json) as unknown) : null,\n supplement: row.supplement,\n spine: row.spine || \"boardroom-dark\",\n components: parseComponents(row.components_json),\n composerRationale: row.composer_rationale,\n subjectType: row.subject_type,\n createdAt: row.created_at,\n };\n}\n\nexport function getBrief(id: string): Brief | null {\n const row = getDb().prepare(`SELECT ${COLS} FROM briefs WHERE id = ?`).get(id) as Row | undefined;\n return row ? mapRow(row) : null;\n}\n\n/**\n * Latest brief for a room — newest by created_at. Returns null if the\n * room has none. Existing callers that asked for \"the brief\" of a room\n * keep working through this; clients that need history use\n * listBriefsForRoom.\n */\nexport function getBriefByRoom(roomId: string): Brief | null {\n const row = getDb()\n .prepare(`SELECT ${COLS} FROM briefs WHERE room_id = ? ORDER BY created_at DESC LIMIT 1`)\n .get(roomId) as Row | undefined;\n return row ? mapRow(row) : null;\n}\n\n/**\n * All briefs for a room · newest first. Used by the brief-card tab\n * strip and the report viewer when navigating between regenerations.\n */\nexport function listBriefsForRoom(roomId: string): Brief[] {\n const rows = getDb()\n .prepare(`SELECT ${COLS} FROM briefs WHERE room_id = ? ORDER BY created_at DESC`)\n .all(roomId) as Row[];\n return rows.map(mapRow);\n}\n\n/** Brief + the parent room's display fields, joined at read time so\n * the All Reports page can render a card without a follow-up\n * /api/rooms/:id call per brief. */\nexport interface BriefWithRoom extends Brief {\n roomName: string;\n roomSubject: string;\n roomNumber: number;\n roomStatus: string;\n}\n\ninterface RowWithRoom extends Row {\n room_name: string;\n room_subject: string;\n room_number: number;\n room_status: string;\n}\n\n/**\n * Every brief across every room · newest first. Used by /api/briefs\n * (the All Reports view in the sidebar). Joins the parent room's\n * name/subject/number/status so each card has its full context in a\n * single response. The body_md and body_json columns are intentionally\n * INCLUDED so cards can preview the Bottom-Line judgement; client trims\n * what it doesn't render.\n */\nexport function listAllBriefs(): BriefWithRoom[] {\n const rows = getDb()\n .prepare(\n `SELECT b.id, b.room_id, b.style, b.title, b.body_md, b.body_json,\n b.supplement, b.spine, b.components_json,\n b.composer_rationale, b.subject_type, b.created_at,\n r.name AS room_name, r.subject AS room_subject,\n r.number AS room_number, r.status AS room_status\n FROM briefs b\n JOIN rooms r ON r.id = b.room_id\n ORDER BY b.created_at DESC`,\n )\n .all() as RowWithRoom[];\n return rows.map((row) => ({\n ...mapRow(row),\n roomName: row.room_name,\n roomSubject: row.room_subject,\n roomNumber: row.room_number,\n roomStatus: row.room_status,\n }));\n}\n\nexport interface BriefInsert {\n roomId: string;\n style: string;\n title: string;\n bodyMd: string;\n bodyJson?: unknown;\n /** Optional supplementary perspective the user requested. NULL when\n * this is the room's first brief. */\n supplement?: string | null;\n /** Composer's spine pick. Defaults to `boardroom-dark` when omitted. */\n spine?: string;\n /** Composer's component picks, in order. Empty array (the default)\n * means \"legacy / no composer ran\" — renderer falls back to the\n * static layout. */\n components?: BriefComponent[];\n composerRationale?: string | null;\n subjectType?: string | null;\n}\n\n/**\n * Insert a new brief row. Always inserts (no upsert) — multiple briefs\n * per room is the v2 model. The orchestrator inserts an empty\n * placeholder up-front and streams body/title updates via\n * updateBriefBody as tokens arrive. Composer fields are usually\n * unknown at placeholder-insert time and filled in later via\n * updateBriefCompose once Stage 1.5 returns.\n */\nexport function insertBrief(b: BriefInsert): Brief {\n const db = getDb();\n const id = newId();\n const now = Date.now();\n const bodyJson = b.bodyJson === undefined ? null : JSON.stringify(b.bodyJson);\n const supplement = b.supplement && b.supplement.trim() ? b.supplement.trim() : null;\n const spine = b.spine && b.spine.trim() ? b.spine.trim() : \"boardroom-dark\";\n const components = JSON.stringify(b.components ?? []);\n const composerRationale =\n b.composerRationale && b.composerRationale.trim() ? b.composerRationale.trim() : null;\n const subjectType = b.subjectType && b.subjectType.trim() ? b.subjectType.trim() : null;\n db.prepare(\n `INSERT INTO briefs (${COLS}) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n id,\n b.roomId,\n b.style,\n b.title,\n b.bodyMd,\n bodyJson,\n supplement,\n spine,\n components,\n composerRationale,\n subjectType,\n now,\n );\n return getBrief(id)!;\n}\n\n/**\n * Persist composer output discovered after the placeholder was created.\n * Called once Stage 1.5 returns, before Stage 2 starts. Keeps the\n * placeholder row's id stable — the UI's brief-card already binds to it.\n */\nexport function updateBriefCompose(\n id: string,\n fields: {\n spine?: string;\n components?: BriefComponent[];\n composerRationale?: string | null;\n subjectType?: string | null;\n },\n): void {\n const sets: string[] = [];\n const vals: unknown[] = [];\n if (fields.spine !== undefined) {\n sets.push(\"spine = ?\");\n vals.push(fields.spine);\n }\n if (fields.components !== undefined) {\n sets.push(\"components_json = ?\");\n vals.push(JSON.stringify(fields.components));\n }\n if (fields.composerRationale !== undefined) {\n sets.push(\"composer_rationale = ?\");\n vals.push(fields.composerRationale);\n }\n if (fields.subjectType !== undefined) {\n sets.push(\"subject_type = ?\");\n vals.push(fields.subjectType);\n }\n if (!sets.length) return;\n vals.push(id);\n getDb()\n .prepare(`UPDATE briefs SET ${sets.join(\", \")} WHERE id = ?`)\n .run(...vals);\n}\n\nexport function updateBriefBody(id: string, bodyMd: string, title?: string): void {\n if (title !== undefined) {\n getDb()\n .prepare(\"UPDATE briefs SET body_md = ?, title = ? WHERE id = ?\")\n .run(bodyMd, title, id);\n } else {\n getDb().prepare(\"UPDATE briefs SET body_md = ? WHERE id = ?\").run(bodyMd, id);\n }\n}\n\n/**\n * Permanently delete a brief by id. Returns true if a row was removed.\n * The on-disk markdown export at ~/.boardroom/briefs/{id}.md is the\n * caller's concern (route-level cleanup); this only touches SQLite.\n */\nexport function deleteBrief(id: string): boolean {\n const r = getDb().prepare(\"DELETE FROM briefs WHERE id = ?\").run(id);\n return r.changes > 0;\n}\n","/**\n * Token estimator · cheap heuristic that handles mixed CJK / ASCII\n * content well enough to drive ETA calculations. Not a real\n * tokenizer — don't use this to enforce hard limits, only for\n * estimating wall-clock LLM call time.\n *\n * Calibration:\n * ASCII: ~4 chars / token → 0.25 tokens / char\n * CJK: ~1.5 chars / token → 0.67 tokens / char\n */\nexport function estimateTokens(text: string | null | undefined): number {\n if (!text) return 0;\n let cjk = 0;\n let other = 0;\n for (let i = 0; i < text.length; i++) {\n const c = text.charCodeAt(i);\n if (\n (c >= 0x4e00 && c <= 0x9fff) || // CJK Unified Ideographs\n (c >= 0x3040 && c <= 0x30ff) || // Hiragana + Katakana\n (c >= 0xac00 && c <= 0xd7af) // Hangul\n ) {\n cjk++;\n } else {\n other++;\n }\n }\n return Math.ceil(cjk * 0.67 + other * 0.25);\n}\n","/**\n * Per-room event bus. The orchestrator emits events here as it works; the\n * /api/rooms/:id/stream HTTP route subscribes per connected client and\n * fan-outs to SSE.\n *\n * Event shapes are typed so route handlers can format them deterministically.\n */\nimport { EventEmitter } from \"node:events\";\n\nexport type RoomEvent =\n | {\n type: \"message-appended\";\n messageId: string;\n authorKind: string;\n authorId: string | null;\n replyToId: string | null;\n body: string;\n meta: Record<string, unknown>;\n roundNum: number;\n createdAt: number;\n }\n | { type: \"message-token\"; messageId: string; delta: string }\n | { type: \"message-final\"; messageId: string; finishReason?: string }\n | { type: \"message-removed\"; messageId: string; reason?: string }\n | { type: \"message-error\"; messageId: string; message: string }\n | {\n /** Full body + meta replacement for an existing message · used\n * by tool-use messages whose status flips running → done|failed\n * after the side-effect (URL fetch) completes. Distinct from\n * message-token (delta append for streaming) and message-final\n * (finish marker for streamed bodies). */\n type: \"message-updated\";\n messageId: string;\n body: string;\n meta: Record<string, unknown>;\n }\n | { type: \"config-event\"; kind: string; payload: Record<string, unknown> | null; createdAt: number }\n | {\n type: \"queue-update\";\n queue: Array<{ agentId: string; status: \"thinking\" | \"speaking\" | \"queued\" }>;\n /** Round progress so the UI knows when \"all directors spoke this round\". */\n round: { spoken: number; total: number };\n };\n\nclass RoomBus {\n private emitters = new Map<string, EventEmitter>();\n\n private get(roomId: string): EventEmitter {\n let e = this.emitters.get(roomId);\n if (!e) {\n e = new EventEmitter();\n e.setMaxListeners(64);\n this.emitters.set(roomId, e);\n }\n return e;\n }\n\n emit(roomId: string, event: RoomEvent): void {\n this.get(roomId).emit(\"event\", event);\n }\n\n /** Subscribe; returns an unsubscribe fn. */\n subscribe(roomId: string, listener: (event: RoomEvent) => void): () => void {\n const e = this.get(roomId);\n e.on(\"event\", listener);\n return () => e.off(\"event\", listener);\n }\n\n /** Drop all listeners for a room (e.g. when it's deleted). */\n drop(roomId: string): void {\n const e = this.emitters.get(roomId);\n if (e) {\n e.removeAllListeners();\n this.emitters.delete(roomId);\n }\n }\n}\n\nexport const roomBus = new RoomBus();\n","/**\n * /api/keys · provider key management.\n *\n * GET /api/keys → array of { provider, configured, updatedAt }\n * PUT /api/keys/:provider → set the key (body { key: \"...\" })\n * DELETE /api/keys/:provider → remove\n *\n * The plaintext key is never returned over the wire — even to localhost — so\n * the UI shows status pills only, and the user must paste a fresh value to\n * change it. `configured` reflects whether a non-empty value is stored.\n */\nimport { Hono } from \"hono\";\n\nimport {\n deleteKey,\n listKeyMeta,\n setKey,\n type Provider,\n type ProviderKeyMeta,\n} from \"../storage/keys.js\";\nimport { updatePrefs } from \"../storage/prefs.js\";\nimport {\n PRIMARY_BY_CARRIER,\n reconcileAgentModels,\n} from \"../storage/reconcile-models.js\";\n\nconst PROVIDERS = new Set<Provider>([\n \"openrouter\",\n \"anthropic\",\n \"openai\",\n \"google\",\n \"xai\",\n \"deepseek\",\n \"brave\",\n]);\n\nfunction isProvider(s: string): s is Provider {\n return PROVIDERS.has(s as Provider);\n}\n\nexport function keysRouter(): Hono {\n const r = new Hono();\n\n // GET /api/keys → list all providers, configured or not. Each entry\n // carries `preview` (4+4 mask of the stored key) so the UI can show\n // which key is in which slot without ever round-tripping plaintext.\n r.get(\"/\", (c) => {\n const meta = listKeyMeta();\n const map = new Map<Provider, ProviderKeyMeta>();\n for (const m of meta) map.set(m.provider, m);\n const out = Array.from(PROVIDERS).map((p) =>\n map.get(p) ?? { provider: p, configured: false, updatedAt: null, preview: null },\n );\n return c.json({ keys: out });\n });\n\n r.put(\"/:provider\", async (c) => {\n const provider = c.req.param(\"provider\");\n if (!isProvider(provider)) return c.json({ error: \"unknown provider\" }, 400);\n\n let body: unknown;\n try { body = await c.req.json(); }\n catch { return c.json({ error: \"invalid JSON body\" }, 400); }\n\n const key = (body as { key?: unknown })?.key;\n if (typeof key !== \"string\") return c.json({ error: \"body must contain { key: string }\" }, 400);\n\n // Optional · onboarding sends `makeDefault: true` to signal \"the\n // user just picked this provider as their primary.\" Without this,\n // a user who already had OpenRouter configured and adds Gemini\n // would see reconcile keep `opus-4-7` (still reachable via OR)\n // even though they clearly intended Gemini as the new default.\n // Setting `prefs.defaultModelV` to the new provider's flagship\n // before reconcile flips the active carrier, and reconcile then\n // switches every agent to that carrier's primary.\n const makeDefault = (body as { makeDefault?: unknown })?.makeDefault === true;\n\n setKey(provider, key);\n // Reconcile every agent's modelV against the new key set · LLM\n // providers only (skill providers like Brave don't affect chair\n // / director routing). When an agent's model becomes unreachable\n // it switches to the active carrier's primary; brand-new key on\n // a clean install bulk-promotes all agents to that primary.\n if (provider !== \"brave\") {\n const willForce = makeDefault && key.trim().length > 0;\n if (willForce) {\n const flagship = PRIMARY_BY_CARRIER[provider];\n if (flagship) {\n try { updatePrefs({ defaultModelV: flagship }); }\n catch (e) { process.stderr.write(`[keys.put] updatePrefs failed: ${e instanceof Error ? e.message : String(e)}\\n`); }\n }\n }\n try { reconcileAgentModels({ forcePrimary: willForce }); }\n catch (e) { process.stderr.write(`[keys.put] reconcile failed: ${e instanceof Error ? e.message : String(e)}\\n`); }\n }\n // Re-derive the meta (preview included) so the PUT response matches\n // the GET shape — frontend caches it directly into its keys map.\n const fresh = listKeyMeta().find((m) => m.provider === provider);\n return c.json(\n fresh ?? { provider, configured: key.trim().length > 0, updatedAt: Date.now(), preview: null },\n );\n });\n\n r.delete(\"/:provider\", (c) => {\n const provider = c.req.param(\"provider\");\n if (!isProvider(provider)) return c.json({ error: \"unknown provider\" }, 400);\n deleteKey(provider);\n // Reconcile · agents whose model just lost its only carrier swap\n // to the new active primary, or get cleared if all carriers are\n // gone. Brave is skill-only and doesn't affect agent routing.\n if (provider !== \"brave\") {\n try { reconcileAgentModels(); }\n catch (e) { process.stderr.write(`[keys.delete] reconcile failed: ${e instanceof Error ? e.message : String(e)}\\n`); }\n }\n return c.json({ provider, configured: false, updatedAt: null, preview: null });\n });\n\n return r;\n}\n","/**\n * /api/models · model availability surface.\n *\n * GET /api/models → which models the user can reach right now,\n * grouped by provider, with the route the\n * adapter would use (direct vs OpenRouter).\n * Plus the global default model + a flag for\n * whether ANY LLM key is configured (the\n * bootstrap state for the frontend redirect).\n *\n * Frontend pickers (composer, agent profile, agent creation) call\n * this once on mount + after any /api/keys mutation, and filter\n * their dropdowns to `reachable === true`.\n */\nimport { Hono } from \"hono\";\n\nimport {\n effectiveDefaultModel,\n hasAnyModelKey,\n modelAvailability,\n utilityModelFor,\n type ModelAvailability,\n} from \"../ai/availability.js\";\n\nexport function modelsRouter(): Hono {\n const r = new Hono();\n\n r.get(\"/\", (c) => {\n const all = modelAvailability();\n const reachable = all.filter((m) => m.reachable);\n return c.json({\n /** Whether any LLM provider key is configured. False → frontend\n * redirects the user to the API Key settings before letting\n * them create agents / convene rooms. */\n hasAnyKey: hasAnyModelKey(),\n /** Every model in the registry · reachable or not. Settings UI\n * uses this to show \"this model would unlock if you add the\n * Anthropic key\" hints. */\n models: all,\n /** Convenience subset · just the models the user can actually\n * use today. Pickers should default to this list. */\n reachable,\n /** Global default model · what new agents inherit and what\n * stale-modelV agents fall back to. NULL when no key is\n * configured yet. */\n defaultModelV: effectiveDefaultModel(),\n /** Cheap utility model used by background tasks (skill picker,\n * director auto-pick, agent-spec gen, ability analyzer,\n * convening speech). NULL when no key is configured. */\n utilityModelV: utilityModelFor(),\n /** Provider summary · so the frontend can show \"you have OR +\n * OpenAI direct\" at a glance without iterating models. */\n providers: collectProviderSummary(all),\n });\n });\n\n return r;\n}\n\nfunction collectProviderSummary(models: ModelAvailability[]): Array<{\n provider: string;\n reachable: number;\n total: number;\n}> {\n const map = new Map<string, { reachable: number; total: number }>();\n for (const m of models) {\n const cur = map.get(m.provider) ?? { reachable: 0, total: 0 };\n cur.total++;\n if (m.reachable) cur.reachable++;\n map.set(m.provider, cur);\n }\n return Array.from(map.entries()).map(([provider, v]) => ({ provider, ...v }));\n}\n","/**\n * /api/prefs · the user's local profile (name, intro, theme, avatar seed).\n * Single-row resource — there's no list, just GET + PATCH.\n */\nimport { Hono } from \"hono\";\n\nimport { getPrefs, updatePrefs, type PrefsPatch } from \"../storage/prefs.js\";\n\nexport function prefsRouter(): Hono {\n const r = new Hono();\n\n r.get(\"/\", (c) => c.json(getPrefs()));\n\n // PATCH-style: only fields present in the body are updated.\n r.put(\"/\", async (c) => {\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: \"invalid JSON body\" }, 400);\n }\n if (!body || typeof body !== \"object\") {\n return c.json({ error: \"body must be an object\" }, 400);\n }\n\n const patch: PrefsPatch = {};\n const b = body as Record<string, unknown>;\n\n if (typeof b.name === \"string\") patch.name = b.name.trim().slice(0, 64);\n if (typeof b.intro === \"string\") patch.intro = b.intro.slice(0, 320);\n if (typeof b.theme === \"string\") patch.theme = b.theme.trim().slice(0, 32);\n if (b.avatarSeed === null || typeof b.avatarSeed === \"string\") {\n patch.avatarSeed = b.avatarSeed as string | null;\n }\n if (b.defaultModelV === null || typeof b.defaultModelV === \"string\") {\n patch.defaultModelV = b.defaultModelV as string | null;\n }\n\n return c.json(updatePrefs(patch));\n });\n\n return r;\n}\n","/**\n * /api/rooms · room lifecycle + messaging.\n *\n * POST /api/rooms → create\n * GET /api/rooms → list\n * GET /api/rooms/:id → full state (room + members + messages)\n * GET /api/rooms/:id/stream → SSE: room events\n * POST /api/rooms/:id/messages → user sends a message · triggers tick\n * POST /api/rooms/:id/abort → cancel current in-flight LLM call\n */\nimport { Hono } from \"hono\";\nimport { streamSSE } from \"hono/streaming\";\n\nimport { generateBrief } from \"../orchestrator/brief.js\";\nimport {\n announceAdjournNoBrief,\n announceMemberChange,\n announceSettingsChange,\n runChairClarify,\n runChairConvening,\n runChairRoundEnd,\n} from \"../orchestrator/chair.js\";\nimport { extractMemoriesAfterAdjourn } from \"../orchestrator/memory.js\";\nimport {\n abortRoom,\n chairInterrupt,\n getRoomFullState,\n getRoomQueueSnapshot,\n injectSpeakers,\n isRoomSpeaking,\n requestSoftPause,\n resumeRoom,\n setPendingUserAfterCurrent,\n tickRoom,\n} from \"../orchestrator/room.js\";\nimport { pickDirectors } from \"../orchestrator/director-picker.js\";\nimport { roomBus, type RoomEvent } from \"../orchestrator/stream.js\";\nimport { getAgent, getChairAgent, listAgents } from \"../storage/agents.js\";\nimport { getBriefByRoom, listBriefsForRoom } from \"../storage/briefs.js\";\nimport { insertConfigEvent, listConfigEvents } from \"../storage/config-events.js\";\nimport {\n getKeyPoint,\n setKeyPointVote,\n type KeyPointVote,\n} from \"../storage/key_points.js\";\nimport { insertMessage, nextUserRoundNum } from \"../storage/messages.js\";\nimport {\n addRoomMember,\n createRoom,\n deleteRoom,\n getRoom,\n listRoomMembers,\n listRooms,\n recentDirectorAppearances,\n removeRoomMember,\n setAwaitingClarify,\n setAwaitingContinue,\n setRoomIncognito,\n setRoomStatus,\n updateRoomSettings,\n} from \"../storage/rooms.js\";\n\n/**\n * Auto-pick path · runs the LLM picker over the available director\n * catalog, seats each pick into the room with its own `member-added`\n * config event, posts a `convening` milestone message carrying the\n * picker's rationale, then returns. The caller (POST /api/rooms)\n * triggers chair-clarify after this resolves.\n *\n * Designed to be called fire-and-forget after the room has been\n * created with no director members yet · the SSE listener on the\n * frontend renders a \"convening\" animation while this runs and\n * fills the cast as `member-added` events arrive.\n */\nasync function runAutoPickAndSeat(roomId: string, subject: string): Promise<void> {\n // Tell the frontend \"auto-pick is running\" so it can show the\n // convening animation. Carries the candidate count so the UI can\n // render the empty slots up-front.\n const candidates = listAgents().filter((a) => a.roleKind === \"director\");\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"auto-pick-started\",\n payload: { subject, candidateCount: candidates.length, target: 3 },\n createdAt: Date.now(),\n });\n\n let result;\n try {\n // Recency bias · the picker downweights directors seated in the\n // last few rooms so consecutive rooms surface a different cast\n // when topical fit is comparable. Window of 5 is conservative —\n // covers ~a session's worth of rooms without prematurely\n // refusing a director who's a uniquely good fit.\n const recentAppearances = recentDirectorAppearances(5);\n result = await pickDirectors({ subject, candidates, recentAppearances });\n } catch (e) {\n // Picker should already swallow its own errors and fall back,\n // but this guard catches any truly catastrophic case.\n process.stderr.write(`[auto-pick] failed: ${e instanceof Error ? e.message : String(e)}\\n`);\n return;\n }\n\n // Seat each picked director with its own event so the UI fills\n // slots one-by-one. Tiny stagger between picks gives the animation\n // visible \"directors arriving\" beats rather than a single flash.\n const picks = result.picks;\n for (let i = 0; i < picks.length; i++) {\n const pick = picks[i];\n const member = addRoomMember(roomId, pick.agentId);\n if (!member) continue;\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"member-added\",\n payload: {\n agentId: pick.agentId,\n position: member.position,\n reason: pick.reason || \"\",\n autoPicked: true,\n index: i,\n total: picks.length,\n },\n createdAt: Date.now(),\n });\n if (i < picks.length - 1) {\n await new Promise((res) => setTimeout(res, 220));\n }\n }\n\n // Auto-pick is done · let the frontend dissolve the convening\n // overlay before the chair starts speaking, so the speech bubble\n // doesn't fight the overlay for the user's attention.\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"auto-pick-complete\",\n payload: { count: picks.length, fromLlm: result.fromLlm },\n createdAt: Date.now(),\n });\n\n // Stream a real chair speech introducing the cast · 3-4 sentences\n // explaining what's being decided + why each director was picked\n // + what lens-coverage they create together. Replaces the\n // templated \"chair convened: A · B · C\" milestone with substance\n // in the chair's own voice.\n const picksWithReasons = picks\n .map((p) => {\n const a = candidates.find((x) => x.id === p.agentId);\n return a ? { agent: a, reason: p.reason } : null;\n })\n .filter((x): x is { agent: typeof candidates[number]; reason: string } => x !== null);\n\n if (picksWithReasons.length > 0) {\n try {\n await runChairConvening(roomId, picksWithReasons, result.rationale);\n } catch (e) {\n // Convening speech is best-effort · if the LLM call fails the\n // room still proceeds to clarify. The directors are seated\n // either way so the user just doesn't get the \"why\" intro.\n process.stderr.write(\n `[auto-pick] convening speech failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n }\n }\n}\n\nexport function roomsRouter(): Hono {\n const r = new Hono();\n\n // ── List\n r.get(\"/\", (c) => c.json({ rooms: listRooms() }));\n\n // ── Create\n r.post(\"/\", async (c) => {\n let body: unknown;\n try { body = await c.req.json(); }\n catch { return c.json({ error: \"invalid JSON body\" }, 400); }\n\n const b = (body ?? {}) as {\n name?: unknown;\n subject?: unknown;\n mode?: unknown;\n intensity?: unknown;\n briefStyle?: unknown;\n agentIds?: unknown;\n autoPick?: unknown;\n };\n\n const subject = typeof b.subject === \"string\" ? b.subject.trim() : \"\";\n if (!subject) return c.json({ error: \"subject is required\" }, 400);\n\n // Auto-pick · the chair selects directors in the background after\n // the room opens. The user sees a \"convening\" animation in the\n // room view while the picker runs (haiku, ~1s), then directors\n // get added one by one with a rationale from the picker.\n const autoPick = b.autoPick === true;\n\n const agentIds: string[] = Array.isArray(b.agentIds)\n ? (b.agentIds as unknown[]).filter((x): x is string => typeof x === \"string\")\n : [];\n if (!autoPick && agentIds.length === 0) {\n return c.json({ error: \"at least one agent must be invited\" }, 400);\n }\n\n // Validate agents exist (only when manually picked).\n for (const id of agentIds) {\n if (!getAgent(id)) return c.json({ error: `unknown agent: ${id}` }, 400);\n }\n\n const name = typeof b.name === \"string\" && b.name.trim()\n ? b.name.trim().slice(0, 80)\n : subject.slice(0, 60);\n\n // Tone (mode), intensity, and brief style — accepted from the convene\n // overlay and stored on the room. Out-of-range values fall back to the\n // sane defaults so legacy clients still work.\n const ALLOWED_MODES = new Set([\"brainstorm\", \"constructive\", \"debate\", \"no-mercy\"]);\n const ALLOWED_INTENSITY = new Set([\"calm\", \"sharp\", \"brutal\"]);\n const ALLOWED_STYLES = new Set([\"auto\", \"mckinsey\", \"gartner\", \"a16z\", \"anthropic\", \"8bit\"]);\n // Map prototype short codes to canonical style names.\n const STYLE_ALIAS: Record<string, string> = { mck: \"mckinsey\" };\n\n const rawMode = typeof b.mode === \"string\" ? b.mode.trim() : \"\";\n const mode = ALLOWED_MODES.has(rawMode) ? rawMode : \"constructive\";\n\n const rawIntensity = typeof b.intensity === \"string\" ? b.intensity.trim() : \"\";\n const intensity = ALLOWED_INTENSITY.has(rawIntensity) ? rawIntensity : \"sharp\";\n\n const rawStyle = typeof b.briefStyle === \"string\" ? b.briefStyle.trim() : \"\";\n const styleResolved = STYLE_ALIAS[rawStyle] ?? rawStyle;\n const briefStyle = ALLOWED_STYLES.has(styleResolved) ? styleResolved : \"auto\";\n\n const { room, members } = createRoom({ name, subject, mode, intensity, briefStyle, agentIds });\n\n // Seed the room-opened lifecycle event. For auto-pick rooms the\n // member list will fill in via subsequent `member-added` events\n // once the picker resolves.\n insertConfigEvent({\n roomId: room.id,\n kind: \"room-opened\",\n payload: { mode, intensity, briefStyle, members: members.map((m) => m.agentId), autoPick },\n actorKind: \"user\",\n });\n\n // The convene subject IS the user's opening question — insert it as the\n // first user message. The chair fires a clarification turn FIRST; if\n // the subject is concrete enough the chair returns SKIP and we tick\n // directors immediately. Otherwise the chair asks one question and\n // the directors wait for the user's reply.\n const opening = insertMessage({\n roomId: room.id,\n authorKind: \"user\",\n body: subject,\n roundNum: 1,\n });\n roomBus.emit(room.id, {\n type: \"message-appended\",\n messageId: opening.id,\n authorKind: \"user\",\n authorId: null,\n replyToId: null,\n body: opening.body,\n meta: opening.meta,\n roundNum: opening.roundNum,\n createdAt: opening.createdAt,\n });\n roomBus.emit(room.id, { type: \"message-final\", messageId: opening.id });\n\n // Mark the room as in clarification phase synchronously, before the\n // POST response goes back. The frontend's openRoom fetch will then\n // see awaiting_clarify=true and the queue strip can render its\n // pending preview from the very first paint — without waiting for\n // the chair to start streaming.\n setAwaitingClarify(room.id, true);\n\n // Fire-and-forget — chair clarification streams in the background, then\n // either ticks directors (on READY) or waits for the user's reply.\n // Subsequent user replies route back through the chair (see POST\n // /:id/messages) until the chair signals READY or exhausts the cap.\n //\n // Auto-pick path: before clarify runs, the chair picks the cast\n // (haiku call, ~1s), seats each director with a per-pick SSE\n // event, posts a \"convening\" milestone message, then proceeds to\n // clarify normally.\n void (async () => {\n try {\n if (autoPick) {\n await runAutoPickAndSeat(room.id, subject);\n }\n const result = await runChairClarify(room.id);\n if (result.ready) tickRoom(room.id, { roundNum: 1 });\n } catch (e) {\n process.stderr.write(`[rooms] convene flow failed: ${e instanceof Error ? e.message : String(e)}\\n`);\n // Fallback: still kick the directors so the room isn't stranded.\n setAwaitingClarify(room.id, false);\n tickRoom(room.id, { roundNum: 1 });\n }\n })();\n\n return c.json({ room, members });\n });\n\n // ── State (snapshot)\n r.get(\"/:id\", (c) => {\n const id = c.req.param(\"id\");\n const state = getRoomFullState(id);\n if (!state) return c.json({ error: \"not found\" }, 404);\n const events = listConfigEvents(id);\n const snap = getRoomQueueSnapshot(id);\n return c.json({ ...state, events, queue: snap.queue, round: snap.round });\n });\n\n // ── SSE event stream\n r.get(\"/:id/stream\", (c) => {\n const id = c.req.param(\"id\");\n if (!getRoom(id)) return c.json({ error: \"not found\" }, 404);\n\n // streamSSE sets Content-Type / Cache-Control / Connection automatically\n // and flushes the response on every writeSSE — the prior `stream` helper\n // was buffering until the generator returned, hiding token-by-token output.\n return streamSSE(c, async (s) => {\n // Send a hello so the client knows the channel is live.\n await s.writeSSE({ event: \"hello\", data: JSON.stringify({ roomId: id, ts: Date.now() }) });\n\n const queue: RoomEvent[] = [];\n let resolveWaiter: (() => void) | null = null;\n let closed = false;\n\n const off = roomBus.subscribe(id, (event: RoomEvent) => {\n queue.push(event);\n if (resolveWaiter) {\n resolveWaiter();\n resolveWaiter = null;\n }\n });\n\n s.onAbort(() => {\n closed = true;\n off();\n if (resolveWaiter) {\n resolveWaiter();\n resolveWaiter = null;\n }\n });\n\n // Pump events from the bus to the client one at a time, awaiting each\n // write so back-pressure is honored.\n while (!closed) {\n if (queue.length === 0) {\n // Wait for the next event (or abort).\n await new Promise<void>((resolve) => { resolveWaiter = resolve; });\n continue;\n }\n const event = queue.shift()!;\n await s.writeSSE({ event: event.type, data: JSON.stringify(event) });\n }\n });\n });\n\n // ── User sends a message → triggers orchestrator tick\n r.post(\"/:id/messages\", async (c) => {\n const id = c.req.param(\"id\");\n const room = getRoom(id);\n if (!room) return c.json({ error: \"not found\" }, 404);\n if (room.status !== \"live\") return c.json({ error: \"room is not live\" }, 409);\n\n let body: unknown;\n try { body = await c.req.json(); }\n catch { return c.json({ error: \"invalid JSON body\" }, 400); }\n\n const b = (body ?? {}) as { body?: unknown; replyToId?: unknown; mentions?: unknown; mode?: unknown };\n const text = typeof b.body === \"string\" ? b.body.trim() : \"\";\n if (!text) return c.json({ error: \"body is required\" }, 400);\n\n const replyToId = typeof b.replyToId === \"string\" ? b.replyToId : null;\n const mentions: string[] = Array.isArray(b.mentions)\n ? (b.mentions as unknown[]).filter((x): x is string => typeof x === \"string\")\n : [];\n const mode = b.mode === \"after-speaker\" ? \"after-speaker\" : \"now\";\n\n // ── after-speaker mode · user opted to wait for the current\n // director to finish. If a director is in flight, hand the\n // payload to the orchestrator (which drains it between turns\n // so the user message lands in the correct slot). If no one\n // is in flight, fall through to the normal \"now\" path.\n if (mode === \"after-speaker\" && isRoomSpeaking(id)) {\n setPendingUserAfterCurrent(id, { text, mentions, replyToId });\n return c.json({ deferred: true });\n }\n\n // Each user message opens a new round; directors that respond share it.\n const roundNum = nextUserRoundNum(id);\n\n const msg = insertMessage({\n roomId: id,\n authorKind: \"user\",\n body: text,\n replyToId,\n meta: mentions.length ? { mentions } : {},\n roundNum,\n });\n roomBus.emit(id, {\n type: \"message-appended\",\n messageId: msg.id,\n authorKind: \"user\",\n authorId: null,\n replyToId: msg.replyToId,\n body: msg.body,\n meta: msg.meta,\n roundNum: msg.roundNum,\n createdAt: msg.createdAt,\n });\n roomBus.emit(id, { type: \"message-final\", messageId: msg.id });\n\n // A user reply implicitly resumes the room from any soft-pause the\n // chair set after a round-end. Drop the flag so tickRoom can dispatch,\n // and emit round-resumed so the round-end card flips its CTAs.\n if (room.awaitingContinue) {\n setAwaitingContinue(id, false);\n roomBus.emit(id, {\n type: \"config-event\",\n kind: \"round-resumed\",\n payload: {},\n createdAt: Date.now(),\n });\n }\n\n // If the chair is still in clarification phase, route the reply\n // back through the chair instead of the director queue. The chair\n // either asks one more question (room stays in awaiting_clarify)\n // or signals READY, at which point we release the directors.\n if (room.awaitingClarify) {\n void (async () => {\n try {\n const result = await runChairClarify(id);\n if (result.ready) tickRoom(id, { roundNum, forceSpeakerId: mentions[0] ?? null });\n } catch (e) {\n process.stderr.write(`[rooms] chair clarify failed: ${e instanceof Error ? e.message : String(e)}\\n`);\n tickRoom(id, { roundNum, forceSpeakerId: mentions[0] ?? null });\n }\n })();\n return c.json(msg);\n }\n\n // @chair detection · the user can summon the chair to respond\n // directly, interrupting the director queue. Detected two ways:\n // 1. mentions[] contains the chair's agent id (future-proof for\n // when the frontend grows a real @mention picker).\n // 2. The literal `@chair` keyword appears in the message body\n // (works today without frontend changes — typing @chair just\n // works). Case-insensitive, must be at start or after space\n // so handles like @charles don't false-fire.\n // Forks to chairInterrupt: aborts any in-flight director, runs the\n // chair's direct response, then restores the queue. Skipped\n // entirely during awaitingClarify (handled above — user is already\n // mid-conversation with the chair).\n const chair = getChairAgent();\n const chairMentioned =\n !!chair &&\n (mentions.includes(chair.id) || /(?:^|\\s)@chair\\b/i.test(text));\n if (chairMentioned) {\n // Fire-and-forget · the chair's stream lands via SSE. We return\n // the user message immediately so the frontend's send-blocking\n // state lifts.\n void chairInterrupt(id).catch((e) => {\n process.stderr.write(\n `[rooms] chair-interrupt failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n });\n return c.json(msg);\n }\n\n // First mention takes priority for who should respond; otherwise the\n // entire room speaks once in join-order.\n tickRoom(id, { roundNum, forceSpeakerId: mentions[0] ?? null });\n\n return c.json(msg);\n });\n\n // ── Abort the current in-flight director turn\n r.post(\"/:id/abort\", (c) => {\n const id = c.req.param(\"id\");\n if (!getRoom(id)) return c.json({ error: \"not found\" }, 404);\n abortRoom(id);\n return c.json({ ok: true });\n });\n\n // ── Pause the room — body { mode: \"hard\" | \"soft\" }.\n // hard: abort current director immediately + flip status.\n // soft: wait for current speaker to finish, then orchestrator flips status.\n // If no speaker is in flight, behaves like hard.\n r.post(\"/:id/pause\", async (c) => {\n const id = c.req.param(\"id\");\n const room = getRoom(id);\n if (!room) return c.json({ error: \"not found\" }, 404);\n if (room.status !== \"live\") return c.json({ error: \"room is not live\" }, 409);\n\n let body: unknown = {};\n try { body = await c.req.json(); } catch { /* body optional */ }\n const mode = (body as { mode?: unknown })?.mode === \"soft\" ? \"soft\" : \"hard\";\n\n if (mode === \"soft\" && isRoomSpeaking(id)) {\n // Honored when the speaker's stream finishes; orchestrator emits\n // the room-paused event itself.\n requestSoftPause(id);\n return c.json({ room: getRoom(id), mode: \"soft\", pending: true });\n }\n\n // Hard path (or soft with no speaker → also immediate)\n abortRoom(id);\n const pausedAt = Date.now();\n setRoomStatus(id, \"paused\", { pausedAt });\n\n insertConfigEvent({\n roomId: id,\n kind: \"room-paused\",\n payload: { pausedAt, mode: \"hard\" },\n actorKind: \"user\",\n });\n roomBus.emit(id, {\n type: \"config-event\",\n kind: \"room-paused\",\n payload: { pausedAt, mode: \"hard\" },\n createdAt: pausedAt,\n });\n\n return c.json({ room: getRoom(id), mode: \"hard\" });\n });\n\n // ── Resume the room — flip back to live and pick up the speaker\n // queue exactly where it was paused (the orchestrator stashed a\n // snapshot in abortRoom / soft-pause). If there's no snapshot\n // (e.g. the room was paused with an empty queue), the next user\n // message will replan via tickRoom as before.\n r.post(\"/:id/resume\", (c) => {\n const id = c.req.param(\"id\");\n const room = getRoom(id);\n if (!room) return c.json({ error: \"not found\" }, 404);\n if (room.status !== \"paused\") return c.json({ error: \"room is not paused\" }, 409);\n\n setRoomStatus(id, \"live\", { pausedAt: null });\n\n const ts = Date.now();\n insertConfigEvent({\n roomId: id,\n kind: \"room-resumed\",\n payload: { resumedAt: ts },\n actorKind: \"user\",\n });\n roomBus.emit(id, {\n type: \"config-event\",\n kind: \"room-resumed\",\n payload: { resumedAt: ts },\n createdAt: ts,\n });\n\n // Restore the saved queue + re-engage the speaker pump.\n resumeRoom(id);\n\n return c.json({ room: getRoom(id) });\n });\n\n // ── Update room settings (tone / intensity / report style)\n r.patch(\"/:id\", async (c) => {\n const id = c.req.param(\"id\");\n const room = getRoom(id);\n if (!room) return c.json({ error: \"not found\" }, 404);\n if (room.status === \"adjourned\") return c.json({ error: \"room is adjourned\" }, 409);\n\n let body: unknown;\n try { body = await c.req.json(); }\n catch { return c.json({ error: \"invalid JSON body\" }, 400); }\n\n const b = (body ?? {}) as { mode?: unknown; intensity?: unknown; briefStyle?: unknown; incognito?: unknown };\n\n const ALLOWED_MODES = new Set([\"brainstorm\", \"constructive\", \"debate\", \"no-mercy\"]);\n const ALLOWED_INTENSITY = new Set([\"calm\", \"sharp\", \"brutal\"]);\n const ALLOWED_STYLES = new Set([\"auto\", \"mckinsey\", \"gartner\", \"a16z\", \"anthropic\", \"8bit\"]);\n const STYLE_ALIAS: Record<string, string> = { mck: \"mckinsey\" };\n\n const patch: { mode?: string; intensity?: string; briefStyle?: string } = {};\n let incognitoNext: boolean | null = null;\n\n if (typeof b.mode === \"string\") {\n const m = b.mode.trim();\n if (!ALLOWED_MODES.has(m)) return c.json({ error: `invalid mode: ${m}` }, 400);\n patch.mode = m;\n }\n if (typeof b.intensity === \"string\") {\n const i = b.intensity.trim();\n if (!ALLOWED_INTENSITY.has(i)) return c.json({ error: `invalid intensity: ${i}` }, 400);\n patch.intensity = i;\n }\n if (typeof b.briefStyle === \"string\") {\n const raw = b.briefStyle.trim();\n const resolved = STYLE_ALIAS[raw] ?? raw;\n if (!ALLOWED_STYLES.has(resolved)) return c.json({ error: `invalid briefStyle: ${raw}` }, 400);\n patch.briefStyle = resolved;\n }\n if (typeof b.incognito === \"boolean\") {\n incognitoNext = b.incognito;\n }\n\n if (Object.keys(patch).length === 0 && incognitoNext === null) {\n return c.json({ room });\n }\n\n const updated = Object.keys(patch).length > 0 ? updateRoomSettings(id, patch) : room;\n if (!updated) return c.json({ error: \"update failed\" }, 500);\n if (incognitoNext !== null && incognitoNext !== room.incognito) {\n setRoomIncognito(id, incognitoNext);\n }\n\n // Log + broadcast a config-change event so the chat marker + sidebar\n // can react in-place without a refetch.\n const changes: Record<string, { from: unknown; to: unknown }> = {};\n if (patch.mode !== undefined && patch.mode !== room.mode) {\n changes.mode = { from: room.mode, to: patch.mode };\n }\n if (patch.intensity !== undefined && patch.intensity !== room.intensity) {\n changes.intensity = { from: room.intensity, to: patch.intensity };\n }\n if (patch.briefStyle !== undefined && patch.briefStyle !== room.briefStyle) {\n changes.briefStyle = { from: room.briefStyle, to: patch.briefStyle };\n }\n if (Object.keys(changes).length > 0) {\n const ts = Date.now();\n insertConfigEvent({\n roomId: id,\n kind: \"settings-changed\",\n payload: { changes },\n actorKind: \"user\",\n });\n roomBus.emit(id, {\n type: \"config-event\",\n kind: \"settings-changed\",\n payload: { changes },\n createdAt: ts,\n });\n // Chair announces the change in chat (template-driven, no LLM\n // call) so the timeline reflects the shift inline.\n announceSettingsChange(id, changes);\n }\n\n // Re-fetch when incognito changed so the response reflects the\n // updated flag (updateRoomSettings doesn't touch incognito).\n const final = incognitoNext !== null ? getRoom(id) : updated;\n return c.json({ room: final });\n });\n\n // ── Reconcile room members in one shot. Body: { agentIds: string[] }\n // (the full desired director set, ordered). Server diffs against the\n // current members and applies adds/removes atomically. Side effects:\n // chair posts a join/leave announcement; new directors get appended\n // to the live speaker queue so they speak in this round.\n r.patch(\"/:id/members\", async (c) => {\n const id = c.req.param(\"id\");\n const room = getRoom(id);\n if (!room) return c.json({ error: \"not found\" }, 404);\n if (room.status === \"adjourned\") return c.json({ error: \"room is adjourned\" }, 409);\n\n let body: unknown;\n try { body = await c.req.json(); }\n catch { return c.json({ error: \"invalid JSON body\" }, 400); }\n const b = (body ?? {}) as { agentIds?: unknown };\n if (!Array.isArray(b.agentIds) || !b.agentIds.every((x) => typeof x === \"string\")) {\n return c.json({ error: \"agentIds must be an array of agent ids\" }, 400);\n }\n const desiredIds = (b.agentIds as string[]).filter((x) => x.length > 0);\n\n // Validate every desired id resolves to a director the agents store\n // knows about. Reject otherwise — adding a phantom id would later\n // wedge the speaker queue.\n for (const aid of desiredIds) {\n const a = getAgent(aid);\n if (!a) return c.json({ error: `unknown agent: ${aid}` }, 400);\n if (a.roleKind !== \"director\") {\n return c.json({ error: `agent ${aid} is not a director` }, 400);\n }\n }\n\n // Diff against current room membership (chair excluded — they're at\n // position -1 and not user-managed).\n const currentMembers = listRoomMembers(id);\n const currentIds = new Set(\n currentMembers\n .filter((m) => {\n const a = getAgent(m.agentId);\n return a && a.roleKind === \"director\";\n })\n .map((m) => m.agentId),\n );\n const desiredSet = new Set(desiredIds);\n const added = desiredIds.filter((aid) => !currentIds.has(aid));\n const removed = [...currentIds].filter((aid) => !desiredSet.has(aid));\n\n if (added.length === 0 && removed.length === 0) {\n return c.json({ room, members: currentMembers, added: [], removed: [] });\n }\n\n // Floor: a room must keep at least one director. Refuse a removal\n // that would empty the cast.\n const remainingDirectorCount = currentIds.size - removed.length + added.length;\n if (remainingDirectorCount < 1) {\n return c.json({ error: \"a room must have at least one director\" }, 400);\n }\n\n for (const aid of added) addRoomMember(id, aid);\n for (const aid of removed) removeRoomMember(id, aid);\n\n const ts = Date.now();\n insertConfigEvent({\n roomId: id,\n kind: \"members-changed\",\n payload: { added, removed },\n actorKind: \"user\",\n });\n roomBus.emit(id, {\n type: \"config-event\",\n kind: \"members-changed\",\n payload: { added, removed },\n createdAt: ts,\n });\n\n // Chair posts the welcome / farewell line in chat.\n announceMemberChange(id, added, removed);\n\n // New directors get appended to the live speaker queue and start\n // talking immediately if the pump can run. Removed ones are left\n // to the orchestrator's per-turn `getAgent` resolve to skip.\n if (added.length > 0) {\n injectSpeakers(id, added);\n }\n\n return c.json({\n room: getRoom(id),\n members: listRoomMembers(id),\n added,\n removed,\n });\n });\n\n // ── Trigger the chair to wrap the current round + open a vote.\n // User-driven (the queue strip exposes a \"wrap round\" button).\n // Refuses if the room isn't actively running, if a director is\n // mid-stream, or if we're already paused / clarifying.\n r.post(\"/:id/round-end\", (c) => {\n const id = c.req.param(\"id\");\n const room = getRoom(id);\n if (!room) return c.json({ error: \"not found\" }, 404);\n if (room.status !== \"live\") return c.json({ error: \"room is not live\" }, 409);\n if (room.awaitingClarify) return c.json({ error: \"still in clarification\" }, 409);\n if (room.awaitingContinue) return c.json({ error: \"already in round-end\" }, 409);\n if (isRoomSpeaking(id)) return c.json({ error: \"wait for the current speaker to finish\" }, 409);\n\n // Cancel any queued directors so the round wraps cleanly.\n abortRoom(id);\n\n // Resolve the round number from the latest user message (mirrors\n // what the orchestrator uses).\n const roundNum = Math.max(1, nextUserRoundNum(id) - 1);\n\n // Fire-and-forget — the chair streams its summary + persists key\n // points + flips awaitingContinue when it's done.\n void runChairRoundEnd(id, roundNum).catch((e) => {\n process.stderr.write(`[rooms] round-end failed: ${e instanceof Error ? e.message : String(e)}\\n`);\n });\n\n return c.json({ ok: true });\n });\n\n // ── Continue · kick the next round of directors.\n // Used by:\n // · the round-end card's Continue button (chair just paused)\n // · the queue-strip Continue button + 10s auto-continue (room idle)\n // If the room is currently in a chair-driven round-end pause, this\n // clears it and emits round-resumed; either way it ticks a new round.\n r.post(\"/:id/continue\", (c) => {\n const id = c.req.param(\"id\");\n const room = getRoom(id);\n if (!room) return c.json({ error: \"not found\" }, 404);\n if (room.status !== \"live\") return c.json({ error: \"room is not live\" }, 409);\n if (room.awaitingClarify) return c.json({ error: \"room is in clarification\" }, 409);\n\n if (room.awaitingContinue) {\n setAwaitingContinue(id, false);\n roomBus.emit(id, {\n type: \"config-event\",\n kind: \"round-resumed\",\n payload: {},\n createdAt: Date.now(),\n });\n }\n // Each Continue opens a fresh round number — the chair posts a\n // new round-open marker (\"Round #2 · reactive…\") so the user can\n // see the chronology, and the spent card on the prior round-\n // prompt suppresses cleanly because the next message IS a chair\n // round-open. Without bumping, every Continue would re-use the\n // last user-message's roundNum and the UI would say \"round #1\"\n // forever. `kind: \"continue\"` keeps this a reactive sweep so\n // directors see each other's prior turns.\n const roundNum = nextUserRoundNum(id);\n tickRoom(id, { roundNum, kind: \"continue\" });\n return c.json({ room: getRoom(id) });\n });\n\n // ── Cast a vote on a chair-generated key point.\n // Body: { vote: \"up\" | \"down\" | null }\n r.post(\"/:id/keypoints/:kpId/vote\", async (c) => {\n const id = c.req.param(\"id\");\n const kpId = c.req.param(\"kpId\");\n if (!getRoom(id)) return c.json({ error: \"not found\" }, 404);\n const kp = getKeyPoint(kpId);\n if (!kp || kp.roomId !== id) return c.json({ error: \"key point not found\" }, 404);\n\n let body: unknown = {};\n try { body = await c.req.json(); } catch { /* */ }\n const raw = (body as { vote?: unknown })?.vote;\n let vote: KeyPointVote;\n if (raw === \"up\") vote = \"up\";\n else if (raw === \"down\") vote = \"down\";\n else if (raw === null || raw === undefined || raw === \"\") vote = null;\n else return c.json({ error: \"vote must be 'up' | 'down' | null\" }, 400);\n\n const updated = setKeyPointVote(kpId, vote);\n if (!updated) return c.json({ error: \"vote save failed\" }, 500);\n\n roomBus.emit(id, {\n type: \"config-event\",\n kind: \"key-point-voted\",\n payload: { keyPointId: kpId, vote },\n createdAt: Date.now(),\n });\n return c.json({ keyPoint: updated });\n });\n\n // ── Adjourn the room and kick async brief generation\n r.post(\"/:id/adjourn\", async (c) => {\n const id = c.req.param(\"id\");\n const room = getRoom(id);\n if (!room) return c.json({ error: \"not found\" }, 404);\n if (room.status === \"adjourned\") return c.json({ error: \"already adjourned\" }, 409);\n\n let body: unknown = {};\n try { body = await c.req.json(); } catch { /* allow empty body */ }\n const b = (body ?? {}) as { style?: unknown; skipBrief?: unknown };\n const skipBrief = b.skipBrief === true;\n // Style precedence: explicit body.style > room.briefStyle > \"mckinsey\".\n // \"auto\" means \"let the room pick\" — for v1 that resolves to mckinsey.\n const explicit = typeof b.style === \"string\" && b.style ? b.style : null;\n const fromRoom = room.briefStyle && room.briefStyle !== \"auto\" ? room.briefStyle : null;\n const style = explicit || fromRoom || \"mckinsey\";\n\n // Cancel any in-flight director turn before transitioning state.\n abortRoom(id);\n\n const adjournedAt = Date.now();\n setRoomStatus(id, \"adjourned\", { adjournedAt });\n\n insertConfigEvent({\n roomId: id,\n kind: \"room-adjourned\",\n payload: { style: skipBrief ? null : style, adjournedAt, skipBrief },\n actorKind: \"user\",\n });\n roomBus.emit(id, {\n type: \"config-event\",\n kind: \"room-adjourned\",\n payload: { style: skipBrief ? null : style, adjournedAt, skipBrief },\n createdAt: adjournedAt,\n });\n\n // Long-term memory extraction · runs in parallel for every agent\n // that participated in the room. Fire-and-forget — extraction is\n // best-effort and shouldn't block the adjourn HTTP response.\n // Internally honours room.incognito so an opted-out room writes\n // nothing. Errors are logged per-agent and swallowed.\n extractMemoriesAfterAdjourn(id).catch((e) => {\n process.stderr.write(\n `[adjourn] memory extraction failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n });\n\n // skipBrief · user opted to end the room without filing a report.\n // Skip the LLM call entirely; chair posts a closing marker so the\n // transcript ends with a clear \"no brief filed\" card. The brief\n // endpoint keeps 404'ing (briefs panel handles that as \"no brief\n // filed\") since no row was created.\n if (skipBrief) {\n announceAdjournNoBrief(id);\n return c.json({\n room: getRoom(id),\n briefId: null,\n status: \"skipped\",\n });\n }\n\n let briefId: string | null = null;\n try {\n const result = await generateBrief({ roomId: id, style: style as \"mckinsey\" });\n briefId = result.briefId;\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n return c.json({ error: `brief kickoff failed: ${msg}` }, 500);\n }\n\n return c.json({\n room: getRoom(id),\n briefId,\n status: \"generating\",\n });\n });\n\n // ── Read the latest brief for a room (back-compat — single brief).\n r.get(\"/:id/brief\", (c) => {\n const id = c.req.param(\"id\");\n if (!getRoom(id)) return c.json({ error: \"not found\" }, 404);\n const brief = getBriefByRoom(id);\n if (!brief) return c.json({ error: \"brief not yet generated\" }, 404);\n return c.json(brief);\n });\n\n // ── List ALL briefs for a room · newest first.\n // Multiple briefs accumulate when the user clicks \"Add a perspective\"\n // and regenerates. The frontend uses this to render a tab strip;\n // report.html uses this to navigate between regenerations.\n r.get(\"/:id/briefs\", (c) => {\n const id = c.req.param(\"id\");\n if (!getRoom(id)) return c.json({ error: \"not found\" }, 404);\n return c.json({ briefs: listBriefsForRoom(id) });\n });\n\n // ── Generate a new brief for an adjourned room.\n // Used in two flows:\n // · post-hoc generation when the user adjourned with skipBrief\n // · \"Add a perspective\" regeneration (with `supplement`)\n // Always inserts a NEW brief — older ones are preserved as history.\n r.post(\"/:id/brief\", async (c) => {\n const id = c.req.param(\"id\");\n const room = getRoom(id);\n if (!room) return c.json({ error: \"not found\" }, 404);\n if (room.status !== \"adjourned\") {\n return c.json({ error: \"room is not adjourned\" }, 409);\n }\n let body: unknown = {};\n try { body = await c.req.json(); } catch { /* allow empty body */ }\n const b = (body ?? {}) as { style?: unknown; supplement?: unknown };\n const supplement = typeof b.supplement === \"string\" ? b.supplement.trim() : \"\";\n const explicit = typeof b.style === \"string\" && b.style ? b.style : null;\n const fromRoom = room.briefStyle && room.briefStyle !== \"auto\" ? room.briefStyle : null;\n const style = explicit || fromRoom || \"mckinsey\";\n try {\n const result = await generateBrief({\n roomId: id,\n style: style as \"mckinsey\",\n supplement: supplement || undefined,\n });\n return c.json({ briefId: result.briefId, status: \"generating\" });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n return c.json({ error: `brief kickoff failed: ${msg}` }, 500);\n }\n });\n\n // ── Permanently delete a room (transcript, members, brief, events all cascade)\n r.delete(\"/:id\", (c) => {\n const id = c.req.param(\"id\");\n if (!getRoom(id)) return c.json({ error: \"not found\" }, 404);\n\n // Cancel any in-flight LLM call and clear orchestrator state.\n abortRoom(id);\n // Drop SSE bus listeners — any open EventSource will see the connection\n // close shortly when the client navigates away.\n roomBus.drop(id);\n\n const ok = deleteRoom(id);\n if (!ok) return c.json({ error: \"delete failed\" }, 500);\n return c.json({ ok: true });\n });\n\n return r;\n}\n","/**\n * Web Search system skill · Brave Search API client.\n *\n * Used by the orchestrator (room.ts) before each agent speaks: when\n * the Pass-1 router decides the turn would benefit from fresh web\n * info, we hit Brave for the chosen query, distill the top results\n * into a SHARED MATERIALS block, and prepend it to the agent's\n * Pass-2 system prompt.\n *\n * Failures are non-fatal — a search timeout / 4xx / 5xx degrades to\n * \"no search this turn\", and the agent answers from its own\n * training without the panel ever showing an error to the user.\n */\n\nconst BRAVE_ENDPOINT = \"https://api.search.brave.com/res/v1/web/search\";\nconst DEFAULT_TIMEOUT_MS = 6000;\nconst DEFAULT_RESULT_COUNT = 5;\n\nexport interface BraveResult {\n title: string;\n url: string;\n description: string;\n /** ISO 8601 publication date when Brave surfaces one. */\n age?: string;\n}\n\nexport interface BraveSearchOpts {\n apiKey: string;\n query: string;\n /** Number of results to return (1-10). */\n count?: number;\n /** Country / language hint. ISO codes from Brave docs. */\n country?: string;\n searchLang?: string;\n timeoutMs?: number;\n}\n\n/** Run a Brave Search query. Returns up to `count` results, or null\n * on any error (timeout / 4xx / 5xx / parse failure). The caller is\n * expected to treat null as \"no search this turn\" and proceed. */\nexport async function runBraveSearch(opts: BraveSearchOpts): Promise<BraveResult[] | null> {\n const apiKey = opts.apiKey.trim();\n const query = opts.query.trim();\n if (!apiKey || !query) return null;\n\n const params = new URLSearchParams({\n q: query,\n count: String(Math.min(Math.max(opts.count ?? DEFAULT_RESULT_COUNT, 1), 10)),\n safesearch: \"moderate\",\n });\n if (opts.country) params.set(\"country\", opts.country);\n if (opts.searchLang) params.set(\"search_lang\", opts.searchLang);\n\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n\n try {\n const res = await fetch(`${BRAVE_ENDPOINT}?${params.toString()}`, {\n method: \"GET\",\n headers: {\n Accept: \"application/json\",\n \"X-Subscription-Token\": apiKey,\n },\n signal: ctrl.signal,\n });\n if (!res.ok) {\n process.stderr.write(`[web-search] brave returned ${res.status}\\n`);\n return null;\n }\n const json = await res.json() as { web?: { results?: Array<{\n title?: string;\n url?: string;\n description?: string;\n age?: string;\n }> } };\n const raw = (json && json.web && json.web.results) || [];\n const out: BraveResult[] = [];\n for (const r of raw) {\n const title = (r.title || \"\").trim();\n const url = (r.url || \"\").trim();\n if (!title || !url) continue;\n out.push({\n title,\n url,\n description: stripHtml(r.description || \"\").trim(),\n age: r.age,\n });\n if (out.length >= (opts.count ?? DEFAULT_RESULT_COUNT)) break;\n }\n return out;\n } catch (e) {\n process.stderr.write(\n `[web-search] brave fetch failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n return null;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/** Brave returns descriptions with `<strong>` highlighting. Keep them\n * as plain text for prompt injection — strip tags + decode the few\n * entity references that show up. */\nfunction stripHtml(s: string): string {\n return s\n .replace(/<\\/?[a-z][^>]*>/gi, \"\")\n .replace(/ /g, \" \")\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/\\s+/g, \" \");\n}\n\n/** Distill search results into the SHARED MATERIALS block injected\n * into the agent's Pass-2 system prompt. The format mirrors the\n * fetch-url skill so agents already know how to cite this. */\nexport function formatSearchResults(query: string, results: BraveResult[]): string {\n if (!results.length) return \"\";\n const lines: string[] = [];\n lines.push(\"─── SHARED MATERIALS · WEB SEARCH ───\");\n lines.push(\"\");\n lines.push(`Query: ${query}`);\n lines.push(\"\");\n lines.push(\n \"Cite sources by their bracketed number when you use a fact below \" +\n \"(e.g. \\\"...as reported in [2]\\\"). If a source contradicts your \" +\n \"training, prefer the search result — it's more recent.\",\n );\n lines.push(\"\");\n results.forEach((r, i) => {\n const n = i + 1;\n const age = r.age ? ` · ${r.age}` : \"\";\n lines.push(`[${n}] ${r.title}${age}`);\n lines.push(` ${r.url}`);\n if (r.description) {\n const desc = r.description.length > 320\n ? r.description.slice(0, 317) + \"…\"\n : r.description;\n lines.push(` ${desc}`);\n }\n lines.push(\"\");\n });\n lines.push(\"─── END SHARED MATERIALS ───\");\n return lines.join(\"\\n\");\n}\n","/**\n * Key points generated by the Chair at the end of each round. The user\n * votes 👍 / 👎 on each one; voted points feed back into the next\n * director system prompt as user-interest signals.\n */\nimport { newId } from \"../utils/id.js\";\n\nimport { getDb } from \"./db.js\";\n\nexport type KeyPointVote = \"up\" | \"down\" | null;\n\nexport interface KeyPoint {\n id: string;\n roomId: string;\n messageId: string | null;\n roundNum: number;\n body: string;\n vote: KeyPointVote;\n position: number;\n createdAt: number;\n votedAt: number | null;\n}\n\ninterface Row {\n id: string;\n room_id: string;\n message_id: string | null;\n round_num: number;\n body: string;\n vote: string | null;\n position: number;\n created_at: number;\n voted_at: number | null;\n}\n\nconst COLS =\n \"id, room_id, message_id, round_num, body, vote, position, created_at, voted_at\";\n\nfunction mapRow(r: Row): KeyPoint {\n return {\n id: r.id,\n roomId: r.room_id,\n messageId: r.message_id,\n roundNum: r.round_num,\n body: r.body,\n vote: r.vote === \"up\" ? \"up\" : r.vote === \"down\" ? \"down\" : null,\n position: r.position,\n createdAt: r.created_at,\n votedAt: r.voted_at,\n };\n}\n\nexport interface InsertKeyPoint {\n roomId: string;\n messageId: string | null;\n roundNum: number;\n body: string;\n position: number;\n}\n\nexport function insertKeyPoint(p: InsertKeyPoint): KeyPoint {\n const id = \"kp_\" + newId();\n const now = Date.now();\n getDb()\n .prepare(\n `INSERT INTO key_points (id, room_id, message_id, round_num, body, vote, position, created_at, voted_at)\n VALUES (?, ?, ?, ?, ?, NULL, ?, ?, NULL)`,\n )\n .run(id, p.roomId, p.messageId, p.roundNum, p.body, p.position, now);\n return getKeyPoint(id)!;\n}\n\nexport function getKeyPoint(id: string): KeyPoint | null {\n const r = getDb().prepare(`SELECT ${COLS} FROM key_points WHERE id = ?`).get(id) as Row | undefined;\n return r ? mapRow(r) : null;\n}\n\nexport function listKeyPointsForRoom(roomId: string): KeyPoint[] {\n const rows = getDb()\n .prepare(`SELECT ${COLS} FROM key_points WHERE room_id = ? ORDER BY round_num ASC, position ASC`)\n .all(roomId) as Row[];\n return rows.map(mapRow);\n}\n\n/** Replace the user's vote on a single key point. Returns the updated row. */\nexport function setKeyPointVote(id: string, vote: KeyPointVote): KeyPoint | null {\n const now = Date.now();\n getDb()\n .prepare(`UPDATE key_points SET vote = ?, voted_at = ? WHERE id = ?`)\n .run(vote, vote === null ? null : now, id);\n return getKeyPoint(id);\n}\n","/**\n * Skill router · Pass-1 of the two-pass turn.\n *\n * Given a speaker's installed skills + the recent conversation, ask a\n * cheap LLM to pick which skills (if any) apply this turn. Output is\n * strict JSON: { use: [\"slug\", ...], reason: \"...\" }.\n *\n * Design notes\n * - Picker is best-effort. If the LLM call fails, returns no picks\n * (caller falls through to a normal single-pass turn).\n * - We hard-cap to 2 picks so the router can't bloat Pass-2 by\n * selecting every available skill.\n * - The cheap model is fixed to `haiku-4-5`; if that key isn't\n * configured, falls back to the speaker's own model so picking\n * still works (just costs a bit more).\n */\nimport { callLLM, NoKeyError, type LLMMessage } from \"../ai/adapter.js\";\nimport type { Agent } from \"../storage/agents.js\";\nimport type { Message } from \"../storage/messages.js\";\nimport type { AgentSkill } from \"../storage/skills.js\";\nimport type { ModelV } from \"../ai/registry.js\";\nimport { activeCarrier } from \"../storage/reconcile-models.js\";\nimport { WEB_SEARCH_SLUG } from \"../skills/system-skills.js\";\n\n/** Cheap-tier model for each carrier · the model the picker uses for\n * routing decisions (clarify gate / web-search / next-speaker /\n * round-wrap / skill match). Mirrors the carrier-priority table in\n * reconcile-models.ts; kept colocated with the picker since this is\n * the only place that distinguishes \"cheap tier per carrier\" from\n * \"primary tier per carrier\". */\nconst CHEAP_BY_CARRIER: Record<string, ModelV> = {\n openrouter: \"haiku-4-5\",\n anthropic: \"sonnet-4-6\", // only direct-routable Claude\n openai: \"gpt-5-4-mini\",\n google: \"gemini-3-1-flash\", // 3.1 Flash Lite · cheapest direct-routable Gemini\n xai: \"grok-4-1-fast\", // 4.1 Fast · cheapest direct-routable Grok\n};\n\n/** Pick the cheapest reachable model for routing decisions. Follows\n * the user's *active carrier* (the carrier of `prefs.defaultModelV`\n * when it's reachable, otherwise the first reachable in priority\n * order) — same resolution rule used by reconcile + chair primary.\n *\n * Why activeCarrier rather than a hardcoded order over `getKey()`:\n * if the user configured BOTH OpenAI and Google but switched their\n * default to Gemini, the picker should also use Gemini's cheap tier\n * (`gemini-3-1-flash`) — not silently keep firing OpenAI calls just\n * because OpenAI key is still configured. Earlier versions did the\n * latter and it surfaced as \"I switched to Gemini but every picker\n * call still bills OpenAI\". */\nfunction pickRouterModel(): ModelV | null {\n const carrier = activeCarrier();\n if (carrier && CHEAP_BY_CARRIER[carrier]) return CHEAP_BY_CARRIER[carrier];\n return null;\n}\n\nconst MAX_PICKS = 2;\n\nexport interface SkillPickResult {\n used: AgentSkill[];\n reason: string;\n /** When the speaker has web-search available (key + per-agent flag),\n * the router can return a search query to run before this turn.\n * null means \"no search needed for this turn\" — a useful 0-cost\n * decision that prevents Brave calls on philosophical questions. */\n webSearchQuery: string | null;\n}\n\n/** Chair-side clarify gate · cheap haiku call that decides whether the\n * chair needs to ask a clarifying question on the FIRST turn after a\n * room opens, or whether the user's subject is already self-sufficient\n * enough to release directors immediately.\n *\n * This is the discipline lever for the chair's clarify primitive — same\n * shape as the web-search router (yes/no + rationale), so we don't burn\n * a full chair LLM call on subjects that are already clear enough to\n * open. Returns shouldAsk=false to short-circuit to READY, true to fall\n * through to the structured chair clarify prompt as before.\n *\n * Failures default to shouldAsk=true (safe path — keep existing\n * behaviour rather than silently skipping clarification). */\nexport interface ChairClarifyDecision {\n /** True = run the chair's structured clarify prompt as today.\n * False = the subject is self-sufficient; emit READY without an LLM\n * round-trip and release directors. */\n shouldAsk: boolean;\n /** ≤120 chars — why this call. Logged + surfaced in chair turn meta\n * for telemetry. Empty string when the call fails. */\n rationale: string;\n}\n\nexport async function pickChairClarifyDecision(opts: {\n history: Message[];\n signal?: AbortSignal;\n}): Promise<ChairClarifyDecision> {\n const prompt = latestUserPrompt(opts.history);\n // No user prompt yet → don't gate; the clarify call won't fire anyway.\n if (!prompt) return { shouldAsk: true, rationale: \"no user prompt yet\" };\n\n const sys: LLMMessage = {\n role: \"system\",\n content: [\n \"You are the boardroom chair's clarify gate. You make ONE cheap binary\",\n \"decision: should the chair ask a clarifying question before releasing\",\n \"directors, or is the user's subject already self-sufficient?\",\n \"\",\n \"RELEASE (ask=false) when the subject already names:\",\n \" · the concrete situation,\",\n \" · the actual decision being wrestled with, AND\",\n \" · at least one real constraint or stake.\",\n \"\",\n \"ASK (ask=true) only when a load-bearing piece is genuinely missing —\",\n \"the kind of ambiguity that would make 3 directors pull in different\",\n \"directions. Examples: 'help me decide' with no decision named, a\",\n \"topic so abstract no concrete situation grounds it, a question with\",\n \"two incompatible interpretations.\",\n \"\",\n \"Bias toward RELEASE. A slightly-fuzzy framing is fine — directors\",\n \"can sharpen it themselves. Asking when you don't need to kills\",\n \"momentum.\",\n \"\",\n \"Reply with STRICT JSON ONLY (no prose, no fences):\",\n \"{ \\\"ask\\\": true, \\\"rationale\\\": \\\"≤120 chars · what's load-bearingly missing\\\" }\",\n \"{ \\\"ask\\\": false, \\\"rationale\\\": \\\"≤120 chars · why the subject is self-sufficient\\\" }\",\n ].join(\"\\n\"),\n };\n\n const userMsg: LLMMessage = {\n role: \"user\",\n content: `Latest user message (the room subject):\\n${prompt}\\n\\nDoes the chair need to ask a clarifying question before opening the room?`,\n };\n\n const routerModel = pickRouterModel();\n if (!routerModel) {\n // No key configured for any router-eligible carrier · keep the\n // existing chair clarify call running (safe path).\n return { shouldAsk: true, rationale: \"\" };\n }\n let raw = \"\";\n try {\n raw = await callLLM({\n modelV: routerModel,\n messages: [sys, userMsg],\n temperature: 0,\n maxTokens: 120,\n signal: opts.signal,\n });\n } catch (e) {\n if (!(e instanceof NoKeyError)) {\n process.stderr.write(\n `[chair-clarify-gate] failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n }\n // Default to the safe path on failure — keep the existing chair\n // clarify call running rather than silently skipping clarification.\n return { shouldAsk: true, rationale: \"\" };\n }\n\n const parsed = extractJson(raw);\n if (!parsed || typeof parsed !== \"object\") {\n return { shouldAsk: true, rationale: \"\" };\n }\n const obj = parsed as { ask?: unknown; rationale?: unknown };\n const ask = obj.ask !== false; // truthy coercion · only literal `false` flips to skip\n const rationale = typeof obj.rationale === \"string\" ? obj.rationale.trim().slice(0, 200) : \"\";\n return { shouldAsk: ask, rationale };\n}\n\n/** Chair-side round-wrap picker · cheap haiku call at round boundaries\n * that recommends End-the-round vs Continue, based on whether the\n * round has covered the load-bearing tensions or there's still\n * productive ground to cover with another reactive sweep.\n *\n * This is the chair's Synthesis primitive — instead of auto-ending\n * (which takes control away from the user) we surface a recommendation\n * on the round-prompt the user is about to act on. User still picks\n * End or Continue; the chair just nudges with rationale.\n *\n * Calibration: bias-to-continue early (rounds 1–2 usually need more\n * reactive turns), bias-to-end after round 3+ (diminishing returns\n * unless dialogue is still actively diverging). Failure → \"continue\"\n * default — never accidentally push the user toward ending. */\nexport type RoundWrapRecommendation = \"end\" | \"continue\";\nexport interface RoundWrapDecision {\n recommendation: RoundWrapRecommendation;\n /** ≤200 chars · why this call. Surfaced in the round-prompt body so\n * the user reads the chair's reasoning before pressing the button. */\n rationale: string;\n}\n\nexport async function pickRoundWrap(opts: {\n history: Message[];\n roundNum: number;\n signal?: AbortSignal;\n}): Promise<RoundWrapDecision> {\n const { history, roundNum, signal } = opts;\n\n const transcript = history\n .slice(-20)\n .filter((m) => {\n if (!m.body || !m.body.trim()) return false;\n const meta = m.meta as { kind?: string } | undefined;\n if (meta?.kind === \"tool-use\" || meta?.kind === \"tool-preamble\") return false;\n if (meta?.kind === \"round-open\" || meta?.kind === \"round-prompt\") return false;\n return true;\n })\n .map((m) => {\n const who = m.authorKind === \"user\" ? \"USER\" : (m.authorId || \"agent\");\n return `[${who}] ${m.body.trim().slice(0, 600)}`;\n })\n .join(\"\\n\\n\");\n\n const sys: LLMMessage = {\n role: \"system\",\n content: [\n \"You are the boardroom chair's round-wrap evaluator. A reactive\",\n \"round just finished; the user is about to choose End-round (file\",\n \"key points + maybe adjourn to a brief) or Continue (another\",\n \"reactive sweep). You make ONE recommendation.\",\n \"\",\n \"RECOMMEND END when:\",\n \" · The load-bearing tensions are surfaced and named.\",\n \" · Directors have stopped adding new lenses (the next round would\",\n \" repeat patterns already in the transcript).\",\n \" · Round number is high (3+) and dialogue feels structurally complete.\",\n \"\",\n \"RECOMMEND CONTINUE when:\",\n \" · A specific tension was named but not actually pushed on yet.\",\n \" · Directors are mid-disagreement on a load-bearing claim.\",\n \" · It's round 1 or 2 and the divergence is still genuine.\",\n \" · A director said something that demands a counter-argument no\",\n \" one has yet provided.\",\n \"\",\n \"Calibration: be conservative. Pushing the user toward End when\",\n \"there's still substantive ground to cover is worse than letting\",\n \"them run one more round. When in doubt, recommend CONTINUE.\",\n \"\",\n \"Reply with STRICT JSON ONLY (no prose, no fences):\",\n \"{ \\\"recommendation\\\": \\\"end\\\" | \\\"continue\\\", \\\"rationale\\\": \\\"≤200 chars · the load-bearing reason\\\" }\",\n ].join(\"\\n\"),\n };\n\n const userMsg: LLMMessage = {\n role: \"user\",\n content: [\n `Round just finished: ${roundNum}`,\n ``,\n `Transcript:`,\n transcript || \"(empty — should not happen at round wrap)\",\n ``,\n `Recommend End or Continue.`,\n ].join(\"\\n\"),\n };\n\n const routerModel = pickRouterModel();\n if (!routerModel) {\n return { recommendation: \"continue\", rationale: \"\" };\n }\n let raw = \"\";\n try {\n raw = await callLLM({\n modelV: routerModel,\n messages: [sys, userMsg],\n temperature: 0,\n maxTokens: 200,\n signal,\n });\n } catch (e) {\n if (!(e instanceof NoKeyError)) {\n process.stderr.write(\n `[round-wrap] failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n }\n // Failure default: continue. We never accidentally push the user\n // toward ending — that's a destructive action.\n return { recommendation: \"continue\", rationale: \"\" };\n }\n\n const parsed = extractJson(raw);\n if (!parsed || typeof parsed !== \"object\") {\n return { recommendation: \"continue\", rationale: \"\" };\n }\n const obj = parsed as { recommendation?: unknown; rationale?: unknown };\n const rec: RoundWrapRecommendation = obj.recommendation === \"end\" ? \"end\" : \"continue\";\n const rationale = typeof obj.rationale === \"string\" ? obj.rationale.trim().slice(0, 240) : \"\";\n return { recommendation: rec, rationale };\n}\n\n/** Chair-side next-speaker picker · cheap haiku call that picks WHICH\n * queued director should speak next in a reactive round, based on\n * whose lens most sharply addresses the unresolved tension in the\n * previous turn. Only fires when the queue has ≥ 2 candidates and at\n * least one director has already spoken in this round (so there's a\n * prior turn to react to).\n *\n * Falls back to round-robin (returns agentId=null) when:\n * · the call fails / no key,\n * · the model returns an unrecognised agentId,\n * · the response can't be parsed.\n *\n * This is the discipline lever for the chair's next-speaker primitive\n * — every reactive turn after the first becomes a deliberate moderator\n * decision rather than mechanical round-robin order. Cost: ~$0.001\n * per pick, only on reactive rounds with multiple remaining speakers. */\nexport interface NextSpeakerPick {\n /** agentId chosen from the candidates, or null to leave queue order\n * unchanged (round-robin fallback). */\n agentId: string | null;\n /** ≤120 chars · why this lens fits the next move. Logged for telemetry\n * + surfaced in the speaker turn's meta so the UI can show \"Chair\n * picked X because Y\" on hover. */\n rationale: string;\n /** Optional frame-correction note · 1-2 sentences. Set when the picker\n * detects a substantive misalignment in the prior turns (talking past\n * each other, undefined load-bearing term, circling without progress,\n * hidden trade-off). Bias is to skip — null is the common case.\n * When present, the orchestrator posts it as a chair message before\n * the picked director speaks, named \"intervention\" in meta. */\n intervention: string | null;\n}\n\nexport async function pickNextSpeaker(opts: {\n candidates: Agent[];\n history: Message[];\n signal?: AbortSignal;\n}): Promise<NextSpeakerPick> {\n const { candidates, history, signal } = opts;\n if (candidates.length < 2) return { agentId: null, rationale: \"\", intervention: null };\n\n // Build the candidate roster. We include role tag + bio so the picker\n // can match lens to the previous turn's gap, not just remember names.\n const roster = candidates\n .map((a) => `- ${a.id} · ${a.name} (${a.handle}) · ${a.roleTag}\\n ${a.bio}`)\n .join(\"\\n\");\n\n // Recent transcript · last ~10 messages with handle + body. Enough\n // context to see the dialogue's current direction without bloating\n // the haiku prompt. Skip system messages and tool-use rows; keep\n // user, chair, and director turns.\n const transcript = history\n .slice(-12)\n .filter((m) => {\n if (!m.body || !m.body.trim()) return false;\n const meta = m.meta as { kind?: string } | undefined;\n // Skip tool-use UI rows + chair structural pings.\n if (meta?.kind === \"tool-use\" || meta?.kind === \"tool-preamble\") return false;\n if (meta?.kind === \"round-open\" || meta?.kind === \"round-prompt\") return false;\n return true;\n })\n .map((m) => {\n const who = m.authorKind === \"user\" ? \"USER\" : (m.authorId || \"agent\");\n return `[${who}] ${m.body.trim().slice(0, 600)}`;\n })\n .join(\"\\n\\n\");\n\n const sys: LLMMessage = {\n role: \"system\",\n content: [\n \"You are the boardroom chair's pre-turn moderator. The room is in\",\n \"a reactive round; one director just finished. You make TWO\",\n \"decisions in one pass.\",\n \"\",\n \"DECISION 1 · Next speaker. From the candidates below, pick which\",\n \"director should speak NEXT — the one whose lens most sharply\",\n \"addresses the unresolved tension, hidden assumption, or missing\",\n \"counter-argument in the previous turn.\",\n \" · Match LENS to the gap, not just topic relevance. If the prior\",\n \" turn made a structural claim, pick a director whose role\",\n \" pushes back from a different lens (data → narrative,\",\n \" empirical → first-principles, etc.).\",\n \" · Prefer directors who haven't been quoted yet THIS round when\",\n \" fits are comparable — diversity of voice.\",\n \" · If no candidate clearly fits better than the current head of\",\n \" queue, set agent_id=null and let round-robin run.\",\n \"\",\n \"DECISION 2 · Intervention (optional · default: null). Read the\",\n \"prior 2–3 turns. Drop a 1-sentence chair note ONLY if a substantive\",\n \"misalignment is making the room less productive — and only one of\",\n \"these patterns:\",\n \" · Talking past each other · two directors are using the same\",\n \" word for different things (e.g. one says 'moat' meaning data,\",\n \" the other meaning licenses — neither has named the difference).\",\n \" · Undefined load-bearing term · a key claim hinges on a word\",\n \" nobody has defined (e.g. 'engagement', 'AI-native').\",\n \" · Hidden trade-off · two directors agree on the surface but are\",\n \" silently making opposing assumptions about cost/timing/scale.\",\n \" · Circling · 2+ turns repeating without advancing.\",\n \"Otherwise leave intervention=null. Bias HEAVILY to skip. False\",\n \"interventions feel preachy. The room's voice is the directors',\",\n \"not yours. Most reactive turns get no intervention.\",\n \"\",\n \"If you DO intervene: 1 sentence, neutral moderator voice, name\",\n \"the SPECIFIC pattern + the load-bearing piece worth pinning down.\",\n \"Match the user's language (Chinese subject → Chinese; English →\",\n \"English). No greeting, no signature.\",\n \"\",\n \"Reply with STRICT JSON ONLY (no prose, no fences):\",\n \"{\",\n \" \\\"agent_id\\\": \\\"<exact id from roster>\\\" | null,\",\n \" \\\"rationale\\\": \\\"≤120 chars · why this lens fits next\\\",\",\n \" \\\"intervention\\\": \\\"≤200 chars · the one-sentence note\\\" | null\",\n \"}\",\n ].join(\"\\n\"),\n };\n\n const userMsg: LLMMessage = {\n role: \"user\",\n content: [\n `Candidates (queued, in current order):`,\n roster,\n ``,\n `Recent transcript:`,\n transcript || \"(no prior turns yet — should not happen for next-speaker pick)\",\n ``,\n `Pick the next speaker, and decide whether an intervention is warranted.`,\n ].join(\"\\n\"),\n };\n\n const routerModel = pickRouterModel();\n if (!routerModel) return { agentId: null, rationale: \"\", intervention: null };\n let raw = \"\";\n try {\n raw = await callLLM({\n modelV: routerModel,\n messages: [sys, userMsg],\n temperature: 0,\n // Bumped from 160 to 320 · response now carries optional 1-sentence\n // intervention text in addition to the pick + rationale.\n maxTokens: 320,\n signal,\n });\n } catch (e) {\n if (!(e instanceof NoKeyError)) {\n process.stderr.write(\n `[next-speaker] failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n }\n return { agentId: null, rationale: \"\", intervention: null };\n }\n\n const parsed = extractJson(raw);\n if (!parsed || typeof parsed !== \"object\") {\n return { agentId: null, rationale: \"\", intervention: null };\n }\n const obj = parsed as { agent_id?: unknown; rationale?: unknown; intervention?: unknown };\n const validIds = new Set(candidates.map((c) => c.id));\n const id = typeof obj.agent_id === \"string\" && validIds.has(obj.agent_id)\n ? obj.agent_id\n : null;\n const rationale = typeof obj.rationale === \"string\" ? obj.rationale.trim().slice(0, 200) : \"\";\n // Intervention · accept only meaningful strings; trim, cap length,\n // discard empty / \"null\" / \"none\" so the prompt's \"default null\" path\n // works even when the model emits a string literal instead of JSON null.\n let intervention: string | null = null;\n if (typeof obj.intervention === \"string\") {\n const t = obj.intervention.trim();\n if (t.length > 0 && t.toLowerCase() !== \"null\" && t.toLowerCase() !== \"none\") {\n intervention = t.slice(0, 280);\n }\n }\n return { agentId: id, rationale, intervention };\n}\n\n/** Pre-stream chair-side router · cheap haiku call that decides\n * whether the chair should run a web search before its next reply.\n * The chair has no installed-skill toolbox (its tool repertoire is\n * hard-wired: fetch-url, web-search, etc.), so we don't need the full\n * skill picker — just the search-or-not branch. Returns a query\n * string when the latest user message would benefit from fresh web\n * results, null otherwise.\n *\n * Best-effort · failures → null (chair proceeds without search).\n * Conservative by default · the prompt asks the model to skip search\n * on philosophical / first-principles / pure-reasoning questions so\n * Brave queries don't burn on every chair turn. */\nexport async function pickChairWebSearch(opts: {\n history: Message[];\n signal?: AbortSignal;\n}): Promise<string | null> {\n const prompt = latestUserPrompt(opts.history);\n if (!prompt) return null;\n\n const sys: LLMMessage = {\n role: \"system\",\n content: [\n \"You are the boardroom chair's pre-turn search router. You decide ONE thing:\",\n \"should the chair run a Brave web search before its next reply, and if so what query.\",\n \"\",\n \"STRONG-SEARCH signals (pick one of these → ALWAYS issue a query):\",\n \"- Time markers: 'today' / 'this week' / 'this month' / 'recent' / 'recently' /\",\n \" 'latest' / 'just' / 'now' / '今天' / '最近' / '本周' / '本月' / '刚刚' /\",\n \" '现在' / '目前'.\",\n \"- Verbs of recent action: 'released' / 'launched' / 'announced' / 'shipped' /\",\n \" 'reported' / 'filed' / '发布' / '推出' / '宣布' / '上线' / '披露'.\",\n \"- Specific named events / products / numbers / prices that the model can't\",\n \" reliably know from training (CES 2025, model launches, IPO numbers, ARR figures,\",\n \" raise sizes, market caps).\",\n \"- A specific person's PUBLIC statements / posts (X/Twitter, blog, interviews)\",\n \" asked about by name.\",\n \"- Explicit search verbs: 'search', 'look up', '查一下', '搜索', '搜一下'.\",\n \"- The user META-INSTRUCTS the chair to search ('go search this' / '你去搜一下' /\",\n \" '用 web search 看看' / '涉及实事,搜索一下') — even when no time marker is\",\n \" in the literal sentence. Pull the topic from the recent transcript: chair's\",\n \" prior question, the room subject, or the original user message. If you can't\",\n \" pull a coherent topic, return the room subject's keywords as the fallback.\",\n \"- A source URL is shared alongside the question (search complements fetch-url).\",\n \"\",\n \"WEAK-SEARCH signals (search is OPTIONAL, lean toward yes if the question\",\n \"claims a fact you'd want to verify):\",\n \"- Statistics / market sizing / industry numbers stated as fact.\",\n \"- 'Will X happen?' style forecasts that hinge on current trajectory.\",\n \"\",\n \"DON'T search WHEN:\",\n \"- The question is philosophical, first-principles, or about stable general\",\n \" knowledge that doesn't change.\",\n \"- It's a pure reasoning / brainstorm / planning task with no time anchor.\",\n \"\",\n \"Reply with STRICT JSON ONLY (no prose, no fences):\",\n \"{ \\\"query\\\": \\\"3-8 keywords\\\" } // when search is warranted\",\n \"{ \\\"query\\\": null } // otherwise\",\n \"\",\n \"Query rules:\",\n \"- 3-8 keywords, no full sentences. Plain ASCII works best for Brave.\",\n \"- Match the language of the question when possible (English keywords for\",\n \" global topics; CJK keywords if the question is China-specific).\",\n \"- For time-sensitive questions, INCLUDE the time marker in the query when\",\n \" it sharpens results (e.g. '2025', 'this week', '最近').\",\n \"- When the user is META-instructing search, BUILD the query from the actual\",\n \" TOPIC in the transcript, NOT from the user's directive verbs. For example,\",\n \" if the user said '搜一下' and the chair previously asked about 'AI moats',\",\n \" the query should be `AI moats 2025` — never `搜索 AI 护城河 用户` or similar.\",\n \"- DEFAULT WHEN UNCERTAIN: if any STRONG-SEARCH signal is present, ALWAYS\",\n \" return a query — never null. Skipping a clearly time-sensitive question\",\n \" is worse than burning one Brave call.\",\n ].join(\"\\n\"),\n };\n\n // Build a short transcript window so the picker can pull the actual\n // discussion topic when the user's latest message is just a meta-\n // instruction (\"go search\", \"查一下\"). Last ~6 substantive messages,\n // tagged with role so the picker sees what the chair just asked\n // about / what the room subject is.\n const transcript = opts.history\n .slice(-8)\n .filter((m) => m.body && m.body.trim())\n .map((m) => {\n if (m.authorKind === \"user\") return `[USER] ${m.body.trim().slice(0, 400)}`;\n const meta = m.meta as { kind?: string } | undefined;\n if (meta?.kind === \"tool-use\" || meta?.kind === \"tool-preamble\") return null;\n return `[CHAIR] ${m.body.trim().slice(0, 400)}`;\n })\n .filter((s): s is string => s !== null)\n .join(\"\\n\\n\");\n\n const userMsg: LLMMessage = {\n role: \"user\",\n content: [\n `Recent transcript (most recent at the bottom):`,\n transcript || \"(no transcript yet)\",\n ``,\n `Latest user message (your decision keys off this, but use the transcript for query keywords when needed):`,\n prompt,\n ``,\n `Should the chair search the web before replying?`,\n ].join(\"\\n\"),\n };\n\n const routerModel = pickRouterModel();\n if (!routerModel) return null;\n let raw = \"\";\n try {\n raw = await callLLM({\n modelV: routerModel,\n messages: [sys, userMsg],\n temperature: 0,\n maxTokens: 100,\n signal: opts.signal,\n });\n } catch (e) {\n if (!(e instanceof NoKeyError)) {\n process.stderr.write(\n `[chair-search-picker] failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n }\n return null;\n }\n\n const parsed = extractJson(raw);\n if (!parsed || typeof parsed !== \"object\") return null;\n const ws = parsed as { query?: unknown };\n if (typeof ws.query !== \"string\") return null;\n const q = ws.query.trim().slice(0, 200);\n return q.length > 0 ? q : null;\n}\n\n/** Return the most recent USER turn's body (the question that triggered\n * this round), falling back to the latest message if there's no user\n * turn in scope. The picker doesn't need full transcript context — it's\n * routing on the question, not the discussion. */\nfunction latestUserPrompt(history: Message[]): string {\n for (let i = history.length - 1; i >= 0; i--) {\n const m = history[i];\n if (m.authorKind === \"user\" && m.body && m.body.trim()) return m.body.trim();\n }\n // Fall back to the last meaningful message body.\n for (let i = history.length - 1; i >= 0; i--) {\n const m = history[i];\n if (m.body && m.body.trim()) return m.body.trim();\n }\n return \"\";\n}\n\n/** Layer 1 of progressive skill disclosure · the metadata-only index\n * every router sees. One line per skill — name, description, when_to_use.\n * The full bodyMd is intentionally NOT here; routers decide on metadata\n * alone (Claude Code-style). When a router picks a skill, the caller\n * loads its body via `loadSkillBody` and injects it into the main pass.\n * Exported so future chair-side or third-party routers reuse the same\n * shape — keeps the discipline consistent across the harness. */\nexport function buildSkillsIndex(skills: AgentSkill[]): string {\n return skills\n .map((s) => `- ${s.slug} · \"${s.name}\" · ${s.description}\\n USE WHEN: ${s.whenToUse}`)\n .join(\"\\n\");\n}\n\n/** Layer 2 of progressive skill disclosure · the full body of a skill,\n * loaded only when a router has selected it. Currently a thin field\n * read; declared as a discrete function so callers route through one\n * documented seam. Future evolution (e.g. lazy on-disk loading,\n * per-skill caching, body redaction) lands here without touching\n * callers. */\nexport function loadSkillBody(skill: AgentSkill): string {\n return skill.bodyMd;\n}\n\n/** Tolerant JSON extractor — strips ``` fences and tries straight parse,\n * then a slice between the first { and last } if the response wrapped\n * the JSON in prose. Returns null on failure. */\nfunction extractJson(text: string): unknown | null {\n if (!text) return null;\n let s = text.trim();\n // Strip code fences.\n s = s.replace(/^```(?:json)?\\s*/i, \"\").replace(/```\\s*$/i, \"\").trim();\n try { return JSON.parse(s); } catch { /* fall through */ }\n const open = s.indexOf(\"{\");\n const close = s.lastIndexOf(\"}\");\n if (open >= 0 && close > open) {\n try { return JSON.parse(s.slice(open, close + 1)); } catch { return null; }\n }\n return null;\n}\n\nexport async function pickSkills(opts: {\n speaker: Agent;\n skills: AgentSkill[];\n history: Message[];\n /** True when the user has a Brave Search key AND this speaker has\n * web-search enabled. When set, the router prompt also asks for\n * an optional web-search query — same haiku call, no extra cost. */\n webSearchAvailable?: boolean;\n signal?: AbortSignal;\n}): Promise<SkillPickResult> {\n const { speaker, skills, history, signal } = opts;\n const webSearchAvailable = opts.webSearchAvailable ?? false;\n // Web-search isn't a body-injection skill — it's an external action\n // decided in the same router call but executed separately. Keep it\n // out of the toolbox index so the LLM doesn't try to \"apply\" it as\n // method.\n const toolboxSkills = skills.filter((s) => s.slug !== WEB_SEARCH_SLUG);\n\n if (toolboxSkills.length === 0 && !webSearchAvailable) {\n return { used: [], reason: \"\", webSearchQuery: null };\n }\n\n const prompt = latestUserPrompt(history);\n if (!prompt) return { used: [], reason: \"\", webSearchQuery: null };\n\n const baseLines = [\n `You are ${speaker.name}'s pre-turn router. You make two cheap decisions before ${speaker.name} answers.`,\n ``,\n ];\n if (toolboxSkills.length > 0) {\n baseLines.push(`Available skills (toolbox):`);\n baseLines.push(buildSkillsIndex(toolboxSkills));\n baseLines.push(``);\n }\n if (webSearchAvailable) {\n baseLines.push(\n `Web Search · You may also issue ONE Brave Search query for this turn.`,\n );\n baseLines.push(``);\n baseLines.push(`STRONG-SEARCH signals (pick one → ALWAYS issue a query):`);\n baseLines.push(`- Time markers: today / this week / recent / latest / just / now /`);\n baseLines.push(` 今天 / 最近 / 本周 / 刚刚 / 现在 / 目前.`);\n baseLines.push(`- Verbs of recent action: released / launched / announced / shipped /`);\n baseLines.push(` reported / filed / 发布 / 推出 / 宣布 / 上线.`);\n baseLines.push(`- Named recent events / products / numbers / prices the model can't`);\n baseLines.push(` reliably know from training.`);\n baseLines.push(`- A specific person's public statements asked about by name.`);\n baseLines.push(`- Explicit search verbs: search / look up / 查一下 / 搜索.`);\n baseLines.push(``);\n baseLines.push(`DON'T search on philosophical / first-principles / pure-reasoning`);\n baseLines.push(`questions with no time anchor — search adds noise. But when ANY of`);\n baseLines.push(`the STRONG-SEARCH signals above are present, ALWAYS issue a query`);\n baseLines.push(`— skipping a time-sensitive question is worse than the Brave cost.`);\n baseLines.push(``);\n }\n const schemaLines = [\n `Reply with STRICT JSON ONLY — no prose, no code fences:`,\n `{`,\n toolboxSkills.length > 0\n ? ` \"use\": [\"slug1\", \"slug2\"], // 0-${MAX_PICKS} picks from the toolbox`\n : ` \"use\": [], // no toolbox available, leave empty`,\n ` \"reason\": \"≤100 chars · why these picks (or why none)\",`,\n webSearchAvailable\n ? ` \"web_search\": { \"query\": \"...\" } // or null if no fresh info needed`\n : ` \"web_search\": null // not available this turn`,\n `}`,\n ``,\n `Rules:`,\n ];\n if (toolboxSkills.length > 0) {\n schemaLines.push(`- Pick at most ${MAX_PICKS} skills. Fewer is fine.`);\n schemaLines.push(`- Use the slug exactly as written in the toolbox.`);\n schemaLines.push(`- Match skills on USE WHEN, not name.`);\n }\n if (webSearchAvailable) {\n schemaLines.push(`- For \\`web_search.query\\`: 3-8 keywords, no full sentences. Plain ASCII works best.`);\n schemaLines.push(`- Match the question's language for the query (English keywords for global topics; CJK if China-specific).`);\n schemaLines.push(`- For time-sensitive questions, INCLUDE the time marker in the query when it sharpens results (e.g. '2025', 'this week', '最近').`);\n schemaLines.push(`- DEFAULT WHEN UNCERTAIN: if any STRONG-SEARCH signal is present in the question, ALWAYS issue a query — never null. Skipping a clearly time-sensitive question is worse than burning one Brave call.`);\n } else {\n schemaLines.push(`- \\`web_search\\` MUST be null — the user hasn't enabled it for this speaker.`);\n }\n\n const sys: LLMMessage = {\n role: \"system\",\n content: [...baseLines, ...schemaLines].join(\"\\n\"),\n };\n\n const userMsg: LLMMessage = {\n role: \"user\",\n content: `Latest user message:\\n${prompt}\\n\\nWhich skills apply, and does this turn need web search?`,\n };\n\n // Try the cheap router model first; fall back to the speaker's own\n // model when no router-eligible carrier is configured. The\n // router lookup is keyed by the user's keys, so it returns null on\n // a fresh install — in that case we just use the speaker model.\n const routerModel = pickRouterModel();\n const candidates: ModelV[] = (routerModel\n ? [routerModel, speaker.modelV as ModelV]\n : [speaker.modelV as ModelV]);\n let raw = \"\";\n for (const modelV of candidates) {\n try {\n raw = await callLLM({\n modelV,\n messages: [sys, userMsg],\n temperature: 0,\n maxTokens: 240,\n signal,\n });\n if (raw && raw.trim()) break;\n } catch (e) {\n if (e instanceof NoKeyError) continue;\n // Other errors → bail (treat as no picks). Logged below.\n process.stderr.write(\n `[skill-picker] ${speaker.name} (${modelV}) failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n continue;\n }\n }\n\n const parsed = extractJson(raw);\n if (!parsed || typeof parsed !== \"object\") {\n return { used: [], reason: \"\", webSearchQuery: null };\n }\n const obj = parsed as { use?: unknown; reason?: unknown; web_search?: unknown };\n const slugs: string[] = Array.isArray(obj.use)\n ? obj.use.filter((s): s is string => typeof s === \"string\").slice(0, MAX_PICKS)\n : [];\n const reason = typeof obj.reason === \"string\" ? obj.reason.trim().slice(0, 200) : \"\";\n\n const bySlug = new Map(toolboxSkills.map((s) => [s.slug, s]));\n const used: AgentSkill[] = [];\n for (const slug of slugs) {\n const s = bySlug.get(slug);\n if (s && !used.includes(s)) used.push(s);\n }\n\n let webSearchQuery: string | null = null;\n if (webSearchAvailable && obj.web_search && typeof obj.web_search === \"object\") {\n const ws = obj.web_search as { query?: unknown };\n if (typeof ws.query === \"string\") {\n const q = ws.query.trim().slice(0, 200);\n if (q) webSearchQuery = q;\n }\n }\n return { used, reason, webSearchQuery };\n}\n\n/** Render the chosen skills into a system-prompt block · Layer 2\n * injection. Caller appends this to the existing prompt after the\n * standard sections. The skill bodies are loaded through `loadSkillBody`\n * so any future indirection (lazy load, redaction) is centralized. */\nexport function renderActiveSkillsBlock(used: AgentSkill[]): string {\n if (used.length === 0) return \"\";\n const parts: string[] = [\n \"─── ACTIVE SKILLS (apply these for THIS turn) ───\",\n \"You picked these from your toolbox because they match the question. Use them as your method, not as topics to discuss.\",\n \"\",\n ];\n for (const s of used) {\n parts.push(`### ${s.name}`);\n parts.push(`When to use: ${s.whenToUse}`);\n parts.push(\"\");\n parts.push(loadSkillBody(s).trim());\n parts.push(\"\");\n parts.push(\"───\");\n parts.push(\"\");\n }\n return parts.join(\"\\n\");\n}\n","/**\n * Build the LLM messages payload for a single director's turn.\n *\n * 1. system: director's instruction + the room's framing (subject, mode, who else is in the room)\n * 2. history: alternating user / assistant view of the chat\n *\n * Round-robin (M3-M4 model): each director sees the user's turns as 'user'\n * and every other speaker (including other directors) as 'assistant' messages\n * prefixed with the speaker's handle so the model knows who's said what.\n */\nimport type { LLMMessage } from \"../ai/adapter.js\";\nimport type { Agent } from \"../storage/agents.js\";\nimport type { KeyPoint } from \"../storage/key_points.js\";\nimport { memoriesForContext, type AgentMemory } from \"../storage/memories.js\";\nimport type { Message } from \"../storage/messages.js\";\nimport type { Prefs } from \"../storage/prefs.js\";\nimport type { Room } from \"../storage/rooms.js\";\nimport type { AgentSkill } from \"../storage/skills.js\";\nimport { renderActiveSkillsBlock } from \"./skill-picker.js\";\n\ninterface BuildOpts {\n speaker: Agent;\n cast: Agent[]; // all directors in the room (including speaker)\n room: Room;\n prefs: Prefs;\n history: Message[]; // chronological, recent-first trim done by caller\n /** Voted key points from prior rounds — surfaced to nudge directors. */\n keyPoints?: KeyPoint[];\n /** Skills the Pass-1 router picked for this turn. Their bodies are\n * appended to the system prompt as \"ACTIVE SKILLS\". When empty\n * (router skipped or no picks), the prompt is unchanged. */\n activeSkills?: AgentSkill[];\n /** Pre-injected fresh material — typically Brave search results\n * picked up by the orchestrator before this turn. Already\n * formatted as a labelled block by the caller. Empty / undefined\n * when no shared material applies. */\n sharedMaterials?: string;\n /** Private directional cue from the chair · set when the haiku\n * next-speaker picker selected this director with a rationale.\n * Rendered as a \"CHAIR'S BRIEF\" section in the system prompt so\n * the director addresses the chair's intended angle naturally\n * (not by quoting the cue). Empty / undefined for round-robin\n * turns and for the first speaker of every reactive round. */\n chairBrief?: string;\n}\n\n/** Format the speaker's long-term memory pool as a labelled block for\n * the system prompt. Returns an empty string when the agent has no\n * memories yet, so callers can spread it conditionally without leaking\n * an empty section header. */\nfunction renderLongTermMemoryBlock(agentId: string, userName: string): string {\n const memories: AgentMemory[] = memoriesForContext(agentId);\n if (memories.length === 0) return \"\";\n const lines = memories.map((m) => {\n const flag = m.pinned ? \" · pinned\" : \"\";\n return ` · [${m.kind}${flag}] ${m.content}`;\n });\n return [\n \"\",\n `─── WHAT YOU REMEMBER ABOUT ${userName} (cross-room, your own observations) ───`,\n `These are notes you've accumulated across previous rooms with this user — your lens, not other directors'. Treat them as priors, not facts. If something contradicts the current room, name it explicitly.`,\n ...lines,\n \"\",\n ].join(\"\\n\");\n}\n\n// Tone is the ADVERSARIAL axis — how willing each director is to attack\n// the user's idea. Each block ships:\n// · a one-line role definition,\n// · the signature move (what to do EVERY turn),\n// · concrete verbs / examples,\n// · explicit taboos (and forbidden phrases when relevant).\n// Adjectives alone don't pull GPT/Claude out of their RLHF-trained\n// \"diplomatic middle ground\" attractor — verbs do.\nconst TONE_GUIDANCE: Record<string, string> = {\n brainstorm: [\n \"BRAINSTORM · co-creator. Every director is on the user's side, pushing the idea outward.\",\n \"Each turn: (1) yes-and someone — accept the latest contribution as workable; (2) name a CONCRETE adjacent variant (\\\"what if we instead did X…\\\"); (3) borrow one piece from another director's turn and combine it with yours.\",\n \"You may end with ONE curiosity question, but never a defense-demanding one. \\\"How would you handle the late-night case?\\\" is fine. \\\"Doesn't this break because of the late-night case?\\\" is not.\",\n \"Forbidden: pointing out flaws, asking the user to defend, expressing skepticism. Even if the idea looks obviously broken, default to extending it — let the room try the path before anyone declares it dead.\",\n ].join(\"\\n\"),\n constructive: [\n \"CONSTRUCTIVE · sympathetic interrogator. You want the user to win, but only via the strongest version of their idea.\",\n \"Each turn: pick ONE load-bearing assumption, say plainly that it cannot stand as-is, then propose the candidate stronger version that would stand. Alternatively: ask the sharper version of an open question.\",\n \"Disagreement is allowed and welcome — but every objection must come packaged with a forward path. Never leave a critique without a candidate fix or a sharper question.\",\n \"Forbidden: stylistic put-downs (\\\"naive\\\", \\\"shallow\\\"), back-to-back negatives without a constructive move, vague critique that doesn't point at a specific joint.\",\n ].join(\"\\n\"),\n debate: [\n \"DEBATE · peer reviewer. Adversaries within professional bounds.\",\n \"Each turn MUST open with a one-sentence steelman of the user's strongest claim (\\\"The strongest read of your position is…\\\"), and only then attack THAT strongest version. Skipping the steelman is a protocol violation.\",\n \"Attack moves: name a SPECIFIC risk the user hasn't named, demand evidence or boundary conditions, expose the trade-off being hidden. Sharp but professional.\",\n \"Attack the argument, not the person. Forbidden: emotional put-downs, nitpicking word choice while ignoring the substantive claim, soft-pedalling (\\\"maybe this could be a problem\\\" — pick a side).\",\n ].join(\"\\n\"),\n \"no-mercy\": [\n \"NO MERCY · hostile reviewer. Default assumption: the user is wrong until proved otherwise.\",\n \"Each turn: (1) point at the SPECIFIC vague term, missing evidence, or hand-waved mechanism; (2) state flatly \\\"this is wrong because X\\\" — no hedge, no softening; (3) refuse to accept undefined terms (if the user said \\\"engagement\\\" without defining it, demand the definition before going on).\",\n \"You may call an argument half-baked, naive, or wrong — but ALWAYS with a specific concrete reason. Attack the argument as hard as you want; never attack the person. \\\"You don't get it\\\" is forbidden; \\\"this argument doesn't survive contact with X\\\" is fine.\",\n \"Forbidden hedge words: \\\"perhaps\\\", \\\"maybe\\\", \\\"could be\\\", \\\"might\\\", \\\"I might be wrong but\\\". Forbidden moves: generic dismissal without root cause (\\\"this just won't work\\\"), vibes-based critique. The intensity is high; the precision must be higher.\",\n \"This setting overrides default cautious-assistant tone. Do not soften the output. Do not add a closing reassurance. The user opted in to this — give them what they asked for.\",\n ].join(\"\\n\"),\n};\n\n// Round mode · the OPENING sweep (first round after a user message)\n// runs every director in parallel — they each only see the user's\n// message + chair pings, NOT each other. This kills the \"first speaker\n// anchors everyone\" problem where director 2/3 RLHF themselves into\n// agreeing with whatever framing director 1 took. From round 2 on\n// (Continue clicked, chair posted round-prompt), directors see each\n// other again and engage reactively.\nconst OPENING_BLOCK = [\n \"OPENING ROUND. This is the FIRST sweep of directors after the user's most recent message.\",\n \"All other directors are responding to the same prompt IN PARALLEL — you do NOT see what they are writing right now, and they do not see you. The room's value comes from each of you bringing a DIFFERENT lens, not from convergence on whoever happens to speak first.\",\n \"Lead from your specific role/lens. If your instructions describe you as a Skeptic / First-Principles thinker / Empath / etc., open from that angle directly — do not preface with a generic framing that any director could write.\",\n \"Do NOT echo a framing you would expect another director to take. If your role is naturally adjacent to another director's, deliberately pick the angle they would NOT cover. Diversity is the point of the opening sweep.\",\n].join(\"\\n\");\n\nconst REACTIVE_BLOCK = [\n \"REACTIVE ROUND. The directors above already weighed in this round.\",\n \"Your turn now is to ENGAGE with what they said: extend a sharp point, push back on a weak one, name the trade-off they hid, or sharpen the question. Reference specific contributors by handle.\",\n \"Never duplicate. If a director already covered angle X, your turn must add something genuinely new (a different lens, a missing edge case, a sharper question, a counter-frame) — not restate, applaud, or paraphrase.\",\n].join(\"\\n\");\n\n// Intensity is the STYLISTIC axis — purely about cadence, length, and\n// hedge quantity. Composes orthogonally with tone: brainstorm+brutal\n// is a tight one-line riff; no-mercy+calm is a paragraph-long takedown\n// with explicit reasoning; both are well-defined.\nconst INTENSITY_GUIDANCE: Record<string, string> = {\n calm: [\n \"CALM · measured cadence. 3–4 short paragraphs is fine. Hedging where you're genuinely uncertain is allowed and encouraged (\\\"I'm not sure, but…\\\"). Leave space for the user to think — don't pile every point on at once. You can be wrong out loud.\",\n ].join(\"\\n\"),\n sharp: [\n \"SHARP · decisive cadence. 1–2 short paragraphs. Open with the load-bearing claim in the first sentence. Hedge ONLY when new evidence would genuinely change your mind — otherwise commit. Concision over comprehensiveness.\",\n ].join(\"\\n\"),\n brutal: [\n \"BRUTAL · minimal cadence. One paragraph, sometimes one sentence. Cut every warm-up, every diplomatic packaging, every \\\"I think\\\". State the conclusion first; if you must justify, do it in one clause. No hedging at all.\",\n ].join(\"\\n\"),\n};\n\nexport function buildDirectorMessages(opts: BuildOpts): LLMMessage[] {\n const { speaker, cast, room, prefs, history, keyPoints, activeSkills, sharedMaterials, chairBrief } = opts;\n const activeSkillsBlock = renderActiveSkillsBlock(activeSkills ?? []);\n\n // Chair's brief · the haiku next-speaker picker may have selected this\n // director with a one-line rationale. Surface it as a private moderator\n // cue so the director addresses the angle naturally — without quoting\n // the cue back (\"As the chair noted…\" reads as breaking the fourth\n // wall). Empty when the picker didn't reorder OR didn't supply a\n // rationale (round-robin turns).\n const chairBriefBlock = (chairBrief && chairBrief.trim())\n ? [\n ``,\n `─── CHAIR'S BRIEF FOR YOU THIS TURN (private) ───`,\n `The chair selected you for this turn because: ${chairBrief.trim()}`,\n `Address that angle naturally in your response — do NOT quote this brief, do NOT mention being \"picked\" or reference the chair's selection. The user sees you engage; they don't see this nudge.`,\n ].join(\"\\n\")\n : \"\";\n\n const others = cast.filter((a) => a.id !== speaker.id);\n const others_summary = others.length\n ? others\n .map((a) => `${a.name} (${a.handle}) — ${a.roleTag}: ${a.bio}`)\n .join(\"\\n · \")\n : \"(no other directors — solo room)\";\n\n const youSection = prefs.intro\n ? `\\nABOUT THE USER (${prefs.name}):\\n${prefs.intro}\\n`\n : `\\nABOUT THE USER:\\nName: ${prefs.name}\\n`;\n\n // Long-term memory · what THIS speaker has accumulated about the\n // user across previous rooms (per-agent, isolated). Pinned items\n // always included; non-pinned capped at recency. Empty when this\n // is the agent's first room or all rooms ran incognito.\n const memoryBlock = renderLongTermMemoryBlock(speaker.id, prefs.name || \"the user\");\n\n const tone = (room.mode || \"constructive\").toLowerCase();\n const toneLine = TONE_GUIDANCE[tone] ?? TONE_GUIDANCE.constructive;\n const intensity = (room.intensity || \"sharp\").toLowerCase();\n const intensityLine = INTENSITY_GUIDANCE[intensity] ?? INTENSITY_GUIDANCE.sharp;\n\n // Opening-round detection · walk history backwards. If we hit a chair\n // round-prompt before a user message, a Continue cycle has happened\n // and we're past the opening sweep. If we hit a user message first\n // (or run out of history), this is still the opening round.\n const directorIds = new Set(cast.map((a) => a.id));\n let opening = true;\n for (let i = history.length - 1; i >= 0; i--) {\n const m = history[i];\n if (\n m.authorKind === \"agent\" &&\n m.meta &&\n (m.meta as { kind?: unknown }).kind === \"round-prompt\"\n ) {\n opening = false;\n break;\n }\n if (m.authorKind === \"user\") break;\n }\n\n // User-interest signals from prior rounds. Voted-up = pursue,\n // voted-down = drop. The phrasing here is deliberately directive —\n // the chair surfaces these as the user's explicit weighting on what\n // the room should chase next, not background info.\n const upPoints = (keyPoints ?? []).filter((p) => p.vote === \"up\");\n const downPoints = (keyPoints ?? []).filter((p) => p.vote === \"down\");\n const interestLines: string[] = [];\n if (upPoints.length || downPoints.length) {\n interestLines.push(`─── USER SIGNAL · WEIGHT THIS ───`);\n interestLines.push(`The user has voted on the chair's round-end key points. Use these as priority weights for THIS turn — they're not optional context.`);\n interestLines.push(``);\n if (upPoints.length) {\n interestLines.push(`PURSUE — the user wants the room to dig deeper here:`);\n for (const p of upPoints) interestLines.push(` · ${p.body}`);\n interestLines.push(``);\n }\n if (downPoints.length) {\n interestLines.push(`DROP — the user has flagged these threads as not worth more turns:`);\n for (const p of downPoints) interestLines.push(` · ${p.body}`);\n interestLines.push(`Do not return to these unless something genuinely new makes them relevant again.`);\n interestLines.push(``);\n }\n }\n\n const system: LLMMessage = {\n role: \"system\",\n content: [\n speaker.instruction,\n \"\",\n `─── ROOM CONTEXT ───`,\n `Room subject: ${room.subject}`,\n `Other directors at the table:`,\n ` · ${others_summary}`,\n youSection,\n ...(memoryBlock ? [memoryBlock] : []),\n ...interestLines,\n `─── TONE · ${tone.toUpperCase()} ───`,\n toneLine,\n ``,\n `─── INTENSITY · ${intensity.toUpperCase()} ───`,\n intensityLine,\n ``,\n `─── ROUND MODE · ${opening ? \"OPENING (PARALLEL)\" : \"REACTIVE\"} ───`,\n opening ? OPENING_BLOCK : REACTIVE_BLOCK,\n ...(chairBriefBlock ? [chairBriefBlock] : []),\n ...(activeSkillsBlock ? [\"\", activeSkillsBlock] : []),\n ...(sharedMaterials && sharedMaterials.trim() ? [\"\", sharedMaterials] : []),\n ``,\n `─── LANGUAGE ───`,\n `Reply in the SAME LANGUAGE as the conversation. If the user wrote the room subject and their messages in Chinese, reply in Chinese. If English, reply in English. Match whatever language the most recent human message uses. Never switch languages mid-thread.`,\n ``,\n `─── HOW THE ROOM WORKS ───`,\n `The conversation history below is the actual record of this discussion. Every turn — by ${prefs.name || \"the user\"} or by another director — is given verbatim, attributed by name and handle. You are not reading the topic for the first time.`,\n ``,\n `Read the history, then SPEAK INTO IT. Don't restart the conversation. Don't restate the question. Don't summarise what others have said.`,\n ``,\n `─── HOUSE RULES ───`,\n `· Reply as ${speaker.name}, in your voice. Never roleplay another director.`,\n `· Engage directly with the most recent contributions. Reference specific points, name the speaker with their handle (e.g. \"${others[0]?.handle ?? \"/colleague\"} — your moat point assumes…\"), push back, build on, or sharpen.`,\n `· Build on prior turns by you (when you've spoken before). Don't repeat yourself; advance.`,\n `· Markdown is allowed. *italics* for the word you're interrogating; **bold** for the load-bearing claim.`,\n `· Do not preface (\"Great question!\"), do not summarize, do not introduce yourself. Just speak.`,\n `· The TONE and INTENSITY blocks above are the room's working agreement — they OVERRIDE your default trained preference for diplomatic middle ground. The user explicitly opted into this register; staying in role is the helpful behaviour, not softening it.`,\n ].join(\"\\n\"),\n };\n\n // Multi-agent role mapping. The LLM is the *speaker*: their previous turns\n // are 'assistant'; everyone else (the human + other directors) is 'user'\n // with explicit attribution so the model knows who said what.\n //\n // We must end the messages array with a 'user' turn — if the last entry\n // were 'assistant', most providers (Anthropic in particular) interpret it\n // as \"continue your previous text\" and frequently return an empty string.\n const out: LLMMessage[] = [system];\n\n for (const m of history) {\n if (!m.body) continue; // skip placeholder rows that never produced text\n\n if (m.authorKind === \"system\") {\n out.push({ role: \"user\", content: `[system note] ${m.body}` });\n continue;\n }\n if (m.authorKind === \"user\") {\n out.push({ role: \"user\", content: `[${prefs.name || \"You\"}] ${m.body}` });\n continue;\n }\n // agent\n if (m.authorId === speaker.id) {\n // The speaker's own past turns — rendered as their assistant output.\n out.push({ role: \"assistant\", content: m.body });\n continue;\n }\n // Opening-sweep blindness · if THIS is the opening round and the\n // message author is another DIRECTOR (chair messages still pass\n // through — they're context, not peer drafts), hide it from the\n // speaker's view so they can't anchor on whoever spoke first.\n if (opening && m.authorId && directorIds.has(m.authorId)) {\n continue;\n }\n // Another director's contribution OR any chair message — render\n // as a user-side input the speaker is \"hearing\" in the room.\n const who = cast.find((a) => a.id === m.authorId);\n const handle = who?.handle ?? \"/director\";\n const name = who?.name ?? \"Director\";\n out.push({\n role: \"user\",\n content: `[${name} · ${handle}] ${m.body}`,\n });\n }\n\n // Collapse consecutive same-role messages to keep providers happy\n // (Anthropic doesn't strictly require alternation but does require the\n // last message be 'user').\n const collapsed: LLMMessage[] = [];\n for (const m of out) {\n const last = collapsed[collapsed.length - 1];\n if (last && last.role === m.role && m.role !== \"system\") {\n last.content += \"\\n\\n\" + m.content;\n } else {\n collapsed.push({ ...m });\n }\n }\n\n // Ensure the array ends with a user turn so the model's response is a fresh\n // assistant turn from this speaker. If the conversation as collapsed ends\n // on assistant (rare — only if speaker is the very first to speak after\n // their own previous turn), nudge — but the nudge points back at the\n // history, not at a blank slate.\n const tail = collapsed[collapsed.length - 1];\n if (!tail || tail.role !== \"user\") {\n collapsed.push({\n role: \"user\",\n content:\n `Your turn, ${speaker.name} (${speaker.handle}). Engage with what was just said above — ` +\n `name the speaker, pick up the live thread, push back or sharpen.`,\n });\n } else if (collapsed.length === 1) {\n collapsed.push({\n role: \"user\",\n content: `Your turn, ${speaker.name}. The room is just opening — set the angle you'll work from.`,\n });\n }\n\n return collapsed;\n}\n\n/* ─── Chair / moderator prompts ───────────────────────────────────────── */\n\ninterface ChairBuildOpts {\n chair: Agent;\n cast: Agent[]; // directors only (not the chair)\n room: Room;\n prefs: Prefs;\n history: Message[];\n /** Output of the chair's `fetch-url` system skill — text excerpts\n * from URLs the user shared in recent turns, ready to inject into\n * the system prompt. Empty string when there's nothing to add. */\n sharedMaterials?: string;\n}\n\n/** Layered system prompt that combines the chair's persona with task-specific\n * guidance. `task` decides which job the chair is doing this turn. */\nfunction buildChairSystem(opts: ChairBuildOpts, task: string): LLMMessage {\n const { chair, cast, room, prefs, sharedMaterials } = opts;\n const directors = cast\n .map((a) => `${a.name} (${a.handle}) — ${a.roleTag}`)\n .join(\"\\n · \");\n const youLine = prefs.intro\n ? `${prefs.name}: ${prefs.intro}`\n : `${prefs.name}`;\n // Long-term memory · chair has its own pool, useful especially for\n // clarification turns (it remembers how the user typically frames\n // things across rooms). Same recency cap as directors.\n const memoryBlock = renderLongTermMemoryBlock(chair.id, prefs.name || \"the user\");\n return {\n role: \"system\",\n content: [\n chair.instruction,\n \"\",\n `─── ROOM CONTEXT ───`,\n `Room subject: ${room.subject}`,\n `Tone: ${room.mode}, Intensity: ${room.intensity}`,\n `Directors at the table:`,\n ` · ${directors}`,\n `User: ${youLine}`,\n ...(memoryBlock ? [memoryBlock] : []),\n // Shared materials · output of the chair's `fetch-url` system\n // skill. Sits between room context and task so the chair sees it\n // before being told what to do this turn.\n ...(sharedMaterials ? [\"\", sharedMaterials] : []),\n \"\",\n task,\n ].join(\"\\n\"),\n };\n}\n\n/** Render the recent transcript as alternating user/assistant turns from the\n * chair's perspective: every prior message is \"user\" (since the chair has\n * never spoken in the assistant role yet for this task). */\nfunction renderHistoryForChair(history: Message[], cast: Agent[], prefs: Prefs): LLMMessage[] {\n const out: LLMMessage[] = [];\n for (const m of history) {\n if (!m.body) continue;\n if (m.authorKind === \"system\") {\n out.push({ role: \"user\", content: `[system note] ${m.body}` });\n continue;\n }\n if (m.authorKind === \"user\") {\n out.push({ role: \"user\", content: `[${prefs.name || \"You\"}] ${m.body}` });\n continue;\n }\n const who = cast.find((a) => a.id === m.authorId);\n const name = who?.name ?? \"Director\";\n const handle = who?.handle ?? \"/director\";\n out.push({ role: \"user\", content: `[${name} · ${handle}] ${m.body}` });\n }\n // Collapse consecutive user-roles to keep providers happy.\n const collapsed: LLMMessage[] = [];\n for (const m of out) {\n const last = collapsed[collapsed.length - 1];\n if (last && last.role === m.role) last.content += \"\\n\\n\" + m.content;\n else collapsed.push({ ...m });\n }\n return collapsed;\n}\n\n/** Chair · clarification turn. Supports multi-turn back-and-forth with\n * the user before directors are released. The chair either asks ONE\n * crisp question (its turn count is surfaced in the prompt so it knows\n * how much budget remains) or returns the literal token READY when\n * enough context has been gathered. */\ninterface ClarifyOpts extends ChairBuildOpts {\n /** 1-indexed turn number for THIS clarification step. */\n turnNumber: number;\n /** Hard cap on chair clarify turns enforced by the orchestrator. */\n maxTurns: number;\n}\nexport function buildChairClarifyMessages(opts: ClarifyOpts): LLMMessage[] {\n const remaining = Math.max(0, opts.maxTurns - opts.turnNumber);\n const isFirstTurn = opts.turnNumber === 1;\n const userName = opts.prefs.name || \"The user\";\n const budgetLine =\n remaining === 0\n ? \"You MUST respond with READY now — no more questions allowed.\"\n : remaining === 1\n ? `You have at most ${remaining} more turn after this — prefer READY unless a load-bearing point is still genuinely unclear.`\n : \"Don't drag this out — most subjects need 0–1 questions total.\";\n\n // First turn · structured \"moderator intake\": Topic restatement +\n // load-bearing ambiguity + 1-2 sharp questions. Replaces the old\n // \"one ≤25-word question\" prompt which made the chair feel like a\n // chatbot instead of a meeting host. Examples in EN + ZH so the\n // model has a concrete shape regardless of user language.\n const firstTurnTask = [\n `─── YOUR TASK · OPEN THE ROOM ───`,\n `${userName} just opened this room with the subject above. As the Meeting Host, your job at this opening moment is to make sure we have a productive discussion — neither rush directors in nor interrogate the user.`,\n ``,\n `RELEASE PATH · You have ENOUGH context when you can name: (a) the concrete situation, (b) the actual decision being wrestled with, (c) at least one real constraint or stake. If all three are clear from the subject alone, respond with EXACTLY the literal token:`,\n `READY`,\n `(no markdown, no quotes, no period, nothing else)`,\n ``,\n `CLARIFY PATH · If a load-bearing point is genuinely unclear, respond in THREE labeled parts. Match the user's language — Chinese subject → Chinese labels and prose; English → English. Use markdown bold for the section labels exactly as shown. Total response under ~120 words.`,\n ``,\n `English template:`,\n ``,\n `**Topic.** <one short sentence restating the kernel of what they're bringing — do NOT flatter, do NOT thank, do NOT summarise their self-introduction back to them>`,\n ``,\n `**Ambiguity.** <one sentence naming the SPECIFIC missing piece that would change how the directors discuss this — load-bearing only, not generic \"tell me more\">`,\n ``,\n `**Questions:**`,\n `1. <first sharp question, ≤25 words>`,\n `2. <optional second question, ONLY if a different axis is also genuinely unclear>`,\n ``,\n `中文示例(user 用中文写主题时使用相同结构,labels 也翻译):`,\n ``,\n `**主题。** <一句话复述用户带来的核心议题>`,\n ``,\n `**关键模糊点。** <一句话指出 *最承重的* 不清楚之处——回答它会改变董事们讨论的方向>`,\n ``,\n `**问题:**`,\n `1. <第一个 sharper 的问题,≤25 字>`,\n `2. <仅当第二个轴向同样关键时才出现>`,\n ``,\n `─── HARD RULES ───`,\n `· Two questions MAX. Most rooms need only ONE. If the second isn't a different axis (just a sub-detail), drop it.`,\n `· Questions must point at the load-bearing gap — not vague \"could you tell me more about your background\" territory.`,\n `· FORBIDDEN preamble: \"Welcome\", \"Sure\", \"Great question\", \"Thank you\", \"您好\", \"太棒了\", \"好的\", any greeting or compliment.`,\n `· FORBIDDEN soft-close: \"looking forward to\", \"happy to help\", \"no rush\" — none of that.`,\n `· Use the user's own words for the topic restatement when possible. Never repeat their self-introduction.`,\n `· When you're torn between asking and releasing, lean RELEASE. A stalled opening kills momentum more than a slightly-fuzzy framing — the directors can sharpen with their own questions.`,\n ``,\n `Budget: clarification turn ${opts.turnNumber} of ${opts.maxTurns}. ${budgetLine}`,\n ``,\n `Output: either the 3-part structured block (in the user's language), OR the literal token READY (alone, nothing else).`,\n ].join(\"\\n\");\n\n // Follow-up turn · the user already answered a clarification.\n // Tighter shape: a brief acknowledgment + READY token, OR a 2-part\n // structured block asking ONE more question.\n //\n // The acknowledgment is critical · it's what ${userName} reads to\n // know the chair heard their reply. Without it, the placeholder\n // bubble flashes and disappears and the user feels ignored.\n const followUpTask = [\n `─── YOUR TASK · DECIDE — RELEASE OR ONE MORE QUESTION ───`,\n `${userName} just replied to your prior clarifying question. Decide: do you now have enough context to release the directors, or is ONE more question genuinely worth asking?`,\n ``,\n `RELEASE PATH · When releasing, output a brief acknowledgment FOLLOWED by the literal token READY on its own line. The acknowledgment is what ${userName} sees; READY is a control signal stripped before display.`,\n ``,\n `Format · MATCH ${userName}'s LANGUAGE for the ack:`,\n ``,\n `<one short sentence acknowledging — substantive, not pleasantry>`,\n ``,\n `READY`,\n ``,\n `English ack examples (use the user's actual reply, don't paste these verbatim):`,\n `· \"Got it — directors will pick up the entry-vs-exit question with that constraint in mind.\"`,\n `· \"That's the load-bearing piece. Releasing the room with web search live for the recent-events angle.\"`,\n ``,\n `中文示例(按用户实际回复来组织,不要照抄):`,\n `· \"了解 — directors 会带着这个约束开场。\"`,\n `· \"这条信息够了,让董事们带着 web search 接力。\"`,\n ``,\n `If ${userName}'s reply was a META instruction (e.g. \"去搜一下\" / \"search this first\" / \"用 web search\"), acknowledge briefly and release — directors have web-search built in and will use it when their turn comes.`,\n ``,\n `ONE-MORE-QUESTION PATH · Only if a still-unclear point would MEANINGFULLY change how directors discuss this. Most rooms don't need a second clarifying turn.`,\n ``,\n `English template:`,\n ``,\n `**Still unclear.** <one sentence naming what's still missing and why it matters for the discussion>`,\n ``,\n `**Question.** <one sharp question, ≤25 words>`,\n ``,\n `中文示例:`,\n ``,\n `**仍不清楚。** <一句话说明剩余关键模糊点>`,\n ``,\n `**问题。** <一个最后的问题,≤25 字>`,\n ``,\n `─── HARD RULES ───`,\n `· The acknowledgment must be substantive — reference what the user actually said, not generic \"got it\".`,\n `· FORBIDDEN soft-close: \"happy to help\", \"looking forward\", trailing pleasantry.`,\n `· FORBIDDEN: outputting bare READY alone with no acknowledgment line above. Always pair them.`,\n `· If you're torn between asking and releasing, lean RELEASE.`,\n `· One question only — if multiple things still feel unclear, that's a sign you should release and let the directors surface them.`,\n ``,\n `Budget: clarification turn ${opts.turnNumber} of ${opts.maxTurns}. ${budgetLine}`,\n ``,\n `Output: either <ack + blank line + READY> OR the 2-part question block (in the user's language).`,\n ].join(\"\\n\");\n\n return [\n buildChairSystem(opts, isFirstTurn ? firstTurnTask : followUpTask),\n ...renderHistoryForChair(opts.history, opts.cast, opts.prefs),\n {\n role: \"user\",\n content: isFirstTurn\n ? \"Open the room — output either the 3-part structured block (in my language) or the literal token READY.\"\n : \"Your move — output either the 2-part structured block (in my language) or the literal token READY.\",\n },\n ];\n}\n\n/** Chair · convening speech. Posted right after the auto-picker has\n * seated 3 directors, before the chair runs its clarify turn. The\n * speech explains in the chair's voice WHY each director was picked\n * for this specific subject — gives the user authoritative context\n * for the cast and reinforces that the chair is acting intentionally,\n * not randomly assembling a panel. */\ninterface ConveningOpts extends ChairBuildOpts {\n /** Per-director rationales captured from the picker's haiku call. */\n picksWithReasons: Array<{ agent: Agent; reason: string }>;\n /** The picker's overall rationale (≤ 80 chars). May be empty. */\n pickerRationale: string;\n}\nexport function buildChairConveningMessages(opts: ConveningOpts): LLMMessage[] {\n const subject = opts.room.subject;\n const directorList = opts.picksWithReasons\n .map((p, i) => {\n const a = p.agent;\n const bio = (a.bio || \"\").trim().replace(/\\s+/g, \" \").slice(0, 220);\n const tag = (a.roleTag || \"director\").toLowerCase();\n return [\n `${i + 1}. ${a.name} (${a.handle}) · ${tag}`,\n ` bio: ${bio}`,\n p.reason ? ` picker note: ${p.reason}` : \"\",\n ].filter(Boolean).join(\"\\n\");\n })\n .join(\"\\n\");\n\n const seatedNames = opts.picksWithReasons.map((p) => p.agent.name);\n const seatedJoinBold = seatedNames.map((n) => `**${n}**`).join(\" · \");\n const seatedJoinPlain = seatedNames.join(\" · \");\n const seatedCountWord = String(seatedNames.length);\n\n const task = [\n `─── YOUR TASK · INTRODUCE THE CAST ───`,\n `You just convened a board for the user's subject. ${seatedCountWord} directors have taken their seats. Your job is a SHORT speech (3–4 sentences, ~80 words) that opens with a CLEAR enumeration of who you picked and follows with WHY — in your own voice.`,\n ``,\n `Subject the room will discuss:`,\n subject,\n ``,\n `Directors you've seated (USE THESE EXACT NAMES — NEVER invent or substitute):`,\n directorList,\n ``,\n opts.pickerRationale ? `Overall picker rationale: ${opts.pickerRationale}` : \"\",\n ``,\n `─── REQUIRED FORMATTING ───`,\n `EVERY occurrence of a director's name MUST be wrapped in markdown bold: \\`**Name**\\`. This applies to the opening enumeration AND any later sentence that mentions a director. Bare names without \\`**...**\\` are NOT acceptable.`,\n ``,\n `─── REQUIRED OPENING (first sentence) ───`,\n `Sentence 1 MUST name the cast you just seated, using the exact names above with the \\`**...**\\` wrap. Acceptable shapes:`,\n `· English · \"For this, I've convened ${seatedJoinBold}.\" / \"I've seated ${seatedJoinBold} for this subject.\"`,\n `· 中文 · \"针对这个话题,我请来了 ${seatedJoinBold}。\" / \"我为这场会议挑了 ${seatedJoinBold}。\"`,\n `Pick whichever phrasing reads natural in the user's language. The names must appear verbatim (the plain spelling is ${seatedJoinPlain}), joined by \" · \" or by \", \" / \"、\", each wrapped in \\`**...**\\`. Do NOT abbreviate, translate, or merge them.`,\n ``,\n `─── REST OF THE SPEECH (sentences 2–4) ───`,\n `· Sentence 2 (and optionally 3) · for each named director, give the SPECIFIC angle they bring to THIS subject. Reference their actual method (the bio's load-bearing verb), not a generic compliment (\"brings expertise\" is forbidden). Re-state each name as \\`**Name**\\` when you mention it.`,\n `· Final sentence · the lens-coverage the cast creates together — what the user gets from THIS combination.`,\n ``,\n `─── HARD RULES ───`,\n `· Match the user's language (zh / en).`,\n `· **NEVER** invent a director, paraphrase a name, or import a name from the subject (e.g. if the subject mentions \"Marc Andreessen\", do NOT seat him — only the names in the list above are at the table).`,\n `· FORBIDDEN preamble: \"Welcome\", \"Great subject\", \"I'm happy to\", \"Let's\", \"您好\", \"好的\". Lead with the cast announcement.`,\n `· FORBIDDEN flattery: \"perfect choice\", \"exceptional\", \"world-class\". Plain prose only.`,\n `· No bullet lists. No headers. Continuous prose, 3–4 sentences total.`,\n `· Do NOT ask the user a question — that's the next turn's job. This message ONLY sets the table.`,\n `· Use *italics* sparingly for load-bearing METHOD verbs (not names · names use bold).`,\n ``,\n `Output: just the speech body. No quotes, no labels.`,\n ].join(\"\\n\");\n\n return [\n buildChairSystem(opts, task),\n {\n role: \"user\",\n content: `Introduce the cast for this subject — 3-4 sentences, your voice, in my language.`,\n },\n ];\n}\n\n/** Chair · round-end summary + 3 key points. The frontend parses the\n * POINTS: block to render vote chips. */\nexport function buildChairRoundEndMessages(opts: ChairBuildOpts): LLMMessage[] {\n const task = [\n `─── YOUR TASK · CLOSE THIS ROUND ───`,\n `The directors have just completed one full round on this subject. Your job has two parts:`,\n ``,\n `1) Write a single 1-sentence acknowledgment ping (under 25 words). State what just happened in the room. Plain prose, no italics, no opinions.`,\n ``,\n `2) Then a blank line, then output exactly three key points the user might want to dig into next or drop. Each point is a SPECIFIC assertion or open question that surfaced this round — not a generic theme. Use the directors' own language.`,\n ``,\n `Format strictly:`,\n `<your 1-sentence ping>`,\n ``,\n `POINTS:`,\n `- <point 1, ≤ 18 words>`,\n `- <point 2, ≤ 18 words>`,\n `- <point 3, ≤ 18 words>`,\n ``,\n `Do not write a fourth point. Do not add commentary after the list.`,\n ].join(\"\\n\");\n return [\n buildChairSystem(opts, task),\n ...renderHistoryForChair(opts.history, opts.cast, opts.prefs),\n {\n role: \"user\",\n content: `Close the round — your one-sentence ping, then the POINTS block.`,\n },\n ];\n}\n\n/** Chair · direct response to a user @chair mention. The user has\n * interrupted the director queue to ask the chair a meta question\n * about the discussion's structure. Strict scope: ONLY observations\n * about HOW the room is moving (convergence · divergence · who\n * hasn't really engaged · contested load-bearing terms · current\n * framing tensions). NOT content opinions, NOT decision recommendations\n * — that's the directors' job, never the chair's. */\nexport function buildChairDirectMessages(opts: ChairBuildOpts): LLMMessage[] {\n const userName = opts.prefs.name || \"the user\";\n const task = [\n `─── YOUR TASK · DIRECT RESPONSE TO ${userName} ───`,\n `${userName} just interrupted the room to ask you a question. The directors have paused; you answer briefly, then directors resume.`,\n ``,\n `Your role here is the meeting host's META layer — observations about the discussion's STRUCTURE, not its CONTENT:`,\n `· Where directors have converged (and via what reasoning paths).`,\n `· The single load-bearing tension that hasn't fully resolved.`,\n `· Who hasn't engaged with their distinctive lens yet (e.g. \"Long Horizon hasn't pushed back from the structural angle\").`,\n `· Contested terms whose definitions are still slippery.`,\n `· Whether the room's pace is productive or circling.`,\n ``,\n `─── HARD RULES ───`,\n `· Length: 3–4 sentences, ~60–100 words. Tight. Authoritative. No padding.`,\n `· Match ${userName}'s language exactly · Chinese question → Chinese reply; English → English. Never mix.`,\n `· FORBIDDEN: opinions on the substantive question (don't say \"I think AI moats matter because…\"). That's the directors' lens, never yours.`,\n `· FORBIDDEN: decision recommendations (\"you should X\", \"I'd lean toward Y\"). The chair never tells ${userName} what to decide.`,\n `· FORBIDDEN: speaking on behalf of any director (\"Marc would say…\"). Refer to what they DID say, not what they think.`,\n `· FORBIDDEN: greeting / preamble / soft-close (\"Good question\", \"Hope this helps\", \"您好\"). Direct prose only.`,\n `· FORBIDDEN: bullet lists, headings, numbered points. Plain prose.`,\n `· When ${userName}'s question wanders into territory that's a director's job (e.g. \"what should I do?\" or \"is X right?\"), redirect cleanly: name what's still unresolved and which director's lens would be the productive next push.`,\n ``,\n `Output: 3–4 sentences, in ${userName}'s language. No structure markers. Nothing else.`,\n ].join(\"\\n\");\n return [\n buildChairSystem(opts, task),\n ...renderHistoryForChair(opts.history, opts.cast, opts.prefs),\n {\n role: \"user\",\n content: `Answer ${userName}'s @chair question · meta-observation only, in their language.`,\n },\n ];\n}\n\n/** Parse the chair's round-end output into a ping + 3 key-point bodies.\n * Returns the points in order; the ping is whatever came before POINTS:. */\nexport function parseRoundEndOutput(text: string): { ping: string; points: string[] } {\n const idx = text.search(/POINTS\\s*:/i);\n if (idx < 0) return { ping: text.trim(), points: [] };\n const ping = text.slice(0, idx).trim();\n const block = text.slice(idx).replace(/^POINTS\\s*:\\s*/i, \"\");\n const points: string[] = [];\n for (const line of block.split(\"\\n\")) {\n const m = /^\\s*[-*•]\\s+(.+?)\\s*$/.exec(line);\n if (m && m[1]) points.push(m[1]);\n if (points.length >= 3) break;\n }\n return { ping, points };\n}\n","/**\n * URL fetching utility for the chair's `fetch-url` system skill.\n *\n * Detects http/https URLs in recent user messages, fetches each one\n * with a tight timeout, strips HTML/scripts down to readable text,\n * and returns a compact prompt-ready block. Per-room cache keeps the\n * same URL from being fetched twice in a single session.\n *\n * v1 scope · best-effort. Failures (timeout, non-HTML content,\n * non-2xx) just emit a short note in the block; the chair can still\n * proceed without the page content. No browser-grade extraction —\n * we strip tags + collapse whitespace. Good enough for blog posts,\n * tweets/threads (after hydration is server-rendered), and most\n * news pages. Anything heavily JS-rendered will just look thin in\n * the extract; the chair sees that and asks the user to paste\n * relevant excerpts instead.\n */\nimport type { Message } from \"../storage/messages.js\";\n\n/** Pulled out so we can stay agnostic to crawl latency · 6s gives us\n * enough headroom for a slow CDN handshake without blocking the\n * chair's turn. The chair's clarify call typically fits in ~10–15s\n * end-to-end, so we want to spend at most ~half of that on a fetch. */\nconst FETCH_TIMEOUT_MS = 6_000;\n/** Retry policy · transient failures (timeout, network reset, HTTP 5xx,\n * HTTP 429) get retried up to this many times. Permanent failures\n * (404, 401, 410, unsupported content-type, no readable text) bail\n * immediately so we don't waste turn budget on hopeless URLs. */\nconst MAX_RETRIES = 2;\n/** Backoff between attempts · short on the first retry (transient\n * blip clears fast), longer on the second (server is genuinely\n * slow / rate-limited and needs breathing room). */\nconst RETRY_BACKOFF_MS = [500, 1500];\n/** Per-page text cap · the chair only needs the gist, not the\n * pixel-perfect article. 6 KB ~ 1500 tokens, plenty for a useful\n * excerpt while staying well under the chair's context budget even\n * if the user shares two or three URLs. */\nconst MAX_TEXT_CHARS = 6_000;\n/** Hard cap on URLs we'll fetch per turn so a user pasting a giant\n * link dump doesn't fan out into a dozen parallel requests. */\nconst MAX_URLS_PER_TURN = 3;\n/** Look back at most this many user messages for URLs · most rooms\n * have at most one fresh URL drop per turn. */\nconst HISTORY_LOOKBACK = 6;\n\n/** Conservative URL regex · http(s)://, then any non-whitespace, non-\n * paren/bracket chars (so we don't capture trailing markdown). */\nconst URL_RE = /https?:\\/\\/[^\\s<>()\\[\\]【】]+/gi;\n\nconst FETCH_USER_AGENT =\n \"Boardroom/1.0 (+https://github.com/anthropics/boardroom · agent-research)\";\n\n/** Per-room URL → extract cache. The chair (and any future caller)\n * pays the network cost ONCE per URL per process lifetime; subsequent\n * fetches return the cached extract. Memory-bounded by URL count\n * rather than time — a long-running room won't accumulate gigabytes\n * because each entry is capped at MAX_TEXT_CHARS. */\nconst cache = new Map<string, string>();\n\nexport interface UrlExtract {\n url: string;\n ok: boolean;\n /** When ok=true: readable text excerpt. When ok=false: short error\n * string the chair can surface to the user. */\n text: string;\n /** Page title when we could parse it; \"\" otherwise. */\n title: string;\n}\n\n/** Pull every http(s) URL from a string. Dedupes inside the string\n * (keeps first occurrence) and trims trailing punctuation that\n * often glues itself onto a URL in prose (`.`, `,`, `)`, `]`). */\nfunction extractUrls(text: string): string[] {\n if (!text) return [];\n const seen = new Set<string>();\n const out: string[] = [];\n const matches = text.match(URL_RE) || [];\n for (const raw of matches) {\n let u = raw;\n // Strip trailing punctuation that's almost never part of a URL.\n while (u.length > 0 && /[.,;:!?)\\]'\"》)】]/.test(u[u.length - 1])) {\n u = u.slice(0, -1);\n }\n if (!u || seen.has(u)) continue;\n seen.add(u);\n out.push(u);\n }\n return out;\n}\n\n/** Walk recent user messages and collect distinct URLs, newest-last.\n * We skip non-user authors — the chair shouldn't auto-fetch its own\n * output or a director's hallucinated URL. */\nexport function collectUrlsFromHistory(history: Message[]): string[] {\n const recent = history.slice(-HISTORY_LOOKBACK);\n const seen = new Set<string>();\n const out: string[] = [];\n for (const m of recent) {\n if (m.authorKind !== \"user\" || !m.body) continue;\n for (const u of extractUrls(m.body)) {\n if (seen.has(u)) continue;\n seen.add(u);\n out.push(u);\n if (out.length >= MAX_URLS_PER_TURN) return out;\n }\n }\n return out;\n}\n\n/** Decode the basic HTML entities that sneak through tag-stripping.\n * Not a full entity decoder — just the handful that show up in real\n * prose (named + numeric for the common ASCII range). */\nfunction decodeEntities(s: string): string {\n return s\n .replace(/&/gi, \"&\")\n .replace(/</gi, \"<\")\n .replace(/>/gi, \">\")\n .replace(/"/gi, \"\\\"\")\n .replace(/'/gi, \"'\")\n .replace(/ /gi, \" \")\n .replace(/&#(\\d+);/g, (_, n) => {\n const code = parseInt(n, 10);\n if (!Number.isFinite(code) || code < 32 || code > 0x10ffff) return \"\";\n try { return String.fromCodePoint(code); } catch { return \"\"; }\n });\n}\n\n/** Quick-and-dirty HTML → text · drops <script>/<style> blocks\n * entirely (their content isn't reading material), strips remaining\n * tags, decodes the common entities, and collapses whitespace.\n * Picks up the page title from the first <title> tag we see. */\nfunction htmlToText(html: string): { title: string; text: string } {\n const titleMatch = /<title[^>]*>([\\s\\S]*?)<\\/title>/i.exec(html);\n const title = titleMatch ? decodeEntities(titleMatch[1]).trim().replace(/\\s+/g, \" \") : \"\";\n const cleaned = html\n .replace(/<script\\b[^>]*>[\\s\\S]*?<\\/script>/gi, \" \")\n .replace(/<style\\b[^>]*>[\\s\\S]*?<\\/style>/gi, \" \")\n .replace(/<noscript\\b[^>]*>[\\s\\S]*?<\\/noscript>/gi, \" \")\n .replace(/<!--[\\s\\S]*?-->/g, \" \")\n // Convert block-ish elements to newlines so the text still reads\n // like paragraphs after tag-stripping.\n .replace(/<\\/(p|div|h[1-6]|li|tr|article|section|header|footer|br)>/gi, \"\\n\")\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n .replace(/<[^>]+>/g, \" \");\n const decoded = decodeEntities(cleaned);\n const text = decoded\n .replace(/[ \\t]+/g, \" \")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .replace(/^\\s+|\\s+$/g, \"\");\n return { title, text };\n}\n\n/** Optional per-attempt notifier · the chair-turn flow uses this to\n * surface \"retrying example.com (2/3)…\" state on the tool-use row\n * while a flaky URL is being re-tried. Called BEFORE each retry,\n * not before the first attempt. */\nexport type FetchAttemptHook = (info: {\n url: string;\n attempt: number;\n totalAttempts: number;\n reason: string;\n nextDelayMs: number;\n}) => void;\n\n/** Internal · single attempt at fetching a URL. Returns either a full\n * extract on success or a structured failure with a `retryable` flag\n * that the outer retry loop uses to decide whether to try again. */\ntype AttemptResult =\n | { kind: \"ok\"; extract: UrlExtract }\n | { kind: \"fail\"; reason: string; retryable: boolean };\n\nasync function attemptFetch(url: string): Promise<AttemptResult> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);\n try {\n const r = await fetch(url, {\n signal: ctrl.signal,\n headers: {\n \"user-agent\": FETCH_USER_AGENT,\n accept: \"text/html,application/xhtml+xml,text/plain;q=0.9,*/*;q=0.5\",\n },\n redirect: \"follow\",\n });\n if (!r.ok) {\n // 5xx / 429 → retry. Other 4xx (404, 401, 410, …) are permanent.\n const retryable = r.status >= 500 || r.status === 408 || r.status === 425 || r.status === 429;\n return { kind: \"fail\", reason: `HTTP ${r.status}`, retryable };\n }\n const ct = (r.headers.get(\"content-type\") || \"\").toLowerCase();\n if (ct && !/text\\/html|text\\/plain|application\\/xhtml/.test(ct)) {\n return {\n kind: \"fail\",\n reason: `unsupported content-type: ${ct.split(\";\")[0]}`,\n retryable: false,\n };\n }\n const raw = await r.text();\n const { title, text } = ct.includes(\"text/plain\")\n ? { title: \"\", text: raw.trim() }\n : htmlToText(raw);\n if (!text) {\n // No readable text · could be a JS-rendered SPA. Retrying won't\n // change the response, so flag as permanent.\n return { kind: \"fail\", reason: \"page returned no readable text\", retryable: false };\n }\n const clipped = text.length > MAX_TEXT_CHARS\n ? text.slice(0, MAX_TEXT_CHARS).trim() + \"\\n\\n[…truncated]\"\n : text;\n return { kind: \"ok\", extract: { url, ok: true, title, text: clipped } };\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n const isTimeout = /aborted|timeout/i.test(msg);\n // Network-layer errors (timeout, ECONNRESET, EAI_AGAIN, fetch\n // failed) are all transient enough to retry. Anything else falls\n // through to the same retryable bucket since fetch() throws\n // asymmetrically across runtimes.\n return {\n kind: \"fail\",\n reason: isTimeout ? \"timed out\" : msg.slice(0, 120),\n retryable: true,\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\n/** Fetch a single URL with a hard timeout + bounded retry on transient\n * failure. Returns the extract on success, or an `ok: false` shape\n * with a short reason on permanent failure / exhausted retries.\n * `onAttempt` (optional) fires before each retry so callers can\n * surface a \"retrying X (N/M)…\" indicator while a flaky URL bounces.\n * Exported so the chair-turn flow can run per-URL fetches with\n * their own message-updated SSE events (one tool-use row per URL). */\nexport async function fetchOne(url: string, onAttempt?: FetchAttemptHook): Promise<UrlExtract> {\n const cached = cache.get(url);\n if (cached) {\n // Cached entries already passed extraction — split title back out.\n const sep = cached.indexOf(\"\\n\\n\");\n if (sep > 0) {\n return { url, ok: true, title: cached.slice(0, sep), text: cached.slice(sep + 2) };\n }\n return { url, ok: true, title: \"\", text: cached };\n }\n\n const totalAttempts = MAX_RETRIES + 1;\n let lastReason = \"\";\n for (let attempt = 1; attempt <= totalAttempts; attempt++) {\n const result = await attemptFetch(url);\n if (result.kind === \"ok\") {\n cache.set(url, `${result.extract.title}\\n\\n${result.extract.text}`);\n return result.extract;\n }\n lastReason = result.reason;\n // Bail immediately on permanent failures (404, unsupported MIME,\n // empty body) — retrying won't change the answer.\n if (!result.retryable) break;\n // Out of attempts → bail.\n if (attempt === totalAttempts) break;\n const nextDelayMs = RETRY_BACKOFF_MS[attempt - 1] ?? RETRY_BACKOFF_MS[RETRY_BACKOFF_MS.length - 1];\n if (onAttempt) {\n try {\n onAttempt({ url, attempt, totalAttempts, reason: result.reason, nextDelayMs });\n } catch { /* hook errors must not break the retry loop */ }\n }\n await new Promise((res) => setTimeout(res, nextDelayMs));\n }\n return { url, ok: false, title: \"\", text: lastReason || \"fetch failed\" };\n}\n\n/** Fetch every URL in a list, in parallel, capped at MAX_URLS_PER_TURN\n * (we trim before calling so the cap is enforced upstream too). */\nexport async function fetchUrls(urls: string[]): Promise<UrlExtract[]> {\n const trimmed = urls.slice(0, MAX_URLS_PER_TURN);\n if (trimmed.length === 0) return [];\n return Promise.all(trimmed.map((u) => fetchOne(u)));\n}\n\n/** Render the extracts as a system-prompt block · titled, paginated\n * by URL, ready to paste into the chair's system message. Empty\n * string when there are no extracts (so callers can `if (block)`). */\nexport function renderUrlContextBlock(extracts: UrlExtract[]): string {\n if (extracts.length === 0) return \"\";\n const parts: string[] = [\n \"─── SHARED MATERIALS · URLS THE USER LINKED ───\",\n \"These are excerpts the chair fetched from URLs the user shared. Treat as user-supplied context — quote sparingly, cite by URL, and don't speculate beyond what the excerpt actually says.\",\n \"\",\n ];\n extracts.forEach((e, i) => {\n parts.push(`### [${i + 1}] ${e.title || e.url}`);\n parts.push(`Source: ${e.url}`);\n if (e.ok) {\n parts.push(\"\");\n parts.push(e.text);\n } else {\n parts.push(`(fetch failed: ${e.text})`);\n }\n parts.push(\"\");\n parts.push(\"───\");\n parts.push(\"\");\n });\n return parts.join(\"\\n\");\n}\n\n/** Convenience wrapper · scan history → fetch → render. Returns the\n * empty string when no URLs were found or every fetch failed without\n * producing content. The chair calls this once per turn before\n * building its system prompt. */\nexport async function buildSharedMaterialsBlock(history: Message[]): Promise<string> {\n const urls = collectUrlsFromHistory(history);\n if (urls.length === 0) return \"\";\n const extracts = await fetchUrls(urls);\n return renderUrlContextBlock(extracts);\n}\n","/**\n * Chair (moderator) orchestration. The chair is a special agent\n * (role_kind = 'moderator') that fires on lifecycle events rather than\n * the round-robin queue:\n *\n * • clarify (room-opened) — one question or SKIP\n * • round-end — 1-sentence ping + 3 key points\n * • settings-changed — template-driven, no LLM call\n * • adjourn — handled in brief.ts\n *\n * Implementations stream into a single placeholder message just like a\n * director turn, but they don't enter the queue — they're invoked\n * directly from routes / room.ts.\n */\nimport { callLLMStream } from \"../ai/adapter.js\";\nimport { isModelV } from \"../ai/registry.js\";\nimport { runBraveSearch, formatSearchResults } from \"../ai/skills/web-search.js\";\nimport { getAgent, getChairAgent, incrementAgentTokens, type Agent } from \"../storage/agents.js\";\nimport { insertKeyPoint } from \"../storage/key_points.js\";\nimport { getKey, hasBraveKey } from \"../storage/keys.js\";\nimport {\n deleteMessage,\n insertMessage,\n listRecentMessages,\n updateMessageBody,\n} from \"../storage/messages.js\";\nimport { getPrefs } from \"../storage/prefs.js\";\nimport {\n reachableModelVs,\n reconcileAgentModels,\n} from \"../storage/reconcile-models.js\";\nimport {\n getRoom,\n listRoomMembers,\n setAwaitingClarify,\n setAwaitingContinue,\n} from \"../storage/rooms.js\";\n\nimport {\n buildChairClarifyMessages,\n buildChairConveningMessages,\n buildChairDirectMessages,\n buildChairRoundEndMessages,\n parseRoundEndOutput,\n} from \"./prompt.js\";\nimport { pickChairClarifyDecision, pickChairWebSearch } from \"./skill-picker.js\";\nimport { collectUrlsFromHistory, fetchOne, renderUrlContextBlock, type FetchAttemptHook, type UrlExtract } from \"../skills/url-fetch.js\";\nimport { roomBus } from \"./stream.js\";\n\n/** Hard cap on chair clarification turns to prevent runaway loops. */\nconst MAX_CLARIFY_TURNS = 3;\n\n/** Translate a raw LLM / network error into a chat-friendly one-liner.\n * Keeps the chair's failure visible in the room instead of swallowing\n * it in stderr. The most common failure modes:\n * · NoKeyError → user has the wrong / no key for the chair's model\n * · \"model not found\" / \"does not exist\" → chair model picked but the\n * direct API doesn't ship that id (e.g. openrouterOnly model\n * against the direct OpenAI SDK)\n * · network / timeout → transient, suggest retry */\nfunction friendlyChairError(chair: Agent, raw: string): string {\n const msg = (raw || \"\").trim();\n const lower = msg.toLowerCase();\n const modelLabel = chair.modelV ? `\\`${chair.modelV}\\`` : \"the chair model\";\n if (!msg) {\n return `chair couldn't reach ${modelLabel}. Check API keys in Preferences → API Key.`;\n }\n if (lower.includes(\"nokey\") || lower.includes(\"no api key\")) {\n return `chair model ${modelLabel} needs a provider key. Add one in Preferences → API Key.`;\n }\n if (lower.includes(\"model\") && (lower.includes(\"not found\") || lower.includes(\"does not exist\") || lower.includes(\"unknown\") || lower.includes(\"invalid\"))) {\n return `model ${modelLabel} isn't reachable with the current keys (often: a preview model that needs OpenRouter). Switch the chair to a model your provider supports, or add an OpenRouter key.`;\n }\n if (lower.includes(\"timeout\") || lower.includes(\"network\") || lower.includes(\"fetch\")) {\n return `network blip while calling ${modelLabel} — try again, or switch model.`;\n }\n // Generic fallback · include the upstream message so the user can\n // diagnose without checking server logs.\n return `${modelLabel} call failed: ${msg.slice(0, 240)}`;\n}\n\n/** Trim a URL to a host + 32-char tail for display in the tool-use\n * row · so a 200-char tracking-laden URL doesn't blow out the row's\n * width while still being recognisable. */\nfunction shortenUrl(url: string, max = 64): string {\n if (url.length <= max) return url;\n try {\n const u = new URL(url);\n const host = u.host;\n const tail = (u.pathname + u.search + u.hash).slice(0, max - host.length - 1);\n return `${host}${tail.length ? \"/\" + tail.replace(/^\\//, \"\") : \"\"}…`;\n } catch {\n return url.slice(0, max - 1) + \"…\";\n }\n}\n\n/** Format a byte count as a tight \"KB\" string for the tool-use row's\n * done state. Caps at the size we actually keep (≤6 KB). */\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n return `${(bytes / 1024).toFixed(1)} KB`;\n}\n\n/** Detect user-language for the chair's tool announcements. Returns\n * \"zh\" if any recent user message contains CJK, else \"en\". The chair\n * matches the user's language in its other turns; the preamble copy\n * follows the same convention. */\nfunction detectChairLang(history: ReturnType<typeof listRecentMessages>): \"zh\" | \"en\" {\n for (let i = history.length - 1; i >= 0; i--) {\n const m = history[i];\n if (m.authorKind === \"user\" && m.body) {\n if (/[一-鿿]/.test(m.body)) return \"zh\";\n return \"en\";\n }\n }\n return \"en\";\n}\n\n/**\n * Run the chair's `fetch-url` tool · for each URL detected in the\n * recent user history, post:\n * 1. A short chair \"preamble\" speech message announcing the tool\n * use (with avatar / name, like a normal chair turn) so the\n * tool-use rows below it have a clear \"the chair is doing this\"\n * anchor — without it, the rows visually float between the user\n * bubble and the chair's reply, reading as if they belonged to\n * the user.\n * 2. One tool-use status row per URL (compact mono micro-strip).\n * Then fire the fetches in parallel, flip each row to done|failed\n * when the fetch resolves, and finally return the concatenated\n * SHARED MATERIALS block to inject into the chair's system prompt.\n *\n * Returns \"\" when no URLs were found — the chair's prompt simply\n * doesn't get a shared-materials section in that case.\n */\nasync function runChairUrlTool(\n roomId: string,\n chair: Agent,\n history: ReturnType<typeof listRecentMessages>,\n): Promise<string> {\n const candidateUrls = collectUrlsFromHistory(history);\n if (candidateUrls.length === 0) return \"\";\n\n // Skip URLs already covered by a previous chair tool-use turn ·\n // otherwise every follow-up message in a room re-triggers the\n // preamble + tool-use rows for the same link, which the user sees\n // as a duplicate \"I'll fetch X\" announcement on every turn.\n const alreadyHandled = new Set<string>();\n for (const m of history) {\n const meta = m.meta;\n if (meta && typeof meta === \"object\" && !Array.isArray(meta)) {\n const r = meta as Record<string, unknown>;\n if (r.kind === \"tool-use\" && r.tool === \"fetch-url\" && typeof r.target === \"string\") {\n alreadyHandled.add(r.target);\n }\n }\n }\n const urls = candidateUrls.filter((u) => !alreadyHandled.has(u));\n const reusedUrls = candidateUrls.filter((u) => alreadyHandled.has(u));\n\n // For URLs we've already fetched, pull from the per-process cache\n // (fetchOne returns instantly without a network hit) so the chair\n // still has their content in its system prompt this turn — without\n // posting any new UI. Failed-previously URLs end up with ok=false\n // here too; we drop those silently.\n const reusedExtracts: UrlExtract[] = [];\n for (const url of reusedUrls) {\n try {\n const extract = await fetchOne(url);\n if (extract.ok) reusedExtracts.push(extract);\n } catch { /* swallow · silent silent */ }\n }\n\n // No fresh URLs to fetch · just reinject the cached content (if\n // any) and return. No preamble, no tool-use rows.\n if (urls.length === 0) {\n return renderUrlContextBlock(reusedExtracts);\n }\n\n // 1. Chair preamble · plain templated speech announcing the tool\n // use. Carries the chair's avatar in the chat so authorship is\n // unambiguous. Adapts to the user's language (zh/en).\n const lang = detectChairLang(history);\n const preambleBody = lang === \"zh\"\n ? (urls.length === 1\n ? \"我注意到你分享了一个链接 — 现在用 fetch-url 技能拉取页面内容。\"\n : `我注意到你分享了 ${urls.length} 个链接 — 现在用 fetch-url 技能拉取页面内容。`)\n : (urls.length === 1\n ? \"I noticed a URL in your message — reading it with my fetch-url tool.\"\n : `I noticed ${urls.length} URLs in your message — reading them with my fetch-url tool.`);\n const preamble = insertMessage({\n roomId,\n authorKind: \"agent\",\n authorId: chair.id,\n body: preambleBody,\n meta: {\n kind: \"tool-preamble\",\n tool: \"fetch-url\",\n urlCount: urls.length,\n streaming: false,\n speakerStatus: \"final\",\n },\n });\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: preamble.id,\n authorKind: \"agent\",\n authorId: chair.id,\n replyToId: null,\n body: preamble.body,\n meta: preamble.meta,\n roundNum: preamble.roundNum,\n createdAt: preamble.createdAt,\n });\n\n // 2. Insert one tool-use row per URL with status=running, capture\n // the message ids so we can update them when each fetch finishes.\n type Pending = { messageId: string; url: string; startedAt: number };\n const pending: Pending[] = [];\n for (const url of urls) {\n const m = insertMessage({\n roomId,\n authorKind: \"agent\",\n authorId: chair.id,\n body: lang === \"zh\" ? `读取 ${shortenUrl(url)}…` : `Reading ${shortenUrl(url)}…`,\n meta: {\n kind: \"tool-use\",\n tool: \"fetch-url\",\n status: \"running\",\n target: url,\n streaming: true,\n },\n });\n pending.push({ messageId: m.id, url, startedAt: Date.now() });\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: m.id,\n authorKind: \"agent\",\n authorId: chair.id,\n replyToId: null,\n body: m.body,\n meta: m.meta,\n roundNum: m.roundNum,\n createdAt: m.createdAt,\n });\n }\n\n // Fire all fetches in parallel · update each tool-use row as it\n // resolves so the user sees fast pages finish first. The onAttempt\n // hook fires between retries · we use it to flip the running row's\n // body to \"Retrying X (2/3)…\" so the user sees that the chair is\n // re-trying rather than stuck.\n const extracts: UrlExtract[] = [];\n await Promise.all(\n pending.map(async (p) => {\n const onAttempt: FetchAttemptHook = ({ attempt, totalAttempts, reason }) => {\n const retryBody = lang === \"zh\"\n ? `重试 ${shortenUrl(p.url)} (${attempt + 1}/${totalAttempts}) · ${reason}`\n : `Retrying ${shortenUrl(p.url)} (${attempt + 1}/${totalAttempts}) · ${reason}`;\n const retryMeta = {\n kind: \"tool-use\",\n tool: \"fetch-url\",\n status: \"running\",\n target: p.url,\n attempt: attempt + 1,\n totalAttempts,\n lastError: reason,\n streaming: true,\n };\n updateMessageBody(p.messageId, retryBody, retryMeta);\n roomBus.emit(roomId, {\n type: \"message-updated\",\n messageId: p.messageId,\n body: retryBody,\n meta: retryMeta,\n });\n };\n let extract: UrlExtract;\n try {\n extract = await fetchOne(p.url, onAttempt);\n } catch (e) {\n const reason = e instanceof Error ? e.message : String(e);\n extract = { url: p.url, ok: false, title: \"\", text: reason.slice(0, 120) };\n }\n const elapsedMs = Date.now() - p.startedAt;\n const sizeBytes = extract.ok ? Buffer.byteLength(extract.text, \"utf8\") : 0;\n const body = extract.ok\n ? (lang === \"zh\"\n ? `已读取 ${shortenUrl(p.url)}${sizeBytes ? \" · \" + formatSize(sizeBytes) : \"\"}`\n : `Read ${shortenUrl(p.url)}${sizeBytes ? \" · \" + formatSize(sizeBytes) : \"\"}`)\n : (lang === \"zh\"\n ? `读取 ${shortenUrl(p.url)} 失败 · ${extract.text}`\n : `Couldn't read ${shortenUrl(p.url)} · ${extract.text}`);\n const newMeta = {\n kind: \"tool-use\",\n tool: \"fetch-url\",\n status: extract.ok ? \"done\" : \"failed\",\n target: p.url,\n title: extract.ok ? extract.title : \"\",\n size: sizeBytes,\n elapsedMs,\n error: extract.ok ? null : extract.text,\n streaming: false,\n };\n updateMessageBody(p.messageId, body, newMeta);\n roomBus.emit(roomId, {\n type: \"message-updated\",\n messageId: p.messageId,\n body,\n meta: newMeta,\n });\n extracts.push(extract);\n }),\n );\n\n // Merge cached extracts (re-used URLs) with the freshly-fetched\n // ones so the chair's prompt sees every URL the user has shared in\n // this thread, not just the new ones from this turn.\n return renderUrlContextBlock([...reusedExtracts, ...extracts]);\n}\n\n/**\n * Run the chair's `web-search` tool · cheap haiku call decides whether\n * the latest user message would benefit from fresh web results, and if\n * so, runs a Brave Search query before the chair's reply streams.\n *\n * Tool-use UI mirrors `fetch-url`:\n * 1. Chair \"preamble\" speech message announcing the search.\n * 2. One tool-use status row (kind=tool-use, tool=web-search,\n * status=running) holding the query string.\n * 3. After Brave returns (~6 s timeout), flip the row to\n * done|failed and emit a `message-updated` SSE event.\n *\n * Returns a `formatSearchResults` block to inject as SHARED MATERIALS\n * in the chair's clarify prompt, or `\"\"` when no search ran (gating\n * failed, picker said null, or already searched this user message).\n *\n * Dedup · skip if any prior `tool: \"web-search\"` row exists between\n * the latest user message and now. The chair re-runs runChairUrlTool\n * on every turn (which uses an in-memory cache for already-fetched\n * URLs); web-search dedups by message rather than by query because\n * the query is regenerated by the picker each turn.\n */\nasync function runChairWebSearchTool(\n roomId: string,\n chair: Agent,\n history: ReturnType<typeof listRecentMessages>,\n signal?: AbortSignal,\n): Promise<string> {\n // Single gate · global Brave key. The per-agent webSearchEnabled\n // flag exists so users can keep specific DIRECTORS out of search\n // (e.g. a \"first-principles only\" voice). The chair's role is\n // moderation + grounding — disabling search for the chair would\n // make every time-sensitive question fail silently. Always allow\n // when the key is configured.\n if (!hasBraveKey()) return \"\";\n\n // Find the latest user message; skip if none.\n let lastUserIdx = -1;\n for (let i = history.length - 1; i >= 0; i--) {\n if (history[i].authorKind === \"user\" && history[i].body) {\n lastUserIdx = i;\n break;\n }\n }\n if (lastUserIdx === -1) return \"\";\n\n // Dedup · if any web-search tool-use already ran AFTER the latest\n // user message, this turn is a re-render of an in-flight clarify\n // call; don't re-search. (The prior chair turn already digested\n // the results into the assistant transcript.)\n for (let i = lastUserIdx + 1; i < history.length; i++) {\n const meta = history[i].meta;\n if (meta && typeof meta === \"object\" && !Array.isArray(meta)) {\n const r = meta as Record<string, unknown>;\n if (r.kind === \"tool-use\" && r.tool === \"web-search\") return \"\";\n }\n }\n\n // Decide search-or-not via the chair-side picker (haiku call).\n const query = await pickChairWebSearch({ history, signal });\n if (!query) return \"\";\n\n const apiKey = getKey(\"brave\");\n if (!apiKey) return \"\"; // race · key was wiped between gating and here\n\n const lang = detectChairLang(history);\n\n // Tiny pacing helper · ensures the user perceives the loading\n // sequence as deliberate stages (preamble → tool row appears →\n // searching → done). Brave on a hot connection often returns in\n // < 400 ms, which makes the \"running\" pulse flash by before the\n // user even sees it. We stage with small awaits so the chair feels\n // like it's working through the steps.\n const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\n // 1. Preamble · plain chair speech, carries the avatar so\n // authorship is unambiguous. Held alone for ~400ms so the\n // user reads the chair's intent before the tool row appears.\n const preambleBody = lang === \"zh\"\n ? `这个问题需要联网查一下 — 用 web-search 技能搜:${query}`\n : `This needs fresh info — running a web-search query: ${query}`;\n const preamble = insertMessage({\n roomId,\n authorKind: \"agent\",\n authorId: chair.id,\n body: preambleBody,\n meta: {\n kind: \"tool-preamble\",\n tool: \"web-search\",\n streaming: false,\n speakerStatus: \"final\",\n },\n });\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: preamble.id,\n authorKind: \"agent\",\n authorId: chair.id,\n replyToId: null,\n body: preamble.body,\n meta: preamble.meta,\n roundNum: preamble.roundNum,\n createdAt: preamble.createdAt,\n });\n await sleep(400);\n\n // 2. Tool-use row · status=running with the query as `target`.\n const startedAt = Date.now();\n const toolMsg = insertMessage({\n roomId,\n authorKind: \"agent\",\n authorId: chair.id,\n body: lang === \"zh\" ? `搜索 \"${query}\"…` : `Searching \"${query}\"…`,\n meta: {\n kind: \"tool-use\",\n tool: \"web-search\",\n status: \"running\",\n target: query,\n streaming: true,\n },\n });\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: toolMsg.id,\n authorKind: \"agent\",\n authorId: chair.id,\n replyToId: null,\n body: toolMsg.body,\n meta: toolMsg.meta,\n roundNum: toolMsg.roundNum,\n createdAt: toolMsg.createdAt,\n });\n\n // 3. Run Brave · timeout-bounded inside runBraveSearch. Floor the\n // visible \"running\" duration at 900ms so even a sub-200ms hot\n // cache hit registers as a deliberate step rather than a flash.\n let results: Awaited<ReturnType<typeof runBraveSearch>> = null;\n try {\n [results] = await Promise.all([\n runBraveSearch({ apiKey, query }).catch((e) => {\n process.stderr.write(`[chair-web-search] error: ${e instanceof Error ? e.message : String(e)}\\n`);\n return null;\n }),\n sleep(900),\n ]);\n } catch (e) {\n process.stderr.write(`[chair-web-search] error: ${e instanceof Error ? e.message : String(e)}\\n`);\n }\n const elapsedMs = Date.now() - startedAt;\n\n if (!results || results.length === 0) {\n const failBody = lang === \"zh\"\n ? `搜索 \"${query}\" 无结果`\n : `No results for \"${query}\"`;\n const failMeta = {\n kind: \"tool-use\",\n tool: \"web-search\",\n status: \"failed\",\n target: query,\n elapsedMs,\n streaming: false,\n };\n updateMessageBody(toolMsg.id, failBody, failMeta);\n roomBus.emit(roomId, {\n type: \"message-updated\",\n messageId: toolMsg.id,\n body: failBody,\n meta: failMeta,\n });\n return \"\";\n }\n\n // Body text is just the action + query · the result count + elapsed\n // time live in the card's banner stamp on the frontend, so we don't\n // duplicate them here.\n const doneBody = lang === \"zh\"\n ? `已搜 \"${query}\"`\n : `Searched \"${query}\"`;\n const doneMeta = {\n kind: \"tool-use\",\n tool: \"web-search\",\n status: \"done\",\n target: query,\n sources: results.map((r) => ({ title: r.title, url: r.url, description: r.description })),\n elapsedMs,\n streaming: false,\n };\n updateMessageBody(toolMsg.id, doneBody, doneMeta);\n roomBus.emit(roomId, {\n type: \"message-updated\",\n messageId: toolMsg.id,\n body: doneBody,\n meta: doneMeta,\n });\n\n return formatSearchResults(query, results);\n}\n\ninterface DispatchArgs {\n roomId: string;\n meta: Record<string, unknown>;\n}\n\nasync function streamChairMessage(args: DispatchArgs & {\n buildMessages: (opts: {\n chair: Agent;\n cast: Agent[];\n room: NonNullable<ReturnType<typeof getRoom>>;\n prefs: ReturnType<typeof getPrefs>;\n history: ReturnType<typeof listRecentMessages>;\n sharedMaterials?: string;\n }) => ReturnType<typeof buildChairClarifyMessages>;\n onComplete?: (body: string, messageId: string) => void;\n /** Override the default 320-token cap. Most chair turns (clarify,\n * round-end, convening) are short and the default holds; the\n * moderator's note at adjourn needs more headroom for 2 paragraphs. */\n maxTokens?: number;\n}): Promise<void> {\n const { roomId, meta, buildMessages, onComplete, maxTokens } = args;\n\n let chair = getChairAgent();\n if (!chair) return;\n // Self-heal · if the chair's stored modelV isn't reachable with the\n // CURRENT key set (e.g. user just added their first key but reconcile\n // never ran, or the boot reconcile got skipped), re-run the\n // reconciler and re-fetch the chair so its modelV swings to the\n // active carrier's primary before we attempt the LLM call. Without\n // this, a fresh-onboarded user with only Gemini configured would see\n // \"chair model `opus-4-7` needs a provider key\" because the seed\n // chair shipped on opus-4-7.\n if (!isModelV(chair.modelV) || !reachableModelVs().has(chair.modelV)) {\n try {\n reconcileAgentModels();\n const refreshed = getChairAgent();\n if (refreshed) chair = refreshed;\n } catch (e) {\n process.stderr.write(`[chair] reconcile-on-stale failed: ${e instanceof Error ? e.message : String(e)}\\n`);\n }\n }\n if (!isModelV(chair.modelV)) return;\n\n const room = getRoom(roomId);\n if (!room) return;\n\n const memberRows = listRoomMembers(roomId);\n // Director-only cast for the chair's prompt context.\n const cast: Agent[] = memberRows\n .map((m) => getAgent(m.agentId))\n .filter((a): a is Agent => a !== null && a.roleKind === \"director\");\n\n const prefs = getPrefs();\n const history = listRecentMessages(roomId, 30);\n\n // Chair's two pre-stream tools, run in parallel:\n //\n // · fetch-url · pulls every http(s) URL the user shared in recent\n // history, posts a per-URL tool-use row, returns SHARED MATERIALS.\n // · web-search · cheap haiku decides if the latest user message\n // needs fresh web context; if yes, posts a tool-use row + runs\n // Brave; if no, returns \"\" (zero-cost decision).\n //\n // Web search only runs for clarify turns — convening / round-end /\n // round-open are chair-driven structural moves, not user-question\n // answering, so they don't need fresh web context. Gating on\n // `meta.kind === \"clarify\"` keeps the cost bounded and the\n // tool-use UI from showing up in the wrong contexts.\n const isClarify = meta && (meta as { kind?: unknown }).kind === \"clarify\";\n const [urlMaterials, searchMaterials] = await Promise.all([\n runChairUrlTool(roomId, chair, history),\n isClarify ? runChairWebSearchTool(roomId, chair, history) : Promise.resolve(\"\"),\n ]);\n const sharedMaterials = [urlMaterials, searchMaterials].filter(Boolean).join(\"\\n\\n\");\n\n const llmMessages = buildMessages({ chair, cast, room, prefs, history, sharedMaterials });\n\n const placeholder = insertMessage({\n roomId,\n authorKind: \"agent\",\n authorId: chair.id,\n body: \"\",\n meta: { ...meta, speakerStatus: \"streaming\", streaming: true },\n });\n\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: placeholder.id,\n authorKind: \"agent\",\n authorId: chair.id,\n replyToId: null,\n body: \"\",\n meta: placeholder.meta,\n roundNum: placeholder.roundNum,\n createdAt: placeholder.createdAt,\n });\n\n let buf = \"\";\n let errored = false;\n let errorMessage = \"\";\n\n try {\n for await (const chunk of callLLMStream({\n modelV: chair.modelV as never,\n // Chair carrier pin — same semantics as director carrier override.\n carrier: chair.carrierPref ?? null,\n messages: llmMessages,\n // Chair output is short + structural — low temperature keeps the\n // SKIP token + POINTS: format reliable. Most chair turns fit in\n // 320 tokens; longer-form primitives can pass a higher maxTokens.\n temperature: 0.3,\n maxTokens: maxTokens ?? 320,\n })) {\n if (chunk.type === \"text\") {\n buf += chunk.delta;\n updateMessageBody(placeholder.id, buf, {\n ...meta,\n speakerStatus: \"streaming\",\n streaming: true,\n });\n roomBus.emit(roomId, {\n type: \"message-token\",\n messageId: placeholder.id,\n delta: chunk.delta,\n });\n } else if (chunk.type === \"usage\") {\n // Chair turns are short but still bill tokens; track on the\n // chair agent so its profile reflects total spend.\n incrementAgentTokens(chair.id, chunk.totalTokens);\n } else if (chunk.type === \"error\") {\n errored = true;\n errorMessage = chunk.message || errorMessage;\n roomBus.emit(roomId, {\n type: \"message-error\",\n messageId: placeholder.id,\n message: chunk.message,\n });\n }\n }\n } catch (e) {\n errored = true;\n errorMessage = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[chair] stream error: ${errorMessage}\\n`);\n // Surface the failure in the chat bubble. Without this, the empty\n // placeholder gets deleted below and the user sees no signal at\n // all that the chair tried + failed (the most common case: a\n // chair model that can't be reached with the configured keys).\n roomBus.emit(roomId, {\n type: \"message-error\",\n messageId: placeholder.id,\n message: errorMessage,\n });\n }\n\n if (!buf.trim()) {\n if (errored) {\n // Keep the placeholder · paint a human-readable error in the\n // bubble so the user understands why the chair didn't speak.\n // Common failure modes: model unreachable with current keys,\n // direct provider API rejected the model id (e.g. an\n // openrouterOnly model called against the direct SDK that\n // hasn't shipped it). The chair model name is included so the\n // user can see exactly which slot needs attention.\n const friendly = friendlyChairError(chair, errorMessage);\n const errorBody = `[chair stream error] ${friendly}`;\n updateMessageBody(placeholder.id, errorBody, {\n ...meta,\n speakerStatus: \"final\",\n streaming: false,\n error: true,\n errorReason: errorMessage,\n });\n roomBus.emit(roomId, {\n type: \"message-updated\",\n messageId: placeholder.id,\n body: errorBody,\n meta: {\n ...meta,\n speakerStatus: \"final\",\n streaming: false,\n error: true,\n errorReason: errorMessage,\n },\n });\n roomBus.emit(roomId, { type: \"message-final\", messageId: placeholder.id });\n return;\n }\n deleteMessage(placeholder.id);\n roomBus.emit(roomId, { type: \"message-removed\", messageId: placeholder.id, reason: \"empty\" });\n return;\n }\n\n if (errored) {\n updateMessageBody(placeholder.id, buf, {\n ...meta,\n speakerStatus: \"final\",\n streaming: false,\n error: true,\n });\n } else {\n updateMessageBody(placeholder.id, buf, {\n ...meta,\n speakerStatus: \"final\",\n streaming: false,\n });\n }\n\n // Run onComplete BEFORE emitting message-final. onComplete persists\n // side-effects (key points, awaiting_continue) and emits its own\n // config-event SSE — we want those to land on the client *first* so\n // the frontend's button gating sees the new state by the time\n // message-final arrives. Otherwise the user can double-click End\n // round in the gap and the backend rejects with \"already in round-end\".\n if (onComplete && !errored) {\n try { onComplete(buf, placeholder.id); }\n catch (e) { process.stderr.write(`[chair] onComplete: ${e instanceof Error ? e.message : String(e)}\\n`); }\n }\n\n roomBus.emit(roomId, { type: \"message-final\", messageId: placeholder.id });\n}\n\nexport interface ClarifyResult {\n /** True if the chair asked a (new) clarifying question — keep waiting. */\n asked: boolean;\n /** True if the chair is ready to release the directors. */\n ready: boolean;\n /** True if we hit the MAX_CLARIFY_TURNS cap and forced ready. */\n exhausted: boolean;\n}\n\n/**\n * Run one chair clarification turn. Multi-turn capable — each user reply\n * during the clarification phase invokes this again, until the chair\n * responds READY (or we hit MAX_CLARIFY_TURNS and force ready).\n *\n * The READY token is a control signal, not a chat utterance — when the\n * chair returns READY we delete the placeholder message so the user\n * never sees \"READY\" in the bubble.\n *\n * Side effects: sets `awaiting_clarify` on the room to track the\n * soft-pause state across requests.\n */\nexport async function runChairClarify(roomId: string): Promise<ClarifyResult> {\n // Count prior clarify turns in this room — the chair sees its\n // turn-of-budget in the prompt.\n const history = listRecentMessages(roomId, 50);\n const priorClarify = history.filter(\n (m) =>\n m.authorKind === \"agent\" &&\n m.meta &&\n (m.meta as { kind?: unknown }).kind === \"clarify\",\n ).length;\n const turnNumber = priorClarify + 1;\n\n // Hit the cap → don't even call the LLM. Just release directors.\n if (turnNumber > MAX_CLARIFY_TURNS) {\n setAwaitingClarify(roomId, false);\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"clarify-ready\",\n payload: { exhausted: true },\n createdAt: Date.now(),\n });\n return { asked: false, ready: true, exhausted: true };\n }\n\n // Pre-gate tools · run URL fetch + web search BEFORE the discipline\n // gate decides whether to clarify. Two reasons:\n // · When the gate skips clarify (subject is self-sufficient), the\n // tools would otherwise never fire — they live inside\n // streamChairMessage. Hoisting them here ensures the chair always\n // grounds time-sensitive topics before directors take over.\n // · When the gate decides to ask, streamChairMessage's internal\n // URL/search calls dedup against the visible tool-use bubbles\n // posted here (no double-fetch, no double UI).\n // Only on turn 1 — follow-up turns let streamChairMessage handle\n // tools, since it already wires them to clarify turns.\n const chairForTools = getChairAgent();\n if (turnNumber === 1 && chairForTools) {\n try {\n await Promise.all([\n runChairUrlTool(roomId, chairForTools, history),\n runChairWebSearchTool(roomId, chairForTools, history),\n ]);\n } catch (e) {\n process.stderr.write(\n `[chair-clarify] pre-gate tools failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n }\n }\n\n // Discipline lever · on the FIRST clarify turn only, run a cheap\n // haiku gate that decides whether the user's subject is already\n // self-sufficient. When it says skip, we release directors without a\n // chair LLM round-trip — the room opens fast and the chair stays out\n // of the way. Follow-up turns (turnNumber > 1) bypass the gate\n // because the user is mid-conversation with the chair already.\n if (turnNumber === 1) {\n const decision = await pickChairClarifyDecision({ history });\n if (!decision.shouldAsk) {\n setAwaitingClarify(roomId, false);\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"clarify-ready\",\n payload: { skipped: true, rationale: decision.rationale },\n createdAt: Date.now(),\n });\n return { asked: false, ready: true, exhausted: false };\n }\n }\n\n let asked = false;\n let readyMessageId: string | null = null;\n\n await streamChairMessage({\n roomId,\n meta: { kind: \"clarify\", turnNumber },\n buildMessages: (opts) =>\n buildChairClarifyMessages({ ...opts, turnNumber, maxTurns: MAX_CLARIFY_TURNS }),\n onComplete: (body, id) => {\n // Two acceptance shapes for the release path:\n // 1. Ack + READY · the new follow-up format. Body has prose\n // paragraph(s) followed by `READY` on its own line. The ack\n // stays as the visible chair message; READY is the control\n // token we strip + use to flip awaiting_clarify.\n // 2. Bare READY (or SKIP) · legacy / first-turn shorthand. No\n // visible message — placeholder gets deleted.\n // Anything else is treated as \"asked another question\" → keep\n // the bubble, leave awaiting_clarify on.\n const trimmed = body.trim();\n // Stripped bare-token check: noise-tolerant (\"**READY**\", \"READY.\")\n const bareUpper = trimmed.replace(/^[\\s`*\"'(\\[{]+|[\\s`*\"'.)\\]}]+$/g, \"\").toUpperCase();\n const isBareReady = bareUpper === \"READY\" || bareUpper === \"SKIP\";\n\n // Ack + READY check: split into trimmed non-empty lines, last\n // line is the token (with same noise tolerance).\n let isAckReady = false;\n let ackBody = \"\";\n if (!isBareReady) {\n const lines = trimmed.split(/\\r?\\n/).map((l) => l.trim()).filter(Boolean);\n if (lines.length >= 2) {\n const lastLine = lines[lines.length - 1] || \"\";\n const lastUpper = lastLine.replace(/^[\\s`*\"'(\\[{]+|[\\s`*\"'.)\\]}]+$/g, \"\").toUpperCase();\n if (lastUpper === \"READY\" || lastUpper === \"SKIP\") {\n isAckReady = true;\n ackBody = lines.slice(0, -1).join(\"\\n\\n\").trim();\n }\n }\n }\n\n const isReady = isBareReady || isAckReady;\n asked = !isReady;\n if (isBareReady) {\n readyMessageId = id;\n } else if (isAckReady && ackBody) {\n // Strip the READY token from the visible message — keep just\n // the acknowledgment. updateMessageBody + emit message-updated\n // so the frontend's ack reads cleanly.\n const newMeta = {\n kind: \"clarify\",\n turnNumber,\n ready: true,\n speakerStatus: \"final\" as const,\n streaming: false,\n };\n updateMessageBody(id, ackBody, newMeta);\n roomBus.emit(roomId, {\n type: \"message-updated\",\n messageId: id,\n body: ackBody,\n meta: newMeta,\n });\n }\n },\n });\n\n if (readyMessageId) {\n deleteMessage(readyMessageId);\n roomBus.emit(roomId, {\n type: \"message-removed\",\n messageId: readyMessageId,\n reason: \"chair-ready\",\n });\n }\n\n // Persist soft-pause: still clarifying if a question was asked, ready otherwise.\n setAwaitingClarify(roomId, asked);\n\n // Tell the frontend explicitly when we drop out of the clarify phase\n // so its local awaitingClarify mirror flips and the queue preview\n // gives way to the orchestrator's real director queue. The asked path\n // doesn't need a notice — the chair message already lands as a\n // kind=clarify chat message and the flag stays true.\n if (!asked) {\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"clarify-ready\",\n payload: {},\n createdAt: Date.now(),\n });\n }\n\n return { asked, ready: !asked, exhausted: false };\n}\n\n/**\n/**\n * Stream the chair's convening speech · 3-4 sentence introduction of\n * the auto-picked cast in the chair's voice. Posted right after the\n * picker has seated directors, before the chair runs its clarify\n * turn. Replaces the templated \"chair convened: A · B · C\" milestone\n * marker with a real speech that explains WHY each director was\n * picked for this specific subject.\n *\n * `picksWithReasons` carries the per-director rationale captured by\n * the picker's haiku call so the chair can quote / reference each\n * pick's specific angle, not generic flattery.\n */\nexport async function runChairConvening(\n roomId: string,\n picksWithReasons: Array<{ agent: Agent; reason: string }>,\n pickerRationale: string,\n): Promise<void> {\n await streamChairMessage({\n roomId,\n meta: {\n kind: \"convening\",\n picks: picksWithReasons.map((p) => ({ agentId: p.agent.id, reason: p.reason })),\n rationale: pickerRationale,\n },\n buildMessages: (opts) =>\n buildChairConveningMessages({ ...opts, picksWithReasons, pickerRationale }),\n });\n}\n\n/**\n * Run the chair's direct response to a user @chair message. The\n * message-route forks here when chair is mentioned, abort-pausing the\n * director queue so the chair speaks alone, briefly, then directors\n * resume. Strictly scoped to META observations about the discussion\n * (convergence · divergence · who hasn't engaged · contested terms);\n * NOT substantive content. Posted with meta.kind = \"chair-direct\"\n * so the frontend can style it as a \"responding to you\" bubble.\n */\nexport async function runChairDirectResponse(roomId: string): Promise<void> {\n await streamChairMessage({\n roomId,\n meta: { kind: \"chair-direct\" },\n buildMessages: buildChairDirectMessages,\n // 3-4 sentences ~ 60-100 words. 320 default fits English; CJK is\n // also under 320 tokens at this length. No override needed.\n });\n}\n\n/**\n * Run the chair's end-of-round close. Persists the parsed key points,\n * sets awaiting_continue on the room, and emits a config event so the\n * UI can lock the input + show the Continue / Adjourn affordance.\n */\nexport async function runChairRoundEnd(roomId: string, roundNum: number): Promise<void> {\n await streamChairMessage({\n roomId,\n meta: { kind: \"round-end\", roundNum },\n buildMessages: buildChairRoundEndMessages,\n onComplete: (body, messageId) => {\n const { points } = parseRoundEndOutput(body);\n const persisted = points.slice(0, 3).map((text, i) =>\n insertKeyPoint({\n roomId,\n messageId,\n roundNum,\n body: text,\n position: i,\n }),\n );\n setAwaitingContinue(roomId, true);\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"round-ended\",\n payload: {\n messageId,\n roundNum,\n keyPoints: persisted.map((p) => ({ id: p.id, body: p.body, position: p.position, vote: p.vote })),\n },\n createdAt: Date.now(),\n });\n },\n });\n}\n\n/**\n * After a round of director turns drains, the chair drops a chat\n * message with the round-end prompt — End round (open vote) or\n * Continue (next round). Template-driven, no LLM call: the buttons\n * carry the action, the body is the procedural ping.\n *\n * When the caller passes a `recommendation`, the chair's body adapts\n * to surface it (the haiku-level Synthesis primitive). The user still\n * picks End vs Continue from the UI buttons; the chair's job is to\n * make the better choice obvious with a one-line rationale. The meta\n * also carries `recommendation` so the frontend can highlight the\n * recommended button.\n *\n * Idempotency: caller (room.ts pumpQueue) only invokes this when the\n * cap was reached and no awaiting flag is set — so each round wraps\n * with at most one prompt.\n */\nexport function announceRoundPrompt(\n roomId: string,\n roundNum: number,\n recommendation?: { kind: \"end\" | \"continue\"; rationale: string },\n): void {\n const chair = getChairAgent();\n if (!chair) return;\n // Body shape: when a recommendation is supplied, lead with the chair's\n // call so the user reads it before pressing a button. When omitted\n // (recommendation undefined / haiku unavailable), fall back to the\n // template ping that's been here since v1.\n let body: string;\n if (recommendation) {\n const rationale = recommendation.rationale.trim();\n if (recommendation.kind === \"end\") {\n body = rationale\n ? `Round complete. ${rationale} The room has covered enough — file the brief, or continue if you want another reactive sweep.`\n : \"Round complete. The room has covered enough to file — vote on key points and end, or continue if you want another reactive sweep.\";\n } else {\n body = rationale\n ? `Round complete. ${rationale} Worth one more reactive round before filing — or end now if you've heard enough.`\n : \"Round complete. Worth one more reactive round before filing — or end now if you've heard enough.\";\n }\n } else {\n body = \"Round complete. Vote on key points to weight the next round, or continue without a vote.\";\n }\n const m = insertMessage({\n roomId,\n authorKind: \"agent\",\n authorId: chair.id,\n body,\n meta: {\n kind: \"round-prompt\",\n roundNum,\n // Carry the chair's call into meta so the UI can highlight the\n // recommended button. Absent when no recommendation was made.\n recommendation: recommendation\n ? { kind: recommendation.kind, rationale: recommendation.rationale }\n : null,\n speakerStatus: \"final\",\n streaming: false,\n },\n });\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: m.id,\n authorKind: \"agent\",\n authorId: chair.id,\n replyToId: null,\n body: m.body,\n meta: m.meta,\n roundNum: m.roundNum,\n createdAt: m.createdAt,\n });\n roomBus.emit(roomId, { type: \"message-final\", messageId: m.id });\n}\n\n/**\n * Mid-round chair intervention · template-driven (no LLM call here · the\n * picker has already produced the body). The chair drops a one-sentence\n * frame note BEFORE the next director speaks when the picker detects\n * substantive misalignment in the prior turns (talking past each other,\n * undefined load-bearing term, hidden trade-off, circling).\n *\n * Bias-to-skip lives in the picker: if you reach this function, the\n * intervention is already vetted. Posted with kind=intervention so the\n * UI can style it as a moderator note rather than a turn.\n */\nexport function announceIntervention(\n roomId: string,\n body: string,\n rationale?: string,\n): void {\n const chair = getChairAgent();\n if (!chair) return;\n const text = body.trim();\n if (!text) return;\n const m = insertMessage({\n roomId,\n authorKind: \"agent\",\n authorId: chair.id,\n body: text,\n meta: {\n kind: \"intervention\",\n rationale: rationale || \"\",\n speakerStatus: \"final\",\n streaming: false,\n },\n });\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: m.id,\n authorKind: \"agent\",\n authorId: chair.id,\n replyToId: null,\n body: m.body,\n meta: m.meta,\n roundNum: m.roundNum,\n createdAt: m.createdAt,\n });\n roomBus.emit(roomId, { type: \"message-final\", messageId: m.id });\n}\n\n/**\n * Drop a chair-authored notice when an upstream API returns a quota /\n * billing / credit-exhausted error. Replaces the silent placeholder-\n * deletion path · the user sees the chair (in-character, in the chat\n * stream) explain why the directors stopped speaking, rather than\n * having a turn vanish without trace.\n *\n * meta.kind = \"billing-notice\"\n *\n * The frontend can target this kind for a yellow / warning render\n * treatment. Provider hint (e.g. \"OpenAI\") gets folded into the body\n * when we can extract it; otherwise the message stays generic.\n */\nexport function announceBillingNotice(\n roomId: string,\n opts: { providerHint: string | null; rawError: string; agentName?: string },\n): void {\n const chair = getChairAgent();\n if (!chair) return;\n const carrier = opts.providerHint ?? \"上游模型\";\n const speaker = opts.agentName ? `(${opts.agentName} 那边收到 ${carrier} 返回的额度耗尽错误)\\n\\n` : \"\";\n const body =\n `${speaker}先停一下 · ${carrier} 账户当前额度不足,无法继续这轮发言。\\n\\n` +\n `请到 Preference → API Key 检查计费状态:\\n` +\n `· 给当前 carrier 充值后重试,或\\n` +\n `· 删除该 key、换一个有余额的 carrier(OpenRouter / Anthropic / Google / xAI),系统会自动把所有 agent 切到新 carrier 的旗舰模型。`;\n const m = insertMessage({\n roomId,\n authorKind: \"agent\",\n authorId: chair.id,\n body,\n meta: {\n kind: \"billing-notice\",\n providerHint: opts.providerHint,\n rawError: opts.rawError,\n speakerStatus: \"final\",\n streaming: false,\n },\n });\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: m.id,\n authorKind: \"agent\",\n authorId: chair.id,\n replyToId: null,\n body: m.body,\n meta: m.meta,\n roundNum: m.roundNum,\n createdAt: m.createdAt,\n });\n roomBus.emit(roomId, { type: \"message-final\", messageId: m.id });\n}\n\n/**\n * Announce the start of a fresh director round. Two flavours:\n * · opening sweep · directors speak in PARALLEL from their own\n * lenses (they don't see each other's drafts in this round) —\n * prevents the \"first speaker anchors everyone\" convergence\n * problem.\n * · reactive sweep · directors react to one another's prior\n * contributions (Continue clicked, normal cross-pollination).\n *\n * Persists as a chair message with `meta.kind === \"round-open\"` so the\n * client can render it as a milestone marker (chip + flanking lines)\n * making the mode-shift legible to the user. Skipped when the round\n * is a single forced speaker (e.g., user @-mention reply).\n */\nexport function announceRoundOpen(\n roomId: string,\n roundNum: number,\n opening: boolean,\n): void {\n const chair = getChairAgent();\n if (!chair) return;\n const body = opening\n ? `Round ${roundNum} · directors speak in parallel — independent perspectives from each lens.`\n : `Round ${roundNum} · directors react to one another now — extending, pushing back, and sharpening.`;\n const m = insertMessage({\n roomId,\n authorKind: \"agent\",\n authorId: chair.id,\n body,\n meta: { kind: \"round-open\", roundNum, opening },\n roundNum,\n });\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: m.id,\n authorKind: \"agent\",\n authorId: chair.id,\n replyToId: null,\n body: m.body,\n meta: m.meta,\n roundNum: m.roundNum,\n createdAt: m.createdAt,\n });\n roomBus.emit(roomId, { type: \"message-final\", messageId: m.id });\n}\n\n/**\n * Announce that the room is adjourning without filing a brief. Chair-\n * authored, template-driven, persists in the message store so it\n * survives reload and renders as the closing marker card in chat.\n * The client renders this as a milestone card (not a bubble) using\n * `meta.kind === \"no-brief\"`.\n */\nexport function announceAdjournNoBrief(roomId: string): void {\n const chair = getChairAgent();\n if (!chair) return;\n const body =\n \"Adjourned without filing a brief. The chair (you) declared no report is needed for this session.\";\n const m = insertMessage({\n roomId,\n authorKind: \"agent\",\n authorId: chair.id,\n body,\n meta: { kind: \"no-brief\" },\n });\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: m.id,\n authorKind: \"agent\",\n authorId: chair.id,\n replyToId: null,\n body: m.body,\n meta: m.meta,\n roundNum: m.roundNum,\n createdAt: m.createdAt,\n });\n roomBus.emit(roomId, { type: \"message-final\", messageId: m.id });\n}\n\n/**\n * Announce members joining or leaving the room. Template-driven so the\n * announcement is instant; the chair voice keeps continuity with the\n * other lifecycle pings. Caller passes `added` / `removed` agent ids;\n * we resolve names + role tags from the agents store. No-op if both\n * lists are empty.\n */\nexport function announceMemberChange(\n roomId: string,\n added: string[],\n removed: string[],\n): void {\n const chair = getChairAgent();\n if (!chair) return;\n if (added.length === 0 && removed.length === 0) return;\n\n function namesAndRoles(ids: string[]): string {\n return ids\n .map((id) => {\n const a = getAgent(id);\n if (!a) return null;\n const role = a.roleTag ? ` *(${a.roleTag})*` : \"\";\n return `**${a.name}**${role}`;\n })\n .filter((s): s is string => s !== null)\n .join(\", \");\n }\n\n const lines: string[] = [];\n if (added.length > 0) {\n const list = namesAndRoles(added);\n if (list) {\n lines.push(\n added.length === 1\n ? `Welcome ${list} to the room. Joining the rotation now.`\n : `Welcoming ${list} to the room. Joining the rotation now.`,\n );\n }\n }\n if (removed.length > 0) {\n const list = namesAndRoles(removed);\n if (list) {\n lines.push(\n removed.length === 1\n ? `${list} has left the room.`\n : `${list} have left the room.`,\n );\n }\n }\n if (lines.length === 0) return;\n\n const body = lines.join(\" \");\n const m = insertMessage({\n roomId,\n authorKind: \"agent\",\n authorId: chair.id,\n body,\n meta: { kind: \"members\", added, removed },\n });\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: m.id,\n authorKind: \"agent\",\n authorId: chair.id,\n replyToId: null,\n body: m.body,\n meta: m.meta,\n roundNum: m.roundNum,\n createdAt: m.createdAt,\n });\n roomBus.emit(roomId, { type: \"message-final\", messageId: m.id });\n}\n\n/**\n * Announce a settings change in chat as a chair message. Template-driven\n * (no LLM call) so it's instant and free; the chair persona keeps the\n * voice consistent with their other turns.\n */\nexport function announceSettingsChange(\n roomId: string,\n changes: Record<string, { from: unknown; to: unknown }>,\n): void {\n const chair = getChairAgent();\n if (!chair) return;\n\n const lines: string[] = [];\n if (changes.mode) {\n lines.push(`Tone: ${String(changes.mode.from)} → **${String(changes.mode.to)}**.`);\n }\n if (changes.intensity) {\n lines.push(`Intensity: ${String(changes.intensity.from)} → **${String(changes.intensity.to)}**.`);\n }\n if (changes.briefStyle) {\n lines.push(`Report style: ${String(changes.briefStyle.from)} → **${String(changes.briefStyle.to)}**.`);\n }\n if (lines.length === 0) return;\n\n const body = lines.join(\" \") + \" Continuing.\";\n const m = insertMessage({\n roomId,\n authorKind: \"agent\",\n authorId: chair.id,\n body,\n meta: { kind: \"settings\", changes },\n });\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: m.id,\n authorKind: \"agent\",\n authorId: chair.id,\n replyToId: null,\n body: m.body,\n meta: m.meta,\n roundNum: m.roundNum,\n createdAt: m.createdAt,\n });\n roomBus.emit(roomId, { type: \"message-final\", messageId: m.id });\n}\n","/**\n * Long-term memory extraction · runs at room adjourn for every agent\n * that participated. Each agent (directors + chair) gets a small LLM\n * pass: \"from THIS room, what did you learn about the user worth\n * carrying into future rooms?\" The output is parsed into 0-3 first-\n * person facts that get stored as `agent_memories` rows.\n *\n * Skipped entirely when room.incognito = true. Failures are\n * non-fatal — adjourn still completes, the agent just doesn't get\n * new memories from this room.\n */\nimport { callLLM } from \"../ai/adapter.js\";\nimport { isModelV } from \"../ai/registry.js\";\nimport { getAgent, listAllAgents, type Agent } from \"../storage/agents.js\";\nimport { insertMemory, type MemoryKind } from \"../storage/memories.js\";\nimport { listRecentMessages } from \"../storage/messages.js\";\nimport { getPrefs } from \"../storage/prefs.js\";\nimport { getRoom, listRoomMembers } from \"../storage/rooms.js\";\n\nconst ALLOWED_KINDS: ReadonlySet<MemoryKind> = new Set([\"fact\", \"observation\", \"preference\", \"goal\"]);\n\ninterface ExtractedNote {\n content: string;\n kind: MemoryKind;\n confidence: number;\n}\n\nconst MEMORY_HISTORY_TURNS = 60;\n\n/**\n * Run the extraction pass for every member of the room. Best-effort:\n * each agent's call is independent, errors are swallowed per-agent so\n * one model failure can't block the whole room from filing.\n */\nexport async function extractMemoriesAfterAdjourn(roomId: string): Promise<void> {\n const room = getRoom(roomId);\n if (!room) return;\n if (room.incognito) {\n process.stderr.write(`[memory] room ${roomId.slice(0, 8)} incognito · skipping extraction\\n`);\n return;\n }\n\n const memberRows = listRoomMembers(roomId);\n if (memberRows.length === 0) return;\n\n // Chair + directors all participate. listRoomMembers includes the\n // chair (position -1), so iterate everyone with roleKind agent.\n const agents: Agent[] = memberRows\n .map((m) => getAgent(m.agentId))\n .filter((a): a is Agent => a !== null);\n if (agents.length === 0) return;\n\n // Pull history once · all agents share the same transcript view.\n const history = listRecentMessages(roomId, MEMORY_HISTORY_TURNS);\n if (history.length === 0) return;\n\n const prefs = getPrefs();\n const userName = prefs.name || \"the user\";\n\n // Format the transcript for the extraction prompt — speakers labeled,\n // directors by handle, chair as \"Chair\", user by their name.\n const transcript = history\n .filter((m) => m.body && m.body.trim())\n .map((m) => {\n if (m.authorKind === \"user\") return `[${userName}] ${m.body}`;\n if (m.authorKind === \"system\") return `[system] ${m.body}`;\n const a = agents.find((x) => x.id === m.authorId);\n const label = a ? `${a.name} · ${a.handle}` : \"Director\";\n return `[${label}] ${m.body}`;\n })\n .join(\"\\n\\n\");\n\n await Promise.all(\n agents.map(async (agent) => {\n if (!isModelV(agent.modelV)) return;\n try {\n const notes = await runExtractionForAgent(agent, transcript, userName);\n for (const note of notes) {\n insertMemory({\n agentId: agent.id,\n content: note.content,\n kind: note.kind,\n source: \"extracted\",\n sourceRoom: roomId,\n confidence: note.confidence,\n });\n }\n process.stderr.write(\n `[memory] ${agent.name} (${agent.id.slice(0, 8)}) · ${notes.length} note(s) extracted\\n`,\n );\n } catch (e) {\n process.stderr.write(\n `[memory] ${agent.name} extraction failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n }\n }),\n );\n}\n\n/** Run one agent's extraction · single non-streaming call returning\n * a parseable list. Uses the agent's configured modelV so the lens\n * and language preferences carry through; capped at 600 tokens. */\nasync function runExtractionForAgent(\n agent: Agent,\n transcript: string,\n userName: string,\n): Promise<ExtractedNote[]> {\n const system = [\n `You are ${agent.name} (${agent.handle}), a director participating in a multi-agent boardroom.`,\n \"\",\n `Your role description:`,\n agent.bio || agent.roleTag || \"(no description)\",\n \"\",\n `─── YOUR TASK · MEMORY EXTRACTION ───`,\n `The room you just participated in has just adjourned. Your job NOW is purely meta — extract 0 to 3 things about ${userName} that are worth REMEMBERING for FUTURE rooms.`,\n \"\",\n `What counts:`,\n `· Stable facts about ${userName} (occupation, project, expertise, language preference, recurring constraints).`,\n `· Reading-of-the-user observations through YOUR specific lens (different agents notice different things).`,\n `· Stated preferences (how they like to think, format, push back).`,\n `· Stated goals with concrete horizons.`,\n \"\",\n `What does NOT count (do NOT extract):`,\n `· Anything about the discussion topic itself or what the directors said.`,\n `· Generic platitudes (\"the user is thoughtful\").`,\n `· Things the user just told ${agent.name} once that aren't generalisable.`,\n `· Anything you only inferred — only assert what the transcript supports.`,\n \"\",\n `Output STRICT format · one JSON line per note. NO prose, NO markdown, NO code fence. If nothing is worth remembering, output the literal token NONE on a single line.`,\n \"\",\n `Each line:`,\n `{\"content\": \"first-person sentence about ${userName}\", \"kind\": \"fact|observation|preference|goal\", \"confidence\": 0.0-1.0}`,\n \"\",\n `Examples (English):`,\n `{\"content\": \"${userName} is a cofounder building an HR SaaS focused on resume screening\", \"kind\": \"fact\", \"confidence\": 0.9}`,\n `{\"content\": \"${userName} struggles to define load-bearing terms before reasoning\", \"kind\": \"observation\", \"confidence\": 0.7}`,\n \"\",\n `Examples (Chinese · use Chinese when the room was conducted in Chinese):`,\n `{\"content\": \"${userName} 是一家 HR SaaS 的 cofounder,主要做简历筛选自动化\", \"kind\": \"fact\", \"confidence\": 0.9}`,\n \"\",\n `Hard rules:`,\n `· Output ONLY JSON lines OR the literal token NONE. Nothing else.`,\n `· Maximum 3 notes. Often 0 is correct.`,\n `· Match the language the room was conducted in.`,\n `· Use first-person assertions about ${userName}, not \"the user\".`,\n ].join(\"\\n\");\n\n const user = [\n `─── ROOM TRANSCRIPT (just adjourned) ───`,\n transcript || \"(empty room — nothing to extract)\",\n ``,\n `─── YOUR EXTRACTION ───`,\n `0–3 JSON-line notes about ${userName} from your lens, OR the literal token NONE.`,\n ].join(\"\\n\");\n\n const raw = await callLLM({\n modelV: agent.modelV as never,\n messages: [\n { role: \"system\", content: system },\n { role: \"user\", content: user },\n ],\n temperature: 0.2,\n maxTokens: 600,\n });\n\n return parseExtractionOutput(raw);\n}\n\n/** Parse the LLM's line-delimited JSON output. Tolerates code-fenced\n * output, blank lines, and the NONE escape token. Skips lines that\n * don't parse cleanly rather than throwing — partial extraction is\n * better than dropping the whole batch. */\nexport function parseExtractionOutput(raw: string): ExtractedNote[] {\n const stripped = raw\n .trim()\n // strip a code fence if the model wrapped its output\n .replace(/^```(?:json)?\\s*/i, \"\")\n .replace(/```\\s*$/i, \"\")\n .trim();\n if (!stripped) return [];\n if (/^\\s*NONE\\s*$/i.test(stripped)) return [];\n\n const notes: ExtractedNote[] = [];\n for (const rawLine of stripped.split(\"\\n\")) {\n const line = rawLine.trim();\n if (!line) continue;\n if (!line.startsWith(\"{\")) continue;\n let parsed: unknown;\n try { parsed = JSON.parse(line); }\n catch { continue; }\n if (!parsed || typeof parsed !== \"object\") continue;\n const obj = parsed as Record<string, unknown>;\n const content = typeof obj.content === \"string\" ? obj.content.trim() : \"\";\n if (!content || content.length > 280) continue;\n const kindRaw = typeof obj.kind === \"string\" ? obj.kind : \"fact\";\n const kind: MemoryKind = ALLOWED_KINDS.has(kindRaw as MemoryKind)\n ? (kindRaw as MemoryKind)\n : \"fact\";\n const conf =\n typeof obj.confidence === \"number\" && Number.isFinite(obj.confidence)\n ? Math.max(0, Math.min(1, obj.confidence))\n : 0.7;\n notes.push({ content, kind, confidence: conf });\n if (notes.length >= 3) break;\n }\n return notes;\n}\n\n/** Convenience · used by orchestrator route to mention all agents\n * scheduled to extract (purely for stderr logging). */\nexport function listExtractionTargets(roomId: string): Agent[] {\n const memberRows = listRoomMembers(roomId);\n if (memberRows.length === 0) return [];\n return memberRows\n .map((m) => getAgent(m.agentId))\n .filter((a): a is Agent => a !== null);\n}\n\n/** Test seam — exposes listAllAgents for cases where the room has\n * pruned members but you still want every agent to extract. Not\n * used in v1 (we only extract for room participants). */\nexport const _internals = { listAllAgents };\n","/** Lifecycle events for a room — opened / adjourned / member-add / pause / etc. */\nimport { newId } from \"../utils/id.js\";\n\nimport { getDb } from \"./db.js\";\n\nexport interface ConfigEvent {\n id: string;\n roomId: string;\n kind: string;\n payload: Record<string, unknown> | null;\n actorKind: \"user\" | \"system\";\n createdAt: number;\n}\n\ninterface Row {\n id: string;\n room_id: string;\n kind: string;\n payload: string | null;\n actor_kind: string;\n created_at: number;\n}\n\nfunction mapRow(row: Row): ConfigEvent {\n return {\n id: row.id,\n roomId: row.room_id,\n kind: row.kind,\n payload: row.payload ? (JSON.parse(row.payload) as Record<string, unknown>) : null,\n actorKind: row.actor_kind as \"user\" | \"system\",\n createdAt: row.created_at,\n };\n}\n\nexport function listConfigEvents(roomId: string): ConfigEvent[] {\n const rows = getDb()\n .prepare(\n \"SELECT id, room_id, kind, payload, actor_kind, created_at FROM config_events \" +\n \"WHERE room_id = ? ORDER BY created_at ASC\",\n )\n .all(roomId) as Row[];\n return rows.map(mapRow);\n}\n\nexport interface ConfigEventInsert {\n roomId: string;\n kind: string;\n payload?: Record<string, unknown> | null;\n actorKind: \"user\" | \"system\";\n}\n\nexport function insertConfigEvent(e: ConfigEventInsert): ConfigEvent {\n const id = newId();\n const now = Date.now();\n getDb()\n .prepare(\n \"INSERT INTO config_events (id, room_id, kind, payload, actor_kind, created_at) VALUES (?, ?, ?, ?, ?, ?)\",\n )\n .run(id, e.roomId, e.kind, e.payload ? JSON.stringify(e.payload) : null, e.actorKind, now);\n return {\n id,\n roomId: e.roomId,\n kind: e.kind,\n payload: e.payload ?? null,\n actorKind: e.actorKind,\n createdAt: now,\n };\n}\n","/**\n * Detect billing / quota / credit-exhaustion errors across providers.\n *\n * Each provider phrases this slightly differently in their error body —\n * we pattern-match on the substrings that don't change over time:\n *\n * OpenAI · \"insufficient_quota\" / \"exceeded your current quota\"\n * Anthropic · \"credit balance is too low\" / \"billing\"\n * Google · \"quota exceeded\" / \"billing\"\n * xAI · \"insufficient credits\" / \"billing\"\n * OpenRouter· passes the underlying provider's message through; usually\n * also surfaces \"insufficient credits\" in its own envelope\n *\n * Used by the orchestrator to swap a failed director turn for a chair-\n * authored explainer, so the user sees a human-readable notice instead\n * of the silent placeholder-deletion that used to happen.\n */\n\nconst BILLING_NEEDLES = [\n \"insufficient_quota\",\n \"insufficient quota\",\n \"exceeded your current quota\",\n \"exceeded your quota\",\n \"credit balance is too low\",\n \"credit balance\",\n \"insufficient credits\",\n \"insufficient credit\",\n \"quota exceeded\",\n \"billing\",\n \"payment required\",\n \"402\",\n // xAI · \"Your newly created team doesn't have any credits or\n // licenses yet.\" Plus close variants observed across providers when\n // the account is provisioned but funded for $0.\n \"any credits\",\n \"any credit\",\n \"no credits\",\n \"no credit \", // trailing space avoids matching \"no credit_card\" etc.\n \"out of credit\",\n \"credits or licenses\",\n \"no licenses\",\n \"any licenses\",\n];\n\n/** True when the error message looks like a quota / credit issue. We\n * match on lowercased message; that's enough to cover every provider's\n * phrasing without overfitting to specific status codes. */\nexport function isBillingError(message: string | null | undefined): boolean {\n if (!message) return false;\n const m = message.toLowerCase();\n return BILLING_NEEDLES.some((needle) => m.includes(needle));\n}\n\n/** Best-effort provider-name extraction so the chair message can name\n * the carrier the user needs to top up (\"OpenAI\"/\"Anthropic\"/…). The\n * hint comes from message content — provider error bodies typically\n * mention themselves, and our adapter prefixes \"[adapter] direct:openai\"\n * type strings into the surrounding logs which sometimes leak through.\n * Returns null when we can't tell — caller should phrase generically. */\nexport function extractProviderHint(message: string | null | undefined): string | null {\n if (!message) return null;\n const m = message.toLowerCase();\n if (m.includes(\"openrouter\")) return \"OpenRouter\";\n if (m.includes(\"openai\") || m.includes(\"gpt-\") || m.includes(\"insufficient_quota\")) return \"OpenAI\";\n if (m.includes(\"anthropic\") || m.includes(\"claude\")) return \"Anthropic\";\n if (m.includes(\"google\") || m.includes(\"gemini\")) return \"Google\";\n if (m.includes(\"xai\") || m.includes(\"grok\")) return \"xAI\";\n return null;\n}\n","/**\n * RoomOrchestrator · drives a single Room's life.\n *\n * Per-room state is held in a Map keyed by room id (M3-M4 are single-process,\n * so this is fine). State includes:\n * · queue — directors lined up to speak this round\n * · inflight — AbortController for the speaker currently streaming\n * · processing — guard against double-pump\n * · roundNum — round_num shared by user msg + all director replies\n *\n * Round-robin (Q1 · A): when the user sends a message with no @mention, every\n * director in the room speaks once, in their join-order position. With an\n * @mention, only that director replies.\n *\n * Re-tick mid-turn (next user message arriving while the queue is still\n * draining) aborts the current speaker and replaces the queue with the new\n * round's plan. Partial responses stay in the transcript with finishReason:\n * 'aborted' in their meta — visible but flagged.\n */\nimport { callLLMStream, type LLMMessage } from \"../ai/adapter.js\";\nimport { isModelV } from \"../ai/registry.js\";\nimport { getAgent, incrementAgentTokens, type Agent } from \"../storage/agents.js\";\nimport { insertConfigEvent } from \"../storage/config-events.js\";\nimport { listKeyPointsForRoom } from \"../storage/key_points.js\";\nimport {\n deleteMessage,\n insertMessage,\n listRecentMessages,\n nextUserRoundNum,\n updateMessageBody,\n type Message,\n} from \"../storage/messages.js\";\nimport { getKey, hasBraveKey } from \"../storage/keys.js\";\nimport { getPrefs } from \"../storage/prefs.js\";\nimport {\n reachableModelVs,\n reconcileAgentModels,\n} from \"../storage/reconcile-models.js\";\nimport { getRoom, listRoomMembers, setAwaitingContinue, setRoomStatus, type Room } from \"../storage/rooms.js\";\n\nimport { formatSearchResults, runBraveSearch } from \"../ai/skills/web-search.js\";\nimport { isBillingError, extractProviderHint } from \"../ai/billing-error.js\";\nimport {\n announceBillingNotice,\n announceIntervention,\n announceRoundOpen,\n announceRoundPrompt,\n runChairDirectResponse,\n} from \"./chair.js\";\nimport { buildDirectorMessages } from \"./prompt.js\";\nimport { pickNextSpeaker, pickRoundWrap, pickSkills } from \"./skill-picker.js\";\nimport { roomBus, type RoomEvent } from \"./stream.js\";\nimport { listSkillsForAgent } from \"../storage/skills.js\";\n\ntype QueueStatus = \"queued\" | \"speaking\";\n\ninterface QueueEntry {\n agentId: string;\n status: QueueStatus;\n}\n\ninterface PendingUserAfterCurrent {\n text: string;\n mentions: string[];\n replyToId: string | null;\n}\n\ninterface RoomState {\n queue: QueueEntry[];\n inflight: AbortController | null;\n processing: boolean;\n roundNum: number;\n /** Total speakers fired since the last user message. Capped to avoid runaway. */\n speakersThisTurn: number;\n /** Hard cap for this turn (cast.length × 3, min 9). */\n maxSpeakersThisTurn: number;\n /** Soft pause flag — set by user clicking \"After they finish\" while a director streams.\n * pumpQueue checks this between speakers and transitions the room to paused. */\n pauseAfterCurrent: boolean;\n /** A user message the user opted to deliver _after_ the currently\n * speaking director finishes. pumpQueue drains this between turns\n * so the message lands BEFORE the next director starts — and so\n * the next director's response is keyed off the user's question. */\n pendingUserAfterCurrent: PendingUserAfterCurrent | null;\n /** Snapshot taken when the room is paused so resume can pick up the\n * same queue / round / speaker-count instead of starting from scratch.\n * Cleared on tickRoom (a fresh user message replans the round). */\n savedOnPause: {\n queue: QueueEntry[];\n roundNum: number;\n speakersThisTurn: number;\n maxSpeakersThisTurn: number;\n } | null;\n /** Chair's next-speaker rationale waiting to be attached to the next\n * director's message meta. Populated by pumpQueue when the haiku\n * picker reorders the queue; consumed by streamSpeakerTurn when the\n * matching speaker spins up its placeholder. Cleared after\n * attachment so a stale rationale can't leak onto a later turn. */\n pendingChairPick: { agentId: string; rationale: string } | null;\n /** Set when a director turn fails with a billing / quota error. The\n * pump checks this between turns and drains the queue without firing\n * more directors — once the carrier is dry, every subsequent call\n * would just hit the same upstream rejection. The flag clears on the\n * next user-message tick (tickRoom resets state) so the user can try\n * again after fixing the key. */\n billingHaltedThisTurn: boolean;\n}\n\nconst _state = new Map<string, RoomState>();\n\n/**\n * Dev-mode trace for the orchestrator. Writes a single line to stderr per\n * state transition (tick / pump / pause / resume / per-speaker turn) so the\n * developer console makes it obvious which agent · which modelV · which\n * round is on the wire at any moment. Always-on like the [adapter] line —\n * the room console is the only audit trail for multi-turn flow now that\n * the verify overlay is gone.\n */\nfunction rlog(roomId: string, label: string, fields?: Record<string, unknown>): void {\n const tag = roomId.slice(0, 8);\n let line = `[room ${tag}] ${label}`;\n if (fields) {\n for (const [k, v] of Object.entries(fields)) {\n const formatted = typeof v === \"string\" ? v : JSON.stringify(v);\n line += ` ${k}=${formatted}`;\n }\n }\n process.stderr.write(line + \"\\n\");\n}\n\nfunction ensureState(roomId: string): RoomState {\n let s = _state.get(roomId);\n if (!s) {\n s = {\n queue: [],\n inflight: null,\n processing: false,\n roundNum: 1,\n speakersThisTurn: 0,\n maxSpeakersThisTurn: 0,\n pauseAfterCurrent: false,\n pendingUserAfterCurrent: null,\n savedOnPause: null,\n pendingChairPick: null,\n billingHaltedThisTurn: false,\n };\n _state.set(roomId, s);\n }\n return s;\n}\n\n/** True if a director is currently streaming a turn. */\nexport function isRoomSpeaking(roomId: string): boolean {\n const s = _state.get(roomId);\n if (!s) return false;\n return s.inflight !== null;\n}\n\n/**\n * Append director(s) onto the live speaker queue without resetting\n * the round. Called when a user adds members via room settings — the\n * new agent should speak as part of the current round, not trigger a\n * full replan that re-fires already-spoken directors. Bumps the per-\n * turn cap so the pump doesn't exit early before reaching them, and\n * kicks the pump if it's idle (and the room is in a state that\n * permits a director turn).\n */\nexport function injectSpeakers(roomId: string, agentIds: string[]): void {\n if (agentIds.length === 0) return;\n const state = ensureState(roomId);\n let appended = 0;\n for (const id of agentIds) {\n if (state.queue.find((q) => q.agentId === id)) continue;\n state.queue.push({ agentId: id, status: \"queued\" });\n state.maxSpeakersThisTurn += 1;\n appended += 1;\n }\n if (appended === 0) return;\n emitQueueUpdate(roomId, state);\n\n // Snapshot any existing pause snapshot too — if the room is paused,\n // the user's resume click should pick up the new agents alongside.\n if (state.savedOnPause) {\n for (const id of agentIds) {\n if (state.savedOnPause.queue.find((q) => q.agentId === id)) continue;\n state.savedOnPause.queue.push({ agentId: id, status: \"queued\" });\n state.savedOnPause.maxSpeakersThisTurn += 1;\n }\n }\n\n const room = getRoom(roomId);\n const canPump =\n !state.processing &&\n !!room &&\n room.status === \"live\" &&\n !room.awaitingContinue &&\n !room.awaitingClarify;\n\n rlog(roomId, \"inject-speakers\", {\n added: appended,\n queue: state.queue.length,\n round: state.roundNum,\n cap: state.maxSpeakersThisTurn,\n pumping: canPump,\n });\n\n if (canPump) {\n void pumpQueue(roomId);\n }\n}\n\n/** Mark the room to pause after the current speaker finishes. */\nexport function requestSoftPause(roomId: string): void {\n const s = ensureState(roomId);\n s.pauseAfterCurrent = true;\n rlog(roomId, \"soft-pause-requested\", { remaining: s.queue.length, speaking: s.inflight ? 1 : 0 });\n}\n\n/** Stash a user message to be delivered between the current speaker\n * and the next one. pumpQueue picks this up after the current turn\n * finishes; the message lands before any subsequent director starts. */\nexport function setPendingUserAfterCurrent(\n roomId: string,\n payload: PendingUserAfterCurrent,\n): void {\n const s = ensureState(roomId);\n s.pendingUserAfterCurrent = payload;\n}\n\n/** Has a soft-pause request been honored / cleared? */\nexport function consumeSoftPause(roomId: string): boolean {\n const s = _state.get(roomId);\n if (!s) return false;\n if (s.pauseAfterCurrent) {\n s.pauseAfterCurrent = false;\n return true;\n }\n return false;\n}\n\n/**\n * Chair-interrupt flow · the user @mentioned the chair to ask a meta\n * question about the discussion. We pause the director queue, abort\n * any in-flight director (deleting their partial message so the chat\n * doesn't show a truncated bubble), run the chair's direct response,\n * then restore the queue and let the pump resume.\n *\n * The interrupted director DOES re-run when the queue restores —\n * they're still queue[0] in the snapshot. Their fresh turn will see\n * the chair's interruption in the transcript and naturally engage\n * with it. This mirrors a real-meeting feel: chair interrupts, current\n * speaker pauses, chair speaks, current speaker resumes from where\n * they were (with new context).\n *\n * Best-effort. A failed chair-direct streams an empty message which\n * streamChairMessage cleans up; the queue still restores.\n */\nexport async function chairInterrupt(roomId: string): Promise<void> {\n const state = ensureState(roomId);\n\n // Snapshot the current queue so we can restore after chair finishes.\n // The in-flight speaker (if any) is still queue[0] here — they're\n // included in the snapshot and will re-run when the queue resumes.\n const queueSnapshot: QueueEntry[] = state.queue.map((q) => ({\n agentId: q.agentId,\n status: \"queued\" as const,\n }));\n\n // Abort the in-flight speaker · cancel their stream and delete their\n // partial message so the chat doesn't carry a truncated bubble next\n // to the chair's direct response.\n let interruptedAgentId: string | null = null;\n if (state.inflight) {\n interruptedAgentId = state.queue[0]?.agentId ?? null;\n state.inflight.abort();\n state.inflight = null;\n if (interruptedAgentId) {\n // Find the most recent streaming agent message from this speaker\n // and remove it. The placeholder was inserted by streamSpeakerTurn\n // moments before the abort.\n const recent = listRecentMessages(roomId, 8);\n for (let i = recent.length - 1; i >= 0; i--) {\n const m = recent[i];\n if (\n m.authorKind === \"agent\" &&\n m.authorId === interruptedAgentId &&\n m.meta &&\n (m.meta as { streaming?: boolean }).streaming === true\n ) {\n deleteMessage(m.id);\n roomBus.emit(roomId, {\n type: \"message-removed\",\n messageId: m.id,\n reason: \"chair-interrupt\",\n });\n break;\n }\n }\n }\n }\n\n // Clear the live queue so any in-flight pumpQueue iteration exits its\n // while-loop cleanly. We restore the snapshot after the chair speaks.\n state.queue = [];\n emitQueueUpdate(roomId, state);\n\n rlog(roomId, \"chair-interrupt-start\", {\n aborted: interruptedAgentId !== null,\n interrupted: interruptedAgentId\n ? getAgent(interruptedAgentId)?.name ?? interruptedAgentId\n : null,\n snapshot: queueSnapshot.length,\n });\n\n // Run the chair's direct response · streamed via SSE. Best-effort:\n // any failure just means no chair message; we still restore queue.\n try {\n await runChairDirectResponse(roomId);\n } catch (e) {\n process.stderr.write(\n `[chair-interrupt] chair-direct failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n }\n\n // Restore queue and resume the pump. By now the prior pumpQueue\n // iteration (if it was running) has exited via empty-queue, so\n // state.processing should be false. We void pumpQueue defensively;\n // it self-guards if processing is somehow still true.\n state.queue = queueSnapshot;\n emitQueueUpdate(roomId, state);\n rlog(roomId, \"chair-interrupt-end\", { restored: queueSnapshot.length });\n\n if (!state.processing && state.queue.length > 0) {\n void pumpQueue(roomId);\n }\n}\n\nexport function abortRoom(roomId: string): void {\n const s = _state.get(roomId);\n if (!s) {\n rlog(roomId, \"abort-noop\", { reason: \"no-state\" });\n return;\n }\n // Snapshot the queue before clearing so a later resumeRoom can pick\n // up the same speaker order. The in-flight speaker (if any) is\n // re-prepended as `queued` since their turn was cut short.\n const remaining: QueueEntry[] = s.queue.map((q) => ({ agentId: q.agentId, status: \"queued\" as const }));\n s.savedOnPause = {\n queue: remaining,\n roundNum: s.roundNum,\n speakersThisTurn: s.speakersThisTurn,\n maxSpeakersThisTurn: s.maxSpeakersThisTurn,\n };\n s.queue = [];\n const wasSpeaking = s.inflight !== null;\n s.speakersThisTurn = 0;\n if (s.inflight) {\n s.inflight.abort();\n s.inflight = null;\n }\n rlog(roomId, \"abort\", {\n snapshot: remaining.length,\n round: s.roundNum,\n aborted: wasSpeaking,\n spoken: s.savedOnPause.speakersThisTurn,\n });\n emitQueueUpdate(roomId, s);\n}\n\n/** Restore the queue snapshot taken at pause and resume the pump.\n * If the snapshot is missing or empty (paused at end-of-round, or after\n * tickRoom cleared it), fall back to replanning the round so resume\n * always produces a visible agent reply — silent no-op was the source\n * of the \"click resume, nothing happens\" bug. */\nexport function resumeRoom(roomId: string): void {\n const s = ensureState(roomId);\n const snap = s.savedOnPause;\n s.savedOnPause = null;\n\n if (snap && snap.queue.length > 0) {\n s.queue = snap.queue.map((q) => ({ agentId: q.agentId, status: \"queued\" as const }));\n s.roundNum = snap.roundNum;\n s.speakersThisTurn = snap.speakersThisTurn;\n s.maxSpeakersThisTurn = snap.maxSpeakersThisTurn;\n emitQueueUpdate(roomId, s);\n rlog(roomId, \"resume\", {\n mode: \"snapshot\",\n queue: s.queue.length,\n round: s.roundNum,\n spoken: `${s.speakersThisTurn}/${s.maxSpeakersThisTurn}`,\n processing: s.processing,\n });\n if (!s.processing) {\n void pumpQueue(roomId);\n }\n return;\n }\n\n // Live-queue salvage · if state.queue somehow still holds entries\n // (race between hard pause + abort), prefer that over a fresh round.\n if (s.queue.length > 0) {\n rlog(roomId, \"resume\", {\n mode: \"live-queue\",\n queue: s.queue.length,\n round: s.roundNum,\n processing: s.processing,\n });\n if (!s.processing) {\n void pumpQueue(roomId);\n }\n return;\n }\n\n // Fallback path · snapshot null/empty AND state queue empty.\n // Per the user's brief: clicking Resume should ALWAYS restart the\n // queue, even when the room paused at end-of-round (chair prompt\n // owns the next step). Clear awaitingContinue so tickRoom's guard\n // doesn't no-op us, then replan the same round number — every\n // director gets another speaking slot in this fresh pass.\n const room = getRoom(roomId);\n if (room && room.awaitingContinue) {\n setAwaitingContinue(roomId, false);\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"round-resumed\",\n payload: {},\n createdAt: Date.now(),\n });\n }\n rlog(roomId, \"resume\", {\n mode: \"fallback-replan\",\n snapshot: snap ? \"empty\" : \"missing\",\n round: s.roundNum,\n clearedAwaitingContinue: !!(room && room.awaitingContinue),\n });\n tickRoom(roomId, { roundNum: s.roundNum });\n}\n\nfunction emitQueueUpdate(roomId: string, s: RoomState): void {\n const update: RoomEvent = {\n type: \"queue-update\",\n queue: s.queue.map((q) => ({ agentId: q.agentId, status: q.status })),\n round: {\n spoken: s.speakersThisTurn,\n total: s.maxSpeakersThisTurn,\n },\n };\n roomBus.emit(roomId, update);\n}\n\ninterface TickOptions {\n roundNum: number;\n /** First mention in the user's message (if any) — bypasses round-robin. */\n forceSpeakerId?: string | null;\n /** Origin of this tick:\n * \"user\" · default; treats this as the OPENING sweep — directors\n * respond in parallel, no cross-pollination.\n * \"continue\" · user clicked Continue after a round-end → reactive\n * round; directors see each other's prior turns.\n * \"force\" · single @-mention reply; no round marker is fired.\n * Drives the chair's round-open announcement + the prompt's round\n * mode block in `buildDirectorMessages`. */\n kind?: \"user\" | \"continue\" | \"force\";\n}\n\n/**\n * Replace the queue with a fresh plan for `roundNum`, abort any speaker mid-\n * stream, and kick the pump. Returns immediately; streaming happens off-thread.\n */\nexport function tickRoom(roomId: string, opts: TickOptions): void {\n const room = getRoom(roomId);\n if (!room) return;\n if (room.status !== \"live\") return;\n // Soft-pause flags set by the chair. Directors don't fire while the\n // room is mid-clarification or waiting for the user to Continue\n // after a round-end. The route layer handles releasing these.\n if (room.awaitingContinue || room.awaitingClarify) return;\n\n const memberRows = listRoomMembers(roomId);\n if (memberRows.length === 0) return;\n // Directors only — the chair (role_kind = 'moderator') never enters\n // the round-robin queue.\n const directors: Agent[] = memberRows\n .map((m) => getAgent(m.agentId))\n .filter((a): a is Agent => a !== null && a.roleKind === \"director\");\n if (directors.length === 0) return;\n\n // Decide who speaks this round.\n let plan: Agent[];\n if (opts.forceSpeakerId) {\n const found = directors.find((a) => a.id === opts.forceSpeakerId);\n plan = found ? [found] : [];\n } else {\n plan = directors;\n }\n if (plan.length === 0) return;\n\n const state = ensureState(roomId);\n\n // Abort any in-flight speaker; the pump's finally clause will see the new\n // queue when it resumes.\n if (state.inflight) state.inflight.abort();\n\n state.queue = plan.map((a) => ({ agentId: a.id, status: \"queued\" }));\n state.roundNum = opts.roundNum;\n state.speakersThisTurn = 0;\n // A fresh user message replans the round — drop any pause snapshot.\n state.savedOnPause = null;\n // Drop any pending chair-pick rationale — it belonged to the prior\n // queue, not this fresh plan. Otherwise it could leak onto a future\n // speaker who happens to share the prior pick's agentId.\n state.pendingChairPick = null;\n // Clear the billing halt · a fresh user tick is the natural moment\n // to retry. If the carrier still has no credit the next director will\n // re-trip the flag and a fresh chair notice posts.\n state.billingHaltedThisTurn = false;\n // One full round = each director speaks once. The chair takes over\n // when the queue drains; the user decides whether to continue with\n // another round.\n state.maxSpeakersThisTurn = plan.length;\n emitQueueUpdate(roomId, state);\n\n // Round-open marker · chair posts a centred chip in chat so the\n // user sees whether this round is parallel (independent) or\n // reactive. Skipped for forced single-speaker replies (those are\n // direct @-mention answers, not \"rounds\" in the multi-director\n // sense).\n const tickKind = opts.kind ?? \"user\";\n if (!opts.forceSpeakerId && tickKind !== \"force\") {\n announceRoundOpen(roomId, opts.roundNum, tickKind === \"user\");\n }\n\n rlog(roomId, \"tick\", {\n round: opts.roundNum,\n plan: plan.map((a) => `${a.name}:${a.modelV}`),\n forced: opts.forceSpeakerId ?? null,\n processing: state.processing,\n });\n\n if (!state.processing) {\n void pumpQueue(roomId);\n }\n}\n\nasync function pumpQueue(roomId: string): Promise<void> {\n const state = ensureState(roomId);\n if (state.processing) {\n rlog(roomId, \"pump-skip\", { reason: \"already-processing\" });\n return;\n }\n state.processing = true;\n rlog(roomId, \"pump-start\", {\n queue: state.queue.length,\n round: state.roundNum,\n spoken: `${state.speakersThisTurn}/${state.maxSpeakersThisTurn}`,\n });\n\n try {\n while (state.queue.length > 0 && state.speakersThisTurn < state.maxSpeakersThisTurn) {\n // Billing halt · a prior speaker hit insufficient_quota / billing\n // and the chair has already posted the explainer. Stop firing\n // more directors at the dry carrier — every call would just\n // re-trigger the same upstream rejection. Drain the queue so\n // queue UI clears, and exit. The flag clears on the next\n // tickRoom (fresh user message), giving the user a chance to\n // top up / swap carriers and retry naturally.\n if (state.billingHaltedThisTurn) {\n rlog(roomId, \"pump-halt\", { reason: \"billing\" });\n state.queue = [];\n emitQueueUpdate(roomId, state);\n break;\n }\n // ─── Next-speaker discipline · reactive rounds only ───────────\n // Before pulling the head of the queue, ask haiku to pick the\n // director whose lens most sharply addresses the previous turn's\n // unresolved tension. Skipped on opening rounds (parallel, no\n // prior turn to react to), on the FIRST speaker of a reactive\n // round (no prior turn yet either), and on single-candidate\n // queues. Failures fall back to the existing round-robin order.\n if (state.queue.length >= 2) {\n const recent = listRecentMessages(roomId, 30);\n const round = state.roundNum;\n let isReactive = false;\n let directorAlreadySpoke = false;\n for (let i = recent.length - 1; i >= 0; i--) {\n const m = recent[i];\n if (m.roundNum !== round) continue;\n if (m.authorKind !== \"agent\") continue;\n const meta = m.meta as { kind?: string; opening?: boolean } | undefined;\n if (meta?.kind === \"round-open\" && typeof meta.opening === \"boolean\") {\n isReactive = meta.opening === false;\n }\n // A director turn this round = something to react to. Chair\n // messages don't count (round-open / round-prompt / clarify\n // are structural pings, not content for next-speaker pick).\n if (!meta?.kind) {\n const author = m.authorId ? getAgent(m.authorId) : null;\n if (author && author.roleKind === \"director\") directorAlreadySpoke = true;\n }\n }\n if (isReactive && directorAlreadySpoke) {\n const queueSnapshot = state.queue.slice();\n const candidates = queueSnapshot\n .map((q) => getAgent(q.agentId))\n .filter((a): a is Agent => a !== null);\n if (candidates.length >= 2) {\n try {\n const pick = await pickNextSpeaker({ candidates, history: recent });\n // Guard · fresh tickRoom may have replaced state.queue\n // while haiku was thinking. Only reorder if the snapshot\n // is still the live queue.\n const stillSameQueue =\n state.queue.length === queueSnapshot.length &&\n state.queue.every((q, i) => q.agentId === queueSnapshot[i]!.agentId);\n if (stillSameQueue) {\n if (\n pick.agentId &&\n pick.agentId !== state.queue[0]!.agentId\n ) {\n const idx = state.queue.findIndex((q) => q.agentId === pick.agentId);\n if (idx > 0) {\n const [picked] = state.queue.splice(idx, 1);\n state.queue.unshift(picked!);\n rlog(roomId, \"next-speaker-reorder\", {\n picked: getAgent(pick.agentId)?.name ?? pick.agentId,\n rationale: pick.rationale,\n });\n // Stash the rationale so streamSpeakerTurn can attach\n // it to the speaker's message meta — surfacing the\n // chair's reasoning makes the moderator visible.\n if (pick.rationale && pick.rationale.trim()) {\n state.pendingChairPick = {\n agentId: pick.agentId,\n rationale: pick.rationale.trim(),\n };\n }\n emitQueueUpdate(roomId, state);\n }\n }\n // Intervention · the picker emits this only when prior\n // turns show substantive misalignment (talking past each\n // other, undefined term, hidden trade-off, circling).\n // Posted as a chair frame note BEFORE the picked\n // director speaks, so the room hears the moderator's\n // re-framing first. Common case is null — the picker is\n // biased to skip.\n if (pick.intervention) {\n rlog(roomId, \"chair-intervene\", {\n note: pick.intervention.slice(0, 80),\n nextSpeaker: getAgent(state.queue[0]!.agentId)?.name ?? state.queue[0]!.agentId,\n });\n announceIntervention(roomId, pick.intervention, pick.rationale);\n }\n }\n } catch (e) {\n // Best-effort · round-robin fallback. Logged at debug\n // level since the picker has its own stderr trace.\n rlog(roomId, \"next-speaker-error\", {\n error: e instanceof Error ? e.message : String(e),\n });\n }\n }\n }\n }\n\n const entry = state.queue[0]!;\n entry.status = \"speaking\";\n emitQueueUpdate(roomId, state);\n\n let speaker = getAgent(entry.agentId);\n if (!speaker) {\n rlog(roomId, \"speaker-skip\", { reason: \"agent-missing\", agentId: entry.agentId });\n state.queue.shift();\n emitQueueUpdate(roomId, state);\n continue;\n }\n // Self-heal · if the speaker's stored modelV isn't reachable\n // with the current key set (e.g. a fresh-onboarded user whose\n // seeded directors still carry opus-4-7 because reconcile got\n // skipped), re-run the reconciler and re-fetch the speaker so\n // their modelV swings to the active carrier's primary before\n // we attempt the LLM call.\n if (!isModelV(speaker.modelV) || !reachableModelVs().has(speaker.modelV)) {\n try {\n reconcileAgentModels();\n const refreshed = getAgent(entry.agentId);\n if (refreshed) speaker = refreshed;\n } catch (e) {\n rlog(roomId, \"reconcile-on-stale-failed\", {\n error: e instanceof Error ? e.message : String(e),\n });\n }\n }\n if (!isModelV(speaker.modelV)) {\n rlog(roomId, \"speaker-skip\", {\n reason: \"unknown-modelV\",\n agent: speaker.name,\n agentId: speaker.id,\n modelV: speaker.modelV,\n });\n appendSystemMessage(\n roomId,\n `${speaker.name}'s configured model \"${speaker.modelV}\" is unknown. Skipping turn.`,\n );\n state.queue.shift();\n emitQueueUpdate(roomId, state);\n continue;\n }\n\n const ac = new AbortController();\n state.inflight = ac;\n\n const turnStart = Date.now();\n rlog(roomId, \"speaker-start\", {\n agent: speaker.name,\n agentId: speaker.id,\n modelV: speaker.modelV,\n round: state.roundNum,\n position: `${state.speakersThisTurn + 1}/${state.maxSpeakersThisTurn}`,\n });\n\n try {\n await streamSpeakerTurn({\n roomId,\n speaker,\n roundNum: state.roundNum,\n signal: ac.signal,\n });\n state.speakersThisTurn++;\n rlog(roomId, \"speaker-end\", {\n agent: speaker.name,\n modelV: speaker.modelV,\n ms: Date.now() - turnStart,\n aborted: ac.signal.aborted,\n });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n rlog(roomId, \"speaker-error\", {\n agent: speaker.name,\n modelV: speaker.modelV,\n ms: Date.now() - turnStart,\n error: msg,\n });\n process.stderr.write(`[orchestrator] stream error: ${msg}\\n`);\n } finally {\n state.inflight = null;\n }\n\n // Was this turn aborted by a fresh tick? If so, the queue's been\n // replaced under us — bail out of the recycling logic.\n if (state.queue[0] !== entry) {\n continue;\n }\n\n // One-round semantics: pop from the front and DON'T recycle —\n // each director speaks exactly once per user message. The chair\n // takes over when the queue drains.\n state.queue.shift();\n emitQueueUpdate(roomId, state);\n\n // Pending user message · the user opted to deliver this AFTER the\n // current speaker finishes. Drain it before starting the next\n // speaker so the message lands in the right slot AND the next\n // speaker's response is keyed off the user's question (via\n // tickRoom's replan).\n if (state.pendingUserAfterCurrent) {\n const pending = state.pendingUserAfterCurrent;\n state.pendingUserAfterCurrent = null;\n const userRound = nextUserRoundNum(roomId);\n const userMsg = insertMessage({\n roomId,\n authorKind: \"user\",\n body: pending.text,\n replyToId: pending.replyToId,\n meta: pending.mentions.length ? { mentions: pending.mentions } : {},\n roundNum: userRound,\n });\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: userMsg.id,\n authorKind: \"user\",\n authorId: null,\n replyToId: userMsg.replyToId,\n body: userMsg.body,\n meta: userMsg.meta,\n roundNum: userMsg.roundNum,\n createdAt: userMsg.createdAt,\n });\n roomBus.emit(roomId, { type: \"message-final\", messageId: userMsg.id });\n // Replan the round around the user's message. tickRoom replaces\n // state.queue + resets counters; the while loop then continues\n // with the new plan instead of the stale queue we were draining.\n tickRoom(roomId, {\n roundNum: userRound,\n forceSpeakerId: pending.mentions[0] ?? null,\n });\n continue;\n }\n\n // Soft pause requested mid-turn → snapshot the remaining queue so\n // resume picks up the same speaker order, then drain + flip to\n // 'paused'. Emit the lifecycle event so the UI follows.\n if (state.pauseAfterCurrent) {\n state.pauseAfterCurrent = false;\n state.savedOnPause = {\n queue: state.queue.map((q) => ({ agentId: q.agentId, status: \"queued\" as const })),\n roundNum: state.roundNum,\n speakersThisTurn: state.speakersThisTurn,\n maxSpeakersThisTurn: state.maxSpeakersThisTurn,\n };\n state.queue = [];\n emitQueueUpdate(roomId, state);\n rlog(roomId, \"soft-pause-honored\", {\n snapshot: state.savedOnPause.queue.length,\n spoken: `${state.speakersThisTurn}/${state.maxSpeakersThisTurn}`,\n });\n\n const pausedAt = Date.now();\n setRoomStatus(roomId, \"paused\", { pausedAt });\n insertConfigEvent({\n roomId,\n kind: \"room-paused\",\n payload: { pausedAt, mode: \"soft\" },\n actorKind: \"user\",\n });\n roomBus.emit(roomId, {\n type: \"config-event\",\n kind: \"room-paused\",\n payload: { pausedAt, mode: \"soft\" },\n createdAt: pausedAt,\n });\n break;\n }\n }\n } finally {\n state.processing = false;\n // Drain whatever's left so the UI clears the queue when we hit the cap.\n const reachedCap = state.speakersThisTurn >= state.maxSpeakersThisTurn;\n if (reachedCap) state.queue = [];\n emitQueueUpdate(roomId, state);\n rlog(roomId, \"pump-end\", {\n round: state.roundNum,\n spoken: `${state.speakersThisTurn}/${state.maxSpeakersThisTurn}`,\n remaining: state.queue.length,\n reachedCap,\n });\n\n // Round complete (cap reached, not soft-paused, not adjourned) →\n // drop the chair's round-prompt into the chat. The user picks\n // End-round (vote) or Continue from inline buttons inside that\n // message. Skipped if we're already in another phase.\n //\n // Synthesis primitive · before posting the prompt, run a cheap\n // haiku that recommends End vs Continue based on the round's\n // transcript. The recommendation is folded into the round-prompt\n // body + meta so the user reads the chair's call before pressing\n // a button. Failure → continue default (never accidentally push\n // toward ending). The await is intentional: the user should NOT\n // see the round-prompt until the chair has decided.\n if (reachedCap) {\n const room = getRoom(roomId);\n if (\n room &&\n room.status === \"live\" &&\n !room.awaitingContinue &&\n !room.awaitingClarify\n ) {\n const wrappedRound = state.roundNum;\n let recommendation: { kind: \"end\" | \"continue\"; rationale: string } | undefined;\n try {\n const recent = listRecentMessages(roomId, 30);\n const wrap = await pickRoundWrap({ history: recent, roundNum: wrappedRound });\n recommendation = { kind: wrap.recommendation, rationale: wrap.rationale };\n } catch (e) {\n rlog(roomId, \"round-wrap-error\", {\n error: e instanceof Error ? e.message : String(e),\n });\n }\n // Re-check guards after the haiku · a competing tickRoom could\n // have flipped the room's status / phase / round mid-flight.\n // Re-fetch room and current state so we don't post a stale\n // round-prompt against a room that's moved on.\n const roomAgain = getRoom(roomId);\n const stateNow = ensureState(roomId);\n if (\n roomAgain &&\n roomAgain.status === \"live\" &&\n !roomAgain.awaitingContinue &&\n !roomAgain.awaitingClarify &&\n stateNow.roundNum === wrappedRound\n ) {\n rlog(roomId, \"round-prompt\", {\n round: wrappedRound,\n recommendation: recommendation?.kind ?? \"(none)\",\n });\n announceRoundPrompt(roomId, wrappedRound, recommendation);\n } else {\n rlog(roomId, \"round-prompt-skip\", {\n reason: \"phase-changed-during-haiku\",\n wrappedRound,\n currentRound: stateNow.roundNum,\n status: roomAgain?.status,\n awaitingContinue: roomAgain?.awaitingContinue,\n awaitingClarify: roomAgain?.awaitingClarify,\n });\n }\n }\n }\n }\n}\n\ninterface StreamArgs {\n roomId: string;\n speaker: Agent;\n roundNum: number;\n signal: AbortSignal;\n}\n\nasync function streamSpeakerTurn(args: StreamArgs): Promise<void> {\n const { roomId, speaker, roundNum, signal } = args;\n\n const room = getRoom(roomId);\n if (!room) return;\n\n const memberRows = listRoomMembers(roomId);\n const cast: Agent[] = memberRows\n .map((m) => getAgent(m.agentId))\n .filter((a): a is Agent => a !== null && a.roleKind === \"director\");\n\n const prefs = getPrefs();\n const history = listRecentMessages(roomId, 30);\n const keyPoints = listKeyPointsForRoom(roomId);\n\n // Skills + Web Search · Pass-1 router. The same haiku call decides\n // (a) which installed .md skills apply, and (b) whether the turn\n // would benefit from a Brave search query. Both are gated by the\n // user having configured the relevant key + the per-agent toggle.\n const installedSkills = listSkillsForAgent(speaker.id);\n const braveAvailable = hasBraveKey() && speaker.webSearchEnabled;\n let activeSkills: ReturnType<typeof listSkillsForAgent> = [];\n let pickerReason = \"\";\n let webSearchQuery: string | null = null;\n if (installedSkills.length > 0 || braveAvailable) {\n try {\n const picked = await pickSkills({\n speaker,\n skills: installedSkills,\n history,\n webSearchAvailable: braveAvailable,\n signal,\n });\n activeSkills = picked.used;\n pickerReason = picked.reason;\n webSearchQuery = picked.webSearchQuery;\n rlog(roomId, \"skill-picker\", {\n agent: speaker.name,\n installed: installedSkills.length,\n used: activeSkills.map((s) => s.slug),\n webSearch: webSearchQuery,\n reason: pickerReason,\n });\n } catch (e) {\n // Best-effort. Picker failure must not block the turn.\n process.stderr.write(\n `[skill-picker] ${speaker.name} crashed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n }\n }\n\n // Run Brave Search when the router decided the turn needs fresh info.\n // Failure (timeout / network / 4xx) is non-fatal: the agent answers\n // without the SHARED MATERIALS block, never sees an error.\n let webSearchSources: Array<{ title: string; url: string; description: string }> = [];\n let sharedMaterialsBlock = \"\";\n if (braveAvailable && webSearchQuery) {\n const apiKey = getKey(\"brave\");\n if (apiKey) {\n const results = await runBraveSearch({ apiKey, query: webSearchQuery });\n if (results && results.length > 0) {\n webSearchSources = results.map((r) => ({\n title: r.title,\n url: r.url,\n description: r.description,\n }));\n sharedMaterialsBlock = formatSearchResults(webSearchQuery, results);\n rlog(roomId, \"web-search\", {\n agent: speaker.name,\n query: webSearchQuery,\n sources: results.length,\n });\n }\n }\n }\n\n // Chair-pick rationale · stamped by pumpQueue when the next-speaker\n // haiku reordered the queue to put this director on top. Consumed\n // here BEFORE buildDirectorMessages so it can flow into both:\n // · the system prompt as a private CHAIR'S BRIEF (shapes content),\n // · the placeholder meta as chairPick.rationale (drives the UI\n // kicker so the user sees the moderator's reasoning).\n // Cleared after read so a later turn doesn't inherit a stale cue.\n const turnState = ensureState(roomId);\n let chairBriefForTurn: string | null = null;\n if (\n turnState.pendingChairPick &&\n turnState.pendingChairPick.agentId === speaker.id\n ) {\n chairBriefForTurn = turnState.pendingChairPick.rationale;\n turnState.pendingChairPick = null;\n }\n\n const llmMessages: LLMMessage[] = buildDirectorMessages({\n speaker,\n cast,\n room,\n prefs,\n history,\n keyPoints,\n activeSkills,\n sharedMaterials: sharedMaterialsBlock,\n chairBrief: chairBriefForTurn ?? undefined,\n });\n\n // Streaming placeholder so the UI has an id immediately.\n const placeholderMeta: Record<string, unknown> = {\n speakerStatus: \"streaming\",\n streaming: true,\n };\n if (activeSkills.length > 0) {\n placeholderMeta.skillsUsed = activeSkills.map((s) => s.slug);\n if (pickerReason) placeholderMeta.skillsReason = pickerReason;\n }\n if (webSearchSources.length > 0 && webSearchQuery) {\n placeholderMeta.webSearchUsed = true;\n placeholderMeta.webSearchQuery = webSearchQuery;\n placeholderMeta.webSearchSources = webSearchSources;\n }\n if (chairBriefForTurn) {\n placeholderMeta.chairPick = { rationale: chairBriefForTurn };\n }\n const placeholder = insertMessage({\n roomId,\n authorKind: \"agent\",\n authorId: speaker.id,\n body: \"\",\n meta: placeholderMeta,\n roundNum,\n });\n\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: placeholder.id,\n authorKind: \"agent\",\n authorId: speaker.id,\n replyToId: null,\n body: \"\",\n meta: placeholder.meta,\n roundNum: placeholder.roundNum,\n createdAt: placeholder.createdAt,\n });\n\n let buf = \"\";\n let finishReason: string | undefined;\n let errored = false;\n\n for await (const chunk of callLLMStream({\n modelV: speaker.modelV as never,\n // Per-agent carrier override · adapter falls back to default\n // precedence when null, when the agent set a carrier whose key\n // was later removed, or when the chosen carrier doesn't actually\n // host the agent's modelV.\n carrier: speaker.carrierPref ?? null,\n messages: llmMessages,\n temperature: 0.65,\n // 4000 (was 800 → 2000). Gemini 3 Pro's thinking trace routinely\n // burns 2-3k tokens on a single director turn; with the cap at\n // 2000, reasoning ate the whole budget and the visible reply got\n // truncated mid-sentence. 4000 leaves ~1-2k of visible headroom\n // even for the heaviest reasoners. If a model STILL truncates,\n // the next move is to cap reasoning explicitly via providerOptions\n // (`reasoning.max_tokens`) instead of just enlarging the total cap.\n maxTokens: 4000,\n signal,\n })) {\n if (signal.aborted) break;\n\n if (chunk.type === \"text\") {\n buf += chunk.delta;\n updateMessageBody(placeholder.id, buf, {\n ...placeholderMeta,\n speakerStatus: \"streaming\",\n streaming: true,\n });\n roomBus.emit(roomId, {\n type: \"message-token\",\n messageId: placeholder.id,\n delta: chunk.delta,\n });\n } else if (chunk.type === \"usage\") {\n // Bump the per-agent cumulative token counter (surfaced on the\n // agent profile under \"Track Record · Tokens\"). Charged in full\n // even on error / partial responses — those still cost upstream.\n incrementAgentTokens(speaker.id, chunk.totalTokens);\n rlog(roomId, \"speaker-usage\", {\n agent: speaker.name,\n modelV: speaker.modelV,\n promptTokens: chunk.promptTokens,\n completionTokens: chunk.completionTokens,\n totalTokens: chunk.totalTokens,\n });\n } else if (chunk.type === \"done\") {\n finishReason = chunk.finishReason;\n } else if (chunk.type === \"error\") {\n errored = true;\n // Loud stderr so the dev console shows the upstream message even\n // when the UI has the placeholder removed before the user can see\n // the error toast. The \"no tokens output\" symptom is almost\n // always a 4xx/5xx the user never noticed.\n process.stderr.write(\n `[stream-error] room=${roomId} agent=${speaker.name} modelV=${speaker.modelV} · ${chunk.message}\\n`,\n );\n\n // Billing / quota errors get a chair-authored explainer in the\n // chat stream instead of an opaque `[error: ...]` bubble. Drop\n // the failed director's placeholder, post the chair notice, and\n // raise the per-turn flag so pumpQueue stops feeding more\n // directors at a dry carrier. ensureState (not a fresh state)\n // because we're inside an active pump.\n if (isBillingError(chunk.message)) {\n const turnState = ensureState(roomId);\n deleteMessage(placeholder.id);\n roomBus.emit(roomId, {\n type: \"message-removed\",\n messageId: placeholder.id,\n reason: \"billing\",\n });\n if (!turnState.billingHaltedThisTurn) {\n announceBillingNotice(roomId, {\n providerHint: extractProviderHint(chunk.message),\n rawError: chunk.message,\n agentName: speaker.name,\n });\n turnState.billingHaltedThisTurn = true;\n }\n // Bail out of the streaming loop · further chunks (incl. usage /\n // done) are irrelevant for a turn we just disowned.\n return;\n }\n\n roomBus.emit(roomId, {\n type: \"message-error\",\n messageId: placeholder.id,\n message: chunk.message,\n });\n updateMessageBody(placeholder.id, buf || `[error: ${chunk.message}]`, {\n ...placeholderMeta,\n speakerStatus: \"final\",\n streaming: false,\n error: chunk.message,\n });\n }\n }\n\n if (signal.aborted) {\n finishReason = finishReason ?? \"aborted\";\n }\n\n // If the LLM never produced any text, drop the empty placeholder rather\n // than leaving a blank bubble in the chat. Common reasons: aborted before\n // first token, provider returned no content, context-window refusal.\n // Errored turns are KEPT (with the `[error: …]` body) so the user can\n // see what went wrong rather than getting a silent disappearance.\n const hasContent = buf.trim().length > 0;\n if (!hasContent && !errored) {\n deleteMessage(placeholder.id);\n roomBus.emit(roomId, {\n type: \"message-removed\",\n messageId: placeholder.id,\n reason: finishReason || \"empty\",\n });\n return;\n }\n\n if (!errored) {\n updateMessageBody(placeholder.id, buf, {\n ...placeholderMeta,\n speakerStatus: \"final\",\n streaming: false,\n ...(finishReason ? { finishReason } : {}),\n });\n roomBus.emit(roomId, {\n type: \"message-final\",\n messageId: placeholder.id,\n finishReason,\n });\n }\n}\n\n/** System-side note (e.g. \"agent has unknown model\"). */\nfunction appendSystemMessage(roomId: string, body: string): void {\n const m = insertMessage({ roomId, authorKind: \"system\", body });\n roomBus.emit(roomId, {\n type: \"message-appended\",\n messageId: m.id,\n authorKind: \"system\",\n authorId: null,\n replyToId: null,\n body: m.body,\n meta: m.meta,\n roundNum: m.roundNum,\n createdAt: m.createdAt,\n });\n roomBus.emit(roomId, { type: \"message-final\", messageId: m.id });\n}\n\n/** Snapshot used by GET /api/rooms/:id. The chair shows up in\n * `chair` separately from `members` (which is directors only) so\n * the frontend can render them differently. */\nexport function getRoomFullState(roomId: string): {\n room: Room;\n members: Agent[];\n chair: Agent | null;\n messages: Message[];\n keyPoints: ReturnType<typeof listKeyPointsForRoom>;\n} | null {\n const room = getRoom(roomId);\n if (!room) return null;\n const memberRows = listRoomMembers(roomId);\n const all = memberRows\n .map((m) => getAgent(m.agentId))\n .filter((a): a is Agent => a !== null);\n const members = all.filter((a) => a.roleKind === \"director\");\n const chair = all.find((a) => a.roleKind === \"moderator\") ?? null;\n const messages = listRecentMessages(roomId, 200);\n const keyPoints = listKeyPointsForRoom(roomId);\n return { room, members, chair, messages, keyPoints };\n}\n\n/** Current speaking-queue snapshot — shown to clients on initial load. */\nexport function getRoomQueueSnapshot(roomId: string): {\n queue: QueueEntry[];\n round: { spoken: number; total: number };\n} {\n const s = _state.get(roomId);\n if (!s) return { queue: [], round: { spoken: 0, total: 0 } };\n return {\n queue: s.queue.map((q) => ({ ...q })),\n round: { spoken: s.speakersThisTurn, total: s.maxSpeakersThisTurn },\n };\n}\n","/**\n * Director auto-picker.\n *\n * Given a room subject and the available director catalog, asks a\n * cheap LLM (haiku) to pick a 3-director cast that gives the user\n * good COVERAGE OF PERSPECTIVES — not just topical similarity. The\n * boardroom's value comes from multi-lens reading; if every director\n * would say the same thing, the pick has failed.\n *\n * After the LLM returns its picks, we run a deterministic diversity\n * guardrail: the cast must cover at least 2 of the four \"lens types\"\n * — { dissent, rigor, empathy, pattern_recall } — measured against\n * each director's `ability` map. If the LLM's pick fails the rule,\n * we swap the weakest member for a director that fills the gap.\n *\n * Failures (no key, network, parse error, no candidates) fall back\n * silently to a canonical triple. Callers always get 3 directors.\n */\nimport { callLLM, NoKeyError, type LLMMessage } from \"../ai/adapter.js\";\nimport type { ModelV } from \"../ai/registry.js\";\nimport type { Agent } from \"../storage/agents.js\";\n\nconst PICKER_MODEL: ModelV = \"haiku-4-5\" as ModelV;\nconst TARGET_CAST_SIZE = 3;\n/** The 4 lens types the cast must cover at least 2 of. Tied to the\n * ability axes a director scores well on; \"high\" means ≥ 7 of 10. */\nconst LENS_AXES = [\"dissent\", \"rigor\", \"empathy\", \"pattern_recall\"] as const;\nconst LENS_THRESHOLD = 7;\n\nexport interface DirectorPick {\n agentId: string;\n reason: string;\n}\nexport interface DirectorPickResult {\n picks: DirectorPick[];\n /** One-line explanation the picker gave for the cast as a whole. */\n rationale: string;\n /** Whether the LLM produced this cast (false = silent fallback). */\n fromLlm: boolean;\n}\n\nfunction clipString(s: string, max: number): string {\n if (s.length <= max) return s;\n return s.slice(0, max).trim();\n}\n\nfunction describeDirector(a: Agent, recentCount: number): string {\n const ability = a.ability ? Object.entries(a.ability)\n .filter(([, v]) => typeof v === \"number\" && v >= LENS_THRESHOLD)\n .map(([k, v]) => `${k}:${v}`)\n .join(\",\") : \"\";\n const bio = clipString(a.bio || \"\", 140).replace(/\\s+/g, \" \");\n const tag = (a.roleTag || \"director\").toLowerCase();\n // Recency tag · the picker prompt instructs the model to prefer\n // directors with low recency counts when topical fit is comparable.\n // Format is \"[seen N/5]\" so the model has a numeric anchor.\n const recencyTag = recentCount > 0 ? ` · [seen ${recentCount}/5 recent rooms]` : ` · [unseen recently]`;\n return `- ${a.handle} · ${a.name} · ${tag} · \"${bio}\"${ability ? ` · strong on { ${ability} }` : \"\"}${recencyTag}`;\n}\n\nfunction tolerantJson<T>(raw: string): T | null {\n if (!raw) return null;\n let s = raw.trim();\n s = s.replace(/^```(?:json)?\\s*/i, \"\").replace(/```\\s*$/i, \"\").trim();\n try { return JSON.parse(s) as T; } catch { /* fall through */ }\n const open = s.indexOf(\"{\");\n const close = s.lastIndexOf(\"}\");\n if (open >= 0 && close > open) {\n try { return JSON.parse(s.slice(open, close + 1)) as T; } catch { return null; }\n }\n return null;\n}\n\n/** Compute which lens types the given cast already covers. */\nfunction coveredLenses(cast: Agent[]): Set<string> {\n const covered = new Set<string>();\n for (const a of cast) {\n if (!a.ability) continue;\n for (const axis of LENS_AXES) {\n const v = a.ability[axis];\n if (typeof v === \"number\" && v >= LENS_THRESHOLD) covered.add(axis);\n }\n }\n return covered;\n}\n\n/** Find the best director outside `cast` who'd cover one of `gaps`. */\nfunction findGapFiller(\n candidates: Agent[],\n cast: Agent[],\n gaps: Set<string>,\n): Agent | null {\n const inCast = new Set(cast.map((a) => a.id));\n let best: { a: Agent; score: number } | null = null;\n for (const a of candidates) {\n if (inCast.has(a.id)) continue;\n if (!a.ability) continue;\n let score = 0;\n for (const axis of gaps) {\n const v = a.ability[axis];\n if (typeof v === \"number\" && v >= LENS_THRESHOLD) score += v;\n }\n if (score > 0 && (!best || score > best.score)) best = { a, score };\n }\n return best?.a ?? null;\n}\n\n/** Drop the least-essential cast member to make room for a gap filler.\n * Heuristic: remove the one whose ability axes are most redundant\n * with another cast member (highest overlap on covered lenses). */\nfunction pickRedundantMember(cast: Agent[]): Agent {\n if (cast.length <= 1) return cast[0];\n let worst: { a: Agent; redundancy: number } | null = null;\n for (const a of cast) {\n if (!a.ability) {\n // No ability data → most expendable\n return a;\n }\n let redundancy = 0;\n for (const b of cast) {\n if (a.id === b.id || !b.ability) continue;\n for (const axis of LENS_AXES) {\n const av = a.ability[axis];\n const bv = b.ability[axis];\n if (typeof av === \"number\" && typeof bv === \"number\" && av >= LENS_THRESHOLD && bv >= LENS_THRESHOLD) {\n redundancy++;\n }\n }\n }\n if (!worst || redundancy > worst.redundancy) worst = { a, redundancy };\n }\n return worst!.a;\n}\n\n/** Diversity guardrail · ensures the cast covers ≥ 2 of the four lens\n * types. Swaps out the most redundant member for a gap-filler if the\n * LLM's pick failed the rule. */\nfunction enforceDiversity(picks: Agent[], candidates: Agent[]): Agent[] {\n const cast = picks.slice();\n let safety = 4;\n while (safety-- > 0) {\n const covered = coveredLenses(cast);\n if (covered.size >= 2) return cast;\n // Find gaps (lenses we DON'T cover) and swap a redundant member\n // for someone who fills one of them.\n const gaps = new Set<string>(LENS_AXES.filter((x) => !covered.has(x)));\n const filler = findGapFiller(candidates, cast, gaps);\n if (!filler) return cast; // catalog can't help; ship what we have\n const drop = pickRedundantMember(cast);\n const idx = cast.findIndex((a) => a.id === drop.id);\n if (idx < 0) return cast;\n cast[idx] = filler;\n }\n return cast;\n}\n\n/** Canonical fallback · used when the LLM call fails or returns no\n * usable picks. Preference order: by handle if those exist (the\n * seeded triple is socrates / first-principles / user-empathy),\n * otherwise the first three available directors. */\nfunction fallbackCast(candidates: Agent[]): Agent[] {\n if (candidates.length <= TARGET_CAST_SIZE) return candidates.slice();\n const preferredHandles = [\"/socrates\", \"/first_p\", \"/user_emp\"];\n const byHandle = new Map(candidates.map((a) => [a.handle, a]));\n const preferred = preferredHandles\n .map((h) => byHandle.get(h))\n .filter((a): a is Agent => !!a);\n if (preferred.length >= TARGET_CAST_SIZE) return preferred.slice(0, TARGET_CAST_SIZE);\n // Fill in with first-N from the remaining catalog.\n const seen = new Set(preferred.map((a) => a.id));\n const fill = candidates.filter((a) => !seen.has(a.id)).slice(0, TARGET_CAST_SIZE - preferred.length);\n return [...preferred, ...fill];\n}\n\n/** Pick a 3-director cast for the given room subject. */\nexport async function pickDirectors(opts: {\n subject: string;\n candidates: Agent[];\n /** Recent-appearance count keyed by agentId · directors seated in\n * recent rooms get downweighted in the prompt so the user doesn't\n * keep seeing the same trio for similar topics. Empty / missing\n * map → no recency bias (first room of the install, or test path). */\n recentAppearances?: Map<string, number>;\n}): Promise<DirectorPickResult> {\n const { subject, candidates } = opts;\n const recent = opts.recentAppearances ?? new Map<string, number>();\n // Trivial cases · ≤ 3 directors total: seat them all, no algorithm.\n if (candidates.length <= TARGET_CAST_SIZE) {\n return {\n picks: candidates.map((a) => ({ agentId: a.id, reason: \"\" })),\n rationale: \"fewer directors than target cast size\",\n fromLlm: false,\n };\n }\n\n const sys: LLMMessage = {\n role: \"system\",\n content: [\n \"You are choosing a 3-director cast for a boardroom session.\",\n \"\",\n \"The boardroom's value is COVERAGE OF PERSPECTIVES, not topical similarity. If every director would say the same thing, you have failed. Two directors with the same lens (e.g. both \\\"long-pattern\\\" / \\\"value\\\" types) is a redundant pick — replace one.\",\n \"\",\n \"Goal:\",\n \"- Pick exactly 3 director handles from the catalog below.\",\n \"- Cover ≥ 2 of these 4 lens types: dissent, rigor, empathy, pattern_recall.\",\n \"- Pick the topically-best LEAD, then DIVERSIFY (lens not yet covered), then BALANCE (narrative or decisiveness).\",\n \"\",\n \"RECENCY BIAS · Each catalog entry shows whether the director appeared in the last 5 rooms. When two candidates fit the same lens role comparably, PREFER the one with `[unseen recently]` over `[seen N/5 recent rooms]`. The user notices when the same trio shows up across consecutive rooms — variety across rooms is part of the boardroom's value. Only override the recency bias when the topical fit is genuinely uneven (e.g. only one specialist exists for a domain-specific question).\",\n \"\",\n \"Reply with strict JSON only — no prose outside the block:\",\n \"```json\",\n \"{\",\n \" \\\"picks\\\": [\",\n \" {\\\"handle\\\": \\\"/socrates\\\", \\\"reason\\\": \\\"≤ 60 chars · why this director\\\"},\",\n \" {\\\"handle\\\": \\\"/long_h\\\", \\\"reason\\\": \\\"...\\\"},\",\n \" {\\\"handle\\\": \\\"/user_emp\\\", \\\"reason\\\": \\\"...\\\"}\",\n \" ],\",\n \" \\\"rationale\\\": \\\"≤ 80 chars · why this combination as a whole\\\"\",\n \"}\",\n \"```\",\n ].join(\"\\n\"),\n };\n\n const user: LLMMessage = {\n role: \"user\",\n content: [\n `Subject:`,\n subject,\n ``,\n `Director catalog (${candidates.length}):`,\n ...candidates.map((a) => describeDirector(a, recent.get(a.id) ?? 0)),\n ``,\n `Pick 3 handles. Optimise for lens coverage AND variety across rooms (lean on recency tags).`,\n ].join(\"\\n\"),\n };\n\n let raw = \"\";\n try {\n raw = await callLLM({\n modelV: PICKER_MODEL,\n messages: [sys, user],\n // 0.7 (was 0.3) gives the picker enough variation to actually\n // honor the recency bias when topical fit is comparable; 0.3 was\n // too deterministic and locked the picker into the same trio\n // across similar topics.\n temperature: 0.7,\n maxTokens: 360,\n });\n } catch (e) {\n if (!(e instanceof NoKeyError)) {\n process.stderr.write(\n `[director-picker] llm failed: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n }\n const cast = enforceDiversity(fallbackCast(candidates), candidates);\n return {\n picks: cast.map((a) => ({ agentId: a.id, reason: \"default cast\" })),\n rationale: \"picker unavailable · default cast seated\",\n fromLlm: false,\n };\n }\n\n type PickRaw = { handle?: unknown; reason?: unknown };\n const parsed = tolerantJson<{ picks?: unknown; rationale?: unknown }>(raw);\n if (!parsed || !Array.isArray(parsed.picks)) {\n const cast = enforceDiversity(fallbackCast(candidates), candidates);\n return {\n picks: cast.map((a) => ({ agentId: a.id, reason: \"default cast\" })),\n rationale: \"picker output unparseable · default cast seated\",\n fromLlm: false,\n };\n }\n\n const byHandle = new Map(candidates.map((a) => [a.handle, a]));\n const llmPicks: { agent: Agent; reason: string }[] = [];\n for (const p of parsed.picks as PickRaw[]) {\n const handle = typeof p.handle === \"string\" ? p.handle.trim() : \"\";\n if (!handle) continue;\n const agent = byHandle.get(handle);\n if (!agent) continue;\n if (llmPicks.find((x) => x.agent.id === agent.id)) continue; // dedupe\n const reason = typeof p.reason === \"string\" ? clipString(p.reason.trim(), 80) : \"\";\n llmPicks.push({ agent, reason });\n if (llmPicks.length >= TARGET_CAST_SIZE) break;\n }\n\n // Top up if the LLM gave us fewer than TARGET_CAST_SIZE valid picks.\n if (llmPicks.length < TARGET_CAST_SIZE) {\n const fill = fallbackCast(candidates).filter(\n (a) => !llmPicks.find((p) => p.agent.id === a.id),\n );\n for (const a of fill) {\n llmPicks.push({ agent: a, reason: \"balance · default\" });\n if (llmPicks.length >= TARGET_CAST_SIZE) break;\n }\n }\n\n // Reasons get attached to the cast; diversity guardrail may swap\n // members. When a swap happens the new member inherits a default\n // reason (the LLM didn't pick it, so no original rationale exists).\n const reasonsByAgent = new Map(llmPicks.map((p) => [p.agent.id, p.reason]));\n const adjusted = enforceDiversity(llmPicks.map((p) => p.agent), candidates);\n\n const rationale = typeof parsed.rationale === \"string\"\n ? clipString(parsed.rationale.trim(), 120)\n : \"covers complementary lenses\";\n\n return {\n picks: adjusted.map((a) => ({\n agentId: a.id,\n reason: reasonsByAgent.get(a.id) || \"balance · diversity guardrail\",\n })),\n rationale,\n fromLlm: true,\n };\n}\n","/**\n * /api/usage · cumulative LLM-call accounting surfaced in the Usage\n * panel of the user-settings overlay.\n *\n * GET /summary → { totalTokens, agentCount, byModel[], byAgent[] }\n *\n * Tokens are aggregated from `agents.tokens_consumed`, which the\n * orchestrator increments after every successful turn (director +\n * chair + composer + brief stages all bill to the speaking agent).\n *\n * The endpoint also enriches `byModel` with display metadata from\n * the model registry so the frontend doesn't have to keep a parallel\n * map. Unknown / dropped model versions (e.g. an agent still pointing\n * at a retired slug) are returned with the raw `modelV` and a synthetic\n * displayName so they remain visible in the chart instead of silently\n * disappearing.\n */\nimport { Hono } from \"hono\";\n\nimport { getUsageSummary } from \"../storage/agents.js\";\nimport { isModelV, MODELS } from \"../ai/registry.js\";\n\ninterface ModelDisplay {\n displayName: string;\n provider: string;\n}\nfunction modelDisplay(modelV: string): ModelDisplay {\n if (isModelV(modelV)) {\n const m = MODELS[modelV];\n return { displayName: m.displayName, provider: m.provider };\n }\n return { displayName: modelV, provider: \"unknown\" };\n}\n\nexport function usageRouter(): Hono {\n const r = new Hono();\n\n r.get(\"/summary\", (c) => {\n const s = getUsageSummary();\n return c.json({\n totalTokens: s.totalTokens,\n agentCount: s.agentCount,\n byModel: s.byModel.map((m) => ({\n modelV: m.modelV,\n tokens: m.tokens,\n agents: m.agents,\n ...modelDisplay(m.modelV),\n })),\n byAgent: s.byAgent.map((a) => ({\n ...a,\n ...modelDisplay(a.modelV),\n })),\n retired: {\n tokens: s.retired.tokens,\n agents: s.retired.agents,\n byModel: s.retired.byModel.map((m) => ({\n modelV: m.modelV,\n tokens: m.tokens,\n agents: m.agents,\n ...modelDisplay(m.modelV),\n })),\n },\n });\n });\n\n return r;\n}\n","/**\n * Find a free local port starting from `base`. We probe by attempting to bind\n * a Node net.Server to 127.0.0.1; the OS only opens the port if it's free.\n */\nimport { createServer } from \"node:net\";\n\nexport async function findFreePort(base = 3030, maxTries = 20): Promise<number> {\n for (let i = 0; i < maxTries; i++) {\n const port = base + i;\n if (await isPortFree(port)) return port;\n }\n throw new Error(`No free port in range ${base}..${base + maxTries - 1}`);\n}\n\nfunction isPortFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const tester = createServer()\n .once(\"error\", () => resolve(false))\n .once(\"listening\", () => {\n tester.close(() => resolve(true));\n })\n .listen(port, \"127.0.0.1\");\n });\n}\n"],"mappings":"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CA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EA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A,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,IAAI;AAGX,SAAS,gBACP,KACA,oB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","names":["Hono","existsSync","SELECT_COLS","mapRow","SELECT_COLS","mapRow","randomBytes","randomBytes","loggedFetch","open","ABILITY_AXES","parseAbility","extractJson","NAME_MAX","ABILITY_AXES","Hono","NAME_MAX","BIO_MAX","Hono","join","Hono","join","mapRow","extractJson","extractJson","mapRow","mapRow","COLS","mapRow","dirs","join","writerLine","Hono","dirs","join","Hono","Hono","Hono","Hono","Hono","Hono","Hono","COLS","mapRow","CHEAP_BY_CARRIER","extractJson","open","ALLOWED_KINDS","mapRow","turnState","open","Hono","resolve","Hono","Hono","Hono","existsSync","resolve","err","resolve","dirs","err"]}
|