@gorlitzer-labs/apiary 1.13.8 → 1.14.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 (36) hide show
  1. package/README.md +15 -3
  2. package/dist/agent/index.d.ts +7 -0
  3. package/dist/agent/index.js +2 -2
  4. package/dist/chunk-36WX2UAF.js +663 -0
  5. package/dist/chunk-36WX2UAF.js.map +1 -0
  6. package/dist/{chunk-3O76FESR.js → chunk-LNSDH2V5.js} +7 -7
  7. package/dist/{chunk-FTN3D6WM.js → chunk-LOONM4G3.js} +8 -177
  8. package/dist/chunk-LOONM4G3.js.map +1 -0
  9. package/dist/{chunk-63Q3KJ4W.js → chunk-N2LPKMPO.js} +186 -3
  10. package/dist/chunk-N2LPKMPO.js.map +1 -0
  11. package/dist/{chunk-YP4K2A7G.js → chunk-TMXQDGMN.js} +30 -30
  12. package/dist/chunk-TMXQDGMN.js.map +1 -0
  13. package/dist/{chunk-LN6ATWE3.js → chunk-WJD5EKO6.js} +158 -158
  14. package/dist/chunk-WJD5EKO6.js.map +1 -0
  15. package/dist/{chunk-J3T2PS3M.js → chunk-XP5F5NCQ.js} +2 -2
  16. package/dist/cli/index.js +38 -380
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/{invites-YZWCUC67.js → invites-ID5SRM3D.js} +5 -5
  19. package/dist/{join-FSKOS5BO.js → join-W53OPEHR.js} +5 -5
  20. package/dist/{room-MAUAV2XD.js → room-XC546NTX.js} +11 -11
  21. package/dist/run-6JRFWMAQ.js +164 -0
  22. package/dist/run-6JRFWMAQ.js.map +1 -0
  23. package/package.json +1 -1
  24. package/dist/chunk-63Q3KJ4W.js.map +0 -1
  25. package/dist/chunk-FTN3D6WM.js.map +0 -1
  26. package/dist/chunk-LN6ATWE3.js.map +0 -1
  27. package/dist/chunk-XKFWEK7W.js +0 -288
  28. package/dist/chunk-XKFWEK7W.js.map +0 -1
  29. package/dist/chunk-YP4K2A7G.js.map +0 -1
  30. package/dist/run-4EJB2KC3.js +0 -90
  31. package/dist/run-4EJB2KC3.js.map +0 -1
  32. /package/dist/{chunk-3O76FESR.js.map → chunk-LNSDH2V5.js.map} +0 -0
  33. /package/dist/{chunk-J3T2PS3M.js.map → chunk-XP5F5NCQ.js.map} +0 -0
  34. /package/dist/{invites-YZWCUC67.js.map → invites-ID5SRM3D.js.map} +0 -0
  35. /package/dist/{join-FSKOS5BO.js.map → join-W53OPEHR.js.map} +0 -0
  36. /package/dist/{room-MAUAV2XD.js.map → room-XC546NTX.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/composer.ts","../src/cli/claude/tmux-bridge.ts","../src/agent/wait-for-agent.ts","../src/cli/runtime-handlers.ts"],"sourcesContent":["/**\n * Did the text we injected actually get submitted?\n *\n * Both bridges type into a TUI and then assume it worked. On 2026-09-10 that\n * assumption cost seven minutes of apparent silence: a room message reached a\n * Codex agent, the `@`-mention popup swallowed the Enter, and the message sat in\n * the composer while apiary reported success and moved on. Nobody learned it had\n * failed until a human read the pane.\n *\n * The check is deliberately narrow. After a submit, the text appears in the\n * *transcript* — that is success, not failure — so the presence of our text on\n * screen proves nothing. What distinguishes the two is where it sits: an\n * unsubmitted message is still in the composer, which both TUIs render as the\n * last prompt-marked block at the bottom of the screen.\n */\n\n/** Prompt markers: Codex renders the composer with ›, Claude with ❯ (> on older builds). */\nconst PROMPT_MARKER = /^\\s*[›❯>]\\s?/;\n\n/**\n * The composer's contents: everything from the last prompt marker down.\n *\n * Long messages wrap, so the marker line alone is not the whole composer — the\n * rest of the block below it belongs to the same input.\n */\nexport function composerBlock(lines: string[]): string {\n const tail = lines.slice(-25);\n let start = -1;\n for (let i = tail.length - 1; i >= 0; i--) {\n if (PROMPT_MARKER.test(tail[i])) {\n start = i;\n break;\n }\n }\n if (start === -1) return \"\";\n return tail.slice(start).join(\" \");\n}\n\n/**\n * A stable fragment of the message to look for.\n *\n * The head of the text rather than the whole of it: the composer wraps, the pane\n * truncates, and a trailing `@mention` may already have been rewritten by the\n * very popup we are checking for — so the tail is exactly the part that cannot\n * be relied on. Whitespace is collapsed because wrapping introduces its own.\n */\nexport function submissionProbe(text: string): string {\n return text.replace(/\\s+/g, \" \").trim().slice(0, 40);\n}\n\n/**\n * True when the message is still sitting in the composer, i.e. it was NOT\n * submitted. A message short enough to produce no usable probe is reported as\n * submitted rather than guessed at — a false \"undelivered\" would have the\n * caller resend, and a duplicate room message is worse than an unchecked one.\n */\nexport function composerStillHolds(lines: string[], text: string): boolean {\n const probe = submissionProbe(text);\n if (probe.length < 8) return false;\n const composer = composerBlock(lines).replace(/\\s+/g, \" \");\n return composer.includes(probe);\n}\n","/**\n * TmuxBridge — state-aware event injection into Claude Code.\n *\n * Reads the Claude Code TUI screen via `tmux capture-pane`, detects the\n * current UI state, and applies the right injection strategy:\n *\n * idle → inject directly\n * typing → Ctrl+U (cut), inject, Ctrl+Y (restore)\n * dialog → queue and poll\n * permission → queue and poll\n * streaming → queue and poll\n * unknown → queue and poll (safe default)\n *\n * Events that can't be injected immediately are queued and drained\n * when the state becomes safe.\n */\n\nimport {\n tmuxPaneIsShell,\n tmuxCapturePane,\n tmuxCapturePaneStyled,\n tmuxInjectText,\n tmuxSendEnter,\n tmuxSendKey,\n} from \"../tmux.js\";\nimport { composerStillHolds } from \"../composer.js\";\nimport { contentPartsToString } from \"../../agent/prompts.js\";\nimport type { ContentPart } from \"../../agent/types.js\";\n\nexport type TuiState =\n /** The CLI is not running — the pane has fallen back to a shell. */\n | \"absent\"\n | \"idle\"\n | \"typing\"\n | \"dialog\"\n | \"permission\"\n | \"streaming\"\n | \"unknown\";\n\n// Spinner characters used by Claude Code during streaming\nconst SPINNER_CHARS = \"⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏\";\n\n/**\n * Claude Code is mid-turn but shows none of the braille spinner glyphs.\n *\n * Current Claude Code (checked on v2.1.299) draws its live status with other\n * glyphs — \"✢ Perusing… (2s · ↓ 88 tokens · thinking)\" — so the spinner scan\n * misses it. The shape is stable even though the verb rotates: a word, an\n * ellipsis, and a parenthesised elapsed time. The finished form (\"✻ Brewed for\n * 1s · done 11:33 AM\") has no ellipsis and does not match.\n *\n * \"← 1 agent\" in the footer used to be read as \"a subagent is running\". On\n * v2.1.299 it is the opposite: every idle session shows it (four live panes\n * checked), and it disappears while a background agent runs. Treating it as\n * busy made every idle Claude Code pane look busy forever, so events only got\n * through via the 30s force-inject. A background agent also no longer blocks\n * submission — a room nudge typed while one ran was answered normally.\n */\nconst BUSY_PATTERNS: RegExp[] = [\n /[A-Za-zÀ-ÿ]+…\\s*\\(\\d+(?:m\\s*\\d+)?s\\b/,\n];\n\n/**\n * Does Claude Code's composer hold nothing the human typed?\n *\n * A plain capture cannot tell a draft from the dim placeholder or suggestion\n * Claude Code shows in an empty composer (\"Try \\\"fix typecheck errors\\\"\",\n * \"check the room for messages\") — both read as text after ❯. The styled\n * capture can: the placeholder is drawn dim (ESC[2m), a draft is not.\n */\nexport function composerHoldsOnlyGhost(styledLines: string[]): boolean {\n const SGR = /\\x1b\\[[0-9;]*m/g;\n for (let i = styledLines.length - 1; i >= 0; i--) {\n const plain = styledLines[i].replace(SGR, \"\").trimStart();\n if (!/^[❯›](\\s|$)/.test(plain)) continue;\n const afterPrompt = styledLines[i].slice(styledLines[i].search(/[❯›]/) + 1);\n const typed = afterPrompt\n .replace(/\\x1b\\[2m[\\s\\S]*?(?:\\x1b\\[(?:0|22)?m|$)/g, \"\")\n .replace(SGR, \"\")\n .trim();\n return typed.length === 0;\n }\n return false;\n}\n\n// Patterns that indicate a selection/question dialog\nconst DIALOG_PATTERNS = [\n \"Enter to select\",\n \"to navigate\",\n \"Esc to cancel\",\n \"Ready to code?\",\n \"Review your answers\",\n \"ctrl+g to edit in\",\n];\n\n// Patterns that indicate a permission/confirmation prompt\nconst PERMISSION_PATTERNS = [\n \"(Y)\",\n \"Allow \",\n \"Deny \",\n \"approve\",\n \"Yes / No\",\n // Current Claude Code wording, captured from a live prompt. Without these a\n // permission dialog reads as idle and the bridge pastes a room event into\n // it, answering on the agent's behalf.\n \"Do you want to proceed?\",\n \"don't ask again\",\n // The folder-trust modal, which no flag skips.\n \"Yes, I trust this folder\",\n];\n\n/**\n * Patterns specific enough to put \"⏸ needs you\" in front of a human.\n *\n * DIALOG_PATTERNS and PERMISSION_PATTERNS above deliberately over-match, and\n * for their original consumer that is correct: they gate DELIVERY, where a\n * false positive only queues a room event for the drain loop to retry. Cheap.\n *\n * That reasoning was then reused for a second consumer with the opposite cost\n * — the agent state reported to the room — where a false positive stops the\n * operator and makes them wait on an agent that needs nothing. Observed: the\n * bare substring `\"approve\"` matched an agent's own tool output, \"hard cap, set\n * by the approved top\", and the room strip read \"⏸ needs you\" at an agent 23\n * minutes into a harness run. `\"to navigate\"` is the same trap and worse — it\n * matches any sentence about navigating, in a project whose entire subject is\n * navigating by bearings.\n *\n * So: two lists. Over-match to protect the agent, under-match before\n * interrupting the human. These are UI chrome a prompt actually renders —\n * wordings and shapes, not vocabulary that can occur in prose.\n */\nconst BLOCKING_PATTERNS: Array<string | RegExp> = [\n \"Do you want to proceed?\",\n \"don't ask again\",\n \"Yes, I trust this folder\",\n \"Ready to code?\",\n \"Enter to select\",\n /❯\\s*1\\.\\s*Yes/,\n /\\(y\\s*\\/\\s*n\\)/i,\n // The arrow glyphs claude renders beside it. Bare \"to navigate\" is prose.\n /[↑↓⬆⬇]\\s*(?:[↑↓⬆⬇]\\s*)?to navigate/,\n];\n\n/** Match a mixed string/RegExp pattern list, case-insensitively for strings. */\nfunction matchesAny(text: string, patterns: Array<string | RegExp>): boolean {\n const haystack = text.toLowerCase();\n for (const pattern of patterns) {\n if (typeof pattern === \"string\") {\n if (haystack.includes(pattern.toLowerCase())) return true;\n } else if (pattern.test(text)) {\n return true;\n }\n }\n return false;\n}\n\nexport interface DeliverOptions {\n /**\n * Drop this text if an identical copy is already queued. For callers that\n * retry a delivery they cannot observe landing (the room invite loop).\n */\n dedupe?: boolean;\n}\n\nexport interface TmuxBridgeOptions {\n /** How often to poll when events are queued (ms). Default: 200 */\n pollIntervalMs?: number;\n /** How long to wait between Ctrl+U/inject/Ctrl+Y steps (ms). Default: 50 */\n keystrokeDelayMs?: number;\n /**\n * Called when a message could not be got into the agent, after the retry.\n *\n * Delivery used to be assumed: apiary typed, reported success and moved on,\n * so a swallowed submit looked exactly like an agent choosing not to answer.\n * This is the hook that makes the difference visible to someone.\n */\n onUndelivered?: (text: string) => void;\n /**\n * A human drives this pane (`apiary mcp` inside their own session). Default\n * false, for panes apiary launched and nobody types in.\n *\n * When true, text goes in only when the composer is empty — never cut and\n * restored around a draft (Ctrl+U clears one line, so a wrapped draft was\n * split in two and half of it sent with the event, seen live) — and never\n * forced after ~30s: a human session can sit on a real permission prompt for\n * minutes, and typing into it would answer the prompt on their behalf.\n */\n attended?: boolean;\n}\n\n/**\n * Last resort when no one has wired a handler: say it on stderr, which lands in\n * the launcher pane. Better than nothing, and a great deal better than success.\n */\nfunction defaultUndeliveredWarning(text: string): void {\n const preview = text.replace(/\\s+/g, \" \").slice(0, 80);\n process.stderr.write(`apiary: message NOT delivered to the agent — still in its composer: ${preview}\\n`);\n}\n\nexport class TmuxBridge {\n private session: string;\n private queue: string[] = [];\n private pollTimer: ReturnType<typeof setInterval> | null = null;\n private pollIntervalMs: number;\n private keystrokeDelayMs: number;\n private onUndelivered: (text: string) => void;\n private attended: boolean;\n private stopped = false;\n private blockedCount = 0;\n private static readonly BLOCKED_THRESHOLD = 150; // ~30s at 200ms poll — force inject if stuck\n\n constructor(session: string, opts?: TmuxBridgeOptions) {\n this.session = session;\n this.pollIntervalMs = opts?.pollIntervalMs ?? 200;\n this.keystrokeDelayMs = opts?.keystrokeDelayMs ?? 50;\n this.onUndelivered = opts?.onUndelivered ?? defaultUndeliveredWarning;\n this.attended = opts?.attended ?? false;\n }\n\n /**\n * Delivery callback — drop-in replacement for the raw tmuxDeliver lambda.\n * Pass `bridge.deliver.bind(bridge)` to EventProcessor.run().\n */\n async deliver(parts: ContentPart[], opts?: DeliverOptions): Promise<void> {\n const text = contentPartsToString(parts);\n if (!text.trim()) return;\n\n this.inject(text, opts?.dedupe === true);\n }\n\n /**\n * Detect the current TUI state by reading the screen.\n * Exported for testing — the heuristic logic is in detectStateFromLines().\n */\n detectState(): TuiState {\n // See the Codex bridge: a shell prompt is indistinguishable from an idle\n // composer once you are only looking at rendered text.\n if (tmuxPaneIsShell(this.session)) return \"absent\";\n const lines = this.captureScreen();\n return detectStateFromLines(lines);\n }\n\n /**\n * State to REPORT to the room, as opposed to the state used to decide\n * whether it is safe to type. Same screen, stricter about claiming a human\n * is needed — see detectReportedStateFromLines.\n */\n detectReportedState(): TuiState {\n if (tmuxPaneIsShell(this.session)) return \"absent\";\n return detectReportedStateFromLines(this.captureScreen());\n }\n\n /**\n * Extract claude's live status label from the pane (the \"Sautéed for 12s\",\n * \"Compacting conversation… 23%\" lines under the prompt). Returns null when\n * the agent is idle / no status visible.\n *\n * The room TUI calls this through the agent runtime's poll loop to surface\n * \"is the agent actually working or stuck?\" without making the user attach\n * to each tmux pane.\n */\n getActivityLabel(): string | null {\n return extractActivityLabel(this.captureScreen());\n }\n\n /**\n * Try to inject text, choosing strategy based on TUI state.\n * If the state is unsafe, queues the text and starts polling.\n *\n * Text is flattened to a single line before injection to avoid triggering\n * Claude Code's paste detection. When multi-line text arrives via\n * `send-keys -l`, Claude Code detects it as a paste and collapses it into\n * \"[Pasted text #1 +N lines]\" which may not reliably submit with Enter.\n */\n private inject(text: string, dedupe: boolean): void {\n const flat = text.replace(/\\n/g, \" \");\n const state = this.deliveryState();\n\n switch (state) {\n case \"idle\":\n this.injectIdle(flat);\n break;\n case \"typing\":\n this.injectWhileTyping(flat);\n break;\n default:\n // dialog, permission, streaming, unknown — queue it\n this.enqueue(flat, dedupe);\n break;\n }\n }\n\n /**\n * The state delivery acts on. Unattended it is the screen state. Attended,\n * \"typing\" splits in two: only placeholder text is idle; a real draft is the\n * human's, so the event waits (\"unknown\" queues it).\n */\n private deliveryState(): TuiState {\n const state = this.detectState();\n if (!this.attended || state !== \"typing\") return state;\n return composerHoldsOnlyGhost(tmuxCapturePaneStyled(this.session)) ? \"idle\" : \"unknown\";\n }\n\n /** Capture the screen via tmux capture-pane. */\n private captureScreen(): string[] {\n return tmuxCapturePane(this.session);\n }\n\n /**\n * Inject into an idle prompt: type text + Enter, then check it went.\n *\n * This used to fire a second Enter unconditionally as a \"safety net\" against\n * Claude Code's paste detection swallowing the first. That is a guess in both\n * directions: it cannot tell whether the first Enter worked, and it cannot\n * tell whether the second one did either. Reading the composer back answers\n * the question instead, and only sends the second Enter when it is needed.\n */\n private injectIdle(text: string): void {\n tmuxInjectText(this.session, text);\n tmuxSendEnter(this.session);\n this.confirmSubmitted(text);\n }\n\n /**\n * Check the message left the composer, and press Enter once more if it did not.\n *\n * A submitted message moves into the transcript; one still in the composer was\n * never sent. If it is still there after the retry, the room is told rather\n * than left to read the agent's silence as a choice.\n */\n private confirmSubmitted(text: string): void {\n this.sleep(80);\n if (!composerStillHolds(this.captureScreen(), text)) return;\n\n tmuxSendEnter(this.session);\n this.sleep(120);\n if (!composerStillHolds(this.captureScreen(), text)) return;\n\n this.onUndelivered(text);\n }\n\n /**\n * Inject while the user is typing:\n * 1. Ctrl+U — cut line to kill ring\n * 2. Inject our text + Enter\n * 3. Ctrl+Y — paste the user's text back\n */\n private injectWhileTyping(text: string): void {\n // Cut user's current input\n tmuxSendKey(this.session, \"C-u\");\n this.sleep(this.keystrokeDelayMs);\n\n // Inject our event, then confirm rather than hope\n tmuxInjectText(this.session, text);\n tmuxSendEnter(this.session);\n this.confirmSubmitted(text);\n this.sleep(this.keystrokeDelayMs);\n\n // Restore user's text\n tmuxSendKey(this.session, \"C-y\");\n }\n\n /** Add to queue and start polling if not already. */\n private enqueue(text: string, dedupe: boolean): void {\n // Off by default — room events must never be collapsed. Only callers that\n // retry a delivery they cannot observe landing (the invite loop) opt in.\n if (dedupe && this.queue.includes(text)) return;\n this.queue.push(text);\n this.startPolling();\n }\n\n /** Start the polling timer to drain queued events. */\n private startPolling(): void {\n if (this.pollTimer || this.stopped) return;\n this.pollTimer = setInterval(() => this.drainQueue(), this.pollIntervalMs);\n }\n\n /** Stop the polling timer. */\n private stopPolling(): void {\n if (this.pollTimer) {\n clearInterval(this.pollTimer);\n this.pollTimer = null;\n }\n }\n\n /**\n * Try to drain one queued event if the state is safe.\n *\n * Drains one event at a time rather than batching all into one multi-line\n * string — multi-line text triggers Claude Code's paste detection which\n * collapses it into \"[Pasted text #1 +N lines]\".\n *\n * After injecting one event, the poll continues. The next cycle re-checks\n * state: if Claude is busy (streaming), remaining events wait. If idle,\n * the next event is injected. Events are already flattened in inject().\n */\n private drainQueue(): void {\n if (this.queue.length === 0) {\n this.stopPolling();\n this.blockedCount = 0;\n return;\n }\n\n const state = this.deliveryState();\n if (state === \"idle\" || state === \"typing\") {\n this.blockedCount = 0;\n const text = this.queue.shift()!;\n\n if (state === \"idle\") {\n this.injectIdle(text);\n } else {\n this.injectWhileTyping(text);\n }\n\n if (this.queue.length === 0) {\n this.stopPolling();\n }\n // else: keep polling to drain remaining events\n } else {\n // Any non-injectable state (streaming, dialog, permission, unknown)\n this.blockedCount++;\n if (!this.attended && this.blockedCount >= TmuxBridge.BLOCKED_THRESHOLD) {\n // Stuck too long — force inject to unblock\n this.blockedCount = 0;\n const text = this.queue.shift()!;\n this.injectIdle(text);\n if (this.queue.length === 0) this.stopPolling();\n }\n }\n }\n\n /** Cleanup. */\n stop(): void {\n this.stopped = true;\n this.stopPolling();\n this.queue.length = 0;\n }\n\n /** Synchronous sleep — only used for tiny keystroke delays. */\n private sleep(ms: number): void {\n if (ms <= 0) return;\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);\n }\n}\n\n// ── Activity label extraction ────────────────────────────────────────────────\n\n/**\n * Pull claude's status string out of a captured pane.\n *\n * Claude's TUI shows these markers above the input prompt while it's working:\n * ✻ Fluttering… (38s · ↓ 2.1k tokens) <- the LIVE form, present throughout\n * ✻ Sautéed for 12s <- the FINISHED form, a moment at the end\n * ✻ Compacting conversation… 23%\n * ✻ Thinking…\n *\n * Three patterns, tried narrowest-first so the broad one cannot swallow the\n * others:\n * - PROG: a known long-op verb (Compacting/Thinking/Loading/Connecting)\n * followed by an optional description and optional N%. Stops at the first\n * `(` so it doesn't capture a trailing metadata blob.\n * - COOK: cooking-verb + \"for Ns\", the form claude leaves behind once a turn\n * ends. Matches unicode letters (Sautéed) via the Latin-1 supplement range.\n * - LIVE: any word, an ellipsis, and a parenthesised progress group. Matched\n * on SHAPE rather than vocabulary, because the verb is whimsical and\n * rotates — Improvising, Fluttering, Reticulating, Cogitating. A word list\n * goes stale the next time someone adds one, which is exactly how this\n * broke: with only COOK and PROG, a *working* agent matched nothing and\n * reported no label, so the room strip showed a bare glyph for as long as\n * the work took. The one moment the operator most needs to know what is\n * happening was the one moment it said nothing.\n *\n * LIVE deliberately keeps its parenthesised group — \"(38s · ↓ 2.1k tokens)\" is\n * the elapsed time and stream progress, which is what distinguishes \"moving\"\n * from \"wedged\". PROG still stops at `(` because its verbs carry their own\n * detail, and it is tried first so that stays true.\n *\n * Scan the last ~25 lines, return the newest match. Null when none → idle.\n */\nconst ACTIVITY_LIVE = /([A-Za-zÀ-ÿ]+…\\s*\\([^)\\n]*\\))/;\nconst ACTIVITY_COOK = /([A-Za-zÀ-ÿ]+ed\\s+for\\s+\\d+s)/;\nconst ACTIVITY_PROG = /((?:Compacting|Thinking|Loading|Connecting)[^()]*?(?:\\s+\\d+%)?)(?=\\s*[(\\n]|\\s*$)/;\nexport function extractActivityLabel(lines: string[]): string | null {\n if (lines.length === 0) return null;\n let best: string | null = null;\n for (const line of lines.slice(-25)) {\n // PROG first, then COOK, then LIVE. LIVE is the broadest — any word plus a\n // parenthesised group — so it must go last or it swallows lines the narrower\n // patterns own: \"Compacting conversation… (2m 9s · ↑ 3.3k tokens)\" is\n // reported as \"Compacting conversation…\" on purpose, and LIVE-first turned\n // that into \"conversation… (2m 9s · ↑ 3.3k tokens)\".\n const m = line.match(ACTIVITY_PROG) ?? line.match(ACTIVITY_COOK) ?? line.match(ACTIVITY_LIVE);\n if (m) best = m[1].trim();\n }\n return best;\n}\n\n// ── State detection heuristics ──────────────────────────────────────────────\n\n/**\n * Detect TUI state from capture-pane output lines.\n * Exported separately so it can be unit-tested without tmux.\n *\n * Claude Code's TUI layout (v2.1+):\n * ────────────────────────\n * ❯ <user input here>\n * ────────────────────────\n * PR #2 /ide ...\n *\n * The ❯ prompt sits between separator lines (─). No ❯❯ footer in v2.1+.\n */\nexport function detectStateFromLines(\n lines: string[],\n opts?: { ignorePrompts?: boolean },\n): TuiState {\n if (lines.length === 0) return \"unknown\";\n\n // Work with the last ~30 lines (the visible bottom of the screen)\n const tail = lines.slice(-30);\n const tailText = tail.join(\"\\n\");\n\n // `ignorePrompts` asks \"what is this screen doing APART from any prompt?\" —\n // used by detectReportedStateFromLines to answer the human's question once\n // the loose lists have been found to be matching prose rather than chrome.\n if (opts?.ignorePrompts !== true) {\n // 1. Dialog: selection/question/plan approval\n for (const pattern of DIALOG_PATTERNS) {\n if (tailText.includes(pattern)) return \"dialog\";\n }\n\n // 2. Permission prompt\n for (const pattern of PERMISSION_PATTERNS) {\n if (tailText.includes(pattern)) return \"permission\";\n }\n }\n\n // 3. Streaming: spinner characters in the last few lines\n const lastFew = tail.slice(-5).join(\"\");\n for (const ch of SPINNER_CHARS) {\n if (lastFew.includes(ch)) return \"streaming\";\n }\n\n // 3b. Busy with no spinner — a subagent is running. Checked after the\n // dialog and permission patterns on purpose: a prompt that needs a human\n // outranks \"also busy\", because only one of those the user can clear.\n // Only the bottom of the screen: the live status sits just above the\n // composer, and the same shape further up is output the agent printed.\n const statusArea = tail.slice(-10).join(\"\\n\");\n for (const pattern of BUSY_PATTERNS) {\n if (pattern.test(statusArea)) return \"streaming\";\n }\n\n // 4. Look for the ❯/› prompt line near the bottom.\n // Supports both old layout (❯❯ footer) and new layout (separator lines).\n const promptChar = /^[❯›](\\s|$)/;\n const footerChar = /^[❯›]{2}\\s/;\n const separatorLine = /^[─━─\\-]{10,}/;\n\n // Strategy A: old layout — find ❯❯ footer then ❯ prompt above it\n for (let i = tail.length - 1; i >= 0; i--) {\n const line = tail[i].trimStart();\n if (footerChar.test(line)) {\n // Found old-style footer, look for prompt above\n for (let j = i - 1; j >= 0; j--) {\n const above = tail[j].trimStart();\n if (promptChar.test(above)) {\n const content = above.replace(/^[❯›]\\s*/, \"\").trim();\n return content.length === 0 ? \"idle\" : \"typing\";\n }\n }\n break;\n }\n }\n\n // Strategy B: new layout — find ❯ prompt between/near separator lines\n for (let i = tail.length - 1; i >= 0; i--) {\n const line = tail[i].trimStart();\n if (promptChar.test(line)) {\n // Verify it's Claude's prompt by checking for separator line nearby\n const above = i > 0 ? tail[i - 1].trimStart() : \"\";\n const below = i < tail.length - 1 ? tail[i + 1].trimStart() : \"\";\n if (separatorLine.test(above) || separatorLine.test(below)) {\n const content = line.replace(/^[❯›]\\s*/, \"\").trim();\n return content.length === 0 ? \"idle\" : \"typing\";\n }\n }\n }\n\n return \"unknown\";\n}\n\n/**\n * The same screen read, but tuned for a human rather than for the delivery\n * queue: only claim a prompt is waiting when one is actually on screen.\n *\n * `detectStateFromLines` leans toward \"there is a prompt\" because a false\n * positive there merely queues an event. This one leans the other way, because\n * its false positive is \"⏸ needs you\" in the operator's face at an agent that\n * needs nothing — which costs a person's attention, and teaches them to stop\n * trusting the strip. When the loose lists fire but no specific prompt wording\n * is present, the screen is re-read with the prompt scan disabled so the\n * spinner and composer get to answer instead.\n */\nexport function detectReportedStateFromLines(lines: string[]): TuiState {\n const loose = detectStateFromLines(lines);\n if (loose !== \"dialog\" && loose !== \"permission\") return loose;\n if (matchesAny(lines.slice(-30).join(\"\\n\"), BLOCKING_PATTERNS)) return loose;\n return detectStateFromLines(lines, { ignorePrompts: true });\n}\n","/**\n * Wait for another agent to reach a state.\n *\n * Without this, an agent that needs to act after another one finishes has two\n * options, both bad: re-read the room on a loop (a full LLM turn per check,\n * which is the expensive thing), or guess and collide.\n *\n * It became buildable once runtimes started reporting the state they read off\n * their own CLI's screen, because \"working\" and \"blocked\" look identical from\n * the room's side and only one of them ever resolves on its own.\n *\n * Deliberately bounded rather than open-ended: the call has to return inside\n * the MCP client's tool timeout, so it reports the state it last saw and lets\n * the caller decide whether to wait again. A tool that hangs past the client's\n * timeout fails the turn, which is worse than one that says \"still working\".\n */\n\n/** The states a runtime reports. `unknown` means it reports nothing. */\nexport type AgentWaitState = \"idle\" | \"working\" | \"blocked\" | \"unknown\";\n\n/** What the caller is waiting for. */\nexport type WaitTarget = \"idle\" | \"blocked\" | \"working\" | \"change\";\n\nexport interface WaitForAgentOptions {\n /** Read the target's current state. `unknown` if it isn't reporting. */\n readState: () => Promise<AgentWaitState>;\n /** What to wait for. Default \"idle\" — \"they finished, my turn\". */\n until?: WaitTarget;\n /** Give up after this long. Default 60s, capped by `maxWaitMs`. */\n timeoutMs?: number;\n /** Hard ceiling, to stay inside the MCP client's tool timeout. */\n maxWaitMs?: number;\n /** Gap between reads. Default 2s — this is cheap, unlike an LLM turn. */\n pollIntervalMs?: number;\n sleep?: (ms: number) => Promise<void>;\n now?: () => number;\n}\n\nexport type WaitOutcome =\n | { result: \"satisfied\"; state: AgentWaitState; waitedMs: number }\n | { result: \"timeout\"; state: AgentWaitState; waitedMs: number }\n /** The target reports no state at all — waiting would never end. */\n | { result: \"not-reporting\"; state: \"unknown\"; waitedMs: number };\n\nexport const DEFAULT_WAIT_MS = 60_000;\nexport const MAX_WAIT_MS = 120_000;\nconst DEFAULT_POLL_MS = 2_000;\n\n/**\n * Clamp a requested wait to something that returns inside the MCP client's\n * tool timeout. Exported so the clamp is testable without actually waiting.\n */\nexport function resolveBudgetMs(timeoutMs: number | undefined, maxWaitMs: number = MAX_WAIT_MS): number {\n return Math.min(Math.max(timeoutMs ?? DEFAULT_WAIT_MS, 0), maxWaitMs);\n}\n\n/** Is `state` what the caller asked for? */\nexport function satisfies(state: AgentWaitState, until: WaitTarget, initial: AgentWaitState): boolean {\n if (state === \"unknown\") return false;\n if (until === \"change\") return state !== initial;\n return state === until;\n}\n\n/**\n * Poll until the target agent reaches `until`, or the deadline passes.\n *\n * Returns `not-reporting` immediately if the agent reports no state, rather\n * than burning the whole timeout on something that cannot change: a runtime\n * with no heartbeat (or one that has died) would otherwise look exactly like\n * an agent that is simply taking a long time.\n */\nexport async function waitForAgent(opts: WaitForAgentOptions): Promise<WaitOutcome> {\n const {\n readState,\n until = \"idle\",\n maxWaitMs = MAX_WAIT_MS,\n pollIntervalMs = DEFAULT_POLL_MS,\n sleep = (ms: number) => new Promise<void>((r) => { setTimeout(r, ms).unref?.(); }),\n now = () => Date.now(),\n } = opts;\n\n const budget = resolveBudgetMs(opts.timeoutMs, maxWaitMs);\n const started = now();\n\n const initial = await readState();\n if (initial === \"unknown\") {\n return { result: \"not-reporting\", state: \"unknown\", waitedMs: now() - started };\n }\n if (satisfies(initial, until, initial)) {\n return { result: \"satisfied\", state: initial, waitedMs: now() - started };\n }\n\n let latest: AgentWaitState = initial;\n // Counted rather than wall-clocked so an injected instant sleep cannot spin.\n const steps = Math.max(1, Math.ceil(budget / pollIntervalMs));\n for (let step = 0; step < steps; step++) {\n await sleep(pollIntervalMs);\n latest = await readState();\n if (satisfies(latest, until, initial)) {\n return { result: \"satisfied\", state: latest, waitedMs: now() - started };\n }\n }\n\n return { result: \"timeout\", state: latest, waitedMs: now() - started };\n}\n\n/** One line for the agent, saying what happened and what to do next. */\nexport function describeOutcome(\n participant: string,\n until: WaitTarget,\n outcome: WaitOutcome,\n): string {\n const secs = Math.round(outcome.waitedMs / 1000);\n switch (outcome.result) {\n case \"satisfied\":\n return `${participant} is now ${outcome.state} (waited ${secs}s).`;\n case \"timeout\":\n return `${participant} is still ${outcome.state} after ${secs}s — not ${until} yet. `\n + `Call again to keep waiting, or get on with something else.`;\n case \"not-reporting\":\n return `${participant} does not report its state, so there is nothing to wait for. `\n + `Only agents started by apiary with a Claude Code or Codex runtime report it. `\n + `Ask them directly instead.`;\n }\n}\n","/**\n * Shared MCP-server option builder for the CLI runtime.\n *\n * Both entry points wire the SAME set of local MCP tool handlers against an\n * EventProcessor + SseMultiplexer:\n * - `setupAgentRuntime` (apiary claude / codex / opencode — tmux/HTTP delivery)\n * - `runMcpServer` (apiary mcp — stdio, pull-based)\n *\n * The handlers proxy tool calls (join/leave/set-mode/ping + admin\n * set-mode-for/mute/unmute/kick/promote/demote) to the apiary server over HTTP.\n * Keeping them here stops the two entry points from drifting — which they had:\n * the stdio path was missing admin promote/demote and the room-rules fetch on\n * join. This builder is the single source for both.\n */\n\nimport { extractToken } from \"./auth.js\";\nimport { can } from \"../core/authority.js\";\nimport { RemoteRoomDataSource } from \"../agent/remote-room-data-source.js\";\nimport { EventProcessor } from \"../agent/event-processor.js\";\nimport { SseMultiplexer } from \"../agent/sse-multiplexer.js\";\nimport { buildCatchUpLines } from \"../agent/tool-handlers.js\";\nimport {\n waitForAgent,\n describeOutcome,\n type AgentWaitState,\n type WaitTarget,\n} from \"../agent/wait-for-agent.js\";\nimport { type EngagementMode } from \"../agent/engagement.js\";\nimport type { Participant, AuthorityLevel } from \"../core/types.js\";\nimport type { RuntimeMcpServerOptions, JoinRoomResult } from \"../agent/mcp/runtime.js\";\n\n/** A room the agent has joined, tracked for cache updates + cleanup. */\nexport interface JoinResult {\n serverUrl: string;\n sessionToken: string;\n participantId: string;\n roomName: string;\n roomId: string;\n authority: string;\n participants: Participant[];\n dataSource: RemoteRoomDataSource;\n}\n\nexport interface RuntimeHandlerContext {\n processor: EventProcessor;\n sseMux: SseMultiplexer;\n /** Mutable — join/leave handlers push and splice as the agent moves rooms. */\n joinResults: JoinResult[];\n agentName: string;\n authority: AuthorityLevel | undefined;\n /** Fired after a successful join (the tmux/HTTP path repaints its UI). */\n onRoomJoined?: () => void | Promise<void>;\n}\n\n/**\n * Build the `RuntimeMcpServerOptions` shared by every CLI runtime entry point.\n * Pass the result straight to `createRuntimeMcpServer` / `createStdioRuntimeMcpServer`.\n */\nexport function buildRuntimeMcpOptions(ctx: RuntimeHandlerContext): RuntimeMcpServerOptions {\n const { processor, sseMux, joinResults, agentName, authority, onRoomJoined } = ctx;\n\n /** Resolve a room + named participant, then POST to the apiary server. */\n const adminAction = (build: (id: string) => { path: string; body: Record<string, unknown> }) =>\n async (room: string, participant: string) => {\n const conn = processor.resolve(room);\n if (!conn) return { success: false, error: `Unknown room \"${room}\".` };\n const ds = conn.dataSource as RemoteRoomDataSource;\n const p = conn.dataSource.listParticipants().find((pp) => pp.name === participant);\n if (!p) return { success: false, error: `Unknown participant \"${participant}\".` };\n const { path, body } = build(p.id);\n try {\n const res = await fetch(`${ds.serverUrl}${path}`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${ds.sessionToken}` },\n body: JSON.stringify(body),\n });\n if (!res.ok) return { success: false, error: await res.text() };\n return { success: true };\n } catch {\n return { success: false, error: \"Server unreachable.\" };\n }\n };\n\n return {\n resolver: processor,\n toolOptions: {\n isEventSeen: (id) => processor.isEventSeen(id),\n markEventsSeen: (ids) => processor.markEventsSeen(ids),\n assignRef: (id) => processor.assignRef(id),\n resolveRef: (ref) => processor.resolveRef(ref),\n },\n authority,\n\n onSetMode: async (room, mode) => {\n const conn = processor.resolve(room);\n if (!conn) return { success: false, error: `Unknown room \"${room}\".` };\n processor.setModeForRoom(conn.dataSource.roomId, mode as EngagementMode, false);\n try {\n const ds = conn.dataSource as RemoteRoomDataSource;\n const res = await fetch(`${ds.serverUrl}/set-mode`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${ds.sessionToken}` },\n body: JSON.stringify({ mode }),\n });\n if (!res.ok) return { success: false, error: `Server rejected: ${await res.text()}` };\n } catch {\n // Server unreachable, local mode still set\n }\n return { success: true };\n },\n\n /**\n * Wait for another agent to reach a state, by polling the room server's\n * participant list. Two seconds of HTTP is far cheaper than the LLM turn\n * the agent would otherwise spend re-reading the room to check.\n */\n onWaitForAgent: async (room, participant, until, timeoutSec) => {\n const conn = processor.resolve(room);\n if (!conn) return { success: false, error: `Unknown room \"${room}\".` };\n const ds = conn.dataSource as RemoteRoomDataSource;\n const target = conn.dataSource.listParticipants().find((pp) => pp.name === participant);\n if (!target) return { success: false, error: `Unknown participant \"${participant}\".` };\n if (target.id === conn.dataSource.selfId) {\n return { success: false, error: \"You cannot wait for yourself.\" };\n }\n\n const readState = async (): Promise<AgentWaitState> => {\n try {\n const res = await fetch(`${ds.serverUrl}/participants`, {\n headers: { Authorization: `Bearer ${ds.sessionToken}` },\n signal: AbortSignal.timeout(10_000),\n });\n if (!res.ok) return \"unknown\";\n const data = (await res.json()) as {\n participants?: Array<{ id: string; agentState?: string }>;\n };\n const found = data.participants?.find((pp) => pp.id === target.id);\n const state = found?.agentState;\n return state === \"idle\" || state === \"working\" || state === \"blocked\" ? state : \"unknown\";\n } catch {\n // A transient failure must not read as a state change.\n return \"unknown\";\n }\n };\n\n const outcome = await waitForAgent({\n readState,\n until,\n timeoutMs: timeoutSec === undefined ? undefined : timeoutSec * 1000,\n });\n return { success: true, message: describeOutcome(participant, until ?? \"idle\", outcome) };\n },\n\n onPing: async (room, participant) => {\n const conn = processor.resolve(room);\n if (!conn) return { success: false, error: `Unknown room \"${room}\".` };\n const ds = conn.dataSource as RemoteRoomDataSource;\n const p = conn.dataSource.listParticipants().find((pp) => pp.name === participant);\n if (!p) return { success: false, error: `Unknown participant \"${participant}\".` };\n try {\n const res = await fetch(`${ds.serverUrl}/ping`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${ds.sessionToken}` },\n body: JSON.stringify({ participantId: p.id }),\n });\n if (!res.ok) return { success: false, error: await res.text() };\n return { success: true };\n } catch {\n return { success: false, error: \"Server unreachable.\" };\n }\n },\n\n onJoinRoom: async (url, alias, nameOverride) => {\n const token = extractToken(url);\n let serverUrl: string;\n try {\n const parsed = new URL(url);\n parsed.search = \"\";\n serverUrl = parsed.toString().replace(/\\/$/, \"\");\n } catch {\n serverUrl = url.replace(/\\/$/, \"\");\n }\n\n try {\n const joinName = nameOverride ?? agentName;\n const joinBody: Record<string, unknown> = { type: \"agent\", name: joinName };\n if (token) joinBody.token = token;\n\n const res = await fetch(`${serverUrl}/join`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(joinBody),\n signal: AbortSignal.timeout(15_000),\n });\n if (!res.ok) return { success: false, error: `Failed to join: ${await res.text()}` };\n\n const data = await res.json() as Record<string, unknown>;\n const sessionToken = String(data.sessionToken ?? \"\");\n const roomName = alias ?? String(data.roomName ?? \"\");\n const roomId = String(data.roomId ?? \"\");\n const roomAuthority = String(data.authority ?? \"member\");\n const participants = (data.participants as Participant[]) ?? [];\n const newParticipantId = String(data.participantId ?? \"\");\n\n const dataSource = new RemoteRoomDataSource(serverUrl, sessionToken, roomId);\n dataSource.setParticipants(participants);\n dataSource.setSelf(newParticipantId, joinName);\n\n // Set global selfId on first join; always set per-room selfId\n if (joinResults.length === 0) {\n processor.participantId = newParticipantId;\n }\n processor.setRoomParticipantId(roomId, newParticipantId);\n\n // Register in EventProcessor and SSE multiplexer\n const mode = processor.getModeForRoom(roomId) ?? \"everyone\";\n processor.connectRemoteRoom(dataSource, roomName);\n sseMux.addConnection(serverUrl, sessionToken, roomName, roomId);\n\n // Track for cleanup\n const jr: JoinResult = {\n serverUrl,\n sessionToken,\n participantId: newParticipantId,\n roomName,\n roomId,\n authority: roomAuthority,\n participants,\n dataSource,\n };\n joinResults.push(jr);\n\n // Build recent activity lines for the response\n const conn = processor.resolve(roomName);\n let recentLines: string[] = [];\n if (conn) {\n const catchUp = await buildCatchUpLines(conn, {\n isEventSeen: (id) => processor.isEventSeen(id),\n markEventsSeen: (ids) => processor.markEventsSeen(ids),\n assignRef: (id) => processor.assignRef(id),\n });\n recentLines = catchUp.lines;\n // TODO: catchUp.imageBlocks not surfaced here — the runtime injection\n // path doesn't support image content blocks yet. Wire up when adding\n // image delivery to the tmux/HTTP agent path.\n }\n\n await onRoomJoined?.();\n\n // Fetch this room's rules so they show up in the join response. Best\n // effort — older servers without /rules return 404 and we just skip.\n let rules: string[] | undefined;\n try {\n const rulesRes = await fetch(`${serverUrl}/rules`, {\n headers: { Authorization: `Bearer ${sessionToken}` },\n });\n if (rulesRes.ok) {\n const rulesData = (await rulesRes.json()) as { rules?: string[] };\n if (Array.isArray(rulesData.rules) && rulesData.rules.length > 0) rules = rulesData.rules;\n }\n } catch {\n /* old server or transient — skip */\n }\n\n return {\n success: true,\n roomName,\n agentName: joinName,\n authority: roomAuthority,\n mode,\n participants: participants\n .filter((p) => p.id !== newParticipantId)\n .map((p) => ({ name: p.name, authority: (p as any).authority ?? \"member\" })),\n recentLines,\n rules,\n } as JoinRoomResult;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { success: false, error: `Unable to connect. Is the server running? (${serverUrl}) — ${msg}` };\n }\n },\n\n onLeaveRoom: async (room) => {\n const conn = processor.resolve(room);\n if (!conn) return { success: false, error: `Unknown room \"${room}\".` };\n const roomId = conn.dataSource.roomId;\n\n const idx = joinResults.findIndex((jr) => jr.roomId === roomId);\n if (idx >= 0) {\n const jr = joinResults[idx];\n sseMux.removeConnection(roomId);\n processor.disconnectRemoteRoom(roomId);\n\n try {\n await fetch(`${jr.serverUrl}/disconnect`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${jr.sessionToken}` },\n body: JSON.stringify({}),\n });\n } catch {\n // Server may be down\n }\n\n joinResults.splice(idx, 1);\n }\n return { success: true };\n },\n\n // Admin actions differ only by endpoint + target tier — one helper covers all.\n onAdminSetModeFor: can(authority, \"set_mode_for\")\n ? (room, participant, mode) =>\n adminAction((id) => ({ path: \"/set-mode\", body: { participantId: id, mode } }))(room, participant)\n : undefined,\n onAdminMute: can(authority, \"mute\")\n ? adminAction((id) => ({ path: \"/set-authority\", body: { participantId: id, authority: \"guest\" } }))\n : undefined,\n onAdminUnmute: can(authority, \"unmute\")\n ? adminAction((id) => ({ path: \"/set-authority\", body: { participantId: id, authority: \"member\" } }))\n : undefined,\n onAdminKick: can(authority, \"kick\")\n ? adminAction((id) => ({ path: \"/kick\", body: { participantId: id } }))\n : undefined,\n onAdminPromote: can(authority, \"promote\")\n ? adminAction((id) => ({ path: \"/set-authority\", body: { participantId: id, authority: \"product_owner\" } }))\n : undefined,\n onAdminDemote: can(authority, \"demote\")\n ? adminAction((id) => ({ path: \"/set-authority\", body: { participantId: id, authority: \"member\" } }))\n : undefined,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiBA,IAAM,gBAAgB;AAQf,SAAS,cAAc,OAAyB;AACrD,QAAM,OAAO,MAAM,MAAM,GAAG;AAC5B,MAAI,QAAQ;AACZ,WAAS,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;AACzC,QAAI,cAAc,KAAK,KAAK,CAAC,CAAC,GAAG;AAC/B,cAAQ;AACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,UAAU,GAAI,QAAO;AACzB,SAAO,KAAK,MAAM,KAAK,EAAE,KAAK,GAAG;AACnC;AAUO,SAAS,gBAAgB,MAAsB;AACpD,SAAO,KAAK,QAAQ,QAAQ,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,EAAE;AACrD;AAQO,SAAS,mBAAmB,OAAiB,MAAuB;AACzE,QAAM,QAAQ,gBAAgB,IAAI;AAClC,MAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,QAAM,WAAW,cAAc,KAAK,EAAE,QAAQ,QAAQ,GAAG;AACzD,SAAO,SAAS,SAAS,KAAK;AAChC;;;ACrBA,IAAM,gBAAgB;AAkBtB,IAAM,gBAA0B;AAAA,EAC9B;AACF;AAUO,SAAS,uBAAuB,aAAgC;AACrE,QAAM,MAAM;AACZ,WAAS,IAAI,YAAY,SAAS,GAAG,KAAK,GAAG,KAAK;AAChD,UAAM,QAAQ,YAAY,CAAC,EAAE,QAAQ,KAAK,EAAE,EAAE,UAAU;AACxD,QAAI,CAAC,cAAc,KAAK,KAAK,EAAG;AAChC,UAAM,cAAc,YAAY,CAAC,EAAE,MAAM,YAAY,CAAC,EAAE,OAAO,MAAM,IAAI,CAAC;AAC1E,UAAM,QAAQ,YACX,QAAQ,2CAA2C,EAAE,EACrD,QAAQ,KAAK,EAAE,EACf,KAAK;AACR,WAAO,MAAM,WAAW;AAAA,EAC1B;AACA,SAAO;AACT;AAGA,IAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,IAAM,sBAAsB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAIA;AAAA,EACA;AAAA;AAAA,EAEA;AACF;AAsBA,IAAM,oBAA4C;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AACF;AAGA,SAAS,WAAW,MAAc,UAA2C;AAC3E,QAAM,WAAW,KAAK,YAAY;AAClC,aAAW,WAAW,UAAU;AAC9B,QAAI,OAAO,YAAY,UAAU;AAC/B,UAAI,SAAS,SAAS,QAAQ,YAAY,CAAC,EAAG,QAAO;AAAA,IACvD,WAAW,QAAQ,KAAK,IAAI,GAAG;AAC7B,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAwCA,SAAS,0BAA0B,MAAoB;AACrD,QAAM,UAAU,KAAK,QAAQ,QAAQ,GAAG,EAAE,MAAM,GAAG,EAAE;AACrD,UAAQ,OAAO,MAAM,4EAAuE,OAAO;AAAA,CAAI;AACzG;AAEO,IAAM,aAAN,MAAM,YAAW;AAAA,EACd;AAAA,EACA,QAAkB,CAAC;AAAA,EACnB,YAAmD;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,eAAe;AAAA,EACvB,OAAwB,oBAAoB;AAAA;AAAA,EAE5C,YAAY,SAAiB,MAA0B;AACrD,SAAK,UAAU;AACf,SAAK,iBAAiB,MAAM,kBAAkB;AAC9C,SAAK,mBAAmB,MAAM,oBAAoB;AAClD,SAAK,gBAAgB,MAAM,iBAAiB;AAC5C,SAAK,WAAW,MAAM,YAAY;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAQ,OAAsB,MAAsC;AACxE,UAAM,OAAO,qBAAqB,KAAK;AACvC,QAAI,CAAC,KAAK,KAAK,EAAG;AAElB,SAAK,OAAO,MAAM,MAAM,WAAW,IAAI;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAwB;AAGtB,QAAI,gBAAgB,KAAK,OAAO,EAAG,QAAO;AAC1C,UAAM,QAAQ,KAAK,cAAc;AACjC,WAAO,qBAAqB,KAAK;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,sBAAgC;AAC9B,QAAI,gBAAgB,KAAK,OAAO,EAAG,QAAO;AAC1C,WAAO,6BAA6B,KAAK,cAAc,CAAC;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,mBAAkC;AAChC,WAAO,qBAAqB,KAAK,cAAc,CAAC;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,OAAO,MAAc,QAAuB;AAClD,UAAM,OAAO,KAAK,QAAQ,OAAO,GAAG;AACpC,UAAM,QAAQ,KAAK,cAAc;AAEjC,YAAQ,OAAO;AAAA,MACb,KAAK;AACH,aAAK,WAAW,IAAI;AACpB;AAAA,MACF,KAAK;AACH,aAAK,kBAAkB,IAAI;AAC3B;AAAA,MACF;AAEE,aAAK,QAAQ,MAAM,MAAM;AACzB;AAAA,IACJ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,gBAA0B;AAChC,UAAM,QAAQ,KAAK,YAAY;AAC/B,QAAI,CAAC,KAAK,YAAY,UAAU,SAAU,QAAO;AACjD,WAAO,uBAAuB,sBAAsB,KAAK,OAAO,CAAC,IAAI,SAAS;AAAA,EAChF;AAAA;AAAA,EAGQ,gBAA0B;AAChC,WAAO,gBAAgB,KAAK,OAAO;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,WAAW,MAAoB;AACrC,mBAAe,KAAK,SAAS,IAAI;AACjC,kBAAc,KAAK,OAAO;AAC1B,SAAK,iBAAiB,IAAI;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,iBAAiB,MAAoB;AAC3C,SAAK,MAAM,EAAE;AACb,QAAI,CAAC,mBAAmB,KAAK,cAAc,GAAG,IAAI,EAAG;AAErD,kBAAc,KAAK,OAAO;AAC1B,SAAK,MAAM,GAAG;AACd,QAAI,CAAC,mBAAmB,KAAK,cAAc,GAAG,IAAI,EAAG;AAErD,SAAK,cAAc,IAAI;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,kBAAkB,MAAoB;AAE5C,gBAAY,KAAK,SAAS,KAAK;AAC/B,SAAK,MAAM,KAAK,gBAAgB;AAGhC,mBAAe,KAAK,SAAS,IAAI;AACjC,kBAAc,KAAK,OAAO;AAC1B,SAAK,iBAAiB,IAAI;AAC1B,SAAK,MAAM,KAAK,gBAAgB;AAGhC,gBAAY,KAAK,SAAS,KAAK;AAAA,EACjC;AAAA;AAAA,EAGQ,QAAQ,MAAc,QAAuB;AAGnD,QAAI,UAAU,KAAK,MAAM,SAAS,IAAI,EAAG;AACzC,SAAK,MAAM,KAAK,IAAI;AACpB,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA,EAGQ,eAAqB;AAC3B,QAAI,KAAK,aAAa,KAAK,QAAS;AACpC,SAAK,YAAY,YAAY,MAAM,KAAK,WAAW,GAAG,KAAK,cAAc;AAAA,EAC3E;AAAA;AAAA,EAGQ,cAAoB;AAC1B,QAAI,KAAK,WAAW;AAClB,oBAAc,KAAK,SAAS;AAC5B,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,aAAmB;AACzB,QAAI,KAAK,MAAM,WAAW,GAAG;AAC3B,WAAK,YAAY;AACjB,WAAK,eAAe;AACpB;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,cAAc;AACjC,QAAI,UAAU,UAAU,UAAU,UAAU;AAC1C,WAAK,eAAe;AACpB,YAAM,OAAO,KAAK,MAAM,MAAM;AAE9B,UAAI,UAAU,QAAQ;AACpB,aAAK,WAAW,IAAI;AAAA,MACtB,OAAO;AACL,aAAK,kBAAkB,IAAI;AAAA,MAC7B;AAEA,UAAI,KAAK,MAAM,WAAW,GAAG;AAC3B,aAAK,YAAY;AAAA,MACnB;AAAA,IAEF,OAAO;AAEL,WAAK;AACL,UAAI,CAAC,KAAK,YAAY,KAAK,gBAAgB,YAAW,mBAAmB;AAEvE,aAAK,eAAe;AACpB,cAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,aAAK,WAAW,IAAI;AACpB,YAAI,KAAK,MAAM,WAAW,EAAG,MAAK,YAAY;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,OAAa;AACX,SAAK,UAAU;AACf,SAAK,YAAY;AACjB,SAAK,MAAM,SAAS;AAAA,EACtB;AAAA;AAAA,EAGQ,MAAM,IAAkB;AAC9B,QAAI,MAAM,EAAG;AACb,YAAQ,KAAK,IAAI,WAAW,IAAI,kBAAkB,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE;AAAA,EACjE;AACF;AAoCA,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AACf,SAAS,qBAAqB,OAAgC;AACnE,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,MAAI,OAAsB;AAC1B,aAAW,QAAQ,MAAM,MAAM,GAAG,GAAG;AAMnC,UAAM,IAAI,KAAK,MAAM,aAAa,KAAK,KAAK,MAAM,aAAa,KAAK,KAAK,MAAM,aAAa;AAC5F,QAAI,EAAG,QAAO,EAAE,CAAC,EAAE,KAAK;AAAA,EAC1B;AACA,SAAO;AACT;AAgBO,SAAS,qBACd,OACA,MACU;AACV,MAAI,MAAM,WAAW,EAAG,QAAO;AAG/B,QAAM,OAAO,MAAM,MAAM,GAAG;AAC5B,QAAM,WAAW,KAAK,KAAK,IAAI;AAK/B,MAAI,MAAM,kBAAkB,MAAM;AAEhC,eAAW,WAAW,iBAAiB;AACrC,UAAI,SAAS,SAAS,OAAO,EAAG,QAAO;AAAA,IACzC;AAGA,eAAW,WAAW,qBAAqB;AACzC,UAAI,SAAS,SAAS,OAAO,EAAG,QAAO;AAAA,IACzC;AAAA,EACF;AAGA,QAAM,UAAU,KAAK,MAAM,EAAE,EAAE,KAAK,EAAE;AACtC,aAAW,MAAM,eAAe;AAC9B,QAAI,QAAQ,SAAS,EAAE,EAAG,QAAO;AAAA,EACnC;AAOA,QAAM,aAAa,KAAK,MAAM,GAAG,EAAE,KAAK,IAAI;AAC5C,aAAW,WAAW,eAAe;AACnC,QAAI,QAAQ,KAAK,UAAU,EAAG,QAAO;AAAA,EACvC;AAIA,QAAM,aAAa;AACnB,QAAM,aAAa;AACnB,QAAM,gBAAgB;AAGtB,WAAS,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;AACzC,UAAM,OAAO,KAAK,CAAC,EAAE,UAAU;AAC/B,QAAI,WAAW,KAAK,IAAI,GAAG;AAEzB,eAAS,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK;AAC/B,cAAM,QAAQ,KAAK,CAAC,EAAE,UAAU;AAChC,YAAI,WAAW,KAAK,KAAK,GAAG;AAC1B,gBAAM,UAAU,MAAM,QAAQ,YAAY,EAAE,EAAE,KAAK;AACnD,iBAAO,QAAQ,WAAW,IAAI,SAAS;AAAA,QACzC;AAAA,MACF;AACA;AAAA,IACF;AAAA,EACF;AAGA,WAAS,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;AACzC,UAAM,OAAO,KAAK,CAAC,EAAE,UAAU;AAC/B,QAAI,WAAW,KAAK,IAAI,GAAG;AAEzB,YAAM,QAAQ,IAAI,IAAI,KAAK,IAAI,CAAC,EAAE,UAAU,IAAI;AAChD,YAAM,QAAQ,IAAI,KAAK,SAAS,IAAI,KAAK,IAAI,CAAC,EAAE,UAAU,IAAI;AAC9D,UAAI,cAAc,KAAK,KAAK,KAAK,cAAc,KAAK,KAAK,GAAG;AAC1D,cAAM,UAAU,KAAK,QAAQ,YAAY,EAAE,EAAE,KAAK;AAClD,eAAO,QAAQ,WAAW,IAAI,SAAS;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAcO,SAAS,6BAA6B,OAA2B;AACtE,QAAM,QAAQ,qBAAqB,KAAK;AACxC,MAAI,UAAU,YAAY,UAAU,aAAc,QAAO;AACzD,MAAI,WAAW,MAAM,MAAM,GAAG,EAAE,KAAK,IAAI,GAAG,iBAAiB,EAAG,QAAO;AACvE,SAAO,qBAAqB,OAAO,EAAE,eAAe,KAAK,CAAC;AAC5D;;;ACpjBO,IAAM,kBAAkB;AACxB,IAAM,cAAc;AAC3B,IAAM,kBAAkB;AAMjB,SAAS,gBAAgB,WAA+B,YAAoB,aAAqB;AACtG,SAAO,KAAK,IAAI,KAAK,IAAI,aAAa,iBAAiB,CAAC,GAAG,SAAS;AACtE;AAGO,SAAS,UAAU,OAAuB,OAAmB,SAAkC;AACpG,MAAI,UAAU,UAAW,QAAO;AAChC,MAAI,UAAU,SAAU,QAAO,UAAU;AACzC,SAAO,UAAU;AACnB;AAUA,eAAsB,aAAa,MAAiD;AAClF,QAAM;AAAA,IACJ;AAAA,IACA,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,QAAQ,CAAC,OAAe,IAAI,QAAc,CAAC,MAAM;AAAE,iBAAW,GAAG,EAAE,EAAE,QAAQ;AAAA,IAAG,CAAC;AAAA,IACjF,MAAM,MAAM,KAAK,IAAI;AAAA,EACvB,IAAI;AAEJ,QAAM,SAAS,gBAAgB,KAAK,WAAW,SAAS;AACxD,QAAM,UAAU,IAAI;AAEpB,QAAM,UAAU,MAAM,UAAU;AAChC,MAAI,YAAY,WAAW;AACzB,WAAO,EAAE,QAAQ,iBAAiB,OAAO,WAAW,UAAU,IAAI,IAAI,QAAQ;AAAA,EAChF;AACA,MAAI,UAAU,SAAS,OAAO,OAAO,GAAG;AACtC,WAAO,EAAE,QAAQ,aAAa,OAAO,SAAS,UAAU,IAAI,IAAI,QAAQ;AAAA,EAC1E;AAEA,MAAI,SAAyB;AAE7B,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,KAAK,SAAS,cAAc,CAAC;AAC5D,WAAS,OAAO,GAAG,OAAO,OAAO,QAAQ;AACvC,UAAM,MAAM,cAAc;AAC1B,aAAS,MAAM,UAAU;AACzB,QAAI,UAAU,QAAQ,OAAO,OAAO,GAAG;AACrC,aAAO,EAAE,QAAQ,aAAa,OAAO,QAAQ,UAAU,IAAI,IAAI,QAAQ;AAAA,IACzE;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ,WAAW,OAAO,QAAQ,UAAU,IAAI,IAAI,QAAQ;AACvE;AAGO,SAAS,gBACd,aACA,OACA,SACQ;AACR,QAAM,OAAO,KAAK,MAAM,QAAQ,WAAW,GAAI;AAC/C,UAAQ,QAAQ,QAAQ;AAAA,IACtB,KAAK;AACH,aAAO,GAAG,WAAW,WAAW,QAAQ,KAAK,YAAY,IAAI;AAAA,IAC/D,KAAK;AACH,aAAO,GAAG,WAAW,aAAa,QAAQ,KAAK,UAAU,IAAI,gBAAW,KAAK;AAAA,IAE/E,KAAK;AACH,aAAO,GAAG,WAAW;AAAA,EAGzB;AACF;;;AClEO,SAAS,uBAAuB,KAAqD;AAC1F,QAAM,EAAE,WAAW,QAAQ,aAAa,WAAW,WAAW,aAAa,IAAI;AAG/E,QAAM,cAAc,CAAC,UACnB,OAAO,MAAc,gBAAwB;AAC3C,UAAM,OAAO,UAAU,QAAQ,IAAI;AACnC,QAAI,CAAC,KAAM,QAAO,EAAE,SAAS,OAAO,OAAO,iBAAiB,IAAI,KAAK;AACrE,UAAM,KAAK,KAAK;AAChB,UAAM,IAAI,KAAK,WAAW,iBAAiB,EAAE,KAAK,CAAC,OAAO,GAAG,SAAS,WAAW;AACjF,QAAI,CAAC,EAAG,QAAO,EAAE,SAAS,OAAO,OAAO,wBAAwB,WAAW,KAAK;AAChF,UAAM,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,EAAE;AACjC,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,GAAG,SAAS,GAAG,IAAI,IAAI;AAAA,QAChD,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,oBAAoB,eAAe,UAAU,GAAG,YAAY,GAAG;AAAA,QAC1F,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B,CAAC;AACD,UAAI,CAAC,IAAI,GAAI,QAAO,EAAE,SAAS,OAAO,OAAO,MAAM,IAAI,KAAK,EAAE;AAC9D,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,QAAQ;AACN,aAAO,EAAE,SAAS,OAAO,OAAO,sBAAsB;AAAA,IACxD;AAAA,EACF;AAEF,SAAO;AAAA,IACL,UAAU;AAAA,IACV,aAAa;AAAA,MACX,aAAa,CAAC,OAAO,UAAU,YAAY,EAAE;AAAA,MAC7C,gBAAgB,CAAC,QAAQ,UAAU,eAAe,GAAG;AAAA,MACrD,WAAW,CAAC,OAAO,UAAU,UAAU,EAAE;AAAA,MACzC,YAAY,CAAC,QAAQ,UAAU,WAAW,GAAG;AAAA,IAC/C;AAAA,IACA;AAAA,IAEA,WAAW,OAAO,MAAM,SAAS;AAC/B,YAAM,OAAO,UAAU,QAAQ,IAAI;AACnC,UAAI,CAAC,KAAM,QAAO,EAAE,SAAS,OAAO,OAAO,iBAAiB,IAAI,KAAK;AACrE,gBAAU,eAAe,KAAK,WAAW,QAAQ,MAAwB,KAAK;AAC9E,UAAI;AACF,cAAM,KAAK,KAAK;AAChB,cAAM,MAAM,MAAM,MAAM,GAAG,GAAG,SAAS,aAAa;AAAA,UAClD,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,oBAAoB,eAAe,UAAU,GAAG,YAAY,GAAG;AAAA,UAC1F,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC;AAAA,QAC/B,CAAC;AACD,YAAI,CAAC,IAAI,GAAI,QAAO,EAAE,SAAS,OAAO,OAAO,oBAAoB,MAAM,IAAI,KAAK,CAAC,GAAG;AAAA,MACtF,QAAQ;AAAA,MAER;AACA,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,gBAAgB,OAAO,MAAM,aAAa,OAAO,eAAe;AAC9D,YAAM,OAAO,UAAU,QAAQ,IAAI;AACnC,UAAI,CAAC,KAAM,QAAO,EAAE,SAAS,OAAO,OAAO,iBAAiB,IAAI,KAAK;AACrE,YAAM,KAAK,KAAK;AAChB,YAAM,SAAS,KAAK,WAAW,iBAAiB,EAAE,KAAK,CAAC,OAAO,GAAG,SAAS,WAAW;AACtF,UAAI,CAAC,OAAQ,QAAO,EAAE,SAAS,OAAO,OAAO,wBAAwB,WAAW,KAAK;AACrF,UAAI,OAAO,OAAO,KAAK,WAAW,QAAQ;AACxC,eAAO,EAAE,SAAS,OAAO,OAAO,gCAAgC;AAAA,MAClE;AAEA,YAAM,YAAY,YAAqC;AACrD,YAAI;AACF,gBAAM,MAAM,MAAM,MAAM,GAAG,GAAG,SAAS,iBAAiB;AAAA,YACtD,SAAS,EAAE,eAAe,UAAU,GAAG,YAAY,GAAG;AAAA,YACtD,QAAQ,YAAY,QAAQ,GAAM;AAAA,UACpC,CAAC;AACD,cAAI,CAAC,IAAI,GAAI,QAAO;AACpB,gBAAM,OAAQ,MAAM,IAAI,KAAK;AAG7B,gBAAM,QAAQ,KAAK,cAAc,KAAK,CAAC,OAAO,GAAG,OAAO,OAAO,EAAE;AACjE,gBAAM,QAAQ,OAAO;AACrB,iBAAO,UAAU,UAAU,UAAU,aAAa,UAAU,YAAY,QAAQ;AAAA,QAClF,QAAQ;AAEN,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,YAAM,UAAU,MAAM,aAAa;AAAA,QACjC;AAAA,QACA;AAAA,QACA,WAAW,eAAe,SAAY,SAAY,aAAa;AAAA,MACjE,CAAC;AACD,aAAO,EAAE,SAAS,MAAM,SAAS,gBAAgB,aAAa,SAAS,QAAQ,OAAO,EAAE;AAAA,IAC1F;AAAA,IAEA,QAAQ,OAAO,MAAM,gBAAgB;AACnC,YAAM,OAAO,UAAU,QAAQ,IAAI;AACnC,UAAI,CAAC,KAAM,QAAO,EAAE,SAAS,OAAO,OAAO,iBAAiB,IAAI,KAAK;AACrE,YAAM,KAAK,KAAK;AAChB,YAAM,IAAI,KAAK,WAAW,iBAAiB,EAAE,KAAK,CAAC,OAAO,GAAG,SAAS,WAAW;AACjF,UAAI,CAAC,EAAG,QAAO,EAAE,SAAS,OAAO,OAAO,wBAAwB,WAAW,KAAK;AAChF,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,GAAG,GAAG,SAAS,SAAS;AAAA,UAC9C,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,oBAAoB,eAAe,UAAU,GAAG,YAAY,GAAG;AAAA,UAC1F,MAAM,KAAK,UAAU,EAAE,eAAe,EAAE,GAAG,CAAC;AAAA,QAC9C,CAAC;AACD,YAAI,CAAC,IAAI,GAAI,QAAO,EAAE,SAAS,OAAO,OAAO,MAAM,IAAI,KAAK,EAAE;AAC9D,eAAO,EAAE,SAAS,KAAK;AAAA,MACzB,QAAQ;AACN,eAAO,EAAE,SAAS,OAAO,OAAO,sBAAsB;AAAA,MACxD;AAAA,IACF;AAAA,IAEA,YAAY,OAAO,KAAK,OAAO,iBAAiB;AAC9C,YAAM,QAAQ,aAAa,GAAG;AAC9B,UAAI;AACJ,UAAI;AACF,cAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,eAAO,SAAS;AAChB,oBAAY,OAAO,SAAS,EAAE,QAAQ,OAAO,EAAE;AAAA,MACjD,QAAQ;AACN,oBAAY,IAAI,QAAQ,OAAO,EAAE;AAAA,MACnC;AAEA,UAAI;AACF,cAAM,WAAW,gBAAgB;AACjC,cAAM,WAAoC,EAAE,MAAM,SAAS,MAAM,SAAS;AAC1E,YAAI,MAAO,UAAS,QAAQ;AAE5B,cAAM,MAAM,MAAM,MAAM,GAAG,SAAS,SAAS;AAAA,UAC3C,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,QAAQ;AAAA,UAC7B,QAAQ,YAAY,QAAQ,IAAM;AAAA,QACpC,CAAC;AACD,YAAI,CAAC,IAAI,GAAI,QAAO,EAAE,SAAS,OAAO,OAAO,mBAAmB,MAAM,IAAI,KAAK,CAAC,GAAG;AAEnF,cAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,cAAM,eAAe,OAAO,KAAK,gBAAgB,EAAE;AACnD,cAAM,WAAW,SAAS,OAAO,KAAK,YAAY,EAAE;AACpD,cAAM,SAAS,OAAO,KAAK,UAAU,EAAE;AACvC,cAAM,gBAAgB,OAAO,KAAK,aAAa,QAAQ;AACvD,cAAM,eAAgB,KAAK,gBAAkC,CAAC;AAC9D,cAAM,mBAAmB,OAAO,KAAK,iBAAiB,EAAE;AAExD,cAAM,aAAa,IAAI,qBAAqB,WAAW,cAAc,MAAM;AAC3E,mBAAW,gBAAgB,YAAY;AACvC,mBAAW,QAAQ,kBAAkB,QAAQ;AAG7C,YAAI,YAAY,WAAW,GAAG;AAC5B,oBAAU,gBAAgB;AAAA,QAC5B;AACA,kBAAU,qBAAqB,QAAQ,gBAAgB;AAGvD,cAAM,OAAO,UAAU,eAAe,MAAM,KAAK;AACjD,kBAAU,kBAAkB,YAAY,QAAQ;AAChD,eAAO,cAAc,WAAW,cAAc,UAAU,MAAM;AAG9D,cAAM,KAAiB;AAAA,UACrB;AAAA,UACA;AAAA,UACA,eAAe;AAAA,UACf;AAAA,UACA;AAAA,UACA,WAAW;AAAA,UACX;AAAA,UACA;AAAA,QACF;AACA,oBAAY,KAAK,EAAE;AAGnB,cAAM,OAAO,UAAU,QAAQ,QAAQ;AACvC,YAAI,cAAwB,CAAC;AAC7B,YAAI,MAAM;AACR,gBAAM,UAAU,MAAM,kBAAkB,MAAM;AAAA,YAC5C,aAAa,CAAC,OAAO,UAAU,YAAY,EAAE;AAAA,YAC7C,gBAAgB,CAAC,QAAQ,UAAU,eAAe,GAAG;AAAA,YACrD,WAAW,CAAC,OAAO,UAAU,UAAU,EAAE;AAAA,UAC3C,CAAC;AACD,wBAAc,QAAQ;AAAA,QAIxB;AAEA,cAAM,eAAe;AAIrB,YAAI;AACJ,YAAI;AACF,gBAAM,WAAW,MAAM,MAAM,GAAG,SAAS,UAAU;AAAA,YACjD,SAAS,EAAE,eAAe,UAAU,YAAY,GAAG;AAAA,UACrD,CAAC;AACD,cAAI,SAAS,IAAI;AACf,kBAAM,YAAa,MAAM,SAAS,KAAK;AACvC,gBAAI,MAAM,QAAQ,UAAU,KAAK,KAAK,UAAU,MAAM,SAAS,EAAG,SAAQ,UAAU;AAAA,UACtF;AAAA,QACF,QAAQ;AAAA,QAER;AAEA,eAAO;AAAA,UACL,SAAS;AAAA,UACT;AAAA,UACA,WAAW;AAAA,UACX,WAAW;AAAA,UACX;AAAA,UACA,cAAc,aACX,OAAO,CAAC,MAAM,EAAE,OAAO,gBAAgB,EACvC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAY,EAAU,aAAa,SAAS,EAAE;AAAA,UAC7E;AAAA,UACA;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,cAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,eAAO,EAAE,SAAS,OAAO,OAAO,8CAA8C,SAAS,YAAO,GAAG,GAAG;AAAA,MACtG;AAAA,IACF;AAAA,IAEA,aAAa,OAAO,SAAS;AAC3B,YAAM,OAAO,UAAU,QAAQ,IAAI;AACnC,UAAI,CAAC,KAAM,QAAO,EAAE,SAAS,OAAO,OAAO,iBAAiB,IAAI,KAAK;AACrE,YAAM,SAAS,KAAK,WAAW;AAE/B,YAAM,MAAM,YAAY,UAAU,CAAC,OAAO,GAAG,WAAW,MAAM;AAC9D,UAAI,OAAO,GAAG;AACZ,cAAM,KAAK,YAAY,GAAG;AAC1B,eAAO,iBAAiB,MAAM;AAC9B,kBAAU,qBAAqB,MAAM;AAErC,YAAI;AACF,gBAAM,MAAM,GAAG,GAAG,SAAS,eAAe;AAAA,YACxC,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,oBAAoB,eAAe,UAAU,GAAG,YAAY,GAAG;AAAA,YAC1F,MAAM,KAAK,UAAU,CAAC,CAAC;AAAA,UACzB,CAAC;AAAA,QACH,QAAQ;AAAA,QAER;AAEA,oBAAY,OAAO,KAAK,CAAC;AAAA,MAC3B;AACA,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAAA;AAAA,IAGA,mBAAmB,IAAI,WAAW,cAAc,IAC5C,CAAC,MAAM,aAAa,SAClB,YAAY,CAAC,QAAQ,EAAE,MAAM,aAAa,MAAM,EAAE,eAAe,IAAI,KAAK,EAAE,EAAE,EAAE,MAAM,WAAW,IACnG;AAAA,IACJ,aAAa,IAAI,WAAW,MAAM,IAC9B,YAAY,CAAC,QAAQ,EAAE,MAAM,kBAAkB,MAAM,EAAE,eAAe,IAAI,WAAW,QAAQ,EAAE,EAAE,IACjG;AAAA,IACJ,eAAe,IAAI,WAAW,QAAQ,IAClC,YAAY,CAAC,QAAQ,EAAE,MAAM,kBAAkB,MAAM,EAAE,eAAe,IAAI,WAAW,SAAS,EAAE,EAAE,IAClG;AAAA,IACJ,aAAa,IAAI,WAAW,MAAM,IAC9B,YAAY,CAAC,QAAQ,EAAE,MAAM,SAAS,MAAM,EAAE,eAAe,GAAG,EAAE,EAAE,IACpE;AAAA,IACJ,gBAAgB,IAAI,WAAW,SAAS,IACpC,YAAY,CAAC,QAAQ,EAAE,MAAM,kBAAkB,MAAM,EAAE,eAAe,IAAI,WAAW,gBAAgB,EAAE,EAAE,IACzG;AAAA,IACJ,eAAe,IAAI,WAAW,QAAQ,IAClC,YAAY,CAAC,QAAQ,EAAE,MAAM,kBAAkB,MAAM,EAAE,eAAe,IAAI,WAAW,SAAS,EAAE,EAAE,IAClG;AAAA,EACN;AACF;","names":[]}
@@ -5,21 +5,21 @@ import {
5
5
  loadConfig,
6
6
  roomEmoji,
7
7
  saveConfig,
8
- saveRoomSession,
8
+ saveRoomSession
9
+ } from "./chunk-LOONM4G3.js";
10
+ import {
11
+ extractToken,
9
12
  tmuxCapturePane,
10
13
  tmuxInjectText,
11
14
  tmuxSendEnter,
12
15
  tmuxSessionExists
13
- } from "./chunk-FTN3D6WM.js";
14
- import {
15
- extractToken
16
- } from "./chunk-63Q3KJ4W.js";
16
+ } from "./chunk-N2LPKMPO.js";
17
17
  import {
18
18
  randomName
19
19
  } from "./chunk-D7DXZQWT.js";
