@martintrojer/murmur 0.2.0 → 0.2.1

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/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/ids.ts","../src/mux.ts","../src/store.ts","../src/paths.ts","../src/version.ts","../src/types.ts","../src/view.ts","../src/cli/clear.ts","../src/channel.ts","../src/snapshot.ts","../src/collector.ts","../src/identity.ts","../src/cli/identity-guard.ts","../src/cli/collect.ts","../src/cli/export.ts","../src/cli/init.ts","../src/cli/link.ts","../src/cli/notify.ts","../src/cli/peer.ts","../src/cli/pick.ts","../src/agents.ts","../src/glance.ts","../src/status.ts","../src/cli/status.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { registerClear } from \"./cli/clear.js\";\nimport { registerCollect } from \"./cli/collect.js\";\nimport { registerExport } from \"./cli/export.js\";\nimport { registerInit } from \"./cli/init.js\";\nimport { registerLink } from \"./cli/link.js\";\nimport { registerNotify } from \"./cli/notify.js\";\nimport { registerPeer } from \"./cli/peer.js\";\nimport { registerPick } from \"./cli/pick.js\";\nimport { registerStatus } from \"./cli/status.js\";\nimport { VERSION } from \"./index.js\";\n\nconst program = new Command();\nprogram\n .name(\"murmur\")\n .description(\"Agent state across every machine, in one view.\")\n .version(VERSION);\nregisterInit(program);\nregisterLink(program);\nregisterExport(program);\nregisterCollect(program);\nregisterClear(program);\nregisterNotify(program);\nregisterPeer(program);\nregisterStatus(program);\nregisterPick(program);\nprogram.parse();\n","/**\n * tmux's three id kinds, kept apart by the type system.\n *\n * tmux itself is unambiguous about this and prints a sigil on every id --\n * `session=$25 window=@75 pane=%89` -- but they are all strings, so murmur\n * could and did pass one where another was meant. Twice, in shipped code: a\n * sweep keyed on window liveness deleted ten live agents, and a window cached\n * at extension startup badged the window a moved pane had left.\n *\n * An agent is addressed by its PANE, which keeps its id across `move-pane`,\n * `break-pane`, and a window closed and reopened. A session and a window are\n * only where that pane currently lives, and both may differ between two reports\n * from one agent. So the rule the brands enforce is:\n *\n * only a pane may decide whether an agent exists.\n *\n * Branding is a compile-time fiction: at runtime these are the same strings\n * tmux printed, which is what keeps the snapshot document and every stored row\n * byte-identical.\n */\n\ndeclare const brand: unique symbol;\n\n/** A tmux session id, `$N`. Mutable location. */\nexport type SessionId = string & { readonly [brand]: \"session\" };\n\n/** A tmux window id, `@N`. Mutable location -- never an agent's identity. */\nexport type WindowId = string & { readonly [brand]: \"window\" };\n\n/** A tmux pane id, `%N`. The agent's identity, stable for its whole life. */\nexport type PaneId = string & { readonly [brand]: \"pane\" };\n\n/*\n * The boundary. Every raw string that becomes an id passes through one of these\n * three, so the unsafe step is in one file and countable rather than scattered\n * as `as` at each call site.\n *\n * Deliberately not validating the sigil. These are called on tmux stdout, on\n * JSON off the wire, on sqlite rows and on argv, and a node that recorded an id\n * murmur does not recognise -- a future tmux, a different harness -- must still\n * round-trip it. Rejecting here would turn a naming change into a behaviour\n * change.\n */\n\nexport function asSessionId(raw: string): SessionId {\n return raw as SessionId;\n}\n\nexport function asWindowId(raw: string): WindowId {\n return raw as WindowId;\n}\n\nexport function asPaneId(raw: string): PaneId {\n return raw as PaneId;\n}\n","import { execFileSync } from \"node:child_process\";\nimport {\n asPaneId,\n asSessionId,\n asWindowId,\n type PaneId,\n type SessionId,\n type WindowId,\n} from \"./ids.js\";\nimport type { Location } from \"./types.js\";\nimport type { RenderState } from \"./view.js\";\n\nexport interface Mux {\n currentWindow(): Location | null;\n livePanes(): Set<PaneId> | null;\n // Sets `@agent_state` on a WINDOW, even though the attention it expresses\n // belongs to a pane. The asymmetry is tmux's: the status bar and the `tms`\n // picker read a window option, and there is no per-pane equivalent they\n // would read instead. Its consequence is that a pane moving between windows\n // must clear the badge it left behind, since nothing else knows it moved.\n setWindowBadge(window: WindowId, state: RenderState | null): void;\n // Reports whether the attach actually happened. runTmux swallows failures to\n // return null, and a jump that silently failed looked exactly like \"enter did\n // nothing\" -- the symptom the remote probe was added to prevent, reproduced\n // on the local path.\n attach(session: SessionId, window: WindowId): boolean;\n windowForPane(pane: PaneId): WindowId | null;\n panesInWindow(window: WindowId): PaneId[];\n capture(pane: PaneId, lines?: number): string | null;\n // --- remote-jump session seam -------------------------------------------\n // A remote attach lives in its own local session rather than a window, so it\n // can be full-screen (no local status bar) and prefix-free (no nested ^b).\n // See jumpToAgent for why that is worth five extra methods.\n clientName(): string | null;\n currentTarget(): string | null;\n sessionNamed(name: string): boolean;\n newSession(name: string, command: string): boolean;\n setSessionOption(session: string, option: string, value: string): void;\n switchClient(client: string | null, session: string): boolean;\n}\n\nfunction runTmux(args: string[]): string | null {\n try {\n return execFileSync(\"tmux\", args, {\n encoding: \"utf8\",\n timeout: 3000,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n } catch {\n return null;\n }\n}\n\n/**\n * A session name as an exact target, in the two spellings tmux needs.\n *\n * Bare names match by PREFIX, so a wrapper for host `bub` silently retargets a\n * session called `bubba` once one exists -- verified, and it sets options on\n * the wrong session rather than failing. A leading `=` demands an exact match.\n * (`name=` is not the syntax; it reads as part of the name and matches nothing.)\n *\n * The trailing colon is the part that is easy to get wrong. `switch-client -t`\n * takes a target-SESSION, where `=name` is right, but `set-option -t` and\n * `show-options -t` take a target-PANE, where `=name` fails outright with `no\n * such session` and the exact form is `=name:` -- the empty window/pane part\n * resolving to the session's current pane.\n *\n * Neither rescues a name starting with `@`, `$` or `%`: those introduce tmux's\n * window, session and pane id syntax. remoteSessionName keeps them out.\n *\n * Both take a session NAME -- not a SessionId, which is why neither is branded.\n * `exactPaneTarget` is named for what it RETURNS, a tmux target-pane, because\n * what it takes and what it produces are different things and the old name\n * `exactPane` read as though it took a pane.\n */\nexport function exactSession(session: string): string {\n return `=${session}`;\n}\n\nexport function exactPaneTarget(session: string): string {\n return `=${session}:`;\n}\n\nexport function tmuxBadgeState(state: RenderState): string {\n // @agent_state is consumed by existing tmux configuration, whose public\n // vocabulary calls active work \"working\". Keep the internal activity named\n // \"running\" without forcing a coordinated config rollout.\n return state === \"running\" ? \"working\" : state;\n}\n\nexport const tmux: Mux = {\n currentWindow() {\n // $TMUX_PANE is the only trustworthy signal that we are inside a pane, and\n // it is set by tmux for every process in one.\n //\n // Asking tmux instead does not work: `display-message` answers from any\n // process on a machine with a running server, and reports whichever pane\n // that server considers active. A pi started outside tmux -- a bare ssh\n // login, a plain terminal, cron -- would then record itself as living in\n // some unrelated agent's pane and overwrite that agent's state. Falling\n // back to `display-message` here was exactly that bug.\n const raw = process.env.TMUX_PANE;\n if (!raw) return null;\n const pane = asPaneId(raw);\n\n // One call for ids and names together. The names travel with every row a\n // snapshot carries, because a reader cannot resolve a remote session or\n // window id against its own tmux.\n const fields = runTmux([\n \"display-message\",\n \"-t\",\n pane,\n \"-p\",\n \"#{session_id}\\t#{window_id}\\t#{session_name}\\t#{window_name}\",\n ]);\n const [session, window, sessionName, windowName] = fields?.split(\"\\t\") ?? [];\n if (!session || !window) return null;\n return {\n session: asSessionId(session),\n window: asWindowId(window),\n pane,\n session_name: sessionName || null,\n window_name: windowName || null,\n };\n },\n\n // Which of this host's PANES still exist. The only liveness question tmux is\n // ever asked, and the one that matches how an agent is addressed: a pane keeps\n // its id when it moves between windows, so a recorded window id can be gone\n // while the agent is very much alive.\n //\n // null means tmux could not answer; an empty set means it did and there are\n // none. Conflating the two would delete every agent on the host the moment\n // tmux was briefly unreachable.\n livePanes() {\n const out = runTmux([\"list-panes\", \"-a\", \"-F\", \"#{pane_id}\"]);\n if (out === null) return null;\n return new Set(out.split(\"\\n\").filter(Boolean).map(asPaneId));\n },\n\n setWindowBadge(window, state) {\n if (state === null) {\n runTmux([\"set-window-option\", \"-qu\", \"-t\", window, \"@agent_state\"]);\n } else {\n runTmux([\"set-window-option\", \"-q\", \"-t\", window, \"@agent_state\", tmuxBadgeState(state)]);\n runTmux([\"set-window-option\", \"-q\", \"-t\", window, \"@pane_agent\", \"1\"]);\n }\n runTmux([\"refresh-client\", \"-S\"]);\n },\n\n attach(session, window) {\n // Two steps, because switch-client alone is a no-op when the target window\n // is in the session you are already attached to — which is the common case\n // for a local agent, and why \"enter\" appeared to do nothing.\n // switch-client moves the client between sessions; select-window moves\n // that session to the right window.\n //\n // Only select-window decides the result. switch-client legitimately fails\n // when there is no client to switch (running outside tmux), and treating\n // that as a failed jump would report an error for a working attach.\n runTmux([\"switch-client\", \"-t\", session]);\n return runTmux([\"select-window\", \"-t\", window]) !== null;\n },\n\n // Sibling panes, for deciding whether an unowned pane may clear the window's\n // badge. A window holding an agent and a shell must not lose the badge when\n // you focus the shell.\n panesInWindow(window) {\n const out = runTmux([\"list-panes\", \"-t\", window, \"-F\", \"#{pane_id}\"]);\n return out?.split(\"\\n\").filter(Boolean).map(asPaneId) ?? [];\n },\n\n // Which client to send home when the remote attach exits. `switch-client`\n // with no -c moves whichever client tmux considers current, and `murmur pick`\n // usually runs in a popup -- a client of its own, which dies with the popup.\n // Naming the real client is what lets the return outlive the picker.\n clientName() {\n return runTmux([\"display-message\", \"-p\", \"#{client_name}\"]) || null;\n },\n\n // Where the jump started, as a switch-client target. Window-level, not just\n // the session: coming back to the right session but the wrong window is\n // still the wrong place. The window id is stable where its index is not,\n // since renumber-windows renumbers on every close.\n currentTarget() {\n return runTmux([\"display-message\", \"-p\", \"#{session_name}:#{window_id}\"]) || null;\n },\n\n // Whether a wrapper session for this host already exists. Deliberately not\n // returning an id: a session is addressed by name, so a `#{session_id}` would\n // only have to be turned back into one.\n sessionNamed(name) {\n const out = runTmux([\"list-sessions\", \"-F\", \"#{session_name}\"]);\n if (out === null) return false;\n return out.split(\"\\n\").includes(name);\n },\n\n newSession(name, command) {\n // Detached, because the caller sets the per-session options before showing\n // it. Creating it attached would paint one frame with the local status bar\n // up and the local prefix live, which is the flicker this design exists to\n // remove.\n return runTmux([\"new-session\", \"-d\", \"-s\", name, command]) !== null;\n },\n\n setSessionOption(session, option, value) {\n runTmux([\"set-option\", \"-t\", exactPaneTarget(session), option, value]);\n },\n\n switchClient(client, session) {\n const target = exactSession(session);\n const args = client\n ? [\"switch-client\", \"-c\", client, \"-t\", target]\n : [\"switch-client\", \"-t\", target];\n return runTmux(args) !== null;\n },\n\n // The window a pane belongs to, for a pane murmur holds no row for. Clearing\n // a badge is a tmux operation and does not require murmur to own the pane.\n windowForPane(pane) {\n const out = runTmux([\"display-message\", \"-t\", pane, \"-p\", \"#{window_id}\"]);\n return out ? asWindowId(out) : null;\n },\n\n capture(pane, lines) {\n const args = [\"capture-pane\", \"-p\", \"-t\", pane];\n if (lines !== undefined) args.push(\"-S\", `-${lines}`);\n return runTmux(args);\n },\n};\n\nexport function pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code !== \"ESRCH\";\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport { mkdirSync, rmSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport Database from \"better-sqlite3\";\nimport type { NodeIdentity } from \"./identity.js\";\nimport type { PaneId } from \"./ids.js\";\nimport { asPaneId, asSessionId, asWindowId } from \"./ids.js\";\nimport { pidAlive } from \"./mux.js\";\nimport { dbPath } from \"./paths.js\";\nimport type {\n ActivityUpdate,\n AgentClaim,\n AgentRelease,\n AttentionKind,\n AttentionRequest,\n ClaimResult,\n LocalWorld,\n PeerFetch,\n PeerRecord,\n ReconcileSummary,\n Snapshot,\n SnapshotAgent,\n SnapshotAttention,\n SnapshotPane,\n} from \"./types.js\";\nimport { MURMUR_VERSION } from \"./version.js\";\nimport { RENDER_PRIORITY } from \"./view.js\";\n\n/**\n * The storage version. Any change to any table bumps it.\n *\n * ONE version strategy: a mismatch salvages the peer names and targets a human\n * typed, deletes the file, and recreates the schema. No ALTER TABLE anywhere, so\n * there is no additive path to forget to use.\n */\nconst SCHEMA_USER_VERSION = 3;\n\nconst SCHEMA = `\n CREATE TABLE agents (\n agent_id TEXT NOT NULL PRIMARY KEY,\n pane TEXT NOT NULL UNIQUE,\n owner_pid INTEGER NOT NULL CHECK (owner_pid > 0),\n activity TEXT NOT NULL CHECK (activity IN ('running', 'stopped')),\n session TEXT NOT NULL,\n window TEXT NOT NULL,\n session_name TEXT,\n window_name TEXT,\n agent_name TEXT,\n pi_session TEXT,\n workstream TEXT,\n role TEXT,\n cli TEXT NOT NULL,\n driver TEXT NOT NULL CHECK (driver IN ('human', 'orchestrated')),\n claimed_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n ) STRICT;\n\n CREATE TABLE attention (\n pane TEXT NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN ('done', 'blocked', 'crashed')),\n message TEXT NOT NULL,\n source TEXT NOT NULL,\n session TEXT NOT NULL,\n window TEXT NOT NULL,\n session_name TEXT,\n window_name TEXT,\n requested_at INTEGER NOT NULL,\n PRIMARY KEY (pane, kind)\n ) STRICT;\n\n CREATE TABLE peers (\n name TEXT NOT NULL PRIMARY KEY,\n target TEXT NOT NULL,\n host_id TEXT,\n display_name TEXT,\n snapshot TEXT,\n snapshot_at INTEGER,\n fetched_at INTEGER,\n last_attempt_at INTEGER,\n last_error TEXT,\n murmur_version TEXT,\n snapshot_version INTEGER\n ) STRICT;\n`;\n\n/**\n * The store, and the only place in murmur that holds a database handle or\n * writes SQL.\n *\n * This interface is CLOSED. There is no `append`, no `ingest`, no log read, no\n * partial-row update, and no local read other than `localPanes` — each of those\n * shapes let a writer say something it had no standing to say, and each cost a\n * shipped bug. Attention methods take no agent identity at all, which is what\n * makes \"a notifier cannot corrupt an agent row\" structural.\n */\nexport interface Store {\n // --- agent lifecycle: owner-only, pid-gated -----------------------------\n claimAgent(claim: AgentClaim): ClaimResult;\n setActivity(update: ActivityUpdate): boolean;\n releaseAgent(release: AgentRelease): boolean;\n\n // --- attention: pane-addressed, no agent authority ----------------------\n requestAttention(request: AttentionRequest): void;\n acknowledgePane(pane: PaneId): number;\n\n // --- local truth --------------------------------------------------------\n /** The one local read. Joins agents and attention by pane. No reconciliation. */\n localPanes(): SnapshotPane[];\n reconcileLocal(world: LocalWorld): ReconcileSummary;\n buildLocalSnapshot(identity: NodeIdentity, world: LocalWorld): Snapshot;\n\n // --- peer cache ---------------------------------------------------------\n peers(): PeerRecord[];\n addPeer(name: string, target: string): void;\n removePeer(name: string): boolean;\n replacePeerSnapshot(name: string, fetch: PeerFetch): void;\n\n close(): void;\n}\n\ntype AgentDbRow = {\n agent_id: string;\n pane: string;\n owner_pid: number;\n activity: string;\n session: string;\n window: string;\n session_name: string | null;\n window_name: string | null;\n agent_name: string | null;\n pi_session: string | null;\n workstream: string | null;\n role: string | null;\n cli: string;\n driver: string;\n claimed_at: number;\n updated_at: number;\n};\n\ntype AttentionDbRow = {\n pane: string;\n kind: string;\n message: string;\n source: string;\n session: string;\n window: string;\n session_name: string | null;\n window_name: string | null;\n requested_at: number;\n};\n\ntype PeerDbRow = {\n name: string;\n target: string;\n host_id: string | null;\n display_name: string | null;\n snapshot: string | null;\n snapshot_at: number | null;\n fetched_at: number | null;\n last_attempt_at: number | null;\n last_error: string | null;\n murmur_version: string | null;\n snapshot_version: number | null;\n};\n\n/** Peer names and targets: the two fields a human typed, and all we salvage. */\nfunction salvagePeers(path: string): { name: string; target: string }[] {\n try {\n const existing = new Database(path, { fileMustExist: true });\n try {\n const version = (existing.pragma(\"user_version\", { simple: true }) as number) ?? 0;\n if (version === SCHEMA_USER_VERSION) return [];\n return existing.prepare(\"SELECT name, target FROM peers\").all() as {\n name: string;\n target: string;\n }[];\n } catch {\n // Too old to have the table, or unreadable. Nothing to save.\n return [];\n } finally {\n existing.close();\n }\n } catch {\n // No database yet, or one too broken to open.\n return [];\n }\n}\n\nfunction needsReset(path: string): boolean {\n try {\n const existing = new Database(path, { fileMustExist: true });\n try {\n return (\n ((existing.pragma(\"user_version\", { simple: true }) as number) ?? 0) !== SCHEMA_USER_VERSION\n );\n } finally {\n existing.close();\n }\n } catch {\n return false;\n }\n}\n\nfunction toAttention(row: AttentionDbRow): SnapshotAttention {\n return {\n kind: row.kind as AttentionKind,\n message: row.message,\n source: row.source,\n requested_at: row.requested_at,\n };\n}\n\nfunction toAgent(row: AgentDbRow): SnapshotAgent {\n return {\n agent_id: row.agent_id,\n activity: row.activity as SnapshotAgent[\"activity\"],\n agent_name: row.agent_name,\n pi_session: row.pi_session,\n workstream: row.workstream,\n role: row.role,\n cli: row.cli,\n driver: row.driver as SnapshotAgent[\"driver\"],\n claimed_at: row.claimed_at,\n updated_at: row.updated_at,\n };\n}\n\nconst PRIORITY = new Map<string, number>(RENDER_PRIORITY.map((kind, index) => [kind, index]));\n\nfunction attentionOrder(left: SnapshotAttention, right: SnapshotAttention): number {\n return (PRIORITY.get(left.kind) ?? 99) - (PRIORITY.get(right.kind) ?? 99);\n}\n\n/**\n * Open the store. Takes no arguments and mints no identity.\n *\n * `openStore` deliberately does NOT read or create `identity.json`: identity is\n * created only by `murmur init`, so a read path — a status-bar tick, a focus\n * hook — cannot bring a node into existence as a side effect.\n */\nexport function openStore(): Store {\n const path = dbPath();\n mkdirSync(dirname(path), { recursive: true });\n\n const salvaged = salvagePeers(path);\n if (needsReset(path)) {\n for (const suffix of [\"\", \"-wal\", \"-shm\"]) rmSync(`${path}${suffix}`, { force: true });\n }\n\n const database = new Database(path);\n database.pragma(\"journal_mode = WAL\");\n database.pragma(\"busy_timeout = 5000\");\n const version = (database.pragma(\"user_version\", { simple: true }) as number) ?? 0;\n if (version !== SCHEMA_USER_VERSION) {\n database.exec(SCHEMA);\n database.pragma(`user_version = ${SCHEMA_USER_VERSION}`);\n // Re-inserted with every OBSERVED column null: a salvaged peer has no\n // snapshot and has never been fetched, and saying otherwise would render a\n // never-reached host as fresh.\n const restore = database.prepare(\"INSERT OR IGNORE INTO peers (name, target) VALUES (?, ?)\");\n for (const peer of salvaged) restore.run(peer.name, peer.target);\n }\n\n const selectAgentByPane = database.prepare(\"SELECT * FROM agents WHERE pane = ?\");\n const insertAgent = database.prepare(`\n INSERT INTO agents (agent_id, pane, owner_pid, activity, session, window,\n session_name, window_name, agent_name, pi_session,\n workstream, role, cli, driver, claimed_at, updated_at)\n VALUES (@agent_id, @pane, @owner_pid, @activity, @session, @window,\n @session_name, @window_name, @agent_name, @pi_session,\n @workstream, @role, @cli, @driver, @claimed_at, @updated_at)\n `);\n const retainAgent = database.prepare(`\n UPDATE agents\n SET session = @session, window = @window, session_name = @session_name,\n window_name = @window_name, agent_name = @agent_name,\n pi_session = @pi_session, workstream = @workstream, role = @role,\n cli = @cli, driver = @driver, updated_at = @updated_at\n WHERE agent_id = @agent_id\n `);\n const deleteAgentByPane = database.prepare(\"DELETE FROM agents WHERE pane = ?\");\n const deleteAttentionForPane = database.prepare(\"DELETE FROM attention WHERE pane = ?\");\n const updateActivity = database.prepare(`\n UPDATE agents\n SET activity = @activity, session = @session, window = @window,\n session_name = @session_name, window_name = @window_name,\n updated_at = @updated_at\n WHERE agent_id = @agent_id AND owner_pid = @owner_pid\n `);\n const deleteAgentOwned = database.prepare(\n \"DELETE FROM agents WHERE agent_id = ? AND owner_pid = ?\",\n );\n const upsertAttention = database.prepare(`\n INSERT INTO attention (pane, kind, message, source, session, window,\n session_name, window_name, requested_at)\n VALUES (@pane, @kind, @message, @source, @session, @window,\n @session_name, @window_name, @requested_at)\n ON CONFLICT (pane, kind) DO UPDATE SET\n message = excluded.message,\n source = excluded.source,\n session = excluded.session,\n window = excluded.window,\n session_name = excluded.session_name,\n window_name = excluded.window_name\n `);\n const selectAgents = database.prepare(\"SELECT * FROM agents\");\n const selectAttention = database.prepare(\"SELECT * FROM attention\");\n const setActivityByPane = database.prepare(\n \"UPDATE agents SET activity = ?, updated_at = ? WHERE pane = ?\",\n );\n\n /**\n * `.immediate`, not deferred, and this is load-bearing.\n *\n * The transaction reads the incumbent row and then writes, so a deferred one\n * starts as a READER and must upgrade. Two doing that at once fails the loser\n * with SQLITE_BUSY_SNAPSHOT, which no busy_timeout can fix: waiting longer\n * cannot make a stale snapshot fresh. Measured previously at 5 of 8\n * concurrent writers failing.\n */\n const claimAgent = database.transaction((claim: AgentClaim): ClaimResult => {\n const now = claim.now ?? Date.now();\n const isAlive = claim.isAlive ?? pidAlive;\n const { location, meta, owner_pid } = claim;\n const incumbent = selectAgentByPane.get(location.pane) as AgentDbRow | undefined;\n\n const values = {\n pane: location.pane,\n owner_pid,\n session: location.session,\n window: location.window,\n session_name: location.session_name,\n window_name: location.window_name,\n agent_name: meta.agent_name,\n pi_session: meta.pi_session,\n workstream: meta.workstream,\n role: meta.role,\n cli: meta.cli,\n driver: meta.driver,\n updated_at: now,\n };\n\n if (!incumbent) {\n const agentId = randomUUID();\n insertAgent.run({ ...values, agent_id: agentId, activity: \"stopped\", claimed_at: now });\n return { outcome: \"claimed\", agent_id: agentId };\n }\n\n // Our own claim, seen again. This is what makes pi's `/reload` a no-op: pi\n // re-runs the extension factory in the same process, and a check that could\n // not recognise its own claim would silence the real agent. `activity` and\n // `agent_id` are deliberately untouched.\n if (incumbent.owner_pid === owner_pid) {\n retainAgent.run({ ...values, agent_id: incumbent.agent_id });\n return { outcome: \"retained\", agent_id: incumbent.agent_id };\n }\n\n // A different LIVE process in one pane: the nested-agent case, and the only\n // answer for it. Fails closed — `pidAlive` reports death only on ESRCH, so\n // an unanswerable probe (EPERM) reads as alive and refuses. An unknown must\n // never let a second writer displace a possibly-live owner.\n if (isAlive(incumbent.owner_pid)) {\n return { outcome: \"refused\", held_by_pid: incumbent.owner_pid };\n }\n\n // The previous occupant is gone. Its attention described a process that no\n // longer exists, and a human looking at the pane now sees a different agent.\n deleteAgentByPane.run(location.pane);\n deleteAttentionForPane.run(location.pane);\n const agentId = randomUUID();\n insertAgent.run({ ...values, agent_id: agentId, activity: \"stopped\", claimed_at: now });\n return { outcome: \"replaced\", agent_id: agentId, previous_agent_id: incumbent.agent_id };\n }).immediate;\n\n /**\n * One transaction, because the `stopped` write and its `crashed` attention row\n * must land together or not at all.\n *\n * A no-op when tmux could not answer: `panes === null` is absence of evidence,\n * not evidence of death, and conflating the two once deleted ten live agents.\n */\n const reconcileLocal = database.transaction((world: LocalWorld): ReconcileSummary => {\n const summary: ReconcileSummary = { crashed: [], removed: [], attention_removed: [] };\n if (world.panes === null) return summary;\n const live = world.panes;\n const isAlive = world.isAlive ?? pidAlive;\n const now = world.now ?? Date.now();\n\n // Which panes already carry a crash we recorded. Read once, before any\n // write, so the loop below sees the state reconciliation started from.\n const alreadyCrashed = new Set(\n (selectAttention.all() as AttentionDbRow[])\n .filter((row) => row.kind === \"crashed\")\n .map((row) => row.pane),\n );\n\n for (const row of selectAgents.all() as AgentDbRow[]) {\n const pane = asPaneId(row.pane);\n if (!live.has(pane)) {\n deleteAgentByPane.run(row.pane);\n deleteAttentionForPane.run(row.pane);\n summary.removed.push(pane);\n continue;\n }\n if (isAlive(row.owner_pid)) continue;\n\n // The asymmetry below is the point. A dead RUNNING owner is an unreported\n // crash and must leave a durable trace. A dead STOPPED owner finished\n // normally, so its row is noise — but any `done` it raised is a fact a\n // human has not yet seen, so the attention stays.\n if (row.activity === \"running\") {\n setActivityByPane.run(\"stopped\", now, row.pane);\n upsertAttention.run({\n pane: row.pane,\n kind: \"crashed\",\n message: \"\",\n source: \"murmur\",\n session: row.session,\n window: row.window,\n session_name: row.session_name,\n window_name: row.window_name,\n requested_at: now,\n });\n summary.crashed.push(pane);\n } else if (!alreadyCrashed.has(row.pane)) {\n deleteAgentByPane.run(row.pane);\n summary.removed.push(pane);\n }\n // A pane we already recorded a crash for keeps its agent row, and that is\n // the one place this deviates from a literal reading of the contract's\n // table -- which says a live pane with a dead STOPPED owner loses its row.\n // Taken literally, the second reconcile after a crash deletes the row the\n // first one had just marked `stopped`, so the crashed pane loses its\n // agent_name, workstream, role and cli one tick after the crash is\n // reported. That contradicts the contract's own idempotence requirement\n // (\"running it again changes nothing\") and it strips exactly the fields a\n // human needs to know WHICH agent died.\n //\n // The distinction the table is drawing is between an owner that finished\n // normally -- whose row is noise -- and one that died mid-run. The\n // `crashed` row we wrote is the record of which case this was, so it is\n // also the right thing to key on.\n }\n\n // Reaps attention for a pane that never had an agent row — an\n // attention-only codex pane whose window was closed. Nothing else would.\n for (const row of selectAttention.all() as AttentionDbRow[]) {\n const pane = asPaneId(row.pane);\n if (live.has(pane)) continue;\n deleteAttentionForPane.run(row.pane);\n if (!summary.attention_removed.includes(pane)) summary.attention_removed.push(pane);\n }\n\n return summary;\n }).immediate;\n\n /**\n * Both tables read at ONE point in time, or a pane can appear with an agent\n * and without the attention that was there when the agent was read.\n */\n const readLocalPanes = database.transaction((): SnapshotPane[] => {\n const agents = selectAgents.all() as AgentDbRow[];\n const attention = selectAttention.all() as AttentionDbRow[];\n const panes = new Map<string, SnapshotPane>();\n\n const locate = (row: AgentDbRow | AttentionDbRow): SnapshotPane => {\n const existing = panes.get(row.pane);\n if (existing) return existing;\n const created: SnapshotPane = {\n pane: asPaneId(row.pane),\n session: asSessionId(row.session),\n window: asWindowId(row.window),\n session_name: row.session_name,\n window_name: row.window_name,\n agent: null,\n attention: [],\n };\n panes.set(row.pane, created);\n return created;\n };\n\n for (const row of agents) locate(row).agent = toAgent(row);\n for (const row of attention) locate(row).attention.push(toAttention(row));\n\n for (const pane of panes.values()) pane.attention.sort(attentionOrder);\n return [...panes.values()].sort((left, right) => left.pane.localeCompare(right.pane));\n });\n\n function peerRecord(row: PeerDbRow): PeerRecord {\n let snapshot: Snapshot | null = null;\n if (row.snapshot !== null) {\n try {\n // Parsed leniently on the way OUT: it was validated on the way in, and\n // a read path must not throw. A stored document that no longer parses\n // reads as \"no snapshot\" and is left in place, not deleted.\n snapshot = JSON.parse(row.snapshot) as Snapshot;\n } catch {\n snapshot = null;\n }\n }\n return {\n name: row.name,\n target: row.target,\n host_id: row.host_id,\n display_name: row.display_name,\n snapshot,\n snapshot_at: row.snapshot_at,\n fetched_at: row.fetched_at,\n last_attempt_at: row.last_attempt_at,\n last_error: row.last_error,\n murmur_version: row.murmur_version,\n snapshot_version: row.snapshot_version,\n };\n }\n\n return {\n claimAgent,\n reconcileLocal,\n\n setActivity(update) {\n // Both key components are required, so a write from a REPLACED owner\n // matches nothing and returns false. That is not an error and must not be\n // retried: it means this process is no longer the owner of record, and the\n // correct response is silence.\n return (\n updateActivity.run({\n activity: update.activity,\n session: update.location.session,\n window: update.location.window,\n session_name: update.location.session_name,\n window_name: update.location.window_name,\n updated_at: update.now ?? Date.now(),\n agent_id: update.agent_id,\n owner_pid: update.owner_pid,\n }).changes === 1\n );\n },\n\n releaseAgent(release) {\n // Attention is deliberately NOT deleted: a `done` raised at settle must\n // survive the agent exiting, or completion becomes invisible the moment\n // the process quits.\n return deleteAgentOwned.run(release.agent_id, release.owner_pid).changes === 1;\n },\n\n requestAttention(request) {\n // `requested_at` is absent from the DO UPDATE list on purpose. Age means\n // \"how long this has gone unmet\", so a repeat must not reset the clock —\n // which also makes crash attention idempotent for free. Touches no\n // `agents` row, ever; there is no column here that could.\n upsertAttention.run({\n pane: request.location.pane,\n kind: request.kind,\n message: request.message,\n source: request.source,\n session: request.location.session,\n window: request.location.window,\n session_name: request.location.session_name,\n window_name: request.location.window_name,\n requested_at: request.now ?? Date.now(),\n });\n },\n\n acknowledgePane(pane) {\n // Every kind, one statement, no agent row touched: focusing a pane cannot\n // alter activity or owner metadata. This is the whole `murmur clear`\n // write path.\n return deleteAttentionForPane.run(pane).changes;\n },\n\n localPanes() {\n return readLocalPanes();\n },\n\n buildLocalSnapshot(identity, world) {\n // Reconcile first, which is what makes \"a snapshot is authoritative\"\n // true: absence from a successful snapshot means absence, so it must\n // never be produced from unreconciled rows. Two transactions rather than\n // one — a write transaction held open across the read would serialise\n // every focus hook on the machine behind an export.\n reconcileLocal(world);\n return {\n murmur_snapshot: 1,\n host_id: identity.host_id,\n display_name: identity.display_name,\n murmur_version: MURMUR_VERSION,\n generated_at: world.now ?? Date.now(),\n // Rule 3: a pane with no agent and no attention must not be published.\n // A no-op against today's `readLocalPanes`, which builds a pane entry\n // only from a row and so cannot produce an empty one -- kept because the\n // rule belongs to the DOCUMENT, and the validator rejects such an entry\n // outright. Without it, one narrowing of the local read would make this\n // node reachable-but-broken on every peer that collects it, and the\n // symptom would show up on the other machines.\n panes: readLocalPanes().filter((pane) => pane.agent !== null || pane.attention.length > 0),\n };\n },\n\n peers() {\n return (database.prepare(\"SELECT * FROM peers ORDER BY name\").all() as PeerDbRow[]).map(\n peerRecord,\n );\n },\n\n addPeer(name, target) {\n // Correcting a target must not discard the cache, so this updates only\n // the field the operator retyped.\n database\n .prepare(\n `INSERT INTO peers (name, target) VALUES (?, ?)\n ON CONFLICT(name) DO UPDATE SET target = excluded.target`,\n )\n .run(name, target);\n },\n\n removePeer(name) {\n return database.prepare(\"DELETE FROM peers WHERE name = ?\").run(name).changes > 0;\n },\n\n replacePeerSnapshot(name, fetch) {\n if (!fetch.ok) {\n // Failure touches neither snapshot, snapshot_at nor fetched_at, so the\n // last-known document stands and the peer ages into `stale` on its own.\n database\n .prepare(\"UPDATE peers SET last_attempt_at = ?, last_error = ? WHERE name = ?\")\n .run(fetch.at, fetch.error, name);\n return;\n }\n // Two clocks, and conflating them is how a freshly fetched three-hour-old\n // fact reads as new. `snapshot_at` is the PEER's clock (when it built the\n // document); `fetched_at` is OURS (when we reached it), and freshness is\n // computed from `fetched_at` only.\n database\n .prepare(\n `UPDATE peers\n SET snapshot = ?, snapshot_at = ?, fetched_at = ?, last_attempt_at = ?,\n last_error = NULL, host_id = ?, display_name = ?,\n murmur_version = ?, snapshot_version = ?\n WHERE name = ?`,\n )\n .run(\n JSON.stringify(fetch.snapshot),\n fetch.snapshot.generated_at,\n fetch.at,\n fetch.at,\n fetch.snapshot.host_id,\n fetch.snapshot.display_name,\n fetch.snapshot.murmur_version,\n fetch.snapshot.murmur_snapshot,\n name,\n );\n },\n\n close() {\n database.close();\n },\n };\n}\n","import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport function stateDir(): string {\n return (\n process.env.MURMUR_STATE_DIR ??\n join(process.env.XDG_STATE_HOME ?? join(homedir(), \".local\", \"state\"), \"murmur\")\n );\n}\n\nexport function configDir(): string {\n return (\n process.env.MURMUR_CONFIG_DIR ??\n join(process.env.XDG_CONFIG_HOME ?? join(homedir(), \".config\"), \"murmur\")\n );\n}\n\n/** The current-state database. The only database murmur holds. */\nexport function dbPath(): string {\n return join(stateDir(), \"state.db\");\n}\n","import { createRequire } from \"node:module\";\n\n/**\n * This node's murmur version, read from the manifest.\n *\n * Read rather than restated, for the reason index.ts already gives: two copies\n * of one fact drift, and npm bumps the manifest. It lives in its own module\n * because THREE bundles need it and they sit at different depths --\n * `dist/index.js`, `dist/cli.js` and `dist/extension/store.js` -- so a single\n * hardcoded `\"../package.json\"` resolves in two of them and throws in the third.\n *\n * That is not hypothetical. `openStore` moved into the extension bundle during\n * the current-state rewrite, and its `../package.json` became\n * `dist/package.json`, which does not exist. The extension catches every store\n * failure and degrades to silence, so the symptom was an agent that reported\n * nothing at all, with no error anywhere -- exactly the failure mode the\n * three-state store handle exists to make survivable, hiding a hard one.\n *\n * Hence both candidates, tried in order, and a throw if neither works: a version\n * this node cannot state belongs in a snapshot even less than a wrong one does.\n */\nfunction readVersion(): string {\n const require = createRequire(import.meta.url);\n for (const candidate of [\"../package.json\", \"../../package.json\"]) {\n try {\n return (require(candidate) as { version: string }).version;\n } catch {\n // Wrong depth for this bundle; try the next.\n }\n }\n throw new Error(\"cannot locate package.json to read the murmur version\");\n}\n\nexport const MURMUR_VERSION: string = readVersion();\n","import type { PaneId, SessionId, WindowId } from \"./ids.js\";\n\n/**\n * The three independent facts, as types.\n *\n * `activity` is what the pane's own process says it is doing. `attention` is\n * whether a human is wanted. `freshness` (src/view.ts) is how recently we\n * reached the node that reported. They are three independent fields, never one\n * enum, and absence carries meaning: no attention row means \"nothing to see\",\n * no agent row means \"no agent here\".\n */\nexport type Activity = \"running\" | \"stopped\";\nexport type AttentionKind = \"done\" | \"blocked\" | \"crashed\";\n\n/**\n * Who is waiting on this agent -- a human, or a supervisor that consumes the\n * result. Not \"which harness\"; that is `cli`.\n */\nexport type Driver = \"human\" | \"orchestrated\";\n\nexport const DEFAULT_DRIVER: Driver = \"human\";\n\n/**\n * Where a pane currently lives. Location, never identity.\n *\n * `pane` is the address and is stable for the life of the pane; `session` and\n * `window` are only where that pane currently is, and both change under\n * move-pane and break-pane. Only a pane may decide whether an agent exists,\n * which is what the brands in ./ids.js enforce.\n */\nexport type Location = {\n session: SessionId;\n window: WindowId;\n pane: PaneId;\n session_name: string | null;\n window_name: string | null;\n};\n\n/** Owner-reported metadata about the agent in a pane. */\nexport type AgentMeta = {\n agent_name: string | null;\n pi_session: string | null;\n workstream: string | null;\n role: string | null;\n cli: string;\n driver: Driver;\n};\n\nexport type PeerRecord = {\n name: string;\n target: string;\n host_id: string | null;\n display_name: string | null;\n /** The whole validated document, or null when we have never parsed one. */\n snapshot: Snapshot | null;\n /** The PEER's clock: when that node built the document. */\n snapshot_at: number | null;\n /** OUR clock: when we last reached it. Freshness is computed from this. */\n fetched_at: number | null;\n last_attempt_at: number | null;\n last_error: string | null;\n murmur_version: string | null;\n /** The peer's `murmur_snapshot` value, i.e. the document version it speaks. */\n snapshot_version: number | null;\n};\n\n/**\n * One node's whole current state. Complete, never a delta: a peer that returns\n * one has said everything it knows, so absence from it is absence.\n */\nexport type Snapshot = {\n murmur_snapshot: 1;\n host_id: string;\n display_name: string;\n murmur_version: string;\n generated_at: number;\n panes: SnapshotPane[];\n};\n\nexport type SnapshotPane = {\n pane: PaneId;\n session: SessionId;\n window: WindowId;\n session_name: string | null;\n window_name: string | null;\n /** Null for an attention-only pane: valid, listable, jumpable. */\n agent: SnapshotAgent | null;\n attention: SnapshotAttention[];\n};\n\nexport type SnapshotAgent = AgentMeta & {\n agent_id: string;\n activity: Activity;\n claimed_at: number;\n updated_at: number;\n};\n\nexport type SnapshotAttention = {\n kind: AttentionKind;\n message: string;\n source: string;\n requested_at: number;\n};\n\n/**\n * Whether a pid is still running. A parameter everywhere it is consulted, so a\n * test needs no process table.\n */\nexport type LiveCheck = (pid: number) => boolean;\n\nexport type AgentClaim = {\n location: Location;\n owner_pid: number;\n meta: AgentMeta;\n now?: number;\n isAlive?: LiveCheck;\n};\n\nexport type ClaimResult =\n | { outcome: \"claimed\"; agent_id: string }\n | { outcome: \"retained\"; agent_id: string }\n | { outcome: \"replaced\"; agent_id: string; previous_agent_id: string }\n | { outcome: \"refused\"; held_by_pid: number };\n\nexport type ActivityUpdate = {\n agent_id: string;\n owner_pid: number;\n activity: Activity;\n location: Location;\n now?: number;\n};\n\nexport type AgentRelease = { agent_id: string; owner_pid: number };\n\n/**\n * Everything an attention writer may say. There is no agent_id, no owner_pid,\n * no activity and no owner metadata field, and adding one is a contract change.\n */\nexport type AttentionRequest = {\n kind: AttentionKind;\n location: Location;\n message: string;\n source: string;\n now?: number;\n};\n\n/**\n * The only local facts reconciliation is allowed to consult.\n *\n * `panes` is null when tmux could not answer, which is not evidence of death.\n * `isAlive` and `now` are parameters so a test needs no process table and no\n * clock control.\n */\nexport type LocalWorld = {\n panes: Set<PaneId> | null;\n isAlive?: LiveCheck;\n now?: number;\n};\n\nexport type ReconcileSummary = {\n crashed: PaneId[];\n removed: PaneId[];\n attention_removed: PaneId[];\n};\n\nexport type PeerFetch =\n | { ok: true; snapshot: Snapshot; at: number }\n | { ok: false; error: string; at: number };\n","import type { NodeIdentity } from \"./identity.js\";\nimport type { PaneId, SessionId, WindowId } from \"./ids.js\";\nimport type { Store } from \"./store.js\";\nimport {\n type Activity,\n type AttentionKind,\n DEFAULT_DRIVER,\n type Driver,\n type SnapshotPane,\n} from \"./types.js\";\n\nexport type Freshness = \"fresh\" | \"stale\";\n\n/**\n * What a surface paints. Presentation only, derived from the three independent\n * facts and never stored.\n */\nexport type RenderState = \"crashed\" | \"blocked\" | \"done\" | \"running\" | \"idle\";\n\n/**\n * THE single ordering table: which state matters most, for sorting and for\n * choosing one word to show.\n *\n * `status.ts` and `pick.ts` import this rather than declaring their own copies,\n * so no two surfaces can sort one list differently.\n */\nexport const RENDER_PRIORITY: readonly RenderState[] = [\n \"crashed\",\n \"blocked\",\n \"done\",\n \"running\",\n \"idle\",\n];\n\n/**\n * The attention kinds only a human can answer, and the second table both\n * surfaces must agree on.\n *\n * `blocked` means waiting for an answer an orchestrator cannot give -- mu places\n * work, it cannot choose between two approaches. `crashed` means the process\n * died, which a supervisor may or may not retry. Everything else about an\n * orchestrated agent is its supervisor's business.\n *\n * `pick.ts` uses it to decide which crew rows are visible by default and\n * `status.ts` to decide which crew states reach the status bar. They were two\n * literals in two files answering one question, which is how a row that needed a\n * human became one a human could not see.\n */\nexport const NEEDS_HUMAN: readonly AttentionKind[] = [\"blocked\", \"crashed\"];\n\n/**\n * One pane, as every surface reads it: address, the three independent facts,\n * owner metadata, and ages.\n *\n * Local and remote panes are the same type, built by the same mapping, because\n * `Store.localPanes()` and a peer's cached snapshot both return\n * `SnapshotPane[]`. One mapping means local and remote cannot drift apart.\n */\nexport type PaneView = {\n // address\n host_id: string;\n /** The name the operator typed, or this node's display_name. */\n host: string;\n local: boolean;\n pane: PaneId;\n session: SessionId;\n window: WindowId;\n session_name: string | null;\n window_name: string | null;\n // the three independent facts\n /** Null for an attention-only pane, which has no agent row. */\n activity: Activity | null;\n attention: AttentionKind[];\n freshness: Freshness;\n // owner-reported metadata, null for an attention-only pane\n agent_id: string | null;\n agent_name: string | null;\n pi_session: string | null;\n workstream: string | null;\n role: string | null;\n cli: string | null;\n driver: Driver;\n // ages\n /** When the pane's own node last said something. Never `fetched_at`. */\n updated_at: number | null;\n /** When that node generated its snapshot. Null for local. */\n snapshot_at: number | null;\n /** When we last reached that node. Null for local. */\n fetched_at: number | null;\n};\n\n/**\n * How long a peer may go unfetched before its panes render stale.\n *\n * Re-exported from here rather than imported from the collector by view\n * consumers, so freshness has one definition. See collector.ts for why sixty\n * seconds.\n */\nexport const STALENESS_MS = 60_000;\n\n/**\n * A duration as the shortest thing worth reading: \"5m\", \"2h\", \"3d\".\n *\n * Under a minute is the empty string: an age that changes every second is noise\n * in a status column. This and `freshness` are the only two places a duration\n * becomes text or a verdict.\n */\nexport function age(ms: number | null): string {\n if (ms === null || ms < 60_000) return \"\";\n if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m`;\n if (ms < 86_400_000) return `${Math.floor(ms / 3_600_000)}h`;\n return `${Math.floor(ms / 86_400_000)}d`;\n}\n\n/**\n * Freshness of a NODE, never of an agent.\n *\n * A peer we have never reached is stale rather than fresh: null means the first\n * collect has not succeeded yet, and an unreachable host you just added must not\n * render as up to date.\n */\nexport function freshness(\n fetchedAt: number | null,\n now: number,\n thresholdMs = STALENESS_MS,\n): Freshness {\n return fetchedAt !== null && now - fetchedAt <= thresholdMs ? \"fresh\" : \"stale\";\n}\n\n/**\n * One word for a pane. Attention wins over activity, because attention is a\n * request and activity is a description.\n *\n * A running agent with `blocked` attention is a valid and expected state, and\n * surfaces that can show both, do — this is only for the ones that must pick.\n */\nexport function renderState(view: Pick<PaneView, \"activity\" | \"attention\">): RenderState {\n for (const kind of [\"crashed\", \"blocked\", \"done\"] as const) {\n if (view.attention.includes(kind)) return kind;\n }\n return view.activity === \"running\" ? \"running\" : \"idle\";\n}\n\n/** The newest attention request on a pane, for the `updated_at` of one with no agent. */\nfunction newestAttention(pane: SnapshotPane): number | null {\n let newest: number | null = null;\n for (const entry of pane.attention) {\n if (newest === null || entry.requested_at > newest) newest = entry.requested_at;\n }\n return newest;\n}\n\ntype ViewSource = {\n host_id: string;\n host: string;\n local: boolean;\n freshness: Freshness;\n snapshot_at: number | null;\n fetched_at: number | null;\n};\n\nfunction paneView(pane: SnapshotPane, source: ViewSource): PaneView {\n const agent = pane.agent;\n return {\n host_id: source.host_id,\n host: source.host,\n local: source.local,\n pane: pane.pane,\n session: pane.session,\n window: pane.window,\n session_name: pane.session_name,\n window_name: pane.window_name,\n activity: agent?.activity ?? null,\n attention: pane.attention.map((entry) => entry.kind),\n freshness: source.freshness,\n agent_id: agent?.agent_id ?? null,\n agent_name: agent?.agent_name ?? null,\n pi_session: agent?.pi_session ?? null,\n workstream: agent?.workstream ?? null,\n role: agent?.role ?? null,\n cli: agent?.cli ?? null,\n driver: agent?.driver ?? DEFAULT_DRIVER,\n updated_at: agent?.updated_at ?? newestAttention(pane),\n snapshot_at: source.snapshot_at,\n fetched_at: source.fetched_at,\n };\n}\n\n/**\n * Every pane this node knows about: its own, plus one cached snapshot per peer.\n *\n * `identity` is non-null because every caller is a command that already requires\n * `murmur init`, so no pane can be misclassified as remote by an absent one.\n *\n * No liveness is probed here, for local or remote. A remote pane's `activity` is\n * whatever its own node last said; a stale node keeps its last-known fields\n * verbatim beside an explicit warning.\n */\nexport function paneViews(store: Store, identity: NodeIdentity, now = Date.now()): PaneView[] {\n const views = store.localPanes().map((pane) =>\n paneView(pane, {\n host_id: identity.host_id,\n host: identity.display_name,\n local: true,\n // Local panes are always fresh: we are the node that authored them.\n freshness: \"fresh\",\n snapshot_at: null,\n fetched_at: null,\n }),\n );\n\n for (const peer of store.peers()) {\n const snapshot = peer.snapshot;\n if (!snapshot) continue;\n const source: ViewSource = {\n host_id: snapshot.host_id,\n // The name the human typed, not the machine's self-reported hostname: a\n // peer added as `linuxpc` can report a container id, which appears\n // nowhere else in the tool and cannot be typed at `peer remove`.\n host: peer.name,\n local: false,\n freshness: freshness(peer.fetched_at, now),\n snapshot_at: peer.snapshot_at,\n fetched_at: peer.fetched_at,\n };\n for (const pane of snapshot.panes) views.push(paneView(pane, source));\n }\n\n return views;\n}\n\nconst ORDER = new Map<RenderState, number>(RENDER_PRIORITY.map((state, index) => [state, index]));\n\n/**\n * Attention-first ordering, then the newest news, then address.\n *\n * TOTAL on purpose, and that is the whole reason the last two comparisons\n * exist. Ties on state and age are ordinary rather than exotic -- a pair of\n * crashed panes reconciled in one transaction shares a `requested_at` exactly --\n * and `Array.prototype.sort` is stable only with respect to the order it was\n * GIVEN, which here is whatever SQLite and the peer loop happened to produce. An\n * unbroken tie therefore makes the list depend on that order: a status bar\n * reshuffles between two identical ticks, and a picker row moves under the\n * keypress that was aimed at it.\n *\n * Presentation only. No caller may read meaning into the position of a row --\n * pane order in a snapshot carries none either, so a reader sorts for itself\n * rather than trusting what it was served.\n */\nexport function viewSort(views: PaneView[]): PaneView[] {\n return [...views].sort((left, right) => {\n const byState = (ORDER.get(renderState(left)) ?? 99) - (ORDER.get(renderState(right)) ?? 99);\n if (byState !== 0) return byState;\n // Unknown age sorts last within its state: an attention-only pane with no\n // timestamp is not news, and 0 is older than any real clock reading.\n const byAge = (right.updated_at ?? 0) - (left.updated_at ?? 0);\n if (byAge !== 0) return byAge;\n // Address as the final key, because it is the only field guaranteed unique\n // across the whole view: `pane` is unique per node and `host` per peer.\n const byHost = left.host.localeCompare(right.host);\n return byHost !== 0 ? byHost : left.pane.localeCompare(right.pane);\n });\n}\n","import type { Command } from \"commander\";\nimport { asPaneId, type WindowId } from \"../ids.js\";\nimport { type Mux, tmux } from \"../mux.js\";\nimport { openStore, type Store } from \"../store.js\";\nimport { RENDER_PRIORITY, type RenderState, renderState } from \"../view.js\";\n\n/**\n * Does any OTHER pane in this window still want attention?\n *\n * The badge is a WINDOW option while \"the user looked\" is only true of one pane,\n * so a window holding an agent and a shell must not lose the badge when you\n * focus the shell.\n *\n * The question is asked of ATTENTION only: a busy agent next door is not a\n * reason to keep an attention badge lit.\n *\n * Fails safe by keeping the badge: if tmux or the store cannot answer we say\n * yes. Wrongly keeping a badge is recoverable by focusing the pane; wrongly\n * clearing one loses the signal.\n */\nfunction windowBadge(window: WindowId, mux: Mux, store: Store): RenderState | null {\n const panes = new Set(mux.panesInWindow(window));\n const states = store\n .localPanes()\n .filter((pane) => panes.has(pane.pane))\n .map((pane) =>\n renderState({\n activity: pane.agent?.activity ?? null,\n attention: pane.attention.map((entry) => entry.kind),\n }),\n );\n return RENDER_PRIORITY.find((state) => state !== \"idle\" && states.includes(state)) ?? null;\n}\n\n/**\n * Acknowledge every attention request on one pane, and clear its window badge.\n *\n * That is the whole write path. There is no state focus must refuse to clear,\n * because attention is the only thing focus can address: `acknowledgePane` is a\n * single `DELETE FROM attention WHERE pane = ?` and cannot touch an agent's\n * activity, its identity or its owner metadata. A focus hook has nothing to\n * overwrite a running agent with.\n *\n * Best effort, silent and total: this runs inside the tmux server.\n */\nexport function clearPane(raw: string, mux: Mux = tmux): void {\n let store: Store | undefined;\n try {\n if (!raw) return;\n // argv is the boundary: a pane id arrives as a bare string from the tmux\n // hook that invoked us.\n const pane = asPaneId(raw);\n // The badge is a tmux window option, not murmur state, so resolving it never\n // needs murmur to know anything. A pane murmur has never seen can still\n // carry an orphan badge that nothing else will ever clear.\n const window = mux.windowForPane(pane);\n\n try {\n store = openStore();\n store.acknowledgePane(pane);\n } catch {\n // No database, or an unwritable one. The badge still clears below, which\n // is the visible half.\n }\n\n if (!window) return;\n // @agent_state is a derived, window-scoped projection. Recompute it after\n // deleting this pane's attention: blindly clearing the option made a live\n // running agent display as idle even though its agent row was untouched.\n try {\n mux.setWindowBadge(window, store ? windowBadge(window, mux, store) : null);\n } catch {\n // If the projection cannot be read, leave the existing badge alone. A\n // stale badge is recoverable; erasing a real signal is not.\n }\n } catch {\n // Focus hooks run inside the tmux server: they must always be silent and\n // total.\n } finally {\n try {\n store?.close();\n } catch {\n // Silent and total.\n }\n }\n}\n\nexport function registerClear(program: Command): void {\n program\n .command(\"clear\")\n .description(\"Acknowledge attention for a pane\")\n .option(\"--pane <pane-id>\", \"focused tmux pane id\")\n .action((options: { pane?: string }) => clearPane(options.pane ?? \"\"));\n}\n","import { execFile, execFileSync } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\nconst CONTROL_PATH = \"~/.ssh/control/%r@%h:%p\";\n\n// Both timeouts are sized against the tmux status bar, because that is what\n// actually drives collection: `murmur status` collects, and tmux re-runs it\n// every `status-interval` — 5s on the author's setup, 15s by default. A collect\n// that outlives its tick is a collect overlapping itself, and tmux offers no\n// way to cancel the last one.\n//\n// So the budget for the whole exchange is under 5s, and these are deliberately\n// aggressive: a really slow node is rejected rather than allowed to hold up the\n// HUD. That is cheap because the cost of losing the race is one tick of\n// staleness, and the next tick is five seconds away.\n\n// OpenSSH's default TCP connect timeout is the kernel's, 75s on macOS, which\n// made `murmur pick` unusable against a sleeping laptop. One second is still\n// ~6x a real cold handshake on a LAN or VPN (measured: 168ms cold, 42ms on a\n// warm control socket), and a peer that misses it simply shows stale — the\n// designed outcome for a host you cannot reach.\nconst CONNECT_TIMEOUT_S = 1;\n\n// Belt and braces for a host that completes the TCP connect and then stops\n// responding — ConnectTimeout does not cover that, and it is how a sleeping\n// laptop behaves. Bounds the whole exchange rather than just the dial, so it\n// has to leave room for the dial plus an export: three seconds is the tick\n// budget minus headroom for the rest of `status`.\nconst EXEC_TIMEOUT_MS = 3_000;\n\n// Warm if possible, cold if not, never interactive.\n//\n// ControlMaster=no attaches to a master socket left behind by an ordinary\n// `ssh <host>` (given ControlMaster auto + ControlPersist in ssh_config), so a\n// peer you have touched recently costs a new channel on an authenticated\n// connection rather than a handshake.\n//\n// When no socket is listening OpenSSH falls back to connecting normally, and we\n// want that: with plain key auth a cold peer collects fine, just slower\n// (~170ms against ~10ms measured on a LAN). Fleet visibility should not depend\n// on having ssh'd somewhere today.\n//\n// BatchMode=yes bounds what that fallback may do. It disables every\n// interactive prompt — password, passphrase, host key confirmation — so a\n// cold peer that cannot authenticate silently fails immediately instead of\n// blocking a background collect on a human. Note this is \"never prompt\", not\n// \"never authenticate\": a host demanding a hardware-token touch per connection\n// is the case this does not fully cover, and the reason `hasWarmSocket` exists\n// should that ever need gating.\n//\n// Exported because every ssh murmur runs wants exactly this posture -- the\n// collector, the picker's preview, the jump probe. Three hand-rolled copies is\n// how one of them ends up without BatchMode and starts prompting for auth on\n// every keypress.\nexport const SSH_OPTIONS = [\n \"-o\",\n \"BatchMode=yes\",\n \"-o\",\n \"ControlMaster=no\",\n \"-o\",\n `ControlPath=${CONTROL_PATH}`,\n \"-o\",\n `ConnectTimeout=${CONNECT_TIMEOUT_S}`,\n];\n\nexport interface Channel {\n exec(target: string, argv: string[]): Promise<string>;\n}\n\n// Node's execFile defaults to a 1 MiB stdout ceiling and rejects with\n// ERR_CHILD_PROCESS_STDIO_MAXBUFFER past it, killing the child. An export is the\n// peer's whole current state, bounded by live pane count -- a few hundred bytes\n// per pane, on a machine that cannot hold thousands of panes -- so the ceiling\n// is out of reach in practice.\n//\n// It is kept generous anyway, because the failure mode is bad out of proportion\n// to its likelihood: a peer whose document exceeds the buffer fails identically\n// on every collect, so it sits stale forever with an error that names a Node\n// internal rather than a size.\n//\n// 64 MiB is orders of magnitude above any real snapshot, and it is a\n// ceiling rather than an allocation. The timeout is the real bound on a\n// runaway peer.\nconst MAX_EXPORT_BYTES = 64 * 1024 * 1024;\n\nexport const ssh: Channel = {\n async exec(target, argv) {\n const { stdout } = await execFileAsync(\"ssh\", [...SSH_OPTIONS, target, ...argv], {\n encoding: \"utf8\",\n timeout: EXEC_TIMEOUT_MS,\n maxBuffer: MAX_EXPORT_BYTES,\n });\n return stdout;\n },\n};\n\nexport function hasWarmSocket(target: string): boolean {\n try {\n execFileSync(\"ssh\", [...SSH_OPTIONS, \"-O\", \"check\", target], { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n","import { asPaneId, asSessionId, asWindowId } from \"./ids.js\";\nimport type {\n Activity,\n AttentionKind,\n Driver,\n Snapshot,\n SnapshotAgent,\n SnapshotAttention,\n SnapshotPane,\n} from \"./types.js\";\n\n/**\n * A peer answered, and what it said is not a snapshot.\n *\n * A distinct type because the collector must be able to tell this from an\n * unreachable host: a node that serves a bad document is REACHABLE BUT BROKEN,\n * and an operator needs to see that rather than \"asleep, probably\".\n */\nexport class SnapshotInvalidError extends Error {\n constructor(\n readonly path: string,\n detail: string,\n ) {\n // An EMPTY path means the failure is about the document as a whole, not\n // about a field in it, so there is nothing to prefix. Joining regardless\n // produced `bubba: : not JSON (...)` in `peer list` and in the one line\n // `murmur collect` prints -- measured against a real second node, and for\n // the most common remote misconfiguration there is (murmur missing, so the\n // \"document\" is a shell error). `path` itself stays \"\", because that is what\n // it means and a caller must not have to know a sentinel.\n super(path === \"\" ? detail : `${path}: ${detail}`);\n this.name = \"SnapshotInvalidError\";\n }\n}\n\nfunction fail(path: string, detail: string): never {\n throw new SnapshotInvalidError(path, detail);\n}\n\n/**\n * Exactly these keys, no more and no fewer.\n *\n * Unknown keys are rejected rather than carried, and nothing is coerced or\n * defaulted: validation happens BEFORE storage, so no unknown value can reach a\n * sort, a count or a render path.\n */\nfunction object(value: unknown, path: string, keys: readonly string[]): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n fail(path, \"expected an object\");\n }\n const record = value as Record<string, unknown>;\n for (const key of keys) if (!(key in record)) fail(path, `missing key ${key}`);\n for (const key of Object.keys(record)) {\n if (!keys.includes(key)) fail(path, `unknown key ${key}`);\n }\n return record;\n}\n\nfunction text(value: unknown, path: string): string {\n if (typeof value !== \"string\" || value === \"\") fail(path, \"expected a non-empty string\");\n return value;\n}\n\nfunction textOrNull(value: unknown, path: string): string | null {\n if (value === null) return null;\n if (typeof value !== \"string\") fail(path, \"expected a string or null\");\n return value;\n}\n\nfunction anyText(value: unknown, path: string): string {\n if (typeof value !== \"string\") fail(path, \"expected a string\");\n return value;\n}\n\nfunction timestamp(value: unknown, path: string): number {\n if (typeof value !== \"number\" || !Number.isInteger(value) || value < 0) {\n fail(path, \"expected a non-negative integer\");\n }\n return value;\n}\n\nfunction member<T extends string>(value: unknown, path: string, allowed: readonly T[]): T {\n if (typeof value !== \"string\" || !allowed.includes(value as T)) {\n fail(path, `expected one of ${allowed.join(\", \")}`);\n }\n return value as T;\n}\n\nconst ACTIVITIES: readonly Activity[] = [\"running\", \"stopped\"];\nconst DRIVERS: readonly Driver[] = [\"human\", \"orchestrated\"];\nconst KINDS: readonly AttentionKind[] = [\"done\", \"blocked\", \"crashed\"];\n\nconst TOP_KEYS = [\n \"murmur_snapshot\",\n \"host_id\",\n \"display_name\",\n \"murmur_version\",\n \"generated_at\",\n \"panes\",\n] as const;\nconst PANE_KEYS = [\n \"pane\",\n \"session\",\n \"window\",\n \"session_name\",\n \"window_name\",\n \"agent\",\n \"attention\",\n] as const;\nconst AGENT_KEYS = [\n \"agent_id\",\n \"activity\",\n \"agent_name\",\n \"pi_session\",\n \"workstream\",\n \"role\",\n \"cli\",\n \"driver\",\n \"claimed_at\",\n \"updated_at\",\n] as const;\nconst ATTENTION_KEYS = [\"kind\", \"message\", \"source\", \"requested_at\"] as const;\n\nfunction parseAgent(value: unknown, path: string): SnapshotAgent | null {\n if (value === null) return null;\n const row = object(value, path, AGENT_KEYS);\n return {\n agent_id: text(row.agent_id, `${path}.agent_id`),\n activity: member(row.activity, `${path}.activity`, ACTIVITIES),\n agent_name: textOrNull(row.agent_name, `${path}.agent_name`),\n pi_session: textOrNull(row.pi_session, `${path}.pi_session`),\n workstream: textOrNull(row.workstream, `${path}.workstream`),\n role: textOrNull(row.role, `${path}.role`),\n cli: text(row.cli, `${path}.cli`),\n driver: member(row.driver, `${path}.driver`, DRIVERS),\n claimed_at: timestamp(row.claimed_at, `${path}.claimed_at`),\n updated_at: timestamp(row.updated_at, `${path}.updated_at`),\n };\n}\n\nfunction parseAttention(value: unknown, path: string): SnapshotAttention[] {\n if (!Array.isArray(value)) fail(path, \"expected an array\");\n const seen = new Set<AttentionKind>();\n return value.map((entry, index) => {\n const at = `${path}[${index}]`;\n const row = object(entry, at, ATTENTION_KEYS);\n const kind = member(row.kind, `${at}.kind`, KINDS);\n if (seen.has(kind)) fail(`${at}.kind`, `duplicate kind ${kind} for this pane`);\n seen.add(kind);\n return {\n kind,\n message: anyText(row.message, `${at}.message`),\n source: anyText(row.source, `${at}.source`),\n requested_at: timestamp(row.requested_at, `${at}.requested_at`),\n };\n });\n}\n\nfunction parsePane(value: unknown, path: string): SnapshotPane {\n const row = object(value, path, PANE_KEYS);\n const agent = parseAgent(row.agent, `${path}.agent`);\n const attention = parseAttention(row.attention, `${path}.attention`);\n // Rule 3 of the document schema: a pane with neither is not a pane worth\n // publishing, so a document carrying one is malformed rather than merely\n // noisy.\n if (agent === null && attention.length === 0) {\n fail(path, \"a pane with no agent and no attention must not be emitted\");\n }\n return {\n pane: asPaneId(text(row.pane, `${path}.pane`)),\n session: asSessionId(text(row.session, `${path}.session`)),\n window: asWindowId(text(row.window, `${path}.window`)),\n session_name: textOrNull(row.session_name, `${path}.session_name`),\n window_name: textOrNull(row.window_name, `${path}.window_name`),\n agent,\n attention,\n };\n}\n\n/**\n * Parse and totally validate one snapshot document.\n *\n * `murmur_snapshot` must be exactly 1: a higher value is rejected too, because\n * forward compatibility is not offered here and a version mismatch is an\n * operator-visible pairing problem. Saying so is the honest report; guessing at\n * a newer document's meaning is not.\n */\nexport function parseSnapshot(input: string): Snapshot {\n let parsed: unknown;\n try {\n parsed = JSON.parse(input);\n } catch (error) {\n fail(\"\", `not JSON (${error instanceof Error ? error.message : String(error)})`);\n }\n const top = object(parsed, \"\", TOP_KEYS);\n if (top.murmur_snapshot !== 1) {\n fail(\"murmur_snapshot\", `expected 1, got ${JSON.stringify(top.murmur_snapshot)}`);\n }\n if (!Array.isArray(top.panes)) fail(\"panes\", \"expected an array\");\n\n const panes = top.panes.map((entry, index) => parsePane(entry, `panes[${index}]`));\n const seen = new Set<string>();\n for (const pane of panes) {\n if (seen.has(pane.pane)) fail(\"panes\", `duplicate pane ${pane.pane}`);\n seen.add(pane.pane);\n }\n\n return {\n murmur_snapshot: 1,\n host_id: text(top.host_id, \"host_id\"),\n display_name: text(top.display_name, \"display_name\"),\n murmur_version: text(top.murmur_version, \"murmur_version\"),\n generated_at: timestamp(top.generated_at, \"generated_at\"),\n panes,\n };\n}\n","import type { Channel } from \"./channel.js\";\nimport { tmux } from \"./mux.js\";\nimport { parseSnapshot, SnapshotInvalidError } from \"./snapshot.js\";\nimport type { Store } from \"./store.js\";\nimport { STALENESS_MS } from \"./view.js\";\n\nexport { STALENESS_MS };\n\n// A reachable peer is cheap — milliseconds on a warm control socket, still\n// only a couple hundred cold. The cap is not about those.\n//\n// It is about the unreachable ones. Each in-flight peer is a forked ssh client\n// process, and a peer that is asleep or off the VPN holds that process for the\n// full ConnectTimeout. Unbounded fan-out over a long list puts every one of\n// them resident at once, which is process churn and file descriptors spent on\n// hosts that were never going to answer.\n//\n// Eight keeps the realistic fleet fully parallel while bounding that.\nexport const MAX_CONCURRENT_PEERS = 8;\n\n// The cap alone does not bound the collect. The per-peer ssh timeout applies\n// once per wave, so nine unreachable peers cost two waves: the pool serialises\n// the timeouts it exists to limit. So the whole collect gets its own deadline,\n// independent of peer count. Peers still in flight when it expires are\n// abandoned and render stale, which is already the designed outcome for a host\n// that did not answer in time.\n//\n// Four seconds: under a 5s tick, and above one full wave (a 3s exec ceiling\n// plus overhead) so a single wave is never cut short by the deadline itself.\nconst COLLECT_DEADLINE_MS = 4_000;\n\n/**\n * Runs `task` over `items` with at most `limit` in flight, preserving input\n * order in the output. Workers pull from a shared cursor rather than running\n * fixed batches, so one slow peer occupies a single slot instead of holding a\n * batch boundary.\n *\n * `deadline` bounds the whole run, not each task. Once it passes, workers stop\n * claiming new items and anything unstarted is left `undefined` for the caller\n * to treat as \"did not answer\". Tasks already in flight are not cancelled --\n * there is nothing to cancel a forked ssh with here -- but they no longer hold\n * the collect open, because the deadline races the pool rather than joining it.\n */\nasync function mapSettled<T, R>(\n items: readonly T[],\n limit: number,\n task: (item: T) => Promise<R>,\n deadline?: Promise<void>,\n): Promise<(PromiseSettledResult<R> | undefined)[]> {\n const results = new Array<PromiseSettledResult<R> | undefined>(items.length);\n let cursor = 0;\n let expired = false;\n const stop = deadline?.then(() => {\n expired = true;\n });\n const worker = async () => {\n while (cursor < items.length && !expired) {\n const index = cursor++;\n try {\n results[index] = { status: \"fulfilled\", value: await task(items[index] as T) };\n } catch (reason) {\n results[index] = { status: \"rejected\", reason };\n }\n }\n };\n const pool = Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));\n await (stop ? Promise.race([pool, stop]) : pool);\n return results;\n}\n\nexport type CollectResult = {\n peer: string;\n ok: boolean;\n /** Panes in the snapshot we just stored. Zero is a normal, valid answer. */\n panes: number;\n error?: string;\n /**\n * True when the peer could not be reached at all, as opposed to answering\n * with something wrong.\n *\n * A fleet normally has nodes that are asleep or switched off, so this is the\n * expected outcome rather than a fault, and callers use it to stay quiet\n * about the ordinary case while still reporting a peer that is reachable but\n * broken -- a bad snapshot version, a missing binary, an auth problem.\n */\n unreachable?: boolean;\n};\n\n/**\n * Whether an error means \"could not reach the host\".\n *\n * ssh exits 255 for its own failures and prints a recognisable line, and the\n * exec wrapper puts both in the message. Matching on the text is unpleasant but\n * it is the only signal available: the channel seam returns an Error, not an\n * exit status.\n *\n * `Permission denied` is deliberately NOT here. An auth misconfiguration is\n * reachable-but-broken and an operator task; classing it as \"asleep, probably\"\n * is how a fixable setup error stays invisible for weeks.\n */\nfunction isUnreachable(message: string): boolean {\n return (\n /Host is down|No route to host|Connection refused|Connection timed out|Connection closed|Operation timed out|Network is unreachable|Name or service not known|Could not resolve hostname|timed out after/i.test(\n message,\n ) || /\\bssh:/.test(message)\n );\n}\n\n/**\n * Drop Node's `Command failed: <argv>` first line, keeping the child's output.\n *\n * Every rejection from the ssh channel arrives in that shape, so the first ~140\n * characters of every real failure are the invocation murmur chose: `ssh -o\n * BatchMode=yes -o ControlMaster=no -o ControlPath=... -o ConnectTimeout=1\n * <host> murmur export`. The operator cannot act on any of it, and it pushed the\n * one line that mattered past the length bound below -- measured against a real\n * second node, where a missing remote binary printed\n * `bubba: Command failed: ssh -o BatchMode=yes ... murmur: command not f...`,\n * truncated on the only actionable word in it.\n *\n * Stripped BEFORE the newlines are collapsed, because the line boundary is the\n * only thing separating the invocation from the diagnosis.\n */\nfunction stripInvocation(message: string): string {\n const firstLine = message.indexOf(\"\\n\");\n if (firstLine === -1 || !message.startsWith(\"Command failed:\")) return message;\n const rest = message.slice(firstLine + 1).trim();\n // A bare `Command failed:` line with nothing after it is all we have; saying\n // nothing would be worse than saying too much.\n return rest === \"\" ? message : rest;\n}\n\n/**\n * The one normalisation, so classification and rendering cannot disagree.\n *\n * `unreachable` (a machine-readable flag on `CollectResult`) and\n * `describeFailure` (the line a human reads) both classify with\n * `isUnreachable`. Feeding them differently-normalised text is how a peer gets\n * reported as reachable-but-broken in JSON and \"unreachable\" in print, about\n * one fetch -- so both go through here.\n */\nfunction normalizeFailure(message: string): string {\n return stripInvocation(message).replace(/\\s+/g, \" \").trim();\n}\n\n/**\n * A peer failure in one line a human can act on.\n *\n * The raw error was the whole ssh invocation plus ssh's own message -- over 200\n * characters, of which the actionable part was the host name. It also leaked\n * every ssh option murmur passes, which a user cannot do anything about.\n */\nexport function describeFailure(peer: string, message: string): string {\n const collapsed = normalizeFailure(message);\n if (isUnreachable(collapsed)) {\n const reason = /ssh: (?:connect to host \\S+ port \\d+: )?(.+?)(?: \\(|$)/i.exec(collapsed);\n return `${peer}: unreachable (${(reason?.[1] ?? \"ssh failed\").trim()})`;\n }\n // Reachable but wrong: keep the message, since it is the diagnosis, but bound\n // it so a corrupt snapshot cannot print a screenful.\n const detail = collapsed.length > 160 ? `${collapsed.slice(0, 157)}...` : collapsed;\n return `${peer}: ${detail}`;\n}\n\n/**\n * Fetch every peer's snapshot, validate it, and replace the cache whole.\n *\n * Concurrent because an unreachable peer costs the full ssh timeout, and a\n * serial loop charged that to every peer behind it: three asleep laptops made\n * `murmur status` hang for thirty seconds. Applied serially in peer order,\n * because better-sqlite3 is synchronous and a stable order keeps the result list\n * aligned with `store.peers()`.\n *\n * One round trip per peer, and never a second: the document is complete, so what\n * arrives either replaces the cache entirely or does not touch it.\n */\nexport async function collect(\n store: Store,\n channel: Channel,\n now = Date.now(),\n deadline?: Promise<void>,\n): Promise<CollectResult[]> {\n const results: CollectResult[] = [];\n let timer: NodeJS.Timeout | undefined;\n try {\n const peers = store.peers();\n // Default deadline, injectable so tests do not have to wait out real time.\n // Unref'd: a pending timer must not hold the process open after a CLI\n // command has printed its output and finished.\n const bounded =\n deadline ??\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, COLLECT_DEADLINE_MS);\n timer.unref?.();\n });\n // Settled, not raw: a peer that fails while we are still applying an\n // earlier one would otherwise be an unhandled rejection for as long as it\n // sits in the queue.\n const fetches = await mapSettled(\n peers,\n MAX_CONCURRENT_PEERS,\n async (peer) => parseSnapshot(await channel.exec(peer.target, [\"murmur\", \"export\"])),\n bounded,\n );\n for (const [index, peer] of peers.entries()) {\n const fetch = fetches[index];\n try {\n // Undefined means the deadline passed before this peer was claimed or\n // finished. Not an error about the peer, so it says so plainly and\n // leaves fetched_at alone: the peer goes stale, which is the designed\n // outcome for a host that did not answer in time.\n if (!fetch) throw new Error(\"collect deadline passed before this peer answered\");\n if (fetch.status === \"rejected\") throw fetch.reason;\n // Every field the cache derives comes out of the document itself, so\n // the cache structurally cannot disagree with the snapshot it holds.\n store.replacePeerSnapshot(peer.name, { ok: true, snapshot: fetch.value, at: now });\n results.push({ peer: peer.name, ok: true, panes: fetch.value.panes.length });\n } catch (error) {\n // Normalised ONCE, here, before it is stored or returned.\n //\n // `last_error` is read by `peer list`, by `status --json` and by\n // anything built on the SDK, and none of them can undo the mangling: a\n // raw `execFile` rejection leads with `Command failed: ssh -o\n // BatchMode=yes -o ControlMaster=no -o ControlPath=... <host> murmur\n // export`, which is murmur's own invocation and nothing an operator can\n // act on. Measured against a real second node, where `peer list`\n // printed 140 characters of ssh options before the four words that\n // mattered. Storing the normalised text means every surface gets the\n // diagnosis without each one having to remember to strip it.\n const message = normalizeFailure(error instanceof Error ? error.message : String(error));\n store.replacePeerSnapshot(peer.name, { ok: false, error: message, at: now });\n // Reported through the return value, never printed here. `collect` runs\n // from `murmur status` on every status-bar tick, and from `pick` inside\n // a display-popup, so a single sleeping laptop wrote to stderr forever\n // and corrupted both. Only the `collect` command -- which a human ran\n // on purpose -- prints.\n results.push({\n peer: peer.name,\n ok: false,\n panes: 0,\n error: message,\n // A peer that answered with a bad document is reachable but broken,\n // and must be visibly so rather than silently stale.\n unreachable:\n error instanceof SnapshotInvalidError\n ? false\n : isUnreachable(normalizeFailure(message)),\n });\n }\n }\n } catch (error) {\n // The whole collect failed rather than one peer -- a broken peer table, say.\n // Still not printed: the caller decides.\n results.push({\n peer: \"\",\n ok: false,\n panes: 0,\n error: error instanceof Error ? error.message : String(error),\n });\n } finally {\n clearTimeout(timer);\n }\n\n // The only housekeeping left, and it runs once per invocation including with\n // zero peers -- which is why it is here rather than on `export`, which only\n // runs when a peer asks over ssh. A single-machine node would otherwise\n // reconcile never. Idempotent, so `buildLocalSnapshot` calling it too is a\n // cheap repeat rather than a second policy.\n try {\n store.reconcileLocal({ panes: tmux.livePanes(), now });\n } catch {\n // Housekeeping must not fail a command, and it must not report either.\n }\n return results;\n}\n","import { randomUUID } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { hostname } from \"node:os\";\nimport { join } from \"node:path\";\nimport { stateDir } from \"./paths.js\";\n\nexport type NodeIdentity = {\n host_id: string;\n display_name: string;\n};\n\nfunction identityPath(): string {\n return join(stateDir(), \"identity.json\");\n}\n\n/**\n * Memoized per process, keyed on the resolved path.\n *\n * `identity.json` cannot change under a running command, and the audit measured\n * eight redundant reads per invocation. Keyed on the path rather than a bare\n * boolean so a test that repoints `MURMUR_STATE_DIR` mid-process is not served\n * another directory's identity.\n */\nlet cache: { path: string; identity: NodeIdentity | null } | null = null;\n\n/**\n * This node's identity, or null when it has none.\n *\n * A READ, and only a read: nothing mints here. Every command that needs a\n * host_id fails with \"murmur is not initialised on this node; run: murmur init\"\n * rather than bringing a node into existence as a side effect of a status-bar\n * tick.\n */\nexport function loadIdentity(): NodeIdentity | null {\n const path = identityPath();\n if (cache?.path === path) return cache.identity;\n const identity = existsSync(path)\n ? (JSON.parse(readFileSync(path, \"utf8\")) as NodeIdentity)\n : null;\n cache = { path, identity };\n return identity;\n}\n\nfunction write(identity: NodeIdentity): NodeIdentity {\n mkdirSync(stateDir(), { recursive: true });\n writeFileSync(identityPath(), `${JSON.stringify(identity, null, 2)}\\n`);\n cache = { path: identityPath(), identity };\n return identity;\n}\n\n/** Create this node's identity. Only `murmur init` calls it. */\nexport function createIdentity(displayName = hostname()): NodeIdentity {\n if (loadIdentity()) throw new Error(`identity already exists: ${identityPath()}`);\n return write({ host_id: randomUUID(), display_name: displayName });\n}\n\n/**\n * Rename an existing node, keeping its `host_id`.\n *\n * `murmur init --name` on an already-initialised node used to ignore the flag\n * silently, which is the one thing a rename must not do.\n */\nexport function setDisplayName(displayName: string): NodeIdentity {\n const existing = loadIdentity();\n return write(\n existing\n ? { host_id: existing.host_id, display_name: displayName }\n : { host_id: randomUUID(), display_name: displayName },\n );\n}\n","import { loadIdentity, type NodeIdentity } from \"../identity.js\";\n\n/**\n * This node's identity, or null after printing why not.\n *\n * Every command that needs a `host_id` -- export, collect, status, pick, peer --\n * fails here rather than minting one, because a node that came into existence as\n * a side effect of a status-bar tick has an identity nobody chose. `notify` and\n * `clear` are absent from that list as a consequence of the model rather than as\n * an exemption: both address a pane, and attention is keyed on pane alone.\n */\nexport function requireIdentity(): NodeIdentity | null {\n const identity = loadIdentity();\n if (identity) return identity;\n process.stderr.write(\"murmur is not initialised on this node; run: murmur init\\n\");\n process.exitCode = 1;\n return null;\n}\n","import type { Command } from \"commander\";\nimport { ssh } from \"../channel.js\";\nimport { collect, describeFailure } from \"../collector.js\";\nimport { openStore } from \"../store.js\";\nimport { requireIdentity } from \"./identity-guard.js\";\n\nexport function registerCollect(program: Command): void {\n program\n .command(\"collect\")\n .description(\"Fetch each peer's snapshot\")\n .option(\"-q, --quiet\", \"report nothing, not even unreachable peers\")\n .action(async (options: { quiet?: boolean }) => {\n if (!requireIdentity()) return;\n const store = openStore();\n try {\n const results = await collect(store, ssh);\n if (options.quiet) return;\n\n // The ONLY place a peer failure is printed. `collect` is run by a human\n // or a timer that wants the answer, unlike `status` (every status-bar\n // tick) and `pick` (inside a display-popup), both of which used to print\n // the same thing and could not stop.\n //\n // One line per peer, on stderr so a caller can still parse stdout, and\n // never a stack or an ssh command line.\n for (const result of results) {\n if (result.ok || !result.error) continue;\n process.stderr.write(`murmur: ${describeFailure(result.peer, result.error)}\\n`);\n }\n\n // A summary only when something is wrong, and only for the case a human\n // can act on. An unreachable node is the normal state of a fleet -- a\n // laptop asleep, a box switched off -- so it is reported per peer above\n // and not counted as a failure here.\n if (results.some((result) => !result.ok && !result.unreachable)) {\n process.exitCode = 1;\n }\n } finally {\n store.close();\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { tmux } from \"../mux.js\";\nimport { openStore } from \"../store.js\";\nimport { requireIdentity } from \"./identity-guard.js\";\n\nexport function registerExport(program: Command): void {\n program\n .command(\"export\")\n // No options, and none to add: the document is complete, so a peer that\n // returns one has said everything it knows and absence in it is absence.\n // There is nothing narrower for a caller to ask for.\n .description(\"Print this node's current-state snapshot\")\n .action(() => {\n const identity = requireIdentity();\n if (!identity) return;\n const store = openStore();\n try {\n // `buildLocalSnapshot` reconciles first, which is what makes the\n // document authoritative: a snapshot built from unreconciled rows would\n // publish agents whose panes are gone, and a reader has no way to tell.\n const snapshot = store.buildLocalSnapshot(identity, { panes: tmux.livePanes() });\n process.stdout.write(`${JSON.stringify(snapshot)}\\n`);\n } finally {\n store.close();\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { createIdentity, loadIdentity, setDisplayName } from \"../identity.js\";\n\nexport function registerInit(program: Command): void {\n program\n .command(\"init\")\n .description(\"Initialize this node's identity\")\n .option(\"--name <name>\", \"display name\")\n .action((opts: { name?: string }) => {\n // `--name` on an already-initialised node RENAMES it, keeping the host_id.\n // It used to be ignored silently, which is the one thing a rename must not\n // do: the operator's only feedback was the old name printed back.\n const existing = loadIdentity();\n const identity = existing\n ? opts.name\n ? setDisplayName(opts.name)\n : existing\n : createIdentity(opts.name);\n console.log(`host_id: ${identity.host_id}`);\n console.log(`display_name: ${identity.display_name}`);\n });\n}\n","import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { Command } from \"commander\";\nimport { loadIdentity } from \"../identity.js\";\n\n/**\n * The installed extension, as a one-line re-export of this installation.\n *\n * `link pi` used to copy the whole built extension into\n * `~/.pi/agent/extensions/murmur.ts`. That made the copy a point-in-time\n * snapshot: upgrading murmur left the OLD extension running, with no warning\n * and nothing to compare against. The author's own machine was running an\n * extension missing two fixes that were already committed, which is exactly the\n * class of bug the extension exists to prevent -- a silently wrong state\n * report.\n *\n * A shim inverts that. The file pi loads never changes, and the code it points\n * at is whatever the current install has, so `npm install -g` is the whole\n * upgrade. The path is stable across upgrades because npm replaces a package's\n * contents in place rather than versioning the directory.\n *\n * Kept to one line on purpose: any logic here is logic that cannot be upgraded.\n */\n/**\n * Identifies a generated shim, so re-linking can tell it from an older inlined\n * copy. Keyed on its own line rather than on the import statement: the shim's\n * import style already changed once (static re-export to dynamic, to fix ESM\n * hoisting), and that silently broke this check -- re-linking a shim reported\n * \"replaced an inlined copy\".\n */\nconst SHIM_MARKER = \"// murmur:shim\";\n\nfunction shim(entry: string, storePath: string): string {\n return `${SHIM_MARKER}\n// Generated by \\`murmur link pi\\`. Do not edit.\n//\n// A re-export, not a copy: the extension code lives in the murmur install, so\n// upgrading murmur upgrades the extension with no reinstall step. Re-run\n// \\`murmur link pi\\` only if the install path itself moves.\n//\n// The store path is set here rather than resolved by the extension. A bare\n// specifier cannot resolve from ~/.pi/agent/extensions, and the failure is\n// silent: the import throws, the extension swallows it, and every state report\n// is dropped while the tmux badge still paints.\n//\n// A dynamic import, not \\`export ... from\\`: ESM hoists static re-exports above\n// this assignment, so the extension loaded before the variable was set and read\n// undefined. Verified -- the static form printed \\`undefined\\` in the target.\nprocess.env.MURMUR_STORE_MODULE ??= ${JSON.stringify(storePath)};\n\nconst { default: extension } = await import(${JSON.stringify(entry)});\nexport default extension;\n`;\n}\n\nexport function registerLink(program: Command): void {\n program\n .command(\"link\")\n .description(\"Install a murmur integration\")\n .argument(\"<target>\", \"integration to install\")\n .option(\n \"--copy\",\n \"inline the extension instead of re-exporting it (pins to this version; needs re-linking after an upgrade)\",\n )\n .action((target: string, options: { copy?: boolean }) => {\n if (target !== \"pi\") throw new Error(`unsupported link target: ${target}`);\n const destination = join(\n process.env.MURMUR_PI_HOME ?? homedir(),\n \".pi\",\n \"agent\",\n \"extensions\",\n \"murmur.ts\",\n );\n mkdirSync(dirname(destination), { recursive: true });\n\n const entry = fileURLToPath(new URL(\"./extension/murmur-pi.js\", import.meta.url));\n const storePath = fileURLToPath(new URL(\"./extension/store.js\", import.meta.url));\n\n // Identity is `init`-generated state by design, and the extension reads\n // it with loadIdentity rather than creating one -- an agent must not\n // decide what this node is called. But the consequence is silent: with no\n // identity the extension loads, registers its handlers, and reports\n // nothing, while the tmux badge still paints. Linking is the moment to say\n // so, since it is the only time a human is looking at this path.\n const identityMissing = loadIdentity() === null;\n\n if (!options.copy) {\n // Say when this replaced an inlined copy. Anyone linked before the shim\n // existed has a snapshot that stopped tracking upgrades silently, and\n // \"wrote a file\" does not tell them their agents may have been\n // misreporting. Best effort: a missing or unreadable file is the normal\n // first-install case and says nothing.\n let replacedCopy = false;\n try {\n const existing = readFileSync(destination, \"utf8\");\n replacedCopy = !existing.includes(SHIM_MARKER);\n } catch {\n // No previous install.\n }\n writeFileSync(destination, shim(entry, storePath));\n console.log(destination);\n if (replacedCopy) {\n console.log(\n \"Replaced an inlined copy from an older murmur. That copy was pinned to the version that wrote it, so it had stopped picking up fixes; running agents keep the old code until they restart.\",\n );\n }\n if (identityMissing) {\n console.log(\n \"This node has no identity yet, so the extension will record nothing. Run: murmur init\",\n );\n }\n return;\n }\n\n // The copy path, kept for the case the shim cannot serve: an extension\n // that has to keep working when the murmur install is gone or moved.\n //\n // Pin the store import to this installation's absolute path. The\n // extension lives in ~/.pi/agent/extensions, where a bare\n // \"@martintrojer/murmur/extension-store\" specifier cannot resolve — not\n // even for a global install. Unpinned, every append silently no-ops:\n // the tmux badge still paints, so nothing looks broken while the log\n // stays empty and the node exports nothing.\n const source = readFileSync(entry, \"utf8\");\n const pinned = source.replace(\n /\"@martintrojer\\/murmur\\/extension-store\"/,\n JSON.stringify(storePath),\n );\n if (pinned === source) {\n throw new Error(\"link pi: could not pin the store import; extension build changed\");\n }\n writeFileSync(destination, pinned);\n console.log(destination);\n if (identityMissing) {\n console.log(\n \"This node has no identity yet, so the extension will record nothing. Run: murmur init\",\n );\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { asPaneId } from \"../ids.js\";\nimport { type Mux, tmux } from \"../mux.js\";\nimport { openStore, type Store } from \"../store.js\";\nimport type { Location } from \"../types.js\";\n\n/**\n * The fields a harness may send, as flags or as a JSON object on stdin.\n *\n * Both forms exist because the two consumers differ: the codex hook line passes\n * flags, opencode's plugin pipes JSON. Same four fields either way.\n *\n * Note the spelling mismatch, which is not ours to fix: the payload calls it\n * `type`, the flag is `--event-type`. Both consumers are already written against\n * those exact names, and this verb exists to keep them working.\n */\ntype NotifyInput = {\n source?: string;\n title?: string;\n eventType?: string;\n message?: string;\n};\n\ntype NotifyPayload = Record<string, unknown>;\n\n/**\n * Resolve the four fields, flags beating the stdin payload.\n *\n * Flags win so the codex hook line behaves identically whether or not something\n * also arrives on stdin, which is what both consumers were written against.\n *\n * `message` falls back through title then event type before the generic\n * \"attention\": a notification whose text is a bare placeholder is worse than\n * one carrying whatever the harness did manage to say.\n */\nexport function notifyFields(\n input: NotifyInput,\n payload: NotifyPayload = {},\n): { source: string; message: string } {\n const field = (key: string, flag: string | undefined): string => {\n if (flag) return clean(flag);\n const value = payload[key];\n return typeof value === \"string\" ? clean(value) : \"\";\n };\n\n const source = field(\"source\", input.source) || \"agent\";\n const title = field(\"title\", input.title);\n const eventType = field(\"type\", input.eventType);\n const message = field(\"message\", input.message) || title || eventType || \"attention\";\n return { source, message };\n}\n\n/**\n * Strip control characters and collapse whitespace.\n *\n * This text reaches a tmux status line and a picker row, and it arrives from\n * another program's event payload. An embedded newline or escape sequence would\n * corrupt both surfaces, and `terminalText` in agents.ts exists for the same\n * reason on the read side -- this is the write side of that rule.\n */\nfunction clean(value: string): string {\n // Char codes, not a character class, and biome's noControlCharactersInRegex is\n // right to insist: an invisible byte in a pattern is a hazard, and the rule\n // fired here. `terminalText` in agents.ts avoids it the same way for the same\n // reason -- that is the read side of this rule, this is the write side.\n //\n // Replaced with a space rather than dropped, so \"line one\\nline two\" does not\n // become \"line oneline two\"; the collapse below then tidies the run.\n const flattened = [...value]\n .map((character) => {\n const code = character.charCodeAt(0);\n const control = code < 0x20 || code === 0x7f || (code >= 0x80 && code <= 0x9f);\n return control ? \" \" : character;\n })\n .join(\"\");\n return flattened.replace(/\\s+/g, \" \").trim();\n}\n\n/** Read a JSON object from stdin, or nothing. */\nexport function parsePayload(raw: string): NotifyPayload {\n if (!raw.trim()) return {};\n try {\n const parsed = JSON.parse(raw) as unknown;\n // An array or a scalar is not a payload. Ignored rather than rejected: a\n // notifier that pipes something odd should still get its attention row,\n // because the flags may carry everything needed.\n return typeof parsed === \"object\" && parsed !== null && !Array.isArray(parsed)\n ? (parsed as NotifyPayload)\n : {};\n } catch {\n return {};\n }\n}\n\n/**\n * Request `blocked` attention for a pane, on behalf of a harness that cannot\n * report itself.\n *\n * WHY THIS EXISTS. pi reports from inside itself, through the extension. codex\n * and opencode have no such hook -- they can only run a command when something\n * happens. `murmur notify` is that command; without it those two harnesses never\n * show `blocked`, and because the status bar keeps working for pi agents, nothing\n * looks broken.\n *\n * WHAT IT CANNOT DO, structurally. The only thing it may write is an\n * `AttentionRequest`, which has no field for an agent_id, an owner_pid, an\n * activity, or any owner metadata. `attention` is keyed on (pane, kind) and the\n * agents table is untouched by every statement this path runs, so a notifier\n * corrupting a live agent's row is unsayable rather than merely guarded against.\n *\n * `blocked` only, hard-coded: an external process cannot know that an agent\n * started, finished or crashed, so those stay the owner's alone. A harness can\n * request attention and nothing else.\n *\n * NO IDENTITY IS NEEDED, which follows from the model rather than being an\n * exemption: attention is addressed by pane, and a pane needs no host_id to name\n * it. So this cannot fail for want of `murmur init`.\n *\n * And the pane comes from the harness's own environment. The codex and opencode\n * hooks run as children of the agent process, in its pane, so $TMUX_PANE names\n * exactly the pane whose agent wants attention. `--pane` overrides it for a\n * notifier that runs elsewhere.\n */\nexport function runNotify(\n store: Store,\n input: NotifyInput & { pane?: string },\n payload: NotifyPayload = {},\n mux: Mux = tmux,\n): boolean {\n const location = resolveLocation(input.pane, mux);\n // No tmux and no pane. Silent and successful, because this runs from another\n // program's notification hook: a harness used outside tmux must not have its\n // own exit code broken by murmur having nothing to record.\n if (!location) return false;\n\n const { source, message } = notifyFields(input, payload);\n store.requestAttention({\n kind: \"blocked\",\n location,\n message,\n // The harness name goes here, not in `driver`. `driver` answers \"who is\n // waiting on this agent\" -- a human, or a supervisor consuming the result --\n // and a codex agent driven by a human is `human` on exactly that question.\n // `source` answers \"who asked\", which is the free-text field a new harness\n // needs no schema change for.\n source,\n });\n\n // The badge, so the status bar reflects it without waiting for a collect.\n mux.setWindowBadge(location.window, \"blocked\");\n return true;\n}\n\n/**\n * The pane this notification is about: the flag, else the caller's own pane.\n *\n * The no-flag path is the one both real consumers take, and the only one either\n * has ever used -- checked against the codex hook line and the opencode plugin,\n * neither of which passes a pane. Their hooks run as children of the agent\n * process, so `$TMUX_PANE` -- which `currentWindow` reads, and which tmux sets\n * for every process in a pane -- names exactly the pane whose agent wants\n * attention.\n *\n * `--pane` exists for a notifier that runs outside the pane it is reporting on,\n * and is deliberately implemented WITHOUT adding a pane-to-session lookup to the\n * Mux interface. `currentWindow` already resolves\n * a full location for the caller's own pane, and `--pane` is only meaningful\n * when it names a pane in the same tmux server, so the flag narrows an existing\n * answer rather than fetching a new one:\n *\n * - naming your own pane is the common case and resolves identically\n * - naming a DIFFERENT pane in the same window keeps that window's location,\n * which is correct, since session and window are exactly what the two panes\n * share\n * - naming a pane in another window returns null rather than guessing, because\n * recording a location this process cannot verify is how a row nothing can\n * clear gets written\n *\n * If a real consumer ever needs the third case, that is when the Mux interface\n * should grow a lookup -- not on speculation.\n */\nfunction resolveLocation(pane: string | undefined, mux: Mux): Location | null {\n const here = mux.currentWindow();\n if (!pane) return here;\n const target = asPaneId(pane);\n if (here && here.pane === target) return here;\n if (here && mux.panesInWindow(here.window).includes(target)) {\n return { ...here, pane: target };\n }\n return null;\n}\n\nexport function registerNotify(program: Command): void {\n program\n .command(\"notify\")\n .description(\"Record an attention request for a harness that cannot report itself\")\n .option(\"--source <name>\", \"harness name, e.g. codex or opencode\")\n .option(\"--event-type <type>\", \"why attention is wanted\")\n .option(\"--title <title>\", \"harness display title\")\n .option(\"--message <message>\", \"the text to show\")\n .option(\"--pane <pane>\", \"pane to notify about (default: $TMUX_PANE)\")\n .action(\n async (options: {\n source?: string;\n eventType?: string;\n title?: string;\n message?: string;\n pane?: string;\n }) => {\n const payload = parsePayload(await readStdin());\n const store = openStore();\n try {\n runNotify(store, options, payload);\n } finally {\n store.close();\n }\n },\n );\n}\n\n/** How long to wait for a piped payload before proceeding on flags alone. */\nconst STDIN_DEADLINE_MS = 250;\n\n/**\n * Whatever is on stdin, or \"\" when nothing arrives in time.\n *\n * BOUNDED, and that is a bug fix rather than caution. `isTTY` catches a notifier\n * run from a terminal, but it says nothing about a non-TTY stdin that never\n * closes -- an inherited pipe the parent created and never writes to, which is\n * the ordinary shape of a plugin host spawning a hook without redirecting\n * stdin. Reading to EOF then waits for an EOF that never comes:\n *\n * sleep 30 | murmur notify --source codex # hung; exit 124 under timeout\n *\n * A hung notify hook is a bad failure: it is a child of the agent process, it\n * holds a store handle, a harness that waits on its hook stalls, and its output\n * goes nowhere so nothing says why. The flags are already sufficient for every\n * documented consumer, so a deadline degrades to exactly the flags-only\n * behaviour codex relies on today.\n *\n * Two details stop the deadline becoming a different hang. The `data` listener\n * is removed BY REFERENCE, because a live handler keeps the stream referenced;\n * and the stream is `unref`ed rather than paused, because `pause()` stops the\n * flow while leaving the handle on the event loop. Verified with\n * `process._getActiveHandles()`, which still reported a `Socket` after a paused\n * read -- the work completed, the row was written, and the process still would\n * not exit. `unref` rather than `destroy`: this is declining to wait, not\n * tearing down a pipe the parent owns.\n */\nasync function readStdin(): Promise<string> {\n if (process.stdin.isTTY) return \"\";\n const chunks: Buffer[] = [];\n return new Promise<string>((resolve) => {\n const onData = (chunk: Buffer) => chunks.push(chunk);\n const done = () => {\n process.stdin.off(\"data\", onData);\n // Optional because only a PIPE is a Socket. Redirect stdin from a file or\n // /dev/null -- which `sh -lc` does, so this is the codex hook's own path --\n // and `process.stdin` is an fs ReadStream with no `unref` at all, so\n // calling it unconditionally threw TypeError and took the whole hook down.\n // Nothing is lost: a file or /dev/null reaches EOF on its own, and it is\n // only the never-ending pipe that needed releasing.\n process.stdin.unref?.();\n resolve(Buffer.concat(chunks).toString(\"utf8\"));\n };\n // Unreffed so the deadline itself cannot be what holds the process open.\n const timer = setTimeout(done, STDIN_DEADLINE_MS);\n timer.unref?.();\n process.stdin.on(\"data\", onData);\n process.stdin.once(\"end\", () => {\n clearTimeout(timer);\n done();\n });\n process.stdin.once(\"error\", () => {\n clearTimeout(timer);\n done();\n });\n });\n}\n","import { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Command } from \"commander\";\nimport { hasWarmSocket, ssh } from \"../channel.js\";\nimport { loadIdentity } from \"../identity.js\";\nimport { parseSnapshot } from \"../snapshot.js\";\nimport { openStore } from \"../store.js\";\nimport type { PeerRecord, Snapshot } from \"../types.js\";\nimport { age, freshness, STALENESS_MS } from \"../view.js\";\n\n/**\n * The snapshot document version this node speaks. One number, and the only one\n * the code enforces: `parseSnapshot` rejects anything else outright.\n */\nexport const SNAPSHOT_VERSION = 1;\n\nexport function parseSshHosts(config: string): string[] {\n const hosts: string[] = [];\n for (const line of config.split(\"\\n\")) {\n const tokens = line.replace(/#.*$/, \"\").trim().split(/\\s+/);\n if (tokens[0]?.toLowerCase() !== \"host\") continue;\n for (const host of tokens.slice(1)) {\n if (!/[*?!]/.test(host)) hosts.push(host);\n }\n }\n return hosts;\n}\n\nfunction sshHosts(): string[] {\n try {\n return parseSshHosts(readFileSync(join(homedir(), \".ssh\", \"config\"), \"utf8\"));\n } catch {\n return [];\n }\n}\n\n/**\n * Column-aligned plain text. Rows are all-ASCII here (peer names, ssh targets\n * and hostnames), so `length` is a fine width; trailing cells are not padded so\n * the output stays clean for `cut` and friends.\n */\n/**\n * How long since a peer last answered.\n *\n * `never` is deliberately distinct from an age: a peer that has never answered\n * is a setup problem (wrong target, murmur not installed there), while an old\n * age is an ordinary sleeping node. Uses the same fetched_at and threshold the\n * picker does, so the two cannot disagree about one peer.\n */\nexport function lastSeen(fetchedAt: number | null, now: number): string {\n if (fetchedAt === null) return \"never\";\n if (freshness(fetchedAt, now, STALENESS_MS) === \"fresh\") return \"just now\";\n return `${age(now - fetchedAt)} ago`;\n}\n\n/**\n * What to show in the VERSION column, and whether the pairing is a problem.\n *\n * The distinction is drawn from what the code actually enforces rather than from\n * taste:\n *\n * - a differing SNAPSHOT VERSION is a hard incompatibility. `parseSnapshot`\n * rejects any `murmur_snapshot` other than 1, so state genuinely does not\n * flow. That is a fact about behaviour, and it is the only thing marked.\n * - a differing murmur version is worth SHOWING and nothing more. Two nodes on\n * snapshot 1 running 0.1.3 and 0.2.0 interoperate fine; marking that would\n * cry wolf on every patch release and train the operator to ignore the\n * column that is supposed to mean something.\n *\n * A peer we have never heard from is `unknown` and is NOT a mismatch: absence of\n * information is not evidence of incompatibility, and a sleeping peer is the\n * common case here.\n */\nexport function versionCell(\n peer: Pick<PeerRecord, \"murmur_version\" | \"snapshot_version\">,\n ours = SNAPSHOT_VERSION,\n): { text: string; incompatible: boolean } {\n if (peer.murmur_version === null && peer.snapshot_version === null) {\n return { text: \"unknown\", incompatible: false };\n }\n // Answered, but from a build too old to say what it is. Distinct from never\n // having answered: this one is reachable and talking.\n const version = peer.murmur_version ?? \"unreported\";\n const incompatible = peer.snapshot_version !== null && peer.snapshot_version !== ours;\n // The number appears ONLY when it is the problem. In the normal case it is\n // noise on every row; in the abnormal case it is the whole explanation.\n return {\n text: incompatible ? `${version} (snapshot ${peer.snapshot_version} \\u2260 ${ours})` : version,\n incompatible,\n };\n}\n\nexport function formatTable(rows: string[][]): string {\n const widths: number[] = [];\n for (const row of rows) {\n row.forEach((cell, index) => {\n widths[index] = Math.max(widths[index] ?? 0, cell.length);\n });\n }\n return rows\n .map((row) =>\n row\n .map((cell, index) => (index === row.length - 1 ? cell : cell.padEnd(widths[index] ?? 0)))\n .join(\" \")\n .trimEnd(),\n )\n .map((line) => `${line}\\n`)\n .join(\"\");\n}\n\n/**\n * Whether `peer add` must refuse, and what to say. Returns null to proceed.\n *\n * Split out of the commander action because that action opens a store, shells\n * out over ssh and sets process.exitCode, so the rules below were unreachable\n * from a test: the suite ended up asserting a reimplementation of this lookup\n * instead, and disabling the real branch left it green.\n */\nexport function peerAddDecision(input: {\n name: string;\n target: string;\n snapshot: Snapshot | null;\n selfHostId: string | null;\n peers: PeerRecord[];\n}): string | null {\n const { name, target, snapshot, selfHostId, peers } = input;\n // No identity means an unreachable host. It is still added, on the operator's\n // word, and the first successful collect fills in who it is.\n if (!snapshot) return null;\n\n // Adding yourself would list this node's own panes twice and collect over ssh\n // to reach a database you already hold.\n if (snapshot.host_id === selfHostId) {\n return `${target} is this node; not adding it as a peer\\n`;\n }\n\n // One node, one peer. Two names for one host_id means two ssh round-trips per\n // command and the\n // same machine listed twice, so nothing looks wrong until you notice every\n // collect is doing double the work. Excluding `name` itself keeps re-adding the same\n // peer idempotent, which is how a target gets corrected.\n const existing = peers.find(\n (candidate) => candidate.host_id === snapshot.host_id && candidate.name !== name,\n );\n if (existing) {\n return (\n `${target} is already configured as peer \"${existing.name}\" ` +\n `(${snapshot.display_name}); remove it first to rename\\n`\n );\n }\n return null;\n}\n\nexport function registerPeer(program: Command): void {\n const peer = program.command(\"peer\").description(\"Manage peers\");\n\n peer\n .command(\"add\")\n .description(\"Add a peer and discover its identity\")\n // The decision itself is `peerAddDecision` below, so it can be tested\n // without an ssh binary or a commander harness.\n .argument(\"<name>\")\n .argument(\"[target]\")\n .action(async (name: string, target = name) => {\n const store = openStore();\n try {\n // Probe BEFORE writing. Identity is discovered, so the probe is what\n // tells us whether this is a node we already have under another name\n // — and a peer written first would be found by its own duplicate\n // check.\n let snapshot: Snapshot | null = null;\n try {\n // Bare `murmur export`: it takes no options, here or in the collector.\n snapshot = parseSnapshot(await ssh.exec(target, [\"murmur\", \"export\"]));\n } catch {\n snapshot = null;\n }\n\n const refusal = peerAddDecision({\n name,\n target,\n snapshot,\n selfHostId: loadIdentity()?.host_id ?? null,\n peers: store.peers(),\n });\n if (refusal) {\n process.stderr.write(refusal);\n process.exitCode = 1;\n return;\n }\n\n store.addPeer(name, target);\n // The probe already parsed a valid document, so recording it here means\n // `peer list` can name the host, its version and its snapshot version\n // immediately rather than after the first collect.\n if (snapshot) {\n store.replacePeerSnapshot(name, { ok: true, snapshot, at: Date.now() });\n }\n process.stdout.write(\n snapshot\n ? `Added ${name} (${snapshot.display_name})\\n`\n : `Added ${name} (identity pending)\\n`,\n );\n } finally {\n store.close();\n }\n });\n\n peer\n .command(\"remove\")\n .description(\"Remove a peer\")\n .argument(\"<name>\", \"peer to remove\")\n .action((name: string) => {\n const store = openStore();\n try {\n if (store.removePeer(name)) process.stdout.write(`Removed ${name}\\n`);\n else {\n process.stderr.write(`no such peer: ${name}\\n`);\n process.exitCode = 1;\n }\n } finally {\n store.close();\n }\n });\n\n peer\n .command(\"list\")\n .description(\"List peers; --all adds SSH hosts that could become peers\")\n .option(\"--json\", \"print JSON\")\n .option(\"-a, --all\", \"also show SSH hosts that are not peers yet\")\n .action((options: { json?: boolean; all?: boolean }) => {\n const store = openStore();\n try {\n // `list` and `discover` were two halves of one question -- \"what hosts\n // can murmur see, and which of them are up?\" -- and discover needed a\n // PEER column and a LAST SEEN column to be readable at all, at which\n // point it WAS list plus the unadded hosts. Merged into this one, with\n // the unadded hosts behind --all.\n //\n // Peers are the default because that is what the command is called and\n // what it is used for: the everyday question is \"are my peers up?\", not\n // \"what could I add?\", which is a setup-time question asked once.\n //\n // --all is the union of configured targets and ssh hosts, not ssh hosts\n // alone: `peer add` accepts any ssh target, so a peer can be an IP, a\n // user@host, or a Tailscale name that appears in no config file, and\n // listing ssh hosts alone would silently omit it.\n const peers = store.peers();\n const configured = new Map(peers.map((entry) => [entry.target, entry]));\n const discovered = options.all ? sshHosts().filter((host) => !configured.has(host)) : [];\n const now = Date.now();\n\n const rows = [...configured.keys(), ...discovered].map((target) => {\n const entry = configured.get(target);\n return {\n // The handle other commands take: a peer's name, or for a host that\n // is not one yet, the ssh host `peer add` wants.\n name: entry?.name ?? target,\n target,\n peer: entry !== undefined,\n // What the node called itself. Shown, never typed: it can be a\n // container id.\n hostname: entry?.display_name ?? null,\n // Being a peer is not the same as being reachable, and the old\n // output said only the first. A node asleep for twelve hours read\n // exactly like one polled a second ago.\n last_seen: entry === undefined ? null : lastSeen(entry.fetched_at, now),\n // A warm ControlMaster socket makes a collect ~10ms instead of\n // ~170ms, and is the only path that works on a host demanding a\n // hardware-token touch per connection. A speed hint, never a\n // requirement -- which is why the old bare `[x]` / `[ ]` was\n // unreadable: it never said what was being checked.\n //\n // Safe for every row: `ssh -O check` talks to a local socket and\n // never dials, so a host that is down or does not exist answers in\n // ~16ms. Measured.\n ssh: hasWarmSocket(target) ? \"warm\" : \"cold\",\n // What it is running, or undefined when nothing is known -- either\n // because the host is not a peer yet, or because it is a peer that\n // has never answered. Undefined is what drops the column, so the\n // test is \"has anything told us\", not \"is this configured\": a fleet\n // of asleep peers must not buy a column of \"unknown\".\n version:\n entry === undefined ||\n (entry.murmur_version === null && entry.snapshot_version === null)\n ? undefined\n : versionCell(entry),\n // Named where it can be acted on: a peer that answered with a bad\n // document is reachable but broken, which is an operator task and\n // reads nothing like a sleeping laptop.\n error: entry?.last_error ?? null,\n };\n });\n\n if (options.json) {\n process.stdout.write(`${JSON.stringify(rows)}\\n`);\n return;\n }\n if (rows.length === 0) {\n // Point at the flag that answers the obvious next question, but only\n // when it would actually show something.\n process.stdout.write(\n options.all\n ? \"no peers configured, and no hosts in ~/.ssh/config\\n\"\n : \"no peers configured. See what could be added with: murmur peer list --all\\n\",\n );\n return;\n }\n\n // The PEER column only earns its width when the table mixes both kinds.\n // Without --all every row would read \"yes\", which is a column that says\n // nothing.\n const showPeerColumn = rows.some((row) => !row.peer);\n // Same rule as the PEER column, for the same reason: a column every row\n // answers \"unknown\" to is width spent on nothing. With zero successful\n // collects -- the common case, and one the task calls out -- the table\n // stays exactly as narrow as it is today.\n const showVersionColumn = rows.some((row) => row.version !== undefined);\n process.stdout.write(\n formatTable([\n [\n \"NAME\",\n \"TARGET\",\n ...(showPeerColumn ? [\"PEER\"] : []),\n \"HOSTNAME\",\n ...(showVersionColumn ? [\"VERSION\"] : []),\n \"LAST SEEN\",\n \"SSH\",\n ],\n ...rows.map((row) => [\n row.name,\n row.target,\n ...(showPeerColumn ? [row.peer ? \"yes\" : \"-\"] : []),\n row.hostname ?? \"unknown\",\n ...(showVersionColumn ? [row.version?.text ?? \"-\"] : []),\n row.last_seen ?? \"-\",\n row.ssh,\n ]),\n ]),\n );\n\n // Named, not just marked in the row: the table cell says WHAT differs\n // and this says what to do about it. Only for a real snapshot-version\n // mismatch, which is the only case where state genuinely cannot sync.\n const incompatible = rows.filter((row) => row.version?.incompatible);\n if (incompatible.length > 0) {\n process.stdout.write(\n `\\n${incompatible.length} peer${incompatible.length === 1 ? \"\" : \"s\"} speak an incompatible snapshot version; state will not sync until murmur versions match: ${incompatible\n .map((row) => row.name)\n .join(\", \")}\\n`,\n );\n }\n\n // A peer that answered with something wrong. Printed after the table\n // rather than in it, because the message is a sentence and a column of\n // sentences is not a table.\n const broken = rows.filter((row) => row.error);\n for (const row of broken) {\n process.stdout.write(`\\n${row.name}: last attempt failed -- ${row.error}\\n`);\n }\n\n const addable = rows.filter((row) => !row.peer).length;\n if (addable > 0) {\n process.stdout.write(\n `\\n${addable} host${addable === 1 ? \"\" : \"s\"} not yet a peer. Add one with: murmur peer add <name>\\n`,\n );\n }\n } finally {\n store.close();\n }\n });\n}\n","import { spawnSync } from \"node:child_process\";\nimport type { Command } from \"commander\";\nimport {\n agentLabel,\n agentLocation,\n type JumpResult,\n jumpToAgent,\n terminalText,\n} from \"../agents.js\";\nimport { glance } from \"../glance.js\";\nimport { status, statusWithCollect } from \"../status.js\";\nimport { openStore, type Store } from \"../store.js\";\nimport {\n age,\n NEEDS_HUMAN,\n type PaneView,\n RENDER_PRIORITY,\n type RenderState,\n renderState,\n} from \"../view.js\";\nimport { requireIdentity } from \"./identity-guard.js\";\n\ntype PickOptions = { all?: boolean };\n\n/**\n * The two effects `runPick` has on the world: it runs fzf, and it jumps.\n *\n * Injectable because everything interesting about the picker happens BETWEEN\n * those two calls -- which id fzf returns, and which agent that id resolves\n * to -- and with both hard-wired that stretch had no coverage at all. The crew\n * rows revealed by alt-a looked selectable but could not be jumped to for\n * exactly as long as this seam did not exist.\n */\ntype PickDeps = {\n fzf?: (args: string[], input: string, env: NodeJS.ProcessEnv) => string;\n jump?: (store: Store, agent: PaneView) => JumpResult;\n};\n\nconst spawnFzf: NonNullable<PickDeps[\"fzf\"]> = (args, input, env) =>\n spawnSync(\"fzf\", args, {\n input,\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n env,\n }).stdout ?? \"\";\n\nconst PREVIEW_MESSAGE_MAX = 300;\n\n// Same glyphs the tmux status bar and window labels use, so one symbol means\n// one thing in every surface. Ported from the dotfiles' _tmux_common.\nconst GLYPH: Record<string, string> = {\n crashed: \"\\u2717\", // ✗\n blocked: \"!\",\n done: \"\\u2713\", // ✓\n running: \"\\u25b6\", // ▶\n idle: \"\\u00b7\", // ·\n};\n\n// Mirrors the window-glyph colours: red needs you now, peach needs you soon,\n// teal is finished-unseen, grey is busy or idle and carries no signal.\nconst COLOUR: Record<string, string> = {\n crashed: \"\\u001b[31m\",\n blocked: \"\\u001b[33m\",\n done: \"\\u001b[36m\",\n running: \"\\u001b[37m\",\n idle: \"\\u001b[90m\",\n};\n// Built from a char class rather than written literally: a bare \\u001b in a\n// regex trips biome's noControlCharactersInRegex, and the rule is right that\n// an invisible byte in a pattern is a hazard.\nconst ANSI_PATTERN = `${String.fromCharCode(27)}\\\\[[0-9;]*m`;\nconst ANSI_ESCAPE = new RegExp(ANSI_PATTERN, \"g\");\n// Non-global twin for anchored single matches: `exec` on a /g/ regex carries\n// lastIndex between calls, so reusing ANSI_ESCAPE inside a loop silently skips\n// sequences.\nconst ANSI_AT_START = new RegExp(`^${ANSI_PATTERN}`);\nconst ANSI_AT_END = new RegExp(`(?:${ANSI_PATTERN})+$`);\n// Remote rows get a colour of their own: cyan reads as \"elsewhere\" without\n// competing with the state colours, which own red/peach/teal.\nconst REMOTE = \"\\u001b[36m\";\nconst BOLD = \"\\u001b[1m\";\nconst DIM = \"\\u001b[2m\";\nconst RESET = \"\\u001b[0m\";\n\n// The order the prompt COUNTS appear in: RENDER_PRIORITY, imported rather than\n// restated, so this file and status.ts cannot disagree about whether `crashed`\n// or `blocked` leads.\n\n/**\n * Marks the picker as showing orchestrated agents, at the front of the prompt.\n *\n * Doubles as the toggle's state: fzf exposes the prompt to a binding through\n * $FZF_PROMPT and nothing else is mutable, so this is both the label a human\n * reads and the flag the alt-a transform branches on.\n */\nconst CREW_MARK = \"crew \";\n\n/**\n * Whether an agent belongs in the default list.\n *\n * Orchestrated agents are hidden because their supervisor consumes the result:\n * a `done` worker needs no acknowledgement from you, and a `working` one asks\n * for nothing. `--all` shows them.\n *\n * The exceptions are `NEEDS_HUMAN` in view.ts, shared with the status bar's\n * count rule so the two surfaces cannot disagree about which crew rows matter.\n * Hiding those behind a flag meant the rows that needed a human were the ones a\n * human could not see.\n */\nexport function isVisible(agent: PaneView): boolean {\n return agent.driver === \"human\" || NEEDS_HUMAN.some((kind) => agent.attention.includes(kind));\n}\n\n/**\n * Column widths, in one place because the header and the rows must agree. They\n * were duplicated as literals in two functions and had already drifted by a\n * column once.\n */\nconst COLUMNS = {\n glyph: 3, // marker + state glyph\n state: 8,\n name: 30,\n stream: 13,\n streamWide: 18, // when no host column is shown\n host: 14,\n} as const;\n\n/**\n * The column header fzf pins above the list.\n *\n * Built from COLUMNS so it cannot drift from the rows, and dim so it reads as\n * furniture rather than as an agent.\n */\nexport function headerRow(showHost: boolean): string {\n return [\n \" \".repeat(COLUMNS.glyph),\n pad(\"state\", COLUMNS.state),\n pad(\"agent\", COLUMNS.name),\n pad(\"stream\", showHost ? COLUMNS.stream : COLUMNS.streamWide),\n showHost ? pad(\"host\", COLUMNS.host) : \"\",\n \"age / flags\",\n ]\n .filter(Boolean)\n .join(\" \");\n}\n\n/**\n * State filters, as [key, query]. An axis kept separate from the text query, so\n * a filter shows blocked panes rather than searching for the word \"blocked\",\n * which would also match a pane merely *named* that.\n *\n *\n * Alt chords, not ctrl. `ctrl-b` was the filter for `blocked` and it could\n * never work: `C-b` is tmux's DEFAULT prefix, and tmux consumes the prefix\n * before delivering to any pane -- including the display-popup the picker runs\n * in. So the one filter a user reaches for most was dead on a stock tmux, which\n * is the configuration the README tells people to set up.\n *\n * The general problem is that murmur cannot know a user's prefix, so any single\n * ctrl-letter is a gamble. Alt chords are never prefix candidates: tmux's\n * `prefix` option takes a ctrl key by convention and nobody binds M-x at the\n * root table for this purpose. Verified against fzf in a real terminal.\n *\n * Ctrl aliases are kept for the three that do not collide with the default\n * prefix, so existing muscle memory still works. `ctrl-b` is deliberately not\n * among them: binding a key that silently does nothing is worse than not\n * binding it.\n *\n * There is no \"clear the filter\" key here. fzf already clears the query with\n * ctrl-u, a standard readline binding that needs no --bind, so one existed --\n * and binding a second spelling of it cost the word \"all\", which this picker\n * needs for something else. See the alt-a toggle below.\n *\n * Every query is a `RenderState`, because that is the word the row prints. The\n * `working` filter outlived the state it searched for: activity and attention\n * are separate facts now and a busy pane paints `running`, so `alt-w working`\n * narrowed the list to nothing and read exactly like \"nothing is busy\".\n */\nexport const FILTER_KEYS: [key: string, query: RenderState][] = [\n [\"alt-x\", \"crashed\"],\n [\"alt-b\", \"blocked\"],\n [\"alt-d\", \"done\"],\n [\"alt-w\", \"running\"],\n];\n\n/** Ctrl aliases that are safe against tmux's default `C-b` prefix. */\nexport const FILTER_ALIASES: [key: string, query: RenderState][] = [\n [\"ctrl-x\", \"crashed\"],\n [\"ctrl-d\", \"done\"],\n [\"ctrl-w\", \"running\"],\n];\n\nfunction timestamp(ts: number): string {\n return new Date(ts).toLocaleTimeString([], {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n });\n}\n\n/**\n * Human age. Blank under a minute: a row that just changed does not need a\n * column saying so, and \"0s\" on every live agent is noise that hides the one\n * row reading \"3h\".\n */\n/**\n * Fit a cell to exactly `width` visible columns, padding or truncating.\n *\n * Both halves are needed. Padding counts VISIBLE length, because a value\n * wrapped in bold plus reset carries nine escape bytes and `padEnd` counts\n * them, which pads nine short and shears every column to its right.\n *\n * Truncating is what was missing: `pad` only ever grew a string, so one long\n * agent name (\"Gchatui 2026 Rebaseline Finalization\", 36 chars in a 30-wide\n * column) pushed the host and flags columns right and broke the grid for that\n * row only. Long pi session names are the normal case, not an edge one.\n *\n * The truncation walks the string and copies escape sequences through without\n * counting them, so a cut never lands inside one. Cutting mid-sequence would\n * leak the colour into the rest of the line and drop the reset that ends it.\n */\nfunction pad(value: string, width: number): string {\n const visible = [...value.replace(ANSI_ESCAPE, \"\")].length;\n if (visible <= width) return value + \" \".repeat(width - visible);\n\n // Room for the ellipsis, which is one column wide.\n const budget = Math.max(0, width - 1);\n let out = \"\";\n let shown = 0;\n let index = 0;\n while (index < value.length && shown < budget) {\n const sequence = ANSI_AT_START.exec(value.slice(index));\n if (sequence) {\n out += sequence[0];\n index += sequence[0].length;\n continue;\n }\n out += value[index];\n index += 1;\n shown += 1;\n }\n // Copy any trailing escapes (the reset) so the cell closes its own styling.\n const tail = value.slice(index).match(ANSI_AT_END);\n return `${out}\\u2026${tail?.[0] ?? \"\"}${\" \".repeat(Math.max(0, width - budget - 1))}`;\n}\n\n/**\n * Are we running inside a `display-popup` rather than a pane?\n *\n * tmux exports $TMUX to a popup but not $TMUX_PANE, because a popup is not a\n * pane. Outside tmux neither is set, so the three cases stay distinguishable\n * with no tmux call.\n */\nexport function isPopup(env: NodeJS.ProcessEnv): boolean {\n return Boolean(env.TMUX) && !env.TMUX_PANE;\n}\n\n/**\n * One fzf row: a hidden key column, a hidden filter column, then the label.\n *\n * The key is `agent_id`, not a tmux target: a target only means something on\n * the agent's own host, so resolving it is `jumpToAgent`'s job once a selection\n * comes back.\n */\nexport function pickerRow(\n agent: PaneView,\n showHost: boolean,\n current: boolean,\n local = agent.local,\n): string {\n // One derivation, shared with the status bar: attention first, then activity.\n const state = renderState(agent);\n const colour = COLOUR[state] ?? \"\";\n const glyph = GLYPH[state] ?? \"?\";\n const marker = current ? `${BOLD}\\u25c6${RESET}` : \" \"; // ◆ you are here\n // Richest name first: mu names its agents, pi names its sessions, tmux names\n // windows. All three travel in the snapshot, recorded by the node that owns\n // the pane, so this reads the same for a local and a remote agent.\n const name = agent.agent_name ?? agent.pi_session ?? agentLabel(agent);\n // Local and remote must be tellable apart at a glance. Two hostnames in one\n // dim column means you have to know your own machine's name to read the list\n // — and the difference is not cosmetic: a local row is a keystroke away, a\n // remote one costs an ssh and a nested tmux.\n //\n // \"here\" rather than the local hostname, because the reader already knows\n // which machine they are on; what they need is which rows are not it. Remote\n // hosts keep their name and get an arrow, so the column scans as \"here /\n // elsewhere\" before you read any words.\n // Both forms start in the same column: a leading space where the arrow would\n // be, so \"here\" and \"→ bubba\" line up and the arrows form a single vertical\n // run you can scan without reading a word.\n const host = showHost\n ? local\n ? `${DIM} here${RESET}`\n : `${REMOTE}\\u2192 ${terminalText(agent.host)}${RESET}`\n : \"\";\n // Workstream if mu set one, otherwise the tmux session name. Both answer\n // \"which piece of work is this\", and only mu-spawned agents have a\n // workstream, so the column was empty for most human agents.\n //\n // The session name is also what the tms picker shows and what you have\n // trained yourself to search on: a session called `hacking/murmur` holding a\n // pi whose window is named `Python` was unfindable by typing `murmur`. A\n // session without an agent still has no place in this list.\n const group = agent.workstream ?? agent.session_name;\n const workstream = group ? `${DIM}${terminalText(group)}${RESET}` : \"\";\n // Two ages, and the one worth showing is how old the AGENT'S news is, not\n // how recently we reached its host. A peer we polled a second ago can be\n // serving a snapshot from three hours back — which read as fresh until this\n // column existed. `unreachable` is the other axis: the cache itself is old.\n // Both attention and activity, simultaneously. A running agent with `blocked`\n // attention is a real and expected state, and the row has room to say so\n // rather than picking one word and hiding the other.\n const extra = agent.attention.filter((kind) => kind !== state);\n const flags = [\n agent.driver === \"orchestrated\" ? \"crew\" : \"\",\n // Freshness is a property of the NODE, and it is stated explicitly rather\n // than inferred from an age: a stale node keeps its last-known fields, and\n // the reader has to be told those fields are old.\n agent.freshness === \"stale\" ? \"stale host\" : \"\",\n ...extra,\n agent.activity === \"running\" && state !== \"running\" ? \"running\" : \"\",\n age(agent.updated_at === null ? null : Date.now() - agent.updated_at),\n ]\n .filter(Boolean)\n .join(\" \");\n // The state word is IN the label, not a hidden column. fzf's --with-nth\n // re-indexes fields, so any --nth that excluded the label broke plain\n // name matching (typing \"glance\" returned 0/4). Keeping state visible costs\n // eight columns and makes both the ctrl-key filters and text search work on\n // one field set — and the word is worth reading anyway.\n const label = [\n `${marker} ${colour}${glyph}${RESET}`,\n `${colour}${pad(state, COLUMNS.state)}${RESET}`,\n pad(`${BOLD}${terminalText(name)}${RESET}`, COLUMNS.name),\n pad(workstream, showHost ? COLUMNS.stream : COLUMNS.streamWide),\n showHost ? pad(host, COLUMNS.host) : \"\",\n flags ? `${DIM}${flags}${RESET}` : \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n // Keyed on the PANE, not on an agent id. The pane is the address, it is what\n // jumps, and an attention-only pane has no agent id at all -- so keying on one\n // would make exactly the rows that need a human unselectable.\n return `${agent.host_id}\\t${agent.pane}\\t${label}`;\n}\n\nfunction previewText(store: Store, agent: PaneView): string {\n const state = renderState(agent);\n const colour = COLOUR[state] ?? \"\";\n const head = [\n `${colour}${GLYPH[state] ?? \"?\"} ${state}${RESET} ${BOLD}${agent.agent_name ? terminalText(agent.agent_name) : agentLabel(agent)}${RESET}`,\n // Says where, and whether \"where\" is this machine. The glance below is a\n // local capture-pane or an ssh depending on this one fact, so it belongs in\n // the header rather than being inferred from a hostname.\n agent.local\n ? `${DIM}here ${agentLocation(agent)}${RESET}`\n : `${REMOTE}\\u2192 ${terminalText(agent.host)}${RESET} ${DIM}${agentLocation(agent)}${RESET}`,\n ];\n // The three facts, each named, because they are independent and a reader has\n // to be able to see all three at once. `activity` is what the pane's own\n // process said; `attention` is who is wanted; `freshness` is how recently we\n // reached the node that said either.\n const facts = [\n `activity ${agent.activity ?? \"none (attention only)\"}`,\n agent.attention.length ? `wants ${agent.attention.join(\", \")}` : \"\",\n agent.workstream ? `stream ${terminalText(agent.workstream)}` : \"\",\n agent.role ? `role ${terminalText(agent.role)}` : \"\",\n agent.pi_session ? `session ${terminalText(agent.pi_session)}` : \"\",\n agent.cli ? `cli ${terminalText(agent.cli)}` : \"\",\n agent.driver === \"orchestrated\" ? \"driver orchestrated (crew)\" : \"\",\n // Two ages, never one. A node polled a second ago can be serving a\n // three-hour-old fact, and collapsing them is how that read as fresh.\n agent.updated_at === null ? \"\" : `said ${timestamp(agent.updated_at)}`,\n agent.local\n ? \"\"\n : `fetched ${agent.fetched_at === null ? \"never\" : timestamp(agent.fetched_at)}`,\n agent.freshness === \"stale\" ? `${DIM}host is stale: fields below are last-known${RESET}` : \"\",\n ].filter(Boolean);\n\n // The glance is the point of the preview: what is the agent actually doing.\n // There is no history section any more, because there is no history -- the\n // store holds current state only, which is the accepted limitation this\n // rewrite takes in exchange for a model where one writer owns each fact.\n const pane = glance(store, agent);\n const live = pane?.trimEnd()\n ? [\n `${DIM}\\u2500\\u2500 pane \\u2500\\u2500${RESET}`,\n pane.trimEnd().slice(-PREVIEW_MESSAGE_MAX * 20),\n ]\n : [\n `${DIM}\\u2500\\u2500 pane \\u2500\\u2500${RESET}`,\n `${DIM}unavailable (host unreachable, or pane gone)${RESET}`,\n ];\n\n return [...head, \"\", ...facts, \"\", ...live].join(\"\\n\");\n}\n\n/**\n * Emit the preview body for one pane. `murmur pick` re-invokes itself here so\n * fzf's `--preview` has a per-row command, rather than the picker precomputing\n * every preview up front — which would mean an ssh round-trip per remote pane\n * before the list even paints.\n */\nexport function runPreview(store: Store, paneId: string, hostId?: string): void {\n const identity = requireIdentity();\n if (!identity) return;\n // Runs as a child of a picker that has just collected, so it reads the store\n // directly rather than syncing again.\n //\n // Keyed on HOST AND PANE, which is the whole address. A pane id is unique per\n // node and nothing more, so two machines routinely hold a `%1`; fzf hands both\n // columns back for exactly this reason. Matching on the pane alone previewed\n // whichever row the sort happened to put first, and for a local hit that meant\n // a local `capture-pane` standing in for a remote agent.\n const agent = status(store, identity).panes.find(\n (candidate) =>\n candidate.pane === paneId && (hostId === undefined || candidate.host_id === hostId),\n );\n // A miss is worth saying. This process's entire output is the preview, so\n // printing nothing is indistinguishable from a broken preview command -- and\n // the row can genuinely vanish between the collect and the keypress.\n process.stdout.write(\n agent ? `${previewText(store, agent)}\\n` : `${DIM}${paneId} is no longer here.${RESET}\\n`,\n );\n}\n\nexport async function runPick(\n store: Store,\n options: PickOptions = {},\n deps: PickDeps = {},\n): Promise<void> {\n const fzf = deps.fzf ?? spawnFzf;\n const jumpTo = deps.jump ?? jumpToAgent;\n const identity = requireIdentity();\n if (!identity) return;\n const view = await statusWithCollect(store, identity);\n const agents = view.panes.filter((agent) => options.all || isVisible(agent));\n const hidden = view.panes.length - agents.length;\n\n if (agents.length === 0) {\n process.stdout.write(\n hidden ? `No human agents (+${hidden} crew — rerun with --all)\\n` : \"No agents\\n\",\n );\n return;\n }\n\n const showHost = agents.some((agent) => !agent.local);\n const currentPane = process.env.TMUX_PANE ?? \"\";\n const input = agents\n .map((agent) => pickerRow(agent, showHost, agent.pane === currentPane))\n .join(\"\\n\");\n\n const counts = new Map<string, number>();\n for (const agent of agents) {\n const state = renderState(agent);\n counts.set(state, (counts.get(state) ?? 0) + 1);\n }\n const prompt = RENDER_PRIORITY.filter((state) => counts.get(state))\n .map((state) => `${COLOUR[state]}${GLYPH[state]}${counts.get(state)}${RESET}`)\n .join(\" \");\n const basePrompt = `${prompt}${prompt ? \" \" : \"\"}`;\n\n const self = process.argv[1] ?? \"murmur\";\n const allFlag = options.all ? \" --all\" : \"\";\n const inPopup = isPopup(process.env);\n // A preview beside the list needs room for both. Below ~150 columns the\n // 58% split squeezes the host and flags columns off the end, so start\n // stacked and let ctrl-p cycle from there.\n const width = process.stdout.columns ?? 0;\n const previewLayout =\n width > 0 && width < 150 ? \"bottom:60%,border-top,wrap\" : \"right:58%,border-left,wrap\";\n // Keyed on the pane, which is the address, and on the host so the preview can\n // tell a local pane from a remote one with the same pane id.\n const preview = `${process.execPath} ${self} pick --preview {2} --host {1}`;\n // Narrow on the hidden state column with an exact-prefix query, then restore\n // the real query. ctrl-a clears it.\n const filterBinds = [\n ...FILTER_KEYS.map(([key, query]) => [key, query] as const),\n ...FILTER_ALIASES,\n ].flatMap(([key, query]) => [\n \"--bind\",\n query ? `${key}:change-query(${query})` : `${key}:change-query()`,\n ]);\n\n const stdout = fzf(\n [\n \"--delimiter\",\n \"\\t\",\n \"--with-nth\",\n \"3..\",\n \"--ansi\",\n // Literal substring matching, and matching only the visible columns.\n // Default fuzzy scatters query characters across the row: `re` matched\n // \"Fix Murmur Pick Fzf Filter\" as well as \"recovered\". A query here is a\n // word or two of an agent or workstream name, so substring is what the\n // fingers expect. Prefix a token with ' to opt back into fuzzy.\n // Same choice as the tms session picker, for consistency across the two.\n \"--exact\",\n // `begin` ranks earlier match positions higher, so `scratch` puts the\n // scratch workstream above a row that merely mentions it. `index` is the\n // empty-query fallback and preserves the attention order `viewSort`\n // produced, which is the whole point of the list.\n \"--tiebreak\",\n \"begin,index\",\n \"--layout\",\n \"reverse\",\n // `display-popup` draws its own border, so fzf's is a second one a\n // character inside the first. A popup is the normal way to run this, via\n // the prefix+a binding, so the doubled frame was what you saw most.\n //\n // Detected by $TMUX set with $TMUX_PANE unset: tmux exports TMUX to a\n // popup but not TMUX_PANE, since a popup is not a pane. Outside tmux\n // neither is set, so the three cases stay distinguishable.\n \"--border\",\n inPopup ? \"none\" : \"rounded\",\n \"--info\",\n \"inline\",\n \"--prompt\",\n `${options.all ? CREW_MARK : \"\"}${basePrompt}`,\n \"--header\",\n [\n // No `del forget`. There is no replica to evict: a reader holds one\n // snapshot per peer, and the next fetch replaces it whole -- so a delete\n // key could only remove a row the next collect would put straight back,\n // while looking like it had done something.\n `enter jump ^r refresh ^p preview ^u clear`,\n // \"toggle crew\", not \"show crew\": the header is built once and the\n // binding flips per keypress, so a directional label would be wrong\n // half the time. The prompt's `crew` marker says which way it is\n // currently set.\n `filter: ${FILTER_KEYS.map(([key, query]) => `${key.replace(\"alt-\", \"M-\")} ${query}`).join(\n \" \",\n )} M-a toggle crew`,\n headerRow(showHost),\n ]\n .filter(Boolean)\n .join(\"\\n\"),\n \"--preview\",\n preview,\n // Narrow terminals cannot show both the columns and a 58% preview, and\n // the columns are the point of the list. ctrl-p cycles right / bottom /\n // hidden, so every column is reachable on a small viewport without\n // giving up the glance entirely.\n \"--preview-window\",\n previewLayout,\n \"--bind\",\n \"ctrl-p:change-preview-window(bottom:60%,border-top,wrap|hidden|right:58%,border-left,wrap)\",\n \"--bind\",\n `ctrl-r:reload(${process.execPath} ${self} pick --rows${allFlag})`,\n // M-a toggles the POPULATION, which is what \"all\" means everywhere else in\n // murmur: the --all flag, and the \"crew hidden (--all)\" notice.\n //\n // It used to be the \"clear the filter\" key, labelled \"all\", which is the\n // collision that made it look broken: pressing it emptied the query\n // instead of revealing the hidden crew rows named two lines below, and\n // nothing said why. One word, two meanings, and the wrong one bound to\n // the key people reach for. Clearing is fzf's own ctrl-u, which needed no\n // binding at all.\n //\n // `transform` rather than a fixed reload, because a bind string is built\n // once at launch and cannot know it has already fired: binding\n // `--rows --all` meant the second press re-ran the same thing and the\n // toggle only worked one way. transform runs a shell snippet per\n // keypress, so it can branch on the current state.\n //\n // The state lives in the prompt, which is the only mutable string fzf\n // exposes to a binding. CREW_MARK is carried at the front of it: visible\n // as a label, and readable back through $FZF_PROMPT.\n \"--bind\",\n `alt-a:transform:[[ $FZF_PROMPT == \"${CREW_MARK}\"* ]] && echo \"reload(${process.execPath} ${self} pick --rows)+change-prompt(${basePrompt})\" || echo \"reload(${process.execPath} ${self} pick --rows --all)+change-prompt(${CREW_MARK}${basePrompt})\"`,\n ...filterBinds,\n \"--no-select-1\",\n \"--no-exit-0\",\n ],\n input,\n // FZF_DEFAULT_OPTS can carry a conflicting layout or bindings from the\n // user's shell; the old picker stripped it for the same reason.\n Object.fromEntries(\n Object.entries(process.env).filter(([key]) => !key.startsWith(\"FZF_DEFAULT_OPTS\")),\n ),\n );\n\n const [selectedHost, selected] = stdout.trim().split(\"\\t\");\n if (!selected) return;\n // Resolved against the UNFILTERED list, not `agents`. `agents` is what this\n // process printed at launch; alt-a reloads the rows from a SUBPROCESS, so a\n // crew row revealed that way was never in the parent's array. fzf returned\n // its key, find() returned undefined, and enter did nothing — the reveal\n // shipped able to show rows it could not select. Filtering is a presentation\n // concern and must not gate the action; the key fzf hands back is\n // authoritative.\n // The WHOLE address, host and pane. A pane id is unique per node and nothing\n // more, so two machines routinely hold a `%1`; matching on the pane alone\n // jumped to whichever one the sort happened to put first, which turns an ssh\n // into a local window switch.\n const agent = view.panes.find(\n (candidate) => candidate.pane === selected && candidate.host_id === selectedHost,\n );\n // So a miss here means the pane is genuinely gone between the collect and\n // the keypress, and that is worth saying. Same argument as the jump.ok\n // branch below: in a popup, a silent return is indistinguishable from a dead\n // key.\n if (!agent) {\n process.stderr.write(`${selected} is no longer here.\\n`);\n process.exitCode = 1;\n return;\n }\n const jump = jumpTo(store, agent);\n // A popup closes the moment this returns, so a bare failure looked exactly\n // like \"enter did nothing\". Say what happened and fail loudly.\n if (!jump.ok) {\n process.stderr.write(`${jump.message}\\n`);\n process.exitCode = 1;\n }\n}\n\n/** Print the row list only, for fzf's `reload` binding. */\nasync function runRows(store: Store, options: PickOptions = {}): Promise<void> {\n const identity = requireIdentity();\n if (!identity) return;\n const view = await statusWithCollect(store, identity);\n const agents = view.panes.filter((agent) => options.all || isVisible(agent));\n const showHost = agents.some((agent) => !agent.local);\n const currentPane = process.env.TMUX_PANE ?? \"\";\n for (const agent of agents) {\n process.stdout.write(`${pickerRow(agent, showHost, agent.pane === currentPane)}\\n`);\n }\n}\n\nexport function registerPick(program: Command): void {\n program\n .command(\"pick\")\n .description(\"Pick an agent and jump to it\")\n .option(\"--all\", \"include orchestrated agents\")\n .option(\"--preview <pane>\", \"render the preview pane for one pane (internal)\")\n .option(\"--host <host-id>\", \"host of the pane being previewed (internal)\")\n .option(\"--rows\", \"print picker rows only (internal, for reload)\")\n .action(async (options: PickOptions & { preview?: string; host?: string; rows?: boolean }) => {\n const store = openStore();\n try {\n if (options.preview) runPreview(store, options.preview, options.host);\n else if (options.rows) await runRows(store, options);\n else await runPick(store, options);\n } finally {\n store.close();\n }\n });\n}\n","import { spawnSync } from \"node:child_process\";\nimport { SSH_OPTIONS } from \"./channel.js\";\nimport { asPaneId } from \"./ids.js\";\nimport { type Mux, tmux } from \"./mux.js\";\nimport type { Store } from \"./store.js\";\nimport type { PaneView } from \"./view.js\";\n\n/**\n * The most specific human-readable name a pane's agent has, never a tmux id.\n *\n * Four sources, most to least specific: mu's agent name, pi's session name, the\n * tmux window name, the tmux session name. All are recorded by the node that\n * owns the pane, so this reads the same for a local and a remote pane -- a\n * reader cannot resolve a remote window id against its own tmux.\n *\n * Falls back to the window id only when a node recorded no names at all, which\n * means a non-tmux harness.\n */\nexport function agentLabel(agent: PaneView): string {\n const name = agent.agent_name ?? agent.pi_session ?? agent.window_name ?? agent.session_name;\n return terminalText(name ?? agent.window);\n}\n\n/**\n * Where the pane lives, for the second column. Names only -- the ids are what\n * jumps, not what a human reads.\n */\nexport function agentLocation(agent: PaneView): string {\n const session = agent.session_name ?? agent.session;\n const window = agent.window_name ?? agent.window;\n return terminalText(session === window ? session : `${session}:${window}`);\n}\n\nexport function terminalText(value: string): string {\n return [...value]\n .map((character) => {\n const code = character.charCodeAt(0);\n return code < 0x20 || code === 0x7f || (code >= 0x80 && code <= 0x9f) ? \"�\" : character;\n })\n .join(\"\");\n}\n\nexport function shellQuote(value: string): string {\n return `'${value.replaceAll(\"'\", `'\\\\''`)}'`;\n}\n\n/**\n * The local session name that wraps a remote attach.\n *\n * The trailing `~` marks it as murmur's, both for a human reading a session\n * list and for the `#{m:*~,...}` match in the suggested escape-hatch binding.\n *\n * The leading character is the part that matters. A tmux `-t` target starting\n * with `@`, `$` or `%` is parsed as a window, session or pane id, so a session\n * named `@bubba` -- which is exactly what the old per-host WINDOW was called --\n * cannot be addressed at all: every `-t @bubba` fails with `can't find window`.\n * Window names were never targets, so the old name was safe; session names are.\n */\nexport function remoteSessionName(peerName: string): string {\n return `${peerName.replace(/^[@$%=]+/, \"\")}~`;\n}\n\n/**\n * The one process call jump makes that is not a tmux command: the remote probe,\n * and the direct ssh attach when we are not inside tmux. Injectable so the jump\n * decision table can be tested without an ssh binary or a live peer -- without\n * this seam, `jumpToAgent` had no behavioural coverage at all and replacing its\n * body with `return { ok: true }` kept every jump test green.\n */\nexport type Runner = (\n file: string,\n args: string[],\n inherit?: boolean,\n) => { status: number | null; stdout: string; failed: boolean };\n\nconst spawnRunner: Runner = (file, args, inherit = false) => {\n const result = spawnSync(file, args, {\n encoding: \"utf8\",\n timeout: 10_000,\n ...(inherit ? { stdio: \"inherit\" as const } : {}),\n });\n return {\n status: result.status,\n stdout: result.stdout ?? \"\",\n // spawnSync reports a failure to even start the child in `error`, leaving\n // status null. Collapsing both here keeps the decision table below reading\n // as one question rather than two.\n failed: result.error !== undefined,\n };\n};\n\nexport type JumpResult =\n | { ok: true }\n | {\n ok: false;\n // Local to this process: the picker prints `message` and nothing else, and\n // no reason code here is ever stored or published in a snapshot.\n reason: \"no_peer\" | \"unreachable\" | \"no_tmux\" | \"pane_gone\" | \"attach_failed\";\n message: string;\n };\n\n/**\n * Jump to a pane, wherever it lives.\n *\n * NEVER MUTATES STATE ON FAILURE. A failure is a report: a reason and a message,\n * nothing written. The next collect reconciles either way, and only the owning\n * node can author facts about its own panes.\n */\nexport function jumpToAgent(\n store: Store,\n agent: PaneView,\n mux: Mux = tmux,\n run: Runner = spawnRunner,\n): JumpResult {\n if (agent.local) {\n // The PANE decides, and only the pane. This once asked whether the agent's\n // WINDOW still existed, which a live pane routinely outlives: after\n // `move-pane -s %0 -t @1`, list-panes still has %0 and list-windows no\n // longer has @0. Asking the wrong one reported healthy agents as gone.\n const panes = mux.livePanes();\n if (panes && !panes.has(agent.pane)) {\n return {\n ok: false,\n reason: \"pane_gone\",\n message: `${agentLabel(agent)} is gone -- its pane no longer exists.`,\n };\n }\n // Reporting the attach rather than assuming it. A select-window that fails\n // is the local twin of the remote symptom: the picker closes, nothing\n // moves, and nothing says why.\n if (!mux.attach(agent.session, agent.window)) {\n return {\n ok: false,\n reason: \"attach_failed\",\n message: `could not attach to ${agentLabel(agent)} (tmux select-window failed).`,\n };\n }\n return { ok: true };\n }\n const peer = store.peers().find((candidate) => candidate.host_id === agent.host_id);\n const target = peer?.target ?? peer?.name;\n if (!target) {\n return {\n ok: false,\n reason: \"no_peer\",\n message: `No peer configured for host ${agent.host_id.slice(0, 8)}. Try: murmur peer add <target>`,\n };\n }\n\n // Check the pane is still there before opening a window to attach to it.\n // Panes, not windows: a recorded window id goes stale every time the pane\n // moves, so it cannot answer whether the agent exists. Without this the attach fails inside a new tmux window that closes\n // instantly, which is indistinguishable from \"enter did nothing\" -- the\n // symptom that sent us looking for a quoting bug that did not exist.\n // ssh does not take an argv: it joins its arguments and hands the string to a\n // shell on the far side. An unquoted `#{window_id}` is mangled by that shell\n // and tmux answers `-F expects an argument`, which looked exactly like an\n // unreachable host. One quoted string, so the remote shell passes the format\n // through untouched.\n //\n // Shares the collector's SSH_OPTIONS rather than passing BatchMode alone.\n // Without ControlPath the probe could not use the warm master socket the\n // collector rides, and without ConnectTimeout it inherited the kernel's dial\n // -- 75s on macOS, bounded only by the timeout below, so a sleeping laptop\n // froze the picker for ten seconds before admitting it was unreachable.\n const probe = run(\"ssh\", [\n ...SSH_OPTIONS,\n target,\n `tmux list-panes -a -F ${shellQuote(\"#{pane_id}\")}`,\n ]);\n if (probe.status !== 0) {\n // 255 is ssh's own failure code; anything else came from the remote\n // command. Conflating them was wrong in the common case: with a warm\n // ControlMaster socket the host answers instantly and it is tmux that is\n // gone, so \"unreachable\" sent you looking at the network for a problem that\n // was not there.\n const sshFailed = probe.status === 255 || probe.failed;\n if (sshFailed) {\n // No mark: we learned nothing about the peer's tmux, only that we could\n // not ask. Its agents may be perfectly alive behind a cold socket or a\n // sleeping laptop, and deleting them here would be guessing.\n return {\n ok: false,\n reason: \"unreachable\",\n message: `cannot reach ${target} over ssh. Nothing here ever prompts for auth, so check the host is awake and reachable, or connect once by hand to see the real error.`,\n };\n }\n\n // ssh worked, tmux did not. A real fact about the host, and reported as\n // one: nothing is deleted here. The peer's own next snapshot is what\n // removes its panes, because only that node may author about them, and a\n // reader that evicts rows on a probe failure is guessing.\n return {\n ok: false,\n reason: \"no_tmux\",\n message: `${target} has no tmux server running, so its agents are gone. They will disappear on the next collect.`,\n };\n }\n const remotePanes = new Set(probe.stdout.split(\"\\n\").filter(Boolean).map(asPaneId));\n if (!remotePanes.has(agent.pane)) {\n return {\n ok: false,\n reason: \"pane_gone\",\n message: `${agentLabel(agent)} is gone -- ${target} no longer has that pane.`,\n };\n }\n\n const attachTarget = shellQuote(`${agent.session}:${agent.window}`);\n\n // Hand the ssh to tmux as its own detached SESSION rather than running it\n // here. `murmur pick` is usually a display-popup, and a popup is modal: an\n // ssh started inside it is killed the moment the picker exits, so the remote\n // pane flashed and vanished. A session outlives the popup and gives the\n // remote tmux a real terminal to attach to.\n //\n // A session, not a window, because session options are per-session and that\n // is what makes the nesting stop being felt:\n //\n // status off -- no local status bar, so the remote's own bar is the only\n // one on screen and the jump reads as a full-screen ssh.\n // prefix None -- no local prefix at all, so ^b reaches the remote\n // directly. No ^b b, and no second prefix to learn.\n //\n // Both would be global if this were a window, and would break every local\n // session. The cost is that the local server is unreachable from inside the\n // wrapper; the README documents a root-table key that detaches out.\n if (process.env.TMUX) {\n // Read BEFORE the wrapper exists, or we would record the wrapper itself as\n // the place to come back to and the return would be a no-op.\n const client = mux.clientName();\n const origin = mux.currentTarget();\n\n // Named after the peer as configured, matching the picker's host column.\n // The machine's self-reported display_name can be a container id, which\n // makes the session unrecognisable in a session list.\n const name = remoteSessionName(peer?.name ?? target);\n\n // Reuse an existing wrapper for this host rather than stacking a new one on\n // every jump. Jumping to bubba three times used to leave three identical\n // windows behind. Matched on name, the only handle available: the ssh is\n // opaque from here and the remote session id is not a local address.\n if (mux.sessionNamed(name)) {\n return mux.switchClient(client, name)\n ? { ok: true }\n : {\n ok: false,\n reason: \"attach_failed\",\n message: `could not switch to the existing ${name} session.`,\n };\n }\n\n // `tmux new-session <command>` runs the command through a shell, so the\n // string is expanded LOCALLY before ssh sees it. A tmux session id is\n // always `$N`, so `$0:@6` arrived as `:@6` and the remote attach failed\n // with \"can't find session\". shellQuote alone is not enough: it protects\n // the remote shell, this protects the local one.\n const attach = `ssh -t ${shellQuote(target)} tmux attach -t ${shellQuote(attachTarget)}`;\n\n // The return home, as part of the wrapper's own command. When the attach\n // exits -- inner detach, remote session killed, ssh dropped -- this runs,\n // then the wrapper has no command left and tmux destroys it.\n //\n // Explicit, rather than relying on detach-on-destroy: `previous` picks\n // tmux's idea of the previous session, which in testing was a stray\n // unrelated session rather than the one the jump started from. It is still\n // set below as a fallback for when this command cannot run (SIGKILL).\n const restore = origin\n ? `; tmux switch-client ${client ? `-c ${shellQuote(client)} ` : \"\"}-t ${shellQuote(`=${origin}`)}`\n : \"\";\n\n if (!mux.newSession(name, `${attach}${restore}`)) {\n return {\n ok: false,\n reason: \"attach_failed\",\n message: `could not open a session to attach to ${target}.`,\n };\n }\n\n mux.setSessionOption(name, \"status\", \"off\");\n mux.setSessionOption(name, \"prefix\", \"None\");\n mux.setSessionOption(name, \"detach-on-destroy\", \"previous\");\n\n return mux.switchClient(client, name)\n ? { ok: true }\n : {\n ok: false,\n reason: \"attach_failed\",\n message: `attached to ${target} in session ${name}, but could not switch to it.`,\n };\n }\n\n // Outside tmux there is no popup to escape, so run it directly. stdio is\n // inherited, so this blocks until the user leaves the remote session; a\n // nonzero exit means the attach itself failed.\n //\n // None of the wrapper-session machinery above applies here, and it must not:\n // there is no local client to switch, nothing to return to but the shell that\n // invoked us, and no local status bar or prefix to suppress. This path is\n // already full-screen and already prefix-clean -- the whole problem is an\n // artifact of being inside tmux. Creating a local session here would attach a\n // client to a server the user never asked for, and leave them inside tmux on\n // exit rather than back at their prompt.\n //\n // The tradeoff is no reuse of an existing attach, since there is no local\n // server holding one. That is correct rather than missing.\n const attach = run(\"ssh\", [\"-t\", target, \"tmux\", \"attach\", \"-t\", attachTarget], true);\n return attach.status === 0 && !attach.failed\n ? { ok: true }\n : {\n ok: false,\n reason: \"attach_failed\",\n message: `ssh attach to ${target} failed.`,\n };\n}\n","import { execFileSync } from \"node:child_process\";\nimport { SSH_OPTIONS } from \"./channel.js\";\nimport { tmux } from \"./mux.js\";\nimport type { Store } from \"./store.js\";\nimport type { PaneView } from \"./view.js\";\n\n/**\n * Glance: the last few lines a pane printed.\n *\n * This is the cheap half of the two things \"render any pane from the master\"\n * hides. It is a stateless `capture-pane`, not a frame stream — no resize\n * negotiation, no input routing, no reconnect. That deferral is what keeps\n * murmur a state layer instead of a multiplexer (DESIGN-NOTES, \"Deferring\n * interactive remote rendering\"), and it is why this file is thirty lines\n * rather than most of herdr.\n */\n\nconst GLANCE_LINES = 40;\n\nexport function glance(store: Store, agent: PaneView, lines = GLANCE_LINES): string | null {\n if (agent.local) return tmux.capture(agent.pane, lines);\n\n const peer = store.peers().find((candidate) => candidate.host_id === agent.host_id);\n const target = peer?.target ?? peer?.name;\n if (!target) return null;\n try {\n // The pane id is `%N`, which a remote shell leaves alone, but quote it\n // anyway: the same class of bug as the `$N` session id that made remote\n // jump fail silently for a day.\n return execFileSync(\n \"ssh\",\n [\n ...SSH_OPTIONS,\n target,\n \"tmux\",\n \"capture-pane\",\n \"-p\",\n \"-t\",\n `'${agent.pane}'`,\n \"-S\",\n `-${lines}`,\n ],\n { encoding: \"utf8\", timeout: 3000, stdio: [\"ignore\", \"pipe\", \"ignore\"] },\n );\n } catch {\n // Unreachable, cold socket, dead tmux, gone pane. The preview says so\n // rather than the picker failing.\n return null;\n }\n}\n","import { type Channel, ssh } from \"./channel.js\";\nimport { collect } from \"./collector.js\";\nimport type { NodeIdentity } from \"./identity.js\";\nimport type { Store } from \"./store.js\";\nimport {\n freshness,\n NEEDS_HUMAN,\n type PaneView,\n paneViews,\n RENDER_PRIORITY,\n type RenderState,\n renderState,\n viewSort,\n} from \"./view.js\";\n\ntype Counts = Record<RenderState, number>;\n\nexport type Status = {\n counts: Counts;\n orchestrated_counts: Counts;\n panes: PaneView[];\n peers: {\n name: string;\n display_name: string | null;\n fetched_at: number | null;\n snapshot_at: number | null;\n last_error: string | null;\n stale: boolean;\n }[];\n};\n\nfunction emptyCounts(): Counts {\n const counts = {} as Counts;\n for (const state of RENDER_PRIORITY) counts[state] = 0;\n return counts;\n}\n\nexport function tmuxStatus(view: Status): string {\n // Orchestrated agents are counted for the states only a human can answer, and\n // hidden for the rest: a supervisor consumes a `done` worker's result, so\n // nobody needs to acknowledge it, and `running` asks for nothing. The list is\n // `NEEDS_HUMAN` in view.ts, shared with the picker's visibility rule so the\n // status bar and the list cannot disagree about which crew rows matter.\n const needsHuman = new Set<string>(NEEDS_HUMAN);\n const total = (state: RenderState): number =>\n view.counts[state] + (needsHuman.has(state) ? view.orchestrated_counts[state] : 0);\n return (\n RENDER_PRIORITY.filter((state) => total(state) > 0)\n // The tmux renderer's public vocabulary predates the internal activity\n // rename. Keep that external protocol stable until the renderer is updated.\n .map((state) => `${state === \"running\" ? \"working\" : state}\\t${total(state)}\\n`)\n .join(\"\")\n );\n}\n\n/**\n * The current view. Pure with respect to the network: the caller decides whether\n * to collect first (see `statusWithCollect`).\n *\n * `identity` is required rather than resolved here, because every caller is a\n * command that already fails without one.\n */\nexport function status(store: Store, identity: NodeIdentity, now = Date.now()): Status {\n const counts = emptyCounts();\n const orchestratedCounts = emptyCounts();\n const panes = viewSort(paneViews(store, identity, now));\n for (const pane of panes) {\n const target = pane.driver === \"human\" ? counts : orchestratedCounts;\n target[renderState(pane)] += 1;\n }\n\n return {\n counts,\n orchestrated_counts: orchestratedCounts,\n panes,\n peers: store.peers().map((peer) => ({\n name: peer.name,\n display_name: peer.display_name,\n fetched_at: peer.fetched_at,\n // Their clock and ours, separately: a peer polled a second ago can be\n // serving a three-hour-old fact, and one number cannot say both.\n snapshot_at: peer.snapshot_at,\n last_error: peer.last_error,\n // The view's verdict, not a second threshold spelled the same way. A\n // peer we have never reached is stale rather than fresh -- null\n // `fetched_at` means the first collect has not succeeded yet -- and\n // `freshness` is the one place that decides, so this list and the panes\n // the peer contributes cannot disagree about the same host.\n stale: freshness(peer.fetched_at, now) === \"stale\",\n })),\n };\n}\n\n/**\n * Collect from peers, then read. This is what every user-facing surface wants:\n * the view reflects the sync that just ran, rather than the one before it.\n *\n * Awaiting matters for two reasons. A fire-and-forget collect makes every\n * invocation show data one run stale. And the callers close the store in a\n * `finally`, so a collect still in flight lands on a closed handle and reports\n * \"The database connection is not open\", which looks like corruption rather\n * than a race.\n *\n * Sync must never fail a command, and on this path it must never print either:\n * `status` runs on every status-bar tick and `pick` runs inside a\n * display-popup, so one sleeping laptop would otherwise write ssh diagnostics\n * to stderr several times a minute, forever. `murmur collect`, which a human\n * runs deliberately, is the only place that prints.\n */\nexport async function statusWithCollect(\n store: Store,\n identity: NodeIdentity,\n now = Date.now(),\n channel: Channel = ssh,\n): Promise<Status> {\n try {\n await collect(store, channel, now);\n } catch {\n // Total by construction: a read of whatever the cache already holds is\n // always better than no output, and this path has no one to tell.\n }\n return status(store, identity, now);\n}\n","import type { Command } from \"commander\";\nimport { statusWithCollect, tmuxStatus } from \"../status.js\";\nimport { openStore } from \"../store.js\";\nimport { requireIdentity } from \"./identity-guard.js\";\n\nexport function registerStatus(program: Command): void {\n program\n .command(\"status\")\n .description(\"Show current agent status\")\n .option(\"--json\", \"print JSON\")\n .action(async (options: { json?: boolean }) => {\n const identity = requireIdentity();\n if (!identity) return;\n const store = openStore();\n try {\n const view = await statusWithCollect(store, identity);\n process.stdout.write(\n options.json ? `${JSON.stringify(view, null, 2)}\\n` : tmuxStatus(view),\n );\n } finally {\n store.close();\n }\n });\n}\n"],"mappings":"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","names":["require","agentId","program","execFileSync","randomUUID","mkdirSync","join","join","mkdirSync","randomUUID","program","program","program","mkdirSync","readFileSync","writeFileSync","homedir","dirname","join","program","join","homedir","mkdirSync","dirname","readFileSync","writeFileSync","program","readFileSync","homedir","join","readFileSync","join","homedir","program","spawnSync","attach","execFileSync","execFileSync","spawnSync","timestamp","agent","program","program"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/ids.ts","../src/mux.ts","../src/store.ts","../src/paths.ts","../src/version.ts","../src/types.ts","../src/view.ts","../src/cli/clear.ts","../src/channel.ts","../src/snapshot.ts","../src/collector.ts","../src/identity.ts","../src/cli/identity-guard.ts","../src/cli/collect.ts","../src/cli/export.ts","../src/cli/init.ts","../src/cli/link.ts","../src/cli/notify.ts","../src/cli/peer.ts","../src/cli/pick.ts","../src/agents.ts","../src/glance.ts","../src/status.ts","../src/cli/status.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { registerClear } from \"./cli/clear.js\";\nimport { registerCollect } from \"./cli/collect.js\";\nimport { registerExport } from \"./cli/export.js\";\nimport { registerInit } from \"./cli/init.js\";\nimport { registerLink } from \"./cli/link.js\";\nimport { registerNotify } from \"./cli/notify.js\";\nimport { registerPeer } from \"./cli/peer.js\";\nimport { registerPick } from \"./cli/pick.js\";\nimport { registerStatus } from \"./cli/status.js\";\nimport { VERSION } from \"./index.js\";\n\nconst program = new Command();\nprogram\n .name(\"murmur\")\n .description(\"Agent state across every machine, in one view.\")\n .version(VERSION);\nregisterInit(program);\nregisterLink(program);\nregisterExport(program);\nregisterCollect(program);\nregisterClear(program);\nregisterNotify(program);\nregisterPeer(program);\nregisterStatus(program);\nregisterPick(program);\nprogram.parse();\n","/**\n * tmux's three id kinds, kept apart by the type system.\n *\n * tmux itself is unambiguous about this and prints a sigil on every id --\n * `session=$25 window=@75 pane=%89` -- but they are all strings, so murmur\n * could and did pass one where another was meant. Twice, in shipped code: a\n * sweep keyed on window liveness deleted ten live agents, and a window cached\n * at extension startup badged the window a moved pane had left.\n *\n * An agent is addressed by its PANE, which keeps its id across `move-pane`,\n * `break-pane`, and a window closed and reopened. A session and a window are\n * only where that pane currently lives, and both may differ between two reports\n * from one agent. So the rule the brands enforce is:\n *\n * only a pane may decide whether an agent exists.\n *\n * Branding is a compile-time fiction: at runtime these are the same strings\n * tmux printed, which is what keeps the snapshot document and every stored row\n * byte-identical.\n */\n\ndeclare const brand: unique symbol;\n\n/** A tmux session id, `$N`. Mutable location. */\nexport type SessionId = string & { readonly [brand]: \"session\" };\n\n/** A tmux window id, `@N`. Mutable location -- never an agent's identity. */\nexport type WindowId = string & { readonly [brand]: \"window\" };\n\n/** A tmux pane id, `%N`. The agent's identity, stable for its whole life. */\nexport type PaneId = string & { readonly [brand]: \"pane\" };\n\n/*\n * The boundary. Every raw string that becomes an id passes through one of these\n * three, so the unsafe step is in one file and countable rather than scattered\n * as `as` at each call site.\n *\n * Deliberately not validating the sigil. These are called on tmux stdout, on\n * JSON off the wire, on sqlite rows and on argv, and a node that recorded an id\n * murmur does not recognise -- a future tmux, a different harness -- must still\n * round-trip it. Rejecting here would turn a naming change into a behaviour\n * change.\n */\n\nexport function asSessionId(raw: string): SessionId {\n return raw as SessionId;\n}\n\nexport function asWindowId(raw: string): WindowId {\n return raw as WindowId;\n}\n\nexport function asPaneId(raw: string): PaneId {\n return raw as PaneId;\n}\n","import { execFileSync } from \"node:child_process\";\nimport {\n asPaneId,\n asSessionId,\n asWindowId,\n type PaneId,\n type SessionId,\n type WindowId,\n} from \"./ids.js\";\nimport type { Location } from \"./types.js\";\nimport type { RenderState } from \"./view.js\";\n\nexport interface Mux {\n currentWindow(): Location | null;\n livePanes(): Set<PaneId> | null;\n // Sets `@agent_state` on a WINDOW, even though the attention it expresses\n // belongs to a pane. The asymmetry is tmux's: the status bar and the `tms`\n // picker read a window option, and there is no per-pane equivalent they\n // would read instead. Its consequence is that a pane moving between windows\n // must clear the badge it left behind, since nothing else knows it moved.\n setWindowBadge(window: WindowId, state: RenderState | null): void;\n // Reports whether the attach actually happened. runTmux swallows failures to\n // return null, and a jump that silently failed looked exactly like \"enter did\n // nothing\" -- the symptom the remote probe was added to prevent, reproduced\n // on the local path.\n attach(session: SessionId, window: WindowId): boolean;\n windowForPane(pane: PaneId): WindowId | null;\n panesInWindow(window: WindowId): PaneId[];\n capture(pane: PaneId, lines?: number): string | null;\n // --- remote-jump session seam -------------------------------------------\n // A remote attach lives in its own local session rather than a window, so it\n // can be full-screen (no local status bar) and prefix-free (no nested ^b).\n // See jumpToAgent for why that is worth five extra methods.\n clientName(): string | null;\n currentTarget(): string | null;\n sessionNamed(name: string): boolean;\n newSession(name: string, command: string): boolean;\n setSessionOption(session: string, option: string, value: string): void;\n switchClient(client: string | null, session: string): boolean;\n}\n\nfunction runTmux(args: string[]): string | null {\n try {\n return execFileSync(\"tmux\", args, {\n encoding: \"utf8\",\n timeout: 3000,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n } catch {\n return null;\n }\n}\n\n/**\n * A session name as an exact target, in the two spellings tmux needs.\n *\n * Bare names match by PREFIX, so a wrapper for host `bub` silently retargets a\n * session called `bubba` once one exists -- verified, and it sets options on\n * the wrong session rather than failing. A leading `=` demands an exact match.\n * (`name=` is not the syntax; it reads as part of the name and matches nothing.)\n *\n * The trailing colon is the part that is easy to get wrong. `switch-client -t`\n * takes a target-SESSION, where `=name` is right, but `set-option -t` and\n * `show-options -t` take a target-PANE, where `=name` fails outright with `no\n * such session` and the exact form is `=name:` -- the empty window/pane part\n * resolving to the session's current pane.\n *\n * Neither rescues a name starting with `@`, `$` or `%`: those introduce tmux's\n * window, session and pane id syntax. remoteSessionName keeps them out.\n *\n * Both take a session NAME -- not a SessionId, which is why neither is branded.\n * `exactPaneTarget` is named for what it RETURNS, a tmux target-pane, because\n * what it takes and what it produces are different things and the old name\n * `exactPane` read as though it took a pane.\n */\n/**\n * The window name worth RECORDING, given tmux's own answer and whether tmux is\n * renaming that window itself.\n *\n * Null while `automatic-rename` is on -- which is tmux's DEFAULT -- because the\n * name is then just the foreground process. The picker's `agent` column showed\n * `Python`, `node` and `zsh` for real agents: pi's own interpreter, labelled\n * \"agent\".\n *\n * A name nobody chose is not a name, and recording it as one is worse than\n * recording nothing: `agentLabel` prefers the window over the session, so a\n * process name shadowed `hacking/murmur` -- the string the reader actually\n * searches on. Dropped at the point of RECORDING rather than at render, so\n * every surface, and every peer reading this node's snapshot, agrees on what\n * counts as a name.\n *\n * Split out of `currentWindow` to be testable: that method shells out to a real\n * tmux server, so the decision had no reachable seam and the format string was\n * the only thing a test could have asserted on.\n */\nexport function chosenWindowName(\n name: string | undefined,\n autoRename: string | undefined,\n): string | null {\n if (autoRename === \"1\") return null;\n return name || null;\n}\n\nexport function exactSession(session: string): string {\n return `=${session}`;\n}\n\nexport function exactPaneTarget(session: string): string {\n return `=${session}:`;\n}\n\nexport function tmuxBadgeState(state: RenderState): string {\n // @agent_state is consumed by existing tmux configuration, whose public\n // vocabulary calls active work \"working\". Keep the internal activity named\n // \"running\" without forcing a coordinated config rollout.\n return state === \"running\" ? \"working\" : state;\n}\n\nexport const tmux: Mux = {\n currentWindow() {\n // $TMUX_PANE is the only trustworthy signal that we are inside a pane, and\n // it is set by tmux for every process in one.\n //\n // Asking tmux instead does not work: `display-message` answers from any\n // process on a machine with a running server, and reports whichever pane\n // that server considers active. A pi started outside tmux -- a bare ssh\n // login, a plain terminal, cron -- would then record itself as living in\n // some unrelated agent's pane and overwrite that agent's state. Falling\n // back to `display-message` here was exactly that bug.\n const raw = process.env.TMUX_PANE;\n if (!raw) return null;\n const pane = asPaneId(raw);\n\n // One call for ids and names together. The names travel with every row a\n // snapshot carries, because a reader cannot resolve a remote session or\n // window id against its own tmux.\n const fields = runTmux([\n \"display-message\",\n \"-t\",\n pane,\n \"-p\",\n \"#{session_id}\\t#{window_id}\\t#{session_name}\\t#{window_name}\\t#{?automatic-rename,1,0}\",\n ]);\n const [session, window, sessionName, windowName, autoRename] = fields?.split(\"\\t\") ?? [];\n if (!session || !window) return null;\n return {\n session: asSessionId(session),\n window: asWindowId(window),\n pane,\n session_name: sessionName || null,\n window_name: chosenWindowName(windowName, autoRename),\n };\n },\n\n // Which of this host's PANES still exist. The only liveness question tmux is\n // ever asked, and the one that matches how an agent is addressed: a pane keeps\n // its id when it moves between windows, so a recorded window id can be gone\n // while the agent is very much alive.\n //\n // null means tmux could not answer; an empty set means it did and there are\n // none. Conflating the two would delete every agent on the host the moment\n // tmux was briefly unreachable.\n livePanes() {\n const out = runTmux([\"list-panes\", \"-a\", \"-F\", \"#{pane_id}\"]);\n if (out === null) return null;\n return new Set(out.split(\"\\n\").filter(Boolean).map(asPaneId));\n },\n\n setWindowBadge(window, state) {\n if (state === null) {\n runTmux([\"set-window-option\", \"-qu\", \"-t\", window, \"@agent_state\"]);\n } else {\n runTmux([\"set-window-option\", \"-q\", \"-t\", window, \"@agent_state\", tmuxBadgeState(state)]);\n runTmux([\"set-window-option\", \"-q\", \"-t\", window, \"@pane_agent\", \"1\"]);\n }\n runTmux([\"refresh-client\", \"-S\"]);\n },\n\n attach(session, window) {\n // Two steps, because switch-client alone is a no-op when the target window\n // is in the session you are already attached to — which is the common case\n // for a local agent, and why \"enter\" appeared to do nothing.\n // switch-client moves the client between sessions; select-window moves\n // that session to the right window.\n //\n // Only select-window decides the result. switch-client legitimately fails\n // when there is no client to switch (running outside tmux), and treating\n // that as a failed jump would report an error for a working attach.\n runTmux([\"switch-client\", \"-t\", session]);\n return runTmux([\"select-window\", \"-t\", window]) !== null;\n },\n\n // Sibling panes, for deciding whether an unowned pane may clear the window's\n // badge. A window holding an agent and a shell must not lose the badge when\n // you focus the shell.\n panesInWindow(window) {\n const out = runTmux([\"list-panes\", \"-t\", window, \"-F\", \"#{pane_id}\"]);\n return out?.split(\"\\n\").filter(Boolean).map(asPaneId) ?? [];\n },\n\n // Which client to send home when the remote attach exits. `switch-client`\n // with no -c moves whichever client tmux considers current, and `murmur pick`\n // usually runs in a popup -- a client of its own, which dies with the popup.\n // Naming the real client is what lets the return outlive the picker.\n clientName() {\n return runTmux([\"display-message\", \"-p\", \"#{client_name}\"]) || null;\n },\n\n // Where the jump started, as a switch-client target. Window-level, not just\n // the session: coming back to the right session but the wrong window is\n // still the wrong place. The window id is stable where its index is not,\n // since renumber-windows renumbers on every close.\n currentTarget() {\n return runTmux([\"display-message\", \"-p\", \"#{session_name}:#{window_id}\"]) || null;\n },\n\n // Whether a wrapper session for this host already exists. Deliberately not\n // returning an id: a session is addressed by name, so a `#{session_id}` would\n // only have to be turned back into one.\n sessionNamed(name) {\n const out = runTmux([\"list-sessions\", \"-F\", \"#{session_name}\"]);\n if (out === null) return false;\n return out.split(\"\\n\").includes(name);\n },\n\n newSession(name, command) {\n // Detached, because the caller sets the per-session options before showing\n // it. Creating it attached would paint one frame with the local status bar\n // up and the local prefix live, which is the flicker this design exists to\n // remove.\n return runTmux([\"new-session\", \"-d\", \"-s\", name, command]) !== null;\n },\n\n setSessionOption(session, option, value) {\n runTmux([\"set-option\", \"-t\", exactPaneTarget(session), option, value]);\n },\n\n switchClient(client, session) {\n const target = exactSession(session);\n const args = client\n ? [\"switch-client\", \"-c\", client, \"-t\", target]\n : [\"switch-client\", \"-t\", target];\n return runTmux(args) !== null;\n },\n\n // The window a pane belongs to, for a pane murmur holds no row for. Clearing\n // a badge is a tmux operation and does not require murmur to own the pane.\n windowForPane(pane) {\n const out = runTmux([\"display-message\", \"-t\", pane, \"-p\", \"#{window_id}\"]);\n return out ? asWindowId(out) : null;\n },\n\n capture(pane, lines) {\n const args = [\"capture-pane\", \"-p\", \"-t\", pane];\n if (lines !== undefined) args.push(\"-S\", `-${lines}`);\n return runTmux(args);\n },\n};\n\nexport function pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code !== \"ESRCH\";\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport { mkdirSync, rmSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport Database from \"better-sqlite3\";\nimport type { NodeIdentity } from \"./identity.js\";\nimport type { PaneId } from \"./ids.js\";\nimport { asPaneId, asSessionId, asWindowId } from \"./ids.js\";\nimport { pidAlive } from \"./mux.js\";\nimport { dbPath } from \"./paths.js\";\nimport type {\n ActivityUpdate,\n AgentClaim,\n AgentRelease,\n AttentionKind,\n AttentionRequest,\n ClaimResult,\n LocalWorld,\n PeerFetch,\n PeerRecord,\n ReconcileSummary,\n Snapshot,\n SnapshotAgent,\n SnapshotAttention,\n SnapshotPane,\n} from \"./types.js\";\nimport { MURMUR_VERSION } from \"./version.js\";\nimport { RENDER_PRIORITY } from \"./view.js\";\n\n/**\n * The storage version. Any change to any table bumps it.\n *\n * ONE version strategy: a mismatch salvages the peer names and targets a human\n * typed, deletes the file, and recreates the schema. No ALTER TABLE anywhere, so\n * there is no additive path to forget to use.\n */\nconst SCHEMA_USER_VERSION = 3;\n\nconst SCHEMA = `\n CREATE TABLE agents (\n agent_id TEXT NOT NULL PRIMARY KEY,\n pane TEXT NOT NULL UNIQUE,\n owner_pid INTEGER NOT NULL CHECK (owner_pid > 0),\n activity TEXT NOT NULL CHECK (activity IN ('running', 'stopped')),\n session TEXT NOT NULL,\n window TEXT NOT NULL,\n session_name TEXT,\n window_name TEXT,\n agent_name TEXT,\n pi_session TEXT,\n workstream TEXT,\n role TEXT,\n cli TEXT NOT NULL,\n driver TEXT NOT NULL CHECK (driver IN ('human', 'orchestrated')),\n claimed_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n ) STRICT;\n\n CREATE TABLE attention (\n pane TEXT NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN ('done', 'blocked', 'crashed')),\n message TEXT NOT NULL,\n source TEXT NOT NULL,\n session TEXT NOT NULL,\n window TEXT NOT NULL,\n session_name TEXT,\n window_name TEXT,\n requested_at INTEGER NOT NULL,\n PRIMARY KEY (pane, kind)\n ) STRICT;\n\n CREATE TABLE peers (\n name TEXT NOT NULL PRIMARY KEY,\n target TEXT NOT NULL,\n host_id TEXT,\n display_name TEXT,\n snapshot TEXT,\n snapshot_at INTEGER,\n fetched_at INTEGER,\n last_attempt_at INTEGER,\n last_error TEXT,\n murmur_version TEXT,\n snapshot_version INTEGER\n ) STRICT;\n`;\n\n/**\n * The store, and the only place in murmur that holds a database handle or\n * writes SQL.\n *\n * This interface is CLOSED. There is no `append`, no `ingest`, no log read, no\n * partial-row update, and no local read other than `localPanes` — each of those\n * shapes let a writer say something it had no standing to say, and each cost a\n * shipped bug. Attention methods take no agent identity at all, which is what\n * makes \"a notifier cannot corrupt an agent row\" structural.\n */\nexport interface Store {\n // --- agent lifecycle: owner-only, pid-gated -----------------------------\n claimAgent(claim: AgentClaim): ClaimResult;\n setActivity(update: ActivityUpdate): boolean;\n releaseAgent(release: AgentRelease): boolean;\n\n // --- attention: pane-addressed, no agent authority ----------------------\n requestAttention(request: AttentionRequest): void;\n acknowledgePane(pane: PaneId): number;\n\n // --- local truth --------------------------------------------------------\n /** The one local read. Joins agents and attention by pane. No reconciliation. */\n localPanes(): SnapshotPane[];\n reconcileLocal(world: LocalWorld): ReconcileSummary;\n buildLocalSnapshot(identity: NodeIdentity, world: LocalWorld): Snapshot;\n\n // --- peer cache ---------------------------------------------------------\n peers(): PeerRecord[];\n addPeer(name: string, target: string): void;\n removePeer(name: string): boolean;\n replacePeerSnapshot(name: string, fetch: PeerFetch): void;\n\n close(): void;\n}\n\ntype AgentDbRow = {\n agent_id: string;\n pane: string;\n owner_pid: number;\n activity: string;\n session: string;\n window: string;\n session_name: string | null;\n window_name: string | null;\n agent_name: string | null;\n pi_session: string | null;\n workstream: string | null;\n role: string | null;\n cli: string;\n driver: string;\n claimed_at: number;\n updated_at: number;\n};\n\ntype AttentionDbRow = {\n pane: string;\n kind: string;\n message: string;\n source: string;\n session: string;\n window: string;\n session_name: string | null;\n window_name: string | null;\n requested_at: number;\n};\n\ntype PeerDbRow = {\n name: string;\n target: string;\n host_id: string | null;\n display_name: string | null;\n snapshot: string | null;\n snapshot_at: number | null;\n fetched_at: number | null;\n last_attempt_at: number | null;\n last_error: string | null;\n murmur_version: string | null;\n snapshot_version: number | null;\n};\n\n/** Peer names and targets: the two fields a human typed, and all we salvage. */\nfunction salvagePeers(path: string): { name: string; target: string }[] {\n try {\n const existing = new Database(path, { fileMustExist: true });\n try {\n const version = (existing.pragma(\"user_version\", { simple: true }) as number) ?? 0;\n if (version === SCHEMA_USER_VERSION) return [];\n return existing.prepare(\"SELECT name, target FROM peers\").all() as {\n name: string;\n target: string;\n }[];\n } catch {\n // Too old to have the table, or unreadable. Nothing to save.\n return [];\n } finally {\n existing.close();\n }\n } catch {\n // No database yet, or one too broken to open.\n return [];\n }\n}\n\nfunction needsReset(path: string): boolean {\n try {\n const existing = new Database(path, { fileMustExist: true });\n try {\n return (\n ((existing.pragma(\"user_version\", { simple: true }) as number) ?? 0) !== SCHEMA_USER_VERSION\n );\n } finally {\n existing.close();\n }\n } catch {\n return false;\n }\n}\n\nfunction toAttention(row: AttentionDbRow): SnapshotAttention {\n return {\n kind: row.kind as AttentionKind,\n message: row.message,\n source: row.source,\n requested_at: row.requested_at,\n };\n}\n\nfunction toAgent(row: AgentDbRow): SnapshotAgent {\n return {\n agent_id: row.agent_id,\n activity: row.activity as SnapshotAgent[\"activity\"],\n agent_name: row.agent_name,\n pi_session: row.pi_session,\n workstream: row.workstream,\n role: row.role,\n cli: row.cli,\n driver: row.driver as SnapshotAgent[\"driver\"],\n claimed_at: row.claimed_at,\n updated_at: row.updated_at,\n };\n}\n\nconst PRIORITY = new Map<string, number>(RENDER_PRIORITY.map((kind, index) => [kind, index]));\n\nfunction attentionOrder(left: SnapshotAttention, right: SnapshotAttention): number {\n return (PRIORITY.get(left.kind) ?? 99) - (PRIORITY.get(right.kind) ?? 99);\n}\n\n/**\n * Open the store. Takes no arguments and mints no identity.\n *\n * `openStore` deliberately does NOT read or create `identity.json`: identity is\n * created only by `murmur init`, so a read path — a status-bar tick, a focus\n * hook — cannot bring a node into existence as a side effect.\n */\nexport function openStore(): Store {\n const path = dbPath();\n mkdirSync(dirname(path), { recursive: true });\n\n const salvaged = salvagePeers(path);\n if (needsReset(path)) {\n for (const suffix of [\"\", \"-wal\", \"-shm\"]) rmSync(`${path}${suffix}`, { force: true });\n }\n\n const database = new Database(path);\n database.pragma(\"journal_mode = WAL\");\n database.pragma(\"busy_timeout = 5000\");\n const version = (database.pragma(\"user_version\", { simple: true }) as number) ?? 0;\n if (version !== SCHEMA_USER_VERSION) {\n database.exec(SCHEMA);\n database.pragma(`user_version = ${SCHEMA_USER_VERSION}`);\n // Re-inserted with every OBSERVED column null: a salvaged peer has no\n // snapshot and has never been fetched, and saying otherwise would render a\n // never-reached host as fresh.\n const restore = database.prepare(\"INSERT OR IGNORE INTO peers (name, target) VALUES (?, ?)\");\n for (const peer of salvaged) restore.run(peer.name, peer.target);\n }\n\n const selectAgentByPane = database.prepare(\"SELECT * FROM agents WHERE pane = ?\");\n const insertAgent = database.prepare(`\n INSERT INTO agents (agent_id, pane, owner_pid, activity, session, window,\n session_name, window_name, agent_name, pi_session,\n workstream, role, cli, driver, claimed_at, updated_at)\n VALUES (@agent_id, @pane, @owner_pid, @activity, @session, @window,\n @session_name, @window_name, @agent_name, @pi_session,\n @workstream, @role, @cli, @driver, @claimed_at, @updated_at)\n `);\n const retainAgent = database.prepare(`\n UPDATE agents\n SET session = @session, window = @window, session_name = @session_name,\n window_name = @window_name, agent_name = @agent_name,\n pi_session = @pi_session, workstream = @workstream, role = @role,\n cli = @cli, driver = @driver, updated_at = @updated_at\n WHERE agent_id = @agent_id\n `);\n const deleteAgentByPane = database.prepare(\"DELETE FROM agents WHERE pane = ?\");\n const deleteAttentionForPane = database.prepare(\"DELETE FROM attention WHERE pane = ?\");\n const updateActivity = database.prepare(`\n UPDATE agents\n SET activity = @activity, session = @session, window = @window,\n session_name = @session_name, window_name = @window_name,\n updated_at = @updated_at\n WHERE agent_id = @agent_id AND owner_pid = @owner_pid\n `);\n const deleteAgentOwned = database.prepare(\n \"DELETE FROM agents WHERE agent_id = ? AND owner_pid = ?\",\n );\n const upsertAttention = database.prepare(`\n INSERT INTO attention (pane, kind, message, source, session, window,\n session_name, window_name, requested_at)\n VALUES (@pane, @kind, @message, @source, @session, @window,\n @session_name, @window_name, @requested_at)\n ON CONFLICT (pane, kind) DO UPDATE SET\n message = excluded.message,\n source = excluded.source,\n session = excluded.session,\n window = excluded.window,\n session_name = excluded.session_name,\n window_name = excluded.window_name\n `);\n const selectAgents = database.prepare(\"SELECT * FROM agents\");\n const selectAttention = database.prepare(\"SELECT * FROM attention\");\n const setActivityByPane = database.prepare(\n \"UPDATE agents SET activity = ?, updated_at = ? WHERE pane = ?\",\n );\n\n /**\n * `.immediate`, not deferred, and this is load-bearing.\n *\n * The transaction reads the incumbent row and then writes, so a deferred one\n * starts as a READER and must upgrade. Two doing that at once fails the loser\n * with SQLITE_BUSY_SNAPSHOT, which no busy_timeout can fix: waiting longer\n * cannot make a stale snapshot fresh. Measured previously at 5 of 8\n * concurrent writers failing.\n */\n const claimAgent = database.transaction((claim: AgentClaim): ClaimResult => {\n const now = claim.now ?? Date.now();\n const isAlive = claim.isAlive ?? pidAlive;\n const { location, meta, owner_pid } = claim;\n const incumbent = selectAgentByPane.get(location.pane) as AgentDbRow | undefined;\n\n const values = {\n pane: location.pane,\n owner_pid,\n session: location.session,\n window: location.window,\n session_name: location.session_name,\n window_name: location.window_name,\n agent_name: meta.agent_name,\n pi_session: meta.pi_session,\n workstream: meta.workstream,\n role: meta.role,\n cli: meta.cli,\n driver: meta.driver,\n updated_at: now,\n };\n\n if (!incumbent) {\n const agentId = randomUUID();\n insertAgent.run({ ...values, agent_id: agentId, activity: \"stopped\", claimed_at: now });\n return { outcome: \"claimed\", agent_id: agentId };\n }\n\n // Our own claim, seen again. This is what makes pi's `/reload` a no-op: pi\n // re-runs the extension factory in the same process, and a check that could\n // not recognise its own claim would silence the real agent. `activity` and\n // `agent_id` are deliberately untouched.\n if (incumbent.owner_pid === owner_pid) {\n retainAgent.run({ ...values, agent_id: incumbent.agent_id });\n return { outcome: \"retained\", agent_id: incumbent.agent_id };\n }\n\n // A different LIVE process in one pane: the nested-agent case, and the only\n // answer for it. Fails closed — `pidAlive` reports death only on ESRCH, so\n // an unanswerable probe (EPERM) reads as alive and refuses. An unknown must\n // never let a second writer displace a possibly-live owner.\n if (isAlive(incumbent.owner_pid)) {\n return { outcome: \"refused\", held_by_pid: incumbent.owner_pid };\n }\n\n // The previous occupant is gone. Its attention described a process that no\n // longer exists, and a human looking at the pane now sees a different agent.\n deleteAgentByPane.run(location.pane);\n deleteAttentionForPane.run(location.pane);\n const agentId = randomUUID();\n insertAgent.run({ ...values, agent_id: agentId, activity: \"stopped\", claimed_at: now });\n return { outcome: \"replaced\", agent_id: agentId, previous_agent_id: incumbent.agent_id };\n }).immediate;\n\n /**\n * One transaction, because the `stopped` write and its `crashed` attention row\n * must land together or not at all.\n *\n * A no-op when tmux could not answer: `panes === null` is absence of evidence,\n * not evidence of death, and conflating the two once deleted ten live agents.\n */\n const reconcileLocal = database.transaction((world: LocalWorld): ReconcileSummary => {\n const summary: ReconcileSummary = { crashed: [], removed: [], attention_removed: [] };\n if (world.panes === null) return summary;\n const live = world.panes;\n const isAlive = world.isAlive ?? pidAlive;\n const now = world.now ?? Date.now();\n\n // Which panes already carry a crash we recorded. Read once, before any\n // write, so the loop below sees the state reconciliation started from.\n const alreadyCrashed = new Set(\n (selectAttention.all() as AttentionDbRow[])\n .filter((row) => row.kind === \"crashed\")\n .map((row) => row.pane),\n );\n\n for (const row of selectAgents.all() as AgentDbRow[]) {\n const pane = asPaneId(row.pane);\n if (!live.has(pane)) {\n deleteAgentByPane.run(row.pane);\n deleteAttentionForPane.run(row.pane);\n summary.removed.push(pane);\n continue;\n }\n if (isAlive(row.owner_pid)) continue;\n\n // The asymmetry below is the point. A dead RUNNING owner is an unreported\n // crash and must leave a durable trace. A dead STOPPED owner finished\n // normally, so its row is noise — but any `done` it raised is a fact a\n // human has not yet seen, so the attention stays.\n if (row.activity === \"running\") {\n setActivityByPane.run(\"stopped\", now, row.pane);\n upsertAttention.run({\n pane: row.pane,\n kind: \"crashed\",\n message: \"\",\n source: \"murmur\",\n session: row.session,\n window: row.window,\n session_name: row.session_name,\n window_name: row.window_name,\n requested_at: now,\n });\n summary.crashed.push(pane);\n } else if (!alreadyCrashed.has(row.pane)) {\n deleteAgentByPane.run(row.pane);\n summary.removed.push(pane);\n }\n // A pane we already recorded a crash for keeps its agent row, and that is\n // the one place this deviates from a literal reading of the contract's\n // table -- which says a live pane with a dead STOPPED owner loses its row.\n // Taken literally, the second reconcile after a crash deletes the row the\n // first one had just marked `stopped`, so the crashed pane loses its\n // agent_name, workstream, role and cli one tick after the crash is\n // reported. That contradicts the contract's own idempotence requirement\n // (\"running it again changes nothing\") and it strips exactly the fields a\n // human needs to know WHICH agent died.\n //\n // The distinction the table is drawing is between an owner that finished\n // normally -- whose row is noise -- and one that died mid-run. The\n // `crashed` row we wrote is the record of which case this was, so it is\n // also the right thing to key on.\n }\n\n // Reaps attention for a pane that never had an agent row — an\n // attention-only codex pane whose window was closed. Nothing else would.\n for (const row of selectAttention.all() as AttentionDbRow[]) {\n const pane = asPaneId(row.pane);\n if (live.has(pane)) continue;\n deleteAttentionForPane.run(row.pane);\n if (!summary.attention_removed.includes(pane)) summary.attention_removed.push(pane);\n }\n\n return summary;\n }).immediate;\n\n /**\n * Both tables read at ONE point in time, or a pane can appear with an agent\n * and without the attention that was there when the agent was read.\n */\n const readLocalPanes = database.transaction((): SnapshotPane[] => {\n const agents = selectAgents.all() as AgentDbRow[];\n const attention = selectAttention.all() as AttentionDbRow[];\n const panes = new Map<string, SnapshotPane>();\n\n const locate = (row: AgentDbRow | AttentionDbRow): SnapshotPane => {\n const existing = panes.get(row.pane);\n if (existing) return existing;\n const created: SnapshotPane = {\n pane: asPaneId(row.pane),\n session: asSessionId(row.session),\n window: asWindowId(row.window),\n session_name: row.session_name,\n window_name: row.window_name,\n agent: null,\n attention: [],\n };\n panes.set(row.pane, created);\n return created;\n };\n\n for (const row of agents) locate(row).agent = toAgent(row);\n for (const row of attention) locate(row).attention.push(toAttention(row));\n\n for (const pane of panes.values()) pane.attention.sort(attentionOrder);\n return [...panes.values()].sort((left, right) => left.pane.localeCompare(right.pane));\n });\n\n function peerRecord(row: PeerDbRow): PeerRecord {\n let snapshot: Snapshot | null = null;\n if (row.snapshot !== null) {\n try {\n // Parsed leniently on the way OUT: it was validated on the way in, and\n // a read path must not throw. A stored document that no longer parses\n // reads as \"no snapshot\" and is left in place, not deleted.\n snapshot = JSON.parse(row.snapshot) as Snapshot;\n } catch {\n snapshot = null;\n }\n }\n return {\n name: row.name,\n target: row.target,\n host_id: row.host_id,\n display_name: row.display_name,\n snapshot,\n snapshot_at: row.snapshot_at,\n fetched_at: row.fetched_at,\n last_attempt_at: row.last_attempt_at,\n last_error: row.last_error,\n murmur_version: row.murmur_version,\n snapshot_version: row.snapshot_version,\n };\n }\n\n return {\n claimAgent,\n reconcileLocal,\n\n setActivity(update) {\n // Both key components are required, so a write from a REPLACED owner\n // matches nothing and returns false. That is not an error and must not be\n // retried: it means this process is no longer the owner of record, and the\n // correct response is silence.\n return (\n updateActivity.run({\n activity: update.activity,\n session: update.location.session,\n window: update.location.window,\n session_name: update.location.session_name,\n window_name: update.location.window_name,\n updated_at: update.now ?? Date.now(),\n agent_id: update.agent_id,\n owner_pid: update.owner_pid,\n }).changes === 1\n );\n },\n\n releaseAgent(release) {\n // Attention is deliberately NOT deleted: a `done` raised at settle must\n // survive the agent exiting, or completion becomes invisible the moment\n // the process quits.\n return deleteAgentOwned.run(release.agent_id, release.owner_pid).changes === 1;\n },\n\n requestAttention(request) {\n // `requested_at` is absent from the DO UPDATE list on purpose. Age means\n // \"how long this has gone unmet\", so a repeat must not reset the clock —\n // which also makes crash attention idempotent for free. Touches no\n // `agents` row, ever; there is no column here that could.\n upsertAttention.run({\n pane: request.location.pane,\n kind: request.kind,\n message: request.message,\n source: request.source,\n session: request.location.session,\n window: request.location.window,\n session_name: request.location.session_name,\n window_name: request.location.window_name,\n requested_at: request.now ?? Date.now(),\n });\n },\n\n acknowledgePane(pane) {\n // Every kind, one statement, no agent row touched: focusing a pane cannot\n // alter activity or owner metadata. This is the whole `murmur clear`\n // write path.\n return deleteAttentionForPane.run(pane).changes;\n },\n\n localPanes() {\n return readLocalPanes();\n },\n\n buildLocalSnapshot(identity, world) {\n // Reconcile first, which is what makes \"a snapshot is authoritative\"\n // true: absence from a successful snapshot means absence, so it must\n // never be produced from unreconciled rows. Two transactions rather than\n // one — a write transaction held open across the read would serialise\n // every focus hook on the machine behind an export.\n reconcileLocal(world);\n return {\n murmur_snapshot: 1,\n host_id: identity.host_id,\n display_name: identity.display_name,\n murmur_version: MURMUR_VERSION,\n generated_at: world.now ?? Date.now(),\n // Rule 3: a pane with no agent and no attention must not be published.\n // A no-op against today's `readLocalPanes`, which builds a pane entry\n // only from a row and so cannot produce an empty one -- kept because the\n // rule belongs to the DOCUMENT, and the validator rejects such an entry\n // outright. Without it, one narrowing of the local read would make this\n // node reachable-but-broken on every peer that collects it, and the\n // symptom would show up on the other machines.\n panes: readLocalPanes().filter((pane) => pane.agent !== null || pane.attention.length > 0),\n };\n },\n\n peers() {\n return (database.prepare(\"SELECT * FROM peers ORDER BY name\").all() as PeerDbRow[]).map(\n peerRecord,\n );\n },\n\n addPeer(name, target) {\n // Correcting a target must not discard the cache, so this updates only\n // the field the operator retyped.\n database\n .prepare(\n `INSERT INTO peers (name, target) VALUES (?, ?)\n ON CONFLICT(name) DO UPDATE SET target = excluded.target`,\n )\n .run(name, target);\n },\n\n removePeer(name) {\n return database.prepare(\"DELETE FROM peers WHERE name = ?\").run(name).changes > 0;\n },\n\n replacePeerSnapshot(name, fetch) {\n if (!fetch.ok) {\n // Failure touches neither snapshot, snapshot_at nor fetched_at, so the\n // last-known document stands and the peer ages into `stale` on its own.\n database\n .prepare(\"UPDATE peers SET last_attempt_at = ?, last_error = ? WHERE name = ?\")\n .run(fetch.at, fetch.error, name);\n return;\n }\n // Two clocks, and conflating them is how a freshly fetched three-hour-old\n // fact reads as new. `snapshot_at` is the PEER's clock (when it built the\n // document); `fetched_at` is OURS (when we reached it), and freshness is\n // computed from `fetched_at` only.\n database\n .prepare(\n `UPDATE peers\n SET snapshot = ?, snapshot_at = ?, fetched_at = ?, last_attempt_at = ?,\n last_error = NULL, host_id = ?, display_name = ?,\n murmur_version = ?, snapshot_version = ?\n WHERE name = ?`,\n )\n .run(\n JSON.stringify(fetch.snapshot),\n fetch.snapshot.generated_at,\n fetch.at,\n fetch.at,\n fetch.snapshot.host_id,\n fetch.snapshot.display_name,\n fetch.snapshot.murmur_version,\n fetch.snapshot.murmur_snapshot,\n name,\n );\n },\n\n close() {\n database.close();\n },\n };\n}\n","import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport function stateDir(): string {\n return (\n process.env.MURMUR_STATE_DIR ??\n join(process.env.XDG_STATE_HOME ?? join(homedir(), \".local\", \"state\"), \"murmur\")\n );\n}\n\nexport function configDir(): string {\n return (\n process.env.MURMUR_CONFIG_DIR ??\n join(process.env.XDG_CONFIG_HOME ?? join(homedir(), \".config\"), \"murmur\")\n );\n}\n\n/** The current-state database. The only database murmur holds. */\nexport function dbPath(): string {\n return join(stateDir(), \"state.db\");\n}\n","import { createRequire } from \"node:module\";\n\n/**\n * This node's murmur version, read from the manifest.\n *\n * Read rather than restated, for the reason index.ts already gives: two copies\n * of one fact drift, and npm bumps the manifest. It lives in its own module\n * because THREE bundles need it and they sit at different depths --\n * `dist/index.js`, `dist/cli.js` and `dist/extension/store.js` -- so a single\n * hardcoded `\"../package.json\"` resolves in two of them and throws in the third.\n *\n * That is not hypothetical. `openStore` moved into the extension bundle during\n * the current-state rewrite, and its `../package.json` became\n * `dist/package.json`, which does not exist. The extension catches every store\n * failure and degrades to silence, so the symptom was an agent that reported\n * nothing at all, with no error anywhere -- exactly the failure mode the\n * three-state store handle exists to make survivable, hiding a hard one.\n *\n * Hence both candidates, tried in order, and a throw if neither works: a version\n * this node cannot state belongs in a snapshot even less than a wrong one does.\n */\nfunction readVersion(): string {\n const require = createRequire(import.meta.url);\n for (const candidate of [\"../package.json\", \"../../package.json\"]) {\n try {\n return (require(candidate) as { version: string }).version;\n } catch {\n // Wrong depth for this bundle; try the next.\n }\n }\n throw new Error(\"cannot locate package.json to read the murmur version\");\n}\n\nexport const MURMUR_VERSION: string = readVersion();\n","import type { PaneId, SessionId, WindowId } from \"./ids.js\";\n\n/**\n * The three independent facts, as types.\n *\n * `activity` is what the pane's own process says it is doing. `attention` is\n * whether a human is wanted. `freshness` (src/view.ts) is how recently we\n * reached the node that reported. They are three independent fields, never one\n * enum, and absence carries meaning: no attention row means \"nothing to see\",\n * no agent row means \"no agent here\".\n */\nexport type Activity = \"running\" | \"stopped\";\nexport type AttentionKind = \"done\" | \"blocked\" | \"crashed\";\n\n/**\n * Who is waiting on this agent -- a human, or a supervisor that consumes the\n * result. Not \"which harness\"; that is `cli`.\n */\nexport type Driver = \"human\" | \"orchestrated\";\n\nexport const DEFAULT_DRIVER: Driver = \"human\";\n\n/**\n * Where a pane currently lives. Location, never identity.\n *\n * `pane` is the address and is stable for the life of the pane; `session` and\n * `window` are only where that pane currently is, and both change under\n * move-pane and break-pane. Only a pane may decide whether an agent exists,\n * which is what the brands in ./ids.js enforce.\n */\nexport type Location = {\n session: SessionId;\n window: WindowId;\n pane: PaneId;\n session_name: string | null;\n window_name: string | null;\n};\n\n/** Owner-reported metadata about the agent in a pane. */\nexport type AgentMeta = {\n agent_name: string | null;\n pi_session: string | null;\n workstream: string | null;\n role: string | null;\n cli: string;\n driver: Driver;\n};\n\nexport type PeerRecord = {\n name: string;\n target: string;\n host_id: string | null;\n display_name: string | null;\n /** The whole validated document, or null when we have never parsed one. */\n snapshot: Snapshot | null;\n /** The PEER's clock: when that node built the document. */\n snapshot_at: number | null;\n /** OUR clock: when we last reached it. Freshness is computed from this. */\n fetched_at: number | null;\n last_attempt_at: number | null;\n last_error: string | null;\n murmur_version: string | null;\n /** The peer's `murmur_snapshot` value, i.e. the document version it speaks. */\n snapshot_version: number | null;\n};\n\n/**\n * One node's whole current state. Complete, never a delta: a peer that returns\n * one has said everything it knows, so absence from it is absence.\n */\nexport type Snapshot = {\n murmur_snapshot: 1;\n host_id: string;\n display_name: string;\n murmur_version: string;\n generated_at: number;\n panes: SnapshotPane[];\n};\n\nexport type SnapshotPane = {\n pane: PaneId;\n session: SessionId;\n window: WindowId;\n session_name: string | null;\n window_name: string | null;\n /** Null for an attention-only pane: valid, listable, jumpable. */\n agent: SnapshotAgent | null;\n attention: SnapshotAttention[];\n};\n\nexport type SnapshotAgent = AgentMeta & {\n agent_id: string;\n activity: Activity;\n claimed_at: number;\n updated_at: number;\n};\n\nexport type SnapshotAttention = {\n kind: AttentionKind;\n message: string;\n source: string;\n requested_at: number;\n};\n\n/**\n * Whether a pid is still running. A parameter everywhere it is consulted, so a\n * test needs no process table.\n */\nexport type LiveCheck = (pid: number) => boolean;\n\nexport type AgentClaim = {\n location: Location;\n owner_pid: number;\n meta: AgentMeta;\n now?: number;\n isAlive?: LiveCheck;\n};\n\nexport type ClaimResult =\n | { outcome: \"claimed\"; agent_id: string }\n | { outcome: \"retained\"; agent_id: string }\n | { outcome: \"replaced\"; agent_id: string; previous_agent_id: string }\n | { outcome: \"refused\"; held_by_pid: number };\n\nexport type ActivityUpdate = {\n agent_id: string;\n owner_pid: number;\n activity: Activity;\n location: Location;\n now?: number;\n};\n\nexport type AgentRelease = { agent_id: string; owner_pid: number };\n\n/**\n * Everything an attention writer may say. There is no agent_id, no owner_pid,\n * no activity and no owner metadata field, and adding one is a contract change.\n */\nexport type AttentionRequest = {\n kind: AttentionKind;\n location: Location;\n message: string;\n source: string;\n now?: number;\n};\n\n/**\n * The only local facts reconciliation is allowed to consult.\n *\n * `panes` is null when tmux could not answer, which is not evidence of death.\n * `isAlive` and `now` are parameters so a test needs no process table and no\n * clock control.\n */\nexport type LocalWorld = {\n panes: Set<PaneId> | null;\n isAlive?: LiveCheck;\n now?: number;\n};\n\nexport type ReconcileSummary = {\n crashed: PaneId[];\n removed: PaneId[];\n attention_removed: PaneId[];\n};\n\nexport type PeerFetch =\n | { ok: true; snapshot: Snapshot; at: number }\n | { ok: false; error: string; at: number };\n","import type { NodeIdentity } from \"./identity.js\";\nimport type { PaneId, SessionId, WindowId } from \"./ids.js\";\nimport type { Store } from \"./store.js\";\nimport {\n type Activity,\n type AttentionKind,\n DEFAULT_DRIVER,\n type Driver,\n type SnapshotPane,\n} from \"./types.js\";\n\nexport type Freshness = \"fresh\" | \"stale\";\n\n/**\n * What a surface paints. Presentation only, derived from the three independent\n * facts and never stored.\n */\nexport type RenderState = \"crashed\" | \"blocked\" | \"done\" | \"running\" | \"idle\";\n\n/**\n * THE single ordering table: which state matters most, for sorting and for\n * choosing one word to show.\n *\n * `status.ts` and `pick.ts` import this rather than declaring their own copies,\n * so no two surfaces can sort one list differently.\n */\nexport const RENDER_PRIORITY: readonly RenderState[] = [\n \"crashed\",\n \"blocked\",\n \"done\",\n \"running\",\n \"idle\",\n];\n\n/**\n * The attention kinds only a human can answer, and the second table both\n * surfaces must agree on.\n *\n * `blocked` means waiting for an answer an orchestrator cannot give -- mu places\n * work, it cannot choose between two approaches. `crashed` means the process\n * died, which a supervisor may or may not retry. Everything else about an\n * orchestrated agent is its supervisor's business.\n *\n * `pick.ts` uses it to decide which crew rows are visible by default and\n * `status.ts` to decide which crew states reach the status bar. They were two\n * literals in two files answering one question, which is how a row that needed a\n * human became one a human could not see.\n */\nexport const NEEDS_HUMAN: readonly AttentionKind[] = [\"blocked\", \"crashed\"];\n\n/**\n * One pane, as every surface reads it: address, the three independent facts,\n * owner metadata, and ages.\n *\n * Local and remote panes are the same type, built by the same mapping, because\n * `Store.localPanes()` and a peer's cached snapshot both return\n * `SnapshotPane[]`. One mapping means local and remote cannot drift apart.\n */\nexport type PaneView = {\n // address\n host_id: string;\n /** The name the operator typed, or this node's display_name. */\n host: string;\n local: boolean;\n pane: PaneId;\n session: SessionId;\n window: WindowId;\n session_name: string | null;\n window_name: string | null;\n // the three independent facts\n /** Null for an attention-only pane, which has no agent row. */\n activity: Activity | null;\n attention: AttentionKind[];\n freshness: Freshness;\n // owner-reported metadata, null for an attention-only pane\n agent_id: string | null;\n agent_name: string | null;\n pi_session: string | null;\n workstream: string | null;\n role: string | null;\n cli: string | null;\n driver: Driver;\n // ages\n /** When the pane's own node last said something. Never `fetched_at`. */\n updated_at: number | null;\n /** When that node generated its snapshot. Null for local. */\n snapshot_at: number | null;\n /** When we last reached that node. Null for local. */\n fetched_at: number | null;\n};\n\n/**\n * How long a peer may go unfetched before its panes render stale.\n *\n * Re-exported from here rather than imported from the collector by view\n * consumers, so freshness has one definition. See collector.ts for why sixty\n * seconds.\n */\nexport const STALENESS_MS = 60_000;\n\n/**\n * A duration as the shortest thing worth reading: \"5m\", \"2h\", \"3d\".\n *\n * Under a minute is the empty string: an age that changes every second is noise\n * in a status column. This and `freshness` are the only two places a duration\n * becomes text or a verdict.\n */\nexport function age(ms: number | null): string {\n if (ms === null || ms < 60_000) return \"\";\n if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m`;\n if (ms < 86_400_000) return `${Math.floor(ms / 3_600_000)}h`;\n return `${Math.floor(ms / 86_400_000)}d`;\n}\n\n/**\n * Freshness of a NODE, never of an agent.\n *\n * A peer we have never reached is stale rather than fresh: null means the first\n * collect has not succeeded yet, and an unreachable host you just added must not\n * render as up to date.\n */\nexport function freshness(\n fetchedAt: number | null,\n now: number,\n thresholdMs = STALENESS_MS,\n): Freshness {\n return fetchedAt !== null && now - fetchedAt <= thresholdMs ? \"fresh\" : \"stale\";\n}\n\n/**\n * One word for a pane. Attention wins over activity, because attention is a\n * request and activity is a description.\n *\n * A running agent with `blocked` attention is a valid and expected state, and\n * surfaces that can show both, do — this is only for the ones that must pick.\n */\nexport function renderState(view: Pick<PaneView, \"activity\" | \"attention\">): RenderState {\n for (const kind of [\"crashed\", \"blocked\", \"done\"] as const) {\n if (view.attention.includes(kind)) return kind;\n }\n return view.activity === \"running\" ? \"running\" : \"idle\";\n}\n\n/** The newest attention request on a pane, for the `updated_at` of one with no agent. */\nfunction newestAttention(pane: SnapshotPane): number | null {\n let newest: number | null = null;\n for (const entry of pane.attention) {\n if (newest === null || entry.requested_at > newest) newest = entry.requested_at;\n }\n return newest;\n}\n\ntype ViewSource = {\n host_id: string;\n host: string;\n local: boolean;\n freshness: Freshness;\n snapshot_at: number | null;\n fetched_at: number | null;\n};\n\nfunction paneView(pane: SnapshotPane, source: ViewSource): PaneView {\n const agent = pane.agent;\n return {\n host_id: source.host_id,\n host: source.host,\n local: source.local,\n pane: pane.pane,\n session: pane.session,\n window: pane.window,\n session_name: pane.session_name,\n window_name: pane.window_name,\n activity: agent?.activity ?? null,\n attention: pane.attention.map((entry) => entry.kind),\n freshness: source.freshness,\n agent_id: agent?.agent_id ?? null,\n agent_name: agent?.agent_name ?? null,\n pi_session: agent?.pi_session ?? null,\n workstream: agent?.workstream ?? null,\n role: agent?.role ?? null,\n cli: agent?.cli ?? null,\n driver: agent?.driver ?? DEFAULT_DRIVER,\n updated_at: agent?.updated_at ?? newestAttention(pane),\n snapshot_at: source.snapshot_at,\n fetched_at: source.fetched_at,\n };\n}\n\n/**\n * Every pane this node knows about: its own, plus one cached snapshot per peer.\n *\n * `identity` is non-null because every caller is a command that already requires\n * `murmur init`, so no pane can be misclassified as remote by an absent one.\n *\n * No liveness is probed here, for local or remote. A remote pane's `activity` is\n * whatever its own node last said; a stale node keeps its last-known fields\n * verbatim beside an explicit warning.\n */\nexport function paneViews(store: Store, identity: NodeIdentity, now = Date.now()): PaneView[] {\n const views = store.localPanes().map((pane) =>\n paneView(pane, {\n host_id: identity.host_id,\n host: identity.display_name,\n local: true,\n // Local panes are always fresh: we are the node that authored them.\n freshness: \"fresh\",\n snapshot_at: null,\n fetched_at: null,\n }),\n );\n\n for (const peer of store.peers()) {\n const snapshot = peer.snapshot;\n if (!snapshot) continue;\n const source: ViewSource = {\n host_id: snapshot.host_id,\n // The name the human typed, not the machine's self-reported hostname: a\n // peer added as `linuxpc` can report a container id, which appears\n // nowhere else in the tool and cannot be typed at `peer remove`.\n host: peer.name,\n local: false,\n freshness: freshness(peer.fetched_at, now),\n snapshot_at: peer.snapshot_at,\n fetched_at: peer.fetched_at,\n };\n for (const pane of snapshot.panes) views.push(paneView(pane, source));\n }\n\n return views;\n}\n\nconst ORDER = new Map<RenderState, number>(RENDER_PRIORITY.map((state, index) => [state, index]));\n\n/**\n * Attention-first ordering, then the newest news, then address.\n *\n * TOTAL on purpose, and that is the whole reason the last two comparisons\n * exist. Ties on state and age are ordinary rather than exotic -- a pair of\n * crashed panes reconciled in one transaction shares a `requested_at` exactly --\n * and `Array.prototype.sort` is stable only with respect to the order it was\n * GIVEN, which here is whatever SQLite and the peer loop happened to produce. An\n * unbroken tie therefore makes the list depend on that order: a status bar\n * reshuffles between two identical ticks, and a picker row moves under the\n * keypress that was aimed at it.\n *\n * Presentation only. No caller may read meaning into the position of a row --\n * pane order in a snapshot carries none either, so a reader sorts for itself\n * rather than trusting what it was served.\n */\nexport function viewSort(views: PaneView[]): PaneView[] {\n return [...views].sort((left, right) => {\n const byState = (ORDER.get(renderState(left)) ?? 99) - (ORDER.get(renderState(right)) ?? 99);\n if (byState !== 0) return byState;\n // Unknown age sorts last within its state: an attention-only pane with no\n // timestamp is not news, and 0 is older than any real clock reading.\n const byAge = (right.updated_at ?? 0) - (left.updated_at ?? 0);\n if (byAge !== 0) return byAge;\n // Address as the final key, because it is the only field guaranteed unique\n // across the whole view: `pane` is unique per node and `host` per peer.\n const byHost = left.host.localeCompare(right.host);\n return byHost !== 0 ? byHost : left.pane.localeCompare(right.pane);\n });\n}\n","import type { Command } from \"commander\";\nimport { asPaneId, type WindowId } from \"../ids.js\";\nimport { type Mux, tmux } from \"../mux.js\";\nimport { openStore, type Store } from \"../store.js\";\nimport { RENDER_PRIORITY, type RenderState, renderState } from \"../view.js\";\n\n/**\n * Does any OTHER pane in this window still want attention?\n *\n * The badge is a WINDOW option while \"the user looked\" is only true of one pane,\n * so a window holding an agent and a shell must not lose the badge when you\n * focus the shell.\n *\n * The question is asked of ATTENTION only: a busy agent next door is not a\n * reason to keep an attention badge lit.\n *\n * Fails safe by keeping the badge: if tmux or the store cannot answer we say\n * yes. Wrongly keeping a badge is recoverable by focusing the pane; wrongly\n * clearing one loses the signal.\n */\nfunction windowBadge(window: WindowId, mux: Mux, store: Store): RenderState | null {\n const panes = new Set(mux.panesInWindow(window));\n const states = store\n .localPanes()\n .filter((pane) => panes.has(pane.pane))\n .map((pane) =>\n renderState({\n activity: pane.agent?.activity ?? null,\n attention: pane.attention.map((entry) => entry.kind),\n }),\n );\n return RENDER_PRIORITY.find((state) => state !== \"idle\" && states.includes(state)) ?? null;\n}\n\n/**\n * Acknowledge every attention request on one pane, and clear its window badge.\n *\n * That is the whole write path. There is no state focus must refuse to clear,\n * because attention is the only thing focus can address: `acknowledgePane` is a\n * single `DELETE FROM attention WHERE pane = ?` and cannot touch an agent's\n * activity, its identity or its owner metadata. A focus hook has nothing to\n * overwrite a running agent with.\n *\n * Best effort, silent and total: this runs inside the tmux server.\n */\nexport function clearPane(raw: string, mux: Mux = tmux): void {\n let store: Store | undefined;\n try {\n if (!raw) return;\n // argv is the boundary: a pane id arrives as a bare string from the tmux\n // hook that invoked us.\n const pane = asPaneId(raw);\n // The badge is a tmux window option, not murmur state, so resolving it never\n // needs murmur to know anything. A pane murmur has never seen can still\n // carry an orphan badge that nothing else will ever clear.\n const window = mux.windowForPane(pane);\n\n try {\n store = openStore();\n store.acknowledgePane(pane);\n } catch {\n // No database, or an unwritable one. The badge still clears below, which\n // is the visible half.\n }\n\n if (!window) return;\n // @agent_state is a derived, window-scoped projection. Recompute it after\n // deleting this pane's attention: blindly clearing the option made a live\n // running agent display as idle even though its agent row was untouched.\n try {\n mux.setWindowBadge(window, store ? windowBadge(window, mux, store) : null);\n } catch {\n // If the projection cannot be read, leave the existing badge alone. A\n // stale badge is recoverable; erasing a real signal is not.\n }\n } catch {\n // Focus hooks run inside the tmux server: they must always be silent and\n // total.\n } finally {\n try {\n store?.close();\n } catch {\n // Silent and total.\n }\n }\n}\n\nexport function registerClear(program: Command): void {\n program\n .command(\"clear\")\n .description(\"Acknowledge attention for a pane\")\n .option(\"--pane <pane-id>\", \"focused tmux pane id\")\n .action((options: { pane?: string }) => clearPane(options.pane ?? \"\"));\n}\n","import { execFile, execFileSync } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\nconst CONTROL_PATH = \"~/.ssh/control/%r@%h:%p\";\n\n// Both timeouts are sized against the tmux status bar, because that is what\n// actually drives collection: `murmur status` collects, and tmux re-runs it\n// every `status-interval` — 5s on the author's setup, 15s by default. A collect\n// that outlives its tick is a collect overlapping itself, and tmux offers no\n// way to cancel the last one.\n//\n// So the budget for the whole exchange is under 5s, and these are deliberately\n// aggressive: a really slow node is rejected rather than allowed to hold up the\n// HUD. That is cheap because the cost of losing the race is one tick of\n// staleness, and the next tick is five seconds away.\n\n// OpenSSH's default TCP connect timeout is the kernel's, 75s on macOS, which\n// made `murmur pick` unusable against a sleeping laptop. One second is still\n// ~6x a real cold handshake on a LAN or VPN (measured: 168ms cold, 42ms on a\n// warm control socket), and a peer that misses it simply shows stale — the\n// designed outcome for a host you cannot reach.\nconst CONNECT_TIMEOUT_S = 1;\n\n// Belt and braces for a host that completes the TCP connect and then stops\n// responding — ConnectTimeout does not cover that, and it is how a sleeping\n// laptop behaves. Bounds the whole exchange rather than just the dial, so it\n// has to leave room for the dial plus an export: three seconds is the tick\n// budget minus headroom for the rest of `status`.\nconst EXEC_TIMEOUT_MS = 3_000;\n\n// Warm if possible, cold if not, never interactive.\n//\n// ControlMaster=no attaches to a master socket left behind by an ordinary\n// `ssh <host>` (given ControlMaster auto + ControlPersist in ssh_config), so a\n// peer you have touched recently costs a new channel on an authenticated\n// connection rather than a handshake.\n//\n// When no socket is listening OpenSSH falls back to connecting normally, and we\n// want that: with plain key auth a cold peer collects fine, just slower\n// (~170ms against ~10ms measured on a LAN). Fleet visibility should not depend\n// on having ssh'd somewhere today.\n//\n// BatchMode=yes bounds what that fallback may do. It disables every\n// interactive prompt — password, passphrase, host key confirmation — so a\n// cold peer that cannot authenticate silently fails immediately instead of\n// blocking a background collect on a human. Note this is \"never prompt\", not\n// \"never authenticate\": a host demanding a hardware-token touch per connection\n// is the case this does not fully cover, and the reason `hasWarmSocket` exists\n// should that ever need gating.\n//\n// Exported because every ssh murmur runs wants exactly this posture -- the\n// collector, the picker's preview, the jump probe. Three hand-rolled copies is\n// how one of them ends up without BatchMode and starts prompting for auth on\n// every keypress.\nexport const SSH_OPTIONS = [\n \"-o\",\n \"BatchMode=yes\",\n \"-o\",\n \"ControlMaster=no\",\n \"-o\",\n `ControlPath=${CONTROL_PATH}`,\n \"-o\",\n `ConnectTimeout=${CONNECT_TIMEOUT_S}`,\n];\n\nexport interface Channel {\n exec(target: string, argv: string[]): Promise<string>;\n}\n\n// Node's execFile defaults to a 1 MiB stdout ceiling and rejects with\n// ERR_CHILD_PROCESS_STDIO_MAXBUFFER past it, killing the child. An export is the\n// peer's whole current state, bounded by live pane count -- a few hundred bytes\n// per pane, on a machine that cannot hold thousands of panes -- so the ceiling\n// is out of reach in practice.\n//\n// It is kept generous anyway, because the failure mode is bad out of proportion\n// to its likelihood: a peer whose document exceeds the buffer fails identically\n// on every collect, so it sits stale forever with an error that names a Node\n// internal rather than a size.\n//\n// 64 MiB is orders of magnitude above any real snapshot, and it is a\n// ceiling rather than an allocation. The timeout is the real bound on a\n// runaway peer.\nconst MAX_EXPORT_BYTES = 64 * 1024 * 1024;\n\nexport const ssh: Channel = {\n async exec(target, argv) {\n const { stdout } = await execFileAsync(\"ssh\", [...SSH_OPTIONS, target, ...argv], {\n encoding: \"utf8\",\n timeout: EXEC_TIMEOUT_MS,\n maxBuffer: MAX_EXPORT_BYTES,\n });\n return stdout;\n },\n};\n\nexport function hasWarmSocket(target: string): boolean {\n try {\n execFileSync(\"ssh\", [...SSH_OPTIONS, \"-O\", \"check\", target], { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n","import { asPaneId, asSessionId, asWindowId } from \"./ids.js\";\nimport type {\n Activity,\n AttentionKind,\n Driver,\n Snapshot,\n SnapshotAgent,\n SnapshotAttention,\n SnapshotPane,\n} from \"./types.js\";\n\n/**\n * A peer answered, and what it said is not a snapshot.\n *\n * A distinct type because the collector must be able to tell this from an\n * unreachable host: a node that serves a bad document is REACHABLE BUT BROKEN,\n * and an operator needs to see that rather than \"asleep, probably\".\n */\nexport class SnapshotInvalidError extends Error {\n constructor(\n readonly path: string,\n detail: string,\n ) {\n // An EMPTY path means the failure is about the document as a whole, not\n // about a field in it, so there is nothing to prefix. Joining regardless\n // produced `bubba: : not JSON (...)` in `peer list` and in the one line\n // `murmur collect` prints -- measured against a real second node, and for\n // the most common remote misconfiguration there is (murmur missing, so the\n // \"document\" is a shell error). `path` itself stays \"\", because that is what\n // it means and a caller must not have to know a sentinel.\n super(path === \"\" ? detail : `${path}: ${detail}`);\n this.name = \"SnapshotInvalidError\";\n }\n}\n\nfunction fail(path: string, detail: string): never {\n throw new SnapshotInvalidError(path, detail);\n}\n\n/**\n * Exactly these keys, no more and no fewer.\n *\n * Unknown keys are rejected rather than carried, and nothing is coerced or\n * defaulted: validation happens BEFORE storage, so no unknown value can reach a\n * sort, a count or a render path.\n */\nfunction object(value: unknown, path: string, keys: readonly string[]): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n fail(path, \"expected an object\");\n }\n const record = value as Record<string, unknown>;\n for (const key of keys) if (!(key in record)) fail(path, `missing key ${key}`);\n for (const key of Object.keys(record)) {\n if (!keys.includes(key)) fail(path, `unknown key ${key}`);\n }\n return record;\n}\n\nfunction text(value: unknown, path: string): string {\n if (typeof value !== \"string\" || value === \"\") fail(path, \"expected a non-empty string\");\n return value;\n}\n\nfunction textOrNull(value: unknown, path: string): string | null {\n if (value === null) return null;\n if (typeof value !== \"string\") fail(path, \"expected a string or null\");\n return value;\n}\n\nfunction anyText(value: unknown, path: string): string {\n if (typeof value !== \"string\") fail(path, \"expected a string\");\n return value;\n}\n\nfunction timestamp(value: unknown, path: string): number {\n if (typeof value !== \"number\" || !Number.isInteger(value) || value < 0) {\n fail(path, \"expected a non-negative integer\");\n }\n return value;\n}\n\nfunction member<T extends string>(value: unknown, path: string, allowed: readonly T[]): T {\n if (typeof value !== \"string\" || !allowed.includes(value as T)) {\n fail(path, `expected one of ${allowed.join(\", \")}`);\n }\n return value as T;\n}\n\nconst ACTIVITIES: readonly Activity[] = [\"running\", \"stopped\"];\nconst DRIVERS: readonly Driver[] = [\"human\", \"orchestrated\"];\nconst KINDS: readonly AttentionKind[] = [\"done\", \"blocked\", \"crashed\"];\n\nconst TOP_KEYS = [\n \"murmur_snapshot\",\n \"host_id\",\n \"display_name\",\n \"murmur_version\",\n \"generated_at\",\n \"panes\",\n] as const;\nconst PANE_KEYS = [\n \"pane\",\n \"session\",\n \"window\",\n \"session_name\",\n \"window_name\",\n \"agent\",\n \"attention\",\n] as const;\nconst AGENT_KEYS = [\n \"agent_id\",\n \"activity\",\n \"agent_name\",\n \"pi_session\",\n \"workstream\",\n \"role\",\n \"cli\",\n \"driver\",\n \"claimed_at\",\n \"updated_at\",\n] as const;\nconst ATTENTION_KEYS = [\"kind\", \"message\", \"source\", \"requested_at\"] as const;\n\nfunction parseAgent(value: unknown, path: string): SnapshotAgent | null {\n if (value === null) return null;\n const row = object(value, path, AGENT_KEYS);\n return {\n agent_id: text(row.agent_id, `${path}.agent_id`),\n activity: member(row.activity, `${path}.activity`, ACTIVITIES),\n agent_name: textOrNull(row.agent_name, `${path}.agent_name`),\n pi_session: textOrNull(row.pi_session, `${path}.pi_session`),\n workstream: textOrNull(row.workstream, `${path}.workstream`),\n role: textOrNull(row.role, `${path}.role`),\n cli: text(row.cli, `${path}.cli`),\n driver: member(row.driver, `${path}.driver`, DRIVERS),\n claimed_at: timestamp(row.claimed_at, `${path}.claimed_at`),\n updated_at: timestamp(row.updated_at, `${path}.updated_at`),\n };\n}\n\nfunction parseAttention(value: unknown, path: string): SnapshotAttention[] {\n if (!Array.isArray(value)) fail(path, \"expected an array\");\n const seen = new Set<AttentionKind>();\n return value.map((entry, index) => {\n const at = `${path}[${index}]`;\n const row = object(entry, at, ATTENTION_KEYS);\n const kind = member(row.kind, `${at}.kind`, KINDS);\n if (seen.has(kind)) fail(`${at}.kind`, `duplicate kind ${kind} for this pane`);\n seen.add(kind);\n return {\n kind,\n message: anyText(row.message, `${at}.message`),\n source: anyText(row.source, `${at}.source`),\n requested_at: timestamp(row.requested_at, `${at}.requested_at`),\n };\n });\n}\n\nfunction parsePane(value: unknown, path: string): SnapshotPane {\n const row = object(value, path, PANE_KEYS);\n const agent = parseAgent(row.agent, `${path}.agent`);\n const attention = parseAttention(row.attention, `${path}.attention`);\n // Rule 3 of the document schema: a pane with neither is not a pane worth\n // publishing, so a document carrying one is malformed rather than merely\n // noisy.\n if (agent === null && attention.length === 0) {\n fail(path, \"a pane with no agent and no attention must not be emitted\");\n }\n return {\n pane: asPaneId(text(row.pane, `${path}.pane`)),\n session: asSessionId(text(row.session, `${path}.session`)),\n window: asWindowId(text(row.window, `${path}.window`)),\n session_name: textOrNull(row.session_name, `${path}.session_name`),\n window_name: textOrNull(row.window_name, `${path}.window_name`),\n agent,\n attention,\n };\n}\n\n/**\n * Parse and totally validate one snapshot document.\n *\n * `murmur_snapshot` must be exactly 1: a higher value is rejected too, because\n * forward compatibility is not offered here and a version mismatch is an\n * operator-visible pairing problem. Saying so is the honest report; guessing at\n * a newer document's meaning is not.\n */\nexport function parseSnapshot(input: string): Snapshot {\n let parsed: unknown;\n try {\n parsed = JSON.parse(input);\n } catch (error) {\n fail(\"\", `not JSON (${error instanceof Error ? error.message : String(error)})`);\n }\n const top = object(parsed, \"\", TOP_KEYS);\n if (top.murmur_snapshot !== 1) {\n fail(\"murmur_snapshot\", `expected 1, got ${JSON.stringify(top.murmur_snapshot)}`);\n }\n if (!Array.isArray(top.panes)) fail(\"panes\", \"expected an array\");\n\n const panes = top.panes.map((entry, index) => parsePane(entry, `panes[${index}]`));\n const seen = new Set<string>();\n for (const pane of panes) {\n if (seen.has(pane.pane)) fail(\"panes\", `duplicate pane ${pane.pane}`);\n seen.add(pane.pane);\n }\n\n return {\n murmur_snapshot: 1,\n host_id: text(top.host_id, \"host_id\"),\n display_name: text(top.display_name, \"display_name\"),\n murmur_version: text(top.murmur_version, \"murmur_version\"),\n generated_at: timestamp(top.generated_at, \"generated_at\"),\n panes,\n };\n}\n","import type { Channel } from \"./channel.js\";\nimport { type Mux, tmux } from \"./mux.js\";\nimport { parseSnapshot, SnapshotInvalidError } from \"./snapshot.js\";\nimport type { Store } from \"./store.js\";\nimport { STALENESS_MS } from \"./view.js\";\n\nexport { STALENESS_MS };\n\n// A reachable peer is cheap — milliseconds on a warm control socket, still\n// only a couple hundred cold. The cap is not about those.\n//\n// It is about the unreachable ones. Each in-flight peer is a forked ssh client\n// process, and a peer that is asleep or off the VPN holds that process for the\n// full ConnectTimeout. Unbounded fan-out over a long list puts every one of\n// them resident at once, which is process churn and file descriptors spent on\n// hosts that were never going to answer.\n//\n// Eight keeps the realistic fleet fully parallel while bounding that.\nexport const MAX_CONCURRENT_PEERS = 8;\n\n// The cap alone does not bound the collect. The per-peer ssh timeout applies\n// once per wave, so nine unreachable peers cost two waves: the pool serialises\n// the timeouts it exists to limit. So the whole collect gets its own deadline,\n// independent of peer count. Peers still in flight when it expires are\n// abandoned and render stale, which is already the designed outcome for a host\n// that did not answer in time.\n//\n// Four seconds: under a 5s tick, and above one full wave (a 3s exec ceiling\n// plus overhead) so a single wave is never cut short by the deadline itself.\nconst COLLECT_DEADLINE_MS = 4_000;\n\n/**\n * Runs `task` over `items` with at most `limit` in flight, preserving input\n * order in the output. Workers pull from a shared cursor rather than running\n * fixed batches, so one slow peer occupies a single slot instead of holding a\n * batch boundary.\n *\n * `deadline` bounds the whole run, not each task. Once it passes, workers stop\n * claiming new items and anything unstarted is left `undefined` for the caller\n * to treat as \"did not answer\". Tasks already in flight are not cancelled --\n * there is nothing to cancel a forked ssh with here -- but they no longer hold\n * the collect open, because the deadline races the pool rather than joining it.\n */\nasync function mapSettled<T, R>(\n items: readonly T[],\n limit: number,\n task: (item: T) => Promise<R>,\n deadline?: Promise<void>,\n): Promise<(PromiseSettledResult<R> | undefined)[]> {\n const results = new Array<PromiseSettledResult<R> | undefined>(items.length);\n let cursor = 0;\n let expired = false;\n const stop = deadline?.then(() => {\n expired = true;\n });\n const worker = async () => {\n while (cursor < items.length && !expired) {\n const index = cursor++;\n try {\n results[index] = { status: \"fulfilled\", value: await task(items[index] as T) };\n } catch (reason) {\n results[index] = { status: \"rejected\", reason };\n }\n }\n };\n const pool = Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));\n await (stop ? Promise.race([pool, stop]) : pool);\n return results;\n}\n\nexport type CollectResult = {\n peer: string;\n ok: boolean;\n /** Panes in the snapshot we just stored. Zero is a normal, valid answer. */\n panes: number;\n error?: string;\n /**\n * True when the peer could not be reached at all, as opposed to answering\n * with something wrong.\n *\n * A fleet normally has nodes that are asleep or switched off, so this is the\n * expected outcome rather than a fault, and callers use it to stay quiet\n * about the ordinary case while still reporting a peer that is reachable but\n * broken -- a bad snapshot version, a missing binary, an auth problem.\n */\n unreachable?: boolean;\n};\n\n/**\n * Whether an error means \"could not reach the host\".\n *\n * ssh exits 255 for its own failures and prints a recognisable line, and the\n * exec wrapper puts both in the message. Matching on the text is unpleasant but\n * it is the only signal available: the channel seam returns an Error, not an\n * exit status.\n *\n * `Permission denied` is deliberately NOT here. An auth misconfiguration is\n * reachable-but-broken and an operator task; classing it as \"asleep, probably\"\n * is how a fixable setup error stays invisible for weeks.\n */\nfunction isUnreachable(message: string): boolean {\n return (\n /Host is down|No route to host|Connection refused|Connection timed out|Connection closed|Operation timed out|Network is unreachable|Name or service not known|Could not resolve hostname|timed out after/i.test(\n message,\n ) || /\\bssh:/.test(message)\n );\n}\n\n/**\n * Drop Node's `Command failed: <argv>` first line, keeping the child's output.\n *\n * Every rejection from the ssh channel arrives in that shape, so the first ~140\n * characters of every real failure are the invocation murmur chose: `ssh -o\n * BatchMode=yes -o ControlMaster=no -o ControlPath=... -o ConnectTimeout=1\n * <host> murmur export`. The operator cannot act on any of it, and it pushed the\n * one line that mattered past the length bound below -- measured against a real\n * second node, where a missing remote binary printed\n * `bubba: Command failed: ssh -o BatchMode=yes ... murmur: command not f...`,\n * truncated on the only actionable word in it.\n *\n * Stripped BEFORE the newlines are collapsed, because the line boundary is the\n * only thing separating the invocation from the diagnosis.\n */\nfunction stripInvocation(message: string): string {\n const firstLine = message.indexOf(\"\\n\");\n if (firstLine === -1 || !message.startsWith(\"Command failed:\")) return message;\n const rest = message.slice(firstLine + 1).trim();\n // A bare `Command failed:` line with nothing after it is all we have; saying\n // nothing would be worse than saying too much.\n return rest === \"\" ? message : rest;\n}\n\n/**\n * The one normalisation, so classification and rendering cannot disagree.\n *\n * `unreachable` (a machine-readable flag on `CollectResult`) and\n * `describeFailure` (the line a human reads) both classify with\n * `isUnreachable`. Feeding them differently-normalised text is how a peer gets\n * reported as reachable-but-broken in JSON and \"unreachable\" in print, about\n * one fetch -- so both go through here.\n */\nfunction normalizeFailure(message: string): string {\n return stripInvocation(message).replace(/\\s+/g, \" \").trim();\n}\n\n/**\n * A peer failure in one line a human can act on.\n *\n * The raw error was the whole ssh invocation plus ssh's own message -- over 200\n * characters, of which the actionable part was the host name. It also leaked\n * every ssh option murmur passes, which a user cannot do anything about.\n */\nexport function describeFailure(peer: string, message: string): string {\n const collapsed = normalizeFailure(message);\n if (isUnreachable(collapsed)) {\n const reason = /ssh: (?:connect to host \\S+ port \\d+: )?(.+?)(?: \\(|$)/i.exec(collapsed);\n return `${peer}: unreachable (${(reason?.[1] ?? \"ssh failed\").trim()})`;\n }\n // Reachable but wrong: keep the message, since it is the diagnosis, but bound\n // it so a corrupt snapshot cannot print a screenful.\n const detail = collapsed.length > 160 ? `${collapsed.slice(0, 157)}...` : collapsed;\n return `${peer}: ${detail}`;\n}\n\n/**\n * Fetch every peer's snapshot, validate it, and replace the cache whole.\n *\n * Concurrent because an unreachable peer costs the full ssh timeout, and a\n * serial loop charged that to every peer behind it: three asleep laptops made\n * `murmur status` hang for thirty seconds. Applied serially in peer order,\n * because better-sqlite3 is synchronous and a stable order keeps the result list\n * aligned with `store.peers()`.\n *\n * One round trip per peer, and never a second: the document is complete, so what\n * arrives either replaces the cache entirely or does not touch it.\n */\nexport async function collect(\n store: Store,\n channel: Channel,\n now = Date.now(),\n deadline?: Promise<void>,\n mux: Mux = tmux,\n): Promise<CollectResult[]> {\n const results: CollectResult[] = [];\n let timer: NodeJS.Timeout | undefined;\n try {\n const peers = store.peers();\n // Default deadline, injectable so tests do not have to wait out real time.\n // Unref'd: a pending timer must not hold the process open after a CLI\n // command has printed its output and finished.\n const bounded =\n deadline ??\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, COLLECT_DEADLINE_MS);\n timer.unref?.();\n });\n // Settled, not raw: a peer that fails while we are still applying an\n // earlier one would otherwise be an unhandled rejection for as long as it\n // sits in the queue.\n const fetches = await mapSettled(\n peers,\n MAX_CONCURRENT_PEERS,\n async (peer) => parseSnapshot(await channel.exec(peer.target, [\"murmur\", \"export\"])),\n bounded,\n );\n for (const [index, peer] of peers.entries()) {\n const fetch = fetches[index];\n try {\n // Undefined means the deadline passed before this peer was claimed or\n // finished. Not an error about the peer, so it says so plainly and\n // leaves fetched_at alone: the peer goes stale, which is the designed\n // outcome for a host that did not answer in time.\n if (!fetch) throw new Error(\"collect deadline passed before this peer answered\");\n if (fetch.status === \"rejected\") throw fetch.reason;\n // Every field the cache derives comes out of the document itself, so\n // the cache structurally cannot disagree with the snapshot it holds.\n store.replacePeerSnapshot(peer.name, { ok: true, snapshot: fetch.value, at: now });\n results.push({ peer: peer.name, ok: true, panes: fetch.value.panes.length });\n } catch (error) {\n // Normalised ONCE, here, before it is stored or returned.\n //\n // `last_error` is read by `peer list`, by `status --json` and by\n // anything built on the SDK, and none of them can undo the mangling: a\n // raw `execFile` rejection leads with `Command failed: ssh -o\n // BatchMode=yes -o ControlMaster=no -o ControlPath=... <host> murmur\n // export`, which is murmur's own invocation and nothing an operator can\n // act on. Measured against a real second node, where `peer list`\n // printed 140 characters of ssh options before the four words that\n // mattered. Storing the normalised text means every surface gets the\n // diagnosis without each one having to remember to strip it.\n const message = normalizeFailure(error instanceof Error ? error.message : String(error));\n store.replacePeerSnapshot(peer.name, { ok: false, error: message, at: now });\n // Reported through the return value, never printed here. `collect` runs\n // from `murmur status` on every status-bar tick, and from `pick` inside\n // a display-popup, so a single sleeping laptop wrote to stderr forever\n // and corrupted both. Only the `collect` command -- which a human ran\n // on purpose -- prints.\n results.push({\n peer: peer.name,\n ok: false,\n panes: 0,\n error: message,\n // A peer that answered with a bad document is reachable but broken,\n // and must be visibly so rather than silently stale.\n unreachable:\n error instanceof SnapshotInvalidError\n ? false\n : isUnreachable(normalizeFailure(message)),\n });\n }\n }\n } catch (error) {\n // The whole collect failed rather than one peer -- a broken peer table, say.\n // Still not printed: the caller decides.\n results.push({\n peer: \"\",\n ok: false,\n panes: 0,\n error: error instanceof Error ? error.message : String(error),\n });\n } finally {\n clearTimeout(timer);\n }\n\n // The only housekeeping left, and it runs once per invocation including with\n // zero peers -- which is why it is here rather than on `export`, which only\n // runs when a peer asks over ssh. A single-machine node would otherwise\n // reconcile never. Idempotent, so `buildLocalSnapshot` calling it too is a\n // cheap repeat rather than a second policy.\n try {\n store.reconcileLocal({ panes: mux.livePanes(), now });\n } catch {\n // Housekeeping must not fail a command, and it must not report either.\n }\n return results;\n}\n","import { randomUUID } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { hostname } from \"node:os\";\nimport { join } from \"node:path\";\nimport { stateDir } from \"./paths.js\";\n\nexport type NodeIdentity = {\n host_id: string;\n display_name: string;\n};\n\nfunction identityPath(): string {\n return join(stateDir(), \"identity.json\");\n}\n\n/**\n * Memoized per process, keyed on the resolved path.\n *\n * `identity.json` cannot change under a running command, and the audit measured\n * eight redundant reads per invocation. Keyed on the path rather than a bare\n * boolean so a test that repoints `MURMUR_STATE_DIR` mid-process is not served\n * another directory's identity.\n */\nlet cache: { path: string; identity: NodeIdentity | null } | null = null;\n\n/**\n * This node's identity, or null when it has none.\n *\n * A READ, and only a read: nothing mints here. Every command that needs a\n * host_id fails with \"murmur is not initialised on this node; run: murmur init\"\n * rather than bringing a node into existence as a side effect of a status-bar\n * tick.\n */\nexport function loadIdentity(): NodeIdentity | null {\n const path = identityPath();\n if (cache?.path === path) return cache.identity;\n const identity = existsSync(path)\n ? (JSON.parse(readFileSync(path, \"utf8\")) as NodeIdentity)\n : null;\n cache = { path, identity };\n return identity;\n}\n\nfunction write(identity: NodeIdentity): NodeIdentity {\n mkdirSync(stateDir(), { recursive: true });\n writeFileSync(identityPath(), `${JSON.stringify(identity, null, 2)}\\n`);\n cache = { path: identityPath(), identity };\n return identity;\n}\n\n/** Create this node's identity. Only `murmur init` calls it. */\nexport function createIdentity(displayName = hostname()): NodeIdentity {\n if (loadIdentity()) throw new Error(`identity already exists: ${identityPath()}`);\n return write({ host_id: randomUUID(), display_name: displayName });\n}\n\n/**\n * Rename an existing node, keeping its `host_id`.\n *\n * `murmur init --name` on an already-initialised node used to ignore the flag\n * silently, which is the one thing a rename must not do.\n */\nexport function setDisplayName(displayName: string): NodeIdentity {\n const existing = loadIdentity();\n return write(\n existing\n ? { host_id: existing.host_id, display_name: displayName }\n : { host_id: randomUUID(), display_name: displayName },\n );\n}\n","import { loadIdentity, type NodeIdentity } from \"../identity.js\";\n\n/**\n * This node's identity, or null after printing why not.\n *\n * Every command that needs a `host_id` -- export, collect, status, pick, peer --\n * fails here rather than minting one, because a node that came into existence as\n * a side effect of a status-bar tick has an identity nobody chose. `notify` and\n * `clear` are absent from that list as a consequence of the model rather than as\n * an exemption: both address a pane, and attention is keyed on pane alone.\n */\nexport function requireIdentity(): NodeIdentity | null {\n const identity = loadIdentity();\n if (identity) return identity;\n process.stderr.write(\"murmur is not initialised on this node; run: murmur init\\n\");\n process.exitCode = 1;\n return null;\n}\n","import type { Command } from \"commander\";\nimport { ssh } from \"../channel.js\";\nimport { collect, describeFailure } from \"../collector.js\";\nimport { openStore } from \"../store.js\";\nimport { requireIdentity } from \"./identity-guard.js\";\n\nexport function registerCollect(program: Command): void {\n program\n .command(\"collect\")\n .description(\"Fetch each peer's snapshot\")\n .option(\"-q, --quiet\", \"report nothing, not even unreachable peers\")\n .action(async (options: { quiet?: boolean }) => {\n if (!requireIdentity()) return;\n const store = openStore();\n try {\n const results = await collect(store, ssh);\n if (options.quiet) return;\n\n // The ONLY place a peer failure is printed. `collect` is run by a human\n // or a timer that wants the answer, unlike `status` (every status-bar\n // tick) and `pick` (inside a display-popup), both of which used to print\n // the same thing and could not stop.\n //\n // One line per peer, on stderr so a caller can still parse stdout, and\n // never a stack or an ssh command line.\n for (const result of results) {\n if (result.ok || !result.error) continue;\n process.stderr.write(`murmur: ${describeFailure(result.peer, result.error)}\\n`);\n }\n\n // A summary only when something is wrong, and only for the case a human\n // can act on. An unreachable node is the normal state of a fleet -- a\n // laptop asleep, a box switched off -- so it is reported per peer above\n // and not counted as a failure here.\n if (results.some((result) => !result.ok && !result.unreachable)) {\n process.exitCode = 1;\n }\n } finally {\n store.close();\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { tmux } from \"../mux.js\";\nimport { openStore } from \"../store.js\";\nimport { requireIdentity } from \"./identity-guard.js\";\n\nexport function registerExport(program: Command): void {\n program\n .command(\"export\")\n // No options, and none to add: the document is complete, so a peer that\n // returns one has said everything it knows and absence in it is absence.\n // There is nothing narrower for a caller to ask for.\n .description(\"Print this node's current-state snapshot\")\n .action(() => {\n const identity = requireIdentity();\n if (!identity) return;\n const store = openStore();\n try {\n // `buildLocalSnapshot` reconciles first, which is what makes the\n // document authoritative: a snapshot built from unreconciled rows would\n // publish agents whose panes are gone, and a reader has no way to tell.\n const snapshot = store.buildLocalSnapshot(identity, { panes: tmux.livePanes() });\n process.stdout.write(`${JSON.stringify(snapshot)}\\n`);\n } finally {\n store.close();\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { createIdentity, loadIdentity, setDisplayName } from \"../identity.js\";\n\nexport function registerInit(program: Command): void {\n program\n .command(\"init\")\n .description(\"Initialize this node's identity\")\n .option(\"--name <name>\", \"display name\")\n .action((opts: { name?: string }) => {\n // `--name` on an already-initialised node RENAMES it, keeping the host_id.\n // It used to be ignored silently, which is the one thing a rename must not\n // do: the operator's only feedback was the old name printed back.\n const existing = loadIdentity();\n const identity = existing\n ? opts.name\n ? setDisplayName(opts.name)\n : existing\n : createIdentity(opts.name);\n console.log(`host_id: ${identity.host_id}`);\n console.log(`display_name: ${identity.display_name}`);\n });\n}\n","import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { Command } from \"commander\";\nimport { loadIdentity } from \"../identity.js\";\n\n/**\n * The installed extension, as a one-line re-export of this installation.\n *\n * `link pi` used to copy the whole built extension into\n * `~/.pi/agent/extensions/murmur.ts`. That made the copy a point-in-time\n * snapshot: upgrading murmur left the OLD extension running, with no warning\n * and nothing to compare against. The author's own machine was running an\n * extension missing two fixes that were already committed, which is exactly the\n * class of bug the extension exists to prevent -- a silently wrong state\n * report.\n *\n * A shim inverts that. The file pi loads never changes, and the code it points\n * at is whatever the current install has, so `npm install -g` is the whole\n * upgrade. The path is stable across upgrades because npm replaces a package's\n * contents in place rather than versioning the directory.\n *\n * Kept to one line on purpose: any logic here is logic that cannot be upgraded.\n */\n/**\n * Identifies a generated shim, so re-linking can tell it from an older inlined\n * copy. Keyed on its own line rather than on the import statement: the shim's\n * import style already changed once (static re-export to dynamic, to fix ESM\n * hoisting), and that silently broke this check -- re-linking a shim reported\n * \"replaced an inlined copy\".\n */\nconst SHIM_MARKER = \"// murmur:shim\";\n\nfunction shim(entry: string, storePath: string): string {\n return `${SHIM_MARKER}\n// Generated by \\`murmur link pi\\`. Do not edit.\n//\n// A re-export, not a copy: the extension code lives in the murmur install, so\n// upgrading murmur upgrades the extension with no reinstall step. Re-run\n// \\`murmur link pi\\` only if the install path itself moves.\n//\n// The store path is set here rather than resolved by the extension. A bare\n// specifier cannot resolve from ~/.pi/agent/extensions, and the failure is\n// silent: the import throws, the extension swallows it, and every state report\n// is dropped while the tmux badge still paints.\n//\n// A dynamic import, not \\`export ... from\\`: ESM hoists static re-exports above\n// this assignment, so the extension loaded before the variable was set and read\n// undefined. Verified -- the static form printed \\`undefined\\` in the target.\nprocess.env.MURMUR_STORE_MODULE ??= ${JSON.stringify(storePath)};\n\nconst { default: extension } = await import(${JSON.stringify(entry)});\nexport default extension;\n`;\n}\n\nexport function registerLink(program: Command): void {\n program\n .command(\"link\")\n .description(\"Install a murmur integration\")\n .argument(\"<target>\", \"integration to install\")\n .option(\n \"--copy\",\n \"inline the extension instead of re-exporting it (pins to this version; needs re-linking after an upgrade)\",\n )\n .action((target: string, options: { copy?: boolean }) => {\n if (target !== \"pi\") throw new Error(`unsupported link target: ${target}`);\n const destination = join(\n process.env.MURMUR_PI_HOME ?? homedir(),\n \".pi\",\n \"agent\",\n \"extensions\",\n \"murmur.ts\",\n );\n mkdirSync(dirname(destination), { recursive: true });\n\n const entry = fileURLToPath(new URL(\"./extension/murmur-pi.js\", import.meta.url));\n const storePath = fileURLToPath(new URL(\"./extension/store.js\", import.meta.url));\n\n // Identity is `init`-generated state by design, and the extension reads\n // it with loadIdentity rather than creating one -- an agent must not\n // decide what this node is called. But the consequence is silent: with no\n // identity the extension loads, registers its handlers, and reports\n // nothing, while the tmux badge still paints. Linking is the moment to say\n // so, since it is the only time a human is looking at this path.\n const identityMissing = loadIdentity() === null;\n\n if (!options.copy) {\n // Say when this replaced an inlined copy. Anyone linked before the shim\n // existed has a snapshot that stopped tracking upgrades silently, and\n // \"wrote a file\" does not tell them their agents may have been\n // misreporting. Best effort: a missing or unreadable file is the normal\n // first-install case and says nothing.\n let replacedCopy = false;\n try {\n const existing = readFileSync(destination, \"utf8\");\n replacedCopy = !existing.includes(SHIM_MARKER);\n } catch {\n // No previous install.\n }\n writeFileSync(destination, shim(entry, storePath));\n console.log(destination);\n if (replacedCopy) {\n console.log(\n \"Replaced an inlined copy from an older murmur. That copy was pinned to the version that wrote it, so it had stopped picking up fixes; running agents keep the old code until they restart.\",\n );\n }\n if (identityMissing) {\n console.log(\n \"This node has no identity yet, so the extension will record nothing. Run: murmur init\",\n );\n }\n return;\n }\n\n // The copy path, kept for the case the shim cannot serve: an extension\n // that has to keep working when the murmur install is gone or moved.\n //\n // Pin the store import to this installation's absolute path. The\n // extension lives in ~/.pi/agent/extensions, where a bare\n // \"@martintrojer/murmur/extension-store\" specifier cannot resolve — not\n // even for a global install. Unpinned, every append silently no-ops:\n // the tmux badge still paints, so nothing looks broken while the log\n // stays empty and the node exports nothing.\n const source = readFileSync(entry, \"utf8\");\n const pinned = source.replace(\n /\"@martintrojer\\/murmur\\/extension-store\"/,\n JSON.stringify(storePath),\n );\n if (pinned === source) {\n throw new Error(\"link pi: could not pin the store import; extension build changed\");\n }\n writeFileSync(destination, pinned);\n console.log(destination);\n if (identityMissing) {\n console.log(\n \"This node has no identity yet, so the extension will record nothing. Run: murmur init\",\n );\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { asPaneId } from \"../ids.js\";\nimport { type Mux, tmux } from \"../mux.js\";\nimport { openStore, type Store } from \"../store.js\";\nimport type { Location } from \"../types.js\";\n\n/**\n * The fields a harness may send, as flags or as a JSON object on stdin.\n *\n * Both forms exist because the two consumers differ: the codex hook line passes\n * flags, opencode's plugin pipes JSON. Same four fields either way.\n *\n * Note the spelling mismatch, which is not ours to fix: the payload calls it\n * `type`, the flag is `--event-type`. Both consumers are already written against\n * those exact names, and this verb exists to keep them working.\n */\ntype NotifyInput = {\n source?: string;\n title?: string;\n eventType?: string;\n message?: string;\n};\n\ntype NotifyPayload = Record<string, unknown>;\n\n/**\n * Resolve the four fields, flags beating the stdin payload.\n *\n * Flags win so the codex hook line behaves identically whether or not something\n * also arrives on stdin, which is what both consumers were written against.\n *\n * `message` falls back through title then event type before the generic\n * \"attention\": a notification whose text is a bare placeholder is worse than\n * one carrying whatever the harness did manage to say.\n */\nexport function notifyFields(\n input: NotifyInput,\n payload: NotifyPayload = {},\n): { source: string; message: string } {\n const field = (key: string, flag: string | undefined): string => {\n if (flag) return clean(flag);\n const value = payload[key];\n return typeof value === \"string\" ? clean(value) : \"\";\n };\n\n const source = field(\"source\", input.source) || \"agent\";\n const title = field(\"title\", input.title);\n const eventType = field(\"type\", input.eventType);\n const message = field(\"message\", input.message) || title || eventType || \"attention\";\n return { source, message };\n}\n\n/**\n * Strip control characters and collapse whitespace.\n *\n * This text reaches a tmux status line and a picker row, and it arrives from\n * another program's event payload. An embedded newline or escape sequence would\n * corrupt both surfaces, and `terminalText` in agents.ts exists for the same\n * reason on the read side -- this is the write side of that rule.\n */\nfunction clean(value: string): string {\n // Char codes, not a character class, and biome's noControlCharactersInRegex is\n // right to insist: an invisible byte in a pattern is a hazard, and the rule\n // fired here. `terminalText` in agents.ts avoids it the same way for the same\n // reason -- that is the read side of this rule, this is the write side.\n //\n // Replaced with a space rather than dropped, so \"line one\\nline two\" does not\n // become \"line oneline two\"; the collapse below then tidies the run.\n const flattened = [...value]\n .map((character) => {\n const code = character.charCodeAt(0);\n const control = code < 0x20 || code === 0x7f || (code >= 0x80 && code <= 0x9f);\n return control ? \" \" : character;\n })\n .join(\"\");\n return flattened.replace(/\\s+/g, \" \").trim();\n}\n\n/** Read a JSON object from stdin, or nothing. */\nexport function parsePayload(raw: string): NotifyPayload {\n if (!raw.trim()) return {};\n try {\n const parsed = JSON.parse(raw) as unknown;\n // An array or a scalar is not a payload. Ignored rather than rejected: a\n // notifier that pipes something odd should still get its attention row,\n // because the flags may carry everything needed.\n return typeof parsed === \"object\" && parsed !== null && !Array.isArray(parsed)\n ? (parsed as NotifyPayload)\n : {};\n } catch {\n return {};\n }\n}\n\n/**\n * Request `blocked` attention for a pane, on behalf of a harness that cannot\n * report itself.\n *\n * WHY THIS EXISTS. pi reports from inside itself, through the extension. codex\n * and opencode have no such hook -- they can only run a command when something\n * happens. `murmur notify` is that command; without it those two harnesses never\n * show `blocked`, and because the status bar keeps working for pi agents, nothing\n * looks broken.\n *\n * WHAT IT CANNOT DO, structurally. The only thing it may write is an\n * `AttentionRequest`, which has no field for an agent_id, an owner_pid, an\n * activity, or any owner metadata. `attention` is keyed on (pane, kind) and the\n * agents table is untouched by every statement this path runs, so a notifier\n * corrupting a live agent's row is unsayable rather than merely guarded against.\n *\n * `blocked` only, hard-coded: an external process cannot know that an agent\n * started, finished or crashed, so those stay the owner's alone. A harness can\n * request attention and nothing else.\n *\n * NO IDENTITY IS NEEDED, which follows from the model rather than being an\n * exemption: attention is addressed by pane, and a pane needs no host_id to name\n * it. So this cannot fail for want of `murmur init`.\n *\n * And the pane comes from the harness's own environment. The codex and opencode\n * hooks run as children of the agent process, in its pane, so $TMUX_PANE names\n * exactly the pane whose agent wants attention. `--pane` overrides it for a\n * notifier that runs elsewhere.\n */\nexport function runNotify(\n store: Store,\n input: NotifyInput & { pane?: string },\n payload: NotifyPayload = {},\n mux: Mux = tmux,\n): boolean {\n const location = resolveLocation(input.pane, mux);\n // No tmux and no pane. Silent and successful, because this runs from another\n // program's notification hook: a harness used outside tmux must not have its\n // own exit code broken by murmur having nothing to record.\n if (!location) return false;\n\n const { source, message } = notifyFields(input, payload);\n store.requestAttention({\n kind: \"blocked\",\n location,\n message,\n // The harness name goes here, not in `driver`. `driver` answers \"who is\n // waiting on this agent\" -- a human, or a supervisor consuming the result --\n // and a codex agent driven by a human is `human` on exactly that question.\n // `source` answers \"who asked\", which is the free-text field a new harness\n // needs no schema change for.\n source,\n });\n\n // The badge, so the status bar reflects it without waiting for a collect.\n mux.setWindowBadge(location.window, \"blocked\");\n return true;\n}\n\n/**\n * The pane this notification is about: the flag, else the caller's own pane.\n *\n * The no-flag path is the one both real consumers take, and the only one either\n * has ever used -- checked against the codex hook line and the opencode plugin,\n * neither of which passes a pane. Their hooks run as children of the agent\n * process, so `$TMUX_PANE` -- which `currentWindow` reads, and which tmux sets\n * for every process in a pane -- names exactly the pane whose agent wants\n * attention.\n *\n * `--pane` exists for a notifier that runs outside the pane it is reporting on,\n * and is deliberately implemented WITHOUT adding a pane-to-session lookup to the\n * Mux interface. `currentWindow` already resolves\n * a full location for the caller's own pane, and `--pane` is only meaningful\n * when it names a pane in the same tmux server, so the flag narrows an existing\n * answer rather than fetching a new one:\n *\n * - naming your own pane is the common case and resolves identically\n * - naming a DIFFERENT pane in the same window keeps that window's location,\n * which is correct, since session and window are exactly what the two panes\n * share\n * - naming a pane in another window returns null rather than guessing, because\n * recording a location this process cannot verify is how a row nothing can\n * clear gets written\n *\n * If a real consumer ever needs the third case, that is when the Mux interface\n * should grow a lookup -- not on speculation.\n */\nfunction resolveLocation(pane: string | undefined, mux: Mux): Location | null {\n const here = mux.currentWindow();\n if (!pane) return here;\n const target = asPaneId(pane);\n if (here && here.pane === target) return here;\n if (here && mux.panesInWindow(here.window).includes(target)) {\n return { ...here, pane: target };\n }\n return null;\n}\n\nexport function registerNotify(program: Command): void {\n program\n .command(\"notify\")\n .description(\"Record an attention request for a harness that cannot report itself\")\n .option(\"--source <name>\", \"harness name, e.g. codex or opencode\")\n .option(\"--event-type <type>\", \"why attention is wanted\")\n .option(\"--title <title>\", \"harness display title\")\n .option(\"--message <message>\", \"the text to show\")\n .option(\"--pane <pane>\", \"pane to notify about (default: $TMUX_PANE)\")\n .action(\n async (options: {\n source?: string;\n eventType?: string;\n title?: string;\n message?: string;\n pane?: string;\n }) => {\n const payload = parsePayload(await readStdin());\n const store = openStore();\n try {\n runNotify(store, options, payload);\n } finally {\n store.close();\n }\n },\n );\n}\n\n/** How long to wait for a piped payload before proceeding on flags alone. */\nconst STDIN_DEADLINE_MS = 250;\n\n/**\n * Whatever is on stdin, or \"\" when nothing arrives in time.\n *\n * BOUNDED, and that is a bug fix rather than caution. `isTTY` catches a notifier\n * run from a terminal, but it says nothing about a non-TTY stdin that never\n * closes -- an inherited pipe the parent created and never writes to, which is\n * the ordinary shape of a plugin host spawning a hook without redirecting\n * stdin. Reading to EOF then waits for an EOF that never comes:\n *\n * sleep 30 | murmur notify --source codex # hung; exit 124 under timeout\n *\n * A hung notify hook is a bad failure: it is a child of the agent process, it\n * holds a store handle, a harness that waits on its hook stalls, and its output\n * goes nowhere so nothing says why. The flags are already sufficient for every\n * documented consumer, so a deadline degrades to exactly the flags-only\n * behaviour codex relies on today.\n *\n * Two details stop the deadline becoming a different hang. The `data` listener\n * is removed BY REFERENCE, because a live handler keeps the stream referenced;\n * and the stream is `unref`ed rather than paused, because `pause()` stops the\n * flow while leaving the handle on the event loop. Verified with\n * `process._getActiveHandles()`, which still reported a `Socket` after a paused\n * read -- the work completed, the row was written, and the process still would\n * not exit. `unref` rather than `destroy`: this is declining to wait, not\n * tearing down a pipe the parent owns.\n */\nasync function readStdin(): Promise<string> {\n if (process.stdin.isTTY) return \"\";\n const chunks: Buffer[] = [];\n return new Promise<string>((resolve) => {\n const onData = (chunk: Buffer) => chunks.push(chunk);\n const done = () => {\n process.stdin.off(\"data\", onData);\n // Optional because only a PIPE is a Socket. Redirect stdin from a file or\n // /dev/null -- which `sh -lc` does, so this is the codex hook's own path --\n // and `process.stdin` is an fs ReadStream with no `unref` at all, so\n // calling it unconditionally threw TypeError and took the whole hook down.\n // Nothing is lost: a file or /dev/null reaches EOF on its own, and it is\n // only the never-ending pipe that needed releasing.\n process.stdin.unref?.();\n resolve(Buffer.concat(chunks).toString(\"utf8\"));\n };\n // Unreffed so the deadline itself cannot be what holds the process open.\n const timer = setTimeout(done, STDIN_DEADLINE_MS);\n timer.unref?.();\n process.stdin.on(\"data\", onData);\n process.stdin.once(\"end\", () => {\n clearTimeout(timer);\n done();\n });\n process.stdin.once(\"error\", () => {\n clearTimeout(timer);\n done();\n });\n });\n}\n","import { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Command } from \"commander\";\nimport { hasWarmSocket, ssh } from \"../channel.js\";\nimport { loadIdentity } from \"../identity.js\";\nimport { parseSnapshot } from \"../snapshot.js\";\nimport { openStore } from \"../store.js\";\nimport type { PeerRecord, Snapshot } from \"../types.js\";\nimport { age, freshness, STALENESS_MS } from \"../view.js\";\n\n/**\n * The snapshot document version this node speaks. One number, and the only one\n * the code enforces: `parseSnapshot` rejects anything else outright.\n */\nexport const SNAPSHOT_VERSION = 1;\n\nexport function parseSshHosts(config: string): string[] {\n const hosts: string[] = [];\n for (const line of config.split(\"\\n\")) {\n const tokens = line.replace(/#.*$/, \"\").trim().split(/\\s+/);\n if (tokens[0]?.toLowerCase() !== \"host\") continue;\n for (const host of tokens.slice(1)) {\n if (!/[*?!]/.test(host)) hosts.push(host);\n }\n }\n return hosts;\n}\n\nfunction sshHosts(): string[] {\n try {\n return parseSshHosts(readFileSync(join(homedir(), \".ssh\", \"config\"), \"utf8\"));\n } catch {\n return [];\n }\n}\n\n/**\n * Column-aligned plain text. Rows are all-ASCII here (peer names, ssh targets\n * and hostnames), so `length` is a fine width; trailing cells are not padded so\n * the output stays clean for `cut` and friends.\n */\n/**\n * How long since a peer last answered.\n *\n * `never` is deliberately distinct from an age: a peer that has never answered\n * is a setup problem (wrong target, murmur not installed there), while an old\n * age is an ordinary sleeping node. Uses the same fetched_at and threshold the\n * picker does, so the two cannot disagree about one peer.\n */\nexport function lastSeen(fetchedAt: number | null, now: number): string {\n if (fetchedAt === null) return \"never\";\n if (freshness(fetchedAt, now, STALENESS_MS) === \"fresh\") return \"just now\";\n return `${age(now - fetchedAt)} ago`;\n}\n\n/**\n * What to show in the VERSION column, and whether the pairing is a problem.\n *\n * The distinction is drawn from what the code actually enforces rather than from\n * taste:\n *\n * - a differing SNAPSHOT VERSION is a hard incompatibility. `parseSnapshot`\n * rejects any `murmur_snapshot` other than 1, so state genuinely does not\n * flow. That is a fact about behaviour, and it is the only thing marked.\n * - a differing murmur version is worth SHOWING and nothing more. Two nodes on\n * snapshot 1 running 0.1.3 and 0.2.0 interoperate fine; marking that would\n * cry wolf on every patch release and train the operator to ignore the\n * column that is supposed to mean something.\n *\n * A peer we have never heard from is `unknown` and is NOT a mismatch: absence of\n * information is not evidence of incompatibility, and a sleeping peer is the\n * common case here.\n */\nexport function versionCell(\n peer: Pick<PeerRecord, \"murmur_version\" | \"snapshot_version\">,\n ours = SNAPSHOT_VERSION,\n): { text: string; incompatible: boolean } {\n if (peer.murmur_version === null && peer.snapshot_version === null) {\n return { text: \"unknown\", incompatible: false };\n }\n // Answered, but from a build too old to say what it is. Distinct from never\n // having answered: this one is reachable and talking.\n const version = peer.murmur_version ?? \"unreported\";\n const incompatible = peer.snapshot_version !== null && peer.snapshot_version !== ours;\n // The number appears ONLY when it is the problem. In the normal case it is\n // noise on every row; in the abnormal case it is the whole explanation.\n return {\n text: incompatible ? `${version} (snapshot ${peer.snapshot_version} \\u2260 ${ours})` : version,\n incompatible,\n };\n}\n\nexport function formatTable(rows: string[][]): string {\n const widths: number[] = [];\n for (const row of rows) {\n row.forEach((cell, index) => {\n widths[index] = Math.max(widths[index] ?? 0, cell.length);\n });\n }\n return rows\n .map((row) =>\n row\n .map((cell, index) => (index === row.length - 1 ? cell : cell.padEnd(widths[index] ?? 0)))\n .join(\" \")\n .trimEnd(),\n )\n .map((line) => `${line}\\n`)\n .join(\"\");\n}\n\n/**\n * Whether `peer add` must refuse, and what to say. Returns null to proceed.\n *\n * Split out of the commander action because that action opens a store, shells\n * out over ssh and sets process.exitCode, so the rules below were unreachable\n * from a test: the suite ended up asserting a reimplementation of this lookup\n * instead, and disabling the real branch left it green.\n */\nexport function peerAddDecision(input: {\n name: string;\n target: string;\n snapshot: Snapshot | null;\n selfHostId: string | null;\n peers: PeerRecord[];\n}): string | null {\n const { name, target, snapshot, selfHostId, peers } = input;\n // No identity means an unreachable host. It is still added, on the operator's\n // word, and the first successful collect fills in who it is.\n if (!snapshot) return null;\n\n // Adding yourself would list this node's own panes twice and collect over ssh\n // to reach a database you already hold.\n if (snapshot.host_id === selfHostId) {\n return `${target} is this node; not adding it as a peer\\n`;\n }\n\n // One node, one peer. Two names for one host_id means two ssh round-trips per\n // command and the\n // same machine listed twice, so nothing looks wrong until you notice every\n // collect is doing double the work. Excluding `name` itself keeps re-adding the same\n // peer idempotent, which is how a target gets corrected.\n const existing = peers.find(\n (candidate) => candidate.host_id === snapshot.host_id && candidate.name !== name,\n );\n if (existing) {\n return (\n `${target} is already configured as peer \"${existing.name}\" ` +\n `(${snapshot.display_name}); remove it first to rename\\n`\n );\n }\n return null;\n}\n\nexport function registerPeer(program: Command): void {\n const peer = program.command(\"peer\").description(\"Manage peers\");\n\n peer\n .command(\"add\")\n .description(\"Add a peer and discover its identity\")\n // The decision itself is `peerAddDecision` below, so it can be tested\n // without an ssh binary or a commander harness.\n .argument(\"<name>\")\n .argument(\"[target]\")\n .action(async (name: string, target = name) => {\n const store = openStore();\n try {\n // Probe BEFORE writing. Identity is discovered, so the probe is what\n // tells us whether this is a node we already have under another name\n // — and a peer written first would be found by its own duplicate\n // check.\n let snapshot: Snapshot | null = null;\n try {\n // Bare `murmur export`: it takes no options, here or in the collector.\n snapshot = parseSnapshot(await ssh.exec(target, [\"murmur\", \"export\"]));\n } catch {\n snapshot = null;\n }\n\n const refusal = peerAddDecision({\n name,\n target,\n snapshot,\n selfHostId: loadIdentity()?.host_id ?? null,\n peers: store.peers(),\n });\n if (refusal) {\n process.stderr.write(refusal);\n process.exitCode = 1;\n return;\n }\n\n store.addPeer(name, target);\n // The probe already parsed a valid document, so recording it here means\n // `peer list` can name the host, its version and its snapshot version\n // immediately rather than after the first collect.\n if (snapshot) {\n store.replacePeerSnapshot(name, { ok: true, snapshot, at: Date.now() });\n }\n process.stdout.write(\n snapshot\n ? `Added ${name} (${snapshot.display_name})\\n`\n : `Added ${name} (identity pending)\\n`,\n );\n } finally {\n store.close();\n }\n });\n\n peer\n .command(\"remove\")\n .description(\"Remove a peer\")\n .argument(\"<name>\", \"peer to remove\")\n .action((name: string) => {\n const store = openStore();\n try {\n if (store.removePeer(name)) process.stdout.write(`Removed ${name}\\n`);\n else {\n process.stderr.write(`no such peer: ${name}\\n`);\n process.exitCode = 1;\n }\n } finally {\n store.close();\n }\n });\n\n peer\n .command(\"list\")\n .description(\"List peers; --all adds SSH hosts that could become peers\")\n .option(\"--json\", \"print JSON\")\n .option(\"-a, --all\", \"also show SSH hosts that are not peers yet\")\n .action((options: { json?: boolean; all?: boolean }) => {\n const store = openStore();\n try {\n // `list` and `discover` were two halves of one question -- \"what hosts\n // can murmur see, and which of them are up?\" -- and discover needed a\n // PEER column and a LAST SEEN column to be readable at all, at which\n // point it WAS list plus the unadded hosts. Merged into this one, with\n // the unadded hosts behind --all.\n //\n // Peers are the default because that is what the command is called and\n // what it is used for: the everyday question is \"are my peers up?\", not\n // \"what could I add?\", which is a setup-time question asked once.\n //\n // --all is the union of configured targets and ssh hosts, not ssh hosts\n // alone: `peer add` accepts any ssh target, so a peer can be an IP, a\n // user@host, or a Tailscale name that appears in no config file, and\n // listing ssh hosts alone would silently omit it.\n const peers = store.peers();\n const configured = new Map(peers.map((entry) => [entry.target, entry]));\n const discovered = options.all ? sshHosts().filter((host) => !configured.has(host)) : [];\n const now = Date.now();\n\n const rows = [...configured.keys(), ...discovered].map((target) => {\n const entry = configured.get(target);\n return {\n // The handle other commands take: a peer's name, or for a host that\n // is not one yet, the ssh host `peer add` wants.\n name: entry?.name ?? target,\n target,\n peer: entry !== undefined,\n // What the node called itself. Shown, never typed: it can be a\n // container id.\n hostname: entry?.display_name ?? null,\n // Being a peer is not the same as being reachable, and the old\n // output said only the first. A node asleep for twelve hours read\n // exactly like one polled a second ago.\n last_seen: entry === undefined ? null : lastSeen(entry.fetched_at, now),\n // A warm ControlMaster socket makes a collect ~10ms instead of\n // ~170ms, and is the only path that works on a host demanding a\n // hardware-token touch per connection. A speed hint, never a\n // requirement -- which is why the old bare `[x]` / `[ ]` was\n // unreadable: it never said what was being checked.\n //\n // Safe for every row: `ssh -O check` talks to a local socket and\n // never dials, so a host that is down or does not exist answers in\n // ~16ms. Measured.\n ssh: hasWarmSocket(target) ? \"warm\" : \"cold\",\n // What it is running, or undefined when nothing is known -- either\n // because the host is not a peer yet, or because it is a peer that\n // has never answered. Undefined is what drops the column, so the\n // test is \"has anything told us\", not \"is this configured\": a fleet\n // of asleep peers must not buy a column of \"unknown\".\n version:\n entry === undefined ||\n (entry.murmur_version === null && entry.snapshot_version === null)\n ? undefined\n : versionCell(entry),\n // Named where it can be acted on: a peer that answered with a bad\n // document is reachable but broken, which is an operator task and\n // reads nothing like a sleeping laptop.\n error: entry?.last_error ?? null,\n };\n });\n\n if (options.json) {\n process.stdout.write(`${JSON.stringify(rows)}\\n`);\n return;\n }\n if (rows.length === 0) {\n // Point at the flag that answers the obvious next question, but only\n // when it would actually show something.\n process.stdout.write(\n options.all\n ? \"no peers configured, and no hosts in ~/.ssh/config\\n\"\n : \"no peers configured. See what could be added with: murmur peer list --all\\n\",\n );\n return;\n }\n\n // The PEER column only earns its width when the table mixes both kinds.\n // Without --all every row would read \"yes\", which is a column that says\n // nothing.\n const showPeerColumn = rows.some((row) => !row.peer);\n // Same rule as the PEER column, for the same reason: a column every row\n // answers \"unknown\" to is width spent on nothing. With zero successful\n // collects -- the common case, and one the task calls out -- the table\n // stays exactly as narrow as it is today.\n const showVersionColumn = rows.some((row) => row.version !== undefined);\n process.stdout.write(\n formatTable([\n [\n \"NAME\",\n \"TARGET\",\n ...(showPeerColumn ? [\"PEER\"] : []),\n \"HOSTNAME\",\n ...(showVersionColumn ? [\"VERSION\"] : []),\n \"LAST SEEN\",\n \"SSH\",\n ],\n ...rows.map((row) => [\n row.name,\n row.target,\n ...(showPeerColumn ? [row.peer ? \"yes\" : \"-\"] : []),\n row.hostname ?? \"unknown\",\n ...(showVersionColumn ? [row.version?.text ?? \"-\"] : []),\n row.last_seen ?? \"-\",\n row.ssh,\n ]),\n ]),\n );\n\n // Named, not just marked in the row: the table cell says WHAT differs\n // and this says what to do about it. Only for a real snapshot-version\n // mismatch, which is the only case where state genuinely cannot sync.\n const incompatible = rows.filter((row) => row.version?.incompatible);\n if (incompatible.length > 0) {\n process.stdout.write(\n `\\n${incompatible.length} peer${incompatible.length === 1 ? \"\" : \"s\"} speak an incompatible snapshot version; state will not sync until murmur versions match: ${incompatible\n .map((row) => row.name)\n .join(\", \")}\\n`,\n );\n }\n\n // A peer that answered with something wrong. Printed after the table\n // rather than in it, because the message is a sentence and a column of\n // sentences is not a table.\n const broken = rows.filter((row) => row.error);\n for (const row of broken) {\n process.stdout.write(`\\n${row.name}: last attempt failed -- ${row.error}\\n`);\n }\n\n const addable = rows.filter((row) => !row.peer).length;\n if (addable > 0) {\n process.stdout.write(\n `\\n${addable} host${addable === 1 ? \"\" : \"s\"} not yet a peer. Add one with: murmur peer add <name>\\n`,\n );\n }\n } finally {\n store.close();\n }\n });\n}\n","import { spawnSync } from \"node:child_process\";\nimport type { Command } from \"commander\";\nimport {\n agentLabel,\n agentLocation,\n type JumpResult,\n jumpToAgent,\n terminalText,\n} from \"../agents.js\";\nimport { ssh } from \"../channel.js\";\nimport { glance } from \"../glance.js\";\nimport { type Mux, tmux } from \"../mux.js\";\nimport { status, statusWithCollect } from \"../status.js\";\nimport { openStore, type Store } from \"../store.js\";\nimport {\n age,\n NEEDS_HUMAN,\n type PaneView,\n RENDER_PRIORITY,\n type RenderState,\n renderState,\n} from \"../view.js\";\nimport { requireIdentity } from \"./identity-guard.js\";\n\ntype PickOptions = { all?: boolean };\n\n/**\n * The two effects `runPick` has on the world: it runs fzf, and it jumps.\n *\n * Injectable because everything interesting about the picker happens BETWEEN\n * those two calls -- which id fzf returns, and which agent that id resolves\n * to -- and with both hard-wired that stretch had no coverage at all. The crew\n * rows revealed by alt-a looked selectable but could not be jumped to for\n * exactly as long as this seam did not exist.\n */\ntype PickDeps = {\n fzf?: (args: string[], input: string, env: NodeJS.ProcessEnv) => string;\n jump?: (store: Store, agent: PaneView) => JumpResult;\n /**\n * The mux the pre-read collect reconciles against.\n *\n * Injectable because collect deletes any agent whose pane the mux does not\n * list, and that is the right behaviour in production and fatal in a test: a\n * test that claims `%9` in a temp database was reconciled against the REAL\n * tmux server of whoever ran it. `npm run check` then passed on a machine\n * with no tmux -- `livePanes()` returns null, which is a no-op -- and failed\n * inside tmux, where the fixture pane genuinely does not exist.\n */\n mux?: Mux;\n};\n\nconst spawnFzf: NonNullable<PickDeps[\"fzf\"]> = (args, input, env) =>\n spawnSync(\"fzf\", args, {\n input,\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n env,\n }).stdout ?? \"\";\n\nconst PREVIEW_MESSAGE_MAX = 300;\n\n// Same glyphs the tmux status bar and window labels use, so one symbol means\n// one thing in every surface. Ported from the dotfiles' _tmux_common.\nconst GLYPH: Record<string, string> = {\n crashed: \"\\u2717\", // ✗\n blocked: \"!\",\n done: \"\\u2713\", // ✓\n running: \"\\u25b6\", // ▶\n idle: \"\\u00b7\", // ·\n};\n\n// Mirrors the window-glyph colours: red needs you now, peach needs you soon,\n// teal is finished-unseen, grey is busy or idle and carries no signal.\nconst COLOUR: Record<string, string> = {\n crashed: \"\\u001b[31m\",\n blocked: \"\\u001b[33m\",\n done: \"\\u001b[36m\",\n running: \"\\u001b[37m\",\n idle: \"\\u001b[90m\",\n};\n// Built from a char class rather than written literally: a bare \\u001b in a\n// regex trips biome's noControlCharactersInRegex, and the rule is right that\n// an invisible byte in a pattern is a hazard.\nconst ANSI_PATTERN = `${String.fromCharCode(27)}\\\\[[0-9;]*m`;\nconst ANSI_ESCAPE = new RegExp(ANSI_PATTERN, \"g\");\n// Non-global twin for anchored single matches: `exec` on a /g/ regex carries\n// lastIndex between calls, so reusing ANSI_ESCAPE inside a loop silently skips\n// sequences.\nconst ANSI_AT_START = new RegExp(`^${ANSI_PATTERN}`);\nconst ANSI_AT_END = new RegExp(`(?:${ANSI_PATTERN})+$`);\n// Remote rows get a colour of their own: cyan reads as \"elsewhere\" without\n// competing with the state colours, which own red/peach/teal.\nconst REMOTE = \"\\u001b[36m\";\nconst BOLD = \"\\u001b[1m\";\nconst DIM = \"\\u001b[2m\";\nconst RESET = \"\\u001b[0m\";\n\n// The order the prompt COUNTS appear in: RENDER_PRIORITY, imported rather than\n// restated, so this file and status.ts cannot disagree about whether `crashed`\n// or `blocked` leads.\n\n/**\n * Marks the picker as showing orchestrated agents, at the front of the prompt.\n *\n * Doubles as the toggle's state: fzf exposes the prompt to a binding through\n * $FZF_PROMPT and nothing else is mutable, so this is both the label a human\n * reads and the flag the alt-a transform branches on.\n */\nconst CREW_MARK = \"crew \";\n\n/**\n * Whether an agent belongs in the default list.\n *\n * Orchestrated agents are hidden because their supervisor consumes the result:\n * a `done` worker needs no acknowledgement from you, and a `working` one asks\n * for nothing. `--all` shows them.\n *\n * The exceptions are `NEEDS_HUMAN` in view.ts, shared with the status bar's\n * count rule so the two surfaces cannot disagree about which crew rows matter.\n * Hiding those behind a flag meant the rows that needed a human were the ones a\n * human could not see.\n */\nexport function isVisible(agent: PaneView): boolean {\n return agent.driver === \"human\" || NEEDS_HUMAN.some((kind) => agent.attention.includes(kind));\n}\n\n/**\n * Column widths, in one place because the header and the rows must agree. They\n * were duplicated as literals in two functions and had already drifted by a\n * column once.\n */\nconst COLUMNS = {\n glyph: 3, // marker + state glyph\n state: 8,\n name: 30,\n stream: 13,\n streamWide: 18, // when no host column is shown\n host: 14,\n} as const;\n\n/**\n * The column header fzf pins above the list.\n *\n * Built from COLUMNS so it cannot drift from the rows, and dim so it reads as\n * furniture rather than as an agent.\n */\nexport function headerRow(showHost: boolean): string {\n return [\n \" \".repeat(COLUMNS.glyph),\n pad(\"state\", COLUMNS.state),\n pad(\"agent\", COLUMNS.name),\n pad(\"stream\", showHost ? COLUMNS.stream : COLUMNS.streamWide),\n showHost ? pad(\"host\", COLUMNS.host) : \"\",\n \"age / flags\",\n ]\n .filter(Boolean)\n .join(\" \");\n}\n\n/**\n * State filters, as [key, query]. An axis kept separate from the text query, so\n * a filter shows blocked panes rather than searching for the word \"blocked\",\n * which would also match a pane merely *named* that.\n *\n *\n * Alt chords, not ctrl. `ctrl-b` was the filter for `blocked` and it could\n * never work: `C-b` is tmux's DEFAULT prefix, and tmux consumes the prefix\n * before delivering to any pane -- including the display-popup the picker runs\n * in. So the one filter a user reaches for most was dead on a stock tmux, which\n * is the configuration the README tells people to set up.\n *\n * The general problem is that murmur cannot know a user's prefix, so any single\n * ctrl-letter is a gamble. Alt chords are never prefix candidates: tmux's\n * `prefix` option takes a ctrl key by convention and nobody binds M-x at the\n * root table for this purpose. Verified against fzf in a real terminal.\n *\n * Ctrl aliases are kept for the three that do not collide with the default\n * prefix, so existing muscle memory still works. `ctrl-b` is deliberately not\n * among them: binding a key that silently does nothing is worse than not\n * binding it.\n *\n * There is no \"clear the filter\" key here. fzf already clears the query with\n * ctrl-u, a standard readline binding that needs no --bind, so one existed --\n * and binding a second spelling of it cost the word \"all\", which this picker\n * needs for something else. See the alt-a toggle below.\n *\n * Every query is a `RenderState`, because that is the word the row prints. The\n * `working` filter outlived the state it searched for: activity and attention\n * are separate facts now and a busy pane paints `running`, so `alt-w working`\n * narrowed the list to nothing and read exactly like \"nothing is busy\".\n */\nexport const FILTER_KEYS: [key: string, query: RenderState][] = [\n [\"alt-x\", \"crashed\"],\n [\"alt-b\", \"blocked\"],\n [\"alt-d\", \"done\"],\n [\"alt-w\", \"running\"],\n];\n\n/** Ctrl aliases that are safe against tmux's default `C-b` prefix. */\nexport const FILTER_ALIASES: [key: string, query: RenderState][] = [\n [\"ctrl-x\", \"crashed\"],\n [\"ctrl-d\", \"done\"],\n [\"ctrl-w\", \"running\"],\n];\n\nfunction timestamp(ts: number): string {\n return new Date(ts).toLocaleTimeString([], {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n });\n}\n\n/**\n * Human age. Blank under a minute: a row that just changed does not need a\n * column saying so, and \"0s\" on every live agent is noise that hides the one\n * row reading \"3h\".\n */\n/**\n * Fit a cell to exactly `width` visible columns, padding or truncating.\n *\n * Both halves are needed. Padding counts VISIBLE length, because a value\n * wrapped in bold plus reset carries nine escape bytes and `padEnd` counts\n * them, which pads nine short and shears every column to its right.\n *\n * Truncating is what was missing: `pad` only ever grew a string, so one long\n * agent name (\"Gchatui 2026 Rebaseline Finalization\", 36 chars in a 30-wide\n * column) pushed the host and flags columns right and broke the grid for that\n * row only. Long pi session names are the normal case, not an edge one.\n *\n * The truncation walks the string and copies escape sequences through without\n * counting them, so a cut never lands inside one. Cutting mid-sequence would\n * leak the colour into the rest of the line and drop the reset that ends it.\n */\nfunction pad(value: string, width: number): string {\n const visible = [...value.replace(ANSI_ESCAPE, \"\")].length;\n if (visible <= width) return value + \" \".repeat(width - visible);\n\n // Room for the ellipsis, which is one column wide.\n const budget = Math.max(0, width - 1);\n let out = \"\";\n let shown = 0;\n let index = 0;\n while (index < value.length && shown < budget) {\n const sequence = ANSI_AT_START.exec(value.slice(index));\n if (sequence) {\n out += sequence[0];\n index += sequence[0].length;\n continue;\n }\n out += value[index];\n index += 1;\n shown += 1;\n }\n // Copy any trailing escapes (the reset) so the cell closes its own styling.\n const tail = value.slice(index).match(ANSI_AT_END);\n return `${out}\\u2026${tail?.[0] ?? \"\"}${\" \".repeat(Math.max(0, width - budget - 1))}`;\n}\n\n/**\n * Are we running inside a `display-popup` rather than a pane?\n *\n * tmux exports $TMUX to a popup but not $TMUX_PANE, because a popup is not a\n * pane. Outside tmux neither is set, so the three cases stay distinguishable\n * with no tmux call.\n */\nexport function isPopup(env: NodeJS.ProcessEnv): boolean {\n return Boolean(env.TMUX) && !env.TMUX_PANE;\n}\n\n/**\n * One fzf row: a hidden key column, a hidden filter column, then the label.\n *\n * The key is `agent_id`, not a tmux target: a target only means something on\n * the agent's own host, so resolving it is `jumpToAgent`'s job once a selection\n * comes back.\n */\nexport function pickerRow(\n agent: PaneView,\n showHost: boolean,\n current: boolean,\n local = agent.local,\n): string {\n // One derivation, shared with the status bar: attention first, then activity.\n const state = renderState(agent);\n const colour = COLOUR[state] ?? \"\";\n const glyph = GLYPH[state] ?? \"?\";\n const marker = current ? `${BOLD}\\u25c6${RESET}` : \" \"; // ◆ you are here\n // Richest name first: mu names its agents, pi names its sessions, tmux names\n // windows. All three travel in the snapshot, recorded by the node that owns\n // the pane, so this reads the same for a local and a remote agent.\n const name = agent.agent_name ?? agent.pi_session ?? agentLabel(agent);\n // Local and remote must be tellable apart at a glance. Two hostnames in one\n // dim column means you have to know your own machine's name to read the list\n // — and the difference is not cosmetic: a local row is a keystroke away, a\n // remote one costs an ssh and a nested tmux.\n //\n // \"here\" rather than the local hostname, because the reader already knows\n // which machine they are on; what they need is which rows are not it. Remote\n // hosts keep their name and get an arrow, so the column scans as \"here /\n // elsewhere\" before you read any words.\n // Both forms start in the same column: a leading space where the arrow would\n // be, so \"here\" and \"→ bubba\" line up and the arrows form a single vertical\n // run you can scan without reading a word.\n const host = showHost\n ? local\n ? `${DIM} here${RESET}`\n : `${REMOTE}\\u2192 ${terminalText(agent.host)}${RESET}`\n : \"\";\n // Workstream if mu set one, otherwise the tmux session name. Both answer\n // \"which piece of work is this\", and only mu-spawned agents have a\n // workstream, so the column was empty for most human agents.\n //\n // The session name is also what the tms picker shows and what you have\n // trained yourself to search on: a session called `hacking/murmur` holding a\n // pi whose window is named `Python` was unfindable by typing `murmur`. A\n // session without an agent still has no place in this list.\n const group = agent.workstream ?? agent.session_name;\n // Never the same string twice in one row. Both columns fall back to the tmux\n // session name, so an unnamed pi -- no mu agent name, no `/name`, a window\n // name tmux is auto-renaming -- printed `hacking/murmur hacking/murmur` and\n // spent thirteen columns saying nothing. A blank cell is the honest answer:\n // the name column already carries the only fact there is.\n const workstream = group && group !== name ? `${DIM}${terminalText(group)}${RESET}` : \"\";\n // Two ages, and the one worth showing is how old the AGENT'S news is, not\n // how recently we reached its host. A peer we polled a second ago can be\n // serving a snapshot from three hours back — which read as fresh until this\n // column existed. `unreachable` is the other axis: the cache itself is old.\n // Both attention and activity, simultaneously. A running agent with `blocked`\n // attention is a real and expected state, and the row has room to say so\n // rather than picking one word and hiding the other.\n const extra = agent.attention.filter((kind) => kind !== state);\n const flags = [\n agent.driver === \"orchestrated\" ? \"crew\" : \"\",\n // Freshness is a property of the NODE, and it is stated explicitly rather\n // than inferred from an age: a stale node keeps its last-known fields, and\n // the reader has to be told those fields are old.\n agent.freshness === \"stale\" ? \"stale host\" : \"\",\n ...extra,\n agent.activity === \"running\" && state !== \"running\" ? \"running\" : \"\",\n age(agent.updated_at === null ? null : Date.now() - agent.updated_at),\n ]\n .filter(Boolean)\n .join(\" \");\n // The state word is IN the label, not a hidden column. fzf's --with-nth\n // re-indexes fields, so any --nth that excluded the label broke plain\n // name matching (typing \"glance\" returned 0/4). Keeping state visible costs\n // eight columns and makes both the ctrl-key filters and text search work on\n // one field set — and the word is worth reading anyway.\n const label = [\n `${marker} ${colour}${glyph}${RESET}`,\n `${colour}${pad(state, COLUMNS.state)}${RESET}`,\n pad(`${BOLD}${terminalText(name)}${RESET}`, COLUMNS.name),\n pad(workstream, showHost ? COLUMNS.stream : COLUMNS.streamWide),\n showHost ? pad(host, COLUMNS.host) : \"\",\n flags ? `${DIM}${flags}${RESET}` : \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n // Keyed on the PANE, not on an agent id. The pane is the address, it is what\n // jumps, and an attention-only pane has no agent id at all -- so keying on one\n // would make exactly the rows that need a human unselectable.\n return `${agent.host_id}\\t${agent.pane}\\t${label}`;\n}\n\nfunction previewText(store: Store, agent: PaneView): string {\n const state = renderState(agent);\n const colour = COLOUR[state] ?? \"\";\n const head = [\n `${colour}${GLYPH[state] ?? \"?\"} ${state}${RESET} ${BOLD}${agent.agent_name ? terminalText(agent.agent_name) : agentLabel(agent)}${RESET}`,\n // Says where, and whether \"where\" is this machine. The glance below is a\n // local capture-pane or an ssh depending on this one fact, so it belongs in\n // the header rather than being inferred from a hostname.\n agent.local\n ? `${DIM}here ${agentLocation(agent)}${RESET}`\n : `${REMOTE}\\u2192 ${terminalText(agent.host)}${RESET} ${DIM}${agentLocation(agent)}${RESET}`,\n ];\n // The three facts, each named, because they are independent and a reader has\n // to be able to see all three at once. `activity` is what the pane's own\n // process said; `attention` is who is wanted; `freshness` is how recently we\n // reached the node that said either.\n const facts = [\n `activity ${agent.activity ?? \"none (attention only)\"}`,\n agent.attention.length ? `wants ${agent.attention.join(\", \")}` : \"\",\n agent.workstream ? `stream ${terminalText(agent.workstream)}` : \"\",\n agent.role ? `role ${terminalText(agent.role)}` : \"\",\n agent.pi_session ? `session ${terminalText(agent.pi_session)}` : \"\",\n agent.cli ? `cli ${terminalText(agent.cli)}` : \"\",\n agent.driver === \"orchestrated\" ? \"driver orchestrated (crew)\" : \"\",\n // Two ages, never one. A node polled a second ago can be serving a\n // three-hour-old fact, and collapsing them is how that read as fresh.\n agent.updated_at === null ? \"\" : `said ${timestamp(agent.updated_at)}`,\n agent.local\n ? \"\"\n : `fetched ${agent.fetched_at === null ? \"never\" : timestamp(agent.fetched_at)}`,\n agent.freshness === \"stale\" ? `${DIM}host is stale: fields below are last-known${RESET}` : \"\",\n ].filter(Boolean);\n\n // The glance is the point of the preview: what is the agent actually doing.\n // There is no history section any more, because there is no history -- the\n // store holds current state only, which is the accepted limitation this\n // rewrite takes in exchange for a model where one writer owns each fact.\n const pane = glance(store, agent);\n const live = pane?.trimEnd()\n ? [\n `${DIM}\\u2500\\u2500 pane \\u2500\\u2500${RESET}`,\n pane.trimEnd().slice(-PREVIEW_MESSAGE_MAX * 20),\n ]\n : [\n `${DIM}\\u2500\\u2500 pane \\u2500\\u2500${RESET}`,\n `${DIM}unavailable (host unreachable, or pane gone)${RESET}`,\n ];\n\n return [...head, \"\", ...facts, \"\", ...live].join(\"\\n\");\n}\n\n/**\n * Emit the preview body for one pane. `murmur pick` re-invokes itself here so\n * fzf's `--preview` has a per-row command, rather than the picker precomputing\n * every preview up front — which would mean an ssh round-trip per remote pane\n * before the list even paints.\n */\nexport function runPreview(store: Store, paneId: string, hostId?: string): void {\n const identity = requireIdentity();\n if (!identity) return;\n // Runs as a child of a picker that has just collected, so it reads the store\n // directly rather than syncing again.\n //\n // Keyed on HOST AND PANE, which is the whole address. A pane id is unique per\n // node and nothing more, so two machines routinely hold a `%1`; fzf hands both\n // columns back for exactly this reason. Matching on the pane alone previewed\n // whichever row the sort happened to put first, and for a local hit that meant\n // a local `capture-pane` standing in for a remote agent.\n const agent = status(store, identity).panes.find(\n (candidate) =>\n candidate.pane === paneId && (hostId === undefined || candidate.host_id === hostId),\n );\n // A miss is worth saying. This process's entire output is the preview, so\n // printing nothing is indistinguishable from a broken preview command -- and\n // the row can genuinely vanish between the collect and the keypress.\n process.stdout.write(\n agent ? `${previewText(store, agent)}\\n` : `${DIM}${paneId} is no longer here.${RESET}\\n`,\n );\n}\n\nexport async function runPick(\n store: Store,\n options: PickOptions = {},\n deps: PickDeps = {},\n): Promise<void> {\n const fzf = deps.fzf ?? spawnFzf;\n const jumpTo = deps.jump ?? jumpToAgent;\n const identity = requireIdentity();\n if (!identity) return;\n const view = await statusWithCollect(store, identity, Date.now(), ssh, deps.mux ?? tmux);\n const agents = view.panes.filter((agent) => options.all || isVisible(agent));\n const hidden = view.panes.length - agents.length;\n\n if (agents.length === 0) {\n process.stdout.write(\n hidden ? `No human agents (+${hidden} crew — rerun with --all)\\n` : \"No agents\\n\",\n );\n return;\n }\n\n const showHost = agents.some((agent) => !agent.local);\n const currentPane = process.env.TMUX_PANE ?? \"\";\n const input = agents\n .map((agent) => pickerRow(agent, showHost, agent.pane === currentPane))\n .join(\"\\n\");\n\n const counts = new Map<string, number>();\n for (const agent of agents) {\n const state = renderState(agent);\n counts.set(state, (counts.get(state) ?? 0) + 1);\n }\n const prompt = RENDER_PRIORITY.filter((state) => counts.get(state))\n .map((state) => `${COLOUR[state]}${GLYPH[state]}${counts.get(state)}${RESET}`)\n .join(\" \");\n const basePrompt = `${prompt}${prompt ? \" \" : \"\"}`;\n\n const self = process.argv[1] ?? \"murmur\";\n const allFlag = options.all ? \" --all\" : \"\";\n const inPopup = isPopup(process.env);\n // A preview beside the list needs room for both. Below ~150 columns the\n // 58% split squeezes the host and flags columns off the end, so start\n // stacked and let ctrl-p cycle from there.\n const width = process.stdout.columns ?? 0;\n const previewLayout =\n width > 0 && width < 150 ? \"bottom:60%,border-top,wrap\" : \"right:58%,border-left,wrap\";\n // Keyed on the pane, which is the address, and on the host so the preview can\n // tell a local pane from a remote one with the same pane id.\n const preview = `${process.execPath} ${self} pick --preview {2} --host {1}`;\n // Narrow on the hidden state column with an exact-prefix query, then restore\n // the real query. ctrl-a clears it.\n const filterBinds = [\n ...FILTER_KEYS.map(([key, query]) => [key, query] as const),\n ...FILTER_ALIASES,\n ].flatMap(([key, query]) => [\n \"--bind\",\n query ? `${key}:change-query(${query})` : `${key}:change-query()`,\n ]);\n\n const stdout = fzf(\n [\n \"--delimiter\",\n \"\\t\",\n \"--with-nth\",\n \"3..\",\n \"--ansi\",\n // Literal substring matching, and matching only the visible columns.\n // Default fuzzy scatters query characters across the row: `re` matched\n // \"Fix Murmur Pick Fzf Filter\" as well as \"recovered\". A query here is a\n // word or two of an agent or workstream name, so substring is what the\n // fingers expect. Prefix a token with ' to opt back into fuzzy.\n // Same choice as the tms session picker, for consistency across the two.\n \"--exact\",\n // `begin` ranks earlier match positions higher, so `scratch` puts the\n // scratch workstream above a row that merely mentions it. `index` is the\n // empty-query fallback and preserves the attention order `viewSort`\n // produced, which is the whole point of the list.\n \"--tiebreak\",\n \"begin,index\",\n \"--layout\",\n \"reverse\",\n // `display-popup` draws its own border, so fzf's is a second one a\n // character inside the first. A popup is the normal way to run this, via\n // the prefix+a binding, so the doubled frame was what you saw most.\n //\n // Detected by $TMUX set with $TMUX_PANE unset: tmux exports TMUX to a\n // popup but not TMUX_PANE, since a popup is not a pane. Outside tmux\n // neither is set, so the three cases stay distinguishable.\n \"--border\",\n inPopup ? \"none\" : \"rounded\",\n \"--info\",\n \"inline\",\n \"--prompt\",\n `${options.all ? CREW_MARK : \"\"}${basePrompt}`,\n \"--header\",\n [\n // No `del forget`. There is no replica to evict: a reader holds one\n // snapshot per peer, and the next fetch replaces it whole -- so a delete\n // key could only remove a row the next collect would put straight back,\n // while looking like it had done something.\n `enter jump ^r refresh ^p preview ^u clear`,\n // \"toggle crew\", not \"show crew\": the header is built once and the\n // binding flips per keypress, so a directional label would be wrong\n // half the time. The prompt's `crew` marker says which way it is\n // currently set.\n `filter: ${FILTER_KEYS.map(([key, query]) => `${key.replace(\"alt-\", \"M-\")} ${query}`).join(\n \" \",\n )} M-a toggle crew`,\n headerRow(showHost),\n ]\n .filter(Boolean)\n .join(\"\\n\"),\n \"--preview\",\n preview,\n // Narrow terminals cannot show both the columns and a 58% preview, and\n // the columns are the point of the list. ctrl-p cycles right / bottom /\n // hidden, so every column is reachable on a small viewport without\n // giving up the glance entirely.\n \"--preview-window\",\n previewLayout,\n \"--bind\",\n \"ctrl-p:change-preview-window(bottom:60%,border-top,wrap|hidden|right:58%,border-left,wrap)\",\n \"--bind\",\n `ctrl-r:reload(${process.execPath} ${self} pick --rows${allFlag})`,\n // M-a toggles the POPULATION, which is what \"all\" means everywhere else in\n // murmur: the --all flag, and the \"crew hidden (--all)\" notice.\n //\n // It used to be the \"clear the filter\" key, labelled \"all\", which is the\n // collision that made it look broken: pressing it emptied the query\n // instead of revealing the hidden crew rows named two lines below, and\n // nothing said why. One word, two meanings, and the wrong one bound to\n // the key people reach for. Clearing is fzf's own ctrl-u, which needed no\n // binding at all.\n //\n // `transform` rather than a fixed reload, because a bind string is built\n // once at launch and cannot know it has already fired: binding\n // `--rows --all` meant the second press re-ran the same thing and the\n // toggle only worked one way. transform runs a shell snippet per\n // keypress, so it can branch on the current state.\n //\n // The state lives in the prompt, which is the only mutable string fzf\n // exposes to a binding. CREW_MARK is carried at the front of it: visible\n // as a label, and readable back through $FZF_PROMPT.\n \"--bind\",\n `alt-a:transform:[[ $FZF_PROMPT == \"${CREW_MARK}\"* ]] && echo \"reload(${process.execPath} ${self} pick --rows)+change-prompt(${basePrompt})\" || echo \"reload(${process.execPath} ${self} pick --rows --all)+change-prompt(${CREW_MARK}${basePrompt})\"`,\n ...filterBinds,\n \"--no-select-1\",\n \"--no-exit-0\",\n ],\n input,\n // FZF_DEFAULT_OPTS can carry a conflicting layout or bindings from the\n // user's shell; the old picker stripped it for the same reason.\n Object.fromEntries(\n Object.entries(process.env).filter(([key]) => !key.startsWith(\"FZF_DEFAULT_OPTS\")),\n ),\n );\n\n const [selectedHost, selected] = stdout.trim().split(\"\\t\");\n if (!selected) return;\n // Resolved against the UNFILTERED list, not `agents`. `agents` is what this\n // process printed at launch; alt-a reloads the rows from a SUBPROCESS, so a\n // crew row revealed that way was never in the parent's array. fzf returned\n // its key, find() returned undefined, and enter did nothing — the reveal\n // shipped able to show rows it could not select. Filtering is a presentation\n // concern and must not gate the action; the key fzf hands back is\n // authoritative.\n // The WHOLE address, host and pane. A pane id is unique per node and nothing\n // more, so two machines routinely hold a `%1`; matching on the pane alone\n // jumped to whichever one the sort happened to put first, which turns an ssh\n // into a local window switch.\n const agent = view.panes.find(\n (candidate) => candidate.pane === selected && candidate.host_id === selectedHost,\n );\n // So a miss here means the pane is genuinely gone between the collect and\n // the keypress, and that is worth saying. Same argument as the jump.ok\n // branch below: in a popup, a silent return is indistinguishable from a dead\n // key.\n if (!agent) {\n process.stderr.write(`${selected} is no longer here.\\n`);\n process.exitCode = 1;\n return;\n }\n const jump = jumpTo(store, agent);\n // A popup closes the moment this returns, so a bare failure looked exactly\n // like \"enter did nothing\". Say what happened and fail loudly.\n if (!jump.ok) {\n process.stderr.write(`${jump.message}\\n`);\n process.exitCode = 1;\n }\n}\n\n/** Print the row list only, for fzf's `reload` binding. */\nasync function runRows(store: Store, options: PickOptions = {}): Promise<void> {\n const identity = requireIdentity();\n if (!identity) return;\n const view = await statusWithCollect(store, identity);\n const agents = view.panes.filter((agent) => options.all || isVisible(agent));\n const showHost = agents.some((agent) => !agent.local);\n const currentPane = process.env.TMUX_PANE ?? \"\";\n for (const agent of agents) {\n process.stdout.write(`${pickerRow(agent, showHost, agent.pane === currentPane)}\\n`);\n }\n}\n\nexport function registerPick(program: Command): void {\n program\n .command(\"pick\")\n .description(\"Pick an agent and jump to it\")\n .option(\"--all\", \"include orchestrated agents\")\n .option(\"--preview <pane>\", \"render the preview pane for one pane (internal)\")\n .option(\"--host <host-id>\", \"host of the pane being previewed (internal)\")\n .option(\"--rows\", \"print picker rows only (internal, for reload)\")\n .action(async (options: PickOptions & { preview?: string; host?: string; rows?: boolean }) => {\n const store = openStore();\n try {\n if (options.preview) runPreview(store, options.preview, options.host);\n else if (options.rows) await runRows(store, options);\n else await runPick(store, options);\n } finally {\n store.close();\n }\n });\n}\n","import { spawnSync } from \"node:child_process\";\nimport { SSH_OPTIONS } from \"./channel.js\";\nimport { asPaneId } from \"./ids.js\";\nimport { type Mux, tmux } from \"./mux.js\";\nimport type { Store } from \"./store.js\";\nimport type { PaneView } from \"./view.js\";\n\n/**\n * The most specific human-readable name a pane's agent has, never a tmux id.\n *\n * Four sources, most to least specific: mu's agent name, pi's session name, the\n * tmux window name, the tmux session name. All are recorded by the node that\n * owns the pane, so this reads the same for a local and a remote pane -- a\n * reader cannot resolve a remote window id against its own tmux.\n *\n * Falls back to the window id only when a node recorded no names at all, which\n * means a non-tmux harness.\n */\nexport function agentLabel(agent: PaneView): string {\n const name = agent.agent_name ?? agent.pi_session ?? agent.window_name ?? agent.session_name;\n return terminalText(name ?? agent.window);\n}\n\n/**\n * Where the pane lives, for the second column. Names only -- the ids are what\n * jumps, not what a human reads.\n */\nexport function agentLocation(agent: PaneView): string {\n const session = agent.session_name ?? agent.session;\n const window = agent.window_name ?? agent.window;\n return terminalText(session === window ? session : `${session}:${window}`);\n}\n\nexport function terminalText(value: string): string {\n return [...value]\n .map((character) => {\n const code = character.charCodeAt(0);\n return code < 0x20 || code === 0x7f || (code >= 0x80 && code <= 0x9f) ? \"�\" : character;\n })\n .join(\"\");\n}\n\nexport function shellQuote(value: string): string {\n return `'${value.replaceAll(\"'\", `'\\\\''`)}'`;\n}\n\n/**\n * The local session name that wraps a remote attach.\n *\n * The trailing `~` marks it as murmur's, both for a human reading a session\n * list and for the `#{m:*~,...}` match in the suggested escape-hatch binding.\n *\n * The leading character is the part that matters. A tmux `-t` target starting\n * with `@`, `$` or `%` is parsed as a window, session or pane id, so a session\n * named `@bubba` -- which is exactly what the old per-host WINDOW was called --\n * cannot be addressed at all: every `-t @bubba` fails with `can't find window`.\n * Window names were never targets, so the old name was safe; session names are.\n */\nexport function remoteSessionName(peerName: string): string {\n return `${peerName.replace(/^[@$%=]+/, \"\")}~`;\n}\n\n/**\n * The one process call jump makes that is not a tmux command: the remote probe,\n * and the direct ssh attach when we are not inside tmux. Injectable so the jump\n * decision table can be tested without an ssh binary or a live peer -- without\n * this seam, `jumpToAgent` had no behavioural coverage at all and replacing its\n * body with `return { ok: true }` kept every jump test green.\n */\nexport type Runner = (\n file: string,\n args: string[],\n inherit?: boolean,\n) => { status: number | null; stdout: string; failed: boolean };\n\nconst spawnRunner: Runner = (file, args, inherit = false) => {\n const result = spawnSync(file, args, {\n encoding: \"utf8\",\n timeout: 10_000,\n ...(inherit ? { stdio: \"inherit\" as const } : {}),\n });\n return {\n status: result.status,\n stdout: result.stdout ?? \"\",\n // spawnSync reports a failure to even start the child in `error`, leaving\n // status null. Collapsing both here keeps the decision table below reading\n // as one question rather than two.\n failed: result.error !== undefined,\n };\n};\n\nexport type JumpResult =\n | { ok: true }\n | {\n ok: false;\n // Local to this process: the picker prints `message` and nothing else, and\n // no reason code here is ever stored or published in a snapshot.\n reason: \"no_peer\" | \"unreachable\" | \"no_tmux\" | \"pane_gone\" | \"attach_failed\";\n message: string;\n };\n\n/**\n * Jump to a pane, wherever it lives.\n *\n * NEVER MUTATES STATE ON FAILURE. A failure is a report: a reason and a message,\n * nothing written. The next collect reconciles either way, and only the owning\n * node can author facts about its own panes.\n */\nexport function jumpToAgent(\n store: Store,\n agent: PaneView,\n mux: Mux = tmux,\n run: Runner = spawnRunner,\n): JumpResult {\n if (agent.local) {\n // The PANE decides, and only the pane. This once asked whether the agent's\n // WINDOW still existed, which a live pane routinely outlives: after\n // `move-pane -s %0 -t @1`, list-panes still has %0 and list-windows no\n // longer has @0. Asking the wrong one reported healthy agents as gone.\n const panes = mux.livePanes();\n if (panes && !panes.has(agent.pane)) {\n return {\n ok: false,\n reason: \"pane_gone\",\n message: `${agentLabel(agent)} is gone -- its pane no longer exists.`,\n };\n }\n // Reporting the attach rather than assuming it. A select-window that fails\n // is the local twin of the remote symptom: the picker closes, nothing\n // moves, and nothing says why.\n if (!mux.attach(agent.session, agent.window)) {\n return {\n ok: false,\n reason: \"attach_failed\",\n message: `could not attach to ${agentLabel(agent)} (tmux select-window failed).`,\n };\n }\n return { ok: true };\n }\n const peer = store.peers().find((candidate) => candidate.host_id === agent.host_id);\n const target = peer?.target ?? peer?.name;\n if (!target) {\n return {\n ok: false,\n reason: \"no_peer\",\n message: `No peer configured for host ${agent.host_id.slice(0, 8)}. Try: murmur peer add <target>`,\n };\n }\n\n // Check the pane is still there before opening a window to attach to it.\n // Panes, not windows: a recorded window id goes stale every time the pane\n // moves, so it cannot answer whether the agent exists. Without this the attach fails inside a new tmux window that closes\n // instantly, which is indistinguishable from \"enter did nothing\" -- the\n // symptom that sent us looking for a quoting bug that did not exist.\n // ssh does not take an argv: it joins its arguments and hands the string to a\n // shell on the far side. An unquoted `#{window_id}` is mangled by that shell\n // and tmux answers `-F expects an argument`, which looked exactly like an\n // unreachable host. One quoted string, so the remote shell passes the format\n // through untouched.\n //\n // Shares the collector's SSH_OPTIONS rather than passing BatchMode alone.\n // Without ControlPath the probe could not use the warm master socket the\n // collector rides, and without ConnectTimeout it inherited the kernel's dial\n // -- 75s on macOS, bounded only by the timeout below, so a sleeping laptop\n // froze the picker for ten seconds before admitting it was unreachable.\n const probe = run(\"ssh\", [\n ...SSH_OPTIONS,\n target,\n `tmux list-panes -a -F ${shellQuote(\"#{pane_id}\")}`,\n ]);\n if (probe.status !== 0) {\n // 255 is ssh's own failure code; anything else came from the remote\n // command. Conflating them was wrong in the common case: with a warm\n // ControlMaster socket the host answers instantly and it is tmux that is\n // gone, so \"unreachable\" sent you looking at the network for a problem that\n // was not there.\n const sshFailed = probe.status === 255 || probe.failed;\n if (sshFailed) {\n // No mark: we learned nothing about the peer's tmux, only that we could\n // not ask. Its agents may be perfectly alive behind a cold socket or a\n // sleeping laptop, and deleting them here would be guessing.\n return {\n ok: false,\n reason: \"unreachable\",\n message: `cannot reach ${target} over ssh. Nothing here ever prompts for auth, so check the host is awake and reachable, or connect once by hand to see the real error.`,\n };\n }\n\n // ssh worked, tmux did not. A real fact about the host, and reported as\n // one: nothing is deleted here. The peer's own next snapshot is what\n // removes its panes, because only that node may author about them, and a\n // reader that evicts rows on a probe failure is guessing.\n return {\n ok: false,\n reason: \"no_tmux\",\n message: `${target} has no tmux server running, so its agents are gone. They will disappear on the next collect.`,\n };\n }\n const remotePanes = new Set(probe.stdout.split(\"\\n\").filter(Boolean).map(asPaneId));\n if (!remotePanes.has(agent.pane)) {\n return {\n ok: false,\n reason: \"pane_gone\",\n message: `${agentLabel(agent)} is gone -- ${target} no longer has that pane.`,\n };\n }\n\n const attachTarget = shellQuote(`${agent.session}:${agent.window}`);\n\n // Hand the ssh to tmux as its own detached SESSION rather than running it\n // here. `murmur pick` is usually a display-popup, and a popup is modal: an\n // ssh started inside it is killed the moment the picker exits, so the remote\n // pane flashed and vanished. A session outlives the popup and gives the\n // remote tmux a real terminal to attach to.\n //\n // A session, not a window, because session options are per-session and that\n // is what makes the nesting stop being felt:\n //\n // status off -- no local status bar, so the remote's own bar is the only\n // one on screen and the jump reads as a full-screen ssh.\n // prefix None -- no local prefix at all, so ^b reaches the remote\n // directly. No ^b b, and no second prefix to learn.\n //\n // Both would be global if this were a window, and would break every local\n // session. The cost is that the local server is unreachable from inside the\n // wrapper; the README documents a root-table key that detaches out.\n if (process.env.TMUX) {\n // Read BEFORE the wrapper exists, or we would record the wrapper itself as\n // the place to come back to and the return would be a no-op.\n const client = mux.clientName();\n const origin = mux.currentTarget();\n\n // Named after the peer as configured, matching the picker's host column.\n // The machine's self-reported display_name can be a container id, which\n // makes the session unrecognisable in a session list.\n const name = remoteSessionName(peer?.name ?? target);\n\n // Reuse an existing wrapper for this host rather than stacking a new one on\n // every jump. Jumping to bubba three times used to leave three identical\n // windows behind. Matched on name, the only handle available: the ssh is\n // opaque from here and the remote session id is not a local address.\n if (mux.sessionNamed(name)) {\n return mux.switchClient(client, name)\n ? { ok: true }\n : {\n ok: false,\n reason: \"attach_failed\",\n message: `could not switch to the existing ${name} session.`,\n };\n }\n\n // `tmux new-session <command>` runs the command through a shell, so the\n // string is expanded LOCALLY before ssh sees it. A tmux session id is\n // always `$N`, so `$0:@6` arrived as `:@6` and the remote attach failed\n // with \"can't find session\". shellQuote alone is not enough: it protects\n // the remote shell, this protects the local one.\n const attach = `ssh -t ${shellQuote(target)} tmux attach -t ${shellQuote(attachTarget)}`;\n\n // The return home, as part of the wrapper's own command. When the attach\n // exits -- inner detach, remote session killed, ssh dropped -- this runs,\n // then the wrapper has no command left and tmux destroys it.\n //\n // Explicit, rather than relying on detach-on-destroy: `previous` picks\n // tmux's idea of the previous session, which in testing was a stray\n // unrelated session rather than the one the jump started from. It is still\n // set below as a fallback for when this command cannot run (SIGKILL).\n const restore = origin\n ? `; tmux switch-client ${client ? `-c ${shellQuote(client)} ` : \"\"}-t ${shellQuote(`=${origin}`)}`\n : \"\";\n\n if (!mux.newSession(name, `${attach}${restore}`)) {\n return {\n ok: false,\n reason: \"attach_failed\",\n message: `could not open a session to attach to ${target}.`,\n };\n }\n\n mux.setSessionOption(name, \"status\", \"off\");\n mux.setSessionOption(name, \"prefix\", \"None\");\n mux.setSessionOption(name, \"detach-on-destroy\", \"previous\");\n\n return mux.switchClient(client, name)\n ? { ok: true }\n : {\n ok: false,\n reason: \"attach_failed\",\n message: `attached to ${target} in session ${name}, but could not switch to it.`,\n };\n }\n\n // Outside tmux there is no popup to escape, so run it directly. stdio is\n // inherited, so this blocks until the user leaves the remote session; a\n // nonzero exit means the attach itself failed.\n //\n // None of the wrapper-session machinery above applies here, and it must not:\n // there is no local client to switch, nothing to return to but the shell that\n // invoked us, and no local status bar or prefix to suppress. This path is\n // already full-screen and already prefix-clean -- the whole problem is an\n // artifact of being inside tmux. Creating a local session here would attach a\n // client to a server the user never asked for, and leave them inside tmux on\n // exit rather than back at their prompt.\n //\n // The tradeoff is no reuse of an existing attach, since there is no local\n // server holding one. That is correct rather than missing.\n const attach = run(\"ssh\", [\"-t\", target, \"tmux\", \"attach\", \"-t\", attachTarget], true);\n return attach.status === 0 && !attach.failed\n ? { ok: true }\n : {\n ok: false,\n reason: \"attach_failed\",\n message: `ssh attach to ${target} failed.`,\n };\n}\n","import { execFileSync } from \"node:child_process\";\nimport { SSH_OPTIONS } from \"./channel.js\";\nimport { tmux } from \"./mux.js\";\nimport type { Store } from \"./store.js\";\nimport type { PaneView } from \"./view.js\";\n\n/**\n * Glance: the last few lines a pane printed.\n *\n * This is the cheap half of the two things \"render any pane from the master\"\n * hides. It is a stateless `capture-pane`, not a frame stream — no resize\n * negotiation, no input routing, no reconnect. That deferral is what keeps\n * murmur a state layer instead of a multiplexer (DESIGN-NOTES, \"Deferring\n * interactive remote rendering\"), and it is why this file is thirty lines\n * rather than most of herdr.\n */\n\nconst GLANCE_LINES = 40;\n\nexport function glance(store: Store, agent: PaneView, lines = GLANCE_LINES): string | null {\n if (agent.local) return tmux.capture(agent.pane, lines);\n\n const peer = store.peers().find((candidate) => candidate.host_id === agent.host_id);\n const target = peer?.target ?? peer?.name;\n if (!target) return null;\n try {\n // The pane id is `%N`, which a remote shell leaves alone, but quote it\n // anyway: the same class of bug as the `$N` session id that made remote\n // jump fail silently for a day.\n return execFileSync(\n \"ssh\",\n [\n ...SSH_OPTIONS,\n target,\n \"tmux\",\n \"capture-pane\",\n \"-p\",\n \"-t\",\n `'${agent.pane}'`,\n \"-S\",\n `-${lines}`,\n ],\n { encoding: \"utf8\", timeout: 3000, stdio: [\"ignore\", \"pipe\", \"ignore\"] },\n );\n } catch {\n // Unreachable, cold socket, dead tmux, gone pane. The preview says so\n // rather than the picker failing.\n return null;\n }\n}\n","import { type Channel, ssh } from \"./channel.js\";\nimport { collect } from \"./collector.js\";\nimport type { NodeIdentity } from \"./identity.js\";\nimport { type Mux, tmux } from \"./mux.js\";\nimport type { Store } from \"./store.js\";\nimport {\n freshness,\n NEEDS_HUMAN,\n type PaneView,\n paneViews,\n RENDER_PRIORITY,\n type RenderState,\n renderState,\n viewSort,\n} from \"./view.js\";\n\ntype Counts = Record<RenderState, number>;\n\nexport type Status = {\n counts: Counts;\n orchestrated_counts: Counts;\n panes: PaneView[];\n peers: {\n name: string;\n display_name: string | null;\n fetched_at: number | null;\n snapshot_at: number | null;\n last_error: string | null;\n stale: boolean;\n }[];\n};\n\nfunction emptyCounts(): Counts {\n const counts = {} as Counts;\n for (const state of RENDER_PRIORITY) counts[state] = 0;\n return counts;\n}\n\nexport function tmuxStatus(view: Status): string {\n // Orchestrated agents are counted for the states only a human can answer, and\n // hidden for the rest: a supervisor consumes a `done` worker's result, so\n // nobody needs to acknowledge it, and `running` asks for nothing. The list is\n // `NEEDS_HUMAN` in view.ts, shared with the picker's visibility rule so the\n // status bar and the list cannot disagree about which crew rows matter.\n const needsHuman = new Set<string>(NEEDS_HUMAN);\n const total = (state: RenderState): number =>\n view.counts[state] + (needsHuman.has(state) ? view.orchestrated_counts[state] : 0);\n return (\n RENDER_PRIORITY.filter((state) => total(state) > 0)\n // The tmux renderer's public vocabulary predates the internal activity\n // rename. Keep that external protocol stable until the renderer is updated.\n .map((state) => `${state === \"running\" ? \"working\" : state}\\t${total(state)}\\n`)\n .join(\"\")\n );\n}\n\n/**\n * The current view. Pure with respect to the network: the caller decides whether\n * to collect first (see `statusWithCollect`).\n *\n * `identity` is required rather than resolved here, because every caller is a\n * command that already fails without one.\n */\nexport function status(store: Store, identity: NodeIdentity, now = Date.now()): Status {\n const counts = emptyCounts();\n const orchestratedCounts = emptyCounts();\n const panes = viewSort(paneViews(store, identity, now));\n for (const pane of panes) {\n const target = pane.driver === \"human\" ? counts : orchestratedCounts;\n target[renderState(pane)] += 1;\n }\n\n return {\n counts,\n orchestrated_counts: orchestratedCounts,\n panes,\n peers: store.peers().map((peer) => ({\n name: peer.name,\n display_name: peer.display_name,\n fetched_at: peer.fetched_at,\n // Their clock and ours, separately: a peer polled a second ago can be\n // serving a three-hour-old fact, and one number cannot say both.\n snapshot_at: peer.snapshot_at,\n last_error: peer.last_error,\n // The view's verdict, not a second threshold spelled the same way. A\n // peer we have never reached is stale rather than fresh -- null\n // `fetched_at` means the first collect has not succeeded yet -- and\n // `freshness` is the one place that decides, so this list and the panes\n // the peer contributes cannot disagree about the same host.\n stale: freshness(peer.fetched_at, now) === \"stale\",\n })),\n };\n}\n\n/**\n * Collect from peers, then read. This is what every user-facing surface wants:\n * the view reflects the sync that just ran, rather than the one before it.\n *\n * Awaiting matters for two reasons. A fire-and-forget collect makes every\n * invocation show data one run stale. And the callers close the store in a\n * `finally`, so a collect still in flight lands on a closed handle and reports\n * \"The database connection is not open\", which looks like corruption rather\n * than a race.\n *\n * Sync must never fail a command, and on this path it must never print either:\n * `status` runs on every status-bar tick and `pick` runs inside a\n * display-popup, so one sleeping laptop would otherwise write ssh diagnostics\n * to stderr several times a minute, forever. `murmur collect`, which a human\n * runs deliberately, is the only place that prints.\n */\nexport async function statusWithCollect(\n store: Store,\n identity: NodeIdentity,\n now = Date.now(),\n channel: Channel = ssh,\n mux: Mux = tmux,\n): Promise<Status> {\n try {\n await collect(store, channel, now, undefined, mux);\n } catch {\n // Total by construction: a read of whatever the cache already holds is\n // always better than no output, and this path has no one to tell.\n }\n return status(store, identity, now);\n}\n","import type { Command } from \"commander\";\nimport { statusWithCollect, tmuxStatus } from \"../status.js\";\nimport { openStore } from \"../store.js\";\nimport { requireIdentity } from \"./identity-guard.js\";\n\nexport function registerStatus(program: Command): void {\n program\n .command(\"status\")\n .description(\"Show current agent status\")\n .option(\"--json\", \"print JSON\")\n .action(async (options: { json?: boolean }) => {\n const identity = requireIdentity();\n if (!identity) return;\n const store = openStore();\n try {\n const view = await statusWithCollect(store, identity);\n process.stdout.write(\n options.json ? `${JSON.stringify(view, null, 2)}\\n` : tmuxStatus(view),\n );\n } finally {\n store.close();\n }\n });\n}\n"],"mappings":"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","names":["require","agentId","program","execFileSync","randomUUID","mkdirSync","join","join","mkdirSync","randomUUID","program","program","program","mkdirSync","readFileSync","writeFileSync","homedir","dirname","join","program","join","homedir","mkdirSync","dirname","readFileSync","writeFileSync","program","readFileSync","homedir","join","readFileSync","join","homedir","program","spawnSync","attach","execFileSync","execFileSync","spawnSync","timestamp","agent","program","program"]}