# Changelog

> Source: https://github.com/max-listov/ccmux/blob/main/CHANGELOG.md

---
# Changelog

All notable changes to ccmux. The `[Unreleased]` section accumulates as work lands;
`bun run release X.Y.Z "notes"` rolls it into a dated version section, and CI publishes
the GitHub Release with that section as the notes.

## [Unreleased]

## [0.1.19] — 2026-07-25

session-reader library seam — expose the tested block-parser as 'ccmux/session-reader' for external consumers (readSession/parseSession/detect + types), lean (no ink/react), inert for the fleet bundle

- Expose the transcript reader as a library — `ccmux/session-reader` (`src/lib.ts` + a package.json
  `exports` subpath). `readSession(path, agent, textLimit?)` / `parseSession(lines, agent, textLimit?)`
  / `detect(lines)` plus the `TranscriptMessage` types, so an external consumer reuses the tested,
  agent-agnostic (Claude + Codex) block-parser instead of duplicating it. `textLimit` is a passthrough
  (default 6000; pass higher for full-text indexing). Lean by construction: the seam wires only the
  PURE parsers + `readLines` + types, so importing it pulls in `zod` + `node:fs` — not ink/react/tmux
  (verified: `src/lib.ts` bundles to ~15 KB with zero ink/react in the subgraph). Inert for the fleet:
  the shipped bundle is built from `cli.ts`, and `exports` only affects external `import "ccmux/…"`
  resolution. The format sniff moved to `src/agent/detect.ts` (normalize-only deps) and is re-exported
  from `agent/index.ts` under its existing name.
## [0.1.18] — 2026-07-24

fix: 'ccmux update --check' is now read-only and a stale/older staged bundle can't silently downgrade a machine

- Fix `ccmux update --check` mutating the machine. With a leftover staged bundle present (a forgotten
  `bun run stage`), `--check` applied it instead of just reporting — and applied it even when it was
  OLDER than the running version, silently downgrading. Two root fixes: (1) the update decision is now
  a pure `decideUpdate` that `--check` can only ever ask for a `print` from — read-only by
  construction; (2) a staged bundle wins only when NEWER-or-equal than the running version (an
  unreadable one counts as not-newer) — a stale/older staged build is refused as a downgrade unless
  `--force`, with a message pointing at the forgotten file. The legit "test a newer build locally"
  path is unchanged.
## [0.1.17] — 2026-07-24

chat-layer follow-ups from acceptance testing: cancellable watchdogs (msg cancel + --task dedup), honest single-source usage, stdin body, --after+--defer trap warning

- `ccmux msg cancel <task>` — drop a sender's still-undelivered mail for a task (an armed `--after`
  watchdog or a queued `--defer` that hasn't fired). Cancellation is a tombstone in the append-only
  ack-log (`by: "cancel"`), the same log the daemon and the Stop hook already consult — so a cancel
  suppresses delivery in BOTH channels with no new coordination surface and no ledger rewrite.
  Scoped to the sender, so a session can never cancel another's dispatch; already-delivered mail
  can't be un-sent.