20
20
  import {
21
21
  can
22
- } from "./chunk-YP4K2A7G.js";
22
+ } from "./chunk-TMXQDGMN.js";
23
23
 
24
24
  // src/cli/join.ts
25
25
  import { randomUUID } from "crypto";
@@ -2018,4 +2018,4 @@ export {
2018
2018
  HEADLESS_READY,
2019
2019
  join
2020
2020
  };
2021
- //# sourceMappingURL=chunk-3O76FESR.js.map
2021
+ //# sourceMappingURL=chunk-LNSDH2V5.js.map
@@ -4,8 +4,9 @@ import {
4
4
  } from "./chunk-PEB6HEFT.js";
5
5
  import {
6
6
  TokenManager,
7
- buildShareUrl
8
- } from "./chunk-63Q3KJ4W.js";
7
+ buildShareUrl,
8
+ tmuxKillSession
9
+ } from "./chunk-N2LPKMPO.js";
9
10
  import {
10
11
  randomName,
11
12
  randomRoomName
@@ -13,7 +14,7 @@ import {
13
14
  import {
14
15
  can,
15
16
  formatTimestamp
16
- } from "./chunk-YP4K2A7G.js";
17
+ } from "./chunk-TMXQDGMN.js";
17
18
  import {
18
19
  AttachmentSchema,
19
20
  createEvent
@@ -258,166 +259,9 @@ function isAgentAlive(session) {
258
259
  }
259
260
  }
260
261
 
261
- // src/cli/tmux.ts
262
- import { execFileSync, spawn } from "child_process";
263
- function sanitizeSessionName(name) {
264
- return name.replace(/[.:$%]/g, "_");
265
- }
266
- function tmuxAvailable() {
267
- try {
268
- execFileSync("tmux", ["-V"], { stdio: "ignore" });
269
- return true;
270
- } catch {
271
- return false;
272
- }
273
- }
274
- function tmuxSessionExists(session) {
275
- try {
276
- const name = sanitizeSessionName(session);
277
- execFileSync("tmux", ["has-session", "-t", name], { stdio: "ignore" });
278
- return true;
279
- } catch {
280
- return false;
281
- }
282
- }
283
- var AGENT_PANE_COLS = 200;
284
- var AGENT_PANE_ROWS = 50;
285
- function tmuxCreateSession(session, title, size = { cols: AGENT_PANE_COLS, rows: AGENT_PANE_ROWS }) {
286
- const name = sanitizeSessionName(session);
287
- execFileSync("tmux", [
288
- "new-session",
289
- "-d",
290
- "-s",
291
- name,
292
- "-x",
293
- String(size.cols),
294
- "-y",
295
- String(size.rows)
296
- ]);
297
- execFileSync("tmux", ["set", "-t", name, "status", "off"]);
298
- try {
299
- execFileSync("tmux", ["set-option", "-t", name, "window-size", "manual"]);
300
- execFileSync("tmux", ["resize-window", "-t", name, "-x", String(size.cols), "-y", String(size.rows)]);
301
- } catch {
302
- }
303
- if (title) {
304
- execFileSync("tmux", ["set", "-t", name, "set-titles", "on"]);
305
- execFileSync("tmux", ["set", "-t", name, "set-titles-string", title]);
306
- }
307
- }
308
- function tmuxSendCommand(session, command) {
309
- const name = sanitizeSessionName(session);
310
- execFileSync("tmux", ["send-keys", "-t", name, "-l", command]);
311
- execFileSync("tmux", ["send-keys", "-t", name, "Enter"]);
312
- }
313
- function tmuxInjectText(session, text) {
314
- const name = sanitizeSessionName(session);
315
- execFileSync("tmux", ["send-keys", "-t", name, "-l", text]);
316
- }
317
- function tmuxInjectPaste(session, text) {
318
- const name = sanitizeSessionName(session);
319
- execFileSync("tmux", ["send-keys", "-t", name, "-l", `\x1B[200~${text}\x1B[201~`]);
320
- }
321
- function tmuxSendEnter(session) {
322
- const name = sanitizeSessionName(session);
323
- execFileSync("tmux", ["send-keys", "-t", name, "Enter"]);
324
- }
325
- function tmuxAttach(session) {
326
- const name = sanitizeSessionName(session);
327
- if (process.env.TMUX) {
328
- try {
329
- execFileSync("tmux", ["switch-client", "-t", name], { stdio: "ignore" });
330
- } catch {
331
- }
332
- return new Promise((resolve2) => {
333
- const poll = setInterval(() => {
334
- try {
335
- execFileSync("tmux", ["has-session", "-t", name], { stdio: "ignore" });
336
- } catch {
337
- clearInterval(poll);
338
- resolve2();
339
- }
340
- }, 500);
341
- });
342
- }
343
- return new Promise((resolve2) => {
344
- const child = spawn("tmux", ["attach", "-t", name], { stdio: "inherit" });
345
- child.on("exit", () => resolve2());
346
- child.on("error", () => resolve2());
347
- });
348
- }
349
- function tmuxCapturePane(session) {
350
- try {
351
- const name = sanitizeSessionName(session);
352
- const output = execFileSync("tmux", ["capture-pane", "-t", name, "-p"], {
353
- encoding: "utf-8"
354
- });
355
- return trimTrailingBlankLines(output.split("\n"));
356
- } catch {
357
- return [];
358
- }
359
- }
360
- function trimTrailingBlankLines(lines) {
361
- let end = lines.length;
362
- while (end > 0 && lines[end - 1].trim() === "") end--;
363
- return lines.slice(0, end);
364
- }
365
- function tmuxSendKey(session, key) {
366
- const name = sanitizeSessionName(session);
367
- execFileSync("tmux", ["send-keys", "-t", name, key]);
368
- }
369
- function tmuxKillSession(session) {
370
- try {
371
- const name = sanitizeSessionName(session);
372
- execFileSync("tmux", ["kill-session", "-t", name], { stdio: "ignore" });
373
- } catch {
374
- }
375
- }
376
- function resetTerminal() {
377
- if (!process.stdin.isTTY) return;
378
- try {
379
- execFileSync("stty", ["sane"], { stdio: "inherit" });
380
- } catch {
381
- }
382
- }
383
- var SHELL_COMMANDS = /* @__PURE__ */ new Set([
384
- "sh",
385
- "bash",
386
- "zsh",
387
- "fish",
388
- "dash",
389
- "ksh",
390
- "mksh",
391
- "tcsh",
392
- "csh",
393
- "nu",
394
- "pwsh",
395
- "powershell",
396
- "login",
397
- "elvish",
398
- "xonsh"
399
- ]);
400
- function tmuxPaneCommand(session) {
401
- try {
402
- const name = sanitizeSessionName(session);
403
- const out = execFileSync("tmux", ["display-message", "-p", "-t", name, "#{pane_current_command}"], {
404
- encoding: "utf-8",
405
- timeout: 5e3
406
- }).trim();
407
- return out || null;
408
- } catch {
409
- return null;
410
- }
411
- }
412
- function tmuxPaneIsShell(session) {
413
- const cmd = tmuxPaneCommand(session);
414
- if (!cmd) return false;
415
- return SHELL_COMMANDS.has(cmd.toLowerCase().replace(/^-/, ""));
416
- }
417
-
418
262
  // src/cli/serve.ts
419
263
  import { createServer } from "http";
420
- import { spawn as spawn2, execFileSync as execFileSync2 } from "child_process";
264
+ import { spawn, execFileSync } from "child_process";
421
265
  import { randomUUID } from "crypto";
422
266
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync4, rmSync as rmSync2 } from "fs";
423
267
  import { createRequire } from "module";
