cyber-mux 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.mts +66 -0
- package/dist/agent.d.mts.map +1 -0
- package/dist/agent.mjs +58 -0
- package/dist/agent.mjs.map +1 -0
- package/dist/{backend-DnrbmL6N.mjs → backend-Cif257ji.mjs} +326 -6
- package/dist/backend-Cif257ji.mjs.map +1 -0
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +324 -19
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +66 -581
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/mux-CoYDrk3v.d.mts +733 -0
- package/dist/mux-CoYDrk3v.d.mts.map +1 -0
- package/dist/template.mjs +1 -1
- package/dist/{worktree-CX88bGzZ.d.mts → worktree-9QDz-iKC.d.mts} +63 -2
- package/dist/{worktree-CX88bGzZ.d.mts.map → worktree-9QDz-iKC.d.mts.map} +1 -1
- package/dist/{worktree-CoQdRgv1.mjs → worktree-hHuFZkpW.mjs} +84 -2
- package/dist/{worktree-CoQdRgv1.mjs.map → worktree-hHuFZkpW.mjs.map} +1 -1
- package/dist/worktree.d.mts +2 -2
- package/dist/worktree.mjs +2 -2
- package/package.json +5 -1
- package/src/agent.ts +95 -0
- package/src/backend.ts +22 -0
- package/src/cli-error.ts +31 -0
- package/src/cli.ts +436 -60
- package/src/index.ts +3 -1
- package/src/mux.herdr.ts +193 -3
- package/src/mux.tmux.ts +10 -0
- package/src/mux.ts +155 -0
- package/src/mux.wezterm.ts +9 -0
- package/src/mux.zellij.ts +9 -0
- package/src/nudge.ts +9 -0
- package/src/ratio.ts +25 -0
- package/src/template-capture.ts +66 -1
- package/src/wait-output.ts +116 -0
- package/src/worktree.ts +105 -0
- package/dist/backend-DnrbmL6N.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend-Cif257ji.mjs","names":["parsePaneLocation","runLaunch"],"sources":["../src/env-fallback.ts","../src/ratio.ts","../src/wait-output.ts","../src/mux.herdr.ts","../src/mux.tmux.ts","../src/new-id.ts","../src/mux.wezterm.ts","../src/mux.zellij.ts","../src/mux-probe.ts","../src/nudge.ts","../src/backend.ts"],"sourcesContent":["/**\n * The env-prefix fallback — the one compensation for a route that could not set env at birth.\n *\n * env is native at every tier on both backends EXCEPT herdr's worktree `create`/`open`, which take\n * no env parameter (0.7.4 answers `--env` with `unknown option`). A route that hit that wall carries\n * env the only way left: as an `env KEY=VALUE` prefix on the command the pane runs. It is a LAST\n * resort — the values land in `ps` output and the pane's shell history — and it only works when there\n * IS a command to ride; with none, the honest outcome is to warn, never to drop silently.\n *\n * This lives in one module, called by both routes that can lose env (the CLI worktree verbs and the\n * template walk's root pane), so the rule cannot be wired on one and forgotten on the other. Only a\n * route that lost env may call it: prefixing over a natively-set env would push the values into `ps`\n * and shell history on every route, the exact cost the prefix exists to pay only when it must.\n */\n\n/**\n * Single-quote a value for a shell command line. Everything is literal inside single quotes, so the\n * only escape needed is for a single quote itself: end the quoting, emit an escaped `'`, reopen.\n * Without this a value carrying a space or a quote would split into extra words, or unbalance the\n * line outright.\n */\nexport function shellQuote(value: string): string {\n\treturn `'${value.replace(/'/g, `'\\\\''`)}'`\n}\n\n/** `env K=V …` with a trailing space, ready to prepend to a command line. Values are shell-quoted. */\nexport function envPrefix(env: Record<string, string>): string {\n\treturn `env ${Object.entries(env)\n\t\t.map(([key, value]) => `${key}=${shellQuote(value)}`)\n\t\t.join(' ')} `\n}\n\n/**\n * The fallback decision for a route that could not carry env natively — computed once, applied the\n * same way everywhere. `carried` means there was nothing to compensate (no env, or env with a command\n * that now carries it); `dropped` means env was asked for with no command to ride, and the caller\n * must warn naming `variables`.\n */\nexport type EnvFallback = { kind: 'carried'; command: string | undefined } | { kind: 'dropped'; variables: string[] }\n\n/**\n * Given the env a route could not carry and the command (if any) that would run in the opened pane,\n * decide how env rides in. With a command, env is prefixed onto it and the pane carries the value;\n * with none, env is dropped and the caller warns. No env (or an empty map) is `carried` unchanged, so\n * a caller on the losing route can call this unconditionally and get the right command back.\n */\nexport function envFallback(env: Record<string, string> | undefined, command: string | undefined): EnvFallback {\n\tif (env === undefined || Object.keys(env).length === 0) return { kind: 'carried', command }\n\tif (command === undefined) return { kind: 'dropped', variables: Object.keys(env) }\n\treturn { kind: 'carried', command: `${envPrefix(env)}${command}` }\n}\n","/**\n * The seam's own precondition on `MuxOpenOptions.ratio`: a fraction STRICTLY between 0 and 1.\n *\n * `ratio` is the fraction kept by the ORIGINAL pane. Outside `0 < ratio < 1` there is no split it can\n * name: `1 - ratio` goes negative above 1 (tmux `-l -50%` / wezterm `--percent -50`), and 0 or 1 hands\n * one side the whole region and the other nothing — a mistake, never an intent worth honoring. Left\n * unrendered these produce a silently broken split, not an error, which is the exact silent-wrong\n * output this seam's loud-over-quiet preference exists to refuse.\n *\n * Enforced HERE, at the seam, rather than left to each caller, because the invariant is a universal\n * property of what a ratio IS — true on every backend — not a per-caller policy. (The DEGRADE policy —\n * what a caller does when a backend cannot size a split at all — genuinely stays the caller's, unchanged;\n * range validity and degrade policy are different questions.) A caller cannot reach an adapter with an\n * out-of-range ratio and have it silently rendered; `template`'s schema still refuses one earlier, per\n * node, with a path-qualified message, so the two layers do different jobs and the seam is the backstop.\n *\n * The guard lives WITH the rendering: it is called by each backend's size render helper, so a backend\n * that cannot size a split (zellij) renders no ratio and so never reaches this guard — a dropped value\n * is never checked, valid or not, which is the same as the even-default degrade its callers already take.\n */\nexport function assertRatioInRange(ratio: number): void {\n\tif (!Number.isFinite(ratio) || ratio <= 0 || ratio >= 1) {\n\t\tthrow new Error(`ratio must be strictly between 0 and 1 — got ${ratio}`)\n\t}\n}\n","import type { Exec } from './exec.ts'\nimport type { MuxAdapter, MuxTarget, MuxWaitOptions, MuxWaitResult } from './mux.ts'\n\n/** How long a polling backend sleeps between reads when the caller names no cadence. */\nexport const DEFAULT_WAIT_POLL_MS = 150\n\n/**\n * The seam's own precondition on a wait pattern: EXACTLY ONE of `match`/`regex`, and a `regex` that\n * compiles.\n *\n * Enforced here, at the seam, rather than per adapter, for `assertRatioInRange`'s reason — it is a\n * universal property of what a wait pattern IS, true on every backend, not a per-backend policy. Both\n * halves matter for portability in different ways: the one-of rule is refusable by herdr's CLI and by\n * nothing at all on a polling backend, so leaving it to the backend would make the same call fail on\n * one and silently pick a winner on another; and compiling the source turns a MALFORMED pattern into\n * the same loud failure everywhere, instead of a herdr refusal on one backend and a poll that throws\n * on its first read somewhere else.\n *\n * What it deliberately does NOT check is dialect: a pattern using ECMAScript-only syntax compiles here\n * and is then herdr's own to accept or refuse (see `MuxWaitOptions.regex`). Validating against the\n * intersection of two regex engines would mean shipping a third one.\n */\nexport function assertWaitPattern(opts: MuxWaitOptions): void {\n\tconst hasMatch = opts.match != null\n\tconst hasRegex = opts.regex != null\n\tif (hasMatch && hasRegex) {\n\t\tthrow new Error('wait pattern must be one of match or regex — got both')\n\t}\n\tif (!hasMatch && !hasRegex) {\n\t\tthrow new Error('wait pattern must be one of match or regex — got neither')\n\t}\n\tif (opts.match != null && opts.match === '') {\n\t\t// An empty needle matches every snapshot, including an empty one, so the wait would return\n\t\t// instantly no matter what the pane is doing — a spelling whose result carries no information.\n\t\tthrow new Error('wait pattern match must not be empty')\n\t}\n\tif (opts.regex != null) {\n\t\ttry {\n\t\t\tnew RegExp(opts.regex)\n\t\t} catch (err) {\n\t\t\tthrow new Error(`wait pattern regex is not a valid expression: ${opts.regex} — ${(err as Error).message}`)\n\t\t}\n\t}\n}\n\n/**\n * Whether `output` satisfies the pattern, and the single line to point at when it does.\n *\n * The match runs against the WHOLE snapshot, not line by line, so a regex that spans a newline still\n * hits — that is why `matchedLine` is derived separately and left absent when no single line carries\n * the match on its own. Pure, so the tricky half is testable with no multiplexer at all, exactly as\n * `template-capture`'s geometry derivation is.\n */\nexport function matchWaitPattern(output: string, opts: MuxWaitOptions): MuxWaitResult {\n\tassertWaitPattern(opts)\n\tconst hit = (text: string): boolean =>\n\t\topts.match != null ? text.includes(opts.match) : new RegExp(opts.regex as string).test(text)\n\tif (!hit(output)) return { matched: false, output }\n\tconst line = output.split('\\n').find(hit)\n\treturn { matched: true, output, ...(line != null ? { matchedLine: line } : {}) }\n}\n\n/**\n * `waitForOutput` for a backend with NO native wait — poll its own `read` until the pattern matches or\n * the deadline passes. tmux, WezTerm and Zellij all route their seam method straight through here, so\n * the three share one cadence, one deadline rule and one liveness rule rather than three copies that\n * can drift; herdr overrides it with its native primitive.\n *\n * **Reads first, sleeps second.** The snapshot on screen when the call arrives is searched before any\n * sleeping, so a pattern already printed returns immediately — the seam's stated \"existing output\n * counts\" rule, and the same order herdr's native wait documents for itself.\n *\n * **A gone pane throws instead of timing out**, which is `nudge`'s rule for the same reason: a dead\n * pane and a quiet one both read back empty, so without the liveness probe every dead peer would be\n * reported as a timeout — a shape the caller reads as \"still working\" — and the real cause would be\n * buried. Probed BEFORE the first read (so a pane that was already gone fails at once rather than\n * after the full timeout) and again after the deadline (so a pane that died mid-wait is not reported as\n * one that merely stayed quiet). Never probed per poll: that would double every backend's query load\n * for a fact that only changes the verdict at the end.\n */\nexport async function pollForOutput(\n\tadapter: MuxAdapter,\n\texec: Exec,\n\ttarget: MuxTarget,\n\topts: MuxWaitOptions,\n): Promise<MuxWaitResult> {\n\tassertWaitPattern(opts)\n\tconst pollMs = opts.pollMs ?? DEFAULT_WAIT_POLL_MS\n\tconst now = opts.now ?? (() => Date.now())\n\tconst sleep = opts.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)))\n\tconst readOpts = opts.lines != null ? { lines: opts.lines } : undefined\n\n\tassertPaneLive(adapter, exec, target)\n\n\tconst deadline = now() + opts.timeoutMs\n\tlet output = ''\n\tfor (;;) {\n\t\toutput = adapter.read(exec, target, readOpts)\n\t\tconst result = matchWaitPattern(output, opts)\n\t\tif (result.matched) return result\n\t\t// Checked AFTER a read, so a zero/elapsed timeout still gets its one look at the pane: the wait\n\t\t// promises a search of what is already there, and `timeoutMs: 0` means \"look once\", not \"look never\".\n\t\tif (now() >= deadline) break\n\t\tawait sleep(pollMs)\n\t}\n\tassertPaneLive(adapter, exec, target)\n\treturn { matched: false, output }\n}\n\n/** The liveness probe both ends of a poll share, throwing `nudge`'s named failure rather than letting a\n * dead pane be reported as a quiet one. */\nfunction assertPaneLive(adapter: MuxAdapter, exec: Exec, target: MuxTarget): void {\n\tif (!adapter.paneExists(exec, target)) {\n\t\tthrow new Error(`wait failed: pane ${target.id} no longer exists — the pane is gone, not quiet.`)\n\t}\n}\n","import { resolve } from 'node:path'\nimport { envFallback } from './env-fallback.ts'\nimport { type Exec, withReason } from './exec.ts'\nimport type {\n\tAgentStatus,\n\tLivePane,\n\tMuxAdapter,\n\tMuxReadOptions,\n\tMuxWaitResult,\n\tOpenedPane,\n\tRegionPane,\n\tWorkspaceTab,\n\tWorktreeWorkspace,\n\tWorktreeWorkspaceCapability,\n} from './mux.ts'\nimport { assertRatioInRange } from './ratio.ts'\nimport { assertWaitPattern } from './wait-output.ts'\nimport { normalizeWorktreePath } from './worktree.ts'\n\n/**\n * herdr backend — detected via `$HERDR_ENV`. herdr (https://herdr.dev) is an agent-aware terminal\n * multiplexer that also reports real busy-state (working / idle / blocked / done); this adapter\n * only drives its pane lifecycle, not the state feed. Talks to herdr's own CLI (`herdr pane ...`)\n * rather than its Unix-socket API, so it composes with this codebase's synchronous `Exec`\n * convention exactly like the tmux adapter — no new client/transport needed.\n *\n * The pane lifecycle (split/run/read/close) is verified against a live herdr binary; `pane split`\n * returns a JSON `pane_info` envelope whose id is extracted in `parsePaneId`.\n */\nexport const herdrMuxAdapter: MuxAdapter = {\n\tname: 'herdr',\n\n\t// `pane split --ratio` sizes a split — and sizes the ORIGINAL pane, which is the seam's own\n\t// convention, so it passes through unconverted (unlike tmux's `-l`). Verified against 0.7.4.\n\tcanSizeSplits: true,\n\n\topen(exec, opts) {\n\t\tconst at = opts.at ?? 'tab'\n\t\t// herdr takes a label at birth for a workspace and a tab, but not for a split — a pane is\n\t\t// named afterwards, via `pane rename`.\n\t\tconst label = opts.label ? ['--label', opts.label] : []\n\t\t// Native at EVERY tier, not just the split: `WorkspaceCreateParams` and `TabCreateParams` both\n\t\t// carry an `env` Record in herdr's socket schema (protocol 16), and the CLI takes the same\n\t\t// repeatable `--env KEY=VALUE` there as `pane split` does — verified against 0.7.4. That\n\t\t// matters because a template's root pane is born by the region open rather than by a split, so\n\t\t// scoping env to the split path would silently drop that pane's env.\n\t\tconst env = envFlags(opts.env)\n\t\t// `opts.workspaceGroup` is deliberately unread here, and that IS this adapter's answer to it:\n\t\t// herdr's workspace is a real tier and every pane and tab record already carries its\n\t\t// `workspace_id`, so the tier already IS the group. No grouping flag reaches herdr — a second\n\t\t// grouping would duplicate a fact the backend never reads, and herdr would have to be taught to\n\t\t// read it. The seam's group id exists for a backend with no workspace tier to group in (tmux);\n\t\t// an adapter ignoring a new optional member still satisfies the contract.\n\t\tlet opened: OpenedPane\n\t\tif (at === 'workspace') {\n\t\t\t// A genuinely separate workspace, not a pane inside the caller's current one — `--no-focus`\n\t\t\t// so spawning doesn't steal the caller's attention/focus.\n\t\t\tconst out = exec('herdr', ['workspace', 'create', '--cwd', opts.cwd, ...label, ...env, '--no-focus'])\n\t\t\tif (!out) throw new Error(withReason(exec, 'herdr workspace create failed'))\n\t\t\topened = parseRootPaneId(out, 'herdr workspace create')\n\t\t} else if (at === 'tab') {\n\t\t\t// A real tab in the current window, not a split pane — `--no-focus` so spawning doesn't\n\t\t\t// steal the caller's attention/focus, matching workspace/worktree spawns.\n\t\t\t//\n\t\t\t// `--workspace` whenever the caller names one, and the omission is NOT harmless: without it\n\t\t\t// `tab create` resolves the workspace the same way `--current` resolves a pane — from the\n\t\t\t// UI-focused space — so a caller filling a workspace it just opened would put its first tab\n\t\t\t// in the new space and every later one beside the pane it was RUN from. Verified against\n\t\t\t// 0.7.4, whose `tab create` takes `--workspace <workspace_id>` natively.\n\t\t\tconst within = opts.within ? ['--workspace', opts.within] : []\n\t\t\tconst out = exec('herdr', ['tab', 'create', ...within, '--cwd', opts.cwd, ...label, ...env, '--no-focus'])\n\t\t\tif (!out) throw new Error(withReason(exec, 'herdr tab create failed'))\n\t\t\topened = parseRootPaneId(out, 'herdr tab create')\n\t\t} else {\n\t\t\tconst direction = at === 'pane:down' ? 'down' : 'right'\n\t\t\t// Name the pane whenever the caller knows it. herdr's `--current` is not \"the pane that\n\t\t\t// called me\": it reads `$HERDR_PANE_ID` and, when that is unset, resolves to the UI-FOCUSED\n\t\t\t// pane instead — silently, so an unidentified caller splits whatever the user happens to be\n\t\t\t// looking at. Verified against herdr 0.7.4. `--current` is kept only as the last resort for\n\t\t\t// a caller that could not identify itself, where herdr's guess is still better than failing.\n\t\t\t// Native means no command-prefix fallback is needed, so a pane with env and NO command still\n\t\t\t// gets its env.\n\t\t\tconst from = opts.from ? [opts.from.id] : ['--current']\n\t\t\t// `--ratio` takes the seam's number VERBATIM: it sizes the original pane, which is exactly\n\t\t\t// what `ratio` means. tmux's `-l` sizes the new pane and therefore inverts — the one place\n\t\t\t// the two backends convert in opposite directions. Measured against 0.7.4 (splitting a\n\t\t\t// 201-column region at `--ratio 0.333` left the original 67 columns), not documented.\n\t\t\tconst size = opts.ratio != null ? ['--ratio', toHerdrRatio(opts.ratio)] : []\n\t\t\tconst out = exec('herdr', [\n\t\t\t\t'pane',\n\t\t\t\t'split',\n\t\t\t\t...from,\n\t\t\t\t'--direction',\n\t\t\t\tdirection,\n\t\t\t\t'--cwd',\n\t\t\t\topts.cwd,\n\t\t\t\t...size,\n\t\t\t\t...env,\n\t\t\t])\n\t\t\tif (!out) throw new Error(withReason(exec, 'herdr pane split failed'))\n\t\t\topened = parsePaneId(out)\n\t\t\t// Through `rename`, not a second `pane rename` spelled here: post-birth pane naming and the\n\t\t\t// seam's rename are the same act, so one spelling per backend is the only way the two cannot\n\t\t\t// drift.\n\t\t\tif (opts.label) herdrMuxAdapter.rename(exec, opened, 'pane', opts.label)\n\t\t}\n\t\t// `submit`, not `sendText` — a launch command has to actually run, and `submit` is the only\n\t\t// verb that supplies the Enter.\n\t\tif (opts.launch) herdrMuxAdapter.submit(exec, opened, opts.launch)\n\t\treturn opened\n\t},\n\n\trename(exec, target, tier, name) {\n\t\t// herdr spells both tiers the same way — `<tier> rename <id> <name>` — so the tier selects the\n\t\t// noun and nothing else. Neither verb focuses what it names, and neither creates anything.\n\t\t// `tab rename` is what makes a new workspace's root tab nameable at all: `workspace create`\n\t\t// labels that tab `1` and takes no flag for it (verified against 0.7.4).\n\t\texec('herdr', [tier, 'rename', target.id, name])\n\t},\n\n\tgroup() {\n\t\t// Deliberately empty, and that IS this backend's complete answer — not a stub and not a\n\t\t// degrade. herdr's workspace is a REAL tier: every pane and tab record already carries its\n\t\t// `workspace_id`, so the tier already is the group; and its tab label is the tab's own name,\n\t\t// never a composed display name, because its UI groups by the real workspace label and the walk\n\t\t// therefore composes nothing to prefix. Both facts the backend already holds, so no grouping\n\t\t// flag and no name flag reach herdr — storing either would duplicate a fact it never reads, and\n\t\t// herdr would have to be taught to read it. Same answer, and the same reason, as `open`'s unread\n\t\t// `workspaceGroup`.\n\t},\n\n\tworktree: herdrWorktreeCapability(),\n\n\tsendText(exec, target, text) {\n\t\t// herdr splits the two intents at its own CLI, so this maps straight onto `send-text` — no\n\t\t// literal-escaping flag needed (unlike tmux, whose one `send-keys` guesses between them).\n\t\texec('herdr', ['pane', 'send-text', target.id, text])\n\t},\n\n\tsendKeys(exec, target, keys) {\n\t\t// Verbatim: every core key is already herdr's own name for it, so there is nothing to rename.\n\t\t// herdr refuses a key it does not know (`unsupported key <k>`) rather than typing it — so at\n\t\t// THIS boundary the divergence is loud, unlike tmux, which types an unknown token instead.\n\t\t// That loudness stops here, though: `Exec` discards stderr and reports a failed command as\n\t\t// `null`, which this ignores, so the caller sees exit 0 either way. Surfacing it is the\n\t\t// `Exec` seam's job (it affects every verb), not this method's — a follow-up owns it.\n\t\texec('herdr', ['pane', 'send-keys', target.id, ...keys])\n\t},\n\n\tsubmit(exec, target, text) {\n\t\t// A bare Enter keystroke is the only form that types nothing by construction, which is what the\n\t\t// flush contract requires. (`pane run <id> \"\"` also presses Enter — verified against a live\n\t\t// herdr — so it would work; `send-keys Enter` says what it means.)\n\t\tif (!text) {\n\t\t\texec('herdr', ['pane', 'send-keys', target.id, 'Enter'])\n\t\t\treturn\n\t\t}\n\t\t// `pane run` submits text plus Enter atomically — herdr's documented preference over\n\t\t// send-text + send-keys Enter, and it types the text literally (a command named `Up` is typed,\n\t\t// not interpreted), which is exactly submit's guarantee.\n\t\texec('herdr', ['pane', 'run', target.id, text])\n\t},\n\n\tread(exec, target, opts?: MuxReadOptions | undefined) {\n\t\tconst args = ['pane', 'read', target.id, '--source', 'visible']\n\t\tif (opts?.lines != null) args.push('--lines', String(opts.lines))\n\t\treturn exec('herdr', args) ?? ''\n\t},\n\n\t/**\n\t * The one backend with a NATIVE wait: `pane wait-output` blocks in herdr itself (0.7.5), so no poll\n\t * loop is run here and no snapshot is pulled across the CLI boundary on every tick.\n\t *\n\t * `--source visible` is pinned rather than left to herdr's own default (`recent_unwrapped`, verified\n\t * against 0.7.5 — the help says `recent`). The seam's rule is that a wait searches exactly what\n\t * `read` returns, and `read` pins `visible` here; taking the default would make the same wait mean a\n\t * different snapshot on this backend than on every polling one.\n\t *\n\t * Telling a TIMEOUT (an answer) from a broken wait (a failure) is the whole difficulty, because herdr\n\t * spells both the same way: exit 1 with an error envelope on stderr, so `Exec` yields `null` for\n\t * either. Two tiers answer it, in order:\n\t *\n\t * 1. **The envelope's `code`**, when the runner captured stderr into `lastError` (verified against\n\t * 0.7.5: `{\"error\":{\"code\":\"timeout\",…}}` vs `{\"error\":{\"code\":\"pane_not_found\",…}}`). Exact.\n\t * 2. **A live pane that actually consumed the deadline**, when it did not. `Exec.lastError` is\n\t * specified as a diagnostic and NEVER a control-flow signal — a runner that discards stderr must\n\t * still work — so the code cannot be the only answer. Liveness alone is not enough either, and the\n\t * reason is a whole released version of the backend: herdr 0.7.4 has no `pane wait-output` at all,\n\t * so it answers with clap's usage text (not an envelope) INSTANTLY, and a liveness-only rule reads\n\t * that as \"timed out\" — a silently wrong answer for a wait that never ran. Elapsed time is the fact\n\t * that separates them and needs no stderr: a wait that returns in a fraction of its own timeout did\n\t * not wait. Both must hold — the pane is live AND the deadline was spent — or this throws.\n\t *\n\t * A timeout costs ONE extra `read`, because herdr's timeout envelope carries no snapshot and the seam\n\t * promises the caller the evidence its verdict was reached on. It is taken at the deadline, so it is\n\t * the same \"last look at the pane\" a polling backend returns, one poll interval later.\n\t */\n\tasync waitForOutput(exec, target, opts) {\n\t\tassertWaitPattern(opts)\n\t\tconst now = opts.now ?? (() => Date.now())\n\t\tconst pattern = opts.match != null ? ['--match', opts.match] : ['--regex', opts.regex as string]\n\t\tconst args = ['pane', 'wait-output', target.id, '--source', 'visible', '--timeout', String(opts.timeoutMs)]\n\t\targs.push(...pattern)\n\t\tif (opts.lines != null) args.push('--lines', String(opts.lines))\n\t\tconst started = now()\n\t\tconst out = exec('herdr', args)\n\t\tif (out == null) {\n\t\t\tif (!isHerdrWaitTimeout(exec, target, opts.timeoutMs, now() - started)) {\n\t\t\t\tthrow new Error(withReason(exec, `herdr pane wait-output failed for pane ${target.id}`))\n\t\t\t}\n\t\t\tconst readOpts = opts.lines != null ? { lines: opts.lines } : undefined\n\t\t\treturn { matched: false, output: herdrMuxAdapter.read(exec, target, readOpts) }\n\t\t}\n\t\treturn parseWaitOutput(out)\n\t},\n\n\tfocus(exec, target) {\n\t\t// `herdr pane focus` only accepts `--direction` (no by-id form), and a peer's pane can sit in\n\t\t// a different workspace/tab than the attached client — a single pane-level command can't beam\n\t\t// the client there. Resolve the pane's own workspace/tab from the backend first (`pane get`)\n\t\t// and drive the beam in order: workspace focus, then tab focus. A tab's active pane IS the\n\t\t// pane, so landing on the tab lands input focus on it — herdr has no separate by-id pane\n\t\t// focus to reach for. Resolution is attempted BEFORE any switch is issued, so an unresolvable\n\t\t// pane throws instead of a partial or false-success beam.\n\t\tconst out = exec('herdr', ['pane', 'get', target.id])\n\t\tconst { workspaceId, tabId } = parsePaneLocation(out, target.id)\n\t\texec('herdr', ['workspace', 'focus', workspaceId])\n\t\texec('herdr', ['tab', 'focus', tabId])\n\t},\n\n\tteardown(exec, target) {\n\t\texec('herdr', ['pane', 'close', target.id])\n\t},\n\n\tpaneExists(exec, target) {\n\t\t// `pane read` returns the pane's content for a live pane (empty string when the pane is empty),\n\t\t// and fails — Exec yields null — when the pane id no longer names a pane. A live pane is exactly\n\t\t// the non-null case; an empty live pane ('') must NOT read as gone.\n\t\treturn exec('herdr', ['pane', 'read', target.id, '--source', 'visible']) !== null\n\t},\n\n\tisPaneFocused(exec, target) {\n\t\t// `herdr pane get <id>` prints `{\"result\":{\"pane\":{...,\"focused\":true|false,...}}}` on success,\n\t\t// or `{\"error\":{\"code\":\"pane_not_found\",...}}` when the pane can no longer be resolved. Parse\n\t\t// defensively: a missing/non-boolean `focused`, an error envelope, null output, or a JSON parse\n\t\t// failure all fall through to unknown rather than a false `false`.\n\t\tconst out = exec('herdr', ['pane', 'get', target.id])\n\t\tif (out == null) return undefined\n\t\ttry {\n\t\t\tconst focused = JSON.parse(out)?.result?.pane?.focused\n\t\t\treturn typeof focused === 'boolean' ? focused : undefined\n\t\t} catch {\n\t\t\treturn undefined\n\t\t}\n\t},\n\n\tlistPanes(exec): LivePane[] {\n\t\tconst out = exec('herdr', ['pane', 'list'])\n\t\tif (!out) return []\n\t\tlet panes: unknown\n\t\ttry {\n\t\t\tpanes = JSON.parse(out)?.result?.panes\n\t\t} catch {\n\t\t\treturn []\n\t\t}\n\t\tif (!Array.isArray(panes)) return []\n\t\treturn panes\n\t\t\t.filter(\n\t\t\t\t(\n\t\t\t\t\tp,\n\t\t\t\t): p is {\n\t\t\t\t\tpane_id: string\n\t\t\t\t\tagent?: string | undefined\n\t\t\t\t\tagent_status?: unknown | undefined\n\t\t\t\t\tcwd?: string | undefined\n\t\t\t\t\tlabel?: string | undefined\n\t\t\t\t} => typeof p?.pane_id === 'string',\n\t\t\t)\n\t\t\t.map((p): LivePane => {\n\t\t\t\tconst harness = p.agent || undefined\n\t\t\t\t// Verbatim, and no comparison rule to tell an unnamed pane apart: herdr has no default\n\t\t\t\t// label — the key is absent from `pane list` until `pane rename` — so an omitted key IS\n\t\t\t\t// \"nobody named it\". `|| undefined` only collapses an empty-string label to absent, the\n\t\t\t\t// same normalization `harness` takes. tmux needs a title-vs-host heuristic here precisely\n\t\t\t\t// because it lacks this primitive. Each key is OMITTED when absent (conditional spread)\n\t\t\t\t// rather than carried as an explicit `undefined`, so these stay absent-or-present fields.\n\t\t\t\tconst label = p.label || undefined\n\t\t\t\t// herdr 0.7.5's per-pane agent-state feed (`agent_status`). Absent/empty/unrecognized folds\n\t\t\t\t// to OMITTED — never a false `'unknown'` — the same absent-not-false rule `harness`/`label`\n\t\t\t\t// follow: a missing feed is the field simply not being there. (`'unknown'` IS a valid value,\n\t\t\t\t// herdr's own for a pane it cannot classify, and passes through when present.)\n\t\t\t\tconst agentStatus = toAgentStatus(p.agent_status)\n\t\t\t\treturn {\n\t\t\t\t\tid: p.pane_id,\n\t\t\t\t\tmux: 'herdr' as const,\n\t\t\t\t\t...(harness !== undefined ? { harness } : {}),\n\t\t\t\t\t...(agentStatus !== undefined ? { agentStatus } : {}),\n\t\t\t\t\t...(p.cwd !== undefined ? { cwd: p.cwd } : {}),\n\t\t\t\t\t...(label !== undefined ? { label } : {}),\n\t\t\t\t}\n\t\t\t})\n\t},\n\n\tregions: {\n\t\tdescribeRegion(exec, target) {\n\t\t\t// Two calls, because herdr splits the answer across two verbs: `pane layout` reports the\n\t\t\t// region's rects (`layout.panes[].rect`) but carries no cwd and no label, while `pane list`\n\t\t\t// carries both and no geometry. Neither alone can build a template.\n\t\t\t//\n\t\t\t// `layout.splits[]` is deliberately ignored even though it reports `direction` and `ratio`\n\t\t\t// outright. It is FLAT — `[{id:\"split_0_root\",...},{id:\"split_1_0\",...}]` — so the tree is\n\t\t\t// recoverable only by parsing the parent out of that id string, a convention herdr's CLI help\n\t\t\t// never documents and could respell without warning. The rects say the same thing in a fact\n\t\t\t// herdr does promise, so the derivation runs off those; see `RegionInspector.describeRegion` in `mux.ts`.\n\t\t\t// Best-effort: a region whose geometry is known is still worth exporting when the cwd/label\n\t\t\t// lookup fails — the geometry is the verbose part, and the missing dirs are visibly absent.\n\t\t\treturn herdrRegionPanes(exec, target.id, herdrPaneDetails(exec))\n\t\t},\n\n\t\t/**\n\t\t * herdr HAS a workspace tier, so the workspace is a fact the backend holds rather than one\n\t\t * cyber-mux has to reconstruct: the caller's pane names its `workspace_id`, `tab list --workspace`\n\t\t * enumerates that workspace's tabs, and `pane list --workspace` hands back every pane already\n\t\t * stamped with the tab it sits in. No grouping tag is read here and none is written — the tier IS\n\t\t * the group, which is exactly why `open` ignores `workspaceGroup` on this backend.\n\t\t *\n\t\t * The one indirection: geometry is per-PANE (`pane layout --pane`), never per-tab, so each tab's\n\t\t * rects are fetched through any one pane that sits in it. That is safe and race-free, and both\n\t\t * halves were established against 0.7.4: `pane layout` reports live geometry for an UNFOCUSED tab\n\t\t * in a DIFFERENT workspace, so nothing has to be focused first and nothing moves while this runs.\n\t\t *\n\t\t * herdr's own native per-tab layout export would be the obvious road — it takes a `tab_id` — but\n\t\t * `layout` is NOT a CLI verb in 0.7.4; it is socket-API-only, and this adapter speaks the CLI by\n\t\t * design (so it composes with the synchronous `Exec` seam). The road is closed, hence the pane\n\t\t * indirection.\n\t\t */\n\t\tdescribeWorkspace(exec, target) {\n\t\t\tconst { workspaceId } = parsePaneRecord(exec('herdr', ['pane', 'get', target.id]))\n\t\t\tif (!workspaceId) {\n\t\t\t\tthrow new Error(withReason(exec, `herdr could not resolve the workspace around pane ${target.id}`))\n\t\t\t}\n\t\t\tconst out = exec('herdr', ['tab', 'list', '--workspace', workspaceId])\n\t\t\tif (!out) throw new Error(withReason(exec, `herdr could not enumerate the tabs of workspace ${workspaceId}`))\n\t\t\tlet reported: unknown\n\t\t\ttry {\n\t\t\t\treported = JSON.parse(out)?.result?.tabs\n\t\t\t} catch {\n\t\t\t\tthrow new Error(`herdr tab list returned unparseable output: ${out.slice(0, 200)}`)\n\t\t\t}\n\t\t\tif (!Array.isArray(reported) || reported.length === 0) {\n\t\t\t\tthrow new Error(`herdr reported no tabs in workspace ${workspaceId}: ${out.slice(0, 200)}`)\n\t\t\t}\n\t\t\t// Scoped to the workspace, so a busy machine's other workspaces never reach the capture. Every\n\t\t\t// pane arrives stamped with its `tab_id`, which is what makes ONE call enough for every tab.\n\t\t\tconst details = herdrPaneDetails(exec, workspaceId)\n\t\t\tconst tabs: WorkspaceTab[] = []\n\t\t\tfor (const reportedTab of reported) {\n\t\t\t\tif (typeof reportedTab?.tab_id !== 'string') continue\n\t\t\t\tconst tabId: string = reportedTab.tab_id\n\t\t\t\t// Any pane of the tab will do — `pane layout` reports the whole region the pane sits in, so\n\t\t\t\t// which one is asked is immaterial.\n\t\t\t\tconst anchor = [...details].find(([, detail]) => detail.tab === tabId)?.[0]\n\t\t\t\tif (!anchor) throw new Error(`herdr reported no panes in tab ${tabId} of workspace ${workspaceId}`)\n\t\t\t\tconst tab: WorkspaceTab = { id: tabId, panes: herdrRegionPanes(exec, anchor, details) }\n\t\t\t\t// Verbatim, never parsed: herdr labels a tab with the tab's own name because the real\n\t\t\t\t// workspace tier already carries the grouping, so there is nothing composed to take apart.\n\t\t\t\tif (typeof reportedTab.label === 'string' && reportedTab.label !== '') tab.label = reportedTab.label\n\t\t\t\ttabs.push(tab)\n\t\t\t}\n\t\t\tif (tabs.length === 0)\n\t\t\t\tthrow new Error(`herdr reported no usable tabs in workspace ${workspaceId}: ${out.slice(0, 200)}`)\n\t\t\treturn tabs\n\t\t},\n\t},\n\n\tagentLifecycle: {\n\t\twaitForState(exec, target, opts) {\n\t\t\t// `herdr agent wait <id> [--until <s>]… [--timeout <ms>]`, addressing the pane id directly\n\t\t\t// (verified against 0.7.5: `w3A:p1` worked). `--until` is REPEATED once per requested state,\n\t\t\t// and OMITTED entirely when the caller passed none — so herdr's own default set (idle|done|\n\t\t\t// blocked) applies rather than cyber-mux restating it, and a future change to that default is\n\t\t\t// not silently pinned here. `--timeout` is likewise omitted for an indefinite wait, herdr's own\n\t\t\t// no-timeout behavior.\n\t\t\tconst until = opts.until ?? []\n\t\t\tconst args = [\n\t\t\t\t'agent',\n\t\t\t\t'wait',\n\t\t\t\ttarget.id,\n\t\t\t\t...until.flatMap((state) => ['--until', state]),\n\t\t\t\t...(opts.timeoutMs != null ? ['--timeout', String(opts.timeoutMs)] : []),\n\t\t\t]\n\t\t\tconst status = parseReachedAgentStatus(exec('herdr', args))\n\t\t\tif (!status) {\n\t\t\t\tthrow new Error(withReason(exec, `herdr agent wait reported no reached agent_status for pane ${target.id}`))\n\t\t\t}\n\t\t\treturn status\n\t\t},\n\t},\n}\n\n/**\n * The set of `agent_status` values herdr 0.7.5 reports — the runtime witness of the `AgentStatus`\n * type, so a string read off a herdr envelope can be NARROWED to it rather than cast. A value outside\n * this set is treated as absent (the feed said something this build does not model), never forced into\n * the type.\n */\nconst AGENT_STATUSES: readonly AgentStatus[] = ['idle', 'working', 'blocked', 'done', 'unknown']\n\n/** A value narrowed to `AgentStatus`, or `undefined` for anything else (a non-string, an empty string,\n * or a status this build does not model) — the normalization both the listing and the wait share. */\nfunction toAgentStatus(value: unknown): AgentStatus | undefined {\n\treturn typeof value === 'string' && (AGENT_STATUSES as readonly string[]).includes(value)\n\t\t? (value as AgentStatus)\n\t\t: undefined\n}\n\n/**\n * The `AgentStatus` a `herdr agent wait` run reached, read defensively from its JSON envelope —\n * `{\"result\":{\"agent\":{…,\"agent_status\":\"idle\",…},\"type\":\"agent_info\"}}` (verified against 0.7.5), so\n * the reached status lives at `.result.agent.agent_status`. Every unresolvable shape — `out` is null\n * (an Exec failure), the JSON does not parse, or the field is missing/empty/unmodeled — folds to\n * `undefined`, exactly as `parsePaneRecord`/`isPaneFocused` fold, so the caller states its own failure.\n */\nfunction parseReachedAgentStatus(out: string | null): AgentStatus | undefined {\n\tif (out == null) return undefined\n\ttry {\n\t\treturn toAgentStatus(JSON.parse(out)?.result?.agent?.agent_status)\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\n/**\n * The rects of the region `paneId` sits in, joined with the cwd/label half.\n *\n * Two sources, because herdr splits the answer across two verbs: `pane layout` reports the region's\n * rects (`layout.panes[].rect`) but carries no cwd and no label, while `pane list` carries both and\n * no geometry. Neither alone can build a template — hence `details` is passed IN, so a caller reading\n * many tabs pays for that list once rather than once per tab.\n *\n * `layout.splits[]` is deliberately ignored even though it reports `direction` and `ratio` outright.\n * It is FLAT — `[{id:\"split_0_root\",...},{id:\"split_1_0\",...}]` — so the tree is recoverable only by\n * parsing the parent out of that id string, a convention herdr's CLI help never documents and could\n * respell without warning. The rects say the same thing in a fact herdr does promise, so the\n * derivation runs off those; see `RegionInspector.describeRegion` in `mux.ts`.\n */\nfunction herdrRegionPanes(exec: Exec, paneId: string, details: Map<string, HerdrPaneDetail>): RegionPane[] {\n\tconst out = exec('herdr', ['pane', 'layout', '--pane', paneId])\n\tif (!out) throw new Error(withReason(exec, `herdr could not describe the region around pane ${paneId}`))\n\tlet reported: unknown\n\ttry {\n\t\treported = JSON.parse(out)?.result?.layout?.panes\n\t} catch {\n\t\tthrow new Error(`herdr pane layout returned unparseable output: ${out.slice(0, 200)}`)\n\t}\n\tif (!Array.isArray(reported) || reported.length === 0) {\n\t\tthrow new Error(`herdr pane layout reported no panes for ${paneId}: ${out.slice(0, 200)}`)\n\t}\n\treturn reported\n\t\t.filter((p): p is { pane_id: string; rect: Record<string, number> } => typeof p?.pane_id === 'string')\n\t\t.map((p) => {\n\t\t\tconst detail = details.get(p.pane_id)\n\t\t\tconst pane: RegionPane = {\n\t\t\t\tid: p.pane_id,\n\t\t\t\t// Screen-absolute, unlike tmux's window-relative origin — which is why nothing downstream\n\t\t\t\t// may assume a region starts at 0,0. See `PaneRect`.\n\t\t\t\trect: {\n\t\t\t\t\tx: p.rect?.['x'] ?? 0,\n\t\t\t\t\ty: p.rect?.['y'] ?? 0,\n\t\t\t\t\twidth: p.rect?.['width'] ?? 0,\n\t\t\t\t\theight: p.rect?.['height'] ?? 0,\n\t\t\t\t},\n\t\t\t}\n\t\t\tif (detail?.cwd) pane.cwd = detail.cwd\n\t\t\t// herdr has no default label — the key is absent until `pane rename`, so whatever is here\n\t\t\t// is one the author set. No hostname filtering needed, unlike tmux.\n\t\t\tif (detail?.label) pane.label = detail.label\n\t\t\treturn pane\n\t\t})\n}\n\n/** What `pane list` knows and `pane layout` does not: a pane's cwd, its label, and the tab it sits in. */\ninterface HerdrPaneDetail {\n\tcwd?: string | undefined\n\tlabel?: string | undefined\n\t/** The tab this pane sits in — herdr stamps every pane record with it. */\n\ttab?: string | undefined\n}\n\n/**\n * Each pane's cwd, label and tab, keyed by pane id — the half `pane layout` does not report.\n *\n * `workspace` scopes the list to one workspace when the caller has one to scope by; omitting it lists\n * every pane herdr can see, which is what a single-region read wants (it keys by pane id and never\n * cares which workspace a pane came from).\n */\nfunction herdrPaneDetails(exec: Exec, workspace?: string | undefined): Map<string, HerdrPaneDetail> {\n\tconst details = new Map<string, HerdrPaneDetail>()\n\tconst out = exec('herdr', ['pane', 'list', ...(workspace ? ['--workspace', workspace] : [])])\n\tif (!out) return details\n\tlet panes: unknown\n\ttry {\n\t\tpanes = JSON.parse(out)?.result?.panes\n\t} catch {\n\t\treturn details\n\t}\n\tif (!Array.isArray(panes)) return details\n\tfor (const pane of panes) {\n\t\tif (typeof pane?.pane_id !== 'string') continue\n\t\tdetails.set(pane.pane_id, { cwd: pane.cwd, label: pane.label, tab: pane.tab_id })\n\t}\n\treturn details\n}\n\n/**\n * herdr's repeatable `--env KEY=VALUE` — spelled the same way by exactly three verbs: `pane split`,\n * `workspace create` and `tab create`, each backed by a native `env` Record in the socket schema\n * (protocol 16).\n *\n * `worktree create`/`worktree open` are deliberately NOT in that list: their params are\n * `[base, branch, cwd, focus, label, path, workspace_id]` and\n * `[branch, cwd, focus, label, path, workspace_id]` — no `env` — and 0.7.4 rejects the flag with\n * `unknown option: --env`. A caller needing env on that route uses the command-prefix fallback.\n */\nfunction envFlags(env: Record<string, string> | undefined): string[] {\n\treturn env ? Object.entries(env).flatMap(([k, v]) => ['--env', `${k}=${v}`]) : []\n}\n\n/**\n * `--ratio` takes the seam's number VERBATIM — herdr sizes the ORIGINAL pane, so no inversion, unlike\n * tmux's `-l` and wezterm's `--percent`. The guard is the same one those two render helpers call: the\n * seam refuses an out-of-range ratio here rather than pass `--ratio 5` (or `0`) through to a split herdr\n * would then size wrong.\n */\nfunction toHerdrRatio(ratio: number): string {\n\tassertRatioInRange(ratio)\n\treturn String(ratio)\n}\n\n/**\n * Launch a command in a worktree's root pane, carrying env the worktree verb could not set at birth.\n * The prefix-or-warn rule is the seam's (`env-fallback.ts`); this is the one route that invokes it,\n * because it is the one route that loses env. With a command, env rides in as a prefix; with none and\n * env asked for, it warns to stderr (stdout stays machine-readable) rather than dropping in silence.\n */\nfunction carryLaunch(\n\texec: Exec,\n\ttarget: OpenedPane,\n\tenv: Record<string, string> | undefined,\n\tlaunch?: string | undefined,\n): void {\n\tconst fallback = envFallback(env, launch)\n\tif (fallback.kind === 'dropped') {\n\t\tprocess.stderr.write(\n\t\t\t`env (${fallback.variables.join(', ')}) could not be set on this worktree's workspace and ` +\n\t\t\t\t'no command was given to carry it — herdr worktree create/open take no env parameter\\n',\n\t\t)\n\t\treturn\n\t}\n\tif (fallback.command !== undefined) herdrMuxAdapter.submit(exec, target, fallback.command)\n}\n\n/**\n * `herdr pane split` emits a JSON envelope, not a bare id:\n * `{\"id\":\"cli:pane:split\",\"result\":{\"pane\":{\"pane_id\":\"w3:pB\", ...},\"type\":\"pane_info\"}}`.\n * The pane id herdr's other `pane` subcommands accept lives at `.result.pane.pane_id`. Extract it —\n * passing the whole blob downstream lands it in a filename and blows the path length limit.\n */\nfunction parsePaneId(out: string): OpenedPane {\n\treturn parseOpenedPane(out, 'herdr pane split', 'pane')\n}\n\n/**\n * `herdr pane get <id>` emits `{\"result\":{\"pane\":{\"workspace_id\":...,\"tab_id\":...,...}}}`, or an\n * error envelope when the id no longer names a live pane. Every unresolvable shape — `out` is null\n * (an Exec failure), the JSON does not parse, or a field is missing/empty/not a string — folds to the\n * field simply being absent, so each caller states its OWN failure rather than inheriting one\n * phrased for somebody else's verb.\n */\nfunction parsePaneRecord(out: string | null): { workspaceId?: string | undefined; tabId?: string | undefined } {\n\tif (out == null) return {}\n\ttry {\n\t\tconst pane = JSON.parse(out)?.result?.pane\n\t\treturn { workspaceId: nonEmpty(pane?.workspace_id), tabId: nonEmpty(pane?.tab_id) }\n\t} catch {\n\t\treturn {}\n\t}\n}\n\nfunction nonEmpty(value: unknown): string | undefined {\n\treturn typeof value === 'string' && value !== '' ? value : undefined\n}\n\n/**\n * The `code` of a herdr error envelope, when the runner captured one — how a wait's TIMEOUT (an answer)\n * is told from any other failure (a throw). Read from `Exec.lastError` because that is where herdr's\n * envelope lands: it is written to stderr with exit 1, so stdout is `null` for every failure alike and\n * the code is the only thing that separates them. Defensive throughout — no reason, unparseable JSON, or\n * a missing/non-string code all answer `undefined`, which routes to the throw rather than to a silent\n * \"timed out\" the backend never said.\n */\n/**\n * How much of its own timeout a wait must actually spend before a failure is believed to BE that\n * timeout. A fraction rather than the whole, because process start-up and clock granularity make an\n * exact-or-greater comparison flaky on a real runner; wide enough that the case it exists to catch — a\n * herdr with no `wait-output` subcommand, which returns in milliseconds — is nowhere near it.\n */\nconst HERDR_WAIT_ELAPSED_RATIO = 0.9\n\n/**\n * Whether a failed `pane wait-output` was the DEADLINE passing rather than the wait breaking — the\n * two-tier rule `waitForOutput` documents, kept out of the method so the tiers read as one decision.\n *\n * The envelope's code answers when the runner captured one. Otherwise the answer needs two facts, and\n * neither alone is enough: the pane must be LIVE (a gone pane is a failure, `pollForOutput`'s rule) and\n * the call must have SPENT the deadline (a wait that returned instantly never ran — herdr 0.7.4, whose\n * usage text for an unknown subcommand is not an envelope to read a code from).\n */\nfunction isHerdrWaitTimeout(exec: Exec, target: { id: string }, timeoutMs: number, elapsedMs: number): boolean {\n\tconst code = herdrErrorCode(exec.lastError)\n\tif (code != null) return code === 'timeout'\n\tif (elapsedMs < timeoutMs * HERDR_WAIT_ELAPSED_RATIO) return false\n\treturn herdrMuxAdapter.paneExists(exec, target)\n}\n\nfunction herdrErrorCode(reason: string | undefined): string | undefined {\n\tif (!reason) return undefined\n\ttry {\n\t\treturn nonEmpty(JSON.parse(reason)?.error?.code)\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\n/**\n * A successful `pane wait-output` envelope: the snapshot it matched in, and the line it matched on.\n *\n * `matched` is `true` by construction — herdr exits 0 only on a match, so reaching here IS the match;\n * the parse only fills in the evidence. Defensive for the same reason `parsePaneRecord` is: a herdr\n * build that reshapes the envelope degrades to a match with no snapshot, never to a failed wait.\n */\nfunction parseWaitOutput(out: string): MuxWaitResult {\n\tlet text: string | undefined\n\tlet line: string | undefined\n\ttry {\n\t\tconst result = JSON.parse(out)?.result\n\t\ttext = nonEmpty(result?.read?.text)\n\t\tline = nonEmpty(result?.matched_line)\n\t} catch {\n\t\t// Leave both absent — see above.\n\t}\n\treturn { matched: true, output: text ?? '', ...(line != null ? { matchedLine: line } : {}) }\n}\n\n/**\n * The pane's workspace and tab, or a throw — so `focus` never issues a workspace/tab switch against a\n * pane it couldn't actually resolve.\n */\nfunction parsePaneLocation(out: string | null, id: string): { workspaceId: string; tabId: string } {\n\tconst { workspaceId, tabId } = parsePaneRecord(out)\n\tif (!workspaceId || !tabId) throw new Error(`peer's pane ${id} could not be resolved to beam to`)\n\treturn { workspaceId, tabId }\n}\n\n/**\n * herdr binds a git worktree to a workspace as a first-class record, and that binding is what its UI\n * groups a repo's checkouts by. Only `worktree create`/`worktree open` produce it: `git worktree add`\n * followed by `workspace create --cwd <checkout>` yields a workspace herdr does not know is a\n * worktree at all, left out of the group. Hence this capability — see `WorktreeWorkspaceCapability`\n * for what it deliberately does not own.\n *\n * Every call pins the source repo with `--cwd <primaryRoot>` rather than relying on the caller's\n * ambient process cwd (matching how the git adapter always passes `-C <primaryRoot>`), and opens\n * with `--no-focus` so spawning never steals the caller's attention.\n */\nfunction herdrWorktreeCapability(): WorktreeWorkspaceCapability {\n\treturn {\n\t\tcreateInWorkspace(exec, opts) {\n\t\t\tconst args = ['worktree', 'create', '--cwd', opts.primaryRoot, '--branch', opts.branch, '--path', opts.path]\n\t\t\tif (opts.base) args.push('--base', opts.base)\n\t\t\t// Without this herdr names the workspace after the checkout path's basename, because we always\n\t\t\t// pass `--path` — it would use the branch if we let it choose the location itself.\n\t\t\tif (opts.label) args.push('--label', opts.label)\n\t\t\t// Deliberately NO `--env`, unlike every other tier: `WorktreeCreateParams` is\n\t\t\t// `[base, branch, cwd, focus, label, path, workspace_id]` — no `env` — and herdr 0.7.4\n\t\t\t// rejects the flag outright (`unknown option: --env`), which `Exec` would turn into a null\n\t\t\t// and this into a thrown \"worktree create failed\". `opts.env` is NOT emitted to herdr;\n\t\t\t// `carryLaunch` compensates for it on the launch instead — this is the one route that loses\n\t\t\t// env, so it is the one route that invokes the fallback.\n\t\t\targs.push('--no-focus')\n\t\t\tconst out = exec('herdr', args)\n\t\t\tif (!out) throw new Error(withReason(exec, 'herdr worktree create failed'))\n\t\t\tconst created = parseWorktreeWorkspace(out, 'herdr worktree create')\n\t\t\t// This route could not set env at birth, so it compensates on the launch. `submit`, not\n\t\t\t// `sendText` — a launch command has to actually run, and `submit` is the only verb that\n\t\t\t// supplies the Enter. (It lowers to `pane run`, herdr's atomic text-plus-Enter primitive.)\n\t\t\tcarryLaunch(exec, created.target, opts.env, opts.launch)\n\t\t\treturn created\n\t\t},\n\n\t\topenInWorkspace(exec, opts) {\n\t\t\tconst args = ['worktree', 'open', '--cwd', opts.primaryRoot, '--path', opts.path]\n\t\t\tif (opts.label) args.push('--label', opts.label)\n\t\t\t// No `--env` here either — `WorktreeOpenParams` is\n\t\t\t// `[branch, cwd, focus, label, path, workspace_id]`. See `createInWorkspace`.\n\t\t\targs.push('--no-focus')\n\t\t\tconst out = exec('herdr', args)\n\t\t\tif (!out) throw new Error(withReason(exec, 'herdr worktree open failed'))\n\t\t\tconst opened = parseWorktreeWorkspace(out, 'herdr worktree open')\n\t\t\t// Same env compensation as `createInWorkspace` — `worktree open` is exposed identically,\n\t\t\t// taking no env param, so a caller passing env here would lose it just the same.\n\t\t\tcarryLaunch(exec, opened.target, opts.env, opts.launch)\n\t\t\treturn opened\n\t\t},\n\n\t\tbindings(exec, opts) {\n\t\t\treturn parseWorktreeBindings(exec('herdr', ['worktree', 'list', '--cwd', opts.primaryRoot]))\n\t\t},\n\n\t\treleaseWorkspace(exec, workspace) {\n\t\t\t// Closes the workspace only — the checkout stays on disk for `git worktree remove` to take\n\t\t\t// under cyber-mux's own gates. Verified against a live herdr: worktrees survive the close.\n\t\t\texec('herdr', ['workspace', 'close', workspace])\n\t\t},\n\t}\n}\n\n/**\n * `herdr workspace create` and `herdr tab create` both emit their new root pane at\n * `.result.root_pane.pane_id` (a different path than `pane split`'s `.result.pane.pane_id`).\n * `label` names the command in error messages (e.g. \"herdr workspace create\").\n */\nfunction parseRootPaneId(out: string, label: string): OpenedPane {\n\treturn parseOpenedPane(out, label, 'root_pane')\n}\n\n/**\n * Every pane herdr emits carries its own `workspace_id` alongside its `pane_id`, on EVERY route —\n * `workspace create` (which reports the workspace it just made), `tab create` (the workspace the tab\n * was created in), and `pane split` (the workspace the split landed in, i.e. the caller's). Verified\n * against herdr 0.7.4. That is why the workspace costs no extra call: it rides in on the same output\n * the pane id is already read from, so probing for it separately would buy nothing and cost a round\n * trip per open.\n *\n * The pane id is required — a route that cannot name its pane has failed. The workspace is NOT: it\n * is read opportunistically and left absent when missing rather than throwing, so a herdr build that\n * stops emitting it degrades to \"cannot say\" instead of breaking `open` outright. Absent is a\n * meaning this seam already has (`OpenedPane.workspace`); a hard failure here would be inventing a\n * new one for a field no caller is required to use.\n */\nfunction parseOpenedPane(out: string, label: string, key: 'pane' | 'root_pane'): OpenedPane {\n\tlet pane:\n\t\t| { pane_id?: unknown | undefined; tab_id?: unknown | undefined; workspace_id?: unknown | undefined }\n\t\t| undefined\n\ttry {\n\t\tpane = JSON.parse(out)?.result?.[key]\n\t} catch {\n\t\tthrow new Error(`${label} returned unparseable output: ${out.slice(0, 200)}`)\n\t}\n\tconst paneId = pane?.pane_id\n\tif (typeof paneId !== 'string' || paneId === '') {\n\t\tthrow new Error(`${label} output had no result.${key}.pane_id: ${out.slice(0, 200)}`)\n\t}\n\t// The pane's OWN tab, carried in the same envelope on every route — a created tab reports itself,\n\t// a created workspace reports its root tab, a split reports the tab it landed in. Read here rather\n\t// than from the sibling `result.tab`, which only the tab route has, so one spelling serves all\n\t// three.\n\t//\n\t// Throws when absent, unlike `workspace` below: `OpenedPane.tab` is required because every\n\t// multiplexer has the Tab level, so a herdr envelope with no `tab_id` is herdr failing to answer\n\t// a question it always answers, not a tier it lacks. Returning a pane with no tab would hand the\n\t// caller a rename target it could only get wrong.\n\tconst tab = pane?.tab_id\n\tif (typeof tab !== 'string' || tab === '') {\n\t\tthrow new Error(`${label} output had no result.${key}.tab_id: ${out.slice(0, 200)}`)\n\t}\n\tconst workspace = pane?.workspace_id\n\treturn typeof workspace === 'string' && workspace !== '' ? { id: paneId, tab, workspace } : { id: paneId, tab }\n}\n\n/**\n * `herdr worktree create` and `herdr worktree open` emit the same envelope: the root pane at\n * `.result.root_pane.pane_id` (as `workspace create` does), the checkout at\n * `.result.worktree.{path,branch}`, and the bound workspace at `.result.workspace.workspace_id`.\n * That workspace id IS the binding — the whole reason to route through these instead of plain git.\n * `label` names the command in error messages (e.g. \"herdr worktree create\").\n *\n * The root pane is read through `parseOpenedPane`, NOT re-parsed here: `root_pane` is the same record\n * `workspace create` emits, so it carries the same `tab_id`, and one spelling is what keeps the two\n * routes from disagreeing about a field both report. That tab is the region's root tab — what lets a\n * caller handed this workspace group or rename it without reaching for the pane id, which would be\n * green on tmux and silently broken on herdr.\n */\nfunction parseWorktreeWorkspace(out: string, label: string): WorktreeWorkspace {\n\tlet parsed: unknown\n\ttry {\n\t\tparsed = JSON.parse(out)\n\t} catch {\n\t\tthrow new Error(`${label} returned unparseable output: ${out.slice(0, 200)}`)\n\t}\n\tconst result = (parsed as { result?: unknown | undefined })?.result as\n\t\t| {\n\t\t\t\tworkspace?: { workspace_id?: unknown | undefined } | undefined\n\t\t\t\tworktree?: { path?: unknown | undefined; branch?: unknown | undefined } | undefined\n\t\t }\n\t\t| undefined\n\t// Throws on a missing pane id or tab id, with `parseOpenedPane`'s own message.\n\tconst target = parseOpenedPane(out, label, 'root_pane')\n\tconst workspace = result?.workspace?.workspace_id\n\tconst path = result?.worktree?.path\n\tconst branch = result?.worktree?.branch\n\tif (typeof path !== 'string' || path === '' || typeof branch !== 'string' || branch === '') {\n\t\tthrow new Error(`${label} output had no result.worktree.{path,branch}: ${out.slice(0, 200)}`)\n\t}\n\tif (typeof workspace !== 'string' || workspace === '') {\n\t\tthrow new Error(`${label} output had no result.workspace.workspace_id: ${out.slice(0, 200)}`)\n\t}\n\treturn { target, worktree: { root: resolve(path), branch }, workspace }\n}\n\n/**\n * `herdr worktree list` reports every worktree of the repo, each carrying `open_workspace_id` ONLY\n * while a workspace is currently open on it. Everything else it reports (branch, linked, prunable)\n * is herdr re-reading git — deliberately ignored here; git answers those for every backend.\n * Defensive like `listPanes`: a query that cannot be read reports nothing rather than throwing.\n */\nfunction parseWorktreeBindings(out: string | null): Map<string, string> {\n\tconst bindings = new Map<string, string>()\n\tif (!out) return bindings\n\tlet parsed: unknown\n\ttry {\n\t\tparsed = JSON.parse(out)\n\t} catch {\n\t\treturn bindings\n\t}\n\tconst worktrees = ((parsed as { result?: { worktrees?: unknown | undefined } | undefined })?.result?.worktrees ??\n\t\t[]) as unknown\n\tif (!Array.isArray(worktrees)) return bindings\n\tfor (const entry of worktrees as { path?: unknown | undefined; open_workspace_id?: unknown | undefined }[]) {\n\t\tconst path = entry?.path\n\t\tconst workspace = entry?.open_workspace_id\n\t\tif (typeof path === 'string' && path !== '' && typeof workspace === 'string' && workspace !== '') {\n\t\t\tbindings.set(normalizeWorktreePath(path), workspace)\n\t\t}\n\t}\n\treturn bindings\n}\n","import type { Exec } from './exec.ts'\nimport { withReason } from './exec.ts'\nimport type { LivePane, MuxAdapter, MuxReadOptions, OpenedPane, RegionPane, WorkspaceTab } from './mux.ts'\nimport { assertRatioInRange } from './ratio.ts'\nimport { pollForOutput } from './wait-output.ts'\n\n/**\n * The tmux window user option `MuxOpenOptions.workspaceGroup` is stored in. A user option (the\n * `@` prefix) is tmux's own mechanism for a value it stores but never interprets, so tmux carries\n * the tag without cyber-mux teaching it anything: it survives a window rename, and `list-windows`\n * both reads it back (`#{@cm_ws}`) and filters on it server-side (`-f '#{==:#{@cm_ws},<id>}'`).\n *\n * Named here rather than spelled at each use so the write side and every read side cannot drift.\n * Server-lifetime, like every window: it dies with the tmux server, along with the windows it tags.\n */\nexport const TMUX_WORKSPACE_GROUP_OPTION = '@cm_ws'\n\n/**\n * The tmux window user option a grouped window's OWN name is stored in — the name the caller gave the\n * tab, beside the group id, because tmux's single `window_name` field no longer holds it.\n *\n * tmux has ONE name field per space. A caller that composes a display name out of a tab's name\n * (`pool - editor`) has destroyed `editor`, and there is no sound way back: splitting on the separator\n * is ambiguous (`acme - beta - main` reads two legal ways), and reading the display name verbatim\n * re-prefixes it on every round trip (`pool - pool - editor`). So the original is stored here and read\n * back from here — the same rule the group id follows, one tier down. The display name is a human's to\n * read; this is what a machine reads.\n *\n * A user option (the `@` prefix) for `TMUX_WORKSPACE_GROUP_OPTION`'s reasons exactly: tmux stores it\n * without interpreting it, it survives a window rename, and `list-windows` reads it back\n * (`#{@cm_tab}`). Named here rather than spelled at each use so the write side and every read side\n * cannot drift.\n */\nexport const TMUX_TAB_NAME_OPTION = '@cm_tab'\n\n/** tmux backend — detected via `$TMUX`. */\nexport const tmuxMuxAdapter: MuxAdapter = {\n\tname: 'tmux',\n\n\t// `split-window -l N%` sizes a split; see `toTmuxSize` for the inversion it needs.\n\tcanSizeSplits: true,\n\n\topen(exec, opts) {\n\t\t// tmux has fewer tiers than herdr: no Workspace level, and \"window\" is its name for the Tab\n\t\t// concept. So both 'workspace' (own visible space) and 'tab' collapse to a new WINDOW — the\n\t\t// finest \"own visible space\" unit tmux offers, visible in the status bar and reachable by\n\t\t// `select-window` (which cross-window beaming/focus relies on). `-d` opens it in the\n\t\t// background without stealing the caller's focus. A pane is never placed in a detached\n\t\t// (`new-session -d`) session — that is invisible to the attached client and unreachable by\n\t\t// beaming; a truly-detached session would be a separate explicit intent.\n\t\tconst at = opts.at ?? 'tab'\n\t\tconst window = at === 'workspace' || at === 'tab'\n\t\t// `-e` is on BOTH `split-window` and `new-window` (tmux(1): `new-window [-abdkPS] [-c\n\t\t// start-directory] [-e environment] ...`), so env is native at EVERY tier — which it must be:\n\t\t// a template's root pane is the region's own pane, born by the window open rather than by any\n\t\t// split, so scoping this to the split path would silently drop that pane's env.\n\t\tconst env = opts.env ? Object.entries(opts.env).flatMap(([k, v]) => ['-e', `${k}=${v}`]) : []\n\t\t// A group id tags the WINDOW this open created. It no longer drives the FORMAT — `#{window_id}`\n\t\t// is now asked for unconditionally, because `OpenedPane.tab` is required and tmux's Tab is its\n\t\t// Window, so every open owes the window it landed in. This deliberately gives up the property\n\t\t// that an ungrouped open emitted byte-identical argv to before: tmux only reports what `-F`\n\t\t// asks for, and deriving the window from the pane afterwards would be the extra call the field\n\t\t// is specified to cost nothing. An honest, uniformly-present field beats identical argv.\n\t\t//\n\t\t// `group` still gates the tagging itself: a caller that did not ask is not grouped, and a split\n\t\t// creates no window of its own to tag (tagging the caller's would group a space the caller\n\t\t// never opened).\n\t\tconst group = window && opts.workspaceGroup != null\n\t\t// A tab and a real \\t: the pane id and window id are both `%`/`@`-prefixed and contain no\n\t\t// whitespace, so a tab separates them unambiguously.\n\t\tconst format = '#{pane_id}\\t#{window_id}'\n\t\t// `from` and `size` are declared INSIDE the split branch, and that placement is the whole\n\t\t// defense — not a guard. Both are pane concepts: `-t` targets the pane to split, `-l` sizes the\n\t\t// split against it, and a window is neither placed nor sized relative to a pane. Scoping them\n\t\t// here is what makes the leak unwritable: the window branch cannot spread a value that is not\n\t\t// in its scope, so wiring one in is a compile error rather than a wrong flag. A `!window` guard\n\t\t// at function scope would be strictly weaker — it leaves the value reachable and merely empties\n\t\t// it, so the same mistake compiles and goes quiet. herdr scopes these identically; this is the\n\t\t// one adapter that had not caught up.\n\t\tlet args: string[]\n\t\tif (window) {\n\t\t\t// `-d` keeps focus on the caller (opens the window in the background) — without it tmux\n\t\t\t// switches the attached client to the new window, stealing the caller's focus. The returned\n\t\t\t// pane id and subsequent `send-keys -t` still target the new pane.\n\t\t\targs = ['new-window', '-d', ...env, '-c', opts.cwd, '-P', '-F', format]\n\t\t} else {\n\t\t\t// `-t` whenever the caller names a pane. Without it tmux does NOT split the calling pane — it\n\t\t\t// splits the session's ACTIVE pane, ignoring `$TMUX_PANE` outright (verified on tmux 3.6b: a\n\t\t\t// `split-window` run inside pane %1, with `$TMUX_PANE` correctly reading %1, split the active\n\t\t\t// %0 instead). The two coincide while a human types, which is why the default reads as\n\t\t\t// harmless and is not; a program driving a pane it is not focused on gets the wrong one.\n\t\t\tconst from = opts.from ? ['-t', opts.from.id] : []\n\t\t\t// Empty unless the caller asks, so a split that names no ratio emits no `-l` and tmux applies\n\t\t\t// its own even default.\n\t\t\tconst size = opts.ratio != null ? ['-l', toTmuxSize(opts.ratio)] : []\n\t\t\tconst direction = at === 'pane:down' ? '-v' : '-h'\n\t\t\targs = ['split-window', direction, ...from, ...size, ...env, '-c', opts.cwd, '-P', '-F', format]\n\t\t}\n\t\t// A window takes its name at birth — `-n` also turns tmux's `automatic-rename` off for it, so\n\t\t// the name survives whatever the pane goes on to run. A pane has no such flag; its title is\n\t\t// set after the split.\n\t\tif (window && opts.label) args.splice(1, 0, '-n', opts.label)\n\t\tconst out = exec('tmux', args)\n\t\tif (!out) throw new Error(withReason(exec, `tmux ${args[0]} failed`))\n\t\tconst [pane, windowId] = splitOpenReport(out, args[0]!)\n\t\t// The window this pane landed in IS its tab — tmux's Tab is its Window. For a new window that\n\t\t// is the window just opened; for a split it is the caller's own window, which the split landed\n\t\t// in without opening a tab of its own. Both are exactly what tmux reports here.\n\t\t//\n\t\t// No `workspace`: tmux has no workspace tier — `workspace` and `tab` both collapse to a Window —\n\t\t// so it has nothing to report, which is not the same as reporting that nothing is there. Absent\n\t\t// is the seam's own convention for a fact a backend cannot answer (`OpenedPane.workspace`,\n\t\t// `isPaneFocused`'s `undefined`); reporting a null here would assert a \"none\" tmux never said.\n\t\t// `tab` is the opposite case and is never absent: every multiplexer has the Tab level.\n\t\tconst target: OpenedPane = { id: pane, tab: windowId }\n\t\t// Through `group`, not a second `set-option` spelled here: grouping a space this open just\n\t\t// created and grouping one that was already open are the same act, so one spelling per backend\n\t\t// is the only way the two cannot drift. It costs no call — tmux has no `new-window` flag to set\n\t\t// an option at birth, so this was ALREADY a second call after the window exists — and it still\n\t\t// runs before any `--launch` submit, so the window is grouped before anything runs in it.\n\t\t//\n\t\t// No name: `open` knows only the DISPLAY name it just wrote with `-n`, which is exactly the\n\t\t// composed name that must never be stored as the space's own. A caller that composed one knows\n\t\t// the original and calls `group` itself with it; one that did not compose has nothing to store,\n\t\t// its display name already being its own name.\n\t\tif (group && windowId) tmuxMuxAdapter.group(exec, { id: windowId }, opts.workspaceGroup!)\n\t\t// Through `rename`, not a second `select-pane -T` spelled here: post-birth pane naming and the\n\t\t// seam's rename are the same act, so one spelling per backend is the only way the two cannot\n\t\t// drift. A window took its name at birth via `-n` above and needs nothing here.\n\t\tif (!window && opts.label) tmuxMuxAdapter.rename(exec, target, 'pane', opts.label)\n\t\t// `submit`, not `sendText` — a launch command has to actually run, and `submit` is the only\n\t\t// verb that supplies the Enter.\n\t\tif (opts.launch) tmuxMuxAdapter.submit(exec, target, opts.launch)\n\t\treturn target\n\t},\n\n\trename(exec, target, tier, name) {\n\t\t// tmux's two tiers are two different verbs, and the pane one is not `rename-pane` (no such\n\t\t// command exists) — a pane's name IS its title, set through `select-pane -T`.\n\t\tif (tier === 'tab') {\n\t\t\t// `rename-window`, because a tab is a Window on tmux — the same collapse `open` makes, where\n\t\t\t// both 'workspace' and 'tab' become a window. This also pins the name against tmux's\n\t\t\t// `automatic-rename`, exactly as `new-window -n` does at birth.\n\t\t\texec('tmux', ['rename-window', '-t', target.id, name])\n\t\t\treturn\n\t\t}\n\t\t// `-T` makes `select-pane` a pure title write: tmux returns as soon as it has set the title and\n\t\t// never reaches the code that would make the pane active (verified on 3.6b), so this moves no\n\t\t// focus despite the verb's name. That is what lets it serve a rename's read-only side effects.\n\t\texec('tmux', ['select-pane', '-t', target.id, '-T', name])\n\t},\n\n\tgroup(exec, target, group, name) {\n\t\t// A window user option — tmux's own mechanism for a value it stores but never interprets. It\n\t\t// survives a window rename (unlike a name-encoded grouping), and tmux filters on it server-side\n\t\t// (`list-windows -f '#{==:#{@cm_ws},<id>}'`). Set verbatim: opaque means this adapter never\n\t\t// parses, splits, or derives the value, and never reads it off the label.\n\t\texec('tmux', ['set-option', '-w', '-t', target.id, TMUX_WORKSPACE_GROUP_OPTION, group])\n\t\t// The space's own name, beside the group, because tmux's single `window_name` may now hold a\n\t\t// display name composed out of it — see TMUX_TAB_NAME_OPTION. Only when the caller has one:\n\t\t// nothing to store is not the same as an empty name, and no adapter invents one.\n\t\tif (name !== undefined) exec('tmux', ['set-option', '-w', '-t', target.id, TMUX_TAB_NAME_OPTION, name])\n\t},\n\n\tsendText(exec, target, text) {\n\t\t// `-l` is mandatory, not a nicety: without it tmux resolves each argument as a key name first\n\t\t// and only falls back to characters (\"if the string is not recognised as a key, it is sent as\n\t\t// a series of characters\"), so a bare `send-keys -t <p> Up` would press the arrow instead of\n\t\t// typing the word. `-l` disables that lookup outright.\n\t\texec('tmux', ['send-keys', '-t', target.id, '-l', text])\n\t},\n\n\tsendKeys(exec, target, keys) {\n\t\texec('tmux', ['send-keys', '-t', target.id, ...keys.map(toTmuxKey)])\n\t},\n\n\tsubmit(exec, target, text) {\n\t\t// No `-l` here: a bare Enter must resolve as the KEY, which is exactly what the key lookup is\n\t\t// for. `''` is the bare-flush case too — `send-keys -l ''` would be a no-op typing nothing,\n\t\t// leaving the staged buffer unsent.\n\t\tif (!text) {\n\t\t\texec('tmux', ['send-keys', '-t', target.id, 'Enter'])\n\t\t\treturn\n\t\t}\n\t\t// Two calls, unavoidably: tmux has no atomic literal-text-plus-Enter primitive. `-l` applies to\n\t\t// the whole argument list, so `send-keys -l <text> Enter` would type a literal \"Enter\" after the\n\t\t// text rather than pressing it. The composed path is what `submit`'s outcome-not-command\n\t\t// contract exists to permit.\n\t\ttmuxMuxAdapter.sendText(exec, target, text)\n\t\texec('tmux', ['send-keys', '-t', target.id, 'Enter'])\n\t},\n\n\tread(exec, target, opts?: MuxReadOptions | undefined) {\n\t\tconst args = ['capture-pane', '-p', '-t', target.id]\n\t\tif (opts?.lines != null) args.push('-S', `-${opts.lines}`)\n\t\treturn exec('tmux', args) ?? ''\n\t},\n\n\t// tmux has no wait-for-output primitive — `wait-for` synchronizes on a channel some other tmux\n\t// command signals, which says nothing about what a pane PRINTED. So the wait is the shared poll over\n\t// this adapter's own `capture-pane` read; see `pollForOutput` for the cadence and liveness rules.\n\twaitForOutput(exec, target, opts) {\n\t\treturn pollForOutput(tmuxMuxAdapter, exec, target, opts)\n\t},\n\n\tfocus(exec, target) {\n\t\t// A bare `select-pane` only moves focus within the caller's OWN attached session/window — a\n\t\t// peer's pane can live in a different tmux session and window entirely, so that alone would\n\t\t// silently no-op on the attached client. Resolve the pane's session + window from\n\t\t// `list-panes -a` first and drive the beam in order: switch-client (session), then\n\t\t// select-window, then select-pane. Resolution happens BEFORE any switch is issued, so an\n\t\t// unresolvable pane throws instead of a partial or false-success beam.\n\t\tconst out = exec('tmux', ['list-panes', '-a', '-F', '#{pane_id} #{session_name} #{window_id}'])\n\t\tconst { sessionName, windowId } = parsePaneLocation(out, target.id)\n\t\texec('tmux', ['switch-client', '-t', sessionName])\n\t\texec('tmux', ['select-window', '-t', windowId])\n\t\texec('tmux', ['select-pane', '-t', target.id])\n\t},\n\n\tteardown(exec, target) {\n\t\texec('tmux', ['kill-pane', '-t', target.id])\n\t},\n\n\tpaneExists(exec, target) {\n\t\t// `has-session` hits when the pane id happens to name a session; otherwise scan every pane\n\t\t// server-wide for the id (pane ids are globally unique across sessions).\n\t\tif (exec('tmux', ['has-session', '-t', target.id]) !== null) return true\n\t\treturn (exec('tmux', ['list-panes', '-a', '-F', '#{pane_id}']) ?? '').split('\\n').includes(target.id)\n\t},\n\n\tisPaneFocused(exec, target) {\n\t\t// `list-panes -a` server-wide, one line per pane: pane_id, whether it's the active pane of its\n\t\t// window, whether its window is the current one of its session, and the session's attached\n\t\t// client count. Focused iff all three hold; unresolvable (line missing) or no output → unknown,\n\t\t// never a false `false` — a caller can't tell \"not focused\" from \"couldn't find the pane\" here.\n\t\tconst out = exec('tmux', [\n\t\t\t'list-panes',\n\t\t\t'-a',\n\t\t\t'-F',\n\t\t\t'#{pane_id} #{pane_active} #{window_active} #{session_attached}',\n\t\t])\n\t\tif (!out) return undefined\n\t\tconst line = out.split('\\n').find((l) => l.split(' ')[0] === target.id)\n\t\tif (!line) return undefined\n\t\tconst [, paneActive, windowActive, sessionAttached] = line.split(' ')\n\t\treturn paneActive === '1' && windowActive === '1' && sessionAttached !== '0' && sessionAttached !== undefined\n\t},\n\n\t/**\n\t * Tab-separated, not space — the same rule `describeTmuxRegion` follows, and for the same reason:\n\t * `pane_current_path` and `pane_title` can both contain spaces. The old space-separated format\n\t * recovered the cwd by rejoining everything after the command, which works only while the cwd is\n\t * the LAST field. A label is a human's and may hold anything, so appending one to that format would\n\t * make both fields unrecoverable — `my worker` and `/repo/my dir` cannot be told apart by a space.\n\t * A tab can appear in neither id nor command, and the two free-text fields are separated by one.\n\t */\n\tlistPanes(exec): LivePane[] {\n\t\tconst out = exec('tmux', [\n\t\t\t'list-panes',\n\t\t\t'-a',\n\t\t\t'-F',\n\t\t\t'#{pane_id}\\t#{pane_current_command}\\t#{pane_current_path}\\t#{pane_title}\\t#{host}',\n\t\t])\n\t\tif (!out) return []\n\t\treturn out\n\t\t\t.split('\\n')\n\t\t\t.filter(Boolean)\n\t\t\t.map((line) => {\n\t\t\t\tconst [id, , cwd, title, host] = line.split('\\t')\n\t\t\t\tconst pane: LivePane = { id: id ?? '', mux: 'tmux' as const }\n\t\t\t\tif (cwd) pane.cwd = cwd\n\t\t\t\tconst label = paneLabel(title, host)\n\t\t\t\tif (label) pane.label = label\n\t\t\t\treturn pane\n\t\t\t})\n\t\t\t.filter((p) => p.id !== '')\n\t},\n\n\tregions: {\n\t\tdescribeRegion(exec, target) {\n\t\t\treturn describeTmuxRegion(exec, target.id)\n\t\t},\n\n\t\t/**\n\t\t * tmux has NO workspace tier — `workspace` and `tab` both collapse onto a Window — so a workspace\n\t\t * is not a fact this backend holds. What it holds is the grouping TAG the walk wrote\n\t\t * (`MuxOpenOptions.workspaceGroup`, stored in a window user option), so the read here is\n\t\t * literally *\"which windows carry this group id\"*.\n\t\t *\n\t\t * The tag, never the label. `list-windows -a` spans SESSIONS, so a bare name match would\n\t\t * over-collect a same-named window from another session, and taking the workspace off a\n\t\t * `<workspace> - <tab>` label is unsound in the first place (`acme - beta - main` splits two ways,\n\t\t * both legal). `-f '#{==:#{@cm_ws},<id>}'` keys on what actually identifies the group, filtered\n\t\t * server-side — the tag survives a window rename, which a name-encoded grouping does not.\n\t\t *\n\t\t * A window with NO tag is a workspace of ONE: the honest answer for a window nobody grouped, and\n\t\t * it costs no further call — the caller's own window is the whole workspace.\n\t\t */\n\t\tdescribeWorkspace(exec, target) {\n\t\t\t// One call for both facts: the caller's window and that window's tag. `display-message -p`\n\t\t\t// resolves the pane target and prints the format, so nothing has to be matched out of a\n\t\t\t// server-wide listing. Tab-separated — an id and a tag cannot contain one; a window NAME can,\n\t\t\t// but it is last, so a name with a tab in it cannot displace anything.\n\t\t\tconst out = exec('tmux', [\n\t\t\t\t'display-message',\n\t\t\t\t'-p',\n\t\t\t\t'-t',\n\t\t\t\ttarget.id,\n\t\t\t\t`#{window_id}\\t#{${TMUX_WORKSPACE_GROUP_OPTION}}\\t#{${TMUX_TAB_NAME_OPTION}}\\t#{window_name}`,\n\t\t\t])\n\t\t\tif (!out) throw new Error(withReason(exec, `tmux could not resolve the workspace around pane ${target.id}`))\n\t\t\tconst [windowId, group, ownName, ...nameParts] = out.split('\\n')[0]!.split('\\t')\n\t\t\tif (!windowId) throw new Error(`tmux did not report the window around pane ${target.id}`)\n\t\t\t// Untagged: this window is a workspace of one. Not an error and not an empty list — nobody\n\t\t\t// grouped it, and one window is exactly what that means.\n\t\t\tif (!group) return [tmuxTab(exec, windowId, ownName, nameParts.join('\\t'))]\n\t\t\tconst listed = exec('tmux', [\n\t\t\t\t'list-windows',\n\t\t\t\t'-a',\n\t\t\t\t'-F',\n\t\t\t\t`#{window_id}\\t#{${TMUX_TAB_NAME_OPTION}}\\t#{window_name}`,\n\t\t\t\t'-f',\n\t\t\t\t`#{==:#{${TMUX_WORKSPACE_GROUP_OPTION}},${group}}`,\n\t\t\t])\n\t\t\tif (!listed) throw new Error(withReason(exec, `tmux could not enumerate the windows grouped as ${group}`))\n\t\t\tconst tabs = listed\n\t\t\t\t.split('\\n')\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.map((line) => line.split('\\t'))\n\t\t\t\t.filter(([id]) => Boolean(id))\n\t\t\t\t// `window_name` is LAST and absorbs any tab it contains, which is why it is rejoined rather\n\t\t\t\t// than destructured: a window name is a human's and may hold anything.\n\t\t\t\t.map(([id, own, ...rest]) => tmuxTab(exec, id!, own, rest.join('\\t')))\n\t\t\tif (tabs.length === 0) throw new Error(`tmux reported no windows grouped as ${group}`)\n\t\t\treturn tabs\n\t\t},\n\t},\n}\n\n/**\n * One window, read as a tab: its id, the tab's OWN name, and its region's geometry.\n *\n * `ownName` is what `group` stored (`TMUX_TAB_NAME_OPTION`) and it WINS, because `windowName` is the\n * display name — on a grouped window that is the composed `pool - editor`, whose `editor` tmux's\n * single name field no longer holds. Reporting the display name instead would compound the prefix on\n * every capture/apply round trip (`pool - pool - editor`), and splitting it back apart is the unsound\n * parse the option exists to refuse.\n *\n * The window name is the FALLBACK, not a second guess: a window carrying no stored name is one nobody\n * composed a display name for, so its name already IS its own name. That covers the untagged window —\n * a workspace of one — and any window a caller grouped without naming.\n */\nfunction tmuxTab(\n\texec: Exec,\n\twindowId: string,\n\townName: string | undefined,\n\twindowName: string | undefined,\n): WorkspaceTab {\n\tconst tab: WorkspaceTab = { id: windowId, panes: describeTmuxRegion(exec, windowId) }\n\tconst label = ownName || windowName\n\tif (label) tab.label = label\n\treturn tab\n}\n\n/**\n * Every pane of the region `id` names, with its rectangle. `id` is a pane id (that pane's own window)\n * or a window id (that window) — `list-panes -t` resolves both, which is what lets the region read and\n * the workspace read share one query instead of two that could drift apart.\n *\n * `-t` scopes `list-panes` to ONE window — the region tier, which is what capture captures. Without\n * `-a`, so this never reaches the panes of some other window.\n *\n * `#{pane_left}`/`#{pane_top}` are window-relative, and the widths exclude the divider column tmux\n * draws between panes (a 200-wide window split side by side reports 119 + 80, not 200) — both are\n * exactly what `RegionPane.rect` documents, so nothing is adjusted here.\n *\n * Tab-separated, not space: `pane_current_path` and `pane_title` can both contain spaces, and\n * splitting a path on spaces is how a directory with one in it silently becomes the wrong pane.\n */\nfunction describeTmuxRegion(exec: Exec, id: string): RegionPane[] {\n\tconst out = exec('tmux', [\n\t\t'list-panes',\n\t\t'-t',\n\t\tid,\n\t\t'-F',\n\t\t'#{pane_id}\\t#{pane_left}\\t#{pane_top}\\t#{pane_width}\\t#{pane_height}\\t#{pane_current_path}\\t#{pane_title}\\t#{host}',\n\t])\n\tif (!out) throw new Error(withReason(exec, `tmux could not describe the region around pane ${id}`))\n\tconst panes: RegionPane[] = []\n\tfor (const line of out.split('\\n').filter(Boolean)) {\n\t\tconst [paneId, left, top, width, height, cwd, title, host] = line.split('\\t')\n\t\tif (!paneId) continue\n\t\tconst pane: RegionPane = {\n\t\t\tid: paneId,\n\t\t\trect: { x: Number(left), y: Number(top), width: Number(width), height: Number(height) },\n\t\t}\n\t\tif (cwd) pane.cwd = cwd\n\t\tconst label = paneLabel(title, host)\n\t\tif (label) pane.label = label\n\t\tpanes.push(pane)\n\t}\n\tif (panes.length === 0) throw new Error(`tmux reported no panes in the region around pane ${id}`)\n\treturn panes\n}\n\n/**\n * A tmux pane's label — its title, unless that title is the hostname tmux handed it.\n *\n * **tmux has no \"unset title\"**: it defaults `pane_title` to the hostname, so a pane nobody ever named\n * reports a name nobody chose, and every pane in an untouched session reports the SAME one. Exporting\n * that would label them all `zeta`, and `zeta` would then resolve to every pane in the session —\n * ambiguity manufactured out of nothing. A title that differs from the host is one someone set\n * (cyber-mux's own `select-pane -T` among them), so it is the author's and survives.\n *\n * One home for the rule, called by BOTH reads — `listPanes` (which a name resolves against) and\n * `describeTmuxRegion` (which a capture exports). Two spellings of a heuristic this load-bearing is\n * how the listing and the capture come to disagree about which panes are named.\n *\n * The comparison is the workaround, not the shape of the thing: herdr has the honest primitive and\n * omits the key outright until a pane is renamed, so it needs no rule at all.\n */\nfunction paneLabel(title: string | undefined, host: string | undefined): string | undefined {\n\treturn title && title !== host ? title : undefined\n}\n\n/**\n * The `-P -F '#{pane_id}\\t#{window_id}'` report EVERY open asks for, split back into its two ids.\n * Tab-separated because neither id can contain a tab.\n *\n * A report that does not carry both throws rather than returning half an answer: the window is the\n * pane's tab, which `OpenedPane.tab` promises is always present, and it is also what a grouping open\n * tags. Guessing either would be worse than failing — a caller would name or group nothing and never\n * learn it.\n */\nfunction splitOpenReport(out: string, command: string): [string, string] {\n\tconst [pane, windowId] = out.split('\\t')\n\tif (!pane || !windowId) throw new Error(`tmux ${command} did not report the new pane's id and window id`)\n\treturn [pane, windowId]\n}\n\n/**\n * `ratio` is the fraction kept by the ORIGINAL pane; tmux's `-l` sizes the NEW one. So this INVERTS\n * — `1 - ratio` — where herdr's `--ratio` passes the same number through untouched. The two backends\n * genuinely convert in opposite directions, and applying the inversion to both (or to neither) is\n * the way this gets silently backwards: a 0.333 template would size the original pane at 67%.\n *\n * Percent rather than cells: tmux takes `-l` as either, and a percentage is the only form that means\n * the same thing without first querying the region's size.\n */\nfunction toTmuxSize(ratio: number): string {\n\tassertRatioInRange(ratio)\n\treturn `${Math.round((1 - ratio) * 100)}%`\n}\n\n/**\n * The core vocabulary's tmux spelling. Exactly one member differs — probed, not read off tmux(1):\n * tmux has no `Backspace` key name, so it would *type* the word (its unrecognized-token fallback);\n * its name for that key is `BSpace` (tmux(1): \"the following special key names are accepted: Up,\n * Down, Left, Right, BSpace, BTab, DC ...\"). Every other core key — `Up` `Down` `Left` `Right`\n * `Enter` `Escape` `Tab` `Space` `C-c` `F1`-`F12` — is already tmux's own name for it.\n *\n * Deliberately a rename table, NOT a validation table: a token outside the core is forwarded\n * verbatim (the contract), so this must not reject what it does not recognize. Keeping a full tmux\n * key list here would make the passthrough a second vocabulary to maintain.\n */\nconst TMUX_KEY_RENAMES: Readonly<Record<string, string>> = { Backspace: 'BSpace' }\n\nfunction toTmuxKey(key: string): string {\n\treturn TMUX_KEY_RENAMES[key] ?? key\n}\n\n/**\n * `tmux list-panes -a -F '#{pane_id} #{session_name} #{window_id}'` lists every pane server-wide.\n * Resolving fails — no line's pane id matches `id` — when the pane no longer exists in the backend,\n * and that must throw so `focus` never issues a switch-client/select-window against a pane it\n * couldn't actually resolve.\n */\nfunction parsePaneLocation(out: string | null, id: string): { sessionName: string; windowId: string } {\n\tconst line = (out ?? '').split('\\n').find((l) => l.split(' ')[0] === id)\n\t// No `withReason` here, deliberately: this is a pure parser, not a command runner. Its failure is\n\t// \"no line matched\", not \"a command failed\", so the runner's most recent reason belongs to some\n\t// other command entirely and attributing it here would be a confident lie.\n\tif (!line) throw new Error(`peer's pane ${id} could not be resolved to beam to`)\n\tconst [, sessionName, windowId] = line.split(' ')\n\treturn { sessionName: sessionName!, windowId: windowId! }\n}\n","import { randomUUID } from 'node:crypto'\n\n/**\n * The id-minting seam: a source of fresh, collision-free opaque ids. Injected for the same reason\n * `Exec` and `TemplateStore` are — a backend or a walk that mints an id reaches for THIS rather than\n * `node:crypto` directly, so it stays a pure function of its inputs and a test can drive it with a\n * deterministic counter instead of a real UUID.\n *\n * The value is opaque: callers that want a short form slice it themselves. The only contract is that\n * two calls never collide within a run.\n */\nexport type NewId = () => string\n\n/** The real id source — a v4 UUID. The shell binds this; core takes `NewId` as a parameter. */\nexport const nodeNewId: NewId = () => randomUUID()\n","import { envFallback } from './env-fallback.ts'\nimport { type Exec, withReason } from './exec.ts'\nimport type { LivePane, MuxAdapter, MuxReadOptions, OpenedPane } from './mux.ts'\nimport { type NewId, nodeNewId } from './new-id.ts'\nimport { assertRatioInRange } from './ratio.ts'\nimport { pollForOutput } from './wait-output.ts'\n\n/**\n * WezTerm backend — detected via `$WEZTERM_PANE`. Drives WezTerm's built-in multiplexer through\n * `wezterm cli …` (https://wezterm.org/cli/general.html), the same synchronous-CLI shape tmux and\n * herdr already give `Exec`.\n *\n * Probed from `wezterm cli --help`/the CLI reference docs only — there is no live WezTerm GUI in\n * this sandbox, so nothing here carries the \"verified against a live binary\" claim `session.tmux.ts`\n * and `session.herdr.ts` make. Several real capability gaps fell out of that probe, not just missing\n * polish:\n *\n * - **No `--env` on `spawn`/`split-pane` at all.** Unlike herdr (native everywhere except one\n * worktree route), WezTerm's CLI has no env flag on ANY space-creating command — every route is\n * the exception, so `open`'s env always rides the `envFallback` compensation (a `env K=V` prefix\n * on the launch command, or a stderr warning with none to ride), never a native flag.\n * - **No way to title a PANE**, at birth or after — `set-tab-title`/`set-window-title` exist, there\n * is no pane equivalent. `rename(..., 'pane', …)` throws; `open`'s pane-tier `label` degrades to a\n * stderr warning rather than silently dropping it or failing the whole open.\n * - **No focus-query primitive** — `list --format json`'s documented fields carry no active/focused\n * indicator for a pane, tab, or window. `isPaneFocused` always answers `undefined`, which is the\n * seam's own honest answer for \"no primitive to report focus\", not a workaround.\n * - **No per-key press primitive** — there is no `send-keys`-shaped verb, only `send-text`. The core\n * vocabulary is instead realized by encoding each key as its raw terminal byte sequence and typing\n * it via `send-text --no-paste`; see `WEZTERM_KEY_BYTES`.\n * - **No pane geometry** — `list --format json` reports a pane's `size` (rows/cols) but no position,\n * so there is nothing to build a `PaneRect` from. `describeRegion`/`describeWorkspace` are omitted\n * entirely, the same optional-omission `template save` already handles for a backend that cannot.\n * - **No git-worktree concept in the CLI at all** — no `worktree` subcommand, so like tmux this\n * backend never binds one to a workspace; `worktree` is omitted.\n *\n * `spawn`/`split-pane` report only the new pane's bare id — unlike tmux/herdr, which embed the tab\n * (and workspace) in the same `-F`/JSON envelope the pane id rides out on. So `OpenedPane.tab` costs\n * a follow-up `wezterm cli list --format json` lookup here, not a free read of output already held.\n */\n/**\n * Build the WezTerm adapter over its injected effects. The only effect it takes is `newId` — the id\n * source for the fresh workspace name minted when the caller opens a `workspace` without a `label`.\n * Exported as a factory (with `weztermMuxAdapter` binding the real one below) so a test can inject a\n * deterministic id instead of a UUID, exactly the seam `Exec`/`TemplateStore` already are.\n */\nexport function createWeztermAdapter(deps: { newId: NewId }): MuxAdapter {\n\tconst adapter: MuxAdapter = {\n\t\tname: 'wezterm',\n\n\t\t// `split-pane --percent` sizes a split. Verified only against the issue's own probe note and the\n\t\t// CLI's own help text, not a live binary — see `toWeztermSize`.\n\t\tcanSizeSplits: true,\n\n\t\topen(exec, opts) {\n\t\t\tconst at = opts.at ?? 'tab'\n\t\t\tif (at === 'workspace') {\n\t\t\t\t// WezTerm's `--workspace` both selects AND creates: naming one that does not yet exist makes\n\t\t\t\t// it. Reusing \"default\" (the CLI's own default when `--workspace` is omitted) would NOT open\n\t\t\t\t// the pane's own space — it would join whatever the caller was already in — so a fresh name\n\t\t\t\t// is minted whenever the caller gave no `label`. When a label IS given it doubles as the\n\t\t\t\t// workspace's name: WezTerm has one string per workspace, not a separate opaque id plus a\n\t\t\t\t// display name the way tmux's window option split the two.\n\t\t\t\tconst workspace = opts.label ?? `cyber-mux-${deps.newId().slice(0, 8)}`\n\t\t\t\tconst out = exec('wezterm', ['cli', 'spawn', '--new-window', '--workspace', workspace, '--cwd', opts.cwd])\n\t\t\t\tif (!out) throw new Error(withReason(exec, 'wezterm cli spawn --new-window failed'))\n\t\t\t\tconst pane = out.trim()\n\t\t\t\tif (!pane) throw new Error('wezterm cli spawn --new-window did not report the new pane id')\n\t\t\t\tconst opened: OpenedPane = { id: pane, tab: resolveTab(exec, pane), workspace }\n\t\t\t\trunLaunch(adapter, exec, opened, opts.env, opts.launch)\n\t\t\t\treturn opened\n\t\t\t}\n\t\t\tif (at === 'tab') {\n\t\t\t\t// A WezTerm workspace is a set of WINDOWS and a tab lives in a window, so the anchor is\n\t\t\t\t// resolved one level down: any window already in the named workspace will do — a tab spawned\n\t\t\t\t// into it lands in that workspace. Without it `spawn` targets the window the USER is looking\n\t\t\t\t// at, which is the whole reason `within` exists.\n\t\t\t\tconst within = opts.within ? ['--window-id', resolveWorkspaceWindow(exec, opts.within)] : []\n\t\t\t\tconst out = exec('wezterm', ['cli', 'spawn', ...within, '--cwd', opts.cwd])\n\t\t\t\tif (!out) throw new Error(withReason(exec, 'wezterm cli spawn failed'))\n\t\t\t\tconst pane = out.trim()\n\t\t\t\tif (!pane) throw new Error('wezterm cli spawn did not report the new pane id')\n\t\t\t\tconst opened = withTabAndWorkspace(exec, pane)\n\t\t\t\t// `spawn` has no title flag at all (unlike tmux `-n`/herdr `--label`), so a tab's own label\n\t\t\t\t// is always a post-birth rename — not just the one root-tab case herdr has. Addressed by\n\t\t\t\t// TAB id, not the pane id `opened` itself carries — `rename`'s 'tab' tier takes a tab id.\n\t\t\t\tif (opts.label) adapter.rename(exec, { id: opened.tab }, 'tab', opts.label)\n\t\t\t\trunLaunch(adapter, exec, opened, opts.env, opts.launch)\n\t\t\t\treturn opened\n\t\t\t}\n\t\t\t// pane:right / pane:down\n\t\t\tconst direction = at === 'pane:down' ? ['--bottom'] : ['--right']\n\t\t\tconst from = opts.from ? ['--pane-id', opts.from.id] : []\n\t\t\t// The issue's own probe note: `--percent` sizes the NEW pane, the same inversion tmux's `-l`\n\t\t\t// needs — not herdr's pass-through of the ORIGINAL pane's fraction.\n\t\t\tconst size = opts.ratio != null ? ['--percent', toWeztermSize(opts.ratio)] : []\n\t\t\tconst out = exec('wezterm', ['cli', 'split-pane', ...direction, ...from, ...size, '--cwd', opts.cwd])\n\t\t\tif (!out) throw new Error(withReason(exec, 'wezterm cli split-pane failed'))\n\t\t\tconst pane = out.trim()\n\t\t\tif (!pane) throw new Error('wezterm cli split-pane did not report the new pane id')\n\t\t\tconst opened = withTabAndWorkspace(exec, pane)\n\t\t\t// No pane-title primitive exists at all — degrade with a warning rather than silently dropping\n\t\t\t// the label or failing the whole split over a name nobody NEEDS to open the pane.\n\t\t\tif (opts.label) {\n\t\t\t\tprocess.stderr.write(`wezterm cannot name a pane — \"${opts.label}\" was not set on pane ${opened.id}\\n`)\n\t\t\t}\n\t\t\trunLaunch(adapter, exec, opened, opts.env, opts.launch)\n\t\t\treturn opened\n\t\t},\n\n\t\trename(exec, target, tier, name) {\n\t\t\tif (tier === 'tab') {\n\t\t\t\texec('wezterm', ['cli', 'set-tab-title', '--tab-id', target.id, name])\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// No CLI primitive names a pane, at birth or after — `set-tab-title`/`set-window-title` exist,\n\t\t\t// there is no pane equivalent. Throwing here (rather than a silent no-op) is what `open`'s own\n\t\t\t// pane-tier degrade-with-warning is a deliberate alternative TO: a caller reaching this method\n\t\t\t// directly gets told, not a false success.\n\t\t\tthrow new Error(`wezterm cannot name a pane (only a tab or window) — asked to rename ${target.id}`)\n\t\t},\n\n\t\tgroup() {\n\t\t\t// Same complete answer as herdr's, and for the same reason: WezTerm's workspace IS a real tier\n\t\t\t// (every window belongs to one), so a caller opening several tabs and asking to group them has\n\t\t\t// nothing left for this to add — the grouping tag exists for a backend with no workspace tier\n\t\t\t// to hold one in (tmux). The granularity is coarser here (per-WINDOW, since every tab in a\n\t\t\t// window already shares its workspace, and there is no \"move this tab to another workspace\"\n\t\t\t// primitive to retrofit one), but the answer is the same: nothing to write.\n\t\t},\n\n\t\tsendText(exec, target, text) {\n\t\t\t// The default (no `--no-paste`) sends as a bracketed paste, which is what keeps the guarantee\n\t\t\t// send-keys asks for: text is never interpreted as a key name, only ever typed.\n\t\t\texec('wezterm', ['cli', 'send-text', '--pane-id', target.id, text])\n\t\t},\n\n\t\tsendKeys(exec, target, keys) {\n\t\t\t// There is no send-keys-shaped verb in wezterm's CLI, so a named key is realized as its own raw\n\t\t\t// terminal byte sequence typed via `send-text --no-paste` — `--no-paste` because a key's bytes\n\t\t\t// (an escape sequence, a control byte) must reach the pty as-is, never wrapped in a bracketed\n\t\t\t// paste marker the way `sendText`'s literal text is. A token this adapter cannot encode is\n\t\t\t// forwarded as its own literal characters, the same \"cannot refuse a key name\" fallback tmux's\n\t\t\t// send-keys has — there is no backend to ask, so nothing here can refuse it either.\n\t\t\tconst bytes = keys.map((k) => WEZTERM_KEY_BYTES[k] ?? k).join('')\n\t\t\texec('wezterm', ['cli', 'send-text', '--pane-id', target.id, '--no-paste', bytes])\n\t\t},\n\n\t\tsubmit(exec, target, text) {\n\t\t\t// No atomic literal-text-plus-Enter primitive, so this composes exactly as tmux's does: the\n\t\t\t// bare-flush case presses Enter alone, typing nothing; otherwise the literal text first, then\n\t\t\t// Enter as its own key.\n\t\t\tif (!text) {\n\t\t\t\tadapter.sendKeys(exec, target, ['Enter'])\n\t\t\t\treturn\n\t\t\t}\n\t\t\tadapter.sendText(exec, target, text)\n\t\t\tadapter.sendKeys(exec, target, ['Enter'])\n\t\t},\n\n\t\tread(exec, target, opts?: MuxReadOptions | undefined) {\n\t\t\tconst args = ['cli', 'get-text', '--pane-id', target.id]\n\t\t\t// `--start-line` counts backward into scrollback from 0 (the top of the visible screen); the\n\t\t\t// end defaults to the bottom of the screen. Negative-N approximates \"last N lines\" the way\n\t\t\t// tmux's `-S -N` does, though the two are not guaranteed to line up cell-for-cell.\n\t\t\tif (opts?.lines != null) args.push('--start-line', String(-opts.lines))\n\t\t\treturn exec('wezterm', args) ?? ''\n\t\t},\n\n\t\t// No wait-for-output primitive in `wezterm cli` — the surface reads text (`get-text`) and never\n\t\t// blocks on it. So the wait is the shared poll over this adapter's own read; see `pollForOutput`.\n\t\twaitForOutput(exec, target, opts) {\n\t\t\treturn pollForOutput(adapter, exec, target, opts)\n\t\t},\n\n\t\tfocus(exec, target) {\n\t\t\t// The one primitive whose name says what this method wants — \"Activate (focus) a pane\". Its\n\t\t\t// docs do not state whether it crosses a window or workspace boundary to get there (untestable\n\t\t\t// here, no live GUI); it is used as the whole implementation because there is nothing else in\n\t\t\t// the CLI surface that names this intent.\n\t\t\texec('wezterm', ['cli', 'activate-pane', '--pane-id', target.id])\n\t\t},\n\n\t\tteardown(exec, target) {\n\t\t\texec('wezterm', ['cli', 'kill-pane', '--pane-id', target.id])\n\t\t},\n\n\t\tpaneExists(exec, target) {\n\t\t\treturn listWeztermPanes(exec).some((p) => String(p.pane_id) === target.id)\n\t\t},\n\n\t\tisPaneFocused() {\n\t\t\t// `list --format json`'s documented fields carry no active/focused indicator for a pane, tab, or\n\t\t\t// window — there is no primitive to ask. `undefined` is the seam's own answer for exactly this\n\t\t\t// case, not a stand-in for `false`: callers fail OPEN on it.\n\t\t\treturn undefined\n\t\t},\n\n\t\tlistPanes(exec): LivePane[] {\n\t\t\treturn listWeztermPanes(exec).map((p) => {\n\t\t\t\tconst pane: LivePane = { id: String(p.pane_id), mux: 'wezterm' as const }\n\t\t\t\tconst cwd = weztermCwd(p.cwd)\n\t\t\t\tif (cwd) pane.cwd = cwd\n\t\t\t\t// No `label`, ever — not a filtering rule like tmux's hostname guard, but the honest answer\n\t\t\t\t// to there being no way for a human OR cyber-mux to set a pane's title on this backend (see\n\t\t\t\t// `rename`). `title` is whatever program is running, ambient rather than chosen — reporting\n\t\t\t\t// it as a label would manufacture the exact collision (every shell pane named the same\n\t\t\t\t// thing) the hostname guard exists to prevent, except with no author ever able to override\n\t\t\t\t// it here.\n\t\t\t\treturn pane\n\t\t\t})\n\t\t},\n\n\t\t// No `describeRegion`/`describeWorkspace`: `list --format json` reports a pane's size, never its\n\t\t// position, so there is no rect to build. Omitted entirely — the same optional-absence `template\n\t\t// save` already handles for a backend that cannot describe its own region, not a stub.\n\n\t\t// No `worktree`: no `worktree` subcommand exists in the CLI at all, so — like tmux — this backend\n\t\t// never binds a git worktree to a workspace; callers fall back to plain git plus `open()`.\n\t}\n\treturn adapter\n}\n\nexport const weztermMuxAdapter: MuxAdapter = createWeztermAdapter({ newId: nodeNewId })\n\n/**\n * `wezterm cli spawn`/`split-pane` report ONLY the new pane's bare id on stdout — unlike tmux/herdr,\n * neither embeds the tab (or workspace) in that same output. So the tab this pane landed in is a\n * follow-up `list --format json` lookup rather than a free read of an envelope already held.\n * `OpenedPane.tab` is still required — every multiplexer has the Tab level — it simply costs more\n * here than the \"no extra call\" property tmux/herdr get to claim.\n */\nfunction resolveTab(exec: Exec, pane: string): string {\n\tconst found = listWeztermPanes(exec).find((p) => String(p.pane_id) === pane)\n\tif (!found) throw new Error(`wezterm did not report a tab for the new pane ${pane}`)\n\treturn String(found.tab_id)\n}\n\n/**\n * A window of the named workspace — what `spawn --window-id` needs to place a tab in a workspace\n * WezTerm's CLI cannot target directly (`--workspace` only names the space a `--new-window` spawn\n * creates, so it cannot place a tab in an existing one).\n *\n * Throws rather than falling back to an untargeted spawn: an unresolvable anchor means the workspace\n * is gone, and the untargeted spawn is exactly the wrong-space bug `within` was added to close.\n */\nfunction resolveWorkspaceWindow(exec: Exec, workspace: string): string {\n\tconst found = listWeztermPanes(exec).find((p) => p.workspace === workspace)\n\tif (!found) throw new Error(`wezterm reported no window in workspace ${workspace} to open a tab in`)\n\treturn String(found.window_id)\n}\n\n/** `resolveTab` plus the workspace the same lookup already answers — one call serves both facts. */\nfunction withTabAndWorkspace(exec: Exec, pane: string): OpenedPane {\n\tconst found = listWeztermPanes(exec).find((p) => String(p.pane_id) === pane)\n\tif (!found) throw new Error(`wezterm did not report a tab for the new pane ${pane}`)\n\t// Every WezTerm pane belongs to SOME workspace, even the implicit \"default\" one it starts in — so\n\t// unlike tmux (never reports; no tier at all) this is never absent, on every placement including\n\t// tab and pane:*, whether or not the caller ever asked for one.\n\treturn { id: pane, tab: String(found.tab_id), workspace: found.workspace }\n}\n\n/**\n * Env is native at NO tier on this backend — `spawn`/`split-pane` take no `--env` at all, unlike\n * herdr (native everywhere but one worktree route). So every `open` funnels through the same\n * fallback herdr's worktree route uses: with a launch command, env rides in as an `env K=V` prefix;\n * with none, a warning names what did not land. `envFallback` is a no-op when there is no env to\n * carry, so this is safe to call unconditionally.\n */\nfunction runLaunch(\n\tadapter: MuxAdapter,\n\texec: Exec,\n\ttarget: OpenedPane,\n\tenv: Record<string, string> | undefined,\n\tlaunch: string | undefined,\n) {\n\tconst fallback = envFallback(env, launch)\n\tif (fallback.kind === 'dropped') {\n\t\tprocess.stderr.write(\n\t\t\t`env (${fallback.variables.join(', ')}) could not be set on this wezterm pane — ` +\n\t\t\t\t'wezterm has no --env flag on any space-creating command\\n',\n\t\t)\n\t\treturn\n\t}\n\tif (fallback.command !== undefined) adapter.submit(exec, target, fallback.command)\n}\n\ninterface WeztermListEntry {\n\twindow_id: number | string\n\ttab_id: number | string\n\tpane_id: number | string\n\tworkspace: string\n\ttitle?: string | undefined\n\tcwd?: string | undefined\n}\n\n/** One `wezterm cli list --format json` call, parsed defensively — never throws on bad output. */\nfunction listWeztermPanes(exec: Exec): WeztermListEntry[] {\n\tconst out = exec('wezterm', ['cli', 'list', '--format', 'json'])\n\tif (!out) return []\n\tlet parsed: unknown\n\ttry {\n\t\tparsed = JSON.parse(out)\n\t} catch {\n\t\treturn []\n\t}\n\tif (!Array.isArray(parsed)) return []\n\treturn parsed.filter(\n\t\t(p): p is WeztermListEntry => p != null && p.pane_id != null && p.tab_id != null && p.window_id != null,\n\t)\n}\n\n/** `cwd` is reported as a `file://` URI; strip the scheme and host down to the bare path. */\nfunction weztermCwd(cwd: string | undefined): string | undefined {\n\tif (!cwd) return undefined\n\tconst match = /^file:\\/\\/[^/]*(\\/.*)$/.exec(cwd)\n\treturn match ? decodeURIComponent(match[1]!) : cwd\n}\n\n/**\n * `ratio` is the fraction kept by the ORIGINAL pane; `--percent` sizes the NEW one (the issue's own\n * probe note, #47) — the same inversion tmux's `-l` needs, unlike herdr's pass-through.\n */\nfunction toWeztermSize(ratio: number): string {\n\tassertRatioInRange(ratio)\n\treturn String(Math.round((1 - ratio) * 100))\n}\n\n/**\n * The core vocabulary, realized as raw terminal bytes rather than a backend key NAME — there is no\n * send-keys-shaped verb to name a key TO, only `send-text`. Escape sequences are the ANSI/VT100\n * \"cursor key mode\" forms every common shell/program already parses; `Backspace` sends DEL (`\\x7f`),\n * what most terminals emit for that key today (probed, not read off any wezterm spec — wezterm ships\n * no such table because it has no key-name CLI surface to spec).\n *\n * `Home`/`End`/`Delete`/`Insert`/`PageUp`/`PageDown` are extras beyond the core, included for the\n * same reason tmux \"knows\" `Home` even though the core vocabulary does not name it: these are\n * standard-enough ANSI keys that encoding them costs nothing extra and a caller reaching for one\n * should not silently get the literal word typed instead.\n */\nconst WEZTERM_KEY_BYTES: Readonly<Record<string, string>> = {\n\tUp: '\\x1b[A',\n\tDown: '\\x1b[B',\n\tRight: '\\x1b[C',\n\tLeft: '\\x1b[D',\n\tEnter: '\\r',\n\tEscape: '\\x1b',\n\tTab: '\\t',\n\tSpace: ' ',\n\tBackspace: '\\x7f',\n\t'C-c': '\\x03',\n\tF1: '\\x1bOP',\n\tF2: '\\x1bOQ',\n\tF3: '\\x1bOR',\n\tF4: '\\x1bOS',\n\tF5: '\\x1b[15~',\n\tF6: '\\x1b[17~',\n\tF7: '\\x1b[18~',\n\tF8: '\\x1b[19~',\n\tF9: '\\x1b[20~',\n\tF10: '\\x1b[21~',\n\tF11: '\\x1b[23~',\n\tF12: '\\x1b[24~',\n\tHome: '\\x1b[H',\n\tEnd: '\\x1b[F',\n\tDelete: '\\x1b[3~',\n\tInsert: '\\x1b[2~',\n\tPageUp: '\\x1b[5~',\n\tPageDown: '\\x1b[6~',\n}\n","import { envFallback } from './env-fallback.ts'\nimport { type Exec, withReason } from './exec.ts'\nimport type { LivePane, MuxAdapter, MuxReadOptions, OpenedPane } from './mux.ts'\nimport { pollForOutput } from './wait-output.ts'\n\n/**\n * Zellij backend — detected via `$ZELLIJ`. Drives Zellij's built-in multiplexer through `zellij\n * action …` (https://zellij.dev/documentation/cli-actions), the same synchronous-CLI shape tmux,\n * herdr and wezterm already give `Exec`.\n *\n * Requires **Zellij ≥ 0.44.1**. The whole per-pane-addressable surface this adapter stands on landed\n * in 0.44.0 (2026-03-23): `--pane-id` across the action verbs, `list-panes --json`, ids returned from\n * `new-pane`/`new-tab`, and `focus-pane-id`. Before that release Zellij's CLI was almost entirely\n * FOCUS-relative — no stable per-pane handle — and no faithful adapter was possible. On an older\n * binary these commands fail and the adapter surfaces the failure rather than silently mis-targeting\n * the focused pane.\n *\n * Probed from the Zellij docs + CHANGELOG only — Zellij is not installed in this sandbox, so nothing\n * here carries the \"verified against a live binary\" claim `session.tmux.ts`/`session.herdr.ts` make;\n * it makes the same honest disclaimer `mux.wezterm.ts` does. Two literals in particular are worth a\n * one-line confirmation on a live 0.44.1 binary — the exact form `new-pane` prints an id in (bare `3`\n * vs `terminal_3`) and the shell value of `$ZELLIJ_PANE_ID`. Both are handled either way: ids are\n * carried verbatim and compared through `samePane`, which treats a bare `N` and its `terminal_N` twin\n * as the same pane (per the docs' own `terminal_N | plugin_N | bare N` scheme).\n *\n * Real capability shape that fell out of the probe:\n *\n * - **The session is the workspace tier, but a SEPARATE session's panes are un-addressable through\n * this seam.** Zellij's native tiers are Session › Tab › Pane, so a `workspace` placement would\n * naturally open a new session (`zellij attach --create-background`). But Zellij pane ids are\n * SESSION-SCOPED — driving another session's pane requires `zellij --session <name> action …` — and\n * `MuxTarget` carries only an opaque pane id, no session. A pane returned from a freshly-created\n * session would therefore fail on the next `write`/`read`/`focus`, which is a trap, not a feature.\n * So this adapter operates within the AMBIENT session: `workspace` collapses to a new **tab**, the\n * same collapse tmux makes for a Window. Unlike tmux, the occupied workspace IS reported —\n * `OpenedPane.workspace` carries the ambient session name (`deps.session`), because every pane\n * genuinely lives in that session. (Lifting the collapse needs a seam change: a session qualifier\n * on `MuxTarget`. Recorded in the ADR log.)\n * - **No `--env` on `new-pane`/`new-tab`.** Like wezterm, env is native at no tier, so every open\n * rides the `envFallback` compensation (an `env K=V` prefix on the launch command, or a stderr\n * warning when there is no command to ride).\n * - **Tiled splits cannot be sized.** `new-pane`'s `-x/-y/--width/--height` all require `--floating`;\n * a tiled `pane:*` split is always even. So `canSizeSplits` is omitted and a `ratio` is dropped —\n * callers degrade to the even default, the same path the flag's absence already documents.\n * - **`new-pane` has no split-TARGET flag.** It splits the focused pane (or the biggest space); the\n * only flag is `--tab-id`. So `from` — which pane a `pane:*` split lands beside — is honored by\n * FOCUSING that pane first, the sole way to choose the split target. That is a real focus move, and\n * the honest cost of getting the RIGHT pane split.\n * - **No pane geometry adapter.** `list-panes --json` does report `pane_x`/`pane_y`, so `regions`\n * (`describeRegion`/`describeWorkspace`) is IMPLEMENTABLE here — unlike wezterm, which lacks\n * position entirely — but the cell-vs-divider semantics of Zellij's rects need a live binary to pin,\n * so it is deliberately left as a follow-up rather than guessed. `template save` refuses on zellij\n * by naming the backend, the same optional-absence it already handles for wezterm.\n * - **No git-worktree concept in the CLI.** No `worktree` subcommand, so — like tmux and wezterm —\n * this backend never binds one to a workspace; callers fall back to plain git plus `open()`.\n *\n * Wins Zellij has that wezterm does not: it CAN name a pane (`new-pane --name` / `rename-pane`), and\n * it CAN report which pane is focused (`list-panes --json`'s `is_focused`), so `isPaneFocused`\n * answers a real value rather than always `unknown`.\n */\n/**\n * Build the Zellij adapter over its injected effects. The only effect it takes is `session` — the\n * ambient Zellij session name (`$ZELLIJ_SESSION_NAME`), reported as `OpenedPane.workspace` so a\n * caller learns which workspace a new pane landed in. Undefined when unknown (the exported singleton),\n * in which case `workspace` is simply omitted — the same absent-not-false convention the seam uses\n * elsewhere. Exported as a factory (with `zellijMuxAdapter` binding the effect-less one) so\n * `backend.ts` can bind the real session name off `env` at resolution, exactly the seam `Exec` and\n * wezterm's `newId` already are.\n */\nexport function createZellijAdapter(deps: { session?: string | undefined }): MuxAdapter {\n\tconst adapter: MuxAdapter = {\n\t\tname: 'zellij',\n\n\t\t// No `canSizeSplits`: a tiled split is always even — `new-pane`'s size flags all require\n\t\t// `--floating`, so there is nothing to honor a `ratio` with. Its absence is what callers degrade\n\t\t// against.\n\n\t\topen(exec, opts) {\n\t\t\tconst at = opts.at ?? 'tab'\n\t\t\t// `workspace` and `tab` both open a new TAB in the ambient session — the collapse forced by\n\t\t\t// session-scoped pane ids plus a session-less `MuxTarget` (see the header). tmux makes the\n\t\t\t// same collapse onto a Window; the one difference is that `workspace` is still reported here.\n\t\t\tif (at === 'tab' || at === 'workspace') {\n\t\t\t\tconst args = ['action', 'new-tab', '--cwd', opts.cwd]\n\t\t\t\t// `--name` names the tab at birth — native, unlike wezterm's post-birth `set-tab-title`.\n\t\t\t\tif (opts.label) args.push('--name', opts.label)\n\t\t\t\tconst out = exec('zellij', args)\n\t\t\t\tif (!out) throw new Error(withReason(exec, 'zellij action new-tab failed'))\n\t\t\t\tconst tabId = out.trim()\n\t\t\t\tif (!tabId) throw new Error('zellij action new-tab did not report the new tab id')\n\t\t\t\t// `new-tab` reports the TAB id, not a pane id; the tab's own initial pane is the single\n\t\t\t\t// `list-panes` record carrying that `tab_id`.\n\t\t\t\tconst opened = openedForTab(exec, tabId, deps.session)\n\t\t\t\trunLaunch(adapter, exec, opened, opts.env, opts.launch)\n\t\t\t\treturn opened\n\t\t\t}\n\t\t\t// pane:right / pane:down — a tiled split. `new-pane` has no split-target flag (only\n\t\t\t// `--tab-id`), so `from` is honored by focusing that pane FIRST — the only way to choose which\n\t\t\t// pane the split lands beside. Omitted `from` splits whatever is focused, the backend default\n\t\t\t// the seam documents (never silently \"the caller's pane\").\n\t\t\tif (opts.from) adapter.focus(exec, opts.from)\n\t\t\tconst direction = at === 'pane:down' ? 'down' : 'right'\n\t\t\tconst args = ['action', 'new-pane', '--direction', direction, '--cwd', opts.cwd]\n\t\t\t// `--name` names the pane at birth — Zellij can title a pane, unlike wezterm.\n\t\t\tif (opts.label) args.push('--name', opts.label)\n\t\t\t// No size flag: `ratio` is dropped because a tiled split is always even (see `canSizeSplits`\n\t\t\t// above). Callers that asked for a ratio degrade to the even default with one warning.\n\t\t\tconst out = exec('zellij', args)\n\t\t\tif (!out) throw new Error(withReason(exec, 'zellij action new-pane failed'))\n\t\t\tconst paneId = out.trim()\n\t\t\tif (!paneId) throw new Error('zellij action new-pane did not report the new pane id')\n\t\t\tconst opened = openedForPane(exec, paneId, deps.session)\n\t\t\trunLaunch(adapter, exec, opened, opts.env, opts.launch)\n\t\t\treturn opened\n\t\t},\n\n\t\trename(exec, target, tier, name) {\n\t\t\tif (tier === 'tab') {\n\t\t\t\t// `rename-tab-by-id` names a tab by id without visiting it — the read-only side effects a\n\t\t\t\t// rename promises. (`rename-tab` alone would target the focused tab.)\n\t\t\t\texec('zellij', ['action', 'rename-tab-by-id', target.id, name])\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// `rename-pane --pane-id` names a specific pane by id — no focus move, unlike the bare\n\t\t\t// `rename-pane` which renames the focused pane.\n\t\t\texec('zellij', ['action', 'rename-pane', '--pane-id', target.id, name])\n\t\t},\n\n\t\tgroup() {\n\t\t\t// A complete no-op, herdr/wezterm-style, and for the same reason: Zellij's session is a real\n\t\t\t// workspace tier that already groups every tab in it — that grouping is exactly what\n\t\t\t// `OpenedPane.workspace` reports. The grouping TAG (`MuxOpenOptions.workspaceGroup`) exists for\n\t\t\t// a backend with NO workspace tier to hold one in (tmux, via a window option). Zellij has no\n\t\t\t// per-tab opaque metadata store to stash a finer tag in AND has a real tier, so there is\n\t\t\t// nothing for this to add. The granularity is the whole session, coarser than a per-caller\n\t\t\t// tag, matching wezterm's per-window answer.\n\t\t},\n\n\t\tsendText(exec, target, text) {\n\t\t\t// `write-chars` types the literal characters with NO trailing newline — the literal-text-no-\n\t\t\t// Enter guarantee. Literal means literal: text that names a key (`Enter`) is typed as those\n\t\t\t// characters, never pressed. That is why this is its own verb, not a mode of `sendKeys`.\n\t\t\texec('zellij', ['action', 'write-chars', '--pane-id', target.id, text])\n\t\t},\n\n\t\tsendKeys(exec, target, keys) {\n\t\t\t// `send-keys` names each key in Zellij's own vocabulary (`Enter`, `Ctrl c`, `F1`), one key per\n\t\t\t// argument. A core token is renamed to its Zellij spelling (`Escape`→`Esc`, `C-c`→`Ctrl c`);\n\t\t\t// anything outside the core is forwarded verbatim — the seam's passthrough — reaching a\n\t\t\t// backend-specific key at the cost of portability. `Enter` is a key like any other here: this\n\t\t\t// presses it because the caller asked; it never ADDS one (that is `submit`'s job).\n\t\t\texec('zellij', ['action', 'send-keys', '--pane-id', target.id, ...keys.map(toZellijKey)])\n\t\t},\n\n\t\tsubmit(exec, target, text) {\n\t\t\t// No atomic literal-text-plus-Enter primitive, so this composes exactly as tmux/wezterm do:\n\t\t\t// the bare-flush case presses Enter alone, typing nothing (flushing a staged buffer without\n\t\t\t// re-typing it); otherwise the literal text first, then Enter as its own key.\n\t\t\tif (!text) {\n\t\t\t\tadapter.sendKeys(exec, target, ['Enter'])\n\t\t\t\treturn\n\t\t\t}\n\t\t\tadapter.sendText(exec, target, text)\n\t\t\tadapter.sendKeys(exec, target, ['Enter'])\n\t\t},\n\n\t\tread(exec, target, opts?: MuxReadOptions | undefined) {\n\t\t\t// `dump-screen` with no file path writes the pane's viewport to stdout. There is no\n\t\t\t// \"last N lines\" primitive, so a `lines` request dumps the full scrollback (`--full`) and keeps\n\t\t\t// the trailing N — the closest Zellij offers to tmux's `-S -N`, not guaranteed to line up\n\t\t\t// cell-for-cell.\n\t\t\tif (opts?.lines != null) {\n\t\t\t\tconst full = exec('zellij', ['action', 'dump-screen', '--pane-id', target.id, '--full']) ?? ''\n\t\t\t\treturn lastLines(full, opts.lines)\n\t\t\t}\n\t\t\treturn exec('zellij', ['action', 'dump-screen', '--pane-id', target.id]) ?? ''\n\t\t},\n\n\t\t// No wait-for-output primitive in `zellij action` — it dumps a screen and never blocks on what is\n\t\t// on it. So the wait is the shared poll over this adapter's own read; see `pollForOutput`. The\n\t\t// `lines` caveat `read` documents rides along unchanged: a `lines` wait searches the trailing N of\n\t\t// a full-scrollback dump, which is Zellij's closest approximation, not a cell-exact viewport.\n\t\twaitForOutput(exec, target, opts) {\n\t\t\treturn pollForOutput(adapter, exec, target, opts)\n\t\t},\n\n\t\tfocus(exec, target) {\n\t\t\t// `focus-pane-id` focuses a specific pane by id, crossing tabs to reach it — the one primitive\n\t\t\t// whose name is exactly this method's intent (added in 0.44.1).\n\t\t\texec('zellij', ['action', 'focus-pane-id', target.id])\n\t\t},\n\n\t\tteardown(exec, target) {\n\t\t\texec('zellij', ['action', 'close-pane', '--pane-id', target.id])\n\t\t},\n\n\t\tpaneExists(exec, target) {\n\t\t\treturn listZellijPanes(exec).some((p) => samePane(p.id, target.id))\n\t\t},\n\n\t\tisPaneFocused(exec, target) {\n\t\t\t// `list-panes --json` carries `is_focused` per pane — a real focus primitive, unlike wezterm's\n\t\t\t// always-`unknown`. Unresolvable (no matching record) answers `undefined`, never a false\n\t\t\t// `false`: a caller cannot tell \"not focused\" from \"pane gone\" here, and fails OPEN on\n\t\t\t// `undefined`.\n\t\t\tconst found = listZellijPanes(exec).find((p) => samePane(p.id, target.id))\n\t\t\tif (!found) return undefined\n\t\t\treturn found.is_focused === true\n\t\t},\n\n\t\tlistPanes(exec): LivePane[] {\n\t\t\treturn listZellijPanes(exec).map((p) => {\n\t\t\t\tconst pane: LivePane = { id: p.id, mux: 'zellij' as const }\n\t\t\t\tif (p.pane_cwd) pane.cwd = p.pane_cwd\n\t\t\t\t// A pane's title CAN be an authored name here (`new-pane --name` / `rename-pane`), unlike\n\t\t\t\t// wezterm. But Zellij defaults an unnamed pane's title to its running command, so a title\n\t\t\t\t// equal to `pane_command` is ambient rather than chosen — dropped the same way tmux drops a\n\t\t\t\t// title equal to the hostname, so every shell pane does not resolve to one manufactured name.\n\t\t\t\tconst label = zellijLabel(p.title, p.pane_command)\n\t\t\t\tif (label) pane.label = label\n\t\t\t\treturn pane\n\t\t\t})\n\t\t},\n\n\t\t// No `regions`: geometry is scoped out of this adapter — see the header. `template save` refuses\n\t\t// on zellij by naming the backend, the same optional-absence it handles for wezterm.\n\n\t\t// No `worktree`: Zellij has no `worktree` subcommand, so — like tmux and wezterm — it never binds\n\t\t// a git worktree to a workspace; callers fall back to plain git plus `open()`.\n\t}\n\treturn adapter\n}\n\nexport const zellijMuxAdapter: MuxAdapter = createZellijAdapter({})\n\n/** One pane record from `zellij action list-panes --json`, the fields this adapter reads. */\ninterface ZellijPane {\n\tid: string\n\ttab_id?: number | string | undefined\n\ttitle?: string | undefined\n\tis_focused?: boolean | undefined\n\tpane_cwd?: string | undefined\n\tpane_command?: string | undefined\n}\n\n/**\n * One `zellij action list-panes --json` call, parsed defensively — never throws on bad output. The id\n * is coerced to a string so a bare-integer id (`3`) and a prefixed one (`terminal_3`) are compared as\n * strings by `samePane` rather than one being a number.\n */\nfunction listZellijPanes(exec: Exec): ZellijPane[] {\n\tconst out = exec('zellij', ['action', 'list-panes', '--json'])\n\tif (!out) return []\n\tlet parsed: unknown\n\ttry {\n\t\tparsed = JSON.parse(out)\n\t} catch {\n\t\treturn []\n\t}\n\tif (!Array.isArray(parsed)) return []\n\treturn parsed\n\t\t.filter((p): p is ZellijPane => p != null && (p as ZellijPane).id != null)\n\t\t.map((p) => ({ ...p, id: String(p.id) }))\n}\n\n/**\n * The `OpenedPane` for a pane `new-pane` just reported — its tab resolved from the one `list-panes`\n * record carrying that pane id, and the workspace filled from the ambient session name. Throws rather\n * than guessing a tab: `OpenedPane.tab` is required (every multiplexer has the Tab level), and a wrong\n * tab is worse than a loud failure.\n */\nfunction openedForPane(exec: Exec, paneId: string, session: string | undefined): OpenedPane {\n\tconst found = listZellijPanes(exec).find((p) => samePane(p.id, paneId))\n\tif (!found || found.tab_id == null) throw new Error(`zellij did not report a tab for the new pane ${paneId}`)\n\treturn openedPane(paneId, String(found.tab_id), session)\n}\n\n/**\n * The `OpenedPane` for a tab `new-tab` just reported — its initial pane resolved as the single\n * `list-panes` record carrying that tab id. Throws rather than guessing: a new tab must have a pane,\n * and a caller handed a tab with no pane could neither drive nor name it.\n */\nfunction openedForTab(exec: Exec, tabId: string, session: string | undefined): OpenedPane {\n\tconst pane = listZellijPanes(exec).find((p) => p.tab_id != null && String(p.tab_id) === tabId)\n\tif (!pane) throw new Error(`zellij did not report a pane in the new tab ${tabId}`)\n\treturn openedPane(pane.id, tabId, session)\n}\n\n/**\n * Assemble an `OpenedPane`, attaching `workspace` only when the ambient session name is known — the\n * absent-not-false convention. Every Zellij pane genuinely lives in a session, so where the name IS\n * known this is never absent, on every placement including `pane:*`; where it is not (the effect-less\n * singleton), it is omitted rather than reported as a false \"none\".\n */\nfunction openedPane(id: string, tab: string, session: string | undefined): OpenedPane {\n\tconst opened: OpenedPane = { id, tab }\n\tif (session) opened.workspace = session\n\treturn opened\n}\n\n/**\n * Env is native at NO tier on this backend — `new-pane`/`new-tab` take no `--env`, like wezterm. So\n * every `open` funnels through the same fallback herdr's worktree route uses: with a launch command,\n * env rides in as an `env K=V` prefix; with none, a warning names what did not land. `envFallback` is\n * a no-op when there is no env to carry, so this is safe to call unconditionally.\n */\nfunction runLaunch(\n\tadapter: MuxAdapter,\n\texec: Exec,\n\ttarget: OpenedPane,\n\tenv: Record<string, string> | undefined,\n\tlaunch: string | undefined,\n) {\n\tconst fallback = envFallback(env, launch)\n\tif (fallback.kind === 'dropped') {\n\t\tprocess.stderr.write(\n\t\t\t`env (${fallback.variables.join(', ')}) could not be set on this zellij pane — ` +\n\t\t\t\t'zellij has no --env flag on new-pane/new-tab\\n',\n\t\t)\n\t\treturn\n\t}\n\tif (fallback.command !== undefined) adapter.submit(exec, target, fallback.command)\n}\n\n/**\n * The core key vocabulary's Zellij spelling — a rename table, NOT a validation table: a token outside\n * the core is forwarded verbatim (the seam's passthrough), so this must not reject what it does not\n * recognize. Only the two members that differ from the core name are listed; `Up` `Down` `Left`\n * `Right` `Enter` `Tab` `Space` `Backspace` `F1`–`F12` are already Zellij's own names. Probed from\n * Zellij's key-binding vocabulary in the docs, not verified against a live `send-keys`:\n *\n * - `Escape` → `Esc`: Zellij's key name for that key is `Esc`.\n * - `C-c` → `Ctrl c`: Zellij writes a modified key as space-separated words (`\"Ctrl a\"`, `\"Alt Shift\n * b\"`), so the whole `Ctrl c` is one argument — the modifier and key are not two keys.\n */\nconst ZELLIJ_KEY_RENAMES: Readonly<Record<string, string>> = { Escape: 'Esc', 'C-c': 'Ctrl c' }\n\nfunction toZellijKey(key: string): string {\n\treturn ZELLIJ_KEY_RENAMES[key] ?? key\n}\n\n/**\n * A Zellij pane's label — its title, unless that title is the running command Zellij handed an unnamed\n * pane. Zellij defaults an unnamed pane's title to its `pane_command`, so a title equal to it is\n * ambient rather than chosen; exporting it would put the same manufactured name on every shell pane,\n * exactly the collision tmux's hostname guard exists to prevent. A title that differs from the command\n * is one someone set (`new-pane --name`/`rename-pane`), so it is the author's and survives.\n */\nfunction zellijLabel(title: string | undefined, command: string | undefined): string | undefined {\n\treturn title && title !== command ? title : undefined\n}\n\n/**\n * Normalize a pane id to a single canonical form for comparison. Per the Zellij docs a bare integer\n * `N` is the same terminal pane as `terminal_N`; `plugin_N` is a distinct space. So a bare id is\n * folded to its `terminal_` form and everything else is left as-is — a comparison-only transform,\n * never stored (`MuxTarget.id` stays whatever the backend reported).\n */\nfunction normalizePaneId(id: string): string {\n\treturn /^\\d+$/.test(id) ? `terminal_${id}` : id\n}\n\n/** Whether two pane ids name the same pane, treating a bare `N` and its `terminal_N` twin as equal. */\nfunction samePane(a: string, b: string): boolean {\n\treturn normalizePaneId(a) === normalizePaneId(b)\n}\n\n/** The last `n` lines of `text` — `read`'s client-side approximation of a trailing-lines capture. */\nfunction lastLines(text: string, n: number): string {\n\tconst lines = text.split('\\n')\n\treturn lines.slice(Math.max(0, lines.length - n)).join('\\n')\n}\n","import type { Exec } from './exec.ts'\n\ntype Mux = 'tmux' | 'herdr' | 'wezterm' | 'zellij' | 'screen' | 'none'\n\n/** A multiplexer that carries a per-pane env var, so a session can key its own identity from it. */\nexport type PaneMux = 'tmux' | 'herdr' | 'wezterm' | 'zellij'\n\nexport interface MuxProbe {\n\tmux: Mux\n\tpane?: string | undefined\n\t/** 'env' — trusted the $CYBER_MUX fast-path/override. 'ancestry' — walked the process tree. */\n\tvia: 'env' | 'ancestry'\n}\n\nexport interface ProbeOptions {\n\t/** Set `false` to skip the process-ancestry walk and answer `none` when the env fast-path misses —\n\t * for a caller that only trusts the explicit override. */\n\tdiscover?: boolean | undefined\n\t/**\n\t * The environment-variable namespace the fast-path reads, without the trailing `_PANE`. Defaults to\n\t * `CYBER_MUX`, so `<prefix>` names the mux (`tmux|herdr|wezterm|screen|none`) and `<prefix>_PANE`\n\t * the pane. `screen` is recognized here but is not a drivable backend — see `resolveMuxAdapter`.\n\t * A host embedding cyber-mux under its OWN namespace passes its prefix here and adopts the same\n\t * fast-path without forking detection.\n\t */\n\tenvPrefix?: string | undefined\n}\n\nconst KNOWN_MUX: readonly Mux[] = ['tmux', 'herdr', 'wezterm', 'zellij', 'screen', 'none']\n\nfunction isKnownMux(v: string | undefined): v is Mux {\n\treturn v != null && (KNOWN_MUX as readonly string[]).includes(v)\n}\n\n/**\n * The single source of the mux → per-pane-env-var mapping. tmux exports `$TMUX_PANE`; herdr exports\n * `$HERDR_PANE_ID` (both in the same `wX:pY`-style namespace); WezTerm exports `$WEZTERM_PANE` in\n * every pane (its own bare-integer id) — per the issue that requested that backend (#47), the same\n * fast-path extension `$TMUX_PANE`/`$HERDR_PANE_ID` already get; Zellij exports `$ZELLIJ_PANE_ID` in\n * every terminal pane (its own `terminal_N`/bare-`N` id) — per the issue that requested this backend\n * (#46). screen carries no per-pane env var. Both the ancestry probe and the `currentPane`\n * self-identity helper read the pane through this table so the two never diverge on which env var a\n * given mux uses.\n */\nconst PANE_ENV: Record<PaneMux, (env: NodeJS.ProcessEnv) => string | undefined> = {\n\ttmux: (env) => env['TMUX_PANE'],\n\therdr: (env) => env['HERDR_PANE_ID'],\n\twezterm: (env) => env['WEZTERM_PANE'],\n\tzellij: (env) => env['ZELLIJ_PANE_ID'],\n}\n\n/**\n * Resolve THIS session's own pane from env alone (no `ps` walk): the `$CYBER_MUX_PANE` fast-path a\n * spawn propagates → `$TMUX_PANE` (tmux) → `$HERDR_PANE_ID` (herdr) → `$WEZTERM_PANE` (wezterm) →\n * `$ZELLIJ_PANE_ID` (zellij). Returns the pane tagged with its multiplexer, or undefined when the\n * session is in no pane-carrying multiplexer. This is the mux-agnostic self-identity key.\n */\nexport function currentPane(env: NodeJS.ProcessEnv): { mux: PaneMux; pane: string } | undefined {\n\tif (env['CYBER_MUX_PANE']) {\n\t\t// The fast-path pane carries its mux in $CYBER_MUX (herdr/wezterm/zellij spawns tag it; tmux is\n\t\t// the default when none does).\n\t\tconst mux: PaneMux =\n\t\t\tenv['CYBER_MUX'] === 'herdr'\n\t\t\t\t? 'herdr'\n\t\t\t\t: env['CYBER_MUX'] === 'wezterm'\n\t\t\t\t\t? 'wezterm'\n\t\t\t\t\t: env['CYBER_MUX'] === 'zellij'\n\t\t\t\t\t\t? 'zellij'\n\t\t\t\t\t\t: 'tmux'\n\t\treturn { mux, pane: env['CYBER_MUX_PANE'] }\n\t}\n\tconst tmux = PANE_ENV.tmux(env)\n\tif (tmux) return { mux: 'tmux', pane: tmux }\n\tconst herdr = PANE_ENV.herdr(env)\n\tif (herdr) return { mux: 'herdr', pane: herdr }\n\tconst wezterm = PANE_ENV.wezterm(env)\n\tif (wezterm) return { mux: 'wezterm', pane: wezterm }\n\tconst zellij = PANE_ENV.zellij(env)\n\tif (zellij) return { mux: 'zellij', pane: zellij }\n\treturn undefined\n}\n\n/**\n * Two-mode multiplexer detection.\n *\n * Fast-path: `$CYBER_MUX` (tmux | herdr | wezterm | zellij | screen | none) is trusted outright —\n * this also serves as an OVERRIDE (`=none` forces no-mux even inside a real multiplexer).\n * `$CYBER_MUX_PANE` carries the pane id alongside it. Detection RECOGNIZES `screen` (so an override\n * pinning it, or a real screen ancestor, is reported truthfully rather than silently ignored), but\n * `screen` is not a drivable backend — `resolveMuxAdapter` rejects it with a reason. Recognition is\n * not support.\n *\n * Discovery (else): walk the process ancestry from `$$` via `ps -o ppid=,comm= -p <pid>`, since the\n * tool's own shell may not be the human's pane. `$TMUX`/`$HERDR_ENV` are NOT trusted alone — they\n * are used only as a fast-positive hint the ancestry walk falls back to when the walk itself is\n * inconclusive (e.g. `ps` unavailable), never as a substitute for it.\n */\nexport function probeMultiplexer(exec: Exec, env: NodeJS.ProcessEnv, opts: ProbeOptions = {}): MuxProbe {\n\t// A host with its own namespace ADOPTS the fast-path by passing its prefix — `<prefix>` names the\n\t// mux and `<prefix>_PANE` the pane — rather than forking the probe. Defaults to `CYBER_MUX`, so an\n\t// unset `envPrefix` is exactly today's behavior. NOT an alias list: one prefix per call, the host's.\n\tconst prefix = opts.envPrefix ?? 'CYBER_MUX'\n\tconst override = env[prefix]\n\tconst pane = env[`${prefix}_PANE`]\n\tif (isKnownMux(override)) {\n\t\treturn {\n\t\t\tmux: override,\n\t\t\t...(pane ? { pane } : {}),\n\t\t\tvia: 'env',\n\t\t}\n\t}\n\tif (opts.discover === false) return { mux: 'none', via: 'ancestry' }\n\treturn discoverByAncestry(exec, env)\n}\n\nconst MUX_COMM: readonly { re: RegExp; mux: Mux }[] = [\n\t{ re: /^tmux(:|$)/, mux: 'tmux' },\n\t{ re: /^herdr(:|$)/, mux: 'herdr' },\n\t// Unverified against a live WezTerm (no GUI in this sandbox) — its GUI process is commonly\n\t// `wezterm-gui`, and a headless mux server `wezterm-mux-server`; both are matched so the walk\n\t// does not miss the one form because the other happened to be named differently.\n\t{ re: /^wezterm(-gui|-mux-server)?(:|$)/, mux: 'wezterm' },\n\t// Zellij's server process is `zellij` (it runs a client and a server, both named `zellij`).\n\t{ re: /^zellij(:|$)/, mux: 'zellij' },\n\t{ re: /^screen(:|$)/, mux: 'screen' },\n]\n\n/** The per-pane env var for a mux, via the shared `PANE_ENV` table; undefined for screen/none. */\nfunction paneFor(mux: Mux, env: NodeJS.ProcessEnv): string | undefined {\n\treturn mux === 'tmux' || mux === 'herdr' || mux === 'wezterm' || mux === 'zellij' ? PANE_ENV[mux](env) : undefined\n}\n\n/**\n * An ancestry-discovered probe, OMITTING `pane` when the mux carries none — never carrying it as an\n * explicit `undefined`, so `MuxProbe.pane` stays an absent-or-present field (the same conditional\n * shape the `$CYBER_MUX_PANE` fast-path uses above).\n */\nfunction ancestryProbe(mux: Mux, env: NodeJS.ProcessEnv): MuxProbe {\n\tconst pane = paneFor(mux, env)\n\treturn { mux, ...(pane !== undefined ? { pane } : {}), via: 'ancestry' }\n}\n\nconst MAX_ANCESTORS = 32\n\nfunction walkAncestry(exec: Exec, env: NodeJS.ProcessEnv): MuxProbe | undefined {\n\tlet pid = process.pid\n\tconst seen = new Set<number>()\n\tfor (let i = 0; i < MAX_ANCESTORS; i++) {\n\t\tif (seen.has(pid)) break\n\t\tseen.add(pid)\n\t\tconst line = exec('ps', ['-o', 'ppid=,comm=', '-p', String(pid)])\n\t\tif (!line) break\n\t\tconst trimmed = line.trim()\n\t\tconst spaceIdx = trimmed.indexOf(' ')\n\t\tconst ppidStr = spaceIdx === -1 ? trimmed : trimmed.slice(0, spaceIdx)\n\t\tconst comm = spaceIdx === -1 ? '' : trimmed.slice(spaceIdx + 1).trim()\n\t\tconst ppid = Number.parseInt(ppidStr, 10)\n\t\tfor (const entry of MUX_COMM) {\n\t\t\tif (entry.re.test(comm)) return ancestryProbe(entry.mux, env)\n\t\t}\n\t\tif (!Number.isFinite(ppid) || ppid <= 1) break\n\t\tpid = ppid\n\t}\n\treturn undefined\n}\n\nfunction discoverByAncestry(exec: Exec, env: NodeJS.ProcessEnv): MuxProbe {\n\tconst found = walkAncestry(exec, env)\n\tif (found) return found\n\t// Ancestry walk was inconclusive (no ps, or no mux ancestor found) — fall back to the\n\t// fast-positive env hint rather than declaring 'none' outright. WezTerm has no separate\n\t// \"inside wezterm\" flag the way $TMUX/$HERDR_ENV are — $WEZTERM_PANE IS the hint, doubling as\n\t// both the fast-positive signal and the pane id. Zellij DOES have a dedicated flag: $ZELLIJ is\n\t// set inside any Zellij pane (its pane id rides separately in $ZELLIJ_PANE_ID, attached by\n\t// `ancestryProbe`), so it plays the same role as $TMUX/$HERDR_ENV.\n\tif (env['TMUX']) return ancestryProbe('tmux', env)\n\tif (env['HERDR_ENV']) return ancestryProbe('herdr', env)\n\tif (env['WEZTERM_PANE']) return ancestryProbe('wezterm', env)\n\tif (env['ZELLIJ']) return ancestryProbe('zellij', env)\n\treturn { mux: 'none', via: 'ancestry' }\n}\n","import type { Exec } from './exec.ts'\nimport type { MuxAdapter, MuxTarget } from './mux.ts'\n\n/** Max flush re-submits after the initial submit, before nudge fails loud. */\nconst DEFAULT_ATTEMPTS = 10\n/** Wait after a submit before reading the pane back, in ms. */\nconst DEFAULT_SETTLE_MS = 400\n/** How many trailing non-empty lines of `visible` count as the \"input box\" tail scanned for staged text. */\nconst TAIL_LINES = 5\n/** Length of the message prefix used as the staged-text needle. */\nconst NEEDLE_LEN = 40\n\nexport interface NudgeOptions {\n\t/** Max flush re-submits after the initial send; default 10. */\n\tattempts?: number | undefined\n\t/** Wait after a submit before reading back, in ms; default 400. */\n\tsettleMs?: number | undefined\n\tsleep?: ((ms: number) => Promise<void>) | undefined\n}\n\nexport interface NudgeResult {\n\ttaken: boolean\n\tresubmits: number\n}\n\n/**\n * Whether `message` is still sitting staged in `visible`'s input box (not yet submitted). A real\n * submit scrolls the message into the transcript, leaving the bottom input box empty, so the\n * needle is absent from the tail; while staged it sits in the bottom input box, so the needle is\n * present. `visible` being null/empty means the turn can't be confirmed — treated as still staged\n * so callers keep retrying rather than falsely reporting success.\n */\nexport function isStaged(visible: string | null | undefined, message: string): boolean {\n\tif (!visible) return true\n\tconst collapsedMessage = message.replace(/\\s+/g, ' ').trim()\n\tconst needle = collapsedMessage.slice(0, NEEDLE_LEN)\n\tif (needle === '') return true\n\tconst tailLines = visible\n\t\t.split('\\n')\n\t\t.filter((l) => l.trim() !== '')\n\t\t.slice(-TAIL_LINES)\n\tconst tail = tailLines.join(' ').replace(/\\s+/g, ' ').trim()\n\treturn tail.includes(needle)\n}\n\n/**\n * Submit `message` to `target` and verify the peer actually took the turn — a booting harness can\n * swallow the Enter of the initial `submit`, leaving the text staged unsent while nudge would\n * otherwise report false success. Types the message exactly once; a swallowed Enter is recovered by\n * flushing the staged buffer (`adapter.submit` with no text, a bare Enter) — never re-typing the\n * message — up to a bounded number of attempts. Throws if the turn is never taken within the cap.\n *\n * A pane that no longer exists is rejected up front rather than retried: a gone pane and a booting\n * one both read back empty, so without the liveness probe the retry loop reports a dead peer as\n * \"never took the turn\" — a boot-race shape — and buries the real cause.\n *\n * **Not built on `waitForOutput`, deliberately.** The two look alike and wait on opposite conditions:\n * `waitForOutput` returns when a pattern APPEARS anywhere in the snapshot, while nudge returns when the\n * message DISAPPEARS from the input box at the bottom (`isStaged`) — a negative, position-sensitive\n * condition the wait primitive cannot express, and one that must not be satisfied by the same text\n * sitting up in the transcript, which is exactly where a submitted message ends up. Nor is the loop body\n * the same: nudge does not merely observe between polls, it re-submits, so its \"poll\" is a corrective\n * action with its own attempt budget rather than a read. What the two DO share is the liveness rule —\n * a gone pane throws instead of being reported as a quiet one — and `pollForOutput` adopts it from here.\n */\nexport async function nudge(\n\tadapter: MuxAdapter,\n\texec: Exec,\n\ttarget: MuxTarget,\n\tmessage: string,\n\topts: NudgeOptions = {},\n): Promise<NudgeResult> {\n\tconst attempts = opts.attempts ?? DEFAULT_ATTEMPTS\n\tconst settleMs = opts.settleMs ?? DEFAULT_SETTLE_MS\n\tconst sleep = opts.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)))\n\n\tif (!adapter.paneExists(exec, target)) {\n\t\tthrow new Error(`nudge failed: pane ${target.id} no longer exists — the peer's session is gone, not busy.`)\n\t}\n\n\tadapter.submit(exec, target, message)\n\tawait sleep(settleMs)\n\tif (!isStaged(adapter.read(exec, target), message)) return { taken: true, resubmits: 0 }\n\n\tfor (let attempt = 1; attempt <= attempts; attempt++) {\n\t\tadapter.submit(exec, target)\n\t\tawait sleep(settleMs)\n\t\tif (!isStaged(adapter.read(exec, target), message)) return { taken: true, resubmits: attempt }\n\t}\n\n\tthrow new Error(\n\t\t`nudge failed: peer at pane ${target.id} never took the turn — input still staged after ${attempts} re-submit attempts`,\n\t)\n}\n","import { type Exec, nodeExec } from './exec.ts'\nimport { herdrMuxAdapter } from './mux.herdr.ts'\nimport { tmuxMuxAdapter } from './mux.tmux.ts'\nimport type {\n\tAgentStatus,\n\tAgentWaitOptions,\n\tCreateWorktreeWorkspaceOptions,\n\tLivePane,\n\tMuxAdapter,\n\tMuxOpenOptions,\n\tMuxReadOptions,\n\tMuxSpaceTier,\n\tMuxTarget,\n\tMuxWaitOptions,\n\tMuxWaitResult,\n\tOpenedPane,\n\tOpenWorktreeWorkspaceOptions,\n\tRegionPane,\n\tWorkspaceTab,\n\tWorktreeWorkspace,\n} from './mux.ts'\nimport { weztermMuxAdapter } from './mux.wezterm.ts'\nimport { createZellijAdapter } from './mux.zellij.ts'\nimport { currentPane, probeMultiplexer } from './mux-probe.ts'\nimport { type NudgeOptions, type NudgeResult, nudge } from './nudge.ts'\n\n/**\n * Resolve the raw backend for the multiplexer this process is inside, via the two-mode mux probe\n * (`$CYBER_MUX` fast-path/override, else ancestry discovery from `$$` falling back to the\n * `$TMUX`/`$HERDR_ENV`/`$WEZTERM_PANE`/`$ZELLIJ` hint when the walk is inconclusive) —\n * tmux/herdr/wezterm/zellij map to their adapters; anything else throws, because a caller asking to\n * drive panes with no multiplexer has an unmet precondition, and a loud, actionable failure beats a\n * silent no-op.\n *\n * The zellij adapter is bound to the ambient session name (`$ZELLIJ_SESSION_NAME`) at resolution — it\n * reports that as `OpenedPane.workspace`, so the session name has to come from the SAME `env` the\n * probe reads. A missing name simply omits `workspace`; see `createZellijAdapter`.\n *\n * `screen` is a KNOWN mux — the probe detects it (ancestry) and honors it as an override (fast-path)\n * — but it is NOT a drivable backend, so it throws its OWN message naming the reason rather than the\n * generic \"no multiplexer\" one. That distinction is the whole point of issue #45: a caller who\n * pinned `CYBER_MUX=screen` declared a real multiplexer, and telling them \"run inside a multiplexer\"\n * would be a lie. The empirical probe behind the rejection is recorded in the ADR log\n * (`.agents/spec/design/decisions`): GNU Screen addresses its split regions POSITIONALLY (no\n * per-pane id), and `$WINDOW` is left unset in panes opened via `screen -X`, so a driver-created\n * pane has no stable identity to send to, read from, self-identify by, or enumerate — the exact\n * affordance `SessionTarget.id`/`currentPane`/`LivePane.id` are load-bearing on, and the exact one\n * wezterm (#47) had and screen lacks. Keeping the value KNOWN-but-rejected honors the override's\n * whole reason to exist: pinning it tells the caller the truth immediately instead of silently\n * discovering something else.\n *\n * Returns the PURE, exec-injected `MuxAdapter` seam — the one whose methods each take an `Exec`.\n * `resolveMux` binds one of these into a `MuxSession`; this is the lower layer, exported for a caller\n * that wants to thread its own runner per call rather than bind one. Not in a multiplexer? Gate on\n * `probeMultiplexer(env).mux !== 'none'` first — that answer never throws.\n */\nexport function resolveMuxAdapter(env: NodeJS.ProcessEnv, exec: Exec = nodeExec): MuxAdapter {\n\tconst probe = probeMultiplexer(exec, env)\n\tif (probe.mux === 'tmux') return tmuxMuxAdapter\n\tif (probe.mux === 'herdr') return herdrMuxAdapter\n\tif (probe.mux === 'wezterm') return weztermMuxAdapter\n\tif (probe.mux === 'zellij') return createZellijAdapter({ session: env['ZELLIJ_SESSION_NAME'] })\n\tif (probe.mux === 'screen') {\n\t\tthrow new Error(\n\t\t\t'cyber-mux detected GNU Screen, which it cannot drive: Screen addresses its split regions ' +\n\t\t\t\t'positionally (no per-pane id) and leaves $WINDOW unset in panes opened via `screen -X`, so a ' +\n\t\t\t\t'pane has no stable identity to send to, read from, or self-identify by. Run inside tmux ' +\n\t\t\t\t'($TMUX), herdr ($HERDR_ENV=1), wezterm ($WEZTERM_PANE set), or zellij ($ZELLIJ set) instead.',\n\t\t)\n\t}\n\tthrow new Error(\n\t\t'cyber-mux requires a session backend — run inside tmux ($TMUX), herdr ($HERDR_ENV=1), wezterm ($WEZTERM_PANE set), or zellij ($ZELLIJ set)',\n\t)\n}\n\n/**\n * The seams a `MuxSession` call may override. Every member is optional: an omitted member falls back\n * to whatever the session was bound with at `resolveMux` (which in turn defaults to `nodeExec`).\n *\n * This is the \"uniform\" trailing parameter every session method carries — pass nothing in the common\n * case, pass `{ exec }` for a one-off override (a recording fake in a test, a decorated runner).\n */\nexport interface MuxDeps {\n\texec?: Exec | undefined\n}\n\n/** The worktree capability with its `Exec` bound — see `MuxSession`. */\nexport interface BoundWorktreeWorkspaceCapability {\n\tcreateInWorkspace(opts: CreateWorktreeWorkspaceOptions, deps?: MuxDeps | undefined): WorktreeWorkspace\n\topenInWorkspace(opts: OpenWorktreeWorkspaceOptions, deps?: MuxDeps | undefined): WorktreeWorkspace\n\tbindings(opts: { primaryRoot: string }, deps?: MuxDeps | undefined): Map<string, string>\n\treleaseWorkspace(workspace: string, deps?: MuxDeps | undefined): void\n}\n\n/** The region-inspection capability with its `Exec` bound — see `MuxSession`. */\nexport interface BoundRegionInspector {\n\tdescribeRegion(target: MuxTarget, deps?: MuxDeps | undefined): RegionPane[]\n\tdescribeWorkspace(target: MuxTarget, deps?: MuxDeps | undefined): WorkspaceTab[]\n}\n\n/** The agent-lifecycle-wait capability with its `Exec` bound — see `MuxSession`. */\nexport interface BoundAgentLifecycle {\n\twaitForState(target: MuxTarget, opts: AgentWaitOptions, deps?: MuxDeps | undefined): AgentStatus\n}\n\n/**\n * A `MuxAdapter` with its `Exec` BOUND — the consumer-facing surface `resolveMux` returns.\n *\n * Every method mirrors `MuxAdapter` but drops the leading `exec` (bound once, at resolution) and gains\n * a trailing optional `deps?` for a per-call override. `callerPane` and `nudge` are folded in as\n * methods so a caller never re-plumbs the adapter or an exec into them: `mux.open(opts)` in the common\n * case, `mux.open(opts, { exec })` to override, and a test binds its fake once with\n * `resolveMux(env, { exec: fake })`.\n *\n * The raw `MuxAdapter` stays the pure, exec-injected seam underneath — and stays exported — for a\n * caller threading its own runner. Binding lives here at the composition layer, not inside each\n * adapter, so a driven adapter is never coupled to a concrete `nodeExec`.\n */\nexport interface MuxSession {\n\treadonly name: string\n\treadonly canSizeSplits?: boolean | undefined\n\tcallerPane(): MuxTarget | undefined\n\topen(opts: MuxOpenOptions, deps?: MuxDeps | undefined): OpenedPane\n\trename(target: MuxTarget, tier: MuxSpaceTier, name: string, deps?: MuxDeps | undefined): void\n\tgroup(target: MuxTarget, group: string, name?: string | undefined, deps?: MuxDeps | undefined): void\n\tsendText(target: MuxTarget, text: string, deps?: MuxDeps | undefined): void\n\tsendKeys(target: MuxTarget, keys: string[], deps?: MuxDeps | undefined): void\n\tsubmit(target: MuxTarget, text?: string | undefined, deps?: MuxDeps | undefined): void\n\tread(target: MuxTarget, opts?: MuxReadOptions | undefined, deps?: MuxDeps | undefined): string\n\twaitForOutput(target: MuxTarget, opts: MuxWaitOptions, deps?: MuxDeps | undefined): Promise<MuxWaitResult>\n\tfocus(target: MuxTarget, deps?: MuxDeps | undefined): void\n\tteardown(target: MuxTarget, deps?: MuxDeps | undefined): void\n\tpaneExists(target: MuxTarget, deps?: MuxDeps | undefined): boolean\n\tisPaneFocused(target: MuxTarget, deps?: MuxDeps | undefined): boolean | undefined\n\tlistPanes(deps?: MuxDeps | undefined): LivePane[]\n\tnudge(\n\t\ttarget: MuxTarget,\n\t\tmessage: string,\n\t\topts?: NudgeOptions | undefined,\n\t\tdeps?: MuxDeps | undefined,\n\t): Promise<NudgeResult>\n\treadonly worktree?: BoundWorktreeWorkspaceCapability | undefined\n\treadonly regions?: BoundRegionInspector | undefined\n\treadonly agentLifecycle?: BoundAgentLifecycle | undefined\n}\n\n/**\n * Resolve the multiplexer this process is inside and return it as a `MuxSession` with `exec` BOUND —\n * the ergonomic entry point. `deps.exec` (default `nodeExec`) runs the detection probe AND is the\n * default runner every method binds; a per-call `deps` overrides it.\n *\n * Throws when no supported multiplexer is detected (see `resolveMuxAdapter`); gate on\n * `probeMultiplexer(env).mux !== 'none'` first if a caller runs with-or-without one. For the pure\n * exec-injected seam, reach for `resolveMuxAdapter` instead.\n */\nexport function resolveMux(env: NodeJS.ProcessEnv, deps?: MuxDeps | undefined): MuxSession {\n\tconst boundExec = deps?.exec ?? nodeExec\n\tconst raw = resolveMuxAdapter(env, boundExec)\n\t// The resolved runner for a call: its own `deps.exec` override, else the exec bound at resolution.\n\tconst pick = (d?: MuxDeps | undefined): Exec => d?.exec ?? boundExec\n\treturn {\n\t\tname: raw.name,\n\t\t...(raw.canSizeSplits !== undefined ? { canSizeSplits: raw.canSizeSplits } : {}),\n\t\tcallerPane: () => callerPane(raw, env),\n\t\topen: (opts, d) => raw.open(pick(d), opts),\n\t\trename: (target, tier, name, d) => raw.rename(pick(d), target, tier, name),\n\t\tgroup: (target, group, name, d) => raw.group(pick(d), target, group, name),\n\t\tsendText: (target, text, d) => raw.sendText(pick(d), target, text),\n\t\tsendKeys: (target, keys, d) => raw.sendKeys(pick(d), target, keys),\n\t\tsubmit: (target, text, d) => raw.submit(pick(d), target, text),\n\t\tread: (target, opts, d) => raw.read(pick(d), target, opts),\n\t\twaitForOutput: (target, opts, d) => raw.waitForOutput(pick(d), target, opts),\n\t\tfocus: (target, d) => raw.focus(pick(d), target),\n\t\tteardown: (target, d) => raw.teardown(pick(d), target),\n\t\tpaneExists: (target, d) => raw.paneExists(pick(d), target),\n\t\tisPaneFocused: (target, d) => raw.isPaneFocused(pick(d), target),\n\t\tlistPanes: (d) => raw.listPanes(pick(d)),\n\t\tnudge: (target, message, opts, d) => nudge(raw, pick(d), target, message, opts),\n\t\t...(raw.worktree ? { worktree: bindWorktree(raw.worktree, pick) } : {}),\n\t\t...(raw.regions ? { regions: bindRegions(raw.regions, pick) } : {}),\n\t\t...(raw.agentLifecycle ? { agentLifecycle: bindAgentLifecycle(raw.agentLifecycle, pick) } : {}),\n\t}\n}\n\nfunction bindAgentLifecycle(\n\tagent: NonNullable<MuxAdapter['agentLifecycle']>,\n\tpick: (d?: MuxDeps | undefined) => Exec,\n): BoundAgentLifecycle {\n\treturn {\n\t\twaitForState: (target, opts, d) => agent.waitForState(pick(d), target, opts),\n\t}\n}\n\nfunction bindWorktree(\n\twt: NonNullable<MuxAdapter['worktree']>,\n\tpick: (d?: MuxDeps | undefined) => Exec,\n): BoundWorktreeWorkspaceCapability {\n\treturn {\n\t\tcreateInWorkspace: (opts, d) => wt.createInWorkspace(pick(d), opts),\n\t\topenInWorkspace: (opts, d) => wt.openInWorkspace(pick(d), opts),\n\t\tbindings: (opts, d) => wt.bindings(pick(d), opts),\n\t\treleaseWorkspace: (workspace, d) => wt.releaseWorkspace(pick(d), workspace),\n\t}\n}\n\nfunction bindRegions(\n\tregions: NonNullable<MuxAdapter['regions']>,\n\tpick: (d?: MuxDeps | undefined) => Exec,\n): BoundRegionInspector {\n\treturn {\n\t\tdescribeRegion: (target, d) => regions.describeRegion(pick(d), target),\n\t\tdescribeWorkspace: (target, d) => regions.describeWorkspace(pick(d), target),\n\t}\n}\n\n/**\n * This process's own pane, as something `adapter` can address — `MuxOpenOptions.from`'s intended\n * argument for a `pane:*` open, so a split lands on the caller rather than on whichever pane the\n * user is looking at (see `from`'s note for why each backend's default gets that wrong).\n *\n * `undefined` when this session is in no pane, or in a pane belonging to a *different* multiplexer\n * than `adapter` drives — that mismatch is reachable (a `$TMUX_PANE` inherited into a herdr pane,\n * `$CYBER_MUX` overridden to the other backend), and handing one backend the other's pane id would\n * turn a self-identity mixup into a split of some unrelated pane. Falling back to the backend's own\n * default is the conservative answer: still possibly the wrong pane, but never a foreign id.\n *\n * A `MuxSession` exposes this bound as `mux.callerPane()`; this free form is for the raw seam.\n */\nexport function callerPane(adapter: MuxAdapter, env: NodeJS.ProcessEnv): MuxTarget | undefined {\n\tconst self = currentPane(env)\n\treturn self && self.mux === adapter.name ? { id: self.pane } : undefined\n}\n"],"mappings":"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"}
|
package/dist/cli.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.mts","names":[],"sources":["../src/prompt.ts","../src/cli.ts"],"mappings":";;;;;;;;;;;;;;UAYiB;;;;;;;;;;;EAWhB,IAAI,kBAAkB,mBAAmB;;EAEzC;;;;;;;;KASW,mBAAmB;;;;;
|
|
1
|
+
{"version":3,"file":"cli.d.mts","names":[],"sources":["../src/prompt.ts","../src/cli.ts"],"mappings":";;;;;;;;;;;;;;UAYiB;;;;;;;;;;;EAWhB,IAAI,kBAAkB,mBAAmB;;EAEzC;;;;;;;;KASW,mBAAmB;;;;;UCqCd;EAChB,KAAK,OAAO;EACZ,MAAM;;;;EAIN,QAAQ;;;;EAIR,SAAS;;;;;;;iBA83DM,aAAa,UAAS,UAAyB;;;iBAgCzC,QAAQ"}
|