Skip to content

Releases: max-listov/ccmux

ccmux v0.4.0

Choose a tag to compare

@github-actions github-actions released this 30 Jul 03:34

Claude session status from structured sources (statusLine JSON + hooks), not pane-scraping

feat: Claude session status from structured sources (statusLine JSON + hooks), not pane-scraping

  • Context %, model and cost now come from the STRUCTURED JSON Claude Code feeds its statusLine command
    (context_window.used_percentage × context_window_size), captured by an injected statusLine
    wrapper that ALSO runs the user's own statusline unchanged (or renders a minimal model · ctx%
    default if they have none). This removes the regex-over-rendered-text scrape and its dependency on
    the user's statusline FORMAT, so context % now works on default Claude Code and any user's setup —
    not only a bespoke statusline — with no hardcoded model→window map (the window size comes from Claude).
  • Turn-boundary hooks (UserPromptSubmit/Stop/SessionStart) write a per-session working/idle lifecycle
    file. Working/idle display stays pane-decisive (the spinner is reliable and, unlike the hooks, reads
    idle correctly right after an ESC-interrupt); the hook fills only the cold-start gap and SessionStart
    clears a stale working. The lifecycle file is the substrate for future event-driven push/"waiting".
  • Both inject via --settings and coexist with the chat Stop hook; status files live under
    ~/.ccmux/status/ and are cleared on stop/rm/restart. Fully fail-open — a status/statusline hiccup
    can never wedge a turn or corrupt the rendered bar.

ccmux v0.3.0

Choose a tag to compare

@github-actions github-actions released this 30 Jul 01:47

Codex launch/resume (close the launch gap) + shell completions

feat: Codex launch/resume (close the launch gap) + shell completions

  • Codex sessions now launch and resume through ccmux, 1:1 with Claude as far as the Codex CLI
    allows. Codex has no --session-id (a fresh session mints its own rollout id) and no
    --append-system-prompt, so: the first launch injects the ccmux management instructions as the
    leading positional PROMPT, and a new detectFork reconciles Codex's self-assigned id back into the
    registry through the SAME follow-fork pipeline Claude uses — after which codex resume <uuid> tracks
    the real conversation (no prompt re-injected on resume). RC has no Codex equivalent (that's a
    claude.ai feature), so it stays Claude-only. The root daemon strips Codex's --dangerously-bypass-*
    switches, mirroring the Claude root guard.
  • ccmux completions <bash|zsh|fish> — prints a shell completion script generated from the same
    COMMANDS registry ccmux help uses, so a new/renamed verb can never drift from what completes.
  • Test coverage filled in for the transcript adapters (Claude tool-call folding + Codex response items),
    the list context-label parse, and the TUI width/wrap primitives.

ccmux v0.2.1

Choose a tag to compare

@github-actions github-actions released this 29 Jul 11:22

model from transcript (source of truth), not the statusline whitelist — a new Claude family (Fable/Mythos) is never shown as a blank model again

fix: session model is read from the transcript (source of truth), not scraped from the statusline against a family whitelist — so a new Claude family (Fable, Mythos, …) is never shown as a blank model again

  • ccmux list reported model: null for sessions on any family the pane scraper hadn't been taught.
    The model was matched with a (Opus|Sonnet|Haiku) regex against the rendered statusline — a
    whitelist that silently dropped Fable 5 (and would drop the next family too), and that depended on
    the user's arbitrary custom statusline and reflected the start-time model, not the current one.
  • The model now comes from the transcript's message.model (Claude) / turn_context.model (Codex) —
    the source of truth, always fresh, format-independent. <synthetic> turns are skipped and only real
    assistant turns are trusted (image-gen model ids live in tool payloads). Display formatting is a pure
    transform (prettyModel: claude-fable-5 → "Fable 5"), never a lookup table, so a future family
    renders with zero code change; anything off-shape falls back to the raw stripped id.
  • The pane scraper keeps only genuinely-live signals (working/idle, best-effort context); its old
    double-duty "model → booted" gate is replaced by a statusline-independent ready marker. The
    managed-list and external-discover paths now share one model source and one formatter.

fix: shipped bundle is truly self-contained — stub react-devtools-core at build time so a cache-cleared / offline machine no longer dies on start with ENOENT; + guard test against future hoisted externals

  • Fix the shipped bundle silently depending on the global bun cache / npm at startup. ink imports an
    optional DEV-only React DevTools client (react-devtools-core) via a HOISTED static import, so it
    loaded on every launch — and built with --external it resolved that import at runtime against
    ~/.bun/install/cache (or an npm auto-install). A machine whose cache was cleared, or that had no
    network, died on start with ENOENT ... react-devtools-core — the daemon (and every session it
    supervises) down. The "self-contained" bundle was never actually self-contained. The build now
    compiles an inert stub in its place (Bun.build() API + a resolve plugin instead of the
    bun build --external spawn), so the single-file bundle carries no external import and starts
    offline / with an empty cache. The bundling moved to scripts/bundle.ts (one build path shared by
    stage / CI / release), the misleading "never reached in prod" comment and the obsolete "build only
    outside the project tree" caveat are gone, and a guard test builds via that same path and asserts
    the bundle starts under a wiped cache + dead registry — so this can never silently regress.

ccmux v0.2.0

Choose a tag to compare

@github-actions github-actions released this 29 Jul 10:35

fix: shipped bundle is truly self-contained — stub react-devtools-core at build time so a cache-cleared / offline machine no longer dies on start with ENOENT; + guard test against future hoisted externals

  • Fix the shipped bundle silently depending on the global bun cache / npm at startup. ink imports an
    optional DEV-only React DevTools client (react-devtools-core) via a HOISTED static import, so it
    loaded on every launch — and built with --external it resolved that import at runtime against
    ~/.bun/install/cache (or an npm auto-install). A machine whose cache was cleared, or that had no
    network, died on start with ENOENT ... react-devtools-core — the daemon (and every session it
    supervises) down. The "self-contained" bundle was never actually self-contained. The build now
    compiles an inert stub in its place (Bun.build() API + a resolve plugin instead of the
    bun build --external spawn), so the single-file bundle carries no external import and starts
    offline / with an empty cache. The bundling moved to scripts/bundle.ts (one build path shared by
    stage / CI / release), the misleading "never reached in prod" comment and the obsolete "build only
    outside the project tree" caveat are gone, and a guard test builds via that same path and asserts
    the bundle starts under a wiped cache + dead registry — so this can never silently regress.

ccmux v0.1.19

Choose a tag to compare

@github-actions github-actions released this 25 Jul 02:30

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.

ccmux v0.1.18

Choose a tag to compare

@github-actions github-actions released this 24 Jul 22:02

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.

ccmux v0.1.17

Choose a tag to compare

@github-actions github-actions released this 24 Jul 21:55

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.

ccmux v0.1.16

Choose a tag to compare

@github-actions github-actions released this 24 Jul 12:01

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 " 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.

ccmux v0.1.15

Choose a tag to compare

@github-actions github-actions released this 19 Jul 07:11

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).

ccmux v0.1.14

Choose a tag to compare

@github-actions github-actions released this 19 Jul 03:09

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.