@@ -1635,7 +1479,7 @@ function logServer(message) {
1635
1479
  }
1636
1480
  function cloudflaredAvailable() {
1637
1481
  try {
1638
- execFileSync2("which", ["cloudflared"], { stdio: "ignore" });
1482
+ execFileSync("which", ["cloudflared"], { stdio: "ignore" });
1639
1483
  return true;
1640
1484
  } catch {
1641
1485
  return false;
@@ -1646,7 +1490,7 @@ async function startTunnel(port) {
1646
1490
  console.error(" --share requires cloudflared. Install: brew install cloudflared");
1647
1491
  return null;
1648
1492
  }
1649
- const child = spawn2("cloudflared", ["tunnel", "--url", `http://localhost:${port}`], {
1493
+ const child = spawn("cloudflared", ["tunnel", "--url", `http://localhost:${port}`], {
1650
1494
  stdio: ["ignore", "ignore", "pipe"]
1651
1495
  });
1652
1496
  child.on("error", () => {
@@ -1697,23 +1541,10 @@ export {
1697
1541
  clearAgentSession,
1698
1542
  listAgentSessions,
1699
1543
  isAgentAlive,
1700
- tmuxAvailable,
1701
- tmuxSessionExists,
1702
- tmuxCreateSession,
1703
- tmuxSendCommand,
1704
- tmuxInjectText,
1705
- tmuxInjectPaste,
1706
- tmuxSendEnter,
1707
- tmuxAttach,
1708
- tmuxCapturePane,
1709
- tmuxSendKey,
1710
- tmuxKillSession,
1711
- resetTerminal,
1712
- tmuxPaneIsShell,
1713
1544
  INVITES_DIR,
1714
1545
  saveRoomSession,
1715
1546
  listRoomSessions,
1716
1547
  removeRoomSession,
1717
1548
  serve
1718
1549
  };
1719
- //# sourceMappingURL=chunk-FTN3D6WM.js.map
1550
+ //# sourceMappingURL=chunk-LOONM4G3.js.map