@madarco/agentbox 0.11.1 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/{_cloud-attach-45ECDTRL.js → _cloud-attach-XWCVLO5V.js} +6 -4
- package/dist/{chunk-PZ2TJF2U.js → chunk-GYJ62GFL.js} +32 -12
- package/dist/chunk-GYJ62GFL.js.map +1 -0
- package/dist/index.js +66 -24
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-PZ2TJF2U.js.map +0 -1
- /package/dist/{_cloud-attach-45ECDTRL.js.map → _cloud-attach-XWCVLO5V.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@madarco/agentbox",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "Launch Claude Code, Codex, and other coding agents in isolated sandboxes",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Marco D'Alia",
|
|
@@ -60,14 +60,14 @@
|
|
|
60
60
|
"vitest": "^2.1.8",
|
|
61
61
|
"@agentbox/config": "0.0.0",
|
|
62
62
|
"@agentbox/ctl": "0.0.0",
|
|
63
|
-
"@agentbox/core": "0.0.0",
|
|
64
|
-
"@agentbox/sandbox-cloud": "0.0.0",
|
|
65
63
|
"@agentbox/relay": "0.0.0",
|
|
64
|
+
"@agentbox/sandbox-cloud": "0.0.0",
|
|
65
|
+
"@agentbox/core": "0.0.0",
|
|
66
66
|
"@agentbox/sandbox-core": "0.0.0",
|
|
67
|
-
"@agentbox/sandbox-daytona": "0.0.0",
|
|
68
67
|
"@agentbox/sandbox-docker": "0.0.0",
|
|
69
68
|
"@agentbox/sandbox-hetzner": "0.0.0",
|
|
70
|
-
"@agentbox/sandbox-vercel": "0.0.0"
|
|
69
|
+
"@agentbox/sandbox-vercel": "0.0.0",
|
|
70
|
+
"@agentbox/sandbox-daytona": "0.0.0"
|
|
71
71
|
},
|
|
72
72
|
"scripts": {
|
|
73
73
|
"build": "tsup",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/_cloud-attach.ts","../src/provider/registry.ts","../src/wrapped-pty/run.ts","../src/pty/pty-backend.ts","../src/terminal/host.ts","../src/terminal/title.ts","../src/wrapped-pty/input-router.ts","../src/dashboard/sidebar.ts","../src/wrapped-pty/footer.ts","../src/wrapped-pty/prompt-client.ts","../src/lib/paste-image.ts","../src/lib/host-clipboard.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { appendFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { spinner } from '@clack/prompts';\nimport { DEFAULT_RELAY_PORT } from '@agentbox/sandbox-docker';\nimport type { BoxRecord } from '@agentbox/core';\nimport type { AttachOpenIn } from '@agentbox/config';\nimport { providerForBox } from '../provider/registry.js';\nimport { runWrappedAttach } from '../wrapped-pty/index.js';\nimport { pasteHostClipboardImage } from '../lib/paste-image.js';\nimport { clipboardCaptureAvailable } from '../lib/host-clipboard.js';\n\nconst RELAY_HOST_URL = `http://127.0.0.1:${String(DEFAULT_RELAY_PORT)}`;\n/** Give up reconnecting a dropped attach after this long (box likely gone). */\nconst RECONNECT_TIMEOUT_MS = 5 * 60_000;\n\n/** setTimeout that also resolves early if the signal aborts (no rejection). */\nfunction abortableSleep(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise<void>((resolve) => {\n if (signal.aborted) {\n resolve();\n return;\n }\n const t = setTimeout(resolve, ms);\n signal.addEventListener(\n 'abort',\n () => {\n clearTimeout(t);\n resolve();\n },\n { once: true },\n );\n });\n}\n\n/**\n * Attach to (or create) a tmux session inside a cloud sandbox over SSH and\n * run an agent CLI inside it. Shared between `agentbox claude`/`codex`/\n * `opencode` so the SSH + tmux mechanics live in one place.\n *\n * The inner command tmux runs is `bash -lc 'exec <binary>'`:\n * - login shell so `/home/vscode/.local/bin` is on PATH and `/etc/profile.d/\n * agentbox.sh` exports `AGENTBOX_BOX_*` env;\n * - `exec` so the agent gets PID 2 (Ctrl-c in the agent kills the session\n * cleanly rather than dropping to bash).\n *\n * When `extraArgs` is non-empty, we base64-encode the argv (one arg per line)\n * and hand the inner shell a small `mapfile`-based launcher that reconstructs\n * the array — see `buildCloudAttachInnerCommand`. Base64 is alphanumeric+`/+=`\n * so it survives every shell-quoting layer (host single-quote, SSH, tmux,\n * bash) untouched, which avoids the 3-layer escaping mess the literal form\n * would otherwise require.\n */\nexport interface CloudAgentAttachArgs {\n box: BoxRecord;\n /** In-sandbox binary path or name (`claude`, `codex`, `opencode`). */\n binary: string;\n /** Tmux session name (e.g. `claude`). */\n sessionName: string;\n /** Mode label for the wrapper's footer. */\n mode: 'claude' | 'codex' | 'opencode';\n /**\n * Extra args the user typed after `--`. Passed through to the in-box agent\n * verbatim via a base64-encoded launcher. Limitation: args containing\n * literal `\\n` aren't supported (none of claude/codex/opencode flags do).\n */\n extraArgs?: string[];\n /**\n * Where to open the attached session in the host's terminal (`split`/`window`/\n * `tab`/`same`). Forwarded to `runWrappedAttach`. Daytona attaches are forced\n * to `same` for now because `provider.buildAttach()` may return a `cleanup`\n * that tears down per-call SSH tunnels — running cleanup while a detached\n * new pane still holds the connection would kill the pane. Hetzner's\n * ControlMaster is per-box-lifetime so spawn-and-detach is safe there.\n */\n openIn?: AttachOpenIn;\n}\n\n/**\n * Render the inner shell command tmux runs inside the cloud sandbox. Exported\n * so unit tests can exercise the base64 round-trip without spinning up SSH.\n *\n * Empty `extraArgs` keeps the no-args path identical to the pre-args\n * behaviour — `bash -lc 'exec <binary>'` with a backslash-space so the outer\n * shell-quoting layers don't split `exec` from the binary name.\n */\nexport function buildCloudAttachInnerCommand(binary: string, extraArgs?: string[]): string {\n if (!extraArgs || extraArgs.length === 0) {\n return `bash -lc exec\\\\ ${binary}`;\n }\n // One arg per line, base64-encoded. The launcher runs `mapfile -t A` against\n // the decoded stream, then `exec <binary> \"${A[@]}\"` so each arg lands as\n // its own argv element — quotes/spaces inside an arg are preserved exactly\n // because base64 is opaque to every outer shell quoting pass.\n const blob = Buffer.from(extraArgs.join('\\n'), 'utf8').toString('base64');\n // The decode feeds `mapfile` via a **here-string**, NOT process substitution\n // (`< <(…)`). Process substitution needs `/dev/fd/N`, and the Vercel Sandbox\n // (Firecracker microVM, AL2023) has no `/dev/fd` — so `mapfile -t A < <(…)`\n // fails with `/dev/fd/63: No such file or directory`, A stays empty, and the\n // agent launches with no args (the wizard's initial setup prompt is silently\n // dropped). A here-string is backed by a temp file, needs no `/dev/fd`, and\n // works on every backend (docker/daytona/hetzner unaffected). `$(…)` strips\n // the trailing newline `<<<` re-adds, so mapfile -t yields one element per\n // arg exactly as the join produced them.\n //\n // **bash -lc body MUST be single-quoted, not double-quoted.** When tmux\n // launches the session command, it goes through `/bin/sh -c <cmd>`. If we\n // double-quote, sh's parser sees `\"${A[@]}\"` and expands it eagerly —\n // before mapfile ever runs — to the empty string, so claude is invoked as\n // `claude \"\"` and the wizard's initial prompt is silently dropped. Single\n // quotes are inert in sh's parser: the literal `${A[@]}` (and `$(…)`) reach\n // bash, which runs them AFTER the outer sh layer. The outer shellSingle wrap\n // in renderInnerCommand re-escapes any internal `'` as `'\\''`; this body has\n // no single quotes (it uses double quotes around the here-string), so it\n // composes fine.\n return `bash -lc 'mapfile -t A <<< \"$(echo ${blob} | base64 -d)\"; exec ${binary} \"\\${A[@]}\"'`;\n}\n\nexport async function cloudAgentAttach(args: CloudAgentAttachArgs): Promise<void> {\n const provider = await providerForBox(args.box);\n if (!provider.buildAttach) {\n throw new Error(`provider '${provider.name}' does not support interactive attach`);\n }\n // Captured for the reconnect closure (TS won't preserve the narrowing above\n // inside a later-invoked callback).\n const buildAttach = provider.buildAttach.bind(provider);\n // Ensure the box is running before we attach. A cloud box can be stopped\n // out from under an attach — most notably `checkpoint --set-default`, which\n // snapshots and stops the sandbox. Without this, buildAttach runs against a\n // dead sandbox and the relay poller 502s (\"not listening on the requested\n // port\") forever while the user stares at \"Waiting for connection...\".\n // `provider.start` auto-resumes from snapshot and returns the record with\n // refreshed preview URLs / relay tokens, which we must use downstream.\n // Mirrors the docker attach path (unpause/start) and `checkpoint create`.\n let box = args.box;\n const state = await provider.probeState(box);\n if (state === 'missing') {\n throw new Error(`cloud sandbox for ${box.name} is missing; was it destroyed?`);\n }\n if (state !== 'running') {\n const s = spinner();\n s.start(state === 'paused' ? 'resuming box' : 'starting box');\n box = await provider.start(box);\n s.stop('box running');\n }\n const command = buildCloudAttachInnerCommand(args.binary, args.extraArgs);\n // Daytona-only: force inline attach. `spec.cleanup` would otherwise run as\n // soon as the host process returns from the spawn (before the new pane has\n // released the per-call SSH tunnel), breaking the detached attach.\n const safeOpenIn: AttachOpenIn | undefined =\n box.provider === 'daytona' ? 'same' : args.openIn;\n\n // New-terminal attaches (tab/window/split) re-invoke `agentbox <agent> attach`\n // in the fresh pane, and that re-invocation carries NO `extraArgs` — so for a\n // resume/teleport launch (`claude --resume <id>`, etc.) the session would\n // otherwise be created fresh, dropping the resumed session. Pre-create the\n // session detached here with the full command; the re-invoked attach then\n // finds it via `tmux has-session` and just attaches. (Inline attach runs the\n // full command itself, so it doesn't need this.)\n if (safeOpenIn && safeOpenIn !== 'same' && args.extraArgs && args.extraArgs.length > 0) {\n const pre = await provider.buildAttach(box, 'agent', {\n sessionName: args.sessionName,\n command,\n detached: true,\n });\n try {\n await runDetached(pre.argv, pre.env);\n } finally {\n if (pre.cleanup) await pre.cleanup();\n }\n }\n\n let spec = await provider.buildAttach(box, 'agent', {\n sessionName: args.sessionName,\n command,\n });\n // claude only, and only when this host can capture a clipboard image (macOS,\n // or a Linux desktop with xclip/wl-paste). Otherwise Ctrl+V forwards verbatim.\n const canPaste =\n args.mode === 'claude' && (await clipboardCaptureAvailable());\n\n // Re-establish the attach after the wrapper decides the box dropped (a vercel\n // checkpoint reboot, or a connection blip). Keep trying `provider.start` —\n // which resumes a stopped box and no-ops a running one — until it succeeds or\n // the deadline lapses. We deliberately DON'T bail on consecutive failures: a\n // box mid-snapshot rejects `start` for the whole (multi-minute) capture, and\n // that's indistinguishable from a destroyed box by error alone — so we lean on\n // the time budget (and the user's Ctrl+C, which aborts the signal) instead. On\n // a reboot this lands in a freshly-created tmux session (the snapshot is\n // filesystem-only); a blip on a still-running box re-attaches the same live\n // session. Returns null to give up (cancelled or timed out).\n const reconnect = async (\n signal: AbortSignal,\n ): Promise<{ command: string; argv: string[]; env?: Record<string, string> } | null> => {\n const deadline = Date.now() + RECONNECT_TIMEOUT_MS;\n let backoff = 500;\n for (;;) {\n if (signal.aborted || Date.now() > deadline) return null;\n try {\n box = await provider.start(box);\n break;\n } catch {\n await abortableSleep(backoff, signal);\n backoff = Math.min(backoff * 2, 5000);\n }\n }\n if (signal.aborted) return null;\n // Mint the fresh attach FIRST, then release the previous one's per-call\n // resources (SSH tunnel / token). Order matters: if buildAttach throws,\n // `spec` still points at the old spec (uncleaned), so the outer `finally`\n // cleans it exactly once — building-then-cleaning avoids the double-cleanup\n // that the reverse order would cause on a buildAttach failure.\n const prev = spec;\n spec = await buildAttach(box, 'agent', { sessionName: args.sessionName, command });\n if (prev.cleanup) {\n try {\n await prev.cleanup();\n } catch {\n // best-effort\n }\n }\n return { command: spec.argv[0]!, argv: spec.argv.slice(1), env: spec.env };\n };\n\n try {\n const code = await runWrappedAttach({\n container: box.name,\n command: spec.argv[0],\n dockerArgv: spec.argv.slice(1),\n env: spec.env,\n relayBaseUrl: RELAY_HOST_URL,\n boxId: box.id,\n boxName: box.name,\n projectIndex: box.projectIndex,\n mode: args.mode,\n detachable: true,\n openIn: safeOpenIn,\n reconnect,\n onError: (msg) => {\n // Non-fatal wrapper diagnostics (reconnect failures, give-ups, etc.) —\n // logged to a file because writing to stderr would corrupt the PTY.\n try {\n appendFileSync(\n join(homedir(), '.agentbox', 'logs', 'attach.log'),\n `${new Date().toISOString()} [${box.name}] ${msg}\\n`,\n );\n } catch {\n // best-effort\n }\n },\n onPasteImage: canPaste\n ? () => pasteHostClipboardImage(provider, box)\n : undefined,\n });\n process.exit(code);\n } finally {\n if (spec.cleanup) await spec.cleanup();\n }\n}\n\n/**\n * Run an attach-style argv non-interactively to completion (used for the\n * `detached` session pre-start). stdio is ignored — the remote command only\n * creates + configures the tmux session and exits; there's nothing to show.\n * Resolves on exit regardless of code (a non-zero here shouldn't block the\n * subsequent attach, which surfaces any real failure to the user).\n */\nfunction runDetached(argv: string[], env?: Record<string, string>): Promise<void> {\n return new Promise((resolve) => {\n const child = spawn(argv[0]!, argv.slice(1), {\n stdio: 'ignore',\n env: env ? { ...process.env, ...env } : process.env,\n });\n child.on('error', () => resolve());\n child.on('exit', () => resolve());\n });\n}\n","/**\n * Provider registry — resolves a `Provider` for either an existing box (from\n * its `provider` discriminator) or a fresh `create` (from --provider flag /\n * config / default). Lazy `import()` keeps the Daytona SDK out of the Docker\n * hot path.\n */\n\nimport type { EffectiveConfig } from '@agentbox/config';\nimport type { BoxRecord, Provider, ProviderName } from '@agentbox/core';\n\nexport type KnownProviderName = 'docker' | 'daytona' | 'hetzner' | 'vercel';\n\nconst KNOWN: readonly KnownProviderName[] = ['docker', 'daytona', 'hetzner', 'vercel'];\n\nexport function isKnownProvider(name: string): name is KnownProviderName {\n return (KNOWN as readonly string[]).includes(name);\n}\n\nexport async function getProvider(name: ProviderName): Promise<Provider> {\n switch (name) {\n case 'docker': {\n const mod = await import('@agentbox/sandbox-docker');\n return mod.dockerProvider;\n }\n case 'daytona': {\n // Single lazy import covers both the first-run prompt gate and the\n // provider itself — keeps the Daytona SDK off the Docker hot path.\n // The prompt is a no-op when env is already configured or stdin isn't\n // a TTY (scripted callers get the SDK's \"not configured\" error instead\n // of a hung prompt).\n const mod = await import('@agentbox/sandbox-daytona');\n await mod.ensureDaytonaCredentials();\n return mod.daytonaProvider;\n }\n case 'hetzner': {\n // Same lazy-import pattern as daytona. `ensureHetznerCredentials` walks\n // the user through `agentbox hetzner login` on first use. The base-\n // snapshot gate (`ensureHetznerBaseSnapshot`) is deliberately *not*\n // called here: it would chicken-and-egg `agentbox prepare --provider\n // hetzner` (which exists precisely to BUILD the snapshot). The gate\n // lives inside `backend.provision` instead — `prepare` calls the REST\n // client directly, never `provision`, so it slips past the gate while\n // `create`/`claude`/etc. still trip it.\n const mod = await import('@agentbox/sandbox-hetzner');\n await mod.ensureHetznerCredentials();\n return mod.hetznerProvider;\n }\n case 'vercel': {\n // Same lazy-import pattern. `ensureVercelCredentials` walks the user\n // through `agentbox vercel login` (OIDC or token trio) on first use. The\n // base-snapshot gate lives inside `backend.provision` (so `prepare` can\n // build it without tripping the gate), matching the hetzner shape.\n const mod = await import('@agentbox/sandbox-vercel');\n await mod.ensureVercelCredentials();\n return mod.vercelProvider;\n }\n default:\n throw new Error(`unknown sandbox provider: ${String(name)}`);\n }\n}\n\n/** Provider for an existing box record. Defaults to 'docker' for legacy records. */\nexport async function providerForBox(box: BoxRecord): Promise<Provider> {\n return getProvider(box.provider ?? 'docker');\n}\n\nexport interface CreateProviderChoice {\n /** Explicit --provider flag, if the command exposed one. */\n flag?: string;\n /** Effective config (carries box.provider for the layered default). */\n config: EffectiveConfig;\n}\n\n/**\n * Provider for a fresh `agentbox create`. Precedence: --provider flag >\n * box.provider config > 'docker'. Throws if the resolved name isn't registered.\n */\nexport async function providerForCreate(choice: CreateProviderChoice): Promise<Provider> {\n const flag = choice.flag?.trim();\n const name = (flag && flag.length > 0 ? flag : choice.config.box.provider) as ProviderName;\n if (typeof name !== 'string' || name.length === 0 || !isKnownProvider(name)) {\n throw new Error(\n `unknown sandbox provider \"${String(name)}\" (known: ${KNOWN.join(', ')})`,\n );\n }\n return getProvider(name);\n}\n","import { spawn, spawnSync } from 'node:child_process';\nimport { readBoxStatus } from '@agentbox/sandbox-docker';\nimport type { AttachOpenIn } from '@agentbox/config';\nimport { loadPtyBackend } from '../pty/pty-backend.js';\nimport { detectHostTerminal, spawnInNewTerminal } from '../terminal/host.js';\nimport { popTerminalTitle, pushTerminalTitle, setTerminalTitle } from '../terminal/title.js';\nimport {\n createInputRouter,\n type InputRouter,\n type LeaderAction,\n} from './input-router.js';\nimport {\n ALERT_BAND_ROWS,\n CURSOR_RESTORE,\n CURSOR_SAVE,\n cursorMoveTo,\n renderAlertBand,\n renderFooter,\n SYNC_BEGIN,\n SYNC_END,\n type AlertBandState,\n type FooterState,\n} from './footer.js';\nimport { postAnswer, subscribePrompts, type PromptStream } from './prompt-client.js';\nimport type { BoxNoticeEvent, PromptAskEvent } from '@agentbox/relay';\nimport type { ClaudeQuestionPayload } from '@agentbox/ctl';\n\nexport interface WrappedAttachOptions {\n /** Docker container name (only used for log lines). */\n container: string;\n /** Full docker argv (e.g. result of buildClaudeAttachArgv). */\n dockerArgv: string[];\n /**\n * The program to spawn for the PTY. Defaults to `'docker'` (the historical\n * behavior; `dockerArgv` is then the docker subcommand argv). Cloud boxes\n * pass `'ssh'` with the Daytona SSH argv instead.\n */\n command?: string;\n /** Extra env merged over `process.env` for the spawned child (e.g. the\n * Vercel provider's `VERCEL_AUTH_TOKEN` for the `sbx` CLI). */\n env?: Record<string, string>;\n /** Relay base URL — http://127.0.0.1:8787 in normal use. */\n relayBaseUrl: string;\n boxId: string;\n /** Friendly box name; rendered in the idle footer. */\n boxName: string;\n /** Per-project box index (BoxRecord.projectIndex). Used together with\n * boxId/boxName to read the per-box status.json for the live session\n * title. Pre-feature boxes lack it; absent is fine. */\n projectIndex?: number;\n /** Mode label affects the idle footer state label only. */\n mode: 'claude' | 'shell' | 'codex' | 'opencode';\n /** Whether the inner session can be detached (tmux-backed). Drives the\n * `Ctrl+a d` detach chord + footer hint. Defaults to `mode === 'claude'`\n * (claude is always tmux-backed); a tmux-backed `agentbox shell` passes\n * `true`, a `--no-tmux` shell leaves it false. */\n detachable?: boolean;\n /** Optional notice printed to stdout *after* the pty exits with code 0\n * (mirrors today's `formatDetachNotice` for `agentbox claude`). */\n detachNotice?: string;\n /** Optional sink for non-fatal errors that we'd otherwise swallow (Ctrl+a\n * action spawn failures, status-poll failures, unexpected prompt-capture\n * rejections). Callers wire this to their command log so post-mortem\n * inspection isn't blind. */\n onError?: (msg: string) => void;\n /** Where to open the attached session. When set to anything other than\n * `same` (or undefined) and the host shell is running inside tmux or iTerm2,\n * the attach runs in a fresh pane/tab/window and this function returns 0\n * without taking over the current terminal. Outside tmux/iTerm2 it falls\n * back to inline attach (the original behavior). */\n openIn?: AttachOpenIn;\n /** Optional host→box clipboard image paste, invoked when the user presses\n * Ctrl+V (wired for claude only). Ships the host clipboard image into the\n * box and loads it into the box's X11 clipboard; resolves with the outcome\n * so the footer can flash a result. The input router re-emits Ctrl+V after\n * this settles, so Claude Code reads the now-loaded clipboard. Omitted →\n * Ctrl+V forwards verbatim. */\n onPasteImage?: () => Promise<'pasted' | 'no-image' | 'error'>;\n /**\n * Re-establish the connection after the inner PTY drops. The wrapper decides\n * *whether* to call this (a checkpoint reboot — signalled by an active\n * `checkpoint` notice — or a non-zero exit; a clean exit-0 on a healthy box,\n * or an explicit `agentbox stop`, ends the wrapper instead). The callback then\n * resumes the box if needed and returns a fresh spawn spec to re-attach with,\n * or `null` to give up (box destroyed, cancelled, or timed out). `signal`\n * aborts if the user hits Ctrl+C while reconnecting. Only cloud attaches wire\n * this; docker omits it (exit-on-drop, unchanged).\n */\n reconnect?: (\n signal: AbortSignal,\n exitCode: number,\n ) => Promise<{ command: string; argv: string[]; env?: Record<string, string> } | null>;\n}\n\nconst FOOTER_ROWS = 1;\n/** Min visible inner-PTY rows below which we collapse the band back into the\n * one-line footer (today's behavior). Keeps a tiny terminal usable instead of\n * driving the inner program to a 0-row pane. */\nconst MIN_INNER_ROWS = 5;\nconst STATUS_POLL_INTERVAL_MS = 3000;\n/** Spinner advance cadence while a `notice` footer is active. */\nconst SPINNER_INTERVAL_MS = 120;\n/** How long the post-action confirmation flash stays in the footer. */\nconst FLASH_DURATION_MS = 2000;\n/** A respawned session that dies within this window counts as a rapid failure. */\nconst RAPID_RECONNECT_MS = 8000;\n/** Give up reconnecting after this many consecutive rapid failures (crash loop). */\nconst MAX_RAPID_RECONNECTS = 3;\n/** A drop within this long of a checkpoint notice clearing is still treated as\n * a reboot — only to bridge a tiny SSE-vs-pty event race (the box stops, and\n * thus the pty drops, while the notice is still active, so this rarely fires).\n * Kept short so a clean exit shortly after a checkpoint isn't misread. */\nconst CHECKPOINT_DROP_GRACE_MS = 4000;\n\n/** Per-action confirmation text shown in the footer flash. */\nconst ACTION_FLASH: Record<Exclude<LeaderAction, 'detach'>, string> = {\n screen: 'Opening noVNC viewer…',\n code: 'Launching VS Code / Cursor…',\n url: 'Opening box URL…',\n};\n\n/** Per-action `agentbox` subcommand: `<sub> <boxId> <...flags>`. */\nconst ACTION_CMD: Record<\n Exclude<LeaderAction, 'detach'>,\n { sub: string; flags: string[] }\n> = {\n screen: { sub: 'screen', flags: [] },\n // --no-wait: don't block on `wait-ready` — the box is already running.\n code: { sub: 'code', flags: ['--no-wait'] },\n url: { sub: 'url', flags: [] },\n};\n\n/** Recursive `agentbox <agent> attach <box> --attach-in same` argv for the\n * new-pane re-entry. Returns null for modes that don't have an `attach`\n * subcommand (notably `shell`), so the caller can skip new-pane spawning. */\nfunction buildAgentboxAttachArgv(\n mode: WrappedAttachOptions['mode'],\n boxName: string,\n): string[] | null {\n if (mode !== 'claude' && mode !== 'codex' && mode !== 'opencode') return null;\n return [mode, 'attach', boxName, '--attach-in', 'same'];\n}\n\n/**\n * Replace `spawnSync('docker', argv, { stdio: 'inherit' })` with a\n * node-pty wrapper that reserves the bottom row for a permission-prompt\n * footer. Falls back transparently to today's spawnSync behavior when\n * node-pty isn't available (optional dep missing), or when stdin/stdout\n * isn't a TTY (piping / non-interactive use).\n *\n * Returns the pty's exit code; caller `process.exit`s with it.\n */\nexport async function runWrappedAttach(opts: WrappedAttachOptions): Promise<number> {\n const command = opts.command ?? 'docker';\n const logErr = (msg: string): void => {\n opts.onError?.(msg);\n };\n\n // Open-in-new-terminal short-circuit: if the user asked for split/window/tab\n // and we're inside tmux or iTerm2, re-invoke `agentbox <agent> attach <box>\n // --attach-in same` in a fresh pane so the new pane runs the full wrapper\n // (footer + prompt channel) against the already-prepared session — same UX\n // as inline, just in a new pane. The host process then exits 0. Unknown\n // hosts, shell mode (no attach subcommand to recurse into), and spawn\n // failures fall through to the inline attach below.\n const openIn = opts.openIn ?? 'same';\n if (openIn !== 'same') {\n const subArgv = buildAgentboxAttachArgv(opts.mode, opts.boxName);\n const host = subArgv ? detectHostTerminal() : 'unknown';\n if (subArgv && host !== 'unknown' && process.argv[1]) {\n const r = await spawnInNewTerminal({\n host,\n mode: openIn,\n argv: [process.execPath, process.argv[1], ...subArgv],\n cwd: process.cwd(),\n title: opts.boxName,\n });\n if (r.launched) {\n process.stdout.write(r.note + '\\n');\n return 0;\n }\n if (r.error) logErr(r.error);\n // fall through to inline attach\n }\n }\n\n if (!process.stdout.isTTY || !process.stdin.isTTY) {\n // Non-interactive path: piping / scripts. Don't wrap — preserves\n // machine-readable stdout, no footer corruption.\n return runFallback(command, opts.dockerArgv, opts.env);\n }\n const backend = await loadPtyBackend();\n if (!backend) {\n // One-line stderr notice; preserves current behavior bit-for-bit.\n process.stderr.write(\n 'agentbox: permission prompts disabled (node-pty backend unavailable)\\n',\n );\n return runFallback(command, opts.dockerArgv, opts.env);\n }\n\n const cols = process.stdout.columns ?? 80;\n const rows = process.stdout.rows ?? 24;\n const innerRows = Math.max(1, rows - FOOTER_ROWS);\n\n let pty = backend.ptySpawn(command, opts.dockerArgv, {\n name: 'xterm-256color',\n cols,\n rows: innerRows,\n env: opts.env ? { ...process.env, ...opts.env } : process.env,\n });\n // When the current pty was (re)spawned — feeds the crash-loop guard below.\n let lastSpawnAt = Date.now();\n // Resize the current pty, tolerating a dead one: during a reconnect the old\n // pty has exited and the new one isn't spawned yet, and node-pty throws on a\n // closed pty. A throw here used to propagate out of the band-relayout in\n // reconnectFlow's finally and silently kill the wrapper. The next spawn uses\n // the correct size regardless.\n const resizePty = (c: number, r: number): void => {\n try {\n pty.resize(c, r);\n } catch {\n // pty exited / mid-respawn\n }\n };\n\n // Mirror the agent's session title to the host terminal/tab title (iTerm2\n // etc.). tmux swallows the inner OSC title (set-titles off), so the host\n // never sees it; we re-emit it ourselves from the polled status below. Save\n // the user's current title first so teardown can restore it. Seed with the\n // box name so the tab is named immediately, before the first status poll.\n pushTerminalTitle();\n let lastEmittedTitle = opts.boxName;\n setTerminalTitle(lastEmittedTitle);\n\n // claude is always tmux-backed; a tmux-backed `agentbox shell` opts in via\n // `detachable: true`, a `--no-tmux` shell leaves it false (nothing to detach).\n const detachable = opts.detachable ?? opts.mode === 'claude';\n\n // Idle footer = dashboard's statusLine() with a single hint (`Control+a:\n // Actions`, expanding to the chord menu while the leader is open). Session\n // title + claude activity come from the per-box status.json polled below.\n let leaderActive = false;\n const buildIdle = (sessionTitle?: string, claudeActivity?: string): FooterState => ({\n kind: 'idle',\n boxName: opts.boxName,\n sessionTitle,\n claudeActivity,\n mode: opts.mode,\n detachable,\n leaderActive,\n });\n let footerState: FooterState = buildIdle();\n let lastSessionTitle: string | undefined;\n let lastActivity: string | undefined;\n // Prompt + notice + question feed the alert band above the footer; flash +\n // leader stay in the footer. `recomputeFooter` keeps the footer at idle/flash;\n // `recomputeBand` derives the band visibility from prompt > notice > question.\n let capturingPrompt: PromptAskEvent | null = null;\n let activeNotice: BoxNoticeEvent | null = null;\n // The \"box rebooting — reconnecting…\" banner, owned solely by reconnectFlow.\n // Kept separate from `activeNotice` (which the SSE notice callbacks mutate) so\n // a real notice-set/clear arriving mid-reconnect can't clobber or prematurely\n // dismiss it. Takes precedence over everything while a reconnect is in flight.\n let reconnectBanner: string | null = null;\n let noticeFrame = 0;\n let questionPayload: ClaudeQuestionPayload | null = null;\n let bandState: AlertBandState | null = null;\n let bandReservedRows = 0; // 0 or ALERT_BAND_ROWS depending on band visibility\n let spinnerTimer: ReturnType<typeof setInterval> | null = null;\n // Transient confirmation shown after a Ctrl+a action fires.\n let flashMessage: string | null = null;\n let flashTimer: ReturnType<typeof setTimeout> | null = null;\n // True while the inner pty has dropped and we're re-establishing it. Stdin is\n // swallowed (no live pty to write to) except Ctrl+C, which aborts the wait.\n let reconnecting = false;\n let reconnectAbort: AbortController | null = null;\n // Set when the user deliberately detaches (Ctrl+a d) — that exit must end the\n // wrapper, never trigger a reconnect.\n let userDetached = false;\n // When a `checkpoint` notice last set / cleared (epoch ms; 0 = never). A pty\n // drop while one is active, or within CHECKPOINT_DROP_GRACE_MS of it clearing,\n // is a box reboot (snapshot stopped it) → reconnect; otherwise an exit-0 drop\n // on a healthy box is a clean session end → exit. Plain numbers so the loop's\n // check doesn't trip TS's null-narrowing on `activeNotice` (assigned only in a\n // callback).\n let checkpointNoticeAt = 0;\n let checkpointNoticeClearedAt = 0;\n\n /** Reserved rows above the inner pty: footer (always 1) + band (3 or 0). */\n const reservedRows = (): number => FOOTER_ROWS + bandReservedRows;\n /** Whether the current terminal has room for the band without collapsing\n * the inner pty below `MIN_INNER_ROWS`; gates the band on tiny terminals. */\n const bandFits = (): boolean => {\n const rs = process.stdout.rows ?? rows;\n return rs - FOOTER_ROWS - ALERT_BAND_ROWS >= MIN_INNER_ROWS;\n };\n\n // Lazy SGR mirror: when the inner pty's most recent attribute is bright\n // bold, our footer paint won't reset it correctly via the inner program's\n // next byte. We always end the chrome with SGR reset, but the inner program\n // may be in the middle of a graphics run when the redraw happens — wrap the\n // redraw in cursor save/restore + sync output so the inner program never\n // sees our cursor moves and the user sees one atomic frame.\n const redrawChrome = (): void => {\n const cs = process.stdout.columns ?? cols;\n const rs = process.stdout.rows ?? rows;\n const footerLine = renderFooter(footerState, cs);\n let payload = SYNC_BEGIN + CURSOR_SAVE;\n if (bandReservedRows > 0 && bandState) {\n const bandLines = renderAlertBand(bandState, cs, bandReservedRows);\n for (let i = 0; i < bandLines.length; i++) {\n const row = rs - FOOTER_ROWS - (bandLines.length - i);\n payload += cursorMoveTo(row + 1, 1) + bandLines[i];\n }\n }\n payload += cursorMoveTo(rs, 1) + footerLine + CURSOR_RESTORE + SYNC_END;\n process.stdout.write(payload);\n };\n\n // Derive `footerState` from flash > leader/idle. Prompt/notice/question are\n // surfaced in the alert band above the footer (see `recomputeBand`); the\n // footer keeps showing the calm status bar so the user always has context.\n // **Min-size fallback**: when the band collapses on a tiny terminal\n // (`bandReservedRows === 0` while `bandState != null`), prompt and notice\n // fall back to the pre-band footer-replacement so they're not lost (the\n // question state has no one-line footer renderer — sidebar marker only).\n const recomputeFooter = (): void => {\n const collapsed = bandState !== null && bandReservedRows === 0;\n if (collapsed && reconnectBanner) {\n footerState = { kind: 'notice', message: reconnectBanner, frame: noticeFrame };\n } else if (collapsed && capturingPrompt) {\n footerState = { kind: 'prompt', prompt: capturingPrompt };\n } else if (collapsed && activeNotice) {\n footerState = { kind: 'notice', message: activeNotice.message, frame: noticeFrame };\n } else if (flashMessage) {\n footerState = { kind: 'flash', message: flashMessage };\n } else {\n footerState = buildIdle(lastSessionTitle, lastActivity);\n }\n };\n\n // Derive the band's content + visibility from prompt > notice > question.\n // Priority chain: a relay prompt hard-blocks an in-box RPC (most urgent);\n // a notice means the box is frozen for a snapshot (loud animated banner);\n // a question is the agent waiting for the user. When nothing is active the\n // band collapses entirely.\n const recomputeBand = (): void => {\n if (reconnectBanner) {\n // While reconnecting nothing else is actionable (stdin is swallowed, the\n // box is down), so the banner outranks prompt/notice/question.\n bandState = { kind: 'notice', message: reconnectBanner, frame: noticeFrame };\n } else if (capturingPrompt) {\n bandState = { kind: 'prompt', prompt: capturingPrompt };\n } else if (activeNotice) {\n bandState = { kind: 'notice', message: activeNotice.message, frame: noticeFrame };\n } else if (questionPayload) {\n bandState = { kind: 'question', question: questionPayload };\n } else {\n bandState = null;\n }\n };\n\n /** Resize the inner pty + reapply the scroll region for the current reserved\n * rows, then clear any rows that just changed ownership (the freed region\n * when the band collapses; the band area itself when it appears). Called\n * after `recomputeBand` whenever the band visibility flips. */\n const relayoutForBand = (): void => {\n const cs = process.stdout.columns ?? cols;\n const rs = process.stdout.rows ?? rows;\n const inner = Math.max(1, rs - reservedRows());\n resizePty(cs, inner);\n process.stdout.write(`\\x1b[1;${String(inner)}r`);\n // Clear the chrome area (band + footer rows) so stale agent output left\n // over from the previous scroll region doesn't show through under the\n // newly painted band/footer.\n let clear = SYNC_BEGIN + CURSOR_SAVE;\n for (let r = inner + 1; r <= rs; r++) clear += cursorMoveTo(r, 1) + '\\x1b[2K';\n clear += CURSOR_RESTORE + SYNC_END;\n process.stdout.write(clear);\n };\n\n /** Re-derive the band state and, if visibility changed, resize + reflow.\n * Always finishes with a chrome redraw so the band/footer are repainted.\n * Also re-derives the footer so the min-size fallback (prompt/notice in\n * the footer when the band collapses) keeps in sync. */\n const applyBandChange = (): void => {\n recomputeBand();\n const wantRows = bandState && bandFits() ? ALERT_BAND_ROWS : 0;\n if (wantRows !== bandReservedRows) {\n bandReservedRows = wantRows;\n relayoutForBand();\n }\n recomputeFooter();\n redrawChrome();\n };\n\n const startSpinner = (): void => {\n if (spinnerTimer) return;\n spinnerTimer = setInterval(() => {\n noticeFrame++;\n // Advance the spinner frame whenever a notice is the live band; if the\n // notice was outranked by a prompt the frame still advances so it\n // resumes mid-animation when the prompt clears.\n if (bandState?.kind === 'notice') {\n bandState = { kind: 'notice', message: bandState.message, frame: noticeFrame };\n // When the band is collapsed on a tiny terminal the notice renders\n // through `footerState` instead, so re-derive it to pick up the new\n // frame — otherwise the footer-fallback spinner glyph freezes.\n if (bandReservedRows === 0) recomputeFooter();\n redrawChrome();\n }\n }, SPINNER_INTERVAL_MS);\n if (typeof spinnerTimer.unref === 'function') spinnerTimer.unref();\n };\n const stopSpinner = (): void => {\n if (spinnerTimer) {\n clearInterval(spinnerTimer);\n spinnerTimer = null;\n }\n };\n\n // Wire pty -> stdout. The inner program writes raw bytes; we forward as-is.\n // The outer terminal has `rows` real rows, but the pty thinks it has `innerRows`.\n // The inner program's writes can still physically touch row `rows` (our footer\n // row) via: (1) scroll when its bottom line emits a newline — the terminal\n // scrolls the whole screen and row `rows` gets cleared; (2) clear-screen\n // sequences like `\\x1b[2J`; (3) alt-screen entry `\\x1b[?1049h`; (4) column\n // wraparound from the inner program's last row. The scroll-region setup\n // below limits (1); always-repaint here handles the rest. Each redraw is\n // wrapped in synchronized output (DECSET 2026) so the user never sees a\n // half-painted frame on terminals that support it (iTerm2/WezTerm/kitty/\n // Apple Terminal/Ghostty).\n // Wire the inner pty's output -> stdout. Factored out so a respawned pty\n // (after a reconnect) can be re-wired the same way.\n const wireOutput = (): void => {\n pty.onData((d: string) => {\n process.stdout.write(d);\n redrawChrome();\n });\n };\n wireOutput();\n\n // Ctrl+a leader chord map — keys mirror the dashboard's (`c`/`s`/`u`).\n // A detachable (tmux-backed) session also gets `d: detach`; a plain\n // `--no-tmux` shell has nothing to detach from.\n const leaderChords: Record<string, LeaderAction> = detachable\n ? { c: 'code', s: 'screen', u: 'url', d: 'detach' }\n : { c: 'code', s: 'screen', u: 'url' };\n\n // Run a Ctrl+a leader action. `detach` writes the tmux detach sequence to\n // the pty (`\\x02` = Ctrl+b, tmux's secondary prefix; `d` = detach-client) —\n // the attach process then exits 0 and teardown runs normally. The other\n // actions shell out to the real `agentbox` subcommand, detached, so the\n // long-running open/launch never blocks (or corrupts) this terminal.\n const runAction = (name: LeaderAction): void => {\n if (name === 'detach') {\n if (!reconnecting) {\n userDetached = true;\n pty.write('\\x02d');\n }\n return;\n }\n const cliEntry = process.argv[1];\n if (typeof cliEntry === 'string' && cliEntry.length > 0) {\n const cmd = ACTION_CMD[name];\n try {\n spawn(\n process.execPath,\n [cliEntry, cmd.sub, opts.boxId, ...cmd.flags],\n { detached: true, stdio: 'ignore' },\n ).unref();\n } catch (e) {\n // Best-effort — the footer flash still shows. Surface for inspection.\n logErr(`leader-action spawn (${name}) failed: ${(e as Error).message}`);\n }\n }\n flashMessage = ACTION_FLASH[name];\n if (flashTimer) clearTimeout(flashTimer);\n flashTimer = setTimeout(() => {\n flashTimer = null;\n flashMessage = null;\n recomputeFooter();\n redrawChrome();\n }, FLASH_DURATION_MS);\n if (typeof flashTimer.unref === 'function') flashTimer.unref();\n recomputeFooter();\n redrawChrome();\n };\n\n // Ctrl+V image paste: hold a \"Pasting image…\" notice in the footer while the\n // host clipboard image is shipped into the box, then flash the outcome. The\n // input router re-emits the Ctrl+V once this resolves, so Claude reads the\n // now-loaded box clipboard. Never throws — failures degrade to a flash.\n const handlePasteImage = async (): Promise<void> => {\n if (!opts.onPasteImage) return;\n if (flashTimer) {\n clearTimeout(flashTimer);\n flashTimer = null;\n }\n flashMessage = 'Pasting image…';\n recomputeFooter();\n redrawChrome();\n let result: 'pasted' | 'no-image' | 'error' = 'error';\n try {\n result = await opts.onPasteImage();\n } catch (e) {\n logErr(`paste-image failed: ${(e as Error).message}`);\n }\n flashMessage =\n result === 'pasted'\n ? 'Image pasted'\n : result === 'no-image'\n ? 'No image in clipboard'\n : 'Image paste failed';\n flashTimer = setTimeout(() => {\n flashTimer = null;\n flashMessage = null;\n recomputeFooter();\n redrawChrome();\n }, FLASH_DURATION_MS);\n if (typeof flashTimer.unref === 'function') flashTimer.unref();\n recomputeFooter();\n redrawChrome();\n };\n\n // Wire stdin -> pty (through the router so prompts + the leader can intercept).\n const router: InputRouter = createInputRouter({\n onForward: (b) => {\n // While reconnecting there's no live pty to write to. Swallow input, but\n // let a bare Ctrl+C (a lone 0x03 byte) abort the reconnect wait so the\n // user can bail out. Match exactly — a pasted/coalesced buffer that merely\n // contains 0x03 must not trip the abort.\n if (reconnecting) {\n if (b.length === 1 && b[0] === 0x03) reconnectAbort?.abort();\n return;\n }\n // node-pty wants utf8 strings; stdin is binary safe via Buffer.\n pty.write(b.toString('utf8'));\n },\n onAnswer: (body) => {\n // Fire-and-forget; the relay-side route is idempotent. We don't\n // block the input flow on the network roundtrip.\n void postAnswer({ relayBaseUrl: opts.relayBaseUrl, body });\n capturingPrompt = null;\n applyBandChange();\n },\n leaderChords,\n onLeaderChange: (open) => {\n leaderActive = open;\n recomputeFooter();\n redrawChrome();\n },\n onAction: (name) => {\n runAction(name);\n },\n onPasteImage: opts.onPasteImage ? handlePasteImage : undefined,\n });\n\n if (process.stdin.isTTY) process.stdin.setRawMode(true);\n process.stdin.resume();\n const onStdinData = (chunk: Buffer): void => {\n router.feed(chunk);\n };\n process.stdin.on('data', onStdinData);\n\n // Resize: keep the pty `reservedRows` shorter than the host terminal; the\n // footer owns the last row directly and the band (when active) owns the 3\n // rows above it. Re-apply the scroll region too — most terminals reset\n // DECSTBM on resize. The bandFits() check downgrades band → 0 if the new\n // size is too small to host both.\n const onResize = (): void => {\n const cs = process.stdout.columns ?? cols;\n const rs = process.stdout.rows ?? rows;\n // Re-evaluate band visibility against the new size first; a now-too-small\n // terminal collapses the band, a now-big-enough one re-opens it. Refresh\n // the footer so the collapsed-band fallback (prompt/notice in the footer)\n // tracks the new reserve.\n bandReservedRows = bandState && bandFits() ? ALERT_BAND_ROWS : 0;\n const inner = Math.max(1, rs - reservedRows());\n resizePty(cs, inner);\n process.stdout.write(`\\x1b[1;${String(inner)}r`);\n recomputeFooter();\n redrawChrome();\n };\n process.stdout.on('resize', onResize);\n\n // SSE: subscribe to the relay's prompt stream for this box.\n const stream: PromptStream = subscribePrompts({\n relayBaseUrl: opts.relayBaseUrl,\n boxId: opts.boxId,\n onPrompt: (ev: PromptAskEvent) => {\n capturingPrompt = ev;\n applyBandChange();\n // capture() returns a Promise that resolves with the answer body; the\n // input-router's onAnswer callback already POSTs and resets the band.\n // We just need to await so unhandled rejections (router.abort) don't\n // crash the process.\n router.capture(ev).catch((e: unknown) => {\n // Expected reasons: sibling answered ('resolved-elsewhere'), pty exit.\n // Anything else is a real bug worth surfacing.\n const msg = e instanceof Error ? e.message : String(e);\n if (msg !== 'resolved-elsewhere') {\n logErr(`prompt capture rejected: ${msg}`);\n }\n });\n },\n onResolved: (id: string) => {\n // Clear band if it's still showing this id (sibling wrapper won).\n if (capturingPrompt && capturingPrompt.id === id) {\n capturingPrompt = null;\n router.abort('resolved-elsewhere');\n applyBandChange();\n }\n },\n onNotice: (ev: BoxNoticeEvent) => {\n if (ev.kind === 'checkpoint') checkpointNoticeAt = Date.now();\n activeNotice = ev;\n startSpinner();\n applyBandChange();\n },\n onNoticeCleared: (id: string) => {\n if (activeNotice && activeNotice.id === id) {\n if (activeNotice.kind === 'checkpoint') checkpointNoticeClearedAt = Date.now();\n activeNotice = null;\n stopSpinner();\n applyBandChange();\n }\n },\n });\n\n // Poll the box's status.json for `claude.sessionTitle` so the idle\n // footer can show what claude set as its terminal title (mirrors the\n // dashboard's sidebar entry). Best-effort — paused/stopped boxes and\n // pre-status-feature boxes return null and we just keep the previous\n // title (or no title).\n const pollStatus = async (): Promise<void> => {\n try {\n const status = await readBoxStatus({\n id: opts.boxId,\n name: opts.boxName,\n projectIndex: opts.projectIndex,\n });\n // Read the title/activity from the body of the agent we attached to;\n // shell mode has no agent session so it keeps the box-name title.\n const body =\n opts.mode === 'codex'\n ? status?.codex\n : opts.mode === 'opencode'\n ? status?.opencode\n : opts.mode === 'shell'\n ? undefined\n : status?.claude;\n const nextTitle = body?.sessionTitle?.trim() || undefined;\n const nextActivity = body?.state || undefined;\n // Mirror the live title to the host terminal/tab, falling back to the box\n // name until the agent sets one. Deduped so we don't spam the terminal.\n const desiredTitle = nextTitle ?? opts.boxName;\n if (desiredTitle !== lastEmittedTitle) {\n lastEmittedTitle = desiredTitle;\n setTerminalTitle(desiredTitle);\n }\n // Surface claude's AskUserQuestion payload to the band when the agent\n // is in `question` state; clear it on any other state. Only meaningful\n // for claude mode (codex/opencode have no question payload). The band's\n // priority chain (`recomputeBand`) demotes question below prompt/notice,\n // so it only shows when nothing more urgent is pending.\n const nextQuestion =\n opts.mode === 'claude' && status?.claude.state === 'question'\n ? (status.claude.question ?? null)\n : null;\n const questionChanged =\n (nextQuestion?.capturedAt ?? null) !== (questionPayload?.capturedAt ?? null);\n if (questionChanged) {\n questionPayload = nextQuestion;\n applyBandChange();\n }\n if (nextTitle === lastSessionTitle && nextActivity === lastActivity) return;\n lastSessionTitle = nextTitle;\n lastActivity = nextActivity;\n if (footerState.kind === 'idle') {\n recomputeFooter();\n redrawChrome();\n }\n } catch (e) {\n // readBoxStatus already swallows the common cases (paused/stopped/pre-feature);\n // anything reaching here is unexpected and worth a log line.\n logErr(`status poll failed: ${(e as Error).message}`);\n }\n };\n void pollStatus();\n const statusTimer = setInterval(() => {\n void pollStatus();\n }, STATUS_POLL_INTERVAL_MS);\n if (typeof statusTimer.unref === 'function') statusTimer.unref();\n\n // Restrict the outer terminal's scroll region to rows 1..innerRows so the\n // inner program's natural scrolling (bottom-line newline) doesn't push\n // content into our footer row. DECSTBM also resets the cursor to (1,1) on\n // some terminals, so we follow it with a cursor restore. Reverted in\n // teardown via `\\x1b[r` (clear scroll region -> full screen).\n process.stdout.write(`\\x1b[1;${String(innerRows)}r`);\n\n // Plain shell (`--no-tmux`): bash doesn't enter alt-screen, so without help\n // the user's pre-shell host-terminal content stays visible above bash's\n // freshly drawn prompt. Clear the visible screen + home the cursor before\n // the pty's first write. We don't touch scrollback (`\\x1b[3J`) — the user's\n // pre-shell context stays scroll-up-able. Claude and the tmux-backed shell\n // skip this: they enter their own alt-screen on init and would just\n // overpaint anyway (clearing first would only flicker).\n if (opts.mode === 'shell' && !detachable) {\n process.stdout.write('\\x1b[H\\x1b[2J');\n }\n\n // Initial paint so the idle footer appears immediately.\n redrawChrome();\n\n /**\n * Keep the wrapper open across a drop: show the \"box rebooting — reconnecting…\"\n * band and let `opts.reconnect` resume the box + hand back a fresh spawn spec\n * (or null to give up). Only called once the loop has decided the drop is a\n * reboot/blip, so the band always shows here.\n */\n const reconnectFlow = async (\n code: number,\n ): Promise<{ command: string; argv: string[]; env?: Record<string, string> } | null> => {\n const controller = new AbortController();\n reconnecting = true; // swallow stdin (no live pty) while we re-establish\n reconnectAbort = controller;\n reconnectBanner = 'box rebooting — reconnecting…';\n startSpinner();\n applyBandChange();\n let spec: { command: string; argv: string[]; env?: Record<string, string> } | null = null;\n try {\n spec = (await opts.reconnect?.(controller.signal, code)) ?? null;\n } catch (e) {\n logErr(`reconnect failed: ${(e as Error).message}`);\n } finally {\n reconnecting = false;\n reconnectAbort = null;\n reconnectBanner = null;\n // A real checkpoint notice may have arrived via SSE during the reconnect;\n // keep the spinner running if so, only stop it when nothing animates.\n if (!activeNotice) stopSpinner();\n applyBandChange();\n }\n if (spec) {\n flashMessage = 'reconnected';\n if (flashTimer) clearTimeout(flashTimer);\n flashTimer = setTimeout(() => {\n flashTimer = null;\n flashMessage = null;\n recomputeFooter();\n redrawChrome();\n }, FLASH_DURATION_MS);\n if (typeof flashTimer.unref === 'function') flashTimer.unref();\n recomputeFooter();\n redrawChrome();\n }\n return spec;\n };\n\n // Wait for the pty to exit. Reconnect only on a real drop: a checkpoint reboot\n // (an active/just-cleared `checkpoint` notice — vercel's snapshot stops the\n // box and its `sbx exec` exits 0, so the notice is the signal, not the code)\n // or a non-zero exit (connection blip). A clean exit-0 on a healthy box (agent\n // exited) or an explicit `agentbox stop` (no notice) ends the wrapper. A\n // crash-loop guard bails if respawned sessions keep dying immediately.\n let exitCode = 0;\n let rapidFails = 0;\n for (;;) {\n const code = await new Promise<number>((resolve) => {\n pty.onExit(({ exitCode }) => resolve(exitCode));\n });\n if (userDetached || !opts.reconnect) {\n exitCode = code;\n break;\n }\n // Checkpoint notice currently active (set more recently than cleared) or\n // cleared within the grace window → this drop is a box reboot.\n const checkpointing =\n checkpointNoticeAt > checkpointNoticeClearedAt ||\n Date.now() - checkpointNoticeClearedAt < CHECKPOINT_DROP_GRACE_MS;\n if (!checkpointing && code === 0) {\n exitCode = code; // clean session end on a healthy box\n break;\n }\n rapidFails = Date.now() - lastSpawnAt < RAPID_RECONNECT_MS ? rapidFails + 1 : 0;\n if (rapidFails >= MAX_RAPID_RECONNECTS) {\n logErr('giving up reconnect after repeated rapid failures');\n exitCode = code;\n break;\n }\n const next = await reconnectFlow(code);\n if (!next) {\n exitCode = code;\n break;\n }\n const rsNow = process.stdout.rows ?? rows;\n const innerNow = Math.max(1, rsNow - reservedRows());\n pty = backend.ptySpawn(next.command, next.argv, {\n name: 'xterm-256color',\n cols: process.stdout.columns ?? cols,\n rows: innerNow,\n env: next.env ? { ...process.env, ...next.env } : process.env,\n });\n wireOutput();\n lastSpawnAt = Date.now();\n // The checkpoint that caused this drop is consumed — clear its tracking so a\n // clean exit in the reconnected session isn't misclassified as another\n // reboot (a fresh checkpoint sets these again via onNotice).\n checkpointNoticeAt = 0;\n checkpointNoticeClearedAt = 0;\n // Re-assert the scroll region (the fresh session repaints into it) and\n // repaint chrome so the footer/band survive the respawn.\n process.stdout.write(`\\x1b[1;${String(innerNow)}r`);\n redrawChrome();\n }\n\n // Teardown order: stop reading stdin, restore cooked mode, drop SSE,\n // dispose the router (rejects any in-flight capture), clear the footer\n // row so the shell prompt below doesn't sit on top of our bar.\n process.stdin.off('data', onStdinData);\n process.stdout.off('resize', onResize);\n clearInterval(statusTimer);\n stopSpinner();\n if (flashTimer) clearTimeout(flashTimer);\n if (process.stdin.isTTY) process.stdin.setRawMode(false);\n process.stdin.pause();\n stream.close();\n router.dispose();\n const rsFinal = process.stdout.rows ?? rows;\n const csFinal = process.stdout.columns ?? cols;\n // Clear the scroll region first so the cursor moves below can reach row N\n // without the terminal trying to keep them inside the smaller region.\n // Then erase every row owned by chrome (band + footer) so a stale band\n // doesn't sit above the next shell prompt; return the cursor afterwards.\n let teardownPaint = '\\x1b[r';\n for (let r = rsFinal - bandReservedRows; r <= rsFinal; r++) {\n if (r >= 1) teardownPaint += cursorMoveTo(r, 1) + '\\x1b[2K';\n }\n teardownPaint += cursorMoveTo(rsFinal, csFinal);\n process.stdout.write(teardownPaint);\n // Restore the host terminal/tab title we saved at attach time.\n popTerminalTitle();\n\n if (exitCode === 0 && opts.detachNotice) {\n // Match the cosmetic of the old attachClaudeSession: overwrite tmux's\n // own `[detached]` line if it's visible, then print the reattach hint.\n process.stdout.write('\\x1b[1A\\x1b[2K\\r' + opts.detachNotice + '\\n');\n }\n return exitCode;\n}\n\n/**\n * Fallback when node-pty is unavailable or stdio isn't a TTY. Identical to\n * today's call: blocking spawnSync with inherited stdio.\n */\nfunction runFallback(command: string, argv: string[], env?: Record<string, string>): number {\n const child = spawnSync(command, argv, {\n stdio: 'inherit',\n env: env ? { ...process.env, ...env } : process.env,\n });\n return child.status ?? 0;\n}\n","import type { Terminal as XtermTerminal } from '@xterm/headless';\n\n/**\n * The `@xterm/headless` `Terminal` class. Injected (not imported) because\n * @xterm/headless is CJS — a static ESM named import breaks Node's loader for\n * the whole CLI, so callers dynamic-import it and pass the ctor through.\n *\n * Used by the dashboard (for its xterm-headless screen-state mirror). The\n * wrapped-pty wrapper does not need it — the inner program writes raw bytes\n * directly to the user's terminal, no parsing required.\n */\nexport type TerminalCtor = new (opts: {\n cols: number;\n rows: number;\n allowProposedApi: boolean;\n scrollback: number;\n convertEol: boolean;\n}) => XtermTerminal;\n\n/**\n * Minimal shape of a node-pty IPty (avoids a hard type dep on the optional\n * module — node-pty is in optionalDependencies, may not be installed).\n */\nexport interface IPtyLike {\n onData(cb: (d: string) => void): void;\n onExit(cb: (e: { exitCode: number }) => void): void;\n write(d: string): void;\n resize(cols: number, rows: number): void;\n kill(): void;\n}\n\nexport type PtySpawn = (\n file: string,\n args: string[],\n opts: { name: string; cols: number; rows: number; env: NodeJS.ProcessEnv },\n) => IPtyLike;\n\nexport interface PtyBackend {\n ptySpawn: PtySpawn;\n /** Present for callers that also need the xterm headless ctor (dashboard). */\n termCtor: TerminalCtor;\n}\n\n/**\n * Dynamic-load the optional pty + xterm/headless backends. Returns null\n * when either prebuild is missing (we don't throw — callers decide how to\n * degrade). Centralized here so the dashboard and the wrapped-pty wrapper\n * use the same exact load dance.\n */\nexport async function loadPtyBackend(): Promise<PtyBackend | null> {\n try {\n const ptyMod = (await import('@homebridge/node-pty-prebuilt-multiarch')) as Record<\n string,\n unknown\n >;\n const xtermMod = (await import('@xterm/headless')) as Record<string, unknown>;\n const spawn =\n (ptyMod['spawn'] as unknown) ??\n (ptyMod['default'] as Record<string, unknown> | undefined)?.['spawn'];\n const Terminal =\n (xtermMod['Terminal'] as unknown) ??\n (xtermMod['default'] as Record<string, unknown> | undefined)?.['Terminal'];\n if (typeof spawn !== 'function' || typeof Terminal !== 'function') {\n return null;\n }\n return {\n ptySpawn: spawn as unknown as PtySpawn,\n termCtor: Terminal as unknown as TerminalCtor,\n };\n } catch {\n return null;\n }\n}\n","import { spawn } from 'node:child_process';\nimport type { AttachOpenIn } from '@agentbox/config';\n\nexport type HostTerminal = 'tmux' | 'iterm2' | 'unknown';\n\n/**\n * Identify the user's host terminal from env vars. tmux wins over iTerm2 even\n * when nested — when `TMUX` is set, the tmux CLI is the right primitive (it can\n * split the current pane / open a new window without going through AppleScript).\n *\n * tmux is recognized on every host (macOS + Linux) — its CLI is the portable\n * primitive. The iTerm2 path is macOS-only (it drives AppleScript). On Linux we\n * deliberately don't recognize native emulators (gnome-terminal / alacritty /\n * konsole) yet: outside tmux the caller falls back to attaching in the current\n * terminal. See docs/linux-host-backlog.md.\n */\nexport function detectHostTerminal(env: NodeJS.ProcessEnv = process.env): HostTerminal {\n const tmux = env['TMUX'];\n if (tmux && tmux.length > 0) return 'tmux';\n const termProgram = env['TERM_PROGRAM'];\n if (termProgram === 'iTerm.app') return 'iterm2';\n return 'unknown';\n}\n\n/** Single-quote a string so it survives a shell parse intact. */\nfunction shellQuote(s: string): string {\n if (s.length === 0) return \"''\";\n // Replace any internal `'` with the four-byte sequence `'\\''` (close, escaped\n // quote, reopen). Cheaper than picking double-quotes — no $/`/\\ to worry about.\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n/** Escape a string for embedding in a double-quoted AppleScript literal. */\nfunction appleScriptEscape(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\n/** Join an argv into a single shell-safe command line. */\nfunction shellJoin(argv: string[]): string {\n return argv.map(shellQuote).join(' ');\n}\n\nexport interface SpawnInNewTerminalArgs {\n host: Exclude<HostTerminal, 'unknown'>;\n /** Where to open the session in that host's terminology. `'same'` is rejected\n * by the caller — we never produce `same` here. */\n mode: Exclude<AttachOpenIn, 'same'>;\n /** Full argv to run in the new pane: `[program, ...args]`. The first element\n * is the binary; the rest are passed verbatim. */\n argv: string[];\n /** Working directory for the new pane. Passed to tmux via `-c` and prepended\n * to the iTerm2 command as `cd <cwd> && exec …`. */\n cwd: string;\n /** Short title for the new tmux window / iTerm2 tab when applicable. */\n title: string;\n}\n\nexport interface SpawnInNewTerminalResult {\n launched: boolean;\n /** One-line user-facing message printed to the host's stdout on success.\n * Empty string when `launched` is false. */\n note: string;\n /** stderr captured from the spawner, when `launched` is false. Used only for\n * the command log; not surfaced to the user. */\n error?: string;\n}\n\n/**\n * Open a fresh tmux pane / iTerm2 split-tab-window and run `<command> <argv...>`\n * there. Returns synchronously after the new pane is requested — the inner\n * command runs in its own terminal and is no longer this process's child.\n *\n * On failure (tmux/osascript exits non-zero, or wasn't found), the caller is\n * expected to fall back to inline attach.\n */\nexport async function spawnInNewTerminal(\n args: SpawnInNewTerminalArgs,\n): Promise<SpawnInNewTerminalResult> {\n if (args.host === 'tmux') return spawnInTmux(args);\n return spawnInITerm2(args);\n}\n\nasync function spawnInTmux(args: SpawnInNewTerminalArgs): Promise<SpawnInNewTerminalResult> {\n // `-c <cwd>` drops the new pane in the host pane's directory so the\n // recursive `agentbox` invocation can resolve project-scoped refs (and so\n // any commands the user runs after detaching start somewhere sensible).\n // The command is passed as a single shell-quoted positional after `--`;\n // tmux hands it to /bin/sh -c, which is why each argv element needs\n // single-quoting.\n const cmdStr = shellJoin(args.argv);\n let tmuxArgv: string[];\n let noteKind: string;\n if (args.mode === 'split') {\n tmuxArgv = ['split-window', '-h', '-c', args.cwd, '--', cmdStr];\n noteKind = 'tmux split';\n } else {\n // `window` and `tab` both map to tmux's only \"another full screen\" primitive.\n tmuxArgv = ['new-window', '-n', args.title, '-c', args.cwd, '--', cmdStr];\n noteKind = 'tmux window';\n }\n const r = await runQuiet('tmux', tmuxArgv);\n if (r.code !== 0) {\n return {\n launched: false,\n note: '',\n error: `tmux ${tmuxArgv.join(' ')} exited ${String(r.code)}: ${r.stderr.trim()}`,\n };\n }\n return {\n launched: true,\n note: `Attached in new ${noteKind}.`,\n };\n}\n\nasync function spawnInITerm2(args: SpawnInNewTerminalArgs): Promise<SpawnInNewTerminalResult> {\n // iTerm2 launches `command` through a shell, but doesn't honor a starting\n // directory parameter on its AppleScript verbs. Prepend `cd <cwd> && exec`\n // so the new tab/window/split lands in the host pane's cwd and replaces\n // the launching shell with the agentbox process.\n const inner = shellJoin(args.argv);\n const cmdLine = `cd ${shellQuote(args.cwd)} && exec ${inner}`;\n const cmdLit = `\"${appleScriptEscape(cmdLine)}\"`;\n\n // Always create the tab/window/split first, then `write text` into its\n // session. The `... with default profile command \"<cmd>\"` parameter form is\n // unreliable on iTerm 3.7 betas — it fails (returns `missing value`) and the\n // command bounces to Terminal.app instead of running in iTerm. The\n // create-then-write-text form is the supported path and works across\n // versions, so every mode uses it.\n let lines: string[];\n let noteKind: string;\n switch (args.mode) {\n case 'split':\n lines = [\n 'tell application \"iTerm\"',\n ' tell current session of current window to set _s to (split vertically with default profile)',\n ` tell _s to write text ${cmdLit}`,\n 'end tell',\n ];\n noteKind = 'iTerm2 split';\n break;\n case 'tab':\n lines = [\n 'tell application \"iTerm\"',\n ' tell current window to set _t to (create tab with default profile)',\n ` tell current session of _t to write text ${cmdLit}`,\n 'end tell',\n ];\n noteKind = 'iTerm2 tab';\n break;\n case 'window':\n lines = [\n 'tell application \"iTerm\"',\n ' set _w to (create window with default profile)',\n ` tell current session of _w to write text ${cmdLit}`,\n 'end tell',\n ];\n noteKind = 'iTerm2 window';\n break;\n }\n\n const r = await runQuiet('osascript', ['-e', lines.join('\\n')]);\n if (r.code !== 0) {\n return {\n launched: false,\n note: '',\n error: `osascript exited ${String(r.code)}: ${r.stderr.trim()}`,\n };\n }\n return {\n launched: true,\n note: `Attached in new ${noteKind}.`,\n };\n}\n\ninterface QuietResult {\n code: number;\n stderr: string;\n}\n\n/** Spawn `cmd argv...`, capture stderr, ignore stdout. Resolves on exit. */\nfunction runQuiet(cmd: string, argv: string[]): Promise<QuietResult> {\n return new Promise((resolve) => {\n const child = spawn(cmd, argv, { stdio: ['ignore', 'ignore', 'pipe'] });\n let stderr = '';\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf8');\n });\n child.on('error', (err) => {\n resolve({ code: 127, stderr: err.message });\n });\n child.on('exit', (code) => {\n resolve({ code: typeof code === 'number' ? code : 1, stderr });\n });\n });\n}\n","const ESC = '\\x1b';\nconst BEL = '\\x07';\n\n/** Replace control chars that would otherwise break the OSC string (the BEL\n * terminator, or any C0 byte) and trim — a stray newline in the agent's\n * session title must not corrupt the host terminal. */\nfunction sanitize(title: string): string {\n return title.replace(/[\\x00-\\x1f\\x7f]/g, ' ').trim();\n}\n\n/**\n * Set the host terminal's title via OSC 0 (`ESC ] 0 ; <title> BEL`), which sets\n * both the window and icon title — the same sequence Claude Code emits. Guarded\n * by `isTTY` so piped / redirected output stays clean.\n */\nexport function setTerminalTitle(\n title: string,\n stream: NodeJS.WriteStream = process.stdout,\n): void {\n if (!stream.isTTY) return;\n stream.write(`${ESC}]0;${sanitize(title)}${BEL}`);\n}\n\n/**\n * Push the terminal's current title onto its title stack (XTPUSHTITLE,\n * `CSI 22 ; 2 t`). Pair with {@link popTerminalTitle} on exit so the user's\n * original tab title is restored. Terminals without title-stack support ignore\n * the unknown CSI.\n */\nexport function pushTerminalTitle(stream: NodeJS.WriteStream = process.stdout): void {\n if (!stream.isTTY) return;\n stream.write(`${ESC}[22;2t`);\n}\n\n/** Pop the title saved by {@link pushTerminalTitle} (XTPOPTITLE, `CSI 23 ; 2 t`). */\nexport function popTerminalTitle(stream: NodeJS.WriteStream = process.stdout): void {\n if (!stream.isTTY) return;\n stream.write(`${ESC}[23;2t`);\n}\n","import type { PromptAnswerBody, PromptAskEvent } from '@agentbox/relay';\n\n/**\n * Steady-state input forwarder + active-prompt capture + Ctrl+a leader.\n *\n * In steady state every byte goes to the pty unmodified — *unless* a\n * `leaderChords` map is supplied, in which case `Ctrl+a` (0x01) opens the\n * actions menu (leader-only: a literal Ctrl+a needs a double-press).\n *\n * Only when `capture()` is awaiting does the router intercept the next\n * keystroke and resolve the prompt with a y/n/cancel answer. Anything else\n * the user types while a prompt is active is dropped (not forwarded) — the\n * inner program doesn't see partial keys.\n */\nexport interface InputRouter {\n /** True while a prompt is being captured. Used by the run loop to know\n * whether to redraw the footer eagerly. */\n readonly capturing: boolean;\n /** Feed raw bytes from process.stdin. Forwards or captures internally. */\n feed(buf: Buffer): void;\n /** Activate prompt capture. Resolves with the answer body. Subsequent\n * capture() calls before resolution overwrite the previous prompt (the\n * newer one wins — relay broadcast order is canonical). */\n capture(p: PromptAskEvent): Promise<PromptAnswerBody>;\n /** Reject the in-flight capture (pty exit, sibling-wrapper answered). */\n abort(reason: 'pty-exit' | 'resolved-elsewhere'): void;\n dispose(): void;\n}\n\ninterface ActivePrompt {\n ev: PromptAskEvent;\n resolve: (b: PromptAnswerBody) => void;\n reject: (e: Error) => void;\n}\n\n/** Actions reachable from the Ctrl+a leader menu. */\nexport type LeaderAction = 'screen' | 'code' | 'url' | 'detach';\n\nconst KEY_ENTER = 0x0d;\nconst KEY_LF = 0x0a;\nconst KEY_ESC = 0x1b;\nconst KEY_CTRL_C = 0x03;\nconst KEY_Y_LOW = 0x79;\nconst KEY_Y_UP = 0x59;\nconst KEY_N_LOW = 0x6e;\nconst KEY_N_UP = 0x4e;\nconst KEY_LEADER = 0x01; // Ctrl-a\nconst KEY_CTRL_V = 0x16; // Ctrl-v — Claude Code's \"paste image from clipboard\"\nconst KEY_A_LOW = 0x61; // 'a'\nconst KEY_V_LOW = 0x76; // 'v'\n\n/**\n * A key decoded from an enhanced-keyboard escape sequence. TUIs like Claude Code\n * can switch the terminal into the kitty keyboard protocol or xterm\n * modifyOtherKeys, in which `Ctrl+a` and even plain letters arrive not as raw\n * bytes but as escape sequences. The leader must decode those so the Actions\n * footer keeps working under those modes.\n */\ninterface CsiKey {\n /** Total byte length of the sequence (so the caller can advance past it). */\n len: number;\n /** Base unicode codepoint of the key (e.g. 97 for 'a'). */\n code: number;\n /** Whether Ctrl was held. */\n ctrl: boolean;\n}\n\n/**\n * Parse a kitty keyboard (`ESC [ <code> ; <mods> u`) or xterm modifyOtherKeys\n * (`ESC [ 27 ; <mods> ; <code> ~`) key sequence at `buf[i]`. Returns the base\n * keycode + Ctrl flag, or null when `buf[i]` isn't one of those (a plain byte,\n * an arrow/function/mouse sequence, or an incomplete sequence split across\n * reads — all of which just forward unchanged). Precise on purpose: only the\n * exact `…u` / `CSI 27 … ~` key shapes match, so cursor/mouse CSI sequences\n * (`ESC [ A`, `ESC [ < … M`) fall through.\n */\nfunction parseCsiKey(buf: Buffer, i: number): CsiKey | null {\n if (buf[i] !== KEY_ESC || buf[i + 1] !== 0x5b /* [ */) return null;\n const params: number[] = [];\n let val = -1;\n for (let j = i + 2; j < buf.length; j++) {\n const b = buf[j];\n if (b !== undefined && b >= 0x30 && b <= 0x39) {\n val = (val < 0 ? 0 : val) * 10 + (b - 0x30);\n continue;\n }\n if (b === 0x3b /* ; */) {\n params.push(val);\n val = -1;\n continue;\n }\n if (b === 0x3a /* : */) {\n // Sub-parameters (kitty event types / alternate keys): record the param,\n // then skip to the next ';' or final byte.\n params.push(val);\n val = -1;\n while (\n j + 1 < buf.length &&\n buf[j + 1] !== 0x3b &&\n buf[j + 1] !== 0x75 &&\n buf[j + 1] !== 0x7e\n ) {\n j++;\n }\n continue;\n }\n if (b === 0x75 /* u */ || b === 0x7e /* ~ */) {\n if (val >= 0) params.push(val);\n const len = j - i + 1;\n const modsToCtrl = (m: number): boolean => ((m - 1) & 4) !== 0;\n if (b === 0x75) {\n // kitty: CSI <code> ; <mods> u\n const code = params[0];\n if (code === undefined || code < 0) return null;\n return { len, code, ctrl: modsToCtrl(params[1] ?? 1) };\n }\n // modifyOtherKeys: CSI 27 ; <mods> ; <code> ~ (other '~' seqs aren't keys)\n if (params[0] !== 27) return null;\n const code = params[2];\n if (code === undefined || code < 0) return null;\n return { len, code, ctrl: modsToCtrl(params[1] ?? 1) };\n }\n return null; // any other byte → not a CSI-u / modifyOtherKeys key\n }\n return null; // incomplete (split across reads) — forward as-is\n}\n\nconst DEFAULT_LEADER_TIMEOUT_MS = 2000;\n\nexport interface InputRouterOptions {\n onForward: (b: Buffer) => void;\n /** Called when a prompt's capture is resolved — the run loop POSTs the answer. */\n onAnswer: (body: PromptAnswerBody) => void;\n /** Ctrl+a leader chord map: a single lowercase character → action. When\n * omitted or empty the leader is disabled and `Ctrl+a` forwards verbatim. */\n leaderChords?: Readonly<Record<string, LeaderAction>>;\n /** Fired when the leader menu opens (true) / closes (false). */\n onLeaderChange?: (active: boolean) => void;\n /** Fired when a recognized chord key resolves the leader. */\n onAction?: (name: LeaderAction) => void;\n /**\n * When set, a lone `Ctrl+V` (0x16) in steady state is intercepted instead of\n * forwarded: the router awaits this hook (which loads the host clipboard\n * image into the box), then re-emits the `Ctrl+V` so the inner program's own\n * paste handler reads the now-populated box clipboard. Presses while one is\n * in flight are dropped (debounced). When omitted, `Ctrl+V` forwards\n * verbatim. Used for claude image paste; other modes don't pass it. */\n onPasteImage?: () => Promise<unknown>;\n /** ms the leader menu stays open with no key before auto-closing (default 2000). */\n leaderTimeoutMs?: number;\n /** Injected for unit tests; defaults to global timers. */\n setTimer?: (ms: number, fn: () => void) => unknown;\n clearTimer?: (h: unknown) => void;\n}\n\nexport function createInputRouter(opts: InputRouterOptions): InputRouter {\n let active: ActivePrompt | null = null;\n let disposed = false;\n\n const leaderChords = opts.leaderChords ?? {};\n const leaderEnabled = Object.keys(leaderChords).length > 0;\n const onPasteImage = opts.onPasteImage;\n const pasteEnabled = typeof onPasteImage === 'function';\n let pasteInFlight = false;\n const leaderTimeoutMs = opts.leaderTimeoutMs ?? DEFAULT_LEADER_TIMEOUT_MS;\n const setTimer = opts.setTimer ?? ((ms, fn) => setTimeout(fn, ms) as unknown);\n const clearTimer =\n opts.clearTimer ?? ((h) => clearTimeout(h as ReturnType<typeof setTimeout>));\n let leader = false;\n let leaderTimer: unknown = null;\n\n const disarmLeader = (): void => {\n if (leaderTimer != null) {\n clearTimer(leaderTimer);\n leaderTimer = null;\n }\n };\n\n const exitLeader = (): void => {\n if (!leader) return;\n leader = false;\n disarmLeader();\n opts.onLeaderChange?.(false);\n };\n\n const enterLeader = (): void => {\n leader = true;\n disarmLeader();\n // Leader-only: a lone Ctrl+a just times the menu out — it is never\n // auto-forwarded. A literal Ctrl+a is sent via a double-press.\n leaderTimer = setTimer(leaderTimeoutMs, () => {\n leaderTimer = null;\n exitLeader();\n });\n opts.onLeaderChange?.(true);\n };\n\n // The leader is open and `b` is the chord byte that resolves it.\n const resolveLeaderByte = (b: number): void => {\n if (b === KEY_LEADER) {\n // Double Ctrl+a → one literal Ctrl+a to the inner program.\n exitLeader();\n opts.onForward(Buffer.from([KEY_LEADER]));\n return;\n }\n if (b === KEY_ESC) {\n // Esc dismisses the menu; nothing forwarded.\n exitLeader();\n return;\n }\n const action = leaderChords[String.fromCharCode(b).toLowerCase()];\n if (action) {\n exitLeader();\n opts.onAction?.(action);\n return;\n }\n // Unrecognized chord: close the menu, forward the key so typing isn't lost.\n exitLeader();\n opts.onForward(Buffer.from([b]));\n };\n\n const settle = (\n answer: PromptAnswerBody['answer'],\n cancelled?: boolean,\n ): void => {\n if (!active) return;\n const body: PromptAnswerBody = {\n id: active.ev.id,\n answer,\n ...(cancelled ? { cancelled: true } : {}),\n };\n const p = active;\n active = null;\n p.resolve(body);\n opts.onAnswer(body);\n };\n\n const handleCapturedByte = (b: number): void => {\n if (!active) return;\n if (b === KEY_Y_LOW || b === KEY_Y_UP) {\n settle('y');\n return;\n }\n if (b === KEY_N_LOW || b === KEY_N_UP) {\n settle('n');\n return;\n }\n if (b === KEY_ESC || b === KEY_CTRL_C) {\n settle('n', true);\n return;\n }\n if (b === KEY_ENTER || b === KEY_LF) {\n // Enter accepts the default answer.\n const def = active.ev.defaultAnswer ?? 'n';\n settle(def);\n return;\n }\n // Anything else: ignored (not forwarded, not consumed).\n };\n\n // Intercepted Ctrl+V: run the host→box image-paste hook, then re-emit the\n // original keypress so the inner program reads the (now-loaded) box clipboard.\n // `reemit` is the exact byte sequence we swallowed — a raw 0x16, or the CSI-u /\n // modifyOtherKeys encoding when an enhanced keyboard protocol is active — so\n // the inner program (Claude) sees the encoding it negotiated. A press while one\n // is in flight is dropped — the Ctrl+V was already swallowed by the caller, so\n // there's nothing to forward.\n const triggerPaste = (reemit: Buffer): void => {\n if (pasteInFlight) return;\n pasteInFlight = true;\n const done = (): void => {\n pasteInFlight = false;\n if (!disposed) opts.onForward(reemit);\n };\n void Promise.resolve()\n .then(() => onPasteImage?.())\n .then(done, done);\n };\n\n // Leader-aware steady-state forwarding: scan bytes, batching plain runs into\n // a single onForward call, and intercept `Ctrl+a` chords + `Ctrl+V` paste.\n const feedSteady = (buf: Buffer): void => {\n let chunkStart = 0;\n const flushChunk = (end: number): void => {\n if (end > chunkStart) opts.onForward(buf.subarray(chunkStart, end));\n chunkStart = end;\n };\n let i = 0;\n while (i < buf.length) {\n const byte = buf[i];\n if (byte === undefined) {\n i++;\n continue;\n }\n\n if (leader) {\n // Resolve the chord. The key may be a raw byte, or — when the inner app\n // enabled an enhanced keyboard protocol — a CSI-u / modifyOtherKeys\n // sequence (e.g. 'c' as `ESC [ 99 u`).\n const k = parseCsiKey(buf, i);\n if (k) {\n if (k.ctrl && k.code === KEY_A_LOW) {\n // Double Ctrl+a → one literal Ctrl+a to the inner program.\n exitLeader();\n opts.onForward(Buffer.from([KEY_LEADER]));\n } else {\n const action = leaderChords[String.fromCharCode(k.code).toLowerCase()];\n exitLeader();\n if (action) opts.onAction?.(action);\n else opts.onForward(buf.subarray(i, i + k.len)); // unknown: don't lose it\n }\n i += k.len;\n chunkStart = i;\n continue;\n }\n resolveLeaderByte(byte);\n i += 1;\n chunkStart = i;\n continue;\n }\n\n if (leaderEnabled && byte === KEY_LEADER) {\n flushChunk(i); // forward everything typed before the Ctrl+a\n enterLeader();\n i += 1;\n chunkStart = i;\n continue;\n }\n // Ctrl+a re-encoded by an enhanced keyboard protocol (kitty / modifyOtherKeys).\n if (leaderEnabled && byte === KEY_ESC) {\n const k = parseCsiKey(buf, i);\n if (k && k.ctrl && k.code === KEY_A_LOW) {\n flushChunk(i);\n enterLeader();\n i += k.len;\n chunkStart = i;\n continue;\n }\n }\n // Ctrl+V re-encoded by an enhanced keyboard protocol (kitty / modifyOtherKeys).\n // The inner app (Claude) can flip this on, in which case the host terminal\n // sends `ESC [ 118 ; 5 u` instead of a raw 0x16 — mirror the leader handling\n // above so the paste hook still fires.\n if (pasteEnabled && byte === KEY_ESC) {\n const k = parseCsiKey(buf, i);\n if (k && k.ctrl && k.code === KEY_V_LOW) {\n flushChunk(i);\n const seq = Buffer.from(buf.subarray(i, i + k.len));\n i += k.len;\n chunkStart = i; // swallow it; triggerPaste re-emits after the load\n triggerPaste(seq);\n continue;\n }\n }\n if (pasteEnabled && byte === KEY_CTRL_V) {\n flushChunk(i); // forward everything typed before the Ctrl+V\n i += 1;\n chunkStart = i; // swallow it; triggerPaste re-emits after the load\n triggerPaste(Buffer.from([KEY_CTRL_V]));\n continue;\n }\n i += 1;\n }\n flushChunk(buf.length);\n };\n\n return {\n get capturing(): boolean {\n return active !== null;\n },\n feed(buf: Buffer): void {\n if (disposed) return;\n if (active) {\n // A multi-byte read starting with ESC is a CSI/SS3/OSC escape\n // sequence — mouse click (`\\x1b[<…M/m`), arrow / function key,\n // window-focus event, bracketed-paste markers, etc. Drop the\n // whole chunk: the user pressed something we don't model as a\n // confirmation key, and they'd be (correctly) surprised if a stray\n // mouse click registered as \"deny\". A *real* Esc keypress arrives\n // as a single byte in its own read, which still cancels below.\n if (buf.length > 1 && buf[0] === KEY_ESC) return;\n // Process bytes one at a time so a paste of \"yes\\n\" is handled\n // sanely: the 'y' settles, the rest is dropped — we don't want\n // stray bytes leaking to the pty after the prompt closed mid-buf.\n // (After settle, `active` is null; remaining bytes fall through to\n // forward path below.)\n for (let i = 0; i < buf.length; i++) {\n const byte = buf[i];\n if (byte === undefined) continue;\n if (active) {\n handleCapturedByte(byte);\n } else {\n // Active became null mid-buffer (settled). Forward the rest as\n // a normal keystroke chunk.\n opts.onForward(buf.subarray(i));\n return;\n }\n }\n return;\n }\n if (!leaderEnabled && !pasteEnabled) {\n opts.onForward(buf);\n return;\n }\n feedSteady(buf);\n },\n capture(ev: PromptAskEvent): Promise<PromptAnswerBody> {\n return new Promise<PromptAnswerBody>((resolve, reject) => {\n // A relay prompt outranks the actions menu — close the leader first.\n if (leader) exitLeader();\n if (active) {\n // A new prompt arrived before the old one was answered — abort\n // the old one (treated as cancelled) and switch to the new one.\n // The relay already broadcast `prompt-ask` for both; we owe the\n // first an answer or it'll stay pending forever.\n settle('n', true);\n }\n active = { ev, resolve, reject };\n });\n },\n abort(reason): void {\n if (!active) return;\n const p = active;\n active = null;\n const msg = reason === 'pty-exit' ? 'pty exited' : 'resolved by sibling wrapper';\n p.reject(new Error(msg));\n },\n dispose(): void {\n if (disposed) return;\n disposed = true;\n disarmLeader();\n if (active) {\n const p = active;\n active = null;\n p.reject(new Error('input router disposed'));\n }\n },\n };\n}\n","import type { ClaudeQuestionPayload } from '@agentbox/ctl';\n\nexport interface SidebarBox {\n id: string;\n name: string;\n /** Container state: 'running' | 'paused' | 'stopped' | 'missing' | … */\n state: string;\n /** Activity of the agent this box runs (claude / codex) — 'working' | 'idle'\n * | 'waiting' | 'unknown' | undefined. Resolved from whichever agent is\n * active (see `resolveAgent` in commands/dashboard.ts). */\n activity?: string;\n /** The in-box terminal/session title the active agent set, or undefined. */\n sessionTitle?: string;\n /** 1-based per-project box number, shown as `[N]`; undefined for\n * pre-feature boxes and the synthetic \"+ New box\" entry. */\n index?: number;\n /** Absolute project root; used to group boxes under a project header.\n * Undefined for pre-feature boxes and the synthetic \"+ New box\" entry. */\n project?: string;\n /** This box has an unanswered relay `prompt-ask` event (e.g. agentbox-ctl\n * git push / cp / download waiting for user confirmation). The compositor\n * injects this flag from its in-memory map of active prompts. Overrides\n * the activity cell — `▲ prompt` reads more urgent than `● working`. */\n pendingPrompt?: boolean;\n /** This box has an active relay notice (currently: a checkpoint is being\n * captured, freezing the box). Injected by the compositor; shown as\n * `◆ checkpoint` in the activity cell. Outranked by `pendingPrompt`. */\n checkpointing?: boolean;\n /** Last `AskUserQuestion` payload from the box's claude status; populated\n * only while `activity === 'question'`. The compositor renders it into the\n * alert band above the footer for the selected box. */\n claudeQuestion?: ClaudeQuestionPayload;\n}\n\n/** Per-row ownership + styling map returned alongside the rendered lines so\n * the compositor can highlight the selected box and style headers without\n * re-deriving the (now non-uniform) layout. */\nexport interface SidebarRender {\n lines: string[];\n /** boxId rendered on row `i`, else null (banner / group header / blank). */\n rowOwner: (string | null)[];\n /** true for the banner and project-header rows (styled like the banner). */\n headerRows: boolean[];\n}\n\n/** Truncate to `max` printable chars, appending `…` when it had to cut\n * (keeps the head). */\nfunction ellipsize(s: string, max: number): string {\n if (max <= 0) return '';\n if (s.length <= max) return s;\n if (max === 1) return '…';\n return s.slice(0, max - 1) + '…';\n}\n\n/** Truncate keeping the *tail* (the distinguishing part of a box name like\n * `…-78b94c78`), prepending `…` when it had to cut. */\nfunction ellipsizeHead(s: string, max: number): string {\n if (max <= 0) return '';\n if (s.length <= max) return s;\n if (max === 1) return '…';\n return '…' + s.slice(s.length - (max - 1));\n}\n\nexport function activityCell(b: SidebarBox): string {\n // Pending relay prompt outranks every other state — the user needs to\n // act before whatever the box is doing can continue.\n if (b.pendingPrompt) return '▲ prompt';\n // A checkpoint freezes the box; surface it over the activity state.\n if (b.checkpointing) return '◆ checkpoint';\n if (b.state !== 'running') return `[${b.state}]`;\n switch (b.activity) {\n case 'working':\n return '● working';\n case 'idle':\n return '○ idle';\n case 'waiting':\n return '◐ waiting';\n default:\n return '? unknown';\n }\n}\n\n/** Synthetic sidebar entry pinned at the top: selecting it opens the create\n * menu. Carried in the compositor's box list like a real box (sentinel id),\n * so selection/switch/highlight need no special-casing. */\nexport const NEW_BOX_ID = '__agentbox_new__';\nexport const NEW_BOX_LABEL = '+ New box';\n\n/** Sidebar banner label (rendered into the top border). */\nexport const SIDEBAR_HEADER = 'AgentBox';\n\n/** Top border: a flat line on the top + a rounded corner into the right edge\n * only (no left/bottom, to save space): `──── AgentBox ─────…` filling\n * exactly `w`. The left end is a straight line; the matching rounded\n * top-right corner (`╮`) is drawn by the compositor at the sidebar separator\n * column. */\nfunction topBorder(label: string, w: number): string {\n const lead = `──── ${label} `;\n if (lead.length >= w) return lead.slice(0, w);\n return lead + '─'.repeat(w - lead.length);\n}\n/** Lines `sidebarLines` reserves before the box rows (banner + blank). The\n * compositor uses this to locate the selected box row for highlighting. */\nexport const SIDEBAR_HEADER_LINES = 2;\n\nfunction fit(s: string, w: number): string {\n if (s.length === w) return s;\n if (s.length > w) return s.slice(0, w);\n return s + ' '.repeat(w - s.length);\n}\n\n/** `s` centered in a field of `w` columns (truncated if it doesn't fit). */\nfunction center(s: string, w: number): string {\n if (s.length >= w) return s.slice(0, w);\n const pad = w - s.length;\n const leftPad = Math.floor(pad / 2);\n return ' '.repeat(leftPad) + s + ' '.repeat(pad - leftPad);\n}\n\n/** `basename` of an absolute project root, for the group header label. */\nfunction projectLabel(project: string | undefined): string {\n if (!project) return '(no project)';\n const parts = project.split('/').filter(Boolean);\n return parts[parts.length - 1] ?? project;\n}\n\n/** Strip the leading decoration Claude prepends to its terminal title (the\n * spinner glyph, e.g. `✳ `) plus any leading symbols/asterisks/space, so the\n * sidebar shows just the words. Falls back to the trimmed original if the\n * title is all decoration. */\nexport function stripTitleGlyph(s: string): string {\n const t = s.replace(/^[\\s\\p{S}*·]+/u, '');\n return t.length > 0 ? t : s.trim();\n}\n\n/**\n * Render one box row: `marker<num> <title|name> <status>`. The number and\n * the status are width-protected; the middle (title, else the box name with\n * its meaningful tail kept) flexes and ellipsizes so the status is never\n * eaten. Compact: no brackets, no glyph, single-char marker.\n */\nfunction boxRow(b: SidebarBox, marker: string, w: number): string {\n const numStr = b.index != null ? `${b.index} ` : '';\n const status = activityCell(b);\n const left = `${marker}${numStr}`;\n // -2: 1 gap before status, 1 margin after so the label doesn't touch the\n // sidebar's right border.\n const room = w - left.length - status.length - 2;\n if (room <= 0) return fit(`${left}${status}`, w);\n const middle =\n b.state === 'running' && b.sessionTitle\n ? ellipsize(stripTitleGlyph(b.sessionTitle), room)\n : ellipsizeHead(b.name, room);\n // Left segment padded so the status sits one column in from the right edge,\n // with a trailing space as the right margin.\n return fit(`${left}${middle}`, w - status.length - 1) + status + ' ';\n}\n\n/**\n * The sidebar region as exactly `h` lines, each exactly `w` columns, plus a\n * per-row ownership/style map. Pure — no ANSI positioning (the compositor\n * places it). Boxes are grouped under a ` ── <project> ── ` header (callers\n * pass them pre-sorted by project).\n */\nexport function sidebarLines(\n boxes: SidebarBox[],\n selectedId: string,\n w: number,\n h: number,\n): SidebarRender {\n const lines: string[] = [topBorder(SIDEBAR_HEADER, w), fit('', w)];\n const rowOwner: (string | null)[] = [null, null];\n const headerRows: boolean[] = [true, false];\n const push = (line: string, owner: string | null, header: boolean): void => {\n lines.push(fit(line, w));\n rowOwner.push(owner);\n headerRows.push(header);\n };\n\n let prevProject: string | undefined;\n let seenGroup = false;\n for (const b of boxes) {\n const marker = b.id === selectedId ? '▸' : ' ';\n if (b.id === NEW_BOX_ID) {\n push(`${marker}${NEW_BOX_LABEL}`, b.id, false);\n continue;\n }\n if (!seenGroup || b.project !== prevProject) {\n push(center(` ── ${projectLabel(b.project)} ── `, w), null, true);\n prevProject = b.project;\n seenGroup = true;\n }\n push(boxRow(b, marker, w), b.id, false);\n }\n if (boxes.length === 0) push(' (no boxes)', null, false);\n while (lines.length < h) push('', null, false);\n return {\n lines: lines.slice(0, h),\n rowOwner: rowOwner.slice(0, h),\n headerRows: headerRows.slice(0, h),\n };\n}\n\n/**\n * Centered action menu for a running box with no Claude session.\n * Exactly `h` lines, each exactly `w` columns. Pure.\n */\nexport function menuLines(boxName: string, w: number, h: number): string[] {\n const body = [\n '',\n ` No agent session in ${boxName}.`,\n '',\n ' [c] Start Claude',\n ' [x] Start Codex',\n ' [o] Start OpenCode',\n ' [s] Open a shell',\n '',\n ' Ctrl+Option+↑/↓ switch · Ctrl-a then c/s/u/q (code/screen/url/quit)',\n ];\n const top = Math.max(0, Math.floor((h - body.length) / 2));\n const out: string[] = [];\n for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? '', w));\n return out;\n}\n\n/**\n * Centered action menu for a non-running box (paused/stopped): resume +\n * destroy, with a two-step destroy confirm (the TUI can't show a prompt).\n * Exactly `h` lines, each exactly `w` columns. Pure.\n */\nexport function lifecycleMenuLines(\n boxName: string,\n state: 'paused' | 'stopped',\n confirmDestroy: boolean,\n w: number,\n h: number,\n): string[] {\n const body = confirmDestroy\n ? [\n '',\n ` Destroy ${boxName}?`,\n ' This removes the container and its volumes.',\n '',\n ' [y] Yes, destroy',\n ' [any other key] Cancel',\n ]\n : [\n '',\n ` Box ${boxName} is ${state}.`,\n '',\n state === 'paused' ? ' [u] Unpause' : ' [s] Start',\n ' [d] Destroy',\n '',\n ' Ctrl+Option+↑/↓ switch · Ctrl-a then q quit',\n ];\n const top = Math.max(0, Math.floor((h - body.length) / 2));\n const out: string[] = [];\n for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? '', w));\n return out;\n}\n\n/**\n * Centered menu for the synthetic \"+ New box\" entry. Exactly `h` lines, each\n * exactly `w` columns. Pure.\n */\nexport function createMenuLines(where: string, w: number, h: number): string[] {\n const body = [\n '',\n ' Create a new box',\n '',\n ' [c] Create + launch Claude',\n ' [x] Create + launch Codex',\n ' [o] Create + launch OpenCode',\n ' [n] Create only',\n '',\n ` in ${where}`,\n '',\n ' Ctrl+Option+↑/↓ switch · Ctrl-a then q quit',\n ];\n const top = Math.max(0, Math.floor((h - body.length) / 2));\n const out: string[] = [];\n for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? '', w));\n return out;\n}\n\n// Status-bar palette — matches the in-box tmux footer\n// (`buildTmuxSessionArgs`): dark bar, blue brand block, dim-grey hints\n// with white key chords.\n/** The footer/sidebar background gray. Truecolor (not palette index 236) so\n * it pins an exact RGB — terminals can remap/shade indexed colors per\n * context, which made the sidebar and status bar look like different grays.\n * Single source so the two regions can't drift. */\nexport const BAR_BG = '\\x1b[48;2;48;48;48m';\nconst BAR_BASE = BAR_BG + '\\x1b[38;5;250m';\nconst BAR_BRAND = '\\x1b[48;5;39m\\x1b[38;5;16m'; // blue block (not bold)\nconst BRAND_BOLD = '\\x1b[1m'; // box name only\nconst BRAND_NOBOLD = '\\x1b[22m';\nconst HINT_KEY = '\\x1b[38;5;255m'; // white: the key chord\nconst HINT_TXT = '\\x1b[38;5;245m'; // gray: labels + separators\nconst BAR_RESET = '\\x1b[0m';\n\n// [key chord, label]. Modifiers spelled out (no ⌥/^ glyphs); arrows use the\n// ↑/↓ glyphs. Rendered as `KEYS: label` with the chord white, label gray.\nconst SWITCH_HINT: readonly [string, string] = ['Control+Option+↑/↓', 'switch'];\nconst HINT_GROUPS: ReadonlyArray<readonly [string, string]> = [\n SWITCH_HINT,\n ['Control+a c', 'code'],\n ['Control+a s', 'screen'],\n ['Control+a u', 'url'],\n ['Control+a q', 'quit'],\n];\n\n/** Minimal hint tier when the bar is too narrow for the full `HINT_GROUPS`:\n * box switching (always important) + the leader. Pressing `Ctrl-a` then\n * expands to `ADVANCED_HINT_GROUPS` (the compositor swaps while the leader is\n * active). */\nexport const COLLAPSED_HINT_GROUPS: ReadonlyArray<readonly [string, string]> = [\n SWITCH_HINT,\n ['Control+a', 'more'],\n];\n\n/** The expanded \"which-key\" chord menu shown while the Ctrl-a leader is\n * pending — every chord, compact (`KEY: label`), reverts on the next key. */\nexport const ADVANCED_HINT_GROUPS: ReadonlyArray<readonly [string, string]> = [\n ['c', 'code'],\n ['s', 'screen'],\n ['u', 'url'],\n ['t', 'stop'],\n ['p', 'pause'],\n ['d', 'destroy'],\n ['q', 'quit'],\n];\n\n/**\n * Status line, exactly `w` printable columns, colored to match the in-box tmux\n * footer (dark bar, blue ` agentbox ▸ … ` brand block on the left, dim-grey\n * shortcut hints on the right). `stateLabel` overrides the box's activity text\n * (used for `shell` / `menu` panes where the box `activity` would otherwise\n * show a misleading `unknown`). `fallbackGroups`, when given, is the narrow-bar tier\n * tried before brand-core-only — used to keep one essential chord pinned\n * (instead of the default dashboard `COLLAPSED_HINT_GROUPS`).\n */\nexport function statusLine(\n box: SidebarBox | undefined,\n w: number,\n stateLabel?: string,\n groups: ReadonlyArray<readonly [string, string]> = HINT_GROUPS,\n fallbackGroups?: ReadonlyArray<readonly [string, string]>,\n): string {\n const state =\n stateLabel ?? (box ? (box.state === 'running' ? (box.activity ?? 'unknown') : box.state) : '');\n // \"agentbox ▸ \" stays normal weight; only the box name + state are bold.\n const brandPrefix = box ? ' agentbox ▸ ' : ' agentbox ';\n // Brand *core* (no title) — the width-protected segment. The title is the\n // lowest-priority segment: it only fills space left after brand + hints.\n const base = box ? `${box.name} (${state})` : '';\n const coreMain = box ? `${base} ` : '';\n const corePlain = brandPrefix + coreMain;\n\n const SEP = ' │ ';\n const renderHints = (\n g: ReadonlyArray<readonly [string, string]>,\n ): { plain: string; styled: string } => ({\n plain: g.map(([k, l]) => `${k}: ${l}`).join(SEP) + ' ',\n styled:\n g.map(([k, l]) => `${HINT_KEY}${k}${HINT_TXT}: ${l}`).join(`${HINT_TXT}${SEP}`) + ' ',\n });\n\n // Hint tier: shortcuts beat the title. Try the requested groups; if the\n // brand core + those hints overflow, fall back to the narrow-bar tier\n // (`fallbackGroups` if supplied, else the dashboard's minimal leader hint);\n // if even that overflows, render brand-core-only (title can never push the\n // box name off-screen).\n let hints: { plain: string; styled: string } | null = null;\n for (const g of [groups, fallbackGroups ?? COLLAPSED_HINT_GROUPS]) {\n const h = renderHints(g);\n if (corePlain.length + h.plain.length + 1 <= w) {\n hints = h;\n break;\n }\n }\n if (!hints) {\n return BAR_BASE + BAR_BRAND + fit(corePlain, w) + BAR_RESET;\n }\n\n // Title fills only the leftover, ellipsized; dropped entirely when there's\n // no meaningful room (≈ ` — ` + a few chars). Capped at 40 cols as before.\n const room = w - corePlain.length - hints.plain.length - 1;\n let titleSeg = '';\n if (box?.sessionTitle && room >= 7) {\n titleSeg = ` — ${ellipsize(box.sessionTitle, Math.min(40, room - 3))}`;\n }\n\n const leftPlain = brandPrefix + base + titleSeg + (box ? ' ' : '');\n const leftStyled =\n BAR_BRAND + brandPrefix + BRAND_BOLD + base + titleSeg + (box ? ' ' : '') + BRAND_NOBOLD;\n const gap = w - leftPlain.length - hints.plain.length;\n // brand block (name + title bold) → base bar → gap → white/gray hints.\n return (\n BAR_BASE +\n leftStyled +\n BAR_BASE +\n ' '.repeat(gap) +\n hints.styled +\n BAR_RESET\n );\n}\n","import { BAR_BG, statusLine, type SidebarBox } from '../dashboard/sidebar.js';\nimport type { PromptAskEvent } from '@agentbox/relay';\nimport type { ClaudeQuestionPayload } from '@agentbox/ctl';\n\n/**\n * Footer rendering state. `idle` reuses the dashboard's `statusLine` shape\n * (brand chip + box name + optional session title + right-aligned hint);\n * `prompt` is shown while a `prompt-ask` event is being captured; `notice`\n * is an animated informational warning (e.g. checkpoint in progress);\n * `flash` is a transient confirmation after a Ctrl+a action fires.\n */\nexport type FooterState =\n | {\n kind: 'idle';\n boxName: string;\n /** Claude's tmux pane title (from BoxStatus.claude.sessionTitle).\n * Undefined until the first status poll completes (or in shell mode). */\n sessionTitle?: string;\n /** Claude activity hint shown in `(<state>)` after the name. Same field\n * the dashboard sidebar uses (`working` / `idle` / `waiting` / etc.). */\n claudeActivity?: string;\n /** Mode drives the state label: claude shows claude activity, the\n * others show `(shell)` / `(codex)` / `(opencode)`. */\n mode: 'claude' | 'shell' | 'codex' | 'opencode';\n /** Whether the session can be detached (tmux-backed). Drives the\n * expanded leader menu + the pinned `Control+a d: detach` hint. */\n detachable?: boolean;\n /** True while the Ctrl+a leader menu is open — swaps the collapsed\n * `Control+a: Actions` hint for the expanded chord list. */\n leaderActive?: boolean;\n }\n | { kind: 'prompt'; prompt: PromptAskEvent }\n | {\n kind: 'notice';\n /** Warning text, e.g. \"Checkpoint in progress — …\". */\n message: string;\n /** Monotonic counter; the spinner glyph is `SPINNER_FRAMES[frame % len]`. */\n frame: number;\n }\n | {\n kind: 'flash';\n /** Transient confirmation text, e.g. \"Opening noVNC viewer…\". */\n message: string;\n };\n\n/**\n * Spinner cycle for the `notice` footer. Solid half-filled circles, not\n * braille: braille glyphs read as a faint dot cluster on the yellow banner\n * (set vs unset dots are hard to tell apart), so the motion gets lost. The\n * rotating black half of these is unambiguous.\n */\nexport const SPINNER_FRAMES = ['◐', '◓', '◑', '◒'] as const;\n\nconst URGENT = '\\x1b[38;5;220m\\x1b[1m'; // bright yellow + bold (active prompt)\nconst TITLE = '\\x1b[1m\\x1b[38;5;253m'; // bold near-white (prompt band title)\nconst TXT = '\\x1b[38;5;250m'; // dim gray body text\nconst SUBTLE = '\\x1b[38;5;245m'; // very dim (detail / Y/N hint)\nconst RESET = '\\x1b[0m';\nconst UNDERLINE = '\\x1b[4m'; // emphasizes the default answer inside the chip\nconst NO_UNDERLINE = '\\x1b[24m'; // ends underline without dropping the chip bg\n// Agent-question accent: cyan + bold, matching the dashboard sidebar's\n// \"awaiting\" hue — distinct from URGENT (relay prompt) so the two readings\n// don't collide when both could in principle stack.\nconst QUESTION_ACCENT = '\\x1b[38;5;51m\\x1b[1m';\n// Notice footer = a full-width warning banner: bright yellow background with\n// near-black bold text. High contrast so the \"box is frozen\" state is\n// unmissable — deliberately louder than the dim-on-dark idle/prompt bars.\nconst NOTICE_BG = '\\x1b[48;5;220m'; // bright yellow background\nconst NOTICE_FG = '\\x1b[38;5;16m\\x1b[1m'; // near-black + bold text\n// Flash footer = a calm one-line confirmation on the normal dark bar.\nconst FLASH_FG = '\\x1b[38;5;150m\\x1b[1m'; // soft green + bold\n\n/** Collapsed idle hint (plain `--no-tmux` shell) — the leader is hidden\n * behind one chord. */\nconst COLLAPSED_HINTS_PLAIN: ReadonlyArray<readonly [string, string]> = [\n ['Control+a', 'Actions'],\n];\n/** Collapsed idle hint (detachable session) — the detach chord stays pinned\n * on the right even while the actions menu is closed. */\nconst COLLAPSED_HINTS_DETACHABLE: ReadonlyArray<readonly [string, string]> = [\n ['Control+a', 'Actions'],\n ['Control+a d', 'detach'],\n];\n/** Narrow-bar fallback for a detachable session: drop the `Actions` hint\n * first, but never the detach chord. */\nconst DETACH_PIN_HINTS: ReadonlyArray<readonly [string, string]> = [\n ['Control+a d', 'detach'],\n];\n/** Expanded which-key menu shown while the Ctrl+a leader is open. A\n * detachable (tmux-backed) session also gets `d: detach`; a plain shell\n * has nothing to detach from. */\nconst DETACHABLE_LEADER_HINTS: ReadonlyArray<readonly [string, string]> = [\n ['c', 'code'],\n ['s', 'screen'],\n ['u', 'url'],\n ['d', 'detach'],\n];\nconst PLAIN_LEADER_HINTS: ReadonlyArray<readonly [string, string]> = [\n ['c', 'code'],\n ['s', 'screen'],\n ['u', 'url'],\n];\n\n/**\n * Truncate `s` to exactly `width` visible columns, padding with spaces when\n * shorter. ANSI SGR sequences must NOT be present in the input.\n */\nfunction padTo(visible: string, width: number): string {\n if (visible.length === width) return visible;\n if (visible.length > width) {\n if (width <= 1) return visible.slice(0, width);\n return visible.slice(0, width - 1) + '…';\n }\n return visible + ' '.repeat(width - visible.length);\n}\n\n/**\n * High-contrast answer chip for a confirm prompt: the keys spelled out on a\n * bright-yellow background (the same NOTICE treatment used for the \"box\n * frozen\" banner) so the y/N choice is unmissable. The default answer is\n * underlined. Returns the styled string plus its visible column width — the\n * ANSI codes don't count toward layout, so callers need the plain width.\n */\nfunction answerChip(defaultAnswer: 'y' | 'n' | undefined): { ansi: string; width: number } {\n const yesKey = 'y Yes';\n const noKey = 'n No';\n const sep = ' · ';\n const yesIsDefault = defaultAnswer === 'y';\n const yes = yesIsDefault ? `${UNDERLINE}${yesKey}${NO_UNDERLINE}` : yesKey;\n const no = yesIsDefault ? noKey : `${UNDERLINE}${noKey}${NO_UNDERLINE}`;\n const ansi = `${NOTICE_BG}${NOTICE_FG} ${yes}${sep}${no} ${RESET}`;\n // Width derived from the plain (underline-free) shape so it stays correct\n // if the wording changes.\n const width = ` ${yesKey}${sep}${noKey} `.length;\n return { ansi, width };\n}\n\n/**\n * Render the footer row as a single ANSI string. Caller positions the\n * cursor at the last row, col 0 before writing, and restores it afterwards.\n * Always ends with SGR reset so the inner pty's next byte starts clean.\n */\nexport function renderFooter(state: FooterState, cols: number): string {\n if (cols <= 0) return '';\n if (state.kind === 'idle') {\n const sidebarBox: SidebarBox = {\n id: '', // unused by statusLine\n name: state.boxName,\n state: 'running', // we're attached, so the container is up\n activity: state.claudeActivity,\n sessionTitle: state.sessionTitle,\n };\n const isClaude = state.mode === 'claude';\n const detachable = state.detachable ?? isClaude;\n // Shell/codex modes have no claude activity to surface — passing\n // `stateLabel` overrides statusLine's default (which would otherwise show\n // `(unknown)` because `claudeActivity` is undefined and the container is\n // running).\n const stateLabel = isClaude ? undefined : state.mode === 'shell' ? 'shell' : state.mode;\n if (state.leaderActive) {\n const leaderHints = detachable ? DETACHABLE_LEADER_HINTS : PLAIN_LEADER_HINTS;\n return statusLine(sidebarBox, cols, stateLabel, leaderHints);\n }\n // Collapsed: a detachable session keeps the detach chord pinned on the\n // right (its narrow-bar fallback drops `Actions` first, never `detach`).\n const collapsed = detachable ? COLLAPSED_HINTS_DETACHABLE : COLLAPSED_HINTS_PLAIN;\n const fallback = detachable ? DETACH_PIN_HINTS : undefined;\n return statusLine(sidebarBox, cols, stateLabel, collapsed, fallback);\n }\n if (state.kind === 'flash') {\n // Flash state: a brief \"<arrow> <message>\" confirmation on the dark bar.\n const prefix = ' ▸ '; // ▸\n const inner = Math.max(0, cols - prefix.length);\n const message = padTo(state.message, inner);\n return `${BAR_BG}${FLASH_FG}${prefix}${TXT}${message}${RESET}`;\n }\n if (state.kind === 'notice') {\n // Notice state: \"<spinner> <message>\" rendered as a full-width\n // high-contrast yellow warning banner. The spinner reassures the user\n // the box is busy, not stuck.\n const spinner = SPINNER_FRAMES[state.frame % SPINNER_FRAMES.length]!;\n const prefix = ` ${spinner} `;\n const inner = Math.max(0, cols - prefix.length);\n const message = padTo(state.message, inner);\n return `${NOTICE_BG}${NOTICE_FG}${prefix}${message}${RESET}`;\n }\n // Prompt state (narrow-terminal fallback): \"[!] <message> [detail] <chip>\".\n // The answer chip is suffixed; we squeeze the message+detail into the space\n // left over (truncating message first, then detail).\n const chip = answerChip(state.prompt.defaultAnswer);\n const tag = ' [!] ';\n const sep = ' ';\n const inner = Math.max(0, cols - tag.length - chip.width);\n const detailRaw = state.prompt.detail ?? '';\n let message = state.prompt.message;\n let detail = detailRaw;\n const messageBudget = Math.max(8, inner - (detail.length > 0 ? sep.length + 8 : 0));\n if (message.length > messageBudget) {\n message = message.slice(0, Math.max(0, messageBudget - 1)) + '…';\n }\n const usedByMessage = message.length;\n const detailBudget = Math.max(0, inner - usedByMessage - sep.length);\n if (detail.length > detailBudget) {\n detail = detailBudget <= 1 ? '' : detail.slice(0, detailBudget - 1) + '…';\n }\n const middlePlain = detail.length > 0 ? `${message}${sep}${detail}` : message;\n const padded = padTo(middlePlain, inner);\n return `${BAR_BG}${URGENT}${tag}${TXT}${padded}${RESET}${chip.ansi}`;\n}\n\n/**\n * ANSI sequence to move the cursor to (row, col) — 1-based, terminal convention.\n */\nexport function cursorMoveTo(row: number, col: number): string {\n return `\\x1b[${String(row)};${String(col)}H`;\n}\n\nexport const CURSOR_SAVE = '\\x1b7';\nexport const CURSOR_RESTORE = '\\x1b8';\n\n/**\n * Synchronized output toggles (DECSET/DECRST 2026). Wrap a multi-write\n * footer paint so terminals that support it commit one atomic frame.\n */\nexport const SYNC_BEGIN = '\\x1b[?2026h';\nexport const SYNC_END = '\\x1b[?2026l';\n\n/**\n * Alert-band state: the surface shown directly above the (idle) footer when\n * a box needs the user's attention. The band is fixed at 3 rows; the inner\n * PTY is resized down by 3 rows so the band never overlaps agent output.\n *\n * - `prompt`: a relay confirm prompt (hard-blocks an in-box RPC).\n * - `notice`: an informational warning (checkpoint/snapshot in progress);\n * `frame` advances the spinner glyph.\n * - `question`: the agent's `AskUserQuestion` payload (claude.state ===\n * 'question'); shown as header + question text + option labels.\n */\nexport type AlertBandState =\n | { kind: 'prompt'; prompt: PromptAskEvent }\n | { kind: 'notice'; message: string; frame: number }\n | { kind: 'question'; question: ClaudeQuestionPayload };\n\n/** Default band height; both TUIs reserve this many rows above the footer. */\nexport const ALERT_BAND_ROWS = 3;\n\nfunction blankBar(cols: number, bg: string): string {\n return `${bg}${' '.repeat(Math.max(0, cols))}${RESET}`;\n}\n\nfunction renderPromptBand(prompt: PromptAskEvent, cols: number, rows: number): string[] {\n const tag = ' [!] ';\n const indent = ' '.repeat(tag.length);\n const contW = Math.max(0, cols - indent.length);\n\n // Row 1: \"[!] TITLE ............ <chip>\". The bold title (the relay action,\n // e.g. GIT PUSH) flags what needs approval; the high-contrast answer chip\n // sits right next to it so the keys are spotted immediately — not stranded\n // dim in the bottom-right corner.\n const chip = answerChip(prompt.defaultAnswer);\n const title = (prompt.context?.command ?? 'confirm').toUpperCase();\n const titleW = Math.max(0, cols - tag.length - chip.width);\n const titlePadded = padTo(title, titleW);\n const line1 = `${BAR_BG}${URGENT}${tag}${TITLE}${titlePadded}${RESET}${chip.ansi}`;\n\n // Row 2: the question itself, full width.\n const line2 = `${BAR_BG}${TXT}${indent}${padTo(prompt.message, contW)}${RESET}`;\n\n // Row 3: optional detail/sub-message, dimmer.\n const line3 = `${BAR_BG}${SUBTLE}${indent}${padTo(prompt.detail ?? '', contW)}${RESET}`;\n\n return [line1, line2, line3].slice(0, rows);\n}\n\nfunction renderNoticeBand(\n message: string,\n frame: number,\n cols: number,\n rows: number,\n): string[] {\n const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length]!;\n const prefix = ` ${spinner} `;\n const indent = ' '.repeat(prefix.length);\n const firstW = Math.max(0, cols - prefix.length);\n const contW = Math.max(0, cols - indent.length);\n\n const out: string[] = [];\n let i = 0;\n for (let r = 0; r < rows; r++) {\n const isLast = r === rows - 1;\n const w = r === 0 ? firstW : contW;\n let cell: string;\n if (i >= message.length) {\n cell = ' '.repeat(w);\n } else if (isLast) {\n cell = padTo(message.slice(i), w);\n i = message.length;\n } else {\n cell = message.slice(i, i + w).padEnd(w);\n i += w;\n }\n const lead = r === 0 ? prefix : indent;\n out.push(`${NOTICE_BG}${NOTICE_FG}${lead}${cell}${RESET}`);\n }\n return out;\n}\n\nfunction renderQuestionBand(\n payload: ClaudeQuestionPayload,\n cols: number,\n rows: number,\n): string[] {\n const q = payload.questions[0];\n if (!q) return Array.from({ length: rows }, () => blankBar(cols, BAR_BG));\n\n const tag = ' [?] ';\n const indent = ' '.repeat(tag.length);\n const innerW = Math.max(0, cols - tag.length);\n const contW = Math.max(0, cols - indent.length);\n\n const header = q.header && q.header.trim().length > 0 ? q.header : 'Question';\n const headerPadded = padTo(header, innerW);\n const line1 = `${BAR_BG}${QUESTION_ACCENT}${tag}${TXT}${headerPadded}${RESET}`;\n\n const questionText = padTo(q.question, contW);\n const line2 = `${BAR_BG}${TXT}${indent}${questionText}${RESET}`;\n\n const optLabels = q.options.map((o) => o.label).join(' · ');\n const optsLine = optLabels.length > 0 ? `options: ${optLabels}` : '';\n const optsPadded = padTo(optsLine, contW);\n const line3 = `${BAR_BG}${SUBTLE}${indent}${optsPadded}${RESET}`;\n\n return [line1, line2, line3].slice(0, rows);\n}\n\n/**\n * Render the 3-row alert band as an array of `rows` ANSI strings. Each\n * element is a full-width painted row (background tint reset at EOL).\n * Callers position the cursor at each row's column 1 and write the line\n * inside the same synchronized-output wrap as the footer.\n */\nexport function renderAlertBand(\n state: AlertBandState,\n cols: number,\n rows: number = ALERT_BAND_ROWS,\n): string[] {\n if (cols <= 0 || rows <= 0) return Array.from({ length: Math.max(0, rows) }, () => '');\n if (state.kind === 'prompt') return renderPromptBand(state.prompt, cols, rows);\n if (state.kind === 'notice') return renderNoticeBand(state.message, state.frame, cols, rows);\n return renderQuestionBand(state.question, cols, rows);\n}\n","import { request as httpRequest, type IncomingMessage } from 'node:http';\nimport { request as httpsRequest } from 'node:https';\nimport type { BoxNoticeEvent, PromptAnswerBody, PromptAskEvent } from '@agentbox/relay';\n\n/**\n * SSE subscription back to the relay's `GET /admin/prompts/stream`. The\n * relay pushes:\n * - `event: prompt-ask` data: PromptAskEvent (with id)\n * - `event: prompt-resolved` data: { id }\n * - `event: notice-set` data: BoxNoticeEvent (with id)\n * - `event: notice-clear` data: { id }\n * - `event: ping` data: { ts }\n *\n * We reconnect with exponential backoff on any error or close — the only\n * way to know the relay is back is to keep trying. Subscribers are\n * loopback-only so latency is sub-ms.\n */\nexport interface PromptStream {\n /** Stop subscribing; aborts any in-flight reconnect attempt. */\n close(): void;\n}\n\nexport interface SubscribeOptions {\n relayBaseUrl: string;\n boxId: string;\n onPrompt: (ev: PromptAskEvent) => void;\n /** Server-driven: a sibling wrapper (or this one) answered; the run loop\n * clears the footer for stale ids it didn't originate. */\n onResolved: (id: string) => void;\n /** A box-level informational notice was set (e.g. checkpoint in progress). */\n onNotice?: (ev: BoxNoticeEvent) => void;\n /** A previously-set notice was cleared (explicitly or via its TTL). */\n onNoticeCleared?: (id: string) => void;\n onError?: (err: Error) => void;\n}\n\nconst INITIAL_BACKOFF_MS = 200;\nconst MAX_BACKOFF_MS = 5_000;\n\nexport function subscribePrompts(opts: SubscribeOptions): PromptStream {\n let closed = false;\n let req: ReturnType<typeof httpRequest> | null = null;\n let res: IncomingMessage | null = null;\n let reconnectTimer: NodeJS.Timeout | null = null;\n let backoffMs = INITIAL_BACKOFF_MS;\n let url: URL;\n try {\n url = new URL(opts.relayBaseUrl);\n } catch (err) {\n if (opts.onError) opts.onError(err instanceof Error ? err : new Error(String(err)));\n return { close: () => {} };\n }\n const isHttps = url.protocol === 'https:';\n const transport = isHttps ? httpsRequest : httpRequest;\n const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;\n\n function scheduleReconnect(): void {\n if (closed) return;\n const delay = backoffMs;\n backoffMs = Math.min(MAX_BACKOFF_MS, backoffMs * 2);\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n connect();\n }, delay);\n if (typeof reconnectTimer.unref === 'function') reconnectTimer.unref();\n }\n\n /**\n * SSE message parser: server sends `event: <type>\\n` then `data: <json>\\n\\n`.\n * The relay never splits an event across writes (one chunk per dispatch),\n * but we still buffer by message boundary `\\n\\n` so a mid-message slice\n * doesn't corrupt parsing.\n */\n let buffer = '';\n function consumeMessages(): void {\n let idx = buffer.indexOf('\\n\\n');\n while (idx !== -1) {\n const raw = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n idx = buffer.indexOf('\\n\\n');\n // Drop the SSE comment line we send on connect (`: connected`).\n if (raw.startsWith(':')) continue;\n let event = '';\n let dataLine = '';\n for (const line of raw.split('\\n')) {\n if (line.startsWith('event:')) event = line.slice('event:'.length).trim();\n else if (line.startsWith('data:')) dataLine = line.slice('data:'.length).trim();\n }\n if (event === 'prompt-ask' && dataLine.length > 0) {\n try {\n const ev = JSON.parse(dataLine) as PromptAskEvent;\n if (ev && typeof ev.id === 'string') opts.onPrompt(ev);\n } catch {\n /* malformed; relay should never send this — ignore rather than die */\n }\n } else if (event === 'prompt-resolved' && dataLine.length > 0) {\n try {\n const payload = JSON.parse(dataLine) as { id?: string };\n if (payload && typeof payload.id === 'string') opts.onResolved(payload.id);\n } catch {\n /* malformed; ignore */\n }\n } else if (event === 'notice-set' && dataLine.length > 0) {\n try {\n const ev = JSON.parse(dataLine) as BoxNoticeEvent;\n if (ev && typeof ev.id === 'string') opts.onNotice?.(ev);\n } catch {\n /* malformed; ignore */\n }\n } else if (event === 'notice-clear' && dataLine.length > 0) {\n try {\n const payload = JSON.parse(dataLine) as { id?: string };\n if (payload && typeof payload.id === 'string') opts.onNoticeCleared?.(payload.id);\n } catch {\n /* malformed; ignore */\n }\n }\n // 'ping' has no caller-visible side effect — its purpose is to keep\n // the socket from going idle and to let the wrapper detect dead links\n // via socket-level errors. No-op here.\n }\n }\n\n function connect(): void {\n if (closed) return;\n req = transport({\n host: url.hostname,\n port,\n method: 'GET',\n path: `${url.pathname.replace(/\\/$/, '')}/admin/prompts/stream?boxId=${encodeURIComponent(opts.boxId)}`,\n headers: { Accept: 'text/event-stream' },\n });\n req.on('response', (r) => {\n res = r;\n if (r.statusCode !== 200) {\n // 400/403 — relay says \"no for you\"; bail without retrying since\n // these are config errors (no boxId, not loopback) that won't fix\n // themselves.\n if (opts.onError) opts.onError(new Error(`SSE stream returned ${String(r.statusCode)}`));\n r.resume();\n close();\n return;\n }\n backoffMs = INITIAL_BACKOFF_MS; // reset on a healthy connect\n r.setEncoding('utf8');\n r.on('data', (chunk: string) => {\n buffer += chunk;\n consumeMessages();\n });\n r.on('end', () => {\n if (!closed) scheduleReconnect();\n });\n r.on('error', () => {\n if (!closed) scheduleReconnect();\n });\n });\n req.on('error', () => {\n if (!closed) scheduleReconnect();\n });\n req.end();\n }\n\n function close(): void {\n if (closed) return;\n closed = true;\n if (reconnectTimer) clearTimeout(reconnectTimer);\n try {\n res?.destroy();\n } catch {\n /* best-effort */\n }\n try {\n req?.destroy();\n } catch {\n /* best-effort */\n }\n }\n\n connect();\n return { close };\n}\n\n/**\n * POST a PromptAnswerBody to /admin/prompts/answer. Fire-and-(mostly)-\n * forget: we don't retry on failure because the relay's `prompts.resolve`\n * is idempotent and a double-resolve returns 404. If the relay was dead,\n * the SSE reconnect loop will repush any prompts that are still pending.\n */\nexport interface PostAnswerOptions {\n relayBaseUrl: string;\n body: PromptAnswerBody;\n}\n\nexport interface PostAnswerResult {\n ok: boolean;\n status: number;\n}\n\nexport function postAnswer(opts: PostAnswerOptions): Promise<PostAnswerResult> {\n return new Promise<PostAnswerResult>((resolve) => {\n let url: URL;\n try {\n url = new URL(opts.relayBaseUrl);\n } catch {\n resolve({ ok: false, status: 0 });\n return;\n }\n const isHttps = url.protocol === 'https:';\n const transport = isHttps ? httpsRequest : httpRequest;\n const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;\n const json = JSON.stringify(opts.body);\n const req = transport(\n {\n host: url.hostname,\n port,\n method: 'POST',\n path: `${url.pathname.replace(/\\/$/, '')}/admin/prompts/answer`,\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(json).toString(),\n },\n timeout: 3000,\n },\n (res) => {\n res.resume();\n const status = res.statusCode ?? 0;\n // 204 = accepted; 404 = already answered (idempotent). Both are \"done\".\n resolve({ ok: status === 204 || status === 404, status });\n },\n );\n req.on('error', () => resolve({ ok: false, status: 0 }));\n req.on('timeout', () => {\n req.destroy();\n resolve({ ok: false, status: 0 });\n });\n req.write(json);\n req.end();\n });\n}\n","/**\n * Host→box clipboard image paste, cross-provider.\n *\n * Wired into the attach wrapper's Ctrl+V hook (`wrapped-pty/run.ts`). When the\n * user pastes while attached to an in-box Claude Code session we:\n * 1. grab the image off the host clipboard (`captureClipboardImage`),\n * 2. make sure the box's X server (`DISPLAY=:1`) is up,\n * 3. ship the PNG into the box (`Provider.uploadPath`),\n * 4. load it into the box's X11 CLIPBOARD via `xclip -t image/png`.\n * The wrapper then forwards the literal Ctrl+V so Claude Code's own\n * \"paste image from clipboard\" binding reads the now-populated selection.\n *\n * All steps go through the provider-neutral `Provider` seam (`uploadPath` +\n * `exec`), so it works identically on docker / daytona / hetzner / vercel.\n */\n\nimport { rm } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport type { BoxRecord, Provider } from '@agentbox/core';\nimport { captureClipboardImage } from './host-clipboard.js';\n\nexport type PasteImageResult = 'pasted' | 'no-image' | 'error';\n\n/** Box-side load: ensure Xvnc is up, wait for the X socket, then hand the PNG\n * to a detached `xclip` that keeps owning the CLIPBOARD selection until Claude\n * reads it. `setsid … &` so it survives `exec` returning. The path is a\n * CLI-generated `/tmp` name (no shell metacharacters), so inlining is safe. */\nfunction loadClipboardScript(boxPngPath: string): string {\n return [\n 'pgrep -x Xvnc >/dev/null 2>&1 || /usr/local/bin/agentbox-vnc-start >/dev/null 2>&1 || true',\n 'for _ in $(seq 1 30); do [ -S /tmp/.X11-unix/X1 ] && break; sleep 0.2; done',\n `setsid sh -c 'DISPLAY=:1 xclip -selection clipboard -t image/png -i ${boxPngPath}' </dev/null >/dev/null 2>&1 &`,\n ].join('; ');\n}\n\nexport async function pasteHostClipboardImage(\n provider: Provider,\n box: BoxRecord,\n): Promise<PasteImageResult> {\n if (typeof provider.uploadPath !== 'function') return 'error';\n\n const hostPng = await captureClipboardImage();\n if (!hostPng) return 'no-image';\n\n const boxPng = `/tmp/agentbox-clip-${String(Date.now())}.png`;\n try {\n await provider.uploadPath(box, hostPng, boxPng);\n await provider.exec(box, ['sh', '-lc', loadClipboardScript(boxPng)], {\n user: 'vscode',\n });\n return 'pasted';\n } catch {\n return 'error';\n } finally {\n await rm(dirname(hostPng), { recursive: true, force: true }).catch(() => {});\n }\n}\n","/**\n * Capture an image off the host clipboard to a temp PNG.\n *\n * Used by the Ctrl+V paste path (`paste-image.ts`): when the user pastes while\n * attached to an in-box Claude Code session, we grab whatever image they copied\n * on the host and ship it into the box. Supported hosts:\n * - macOS: `osascript` coerces the clipboard to PNG (TIFF screenshots are\n * converted with `sips`). Both ship with the OS.\n * - Linux (X11 / Wayland desktop): `xclip` / `wl-paste` read the `image/png`\n * clipboard target. These aren't always installed, so capture degrades to\n * `null` when the tool (or a display) is missing.\n * Every other platform returns `null`, so the caller cleanly forwards Ctrl+V\n * unchanged.\n */\n\nimport { mkdtemp, rm, stat, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { execa } from 'execa';\n\n/**\n * Grab the current clipboard image into a host temp PNG. Returns the file path,\n * or `null` when the clipboard holds no image (unsupported platform, missing\n * tool, or capture failed). The caller owns the returned file and should delete\n * it (and its parent dir) after delivery.\n */\nexport async function captureClipboardImage(): Promise<string | null> {\n if (process.platform !== 'darwin' && process.platform !== 'linux') return null;\n\n const dir = await mkdtemp(join(tmpdir(), 'agentbox-clip-'));\n const pngPath = join(dir, 'clip.png');\n const ok =\n process.platform === 'darwin'\n ? await captureDarwin(dir, pngPath)\n : await captureLinux(pngPath);\n\n if (ok) return pngPath;\n await rm(dir, { recursive: true, force: true }).catch(() => {});\n return null;\n}\n\n/**\n * True when this host has a clipboard-image capture path. Call sites use it to\n * decide whether to wire the Ctrl+V hook at all — so a host with no clipboard\n * tool (or no display) leaves Ctrl+V forwarding verbatim instead of\n * intercepting it for a guaranteed-empty paste.\n */\nexport async function clipboardCaptureAvailable(): Promise<boolean> {\n if (process.platform === 'darwin') return true;\n if (process.platform === 'linux') return (await linuxClipboardTool()) !== null;\n return false;\n}\n\n// ---- macOS ----\n\n/** AppleScript that writes the clipboard to `<path>` as PNG, falling back to\n * TIFF. Prints `PNG`, `TIFF`, or `NONE`. Returns the flattened `-e <line> …`\n * argv for `osascript`. */\nfunction captureScriptArgs(pngPath: string, tiffPath: string): string[] {\n return [\n 'try',\n ' set theData to (the clipboard as «class PNGf»)',\n ` set fh to open for access (POSIX file ${JSON.stringify(pngPath)}) with write permission`,\n ' set eof fh to 0',\n ' write theData to fh',\n ' close access fh',\n ' return \"PNG\"',\n 'on error',\n ' try',\n ' set theData to (the clipboard as «class TIFF»)',\n ` set fh to open for access (POSIX file ${JSON.stringify(tiffPath)}) with write permission`,\n ' set eof fh to 0',\n ' write theData to fh',\n ' close access fh',\n ' return \"TIFF\"',\n ' on error',\n ' return \"NONE\"',\n ' end try',\n 'end try',\n ]\n .map((line) => ['-e', line])\n .flat();\n}\n\nasync function captureDarwin(dir: string, pngPath: string): Promise<boolean> {\n const tiffPath = join(dir, 'clip.tiff');\n const res = await execa('osascript', captureScriptArgs(pngPath, tiffPath), {\n reject: false,\n });\n const kind = res.stdout.trim();\n\n if (kind === 'PNG') return fileHasBytes(pngPath);\n\n if (kind === 'TIFF' && (await fileHasBytes(tiffPath))) {\n // Screenshots land on the clipboard as TIFF; convert to PNG with sips.\n const conv = await execa(\n 'sips',\n ['-s', 'format', 'png', tiffPath, '--out', pngPath],\n { reject: false },\n );\n if (conv.exitCode === 0) return fileHasBytes(pngPath);\n }\n return false;\n}\n\n// ---- Linux (X11 / Wayland) ----\n\n/** Which clipboard tool to use on this Linux host, or `null` when none is\n * usable (no display, or the binary isn't installed). Wayland wins when a\n * Wayland session is present. */\nasync function linuxClipboardTool(): Promise<'wayland' | 'x11' | null> {\n if (process.env['WAYLAND_DISPLAY'] && (await hasCmd('wl-paste'))) return 'wayland';\n if (process.env['DISPLAY'] && (await hasCmd('xclip'))) return 'x11';\n return null;\n}\n\nasync function captureLinux(pngPath: string): Promise<boolean> {\n const tool = await linuxClipboardTool();\n if (!tool) return false;\n\n let buf: Buffer | null = null;\n if (tool === 'wayland') {\n const types = await execa('wl-paste', ['--list-types'], { reject: false });\n if (types.exitCode !== 0 || !/image\\/png/i.test(types.stdout)) return false;\n const r = await execa('wl-paste', ['--type', 'image/png'], {\n encoding: 'buffer',\n reject: false,\n });\n if (r.exitCode === 0) buf = asBuffer(r.stdout);\n } else {\n const sel = ['-selection', 'clipboard'];\n const targets = await execa('xclip', [...sel, '-t', 'TARGETS', '-o'], {\n reject: false,\n });\n if (targets.exitCode !== 0 || !/image\\/png/i.test(targets.stdout)) return false;\n const r = await execa('xclip', [...sel, '-t', 'image/png', '-o'], {\n encoding: 'buffer',\n reject: false,\n });\n if (r.exitCode === 0) buf = asBuffer(r.stdout);\n }\n\n if (!buf || !isPng(buf)) return false;\n await writeFile(pngPath, buf);\n return true;\n}\n\n// ---- helpers ----\n\n/** `command -v <cmd>` — true when the binary is on PATH. `cmd` is a fixed\n * literal at every call site (no injection surface). */\nasync function hasCmd(cmd: string): Promise<boolean> {\n const r = await execa('sh', ['-c', `command -v ${cmd}`], { reject: false });\n return r.exitCode === 0;\n}\n\nfunction asBuffer(out: unknown): Buffer | null {\n if (Buffer.isBuffer(out)) return out;\n if (out instanceof Uint8Array) return Buffer.from(out);\n return null;\n}\n\nfunction isPng(buf: Buffer): boolean {\n return (\n buf.length >= 8 &&\n buf[0] === 0x89 &&\n buf[1] === 0x50 &&\n buf[2] === 0x4e &&\n buf[3] === 0x47\n );\n}\n\nasync function fileHasBytes(path: string): Promise<boolean> {\n try {\n const s = await stat(path);\n return s.isFile() && s.size > 0;\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["spawn","join","spawn","spawn","code","inner","message","spinner","spawn","exitCode","rm","rm","join","spawn"]}
|
|
File without changes
|