- Watchdog dedup by task: re-arming a conditional (`--defer`/`--after`) with the same
  `(sender, recipient, task)` now REPLACES the sender's prior undelivered one instead of piling up a
  duplicate ping — closing the router's "two identical watchdogs both fired" gap. Immediate mail is
  never replaced (it's delivered at once).
- `--after` + `--defer` now prints a note on send: the two multiply to "not before T AND only at a
  turn boundary", so a self-watchdog armed that way won't arrive on time in a long turn — a watchdog
  should use bare `--after` (delivered between tool calls). Not blocked; the flags are compatible.
- `ccmux msg` reads the body from stdin (`echo "…" | ccmux msg <to>`) when no inline text is given
  and stdin isn't a TTY — matching the rest of the toolchain.
- `ccmux msg --help` now shows the full flag set (`--defer`/`--after`/`--on-behalf-of`/`cancel`). The
  short help and the arg-error usage were two separate strings that had drifted; they now render from
  one source, so they can't diverge again.
- `ccmux inbox` help clarifies it's the fallback for held/offline mail, not an archive — a message
  already pushed into a pane doesn't sit there.
- Diagnostic: the daemon now logs when it HOLDS a pending message solely because a human is attached
  to the recipient's pane (delivery resumes on detach) — so "the message never arrived" is traceable
  to that transient cause instead of looking like a broken chat.
## [0.1.16] — 2026-07-24

inter-agent deferred chat + autonomous router sessions + time-delayed watchdog delivery

- Deferred chat delivery: `ccmux msg <to> --defer` holds a follow-up until the recipient
  VOLUNTARILY finishes its turn, then delivers it as if a human typed it — never interrupting
  mid-work (Claude's native queue is steering; it flushes between tool calls). Delivered by a
  Claude Stop hook the instant the turn ends, or by the daemon once the target is stably idle
  (spinner off + assistant-message-last + transcript quiet for a grace window). The Stop hook is
  auto-provisioned at launch, merged into a single `--settings` object (verified it does not
  clobber the user's own hooks). Coordination is an append-only ack-log keyed by message id —
  the daemon stays the sole writer of the delivery cursor, so there is no lost-update race and no
  `block`-loop.
- Router sessions — an autonomous manager. `ccmux new <name> <dir> --router` / `ccmux router
  on|off <name>` gives a session a versioned "manager protocol": it routes an owner-dictated
  follow-up to the right target with `--defer`, waits, validates the result against a stated
  done-criterion, re-asks on a gap (bounded), and escalates to the human ONLY when genuinely
  stuck — never nagging with "continue?". Activated via a `promptModules` data field (a key into
  an in-code module registry, resolved fresh at every launch — no stale snapshot), so it's a
  capability toggle, not a persisted role.
- Time-delayed delivery: `ccmux msg <to> --after <sec>` (a `notBefore` instant). A router arms a
  self-`watchdog` per dispatch, so a target that finishes but never reports back no longer hangs
  it — the timer returns control, the router checks the transcript and closes or escalates on its
  own. Delivery is now two-track — immediate mail flows in order through the cursor, while
  deferred / time-delayed mail is delivered by id off the cursor, so a pending conditional message
  never head-of-line-blocks an immediate reply behind it.
- Honest relay provenance: `ccmux msg --on-behalf-of <who>` renders "on behalf of <who>" so a
  router can carry the owner's authority without ever spoofing the sender — gated so only a router
  (or the cli) may relay, never a plain peer.
- Owner-language: sessions reply to `owner` in the owner's own language by default; an optional
  `ownerLang` in machine.json forces a fixed language.
## [0.1.15] — 2026-07-19

inter-agent chat (menu-safe pane delivery + one-way Telegram mirror) + isolated dev instance

- Isolated dev instance: run a full second ccmux (daemon + sessions + chat) beside prod on one
  machine, fully isolated — a `tmuxSocket` config scopes every tmux call to its own server (`-L`),
  `CCMUX_HOME` overridable for its own app/log/boot-state, `remoteControl:false` keeps its sessions
  out of the claude.ai app. tmux doesn't propagate env into panes, so `new-session -e` pins the
  instance's `CCMUX_HOME/CONFIG/SESSIONS`, and the injected prompt teaches the instance's own cli
  (not the prod shim) when `CCMUX_HOME` is non-default. Scaffold + teardown via `scripts/dev-instance.sh`.
- Inter-agent chat, sender identity: the sender is automatic and unspoofable — an agent sends as its
  own session, a command-line invocation as `cli`; there is no `--from`. `owner` is a reserved
  recipient (the human — Telegram-only, no pane); the injected prompt frames `[chat from owner|cli]`
  as the human side (user-level trust) vs `[chat from <peer>]` as a fellow agent.
- Dev daemon hot-reload: `bun daemon:watch` (= `bun --watch src/cli.ts daemon`) restarts the
  process on any source change — fresh timers each time, unlike `--hot`, which re-runs the entry
  WITHOUT tearing down the old `ensure`/chat loops (they'd accumulate; proven with a `Bun.sleep`
  probe). The boot-loop guard is now skipped when running from live source (`IS_DEV`): it protects
  the auto-updated prod bundle (revert to `.bak`), has no bundle to revert in dev, and would only
  churn false "boot-loop" errors under rapid `--watch` restarts.
- Telegram chat mirror: the routing header (`from → to`, or `📩 for you — from …` for a message to
  the human) is now bold (HTML parse_mode) so who-is-talking-to-whom reads at a glance; the message
  body is HTML-escaped so `<`/`>`/`&` render verbatim and never trip a 400 that would drop the message.
- Inter-agent chat: opt-in messaging between managed sessions. `ccmux msg <to> "..."` /
  `ccmux inbox` / `ccmux chat log|on|off`, with a per-session `chatEnabled` flag (default off).
  The daemon push-delivers into the recipient's pane on a fast cadence, tagged `[chat from X]`
  (framed to the agent as a peer, not the user), gated so it never injects at a selection menu
  (would auto-pick an option — proven live) or while a human is attached; a busy recipient just
  gets it queued at its next turn boundary. In-order per recipient, no double-push across daemon
  bounces; loop/rate guards cap a runaway A→B→A. An append-only ledger (`~/.ccmux-chat.jsonl`) is
  the source of truth; multi-line bodies deliver via bracketed paste. Optional one-way Telegram
  mirror (`telegram` in machine.json → group/DM/topic; fail-soft, outbound only).
## [0.1.14] — 2026-07-19

auto-answer Claude's resume-from-summary picker so daemon-healed reboots don't strand large sessions at the menu

- Auto-answer Claude's blocking "Resume from summary?" picker on an unattended resume. Claude
  2.1.x shows this menu on `--resume` of a large/old session; a daemon-healed reboot had nobody
  to answer it, so big sessions stranded at the menu (typed input — app or tmux — landed on the
  menu, not the conversation) until a human manually restarted each one. The `_run` supervisor now
  watches the freshly-resumed pane and answers per a new `resumePicker` machine-config policy
  (`full` = keep all context [default] · `summary` = compact · `off` = leave for a human). It reads
  the option NUMBER from the pane (robust to reordering) and confirms with Enter only if the number
  key didn't. Claude-only; other agents have no such picker.
## [0.1.13] — 2026-07-17

injected prompt teaches bare ccmux shim, not the absolute bun path

- The sibling-management prompt injected into each session now teaches the bare `ccmux`
  command (the PATH shim) instead of an absolute `bun …/ccmux.js` path, so fleet agents
  call it cleanly. Falls back to the absolute invocation only when the shim isn't
  installed. The machine re-execs (supervisor, boot unit, restart-worker) stay absolute.
## [0.1.12] — 2026-07-17

per-session permission-mode override

- Sessions get an optional `permissionMode` that overrides the machine-wide default
  (`MachineConfig.permissionMode`). Undefined → inherit the machine default, so existing
  sessions and configs are unchanged. Lets one box run bypass by default while a specific
  session (client-prod, untrusted-input) stays gated at `auto`/`plan`.
- New `ccmux mode <name> <mode|default>` sets/clears the override (`default` = inherit the
  machine default). It's a launch-time flag → `ccmux restart <name>` applies it.
- The root-guard is unchanged and still applies to the resolved mode: under a root daemon,
  escalated modes (`bypassPermissions`/`dontAsk`) still downgrade to `auto`, whether they came
  from the machine or the session.
## [0.1.11] — 2026-07-16

transcript whole-session composition stats

- transcript: whole-session `stats { messages, user, assistant, toolCalls, thinking }` on every
  `--json` response, counted over the ENTIRE JSONL (not just the loaded window) and cached by
  mtime — idle sessions cost nothing, active ones recompute only when they move. Lets a viewer
  show true session composition that doesn't drift as you paginate.
## [0.1.10] — 2026-07-16

transcript backward pagination — infinite-scroll-up

- transcript: backward pagination for infinite-scroll-up. `transcript --json --before <line>
  --limit <n>` returns the `n` lines ending just before `<line>` (line-based, so it's robust to
  lines that carry no message — blank / folded tool_result), and every response now carries
  `window { firstLine, lastLine, reachedStart }` so a consumer can page older until line 1.
  `parse` gained an optional `endLine` upper bound (claude + codex parsers).
## [0.1.9] — 2026-07-16

transcript: full tool input + result output for the expanded card

- transcript: `TranscriptMessage` gains `input` (the tool_use input as pretty JSON — the actual
  command/args) and `resultText` (the paired tool_result's full output), both clipped to the
  display text limit. Consumers can now render a real request→response body per tool call
  instead of only the one-line summary. Claude + Codex parsers emit both; null for non-tool
  messages and still-running calls.
## [0.1.8] — 2026-07-14

release pipeline v2 — CI-only publishing

- Release pipeline v2: releases are born only from tags via CI (gate: typecheck + tests +
  bundle smoke); local `--publish` removed; `bun run release X.Y.Z "notes"` is the one
  ceremony (clean-tree guard → check → bump + changelog → commit → tag → push).
- Pre-push git hook runs `bun run check` (wired via `core.hooksPath`).
## [0.1.7] — 2026-07-14

- Follow the fork: the registry re-pins a session to the new session id when Claude Code
  forks the conversation (out-of-context continuation) — previews, transcripts, activity
  and the next restart follow the live conversation instead of a dead file.
- External discovery ignores processes living inside managed panes (a fork leaves the
  pane's stale `--resume` argv looking like a live external session showing a dead
  conversation).
- TUI: fleet sorts by last conversation activity (minute-bucketed, no per-tick reshuffle);
  cards show the activity age (`5m ago`) next to uptime; selection follows the session
  (uuid), not the list position.

## [0.1.6] — 2026-07-11

- Configurable permission mode (all Claude Code modes) in machine.json; escalated modes
  are downgraded to `auto` under a root daemon (server safety guard).

## [0.1.5] — 2026-06-24

- `rcPrefix` is a free-form slug, not a `local|dev|prod` enum — the fleet grows past
  three machines.

## [0.1.4] — 2026-06-23

- Discover live external sessions by process (ps scan), not file mtime — a desktop-app
  open no longer surfaces dead sessions as live.

## [0.1.3] — 2026-06-22

- Add the bun bin dir to the daemon PATH (defense-in-depth for auto-update).

## [0.1.2] — 2026-06-22

- Fix daemon auto-update preflight (bare `bun` not in the launchd PATH).

## [0.1.1] — 2026-06-22

- Fix `ccmux install` ignoring `--release-url` over an existing config.

## [0.1.0] — 2026-06-22

- First public release: persistent self-healing Claude Code tmux sessions with
  deterministic resume, fleet daemon, TUI, GitHub Releases auto-update.
