@gorlitzer-labs/apiary 1.13.9 → 1.15.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.
Files changed (33) hide show
  1. package/README.md +17 -3
  2. package/dist/agent/index.d.ts +73 -0
  3. package/dist/agent/index.js +1 -1
  4. package/dist/{chunk-BCQQJR5E.js → chunk-3UQBSELW.js} +7 -178
  5. package/dist/chunk-3UQBSELW.js.map +1 -0
  6. package/dist/{chunk-7JXPJO3R.js → chunk-74XU7HHN.js} +6 -6
  7. package/dist/{chunk-4A7MREYX.js → chunk-N2LPKMPO.js} +185 -2
  8. package/dist/chunk-N2LPKMPO.js.map +1 -0
  9. package/dist/chunk-OEAHTZCA.js +994 -0
  10. package/dist/chunk-OEAHTZCA.js.map +1 -0
  11. package/dist/{chunk-MEWZT5KA.js → chunk-W4KWQKPP.js} +2 -2
  12. package/dist/{chunk-WJD5EKO6.js → chunk-Z4J6TRJY.js} +154 -41
  13. package/dist/chunk-Z4J6TRJY.js.map +1 -0
  14. package/dist/cli/index.js +39 -386
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/{invites-GRROUSCE.js → invites-SJ4ZVKVX.js} +4 -4
  17. package/dist/{join-H62VB4HU.js → join-5UFFMSLS.js} +4 -4
  18. package/dist/{room-ID4DQDPT.js → room-S6ULVAEH.js} +10 -10
  19. package/dist/run-5PSUQJF4.js +270 -0
  20. package/dist/run-5PSUQJF4.js.map +1 -0
  21. package/package.json +3 -2
  22. package/dist/chunk-4A7MREYX.js.map +0 -1
  23. package/dist/chunk-BCQQJR5E.js.map +0 -1
  24. package/dist/chunk-WJD5EKO6.js.map +0 -1
  25. package/dist/chunk-XLUTCAY6.js +0 -288
  26. package/dist/chunk-XLUTCAY6.js.map +0 -1
  27. package/dist/run-C22WQZVK.js +0 -91
  28. package/dist/run-C22WQZVK.js.map +0 -1
  29. /package/dist/{chunk-7JXPJO3R.js.map → chunk-74XU7HHN.js.map} +0 -0
  30. /package/dist/{chunk-MEWZT5KA.js.map → chunk-W4KWQKPP.js.map} +0 -0
  31. /package/dist/{invites-GRROUSCE.js.map → invites-SJ4ZVKVX.js.map} +0 -0
  32. /package/dist/{join-H62VB4HU.js.map → join-5UFFMSLS.js.map} +0 -0
  33. /package/dist/{room-ID4DQDPT.js.map → room-S6ULVAEH.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/bind.ts","../src/core/rules.ts","../src/cli/config.ts","../src/cli/agent-session.ts","../src/cli/tmux.ts","../src/cli/serve.ts","../src/cli/terminate-agent.ts"],"sourcesContent":["/**\n * Which network interface a room server listens on, and which address it\n * advertises in join links.\n *\n * `--expose` binds 0.0.0.0 and advertises the first non-internal IPv4 the OS\n * happens to list. That is wrong in both directions on a laptop that is on a\n * VPN: the room ends up reachable from whatever café wifi you are on, while\n * the join link points at a home-LAN address that is dead the moment you\n * leave the house.\n *\n * `--bind` fixes both by naming the interface. The common case is a tailnet:\n *\n * apiary room hive --bind tailscale\n *\n * which listens only on the Tailscale interface and mints join links against\n * the tailnet address — reachable from your phone anywhere, invisible to the\n * local network, with Tailscale doing the identity. No public tunnel, and no\n * bearer token in a URL that traverses the internet.\n */\n\nimport { networkInterfaces } from \"node:os\";\n\n/** An IPv4 address on this machine, with the interface that owns it. */\nexport interface LocalAddress {\n iface: string;\n address: string;\n}\n\n/**\n * Tailscale hands out addresses from 100.64.0.0/10 — the CGNAT range reserved\n * by RFC 6598 for carrier NAT, which Tailscale reuses for tailnets because it\n * will not collide with normal private ranges (10/8, 172.16/12, 192.168/16).\n * That makes \"is this a tailnet address?\" a reliable test rather than a guess\n * at interface names, which differ across platforms (utun on macOS,\n * tailscale0 on Linux).\n */\nexport function isTailnetAddress(address: string): boolean {\n const octets = address.split(\".\").map(Number);\n if (octets.length !== 4 || octets.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) {\n return false;\n }\n // 100.64.0.0/10 → first octet 100, second octet 64-127.\n return octets[0] === 100 && octets[1] >= 64 && octets[1] <= 127;\n}\n\n/** Every non-loopback IPv4 address on this machine, in OS order. */\nexport function localAddresses(): LocalAddress[] {\n const out: LocalAddress[] = [];\n for (const [iface, addrs] of Object.entries(networkInterfaces())) {\n for (const addr of addrs ?? []) {\n if (addr.family === \"IPv4\" && !addr.internal) out.push({ iface, address: addr.address });\n }\n }\n return out;\n}\n\n/**\n * This machine's tailnet address, or null if Tailscale is not up.\n *\n * `addrs` is injectable so the interface-ordering behaviour can be tested\n * deterministically — on a real machine the order is whatever the OS reports,\n * which is precisely the thing that made `--expose` unpredictable.\n */\nexport function tailnetAddress(addrs: LocalAddress[] = localAddresses()): string | null {\n return addrs.find((a) => isTailnetAddress(a.address))?.address ?? null;\n}\n\n/**\n * This machine's LAN address — the first non-loopback IPv4 that is NOT a\n * tailnet address.\n *\n * Excluding the tailnet matters: on a machine with Tailscale up, \"the first\n * non-internal IPv4\" can be either one depending on interface order, so\n * without the filter `--bind lan` would sometimes silently mean `tailscale`.\n */\nexport function lanAddress(addrs: LocalAddress[] = localAddresses()): string | null {\n return addrs.find((a) => !isTailnetAddress(a.address))?.address ?? null;\n}\n\nexport const ALL_INTERFACES = \"0.0.0.0\";\nexport const LOOPBACK = \"127.0.0.1\";\n\nexport type BindResolution =\n | { ok: true; address: string; note?: string }\n | { ok: false; error: string };\n\n/**\n * Turn a `--bind` value into an address to listen on.\n *\n * Accepts the aliases `tailscale`/`ts`, `lan`, `all`, and `localhost`, or a\n * literal address. A literal that is not present on this machine is rejected\n * rather than passed to `listen()`, which would otherwise fail with a bare\n * EADDRNOTAVAIL long after the useful context is gone.\n */\nexport function resolveBindAddress(spec: string): BindResolution {\n const value = spec.trim();\n if (!value) return { ok: false, error: \"empty --bind value\" };\n\n switch (value.toLowerCase()) {\n case \"tailscale\":\n case \"tailnet\":\n case \"ts\": {\n const address = tailnetAddress();\n if (!address) {\n return {\n ok: false,\n error: \"no Tailscale address found (100.64.0.0/10). Is Tailscale up? Check with: tailscale ip -4\",\n };\n }\n return { ok: true, address, note: \"tailnet — reachable from your other tailnet devices only\" };\n }\n\n case \"lan\": {\n const address = lanAddress();\n if (!address) return { ok: false, error: \"no LAN address found on this machine\" };\n return { ok: true, address, note: \"LAN — reachable from this network only\" };\n }\n\n case \"all\":\n case \"any\":\n case ALL_INTERFACES:\n return {\n ok: true,\n address: ALL_INTERFACES,\n note: \"ALL interfaces — including any untrusted network this machine joins\",\n };\n\n case \"localhost\":\n case LOOPBACK:\n return { ok: true, address: LOOPBACK, note: \"this machine only\" };\n\n default:\n break;\n }\n\n const owner = localAddresses().find((a) => a.address === value);\n if (!owner) {\n const available = localAddresses().map((a) => `${a.address} (${a.iface})`).join(\", \");\n return {\n ok: false,\n error: `\"${value}\" is not an address on this machine.`\n + (available ? ` Available: ${available}` : \"\")\n + ` Or use: tailscale, lan, all, localhost.`,\n };\n }\n return {\n ok: true,\n address: value,\n note: isTailnetAddress(value)\n ? `tailnet (${owner.iface}) — reachable from your other tailnet devices only`\n : `${owner.iface}`,\n };\n}\n\n/**\n * The address to put in join links, given what the server bound to.\n *\n * 0.0.0.0 is not routable, so it has to be translated to something a client\n * can dial: prefer the tailnet address if there is one, since a link that\n * works from a phone anywhere beats one that works only on this wifi.\n */\nexport function advertisedAddress(\n bound: string,\n addrs: LocalAddress[] = localAddresses(),\n): string {\n if (bound !== ALL_INTERFACES) return bound;\n return tailnetAddress(addrs) ?? lanAddress(addrs) ?? LOOPBACK;\n}\n","/**\n * Room rules — a small text-based set of ground rules visible to every\n * participant, surfaced to agents on join via the `apiary__join_room` MCP\n * response and to humans via the `/rules` TUI command.\n *\n * Storage: per-room Markdown file at `~/.apiary/rooms/<name>.rules.md`, with\n * an optional global default at `~/.apiary/rules.default.md` that new rooms\n * inherit. Format mirrors what `.cursorrules` / `.editorconfig` / Claude\n * Code's `CLAUDE.md` converged on — Markdown with a bullet list, hand-editable\n * by any editor.\n *\n * Schema growth (v1 → v2): v1 stores rules as `string[]` (one bullet per\n * rule). v2 can add optional YAML frontmatter with `schema: apiary.rules/2`\n * and structured per-rule metadata; the v1 parser keeps working on v2\n * documents because frontmatter starts with `---` and is skipped before bullet\n * extraction. Only `schema` and the bullet `text` carry meaning across\n * versions; everything else is opaque-but-preserved (the `.editorconfig`\n * lesson). The design deliberately drops MUST / SHOULD severity — OpenAI's\n * Model Spec found it invites lawyering with no compliance gain.\n *\n * Enforcement: v1 is voluntary compliance only. No audit, no kick-on-violation.\n * Honest about that — rules are guidance for an LLM, not a sandbox.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join as pathJoin, dirname } from \"node:path\";\n\n/**\n * The apiary house ruleset — opinionated defaults. Imperative verbs, no\n * MUST/SHOULD, bee-themed for the apiary tone.\n *\n * Hard-capped: agents tune out long rule lists, and apiary will be tempted\n * to grow this over time. Past ~10 rules compliance per rule drops sharply.\n */\nexport const DEFAULT_RULES: readonly string[] = [\n \"Never push to main. Open a PR.\",\n \"Don't touch repos you don't own — your cwd is your hive.\",\n \"No force-push, no `git reset --hard`, no `rm -rf` on anything you didn't make this session.\",\n \"Announce destructive intent before you act. \\\"About to delete X\\\" beats \\\"I deleted X, sorry.\\\"\",\n \"If a human is in the room, wait for their ack before merging, deploying, or running anything irreversible.\",\n \"Stay in your lane. If a task belongs to another agent, @mention them — don't poach.\",\n \"When uncertain, ask in the room with send_message — nobody reads your terminal. A clarifying question costs a token; a wrong commit costs an afternoon.\",\n];\n\nexport const RULES_MAX = 12; // hard cap — keeps the list scannable for humans + LLMs\n\nconst APIARY_DIR = pathJoin(homedir(), \".apiary\");\nconst GLOBAL_DEFAULT_PATH = pathJoin(APIARY_DIR, \"rules.default.md\");\nconst ROOMS_DIR = pathJoin(APIARY_DIR, \"rooms\");\n\nexport function roomRulesPath(roomName: string): string {\n return pathJoin(ROOMS_DIR, `${roomName}.rules.md`);\n}\n\n// ── Markdown serialization ────────────────────────────────────────────────\n\n/**\n * Parse a Markdown rules document. Strips optional `---` YAML frontmatter\n * (forward-compat with v2), then extracts every line that starts with `- `\n * or `* ` as one rule, trimming the marker.\n *\n * Unknown frontmatter keys are ignored — this is what makes the format\n * evolvable. See `.editorconfig` spec for the same design.\n */\nexport function parseRulesMarkdown(content: string): string[] {\n let body = content.trimStart();\n if (body.startsWith(\"---\")) {\n const end = body.indexOf(\"\\n---\", 3);\n if (end !== -1) body = body.slice(end + 4); // skip past closing `---\\n`\n }\n const rules: string[] = [];\n for (const rawLine of body.split(\"\\n\")) {\n const line = rawLine.trim();\n if (line.startsWith(\"- \")) rules.push(line.slice(2).trim());\n else if (line.startsWith(\"* \")) rules.push(line.slice(2).trim());\n }\n return rules.filter((r) => r.length > 0);\n}\n\nexport function formatRulesMarkdown(rules: readonly string[]): string {\n const header = \"# Room rules\\n\\n\";\n const body = rules.map((r) => `- ${r}`).join(\"\\n\");\n return header + body + \"\\n\";\n}\n\n// ── Disk I/O — with global → room cascade ─────────────────────────────────\n\n/**\n * Load rules for a room with cascade:\n * 1. If `~/.apiary/rooms/<name>.rules.md` exists → use it (room override).\n * 2. Else if `~/.apiary/rules.default.md` exists → use it.\n * 3. Else fall back to DEFAULT_RULES.\n *\n * The returned array is never longer than RULES_MAX; extras are dropped.\n */\nexport function loadRulesForRoom(roomName: string): string[] {\n const roomPath = roomRulesPath(roomName);\n const candidates = [roomPath, GLOBAL_DEFAULT_PATH];\n for (const p of candidates) {\n if (existsSync(p)) {\n try {\n const parsed = parseRulesMarkdown(readFileSync(p, \"utf-8\"));\n if (parsed.length > 0) return parsed.slice(0, RULES_MAX);\n } catch {\n /* corrupt file — fall through */\n }\n }\n }\n return [...DEFAULT_RULES];\n}\n\n/** Write the per-room rules file, creating the rooms dir if needed. */\nexport function saveRulesForRoom(roomName: string, rules: readonly string[]): void {\n const p = roomRulesPath(roomName);\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p, formatRulesMarkdown(rules.slice(0, RULES_MAX)));\n}\n\n/**\n * Seed the per-room rules file with defaults if it doesn't exist yet. Returns\n * the path written, or null if already present.\n */\nexport function seedRulesIfMissing(roomName: string): string | null {\n const p = roomRulesPath(roomName);\n if (existsSync(p)) return null;\n // Seed from the cascade (global default if present, else built-in defaults).\n const initial = existsSync(GLOBAL_DEFAULT_PATH)\n ? parseRulesMarkdown(readFileSync(GLOBAL_DEFAULT_PATH, \"utf-8\"))\n : [...DEFAULT_RULES];\n saveRulesForRoom(roomName, initial.length > 0 ? initial : [...DEFAULT_RULES]);\n return p;\n}\n\n","/**\n * Persistent user config — ~/.apiary/config.json\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst CONFIG_DIR = join(homedir(), \".apiary\");\nconst CONFIG_PATH = join(CONFIG_DIR, \"config.json\");\n\nexport interface ApiaryConfig {\n sound?: boolean;\n}\n\nexport function loadConfig(): ApiaryConfig {\n if (!existsSync(CONFIG_PATH)) return {};\n try {\n return JSON.parse(readFileSync(CONFIG_PATH, \"utf-8\"));\n } catch {\n return {};\n }\n}\n\nexport function saveConfig(config: ApiaryConfig): void {\n if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });\n writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + \"\\n\");\n}\n\n/**\n * Pick a stable index into a pool based on a string seed.\n * Same seed always returns the same index. Different seeds tend to spread\n * across the pool, but collisions are possible with a finite pool.\n */\nexport function stableIndex(seed: string, poolSize: number): number {\n let hash = 0;\n for (const c of seed) hash = ((hash << 5) - hash + c.charCodeAt(0)) | 0;\n return ((hash % poolSize) + poolSize) % poolSize;\n}\n\n/** Stable per-agent insect/bug emoji pool. Used in tmux tab titles + wizard summary. */\nexport const BUG_POOL: readonly string[] = [\n \"🐝\", \"🐛\", \"🦋\", \"🐞\", \"🪲\", \"🐜\", \"🦗\", \"🪳\", \"🦂\", \"🕷️\",\n \"🪰\", \"🦟\", \"🐌\", \"🐙\", \"🦑\", \"🦀\", \"🪱\", \"🦠\", \"🧬\", \"🔬\",\n];\n\n/** Stable per-room hive emoji pool. Used in terminal tab titles for `serve` and `join`. */\nexport const HIVE_POOL: readonly string[] = [\"🍯\", \"🐝\", \"🏠\", \"🪺\", \"🌸\"];\n\n/** Stable bug emoji for an agent — same name always gets the same bug. */\nexport function agentEmoji(agentName: string): string {\n return BUG_POOL[stableIndex(agentName, BUG_POOL.length)];\n}\n\n/** Stable hive emoji for a room — same room name always gets the same hive. */\nexport function roomEmoji(roomName: string): string {\n return HIVE_POOL[stableIndex(roomName, HIVE_POOL.length)];\n}\n","/**\n * Shared session registry for client-side agent runtimes (claude, codex, opencode).\n *\n * Each runtime persists a small JSON file in ~/.apiary/sessions/<runtime>_<name>.json\n * so `apiary ps` and `apiary stop` can see and tear them down.\n */\n\nimport { writeFileSync, readFileSync, readdirSync, existsSync, mkdirSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport const SESSION_DIR = join(homedir(), \".apiary\", \"sessions\");\n\nexport type AgentRuntime = \"claude\" | \"codex\" | \"opencode\";\n\nexport interface AgentSession {\n runtime: AgentRuntime;\n agentName: string;\n pid: number;\n /** Tmux session (codex/claude) — undefined for opencode. */\n tmuxSession?: string;\n /** Tmp dir to remove on stop. */\n tmpDir?: string;\n /** OpenCode-style child pid that needs a separate kill. */\n childPid?: number;\n}\n\nfunction sessionPath(runtime: AgentRuntime, name: string): string {\n return join(SESSION_DIR, `${runtime}_${name}.json`);\n}\n\nexport function saveAgentSession(session: AgentSession): void {\n if (!existsSync(SESSION_DIR)) mkdirSync(SESSION_DIR, { recursive: true });\n writeFileSync(sessionPath(session.runtime, session.agentName), JSON.stringify(session, null, 2));\n}\n\nexport function clearAgentSession(runtime: AgentRuntime, name: string): void {\n try { rmSync(sessionPath(runtime, name)); } catch { /* ok */ }\n}\n\nexport function listAgentSessions(runtime: AgentRuntime): AgentSession[] {\n if (!existsSync(SESSION_DIR)) return [];\n const prefix = `${runtime}_`;\n const sessions: AgentSession[] = [];\n for (const f of readdirSync(SESSION_DIR) as string[]) {\n if (!f.startsWith(prefix) || !f.endsWith(\".json\")) continue;\n try {\n sessions.push(JSON.parse(readFileSync(join(SESSION_DIR, f), \"utf-8\")));\n } catch { /* skip */ }\n }\n return sessions;\n}\n\n/** True if the recorded pid is still alive. */\nexport function isAgentAlive(session: AgentSession): boolean {\n try { process.kill(session.pid, 0); return true; } catch { return false; }\n}\n","/**\n * tmux helpers for apiary CLI.\n *\n * Thin wrappers around tmux commands. Used by the server process to\n * inject room events into Claude Code sessions.\n */\n\nimport { execFileSync, spawn } from \"node:child_process\";\n\n/** Sanitize a string for use as a tmux session name. Replaces tmux-special chars. */\nfunction sanitizeSessionName(name: string): string {\n return name.replace(/[.:$%]/g, \"_\");\n}\n\n/** Check if tmux is installed and available. */\nexport function tmuxAvailable(): boolean {\n try {\n execFileSync(\"tmux\", [\"-V\"], { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Check if a tmux session exists. */\nexport function tmuxSessionExists(session: string): boolean {\n try {\n const name = sanitizeSessionName(session);\n execFileSync(\"tmux\", [\"has-session\", \"-t\", name], { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Geometry for agent sessions.\n *\n * A detached tmux session defaults to 80x24, which is cramped for either\n * agent CLI — and, more importantly, the bridges decide what an agent is\n * doing by pattern-matching `capture-pane` output. Narrow panes wrap text\n * mid-pattern, so the geometry is a correctness input, not a cosmetic one.\n */\nexport const AGENT_PANE_COLS = 200;\nexport const AGENT_PANE_ROWS = 50;\n\n/**\n * Create a detached tmux session with no status bar. Optionally set the\n * terminal tab title.\n *\n * The size is pinned with `window-size manual`. Without it tmux resizes a\n * session to fit whichever client attached most recently, so merely opening a\n * window to watch an agent reflows its pane — 200x50 down to an 80x24\n * terminal's size — and the bridge's screen patterns start missing. Read-only\n * clients resize too, so `attach -r` is not protection.\n */\nexport function tmuxCreateSession(\n session: string,\n title?: string,\n size: { cols: number; rows: number } = { cols: AGENT_PANE_COLS, rows: AGENT_PANE_ROWS },\n): void {\n const name = sanitizeSessionName(session);\n execFileSync(\"tmux\", [\n \"new-session\", \"-d\", \"-s\", name,\n \"-x\", String(size.cols), \"-y\", String(size.rows),\n ]);\n execFileSync(\"tmux\", [\"set\", \"-t\", name, \"status\", \"off\"]);\n // Pin the geometry so an attaching watcher cannot reflow the agent's pane.\n try {\n execFileSync(\"tmux\", [\"set-option\", \"-t\", name, \"window-size\", \"manual\"]);\n execFileSync(\"tmux\", [\"resize-window\", \"-t\", name, \"-x\", String(size.cols), \"-y\", String(size.rows)]);\n } catch {\n // tmux < 3.1 has no window-size option; the agent still runs, it just\n // reflows when watched.\n }\n if (title) {\n execFileSync(\"tmux\", [\"set\", \"-t\", name, \"set-titles\", \"on\"]);\n execFileSync(\"tmux\", [\"set\", \"-t\", name, \"set-titles-string\", title]);\n }\n}\n\n/** Send a command to a tmux session (types it + presses Enter). */\nexport function tmuxSendCommand(session: string, command: string): void {\n const name = sanitizeSessionName(session);\n execFileSync(\"tmux\", [\"send-keys\", \"-t\", name, \"-l\", command]);\n execFileSync(\"tmux\", [\"send-keys\", \"-t\", name, \"Enter\"]);\n}\n\n/**\n * Inject text into a tmux session (literal keys, no Enter).\n * Used for room event injection.\n */\nexport function tmuxInjectText(session: string, text: string): void {\n const name = sanitizeSessionName(session);\n execFileSync(\"tmux\", [\"send-keys\", \"-t\", name, \"-l\", text]);\n}\n\n/**\n * Inject text as a single bracketed paste — start marker, payload and end\n * marker in one `send-keys` call.\n *\n * Three separate calls would be equivalent on paper and worse in practice:\n * each `execFileSync` spawns a tmux client, so the markers and the payload\n * reach the pty with process-spawn gaps between them. Codex decides whether\n * input is a paste or typing partly on timing, and a stretched gap makes the\n * \"typing\" reading more likely. One write removes the gaps we control.\n *\n * This narrows the window; it does not close it, because the pty can still\n * split a large write. The guarantee that a room message actually submits\n * comes from the popup guard in CodexTmuxBridge, not from here.\n */\nexport function tmuxInjectPaste(session: string, text: string): void {\n const name = sanitizeSessionName(session);\n execFileSync(\"tmux\", [\"send-keys\", \"-t\", name, \"-l\", `\\x1b[200~${text}\\x1b[201~`]);\n}\n\n/** Send Enter key to a tmux session (submits input). */\nexport function tmuxSendEnter(session: string): void {\n const name = sanitizeSessionName(session);\n execFileSync(\"tmux\", [\"send-keys\", \"-t\", name, \"Enter\"]);\n}\n\n/** Attach to a tmux session. Returns a promise that resolves when detached/exited.\n *\n * Two modes:\n * - Outside tmux: `tmux attach` (blocks until user detaches, event loop stays free via spawn)\n * - Inside tmux: `tmux switch-client` (exits immediately) + polls until session ends\n */\nexport function tmuxAttach(session: string): Promise<void> {\n const name = sanitizeSessionName(session);\n\n if (process.env.TMUX) {\n // switch-client exits immediately after switching — poll until session is destroyed\n try {\n execFileSync(\"tmux\", [\"switch-client\", \"-t\", name], { stdio: \"ignore\" });\n } catch {\n // switch-client failed (e.g. no client) — fall through to polling\n }\n return new Promise<void>((resolve) => {\n const poll = setInterval(() => {\n try {\n execFileSync(\"tmux\", [\"has-session\", \"-t\", name], { stdio: \"ignore\" });\n } catch {\n clearInterval(poll);\n resolve();\n }\n }, 500);\n });\n }\n\n return new Promise<void>((resolve) => {\n const child = spawn(\"tmux\", [\"attach\", \"-t\", name], { stdio: \"inherit\" });\n child.on(\"exit\", () => resolve());\n child.on(\"error\", () => resolve());\n });\n}\n\n/** Capture visible screen content as array of lines. */\nexport function tmuxCapturePane(session: string): string[] {\n try {\n const name = sanitizeSessionName(session);\n const output = execFileSync(\"tmux\", [\"capture-pane\", \"-t\", name, \"-p\"], {\n encoding: \"utf-8\",\n });\n return trimTrailingBlankLines(output.split(\"\\n\"));\n } catch {\n return [];\n }\n}\n\n/**\n * Drop trailing blank lines from a capture.\n *\n * `capture-pane` returns every row of the pane, blank ones included. Both\n * bridges then read \"the bottom of the screen\" as `lines.slice(-N)` to decide\n * what the CLI is doing — which silently stops working the moment the pane is\n * taller than the CLI's rendered output: the slice lands entirely in the empty\n * rows, every state reads as \"unknown\", and the bridge queues events forever\n * instead of delivering them.\n *\n * Trimming here rather than in each detector keeps \"the last N lines\" meaning\n * \"the last N lines of actual content\" for every caller.\n */\nexport function trimTrailingBlankLines(lines: string[]): string[] {\n let end = lines.length;\n while (end > 0 && lines[end - 1].trim() === \"\") end--;\n return lines.slice(0, end);\n}\n\n/**\n * Send a control key sequence (e.g. \"C-u\", \"C-y\", \"Escape\").\n * Unlike tmuxInjectText, this does NOT use -l, so tmux interprets\n * the key name rather than treating it as literal text.\n */\nexport function tmuxSendKey(session: string, key: string): void {\n const name = sanitizeSessionName(session);\n execFileSync(\"tmux\", [\"send-keys\", \"-t\", name, key]);\n}\n\n/** Kill a tmux session. */\nexport function tmuxKillSession(session: string): void {\n try {\n const name = sanitizeSessionName(session);\n execFileSync(\"tmux\", [\"kill-session\", \"-t\", name], { stdio: \"ignore\" });\n } catch {\n // Session may already be dead\n }\n}\n\n/**\n * Reset terminal to a sane state.\n *\n * Killing tmux sessions can leave the terminal with broken line discipline\n * (no echo, raw mode, garbled input). This restores it. Call after any\n * tmux teardown that might affect the user's terminal.\n */\nexport function resetTerminal(): void {\n // No-op when stdin isn't a tty (headless, piped, backgrounded). Calling\n // stty in that case prints \"stdin isn't a terminal\" to stderr for no gain.\n if (!process.stdin.isTTY) return;\n try {\n // stty sane restores line discipline (echo, cooked mode, signals)\n execFileSync(\"stty\", [\"sane\"], { stdio: \"inherit\" });\n } catch {\n // Best effort\n }\n}\n\n/**\n * Shells, by process name.\n *\n * Detecting the SHELL rather than the agent is deliberate. A pane running an\n * agent reports whatever that CLI happens to call its process — Claude Code\n * reports its own version string, e.g. \"2.1.267\" — so there is nothing stable\n * to match on. Shells, by contrast, are a small and stable set.\n *\n * Screen text is no help either: a prompt is infinitely customisable (this\n * machine's is a starship prompt with a python env and a clock), so\n * \"does it look like a shell?\" cannot be answered from the rendering.\n */\nconst SHELL_COMMANDS = new Set([\n \"sh\", \"bash\", \"zsh\", \"fish\", \"dash\", \"ksh\", \"mksh\", \"tcsh\", \"csh\",\n \"nu\", \"pwsh\", \"powershell\", \"login\", \"elvish\", \"xonsh\",\n]);\n\n/** The command tmux reports as running in the pane, or null if unknown. */\nexport function tmuxPaneCommand(session: string): string | null {\n try {\n const name = sanitizeSessionName(session);\n const out = execFileSync(\"tmux\", [\"display-message\", \"-p\", \"-t\", name, \"#{pane_current_command}\"], {\n encoding: \"utf-8\",\n timeout: 5_000,\n }).trim();\n return out || null;\n } catch {\n return null;\n }\n}\n\n/**\n * True if the pane is sitting at a shell, meaning the agent CLI has exited.\n *\n * The case that prompted this: Codex self-updated on launch, printed\n * \"Update ran successfully! Please restart Codex\" and exited. The pane fell\n * back to zsh, the bridge read the shell prompt as an idle agent, and typed a\n * room message into the shell — \"zsh: command not found: Please\".\n *\n * Returns false when tmux cannot say, so an unknown answer never escalates\n * into \"the agent is gone\".\n */\nexport function tmuxPaneIsShell(session: string): boolean {\n const cmd = tmuxPaneCommand(session);\n if (!cmd) return false;\n return SHELL_COMMANDS.has(cmd.toLowerCase().replace(/^-/, \"\"));\n}\n","/**\n * apiary serve — dumb room server.\n *\n * One room, one HTTP API, SSE broadcasting, authority enforcement.\n * No EventProcessor, no tmux, no agent lifecycle — those live client-side.\n * Humans connect via `apiary join`, agents via `apiary run claude`.\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { spawn, execFileSync, type ChildProcess } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { advertisedAddress, ALL_INTERFACES, LOOPBACK } from \"./bind.js\";\nimport { join as pathJoin, resolve, sep } from \"node:path\";\n\nimport { Room } from \"../core/room.js\";\nimport { InMemoryStorage, FileBackedStorage } from \"../core/storage.js\";\nimport { randomRoomName, randomName } from \"../core/names.js\";\nimport { createEvent, type ActivityEvent, type AuthorityChangedEvent, type ParticipantKickedEvent, type RoomClearedEvent, type RoomEvent, type RulesChangedEvent, type StatusChangedEvent, type WhisperNotifiedEvent } from \"../core/events.js\";\nimport { loadRulesForRoom, saveRulesForRoom, seedRulesIfMissing, roomRulesPath, RULES_MAX } from \"../core/rules.js\";\nimport { watchFile, unwatchFile, type Stats } from \"node:fs\";\nimport { AttachmentSchema } from \"../core/types.js\";\nimport type { AuthorityLevel, Attachment } from \"../core/types.js\";\nimport { can } from \"../core/authority.js\";\nimport type { Channel } from \"../core/channel.js\";\nimport { formatTimestamp } from \"../agent/prompts.js\";\nimport { TokenManager, buildShareUrl } from \"./auth.js\";\nimport { roomEmoji } from \"./config.js\";\nimport { seatsToEvict, terminateLocalAgent } from \"./terminate-agent.js\";\nimport { tmuxKillSession } from \"./tmux.js\";\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\ninterface ConnectedParticipant {\n id: string;\n name: string;\n /** \"human\" or \"agent\" — kick stops an agent's runtime, never a person's. */\n type: \"human\" | \"agent\";\n authority: AuthorityLevel;\n channel: Channel;\n sessionToken: string;\n}\n\ninterface ConnectedGuest {\n id: string;\n authority: \"guest\";\n channel: Channel;\n sessionToken: string;\n}\n\nexport interface ServeOptions {\n room?: string;\n port?: number;\n share?: boolean;\n quiet?: boolean;\n /** Suppress all human-readable output; emit one JSON line with server info on stdout. */\n headless?: boolean;\n /** Path to save room state to (JSON file, written on every event). */\n save?: string;\n /** Path to load room state from (JSON file). Implies save to the same file. */\n load?: string;\n /**\n * Address to listen on. `tailscale` / `lan` / `all` / `localhost`, or a\n * literal IPv4 on this machine. Takes precedence over `expose`.\n */\n bind?: string;\n /** Bind to 0.0.0.0 instead of 127.0.0.1. Required for non-localhost access. */\n expose?: boolean;\n /** Allowed CORS origins (in addition to localhost and tunnel URL). */\n corsOrigins?: string[];\n /** Share token TTL in ms. Defaults to 24h; use a longer value for persistent sessions. */\n shareTtlMs?: number;\n}\n\nexport interface ServeResult {\n serverUrl: string;\n publicUrl: string;\n roomName: string;\n adminToken: string;\n memberToken: string;\n}\n\n// ── Room session persistence ─────────────────────────────────────────────────\n\nexport const SESSION_DIR = pathJoin(homedir(), \".apiary\", \"sessions\");\nexport const INVITES_DIR = pathJoin(homedir(), \".apiary\", \"invites\");\n\nexport interface PersistedRoomSession {\n roomName: string;\n /**\n * Address the server was told to listen on. Persisted so `room resume`\n * restarts it the same way — otherwise a tailnet-bound room silently comes\n * back on localhost and every remote client stops reaching it.\n */\n bind?: string;\n serverUrl: string;\n publicUrl: string;\n adminToken: string;\n memberToken: string;\n pid: number;\n /** Unix timestamp of last TUI activity (set by room create/resume). */\n lastActive?: number;\n /**\n * Display name the host used at create time (e.g. \"Franco\"). roomResume\n * restores this so the admin keeps a consistent identity across reconnects —\n * otherwise the human gets a random name and agents can't recognize them.\n */\n hostName?: string;\n /** Participants recorded at create/resume time. */\n participants?: Array<{\n alias: string;\n cwd: string;\n role: string;\n runtime?: string;\n /**\n * Authority tier this participant joined at. Set by `apiary room create`\n * via alias-suffix grammar (e.g. `cane:owner`). Undefined = member.\n */\n tier?: AuthorityLevel;\n /**\n * Model id passed to the runtime (`--model` for claude). Set by alias-suffix\n * grammar (e.g. `bf:opus`, `anvil:sonnet`). Undefined = runtime default.\n */\n model?: string;\n /**\n * Engagement mode the runtime starts in. Set by alias-suffix grammar\n * (e.g. `cane:standby`), or by the wizard's default of keeping the first\n * agent listening and the rest quiet. Undefined = runtime default\n * (\"everyone\"). Persisted so `room resume` respawns agents as configured\n * rather than reverting them all to active.\n */\n mode?: string;\n }>;\n}\n\nfunction roomSessionPath(name: string): string {\n return pathJoin(SESSION_DIR, `room_${name}.json`);\n}\n\nexport function saveRoomSession(session: PersistedRoomSession): void {\n if (!existsSync(SESSION_DIR)) mkdirSync(SESSION_DIR, { recursive: true });\n writeFileSync(roomSessionPath(session.roomName), JSON.stringify(session, null, 2));\n}\n\nfunction clearRoomSession(name: string): void {\n try { rmSync(roomSessionPath(name)); } catch { /* ok */ }\n}\n\nexport function listRoomSessions(): PersistedRoomSession[] {\n if (!existsSync(SESSION_DIR)) return [];\n const files = readdirSync(SESSION_DIR) as string[];\n const sessions: PersistedRoomSession[] = [];\n for (const f of files) {\n if (!f.startsWith(\"room_\") || !f.endsWith(\".json\")) continue;\n try {\n sessions.push(JSON.parse(readFileSync(pathJoin(SESSION_DIR, f), \"utf-8\")));\n } catch { /* skip */ }\n }\n return sessions;\n}\n\nexport function removeRoomSession(name: string): void {\n try { rmSync(pathJoin(SESSION_DIR, `room_${name}.json`)); } catch { /* ok */ }\n}\n\n// ── SSE helper ───────────────────────────────────────────────────────────────\n\nasync function enrichAndSend(res: ServerResponse, event: RoomEvent, room: Room): Promise<void> {\n if (event.type === \"MessageSent\" && event.message.reply_to_id) {\n const replyMsg = await room.getMessage(event.message.reply_to_id);\n const enriched = {\n ...event,\n _replyToName: replyMsg?.sender_name ?? null,\n };\n res.write(`data: ${JSON.stringify(enriched)}\\n\\n`);\n return;\n }\n res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n}\n\n// ── Main serve command ───────────────────────────────────────────────────────\n\nconst ROOMS_DIR = pathJoin(homedir(), \".apiary\", \"rooms\");\n\nfunction validateSavePath(p: string): string {\n const resolved = resolve(p);\n if (!resolved.endsWith(\".json\")) throw new Error(\"Save/load path must end with .json\");\n const cwd = process.cwd();\n const tmp = tmpdir();\n const apiaryDir = pathJoin(homedir(), \".apiary\");\n if (!resolved.startsWith(cwd + sep) && !resolved.startsWith(tmp + sep) && !resolved.startsWith(apiaryDir + sep))\n throw new Error(`Path must be under ${cwd}, ${tmp}, or ${apiaryDir}`);\n return resolved;\n}\n\nexport async function serve(options: ServeOptions): Promise<ServeResult> {\n const roomName = options.room ?? randomRoomName();\n const port = options.port ?? 7890;\n // --bind names the interface explicitly; --expose is the older boolean that\n // means \"all of them\". Advertise something a client can actually dial:\n // 0.0.0.0 is not routable, and on a machine with a tailnet the first\n // non-internal IPv4 is often the home LAN, which is dead from a phone.\n const boundAddress = options.bind ?? (options.expose ? ALL_INTERFACES : LOOPBACK);\n const serverUrl = `http://${advertisedAddress(boundAddress)}:${port}`;\n const log = options.headless ? () => {} : logServer;\n\n let publicUrl = serverUrl;\n let tunnelProcess: ChildProcess | null = null;\n\n // Validate save/load paths\n if (options.save) options.save = validateSavePath(options.save);\n if (options.load) options.load = validateSavePath(options.load);\n\n // Create room with persistence\n // Default: ~/.apiary/rooms/<room>.json — auto-resumes previous session.\n // Explicit --save/--load overrides the default.\n mkdirSync(ROOMS_DIR, { recursive: true });\n const defaultPath = pathJoin(ROOMS_DIR, `${roomName}.json`);\n const savePath = options.save ?? options.load ?? defaultPath;\n const shouldAutoLoad = !options.save && !options.load && existsSync(defaultPath);\n let storage;\n if (options.load || shouldAutoLoad) {\n const loadPath = options.load ?? defaultPath;\n try {\n storage = await FileBackedStorage.load(loadPath);\n log(`loaded room state from ${loadPath}`);\n } catch (err: any) {\n if (err.code === \"ENOENT\") {\n storage = new FileBackedStorage(loadPath);\n log(`no existing file at ${loadPath}, starting fresh`);\n } else {\n throw err;\n }\n }\n } else {\n storage = new FileBackedStorage(savePath);\n }\n const room = new Room(roomName, storage);\n\n // ── Rules ─────────────────────────────────────────────────────────────────\n // Seed defaults on first boot, then watch the file so hand-edits hot-reload\n // and broadcast `RulesChanged` to every connected SSE client. The current\n // ruleset is held in-memory and re-served on `GET /rules` to avoid disk\n // I/O on the hot path.\n seedRulesIfMissing(roomName);\n let currentRules = loadRulesForRoom(roomName);\n const rulesFilePath = roomRulesPath(roomName);\n const onRulesFileChange = (curr: Stats, prev: Stats) => {\n if (curr.mtimeMs === prev.mtimeMs) return; // spurious event\n try {\n const next = loadRulesForRoom(roomName);\n // Skip no-op reloads (same content). Cheap stringify for short lists.\n if (JSON.stringify(next) === JSON.stringify(currentRules)) return;\n currentRules = next;\n broadcastRulesChanged(currentRules, \"[system]\");\n log(`rules reloaded from disk (${currentRules.length} rules)`);\n } catch {\n /* corrupt mid-edit — ignore, next save will fire again */\n }\n };\n watchFile(rulesFilePath, { interval: 2000 }, onRulesFileChange);\n\n // Auth\n const tokens = new TokenManager({ shareTtlMs: options.shareTtlMs });\n\n // Connected participants and guests (by session token for lookup)\n const participants = new Map<string, ConnectedParticipant>();\n const guests = new Map<string, ConnectedGuest>();\n // Reverse lookup: participantId → sessionToken\n const idToSession = new Map<string, string>();\n\n // Track active SSE connections for cleanup\n const sseConnections = new Map<string, ServerResponse>();\n\n // ── Presence tracking ────────────────────────────────────────────────────\n // Updated on every SSE write (heartbeat or event) and every authenticated HTTP request.\n const lastSeenAt = new Map<string, number>(); // participantId → ms timestamp\n const presenceStatus = new Map<string, \"online\" | \"unresponsive\" | \"offline\">();\n\n // Configurable thresholds (overridable via env for testing or custom deployments).\n // Both are measured from `lastSeenAt` (last proof of life), not from each other.\n // Default: unresponsive at 90s, offline at 180s from last activity.\n const UNRESPONSIVE_AFTER_MS = parseInt(process.env.APIARY_UNRESPONSIVE_MS ?? \"90000\", 10);\n const OFFLINE_AFTER_MS = parseInt(process.env.APIARY_OFFLINE_MS ?? String(UNRESPONSIVE_AFTER_MS * 2), 10);\n const PRESENCE_CHECK_INTERVAL_MS = parseInt(process.env.APIARY_PRESENCE_CHECK_MS ?? \"30000\", 10);\n\n function touchParticipant(id: string): void {\n lastSeenAt.set(id, Date.now());\n }\n\n /** Write an SSE frame to every connected client (unfiltered fan-out). */\n function broadcastToAll(event: unknown): void {\n const frame = `data: ${JSON.stringify(event)}\\n\\n`;\n for (const [, sseRes] of sseConnections) {\n sseRes.write(frame);\n }\n }\n\n /** Current participants as the summary shape returned by /participants + /join. */\n /**\n * Last agent state reported by each runtime, and when.\n *\n * The state was previously only broadcast, never kept, so nothing could ask\n * \"is that agent blocked?\" — which is what an agent needs in order to wait\n * for another one instead of re-reading the room on a loop.\n */\n const agentStates = new Map<string, { state: string; at: number }>();\n\n /** How long a reported state is trusted. Runtimes heartbeat every ~1.5s. */\n const AGENT_STATE_TTL_MS = 15_000;\n\n function agentStateFor(participantId: string): string | undefined {\n const entry = agentStates.get(participantId);\n if (!entry) return undefined;\n // A dead runtime must read as unknown rather than pinning whatever it last\n // said — otherwise a waiter blocks forever on a state that will never change.\n return Date.now() - entry.at < AGENT_STATE_TTL_MS ? entry.state : undefined;\n }\n\n function participantSummaries() {\n return room.listParticipants().map((p) => ({\n id: p.id,\n name: p.name,\n type: p.type,\n authority: p.authority ?? \"member\",\n /** \"idle\" | \"working\" | \"blocked\", or absent if the runtime doesn't report. */\n agentState: agentStateFor(p.id),\n }));\n }\n\n function broadcastStatusChange(\n participantId: string,\n name: string,\n status: \"online\" | \"unresponsive\" | \"offline\",\n previousStatus: \"online\" | \"unresponsive\" | \"offline\",\n reason: \"ping_timeout\" | \"recovered\" | \"left\" | \"kicked\",\n ): void {\n const event = createEvent<StatusChangedEvent>({\n type: \"StatusChanged\",\n category: \"PRESENCE\",\n room_id: room.roomId,\n participant_id: participantId,\n name,\n status,\n previous_status: previousStatus,\n reason,\n });\n broadcastToAll(event);\n }\n\n function broadcastRulesChanged(rules: string[], updatedBy: string): void {\n const event = createEvent<RulesChangedEvent>({\n type: \"RulesChanged\",\n category: \"ACTIVITY\",\n room_id: room.roomId,\n participant_id: \"[system]\",\n rules,\n updated_by: updatedBy,\n });\n broadcastToAll(event);\n }\n\n // ── Attachment storage ───────────────────────────────────────────────────\n // In-memory, room-scoped. Cleared when the room is cleared or server restarts.\n\n const MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024; // 10 MB per upload\n // Hard cap on the in-memory store. When uploads would push us over, evict\n // the oldest attachments (FIFO by upload timestamp) until there is room.\n // Without this, a long-running room with image uploads OOMs the daemon.\n const MAX_ATTACHMENT_STORE_BYTES = 256 * 1024 * 1024; // 256 MB total\n\n interface StoredAttachment {\n id: string;\n name: string;\n mimeType: string;\n size: number;\n roomId: string;\n bytes: Buffer;\n uploadedAt: number; // ms epoch — used for FIFO eviction\n }\n\n // Map preserves insertion order — iteration yields oldest-first, which is\n // exactly what we want for FIFO eviction.\n const attachmentStore = new Map<string, StoredAttachment>();\n let attachmentStoreBytes = 0;\n\n function evictAttachmentsToFit(neededBytes: number): void {\n if (neededBytes > MAX_ATTACHMENT_STORE_BYTES) return; // caller will reject — no point evicting everything\n while (attachmentStoreBytes + neededBytes > MAX_ATTACHMENT_STORE_BYTES) {\n const oldest = attachmentStore.entries().next();\n if (oldest.done) break;\n const [oldestId, oldestAtt] = oldest.value;\n attachmentStore.delete(oldestId);\n attachmentStoreBytes -= oldestAtt.size;\n }\n }\n\n function attachmentUrl(id: string): string {\n return `${publicUrl}/attachment/${id}`;\n }\n\n // ── JSON body parser helper ──────────────────────────────────────────────\n\n async function parseBody(req: IncomingMessage): Promise<Record<string, unknown>> {\n const chunks: Buffer[] = [];\n for await (const chunk of req) chunks.push(chunk as Buffer);\n try { return JSON.parse(Buffer.concat(chunks).toString()); } catch { return {}; }\n }\n\n // ── Auth helper ──────────────────────────────────────────────────────────\n\n function extractSessionToken(req: IncomingMessage, url: URL, body?: Record<string, unknown>): string | null {\n const h = req.headers.authorization;\n if (h?.startsWith(\"Bearer \")) return h.slice(7);\n // Fallback: query param / body (deprecated)\n return url.searchParams.get(\"token\") ?? (body?.token ? String(body.token) : null);\n }\n\n function getSession(token: string | null) {\n if (!token) return null;\n const p = participants.get(token);\n if (p) return { ...p, kind: \"participant\" as const };\n const g = guests.get(token);\n if (g) return { ...g, kind: \"guest\" as const };\n return null;\n }\n\n function clampInt(val: string | null, def: number, min: number, max: number): number {\n const n = parseInt(val ?? String(def), 10);\n return isNaN(n) ? def : Math.max(min, Math.min(max, n));\n }\n\n function jsonError(res: ServerResponse, status: number, error: string): void {\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error }));\n }\n\n function jsonOk(res: ServerResponse, data: Record<string, unknown> = {}): void {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true, ...data }));\n }\n\n // ── CORS helper ────────────────────────────────────────────────────────\n\n const allowedOrigins = new Set<string>([\n `http://127.0.0.1:${port}`,\n `http://localhost:${port}`,\n ...(options.corsOrigins ?? []),\n ]);\n\n function addCorsOrigin(origin: string): void {\n allowedOrigins.add(origin);\n }\n\n function getCorsHeaders(req: IncomingMessage): Record<string, string> {\n const origin = req.headers.origin;\n if (!origin) return {};\n if (allowedOrigins.has(origin)) {\n return {\n \"Access-Control-Allow-Origin\": origin,\n \"Access-Control-Allow-Methods\": \"GET, POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type, Authorization, Accept\",\n \"Access-Control-Max-Age\": \"86400\",\n Vary: \"Origin\",\n };\n }\n return {};\n }\n\n // ── Rate limiter ────────────────────────────────────────────────────────\n\n class RateLimiter {\n private _buckets = new Map<string, { count: number; resetAt: number }>();\n private _opsSincePrune = 0;\n constructor(private _max: number, private _windowMs: number) {}\n check(key: string): { allowed: boolean; retryAfter?: number } {\n const now = Date.now();\n // Opportunistic prune: every 256 ops, drop expired buckets so the map\n // can't grow forever from one-off keys (rotating IPs, dead sessions).\n if (++this._opsSincePrune >= 256) {\n this._opsSincePrune = 0;\n for (const [k, b] of this._buckets) {\n if (now >= b.resetAt) this._buckets.delete(k);\n }\n }\n const bucket = this._buckets.get(key);\n if (!bucket || now >= bucket.resetAt) {\n this._buckets.set(key, { count: 1, resetAt: now + this._windowMs });\n return { allowed: true };\n }\n if (bucket.count >= this._max) {\n return { allowed: false, retryAfter: Math.ceil((bucket.resetAt - now) / 1000) };\n }\n bucket.count++;\n return { allowed: true };\n }\n }\n\n const joinLimiter = new RateLimiter(10, 60_000); // 10/min per IP\n const messageLimiter = new RateLimiter(30, 60_000); // 30/min per session\n const shareLimiter = new RateLimiter(10, 60_000); // 10/min per session\n const getLimiter = new RateLimiter(60, 60_000); // 60/min per session\n const sseLimiter = new RateLimiter(5, 60_000); // 5/min per IP\n\n function getClientIp(req: IncomingMessage): string {\n return req.socket.remoteAddress ?? \"unknown\";\n }\n\n function rateLimitError(res: ServerResponse, retryAfter: number): void {\n res.writeHead(429, {\n \"Content-Type\": \"application/json\",\n \"Retry-After\": String(retryAfter),\n });\n res.end(JSON.stringify({ error: \"Too many requests\" }));\n }\n\n // ── HTTP API ────────────────────────────────────────────────────────────\n\n const httpServer = createServer(async (req, res) => {\n const url = new URL(req.url ?? \"/\", `http://localhost:${port}`);\n\n // ── CORS preflight ───────────────────────────────────────────────────\n if (req.method === \"OPTIONS\") {\n const corsHeaders = getCorsHeaders(req);\n if (Object.keys(corsHeaders).length > 0) {\n res.writeHead(204, corsHeaders);\n } else {\n res.writeHead(204);\n }\n res.end();\n return;\n }\n\n // Apply CORS headers to all responses\n const corsHeaders = getCorsHeaders(req);\n for (const [k, v] of Object.entries(corsHeaders)) {\n res.setHeader(k, v);\n }\n\n // ── SSE event stream ───────────────────────────────────────────────────\n // ⚠️ MUST accept POST — DO NOT change to GET-only.\n // Cloudflare Quick Tunnels buffer GET streaming responses and only flush\n // when the connection closes. POST streams in real-time. (cloudflared#1449)\n // https://github.com/cloudflare/cloudflared/issues/1449\n if (url.pathname === \"/events\" && (req.method === \"GET\" || req.method === \"POST\")) {\n const sseCheck = sseLimiter.check(getClientIp(req));\n if (!sseCheck.allowed) return rateLimitError(res, sseCheck.retryAfter!);\n\n const authHeader = req.headers.authorization;\n const sessionToken = authHeader?.startsWith(\"Bearer \")\n ? authHeader.slice(7)\n : null;\n const session = getSession(sessionToken);\n\n if (!session) {\n jsonError(res, 401, \"Invalid session token\");\n return;\n }\n\n // SSE headers\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n \"Connection\": \"keep-alive\",\n });\n res.flushHeaders();\n\n // Disable Nagle's algorithm so SSE events flush immediately.\n // Without this, small writes may be delayed up to ~200ms waiting\n // for more data, which can cause events to appear \"stuck\" until\n // the next event (e.g. a MentionedEvent) pushes the buffer.\n res.socket?.setNoDelay(true);\n\n sseConnections.set(session.id, res);\n\n // Initialize or recover presence for participants (not guests).\n // Recover from both \"unresponsive\" and \"offline\" (timeout case — session is\n // still valid). Left/kicked participants can't reach here: their session token\n // is revoked and getSession() returns null → 401 above.\n if (session.kind === \"participant\") {\n touchParticipant(session.id);\n const prev = presenceStatus.get(session.id);\n if (prev === \"unresponsive\" || prev === \"offline\") {\n presenceStatus.set(session.id, \"online\");\n broadcastStatusChange(session.id, session.name, \"online\", prev, \"recovered\");\n log(`${session.name} recovered`);\n } else if (!prev) {\n presenceStatus.set(session.id, \"online\");\n }\n }\n\n // Heartbeat every 30s to keep the connection alive through\n // proxies, firewalls, and OS-level TCP idle timeouts.\n //\n // If the write fails (broken socket, client crashed, network died), the\n // OS may not have surfaced the close event yet — treat the failure as a\n // disconnect so the entry isn't left dangling in sseConnections forever.\n let cleaned = false;\n const cleanup = () => {\n if (cleaned) return;\n cleaned = true;\n clearInterval(heartbeat);\n sseConnections.delete(session.id);\n try { res.end(); } catch { /* already gone */ }\n };\n const heartbeat = setInterval(() => {\n try {\n const ok = res.write(\":heartbeat\\n\\n\");\n if (!ok && res.destroyed) { cleanup(); return; }\n } catch {\n cleanup();\n return;\n }\n // SSE heartframe counts as proof of life — prevents false-positive\n // unresponsive for read-only agents that have no HTTP activity.\n if (session.kind === \"participant\") touchParticipant(session.id);\n }, 30_000);\n\n // Send recent history so the joiner has context.\n // Whispers are filtered: only deliver if this participant is sender or recipient.\n const history = await room.listEvents(undefined, 50);\n for (const event of [...history.items].reverse()) {\n if (event.type === \"MessageSent\" && (event.message.recipients?.length ?? 0) > 0) {\n const isParticipant = event.message.recipients!.includes(session.id) || event.message.sender_id === session.id;\n if (!isParticipant) continue;\n }\n await enrichAndSend(res, event, room);\n }\n\n // Live event stream.\n // Whispers: the full MessageSentEvent is filtered here for non-participants;\n // the WhisperNotifiedEvent is sent directly to them at POST /message time.\n const streamEvents = async () => {\n try {\n for await (const event of session.channel) {\n if (event.type === \"MessageSent\" && (event.message.recipients?.length ?? 0) > 0) {\n const isParticipant = event.message.recipients!.includes(session.id) || event.message.sender_id === session.id;\n if (!isParticipant) continue;\n }\n await enrichAndSend(res, event, room);\n if (session.kind === \"participant\") touchParticipant(session.id);\n }\n } catch {\n // Channel disconnected\n }\n };\n streamEvents();\n\n // Cleanup on client disconnect (covers the normal-close path; abnormal\n // disconnects are handled by the heartbeat catch above).\n req.on(\"close\", cleanup);\n return;\n }\n\n // ── GET endpoints ─────────────────────────────────────────────────────\n\n if (req.method === \"GET\") {\n const sessionToken = extractSessionToken(req, url);\n const session = getSession(sessionToken);\n if (sessionToken) {\n const getCheck = getLimiter.check(sessionToken);\n if (!getCheck.allowed) return rateLimitError(res, getCheck.retryAfter!);\n }\n if (session && session.kind === \"participant\") touchParticipant(session.id);\n\n // ── GET /participants ────────────────────────────────────────────────\n if (url.pathname === \"/participants\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n const list = participantSummaries();\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ participants: list }));\n return;\n }\n\n // ── GET /rules ───────────────────────────────────────────────────────\n // Any authenticated participant (including guests) can read the rules.\n if (url.pathname === \"/rules\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ rules: currentRules }));\n return;\n }\n\n // ── GET /message/:id ─────────────────────────────────────────────────\n if (url.pathname.startsWith(\"/message/\")) {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n const messageId = url.pathname.slice(\"/message/\".length);\n const msg = await room.getMessage(messageId);\n if (!msg) return jsonError(res, 404, \"Message not found\");\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ message: msg }));\n return;\n }\n\n // ── GET /messages ────────────────────────────────────────────────────\n if (url.pathname === \"/messages\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n const count = clampInt(url.searchParams.get(\"count\"), 30, 1, 100);\n const cursor = url.searchParams.get(\"cursor\") ?? null;\n const result = await room.listMessages(count, cursor);\n // Filter whispers: only include if sender or recipient.\n // Undefined session.id → no whispers shown (safe default: fail closed).\n const filtered = {\n ...result,\n items: result.items.filter((m) => {\n if (!(m.recipients?.length)) return true;\n if (!session.id) return false;\n return m.sender_id === session.id || m.recipients.includes(session.id);\n }),\n };\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(filtered));\n return;\n }\n\n // ── GET /events/history ──────────────────────────────────────────────\n if (url.pathname === \"/events/history\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n const category = url.searchParams.get(\"category\") ?? null;\n const count = clampInt(url.searchParams.get(\"count\"), 50, 1, 200);\n const cursor = url.searchParams.get(\"cursor\") ?? null;\n const result = await room.listEvents(category as any, count, cursor);\n // Filter whispers from event history: same rules as GET /messages.\n // Undefined session.id → no whispers shown (safe default: fail closed).\n const filteredEvents = {\n ...result,\n items: result.items.filter((e) => {\n if (e.type !== \"MessageSent\" || !(e.message.recipients?.length)) return true;\n if (!session.id) return false;\n return e.message.sender_id === session.id || e.message.recipients.includes(session.id);\n }),\n };\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(filteredEvents));\n return;\n }\n\n // ── GET /search ──────────────────────────────────────────────────────\n if (url.pathname === \"/search\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n const query = url.searchParams.get(\"query\") ?? \"\";\n if (!query) return jsonError(res, 400, \"Missing query parameter\");\n const count = clampInt(url.searchParams.get(\"count\"), 10, 1, 50);\n const cursor = url.searchParams.get(\"cursor\") ?? null;\n const result = await room.searchMessages(query, count, cursor);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(result));\n return;\n }\n\n // ── GET /attachment/:id ──────────────────────────────────────────────\n if (url.pathname.startsWith(\"/attachment/\")) {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n const id = url.pathname.slice(\"/attachment/\".length);\n const stored = attachmentStore.get(id);\n if (!stored) return jsonError(res, 404, \"Attachment not found\");\n res.writeHead(200, {\n \"Content-Type\": stored.mimeType,\n \"Content-Length\": String(stored.size),\n \"Content-Disposition\": `inline; filename=\"${stored.name}\"`,\n });\n res.end(stored.bytes);\n return;\n }\n }\n\n // ── POST /attachment — raw bytes, must come before parseBody ────────────\n\n if (req.method === \"POST\" && url.pathname === \"/attachment\") {\n const attSessionToken = extractSessionToken(req, url);\n const attSession = getSession(attSessionToken);\n if (!attSession) return jsonError(res, 401, \"Invalid session token\");\n if (!can(attSession.authority, \"send_message\")) return jsonError(res, 403, \"Guests cannot upload attachments\");\n\n // Raw body read (intentionally not parseBody — binary, not JSON).\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n let tooBig = false;\n for await (const chunk of req) {\n totalBytes += (chunk as Buffer).length;\n if (totalBytes > MAX_ATTACHMENT_BYTES) { tooBig = true; break; }\n chunks.push(chunk as Buffer);\n }\n if (tooBig) return jsonError(res, 413, `Attachment too large (max ${MAX_ATTACHMENT_BYTES / 1024 / 1024}MB)`);\n\n const bytes = Buffer.concat(chunks);\n const mimeType = (req.headers[\"content-type\"] ?? \"application/octet-stream\").split(\";\")[0].trim();\n const disposition = req.headers[\"content-disposition\"] ?? \"\";\n const filenameMatch = disposition.match(/filename\\*?=(?:UTF-8'')?[\"']?([^\"';\\r\\n]+)/i);\n const rawName =\n filenameMatch?.[1]?.trim() ??\n (req.headers[\"x-filename\"] as string | undefined) ??\n \"attachment\";\n let name: string;\n try {\n name = decodeURIComponent(rawName);\n } catch {\n name = rawName;\n }\n\n const id = randomUUID();\n evictAttachmentsToFit(bytes.length);\n attachmentStore.set(id, {\n id, name, mimeType, size: bytes.length, roomId: room.roomId, bytes,\n uploadedAt: Date.now(),\n });\n attachmentStoreBytes += bytes.length;\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ id, url: attachmentUrl(id), name, mime_type: mimeType, size: bytes.length }));\n return;\n }\n\n // ── PUT /rules — admin updates the ruleset ────────────────────────────\n\n if (req.method === \"PUT\" && url.pathname === \"/rules\") {\n const sessionToken = extractSessionToken(req, url);\n const session = getSession(sessionToken);\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n if (!can(session.authority, \"clear_history\")) {\n return jsonError(res, 403, \"Only admins can update rules\");\n }\n const body = await parseBody(req);\n const rawRules = body.rules;\n if (!Array.isArray(rawRules) || rawRules.some((r) => typeof r !== \"string\")) {\n return jsonError(res, 400, \"Body must be { rules: string[] }\");\n }\n const cleaned = (rawRules as string[])\n .map((r) => r.trim())\n .filter((r) => r.length > 0)\n .slice(0, RULES_MAX);\n currentRules = cleaned;\n saveRulesForRoom(roomName, currentRules);\n const adminP = sessionToken ? participants.get(sessionToken) : undefined;\n broadcastRulesChanged(currentRules, adminP?.name ?? \"admin\");\n log(`rules updated by ${adminP?.name ?? session.id} (${currentRules.length} rules)`);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ rules: currentRules }));\n return;\n }\n\n // ── POST endpoints ────────────────────────────────────────────────────\n\n if (req.method === \"POST\") {\n const body = await parseBody(req);\n\n // ── POST /join ──────────────────────────────────────────────────────\n if (url.pathname === \"/join\") {\n const joinCheck = joinLimiter.check(getClientIp(req));\n if (!joinCheck.allowed) return rateLimitError(res, joinCheck.retryAfter!);\n\n // Accept share token (body.shareToken preferred, body.token as fallback)\n const shareToken = String(body.shareToken ?? body.token ?? \"\");\n const legacyType = String(body.type ?? \"\");\n\n let authority: AuthorityLevel;\n\n if (shareToken) {\n const tokenAuthority = tokens.validateShareToken(shareToken);\n if (!tokenAuthority) return jsonError(res, 403, \"Invalid share token\");\n authority = tokenAuthority;\n } else if (legacyType === \"guest\") {\n authority = \"guest\";\n } else {\n // Default: joins as member\n authority = \"member\";\n }\n\n const participantType = String(body.type ?? \"human\") as \"human\" | \"agent\";\n const name = String(body.name ?? randomName());\n\n if (authority === \"guest\") {\n const id = `obs_${randomUUID().slice(0, 8)}`;\n const channel = room.observe();\n const sessionToken = tokens.createSessionToken(id, \"guest\");\n\n guests.set(sessionToken, { id, authority: \"guest\", channel, sessionToken });\n idToSession.set(id, sessionToken);\n\n const participantList = participantSummaries();\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({\n sessionToken,\n participantId: id,\n roomName,\n roomId: room.roomId,\n participants: participantList,\n authority: \"guest\",\n }));\n return;\n }\n\n // An agent rejoining under a name that is already in the room replaces\n // the old seat rather than adding a second one.\n //\n // Two participants with one name is not a cosmetic wart: every lookup\n // that resolves a name has to pick, and they do not all pick the same\n // one. Observed — a room held a live `aztraboy` and a dead one, an\n // `@aztraboy` mention routed to the dead seat so the real agent never\n // received it, and `/kick aztraboy` removed the LIVE agent and left the\n // corpse behind. The whisper path already refuses an ambiguous name\n // outright; the join path was creating the ambiguity.\n {\n const entries = [...participants.entries()];\n const evicting = new Set(\n seatsToEvict(entries.map(([, p]) => p), { name, type: participantType }).map((s) => s.id),\n );\n for (const [tok, existing] of entries) {\n if (!evicting.has(existing.id)) continue;\n log(`agent \"${name}\" rejoined — replacing its previous seat (${existing.id})`);\n try { await existing.channel.disconnect(true); } catch { /* already gone */ }\n participants.delete(tok);\n idToSession.delete(existing.id);\n tokens.revokeSessionToken(tok);\n sseConnections.get(existing.id)?.end();\n sseConnections.delete(existing.id);\n presenceStatus.delete(existing.id);\n lastSeenAt.delete(existing.id);\n }\n }\n\n // admin or participant — connect as a real participant\n const id = `${participantType}_${randomUUID().slice(0, 8)}`;\n const channel = await room.connect(id, name, { type: participantType, authority });\n const sessionToken = tokens.createSessionToken(id, authority);\n\n participants.set(sessionToken, { id, name, type: participantType, authority, channel, sessionToken });\n idToSession.set(id, sessionToken);\n touchParticipant(id);\n presenceStatus.set(id, \"online\");\n\n const participantList = room.listParticipants().map((p) => ({\n id: p.id,\n name: p.name,\n type: p.type,\n authority: p.authority ?? \"member\",\n }));\n\n log(`${name} joined (${authority})`);\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({\n sessionToken,\n participantId: id,\n roomName,\n roomId: room.roomId,\n participants: participantList,\n authority,\n }));\n return;\n }\n\n // ── All remaining POST endpoints require a session token ────────────\n\n const sessionToken = extractSessionToken(req, url, body) ?? \"\";\n const session = getSession(sessionToken);\n // Any authenticated HTTP request counts as proof of life.\n if (session && session.kind === \"participant\") touchParticipant(session.id);\n\n // ── POST /message ───────────────────────────────────────────────────\n if (url.pathname === \"/message\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n if (sessionToken) {\n const msgCheck = messageLimiter.check(sessionToken);\n if (!msgCheck.allowed) return rateLimitError(res, msgCheck.retryAfter!);\n }\n if (!can(session.authority, \"send_message\")) return jsonError(res, 403, \"Guests cannot send messages\");\n const content = String(body.content ?? \"\");\n const replyTo = body.replyTo ? String(body.replyTo) : undefined;\n if (!content) return jsonError(res, 400, \"Empty message\");\n if (content.length > 50_000) return jsonError(res, 400, \"Message too long (max 50000 chars)\");\n\n const p = participants.get(sessionToken);\n if (!p) return jsonError(res, 403, \"Not a participant\");\n\n let rawAttachments: Attachment[] | undefined;\n if (Array.isArray(body.attachments) && body.attachments.length > 0) {\n const parsed = AttachmentSchema.array().safeParse(body.attachments);\n if (!parsed.success) return jsonError(res, 400, `Invalid attachments: ${parsed.error.issues[0]?.message ?? \"bad shape\"}`);\n rawAttachments = parsed.data;\n }\n\n // Resolve whisper recipients (display names → participant IDs).\n let recipientIds: string[] | undefined;\n if (Array.isArray(body.recipients) && body.recipients.length > 0) {\n recipientIds = [];\n const allParticipants = room.listParticipants();\n for (const name of body.recipients as string[]) {\n const matches = allParticipants.filter((pp) => pp.name === name);\n if (matches.length === 0) return jsonError(res, 400, `Unknown participant \"${name}\"`);\n if (matches.length > 1) return jsonError(res, 400, `Ambiguous participant name \"${name}\"`);\n recipientIds.push(matches[0].id);\n }\n }\n\n const msg = await p.channel.sendMessage(content, replyTo, undefined, rawAttachments, recipientIds);\n\n // Emit WhisperNotifiedEvent to all non-participants so they see [A → B].\n if (recipientIds && recipientIds.length > 0) {\n const recipientNames = recipientIds\n .map((id) => room.listParticipants().find((pp) => pp.id === id)?.name ?? id);\n const whisperNotified = createEvent<WhisperNotifiedEvent>({\n type: \"WhisperNotified\",\n category: \"ACTIVITY\",\n room_id: room.roomId,\n participant_id: p.id,\n sender_id: p.id,\n sender_name: p.name,\n recipient_ids: recipientIds,\n recipient_names: recipientNames,\n });\n // Direct write — intentionally bypasses enrichAndSend (no enrichment\n // needed, not persisted; if enrichAndSend gains per-event auth checks\n // later, revisit this path).\n for (const [participantId, sseRes] of sseConnections) {\n if (!recipientIds.includes(participantId) && participantId !== p.id) {\n sseRes.write(`data: ${JSON.stringify(whisperNotified)}\\n\\n`);\n }\n }\n }\n\n jsonOk(res, { messageId: msg.id });\n return;\n }\n\n // ── POST /event ─────────────────────────────────────────────────────\n if (url.pathname === \"/event\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n if (!can(session.authority, \"send_message\")) return jsonError(res, 403, \"Guests cannot emit events\");\n const event = body.event as RoomEvent | undefined;\n if (!event) return jsonError(res, 400, \"Missing event\");\n\n const p = participants.get(sessionToken);\n if (!p) return jsonError(res, 403, \"Not a participant\");\n\n await p.channel.emit(event);\n jsonOk(res);\n return;\n }\n\n // ── POST /metrics ───────────────────────────────────────────────────\n //\n // Agent-runtime heartbeat with token + context size from claude's\n // local jsonl. Broadcast as an ActivityEvent so the room TUI can\n // show `bf · 87k ctx` next to each agent and roll up totals.\n //\n // Body: {\n // input_tokens, output_tokens, cache_read_tokens, cache_create_tokens,\n // last_ctx_tokens, model\n // }\n if (url.pathname === \"/metrics\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n const p = participants.get(sessionToken);\n if (!p) return jsonError(res, 403, \"Not a participant\");\n const detail = {\n input_tokens: Number(body.input_tokens) || 0,\n output_tokens: Number(body.output_tokens) || 0,\n cache_read_tokens: Number(body.cache_read_tokens) || 0,\n cache_create_tokens: Number(body.cache_create_tokens) || 0,\n last_ctx_tokens: Number(body.last_ctx_tokens) || 0,\n model: typeof body.model === \"string\" ? body.model : \"\",\n participant_name: p.name,\n };\n await p.channel.emit(createEvent<ActivityEvent>({\n type: \"Activity\",\n category: \"ACTIVITY\",\n room_id: room.roomId,\n participant_id: p.id,\n action: \"metrics\",\n detail,\n }));\n jsonOk(res);\n return;\n }\n\n // ── POST /activity ──────────────────────────────────────────────────\n //\n // Agent-runtime heartbeat that surfaces what claude is actually doing\n // (e.g. \"Sautéed for 12s\", \"Compacting conversation… 23%\") so the room\n // TUI can render real activity next to the participant glyph instead of\n // a static green dot. Body: { label: string|null }. Null clears.\n //\n // Broadcasts an ActivityEvent with action=\"status_label\" so other\n // participants' channels see the update. The agent's own channel skips\n // it (no self-echo needed).\n if (url.pathname === \"/activity\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n const p = participants.get(sessionToken);\n if (!p) return jsonError(res, 403, \"Not a participant\");\n const label = body.label === null || body.label === undefined\n ? null\n : String(body.label).slice(0, 120);\n // Optional: what the runtime actually saw on the agent's screen.\n // Whitelisted rather than passed through — this drives a glyph in\n // every participant's TUI, and it arrives over the network.\n const rawState = body.state === undefined ? undefined : String(body.state);\n const state = rawState === \"idle\" || rawState === \"working\" || rawState === \"blocked\"\n ? rawState\n : undefined;\n if (state) agentStates.set(p.id, { state, at: Date.now() });\n await p.channel.emit(createEvent<ActivityEvent>({\n type: \"Activity\",\n category: \"ACTIVITY\",\n room_id: room.roomId,\n participant_id: p.id,\n action: \"status_label\",\n detail: { label, participant_name: p.name, ...(state ? { state } : {}) },\n }));\n jsonOk(res);\n return;\n }\n\n // ── POST /set-mode ──────────────────────────────────────────────────\n if (url.pathname === \"/set-mode\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n const targetId = body.participantId ? String(body.participantId) : session.id;\n const mode = String(body.mode ?? \"\");\n if (!mode) return jsonError(res, 400, \"Missing mode\");\n\n // Setting someone else's mode requires admin or product_owner\n if (targetId !== session.id && !can(session.authority, \"set_mode_for\")) {\n return jsonError(res, 403, \"Only admins and product owners can change other participants' modes\");\n }\n\n // Emit mode_changed activity event\n const p = participants.get(sessionToken);\n if (!p) return jsonError(res, 403, \"Not a participant\");\n\n await p.channel.emit(createEvent<ActivityEvent>({\n type: \"Activity\",\n category: \"ACTIVITY\",\n room_id: room.roomId,\n participant_id: targetId,\n action: \"mode_changed\",\n detail: { mode },\n }));\n\n jsonOk(res);\n return;\n }\n\n // ── POST /set-authority ──────────────────────────────────────────────\n if (url.pathname === \"/set-authority\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n if (!can(session.authority, \"mute\")) {\n return jsonError(res, 403, \"Only admins and product owners can change authority\");\n }\n const targetId = String(body.participantId ?? \"\");\n const newAuthority = String(body.authority ?? \"\") as AuthorityLevel;\n if (!targetId) return jsonError(res, 400, \"Missing participantId\");\n if (![\"admin\", \"product_owner\", \"member\", \"guest\"].includes(newAuthority)) {\n return jsonError(res, 400, \"Invalid authority. Must be admin, product_owner, member, or guest.\");\n }\n if (targetId === session.id) return jsonError(res, 400, \"Cannot change own authority\");\n\n // product_owner can only set member/guest, and cannot touch admins or other product_owners\n if (session.authority === \"product_owner\") {\n if (![\"member\", \"guest\"].includes(newAuthority)) {\n return jsonError(res, 403, \"Product owners can only set member or guest authority\");\n }\n }\n\n // Update all three places: ConnectedParticipant, TokenManager session, Room participant\n const targetSession = idToSession.get(targetId);\n if (!targetSession) return jsonError(res, 404, \"Participant not found\");\n const target = participants.get(targetSession);\n if (!target) return jsonError(res, 404, \"Participant not found\");\n\n // product_owner cannot touch admins or other product_owners\n if (session.authority === \"product_owner\") {\n const currentAuthority = target.authority ?? \"member\";\n if (currentAuthority === \"admin\" || currentAuthority === \"product_owner\") {\n return jsonError(res, 403, \"Cannot change authority of admins or product owners\");\n }\n }\n\n target.authority = newAuthority;\n tokens.updateSessionAuthority(targetSession, newAuthority);\n room.setParticipantAuthority(targetId, newAuthority);\n\n // Emit AuthorityChanged event\n const adminP = participants.get(sessionToken);\n const targetParticipant = room.listParticipants().find(p => p.id === targetId);\n if (adminP && targetParticipant) {\n await adminP.channel.emit(createEvent<AuthorityChangedEvent>({\n type: \"AuthorityChanged\",\n category: \"PRESENCE\",\n room_id: room.roomId,\n participant_id: targetId,\n participant: targetParticipant,\n new_authority: newAuthority,\n changed_by: adminP.name,\n }));\n }\n\n log(`${target.name} authority → ${newAuthority}`);\n jsonOk(res);\n return;\n }\n\n // ── POST /ping ──────────────────────────────────────────────────────\n if (url.pathname === \"/ping\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n if (!can(session.authority, \"ping\")) return jsonError(res, 403, \"Guests cannot ping\");\n const targetId = String(body.participantId ?? \"\");\n if (!targetId) return jsonError(res, 400, \"Missing participantId\");\n\n const p = participants.get(sessionToken);\n if (!p) return jsonError(res, 403, \"Not a participant\");\n\n await p.channel.ping(targetId);\n jsonOk(res);\n return;\n }\n\n // ── POST /kick ──────────────────────────────────────────────────────\n if (url.pathname === \"/kick\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n if (!can(session.authority, \"kick\")) return jsonError(res, 403, \"Only admins can kick\");\n const targetId = String(body.participantId ?? \"\");\n if (!targetId) return jsonError(res, 400, \"Missing participantId\");\n\n // Find and disconnect the target\n const targetSession = idToSession.get(targetId);\n if (targetSession) {\n const targetParticipant = participants.get(targetSession);\n const targetGuest = guests.get(targetSession);\n const target = targetParticipant ?? targetGuest;\n if (target) {\n // Emit ParticipantKicked before disconnect so all participants see it\n const adminP = participants.get(sessionToken);\n const roomParticipant = room.listParticipants().find(p => p.id === targetId);\n if (adminP && roomParticipant) {\n await adminP.channel.emit(createEvent<ParticipantKickedEvent>({\n type: \"ParticipantKicked\",\n category: \"PRESENCE\",\n room_id: room.roomId,\n participant_id: targetId,\n participant: roomParticipant,\n kicked_by: adminP.name,\n }));\n }\n // Companion StatusChangedEvent for participants only\n if (targetParticipant) {\n const prevStatus = presenceStatus.get(targetId) ?? \"online\";\n if (prevStatus !== \"offline\") {\n broadcastStatusChange(targetId, targetParticipant.name, \"offline\", prevStatus, \"kicked\");\n }\n lastSeenAt.delete(targetId);\n presenceStatus.delete(targetId);\n }\n // Silent disconnect — the kicked event replaces ParticipantLeft\n await target.channel.disconnect(true);\n participants.delete(targetSession);\n guests.delete(targetSession);\n idToSession.delete(targetId);\n tokens.revokeSessionToken(targetSession);\n // Stop the agent itself, not just its seat in the room.\n //\n // A kick that leaves the runtime alive produces the worst of both:\n // the process keeps thinking and burning tokens while nothing it\n // says can reach anybody, and `apiary ps` still lists it. Only\n // agents this machine started are stopped — one that joined over\n // the network belongs to whoever launched it.\n if (targetParticipant?.type === \"agent\") {\n const outcome = terminateLocalAgent(targetParticipant.name, { killTmux: tmuxKillSession });\n if (outcome.stopped) {\n log(`kicked ${targetParticipant.name}: stopped local ${outcome.runtime} runtime (pid ${outcome.pid})`);\n } else {\n log(`kicked ${targetParticipant.name}: disconnected only (${outcome.reason})`);\n }\n }\n // Close SSE connection\n const sse = sseConnections.get(targetId);\n if (sse) {\n sse.end();\n sseConnections.delete(targetId);\n }\n log(`kicked ${targetId}`);\n }\n }\n\n jsonOk(res);\n return;\n }\n\n // ── POST /clear ──────────────────────────────────────────────────────\n if (url.pathname === \"/clear\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n if (!can(session.authority, \"clear_history\")) return jsonError(res, 403, \"Only admins can clear\");\n\n // Wipe storage and room-scoped attachments\n await storage.clearRoom(room.roomId);\n for (const [id, att] of attachmentStore) {\n if (att.roomId === room.roomId) {\n attachmentStore.delete(id);\n attachmentStoreBytes -= att.size;\n }\n }\n\n // Broadcast RoomCleared to all connected clients via SSE\n const adminP = participants.get(sessionToken);\n const clearEvent = createEvent<RoomClearedEvent>({\n type: \"RoomCleared\",\n category: \"ACTIVITY\",\n room_id: room.roomId,\n participant_id: session.id,\n cleared_by: adminP?.name ?? \"admin\",\n });\n broadcastToAll(clearEvent);\n\n log(`room cleared by ${adminP?.name ?? session.id}`);\n jsonOk(res);\n return;\n }\n\n // ── POST /share ─────────────────────────────────────────────────────\n if (url.pathname === \"/share\") {\n if (!session) return jsonError(res, 401, \"Invalid session token\");\n if (sessionToken) {\n const shareCheck = shareLimiter.check(sessionToken);\n if (!shareCheck.allowed) return rateLimitError(res, shareCheck.retryAfter!);\n }\n if (!can(session.authority, \"create_share_link\")) return jsonError(res, 403, \"Guests cannot create share links\");\n\n const targetAuthority = (body.authority as AuthorityLevel) ?? undefined;\n\n const links: Record<string, string> = {};\n\n if (targetAuthority) {\n // Generate a specific link\n const token = tokens.generateShareToken(session.authority, targetAuthority);\n if (!token) return jsonError(res, 403, `Cannot generate ${targetAuthority} link`);\n links[targetAuthority] = buildShareUrl(publicUrl, token);\n } else {\n // Generate all links the caller can create\n const tiers: AuthorityLevel[] = [\"admin\", \"product_owner\", \"member\", \"guest\"];\n for (const tier of tiers) {\n const token = tokens.generateShareToken(session.authority, tier);\n if (token) links[tier] = buildShareUrl(publicUrl, token);\n }\n }\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ links }));\n return;\n }\n\n // ── POST /tunnel ────────────────────────────────────────────────────\n if (url.pathname === \"/tunnel\") {\n if (!session || !can(session.authority, \"start_tunnel\")) return jsonError(res, 403, \"Admin only\");\n\n if (tunnelProcess) {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ url: publicUrl, alreadyRunning: true }));\n return;\n }\n\n try {\n tunnelProcess = await startTunnel(port);\n if (!tunnelProcess) {\n return jsonError(res, 500, \"Could not start cloudflared — is it installed?\");\n }\n const tunnelUrl = await waitForTunnelUrl(tunnelProcess);\n if (!tunnelUrl) {\n tunnelProcess.kill(); tunnelProcess = null;\n return jsonError(res, 504, \"Tunnel started but timed out waiting for URL\");\n }\n publicUrl = tunnelUrl;\n addCorsOrigin(tunnelUrl);\n saveRoomSession({ roomName, bind: boundAddress, serverUrl, publicUrl, adminToken, memberToken, pid: process.pid });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ url: tunnelUrl, alreadyRunning: false }));\n } catch {\n return jsonError(res, 500, \"Failed to start tunnel\");\n }\n return;\n }\n\n // ── POST /rotate-token ──────────────────────────────────────────────\n if (url.pathname === \"/rotate-token\") {\n if (!session || !sessionToken) return jsonError(res, 401, \"Invalid session token\");\n const result = tokens.rotateSessionToken(sessionToken);\n if (!result) return jsonError(res, 401, \"Token expired or invalid\");\n\n // Move participant/guest entry to new token\n const p = participants.get(sessionToken);\n if (p) {\n participants.delete(sessionToken);\n p.sessionToken = result.newToken;\n participants.set(result.newToken, p);\n idToSession.set(p.id, result.newToken);\n }\n const g = guests.get(sessionToken);\n if (g) {\n guests.delete(sessionToken);\n g.sessionToken = result.newToken;\n guests.set(result.newToken, g);\n idToSession.set(g.id, result.newToken);\n }\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ sessionToken: result.newToken }));\n return;\n }\n\n // ── POST /disconnect ────────────────────────────────────────────────\n if (url.pathname === \"/disconnect\") {\n // Accept Authorization header or body.token\n const targetToken = extractSessionToken(req, url, body) ?? \"\";\n\n if (targetToken) {\n const p = participants.get(targetToken);\n if (p) {\n const prevStatus = presenceStatus.get(p.id) ?? \"online\";\n if (prevStatus !== \"offline\") {\n broadcastStatusChange(p.id, p.name, \"offline\", prevStatus, \"left\");\n }\n await p.channel.disconnect();\n participants.delete(targetToken);\n idToSession.delete(p.id);\n tokens.revokeSessionToken(targetToken);\n lastSeenAt.delete(p.id);\n presenceStatus.delete(p.id);\n const sse = sseConnections.get(p.id);\n if (sse) { sse.end(); sseConnections.delete(p.id); }\n log(`${p.name} disconnected`);\n }\n\n const g = guests.get(targetToken);\n if (g) {\n await g.channel.disconnect();\n guests.delete(targetToken);\n idToSession.delete(g.id);\n tokens.revokeSessionToken(targetToken);\n const sse = sseConnections.get(g.id);\n if (sse) { sse.end(); sseConnections.delete(g.id); }\n }\n }\n\n jsonOk(res);\n return;\n }\n }\n\n res.writeHead(404).end(\"Not found\");\n });\n\n httpServer.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n console.error(`\\nPort ${port} is already in use. Another apiary instance may be running.`);\n console.error(` Kill it: lsof -ti :${port} | xargs kill`);\n console.error(` Or use: apiary --port ${port + 1}\\n`);\n process.exit(1);\n }\n throw err;\n });\n\n await new Promise<void>((resolve) => {\n httpServer.listen(port, boundAddress, () => resolve());\n });\n\n // Prune expired tokens every 5 minutes\n const pruneInterval = setInterval(() => tokens.pruneExpired(), 5 * 60 * 1000);\n pruneInterval.unref();\n\n // After this long offline, garbage-collect all per-participant state.\n // Without this, lastSeenAt / presenceStatus / participants / idToSession /\n // sseConnections retain entries for crashed clients forever.\n const OFFLINE_GC_MS = 24 * 60 * 60 * 1000; // 24h\n\n // Periodic presence checker — runs every 30s\n const presenceInterval = setInterval(() => {\n const now = Date.now();\n for (const [sessionToken, p] of participants) {\n const last = lastSeenAt.get(p.id) ?? now;\n const elapsed = now - last;\n const current = presenceStatus.get(p.id) ?? \"online\";\n if (current === \"online\" && elapsed > UNRESPONSIVE_AFTER_MS) {\n presenceStatus.set(p.id, \"unresponsive\");\n broadcastStatusChange(p.id, p.name, \"unresponsive\", \"online\", \"ping_timeout\");\n log(`${p.name} unresponsive`);\n } else if (current === \"unresponsive\" && elapsed > OFFLINE_AFTER_MS) {\n presenceStatus.set(p.id, \"offline\");\n broadcastStatusChange(p.id, p.name, \"offline\", \"unresponsive\", \"ping_timeout\");\n log(`${p.name} offline (timeout)`);\n } else if (current === \"offline\" && elapsed > OFFLINE_GC_MS) {\n // Long-offline participant — purge all state. They can rejoin fresh.\n participants.delete(sessionToken);\n idToSession.delete(p.id);\n tokens.revokeSessionToken(sessionToken);\n const sse = sseConnections.get(p.id);\n if (sse) { try { sse.end(); } catch { /* socket already dead */ } sseConnections.delete(p.id); }\n lastSeenAt.delete(p.id);\n presenceStatus.delete(p.id);\n log(`${p.name} purged after ${Math.round(elapsed / 60_000)}min offline`);\n }\n }\n }, PRESENCE_CHECK_INTERVAL_MS);\n presenceInterval.unref();\n\n // Start tunnel if --share\n if (options.share) {\n tunnelProcess = await startTunnel(port);\n if (tunnelProcess) {\n const tunnelUrl = await waitForTunnelUrl(tunnelProcess);\n if (tunnelUrl) {\n publicUrl = tunnelUrl;\n addCorsOrigin(tunnelUrl);\n }\n }\n }\n\n // Generate share tokens on boot\n // The room's own pair, persisted to ~/.apiary/sessions and used by\n // `apiary room resume`. They outlive any TTL because they outlive nothing\n // else: when they expired at 24h the owner was locked out of a room that was\n // still running, with agents still working inside it.\n const adminToken = tokens.generateShareToken(\"admin\", \"admin\", { neverExpires: true })!;\n const memberToken = tokens.generateShareToken(\"admin\", \"member\", { neverExpires: true })!;\n\n // Persist room session for `apiary ps`\n saveRoomSession({ roomName, bind: boundAddress, serverUrl, publicUrl, adminToken, memberToken, pid: process.pid });\n\n function obfuscate(token: string): string {\n if (token.length <= 8) return \"****\";\n return token.slice(0, 4) + \"...\" + token.slice(-4);\n }\n\n if (options.headless) {\n process.stdout.write(JSON.stringify({ serverUrl, publicUrl, roomName, adminToken, memberToken, savePath }) + \"\\n\");\n process.stderr.write(\"Warning: raw tokens in stdout — do not share this output.\\n\");\n } else if (!options.quiet) {\n let version = process.env.npm_package_version ?? \"\";\n if (!version) {\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../../package.json\");\n version = pkg.version ?? \"unknown\";\n } catch {\n version = \"unknown\";\n }\n }\n const adminUrlObfuscated = buildShareUrl(publicUrl, obfuscate(adminToken));\n const joinUrlObfuscated = buildShareUrl(publicUrl, obfuscate(memberToken));\n\n // Set terminal tab title — hive emoji stable per room.\n process.stdout.write(`\\x1b]0;${roomEmoji(roomName)} apiary · ${roomName}\\x07`);\n\n const Y = \"\\x1b[33m\";\n const C = \"\\x1b[36m\";\n const D = \"\\x1b[2m\";\n const B = \"\\x1b[1m\";\n const R = \"\\x1b[0m\";\n\n console.log(`\n ${Y}${B}apiary${R} ${D}v${version}${R}\n\n ${D}Room:${R} ${Y}${roomName}${R}\n ${D}Server:${R} ${C}${serverUrl}${R}${publicUrl !== serverUrl ? `\\n ${D}Tunnel:${R} ${C}${publicUrl}${R}` : \"\"}\n ${D}Saving:${R} ${D}${savePath}${R}\n\n ${D}Share:${R} ${Y}${joinUrlObfuscated}${R}\n ${D}Admin:${R} ${Y}${adminUrlObfuscated}${R}\n\n ${D}Join:${R} ${C}apiary join${R} ${D}<url>${R}\n ${D}Agent:${R} ${C}apiary claude${R} ${D}<name>${R} ${D}→ tell it the URL, it joins${R}\n`);\n }\n\n // ── Graceful shutdown ──────────────────────────────────────────────────\n\n const shutdown = async () => {\n log(\"shutting down...\");\n clearInterval(presenceInterval);\n unwatchFile(rulesFilePath, onRulesFileChange);\n clearRoomSession(roomName);\n // Delete auto-saved room state — room is closed, next open starts fresh.\n // Explicit --save/--load files are kept (user opted into persistence).\n if (!options.save && !options.load) {\n try { rmSync(savePath); } catch { /* ok */ }\n }\n if (tunnelProcess) { tunnelProcess.kill(); tunnelProcess = null; }\n for (const [id, sse] of sseConnections) { sse.end(); sseConnections.delete(id); }\n for (const p of participants.values()) { await p.channel.disconnect().catch(() => {}); }\n for (const g of guests.values()) { await g.channel.disconnect().catch(() => {}); }\n await new Promise<void>((resolve, reject) => {\n httpServer.close((err) => (err ? reject(err) : resolve()));\n });\n process.exit(0);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Last-ditch tunnel reaper — runs synchronously on any process exit\n // (including process.exit() and natural termination), but NOT on SIGKILL.\n // Without this, an unexpected crash leaves cloudflared running indefinitely,\n // pinning the parent's port and blocking restart.\n const reapTunnel = () => {\n if (tunnelProcess && !tunnelProcess.killed) {\n try { tunnelProcess.kill(\"SIGTERM\"); } catch { /* already dead */ }\n }\n };\n process.on(\"exit\", reapTunnel);\n process.on(\"uncaughtException\", (err) => {\n log(`fatal: uncaughtException — ${err.message}`);\n reapTunnel();\n process.exit(1);\n });\n process.on(\"unhandledRejection\", (reason) => {\n log(`fatal: unhandledRejection — ${reason instanceof Error ? reason.message : String(reason)}`);\n reapTunnel();\n process.exit(1);\n });\n\n return { serverUrl, publicUrl, roomName, adminToken, memberToken };\n}\n\n// ── Server log ────────────────────────────────────────────────────────────────\n\nfunction logServer(message: string): void {\n console.log(` [${formatTimestamp(new Date())}] ${message}`);\n}\n\n// ── Cloudflared tunnel ───────────────────────────────────────────────────────\n\nfunction cloudflaredAvailable(): boolean {\n try {\n execFileSync(\"which\", [\"cloudflared\"], { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\nasync function startTunnel(port: number): Promise<ChildProcess | null> {\n if (!cloudflaredAvailable()) {\n console.error(\" --share requires cloudflared. Install: brew install cloudflared\");\n return null;\n }\n\n const child = spawn(\"cloudflared\", [\"tunnel\", \"--url\", `http://localhost:${port}`], {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n });\n\n child.on(\"error\", () => {\n // cloudflared failed to start\n });\n\n return child;\n}\n\nfunction waitForTunnelUrl(child: ChildProcess, timeoutMs = 15000): Promise<string | null> {\n return new Promise((resolve) => {\n let resolved = false;\n let buffer = \"\";\n\n const timer = setTimeout(() => {\n if (!resolved) { resolved = true; resolve(null); }\n }, timeoutMs);\n\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString();\n const match = buffer.match(/https:\\/\\/[a-z0-9-]+\\.trycloudflare\\.com/);\n if (match && !resolved) {\n resolved = true;\n clearTimeout(timer);\n resolve(match[0]);\n }\n });\n\n child.on(\"exit\", () => {\n if (!resolved) { resolved = true; clearTimeout(timer); resolve(null); }\n });\n });\n}\n","/**\n * Terminate the local agent runtime behind a room participant.\n *\n * `/kick` used to only sever the room connection: the channel closed, the token\n * was revoked, the SSE stream ended — and the agent's process and tmux session\n * carried on, reasoning about work it could no longer tell anyone about. The\n * operator's reasonable expectation is that kicking an agent the room started\n * also stops it, and the machinery already existed in `apiary stop`; it simply\n * was not wired to kick.\n *\n * Only agents THIS machine started are stopped. An agent that joined over the\n * network belongs to whoever launched it, and a room has no business reaching\n * across a tailnet to kill someone else's process — that one is still only\n * disconnected, which is all a room can honestly do.\n */\n\nimport {\n clearAgentSession,\n isAgentAlive,\n listAgentSessions,\n type AgentRuntime,\n type AgentSession,\n} from \"./agent-session.js\";\n\nconst RUNTIMES: readonly AgentRuntime[] = [\"claude\", \"codex\", \"opencode\"];\n\nexport interface TerminateDeps {\n /** Every locally recorded session, across runtimes. Injected for tests. */\n list?: (runtime: AgentRuntime) => AgentSession[];\n alive?: (session: AgentSession) => boolean;\n kill?: (pid: number, signal: NodeJS.Signals) => void;\n killTmux?: (session: string) => void;\n clear?: (runtime: AgentRuntime, name: string) => void;\n}\n\nexport type TerminateOutcome =\n /** A local runtime was found alive and signalled. */\n | { stopped: true; runtime: AgentRuntime; pid: number; tmuxSession: string }\n /** Nothing local to stop — a remote agent, or one already gone. */\n | { stopped: false; reason: \"not-local\" | \"already-exited\" };\n\n/**\n * Find the local runtime for `agentName` and stop it: SIGTERM the process, kill\n * its tmux session, and drop the session record so `apiary ps` stops listing a\n * corpse.\n *\n * A record whose pid is dead is still cleaned up — that is the difference\n * between \"this agent is remote, leave it alone\" and \"this agent already died\n * and left its paperwork behind\", and only the first should stay on disk.\n */\nexport function terminateLocalAgent(\n agentName: string,\n deps: TerminateDeps = {},\n): TerminateOutcome {\n const list = deps.list ?? listAgentSessions;\n const alive = deps.alive ?? isAgentAlive;\n const kill = deps.kill ?? ((pid, signal) => { process.kill(pid, signal); });\n const clear = deps.clear ?? clearAgentSession;\n\n for (const runtime of RUNTIMES) {\n const session = list(runtime).find((s) => s.agentName === agentName);\n if (!session) continue;\n\n if (!alive(session)) {\n // The record outlived the process. Clear it so the name is free.\n clear(runtime, agentName);\n return { stopped: false, reason: \"already-exited\" };\n }\n\n try { kill(session.pid, \"SIGTERM\"); } catch { /* raced with its own exit */ }\n if (session.tmuxSession && deps.killTmux) {\n try { deps.killTmux(session.tmuxSession); } catch { /* already gone */ }\n }\n clear(runtime, agentName);\n return {\n stopped: true,\n runtime,\n pid: session.pid,\n tmuxSession: session.tmuxSession ?? \"\",\n };\n }\n\n return { stopped: false, reason: \"not-local\" };\n}\n\n// ── Duplicate seats ─────────────────────────────────────────────────────────\n\n/** The subset of a connected participant this decision needs. */\nexport interface SeatSummary {\n id: string;\n name: string;\n type: \"human\" | \"agent\";\n}\n\n/**\n * Which existing seats a joining participant should evict.\n *\n * An agent runtime that rejoins — after an SSE drop, a reconnect, or a second\n * `join_room` call — used to be handed a brand new seat while its old one\n * stayed in the roster. Two participants then answered to one name, and every\n * lookup that resolves a name had to pick between them without any of them\n * agreeing which to pick. Observed: an `@aztraboy` mention routed to the dead\n * seat so the live agent never received the message, and `/kick aztraboy`\n * removed the LIVE agent and left the corpse in the room.\n *\n * Only agents are reclaimed. Two people may legitimately share a display name\n * — they are different humans and both should be in the room — whereas one\n * agent name is one runtime by construction, so a second seat under it is\n * always the ghost of the first.\n */\nexport function seatsToEvict(\n seats: readonly SeatSummary[],\n joining: { name: string; type: \"human\" | \"agent\" },\n): SeatSummary[] {\n if (joining.type !== \"agent\") return [];\n return seats.filter((s) => s.type === \"agent\" && s.name === joining.name);\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readFileSync","writeFileSync","homedir","writeFileSync","readFileSync","existsSync","mkdirSync","join","homedir","resolve","spawn","execFileSync","existsSync","mkdirSync","readdirSync","readFileSync","writeFileSync","rmSync","homedir","pathJoin","SESSION_DIR","pathJoin","homedir","existsSync","mkdirSync","writeFileSync","rmSync","readdirSync","readFileSync","ROOMS_DIR","corsHeaders","id","channel","sessionToken","participantList","resolve","require","execFileSync","spawn"]}