github asheshgoplani/agent-deck v1.16.29

3 hours ago

Agent Deck v1.16.29

Terminal session manager for AI coding agents.

Installation

Homebrew (recommended):

brew install asheshgoplani/tap/agent-deck

Quick Install:

curl -fsSL https://raw.githubusercontent.com/asheshgoplani/agent-deck/main/install.sh | bash

Go Install:

go install github.com/asheshgoplani/agent-deck/cmd/agent-deck@v1.16.29

What's new in 1.16.29

Security and toolchain

  • Dependency updates: golang.org/x/crypto v0.58.0, golang.org/x/sys v0.49.0, golang.org/x/term v0.47.0, golang.org/x/sync v0.24.0, google.golang.org/api v0.301.0, charmbracelet/x/ansi v0.11.9 and go-runewidth v0.0.31 (#2672, thanks @dependabot).

Sending

  • A Claude prompt suggestion (the dim predicted next message Claude can show in an empty input box) now counts as an empty input box. Automated sends, queued sends, watcher dispatches and the conductor recovery turn reach an idle Claude session that shows one, instead of failing with composer_blocked. agent-deck never presses a bare Enter, Tab or Right at a suggestion, so it can no longer submit or accept text nobody wrote; when the suggestion is still painted after typing, Enter waits until the typed message has replaced it. A send whose own message is still sitting unsent in the input box still gets the recovery Enter (#2651).
  • Sessions launched with a parent (workers, a conductor's children) start Claude with prompt suggestions off (CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false), on start, restart, resume, the first start of a TUI fork with a parent, and a cross-harness switch into Claude. Root sessions are unchanged. Set it with [launch] prompt_suggestions = "children-off" | "on" | "off" (default children-off, also config set launch.prompt_suggestions), per profile with [profiles.<name>.claude] prompt_suggestions, or per session with session set <id> prompt-suggestions. Changes apply on the next start or restart (#2651).
  • session send-keys --enter, --named-key Enter|Tab|Right (any spelling tmux accepts, such as enter, C-m or ^I) and a --text that is only a CR, TAB or cursor-right check a Claude session's input box first. At a prompt suggestion they press Escape once, wait a second, and then refuse with exit 1, delivery: "composer_blocked" and reason: "prompt_suggestion"; while a turn runs they refuse without pressing Escape. Without a suggestion nothing changes. --stream (TUI insert mode) stays unguarded (#2651).
  • session send --require-input-prompt (the guarded send the macOS app uses) delivers to a busy Claude session whose input box is empty, as a message Claude queues for after the current turn, instead of refusing with composer_blocked. A draft or paste marker in the busy input box still refuses with composer_blocked, and a menu, picker or permission dialog with menu_open (a menu under a running spinner used to report composer_blocked). Busy Codex, pi and shell sessions are still refused. launch -m ... --no-wait now runs the same pre-send check as a waited launch (#2652).
  • session send --wait --json to a Claude session over the socket transport no longer times out with "turn identity not established" when the reply has arrived. An idle socket wait returns the next fresh completed reply with wait_outcome: "observed_not_correlated" and verified, submitted and acknowledged all false, because a socket message cannot be matched to the turn it caused. Observing the turn and waiting for its reply share the --timeout budget. A busy socket wait now prints its one unverified JSON result instead of nothing (#2653, thanks @jaideeppyne, reported by @godlen4332).
  • o on the session list now opens a message composer above the footer for any running local session, not only Claude sessions, and Shift+Q opens it to queue the message for after the current turn. Enter sends, Ctrl+J adds a newline, Esc cancels, and Ctrl+X then E opens the draft in $VISUAL/$EDITOR. OpenCode 2.x queues through its session API once its session id is detected. Delivery errors are shown instead of only being logged. Remote rows do not open the composer (#2517, thanks @dtvillafana).

Sessions and accounts

  • session switch-account works when both accounts' projects directories point to the same transcript store: the conversation is already there, so nothing is copied, staged or archived, and the switch fails safely if the store is rebound while it runs. session set ... account no longer prints a needless migration warning in that setup. Accounts with separate stores keep the existing checks (#2648, thanks @jaideeppyne, reported by @bautrey).
  • A launch with -p <profile> from inside a session of another profile no longer runs on the caller's Claude account: the inherited AGENTDECK_ACCOUNT, CLAUDE_CONFIG_DIR and CLAUDE_SECURESTORAGE_CONFIG_DIR are dropped when -p differs from the caller's profile, so the new session uses the target profile's Claude directory. --account still wins, same-profile launches are unchanged, and status line wrappers keep the original environment and account attribution (#2650, thanks @jaideeppyne, reported by @godlen4332).
  • A Codex session that shows Working with collapsed queued questions above the input box (? 1 question) reads as running instead of idle, and sends treat it as busy. Queued questions without a live turn still read as idle (#2666, #2667, thanks @007hacky007).
  • session archive <id> --expected-parent <parent-id> --expected-content-version <version> archives a worker only when it is still the same finished turn: the session must have that parent, be waiting, and its transcript must still have the content_version from session output --json. A Codex worker's latest turn must be the completed reply with no unsent submission left over, and a Claude worker's last reply must end with a done sentinel. Otherwise it refuses with exit 1 and stops nothing, so automatic cleanup no longer kills a turn that started after the check. Both flags are needed together, and other tools are refused. session archive without them is unchanged (#2643, thanks @arnzchen).
  • OpenCode 2.x sessions get live status again (running or waiting within about a second of the turn changing) once their OpenCode session id is bound. 2.x has no per-session --port server, so agent-deck reads the shared service's URL from opencode service status, follows its event stream and counts only the bound session's events; each reconnect re-seeds from the service's list of active sessions, so a turn that ended while the stream was down does not stay running. The service password is read from its own registration file ($XDG_STATE_HOME/opencode/service.json) only when that file names the same URL, and only for a loopback URL. OpenCode 1.x, sessions with no bound id, sandboxed and SSH sessions keep their current status path, and [opencode].disable_sse_status = true turns the shared stream off too (#2515, thanks @3rwww1).
  • limits, list --json and status -v now tell a blocked account (oauth_org_not_allowed), a per-model cap and a plain usage limit apart, and they show the wait reason (for example weekly limit · resets 12pm). An expired 5h or 7d window is marked "expired": true and no longer counts as limit reached, and limits --json marks a blocked account and lists the sessions to move, each with a fix command. session set <id> account|model <v> --intended "<reason>" marks a switch as deliberate (--intended-for, default 24h, at most 168h), and session set <id> intended-switch "<reason>" marks the current account and model. The mark shows as intended_switch in list --json and session show --json until it expires (#2701).
  • OpenCode: opencode2 is a launcher option in the new, edit and setup dialogs, kept across reload, restart and fork, while the session stays an opencode session. 2.x launches set model and agent through the session API, forks can move to a worktree, and MCPs are written to OpenCode's native mcp.servers config without touching other settings (#2516, thanks @dtvillafana).
  • Forking an OpenCode 2.x session works again. Before, the fork pane died at once because 2.x has no --fork flag. The fork goes through the shared service, the new session is bound to the child at once so a restart resumes it, and model and agent overrides (or the [opencode] defaults) are applied to the child. A failed fork shows an error and leaves no orphan session (#2513, thanks @3rwww1).
  • Restarting a Hermes entry resumes that entry's own conversation, even when several entries share one working directory. Before, it could resume another entry's newest conversation. An entry with no recorded conversation in a shared directory starts a new one and shows a warning; a lone entry in its directory still resumes the newest conversation there (#2582, #2696, reported by @renky).
  • Claude agent view: hooks from a background supervisor or worker (a bg entry in Claude's sessions/<pid>.json, or CLAUDE_JOB_DIR when there is no entry) are ignored, so they no longer rebind another pane's session. A restart follows a continued-in record to the conversation's new session id instead of reopening the stalled copy. add --resume-session, launch --resume-session and session set <id> claude-session-id refuse an id that an archived session holds and name that session, instead of starting a new conversation or reverting silently (#2683, #2697, reported by @emcodeio).
  • A cross-harness switch into Claude pre-trusts the target folder, so the new session no longer waits on Claude's "Do you trust the contents of this directory?" prompt (#2677, #2687, reported by @koenzhao).
  • launch -account <slot> (and any launch whose Claude config dir comes from an account, conductor, group, profile or env) also seeds folder trust in that dir's .claude.json, so the session no longer dies on the trust prompt in a folder the slot never opened (#2673, #2688, reported by @bautrey).
  • A shell or editor session is no longer relabelled as an agent because an agent's name appears on screen (in a Neovim buffer, shell output or a conversation). The configured tool is never overwritten by what agent-deck observes, and restart keeps using the configured tool (#2456, thanks @dtvillafana).

Comms

  • pi sessions are text producers in the Comms Ledger: each finished pi run adds one turn record with the reply text (the last 16 KiB), tiered urgent when the reply carries a done sentinel or a question and info otherwise. A tool-only, aborted or failed run adds one status record and no text. Status display for pi sessions is unchanged. Needs the v3 pi extension: run agent-deck pi-hooks install (#2671, thanks @fpmx3bp).
  • A pi child that was revived (session restart after an error) wakes its parent again when a turn ends: pi's turn_end hook now counts as a transition, like Claude's Stop (#2665, #2689, reported by @koenzhao).

Conductors and watchers

  • A conductor keeps its [conductors.<name>] settings (env_file, config_dir, command, model, meta.json env) when its title changes. The conductor name is now stored on the session: conductor setup writes it, and an older conductor row adopts it on its next save. So a renamed conductor or a decorated title such as 🎙 conductor-x still sources its env_file and keeps its TELEGRAM_* variables. Only rows flagged as conductors keep a stored name: a worker that briefly carries a conductor title still resolves by its current title, so renaming it back ends the match. session send conductor-<name>, the heartbeat and the bridges keep reaching a renamed conductor, and conductor status warns when the title no longer matches (session_title and title_mismatch in --json). conductor teardown --remove removes the row it stopped. A prefix wrapper such as env TELEGRAM_STATE_DIR=<dir> {command} now works with the env prelude: the command is passed as one bash -c argument, a {command} inside quotes is escaped for that quote, and a pane warning is shown when a wrapper's TELEGRAM_STATE_DIR is unset for a session that does not own the Telegram channel (#2694, #2708, #2709).
  • A conductor keeps its settings and its Telegram bot across an account switch (session switch-account, the TUI switch, or session set <id> account <slot> and a restart). A conductor with no stored name is now found by the conductor folder it runs in (conductor/<name> with a meta.json, using the name meta.json records) before its title, so a title such as conductor-team-ops for conductor ops no longer loses its [conductors.ops.claude] settings; a worker launched inside the conductor folder is not the conductor. On a switch, the Telegram state from the old config dir is linked at conductor/<name>/telegram (never copied, and an existing entry is never replaced), and a conductor with no configured TELEGRAM_STATE_DIR is pointed at it on every start. Nothing is linked into the new account's shared config dir, so other sessions on that account never see the token. Discord and other channel plugins are not pointed automatically; the switch result says what to set (for Discord, DISCORD_STATE_DIR under [conductors.<name>.claude.env]). session show and conductor status warn when a conductor's channel plugin has no state and will not start (channel_warnings in --json), and conductor status warns when more than one session resolves as the same conductor, which means two pollers on one bot (duplicate_session_ids) (#2703).
  • Mattermost is a fourth conductor bridge, next to Telegram, Slack and Discord (experimental). Set it up with conductor setup or [conductor.mattermost]. It obeys one configured user in a DM or a channel, supports !status, !sessions, !restart and !help, sends NEED alerts and heartbeats, and catches up on missed posts after a reconnect. A plain-http server URL is refused unless it is localhost or allow_insecure_http is set. See docs/conductor/mattermost.md (#2542, #2545, thanks @wallacms).
  • agent-deck web --no-tui now runs the profile's watchers: webhook and GitHub ports listen, ntfy and Slack subscribe, and routed events reach the conductor. Each profile runs its watcher engine in one process. Other TUI or web processes stand by and take over within about 5 seconds when the owner exits, so [instances] allow_multiple = true no longer binds a port twice (#2530, #2638, thanks @AndreIntelas).
  • Watcher events routed to a conductor go through the durable send queue and are typed into the conductor at most once. They are no longer lost when the TUI or web process quits or is signalled, when the composer is busy, or when the conductor is stopped: the event waits until the conductor runs again. [watcher] delivery_deadline (a duration such as "72h", no deadline by default) fails an event that is still undelivered after that long, and watcher status <name> shows per conductor how many events still wait and since when (undelivered in --json). An event for a conductor that switched harness follows it to the new session (#2537, #2641, thanks @AndreIntelas).
  • A queued Slack, Telegram or Discord message to a Claude conductor no longer reports [Failed to send message to conductor] when the conductor accepted it and its turn ran past the synchronous wait. The reply is still delivered, and an accepted message is not sent again (#2509, thanks @kaneda-fr).

Fixes

  • The status filters % (open), & (errors) and ^ (archived) now apply to remote session rows as they do to local rows, so a remote with many stopped or failed sessions no longer floods a filtered view. A remote that sends no status keeps showing under % (#2627, #2645, thanks @koenzhao).
  • / search and Enter on a session hidden by a status or time filter now jumps to it: the filters are cleared the same way the empty-view fallback clears them and collapsed groups are expanded. A session that is gone shows a notice instead of doing nothing (#2655, #2656, thanks @koenzhao).
  • The New Session, Edit Session and account selector option pills no longer show black gaps between them on terminals with a black background (seen on Kitty): the separators use the dialog's surface colour. The three dialogs share one pill renderer (#2647, thanks @siraben).
  • MCP proxy clients (mcp-proxy) exit when their stdin closes or their parent process exits, instead of reconnecting in a loop and staying behind as orphans. A backend disconnect with a live client still reconnects, and a request already partly sent is not replayed. The shared pool is unchanged (#2662, thanks @jaideeppyne, reported by @godlen4332).
  • conductor setup on Linux creates the notifier and bridge log directories (owner-only for new ones) before it enables the systemd units, so the transition notifier no longer crash-loops with 209/STDOUT on a fresh account. A log path that cannot be created fails setup before any service starts (#2646, #2654, thanks @jaideeppyne, reported by @tsblack).
  • Web: opening a /s/<id> link after using Fleet shows that session's terminal instead of leaving Fleet on top. Skills and MCPs stay open if you were on them, and this also works with browser back and forward (#2679, #2684, thanks @littleKitchen, reported by @bautrey).
  • Web: the New Session dialog offers to create a working directory that does not exist yet, instead of failing with an error. It lists the folders it would create, and creates them only when you tick "Create this folder". Editing the path clears the tick, and if the list of missing folders changes before you submit, the dialog asks again. No git repository is set up (#2658, #2660, thanks @007hacky007).
  • / search opens with an empty query and the full session list, instead of the last query or a group-only list. Session titles, project paths and tools now match with small typos: exact text for 1 to 3 characters, one typo (a missing, extra or wrong letter) for 4 or 5, and two for 6 or more. Exact matches are listed first, so Enter on a fully typed name picks that session. The status words (waiting, running, idle, error, stopped) still filter exactly, and Recall is unchanged (#2681, thanks @dtvillafana).
  • Title sync no longer renames a deck row to the name of a Claude session running in another project: the name is used only when the Claude session record's working directory is inside one of the session's own paths (subdirectories and symlinks count). A record with no working directory, or a session with no path, syncs as before (#2678, #2685, reported by @scottyallen).
  • The TUI no longer freezes for up to about 5 seconds (keys ignored, status pass exceeds 250 ms budget) while agent-deck looks for the transcript of a Codex session that is not bound yet. The search of $CODEX_HOME/sessions now runs without holding the session lock, and its result is dropped if the session was stopped, restarted or bound in the meantime (#2674, #2686, reported by @alx-sik).
  • A local parent session's child list (for example a conductor's children) can be folded: Tab, l or → on the parent row toggles it, h or ← folds it, and a ▾N/▸N badge shows the count. Folds are remembered across restarts (#2631, #2636, thanks @koenzhao).
  • Web: the New Session dialog suggests existing folders as you type the working directory. Pick one with the arrow keys and Enter or Tab, or click it. This also works above a phone keyboard (#2657, #2659, thanks @007hacky007).
  • Web: opening a session's terminal no longer waits 5 to 7 seconds behind a session list refresh when many sessions are live (#2663, #2693, reported by @bautrey).
  • session stop from the CLI is no longer recorded as error while agent-deck web or the TUI is running. The running process now keeps the committed stopped status (#2669, #2690, reported by @bautrey).
  • When .agent-deck/worktree-setup.sh fails, the TUI warning shows the last lines of the script's output instead of only exit status 1. This applies to create, fork and the b re-run (#2637, #2691, reported by @godlen4332).
  • After a TUI restart that skipped cleanup (crash, kill, auto-update), the leftover notification bar is no longer saved as your original status-left and shown again, so Ctrl+b N no longer fails with Can't find window (#2705, #2706, reported by @nezakoo).
  • The open-descriptor budget warning explains itself. The footer marks it (advisory), and agent-deck health prints how the budget is computed, when the warning matters, and an lsof command for that process, with a pointer to docs/runtime-health.md#descriptor-budget (#2668, #2692, reported by @netllama).
  • list --json is faster with a long history of failed or stopped sessions: missing panes are checked with one tmux inventory per socket instead of one probe per row (#2644, thanks @arnzchen).

Upgrading

  • Prompt suggestions: workers and conductor children start Claude with prompt suggestions off from their next start or restart. Set [launch] prompt_suggestions = "on" to keep the old behaviour. A worker launched with --no-parent is a root session and keeps suggestions on; set off if you want those off too.
  • Raw keys: a script that sends session send-keys --enter or --named-key Tab|Right to a Claude session can now get exit 1 with composer_blocked and reason: "prompt_suggestion" when a suggestion is shown. Type the text first with --text, then send --enter.
  • Guarded send: --require-input-prompt to a busy Claude session now delivers a queued message where it used to refuse. A menu drawn under a running spinner reports menu_open instead of composer_blocked.
  • Socket waits: a caller of session send --wait --json over the socket transport should read wait_outcome; observed_not_correlated means the output is the next reply observed, not a reply proven to answer this message.
  • Cross-profile launches: agent-deck -p <other> launch from inside a session now uses the other profile's Claude account. Pass --account to pick an account on purpose.
  • pi: run agent-deck pi-hooks install on each host (remotes included) to get the v3 extension; pi-hooks status reports v2 installs as outdated, and a v2 extension adds no pi records to the Comms Ledger.
  • OpenCode 2.x: live status needs the shared OpenCode service running on the same host (opencode service status prints a loopback URL) and the session's OpenCode id bound. Without either, status keeps reading the terminal screen as in 1.16.28.
  • Watchers: only one process per profile runs the watcher engine now. watcher start says whether the watcher runs within seconds or once the engine's process restarts. Routed conductor events wait for a stopped conductor with no time limit. Set [watcher] delivery_deadline to put a limit on that.
  • Wrappers: a session wrapper with a prefix (env X=Y {command}) now gets a compound command as one bash -c '<cmd>' argument. Suffix flags still reach the agent. For remote use, pass --ssh or quote {command}.
  • Conductors: a conductor created before conductors were flagged (very old setups) is found by its exact conductor-<name> title only, as before. To make it rename-proof, run agent-deck conductor setup <name> again; this flags it and stores its name.
  • Agent detection: agent names on screen no longer turn a shell into an agent, so an agent started inside Neovim, a shim or an unrecognized wrapper is not detected. Sessions that were wrongly saved as an agent are not repaired automatically. In both cases, configure the tool explicitly, or recreate the session.

Known issues

  • Watchers: the local webhook adapter has no authentication (it listens on 127.0.0.1 only), and with [watcher] delivery_deadline unset, routed events wait in the durable send queue for a stopped conductor without a limit. A local process that floods the webhook can grow that queue. Set delivery_deadline (for example "72h") if that matters on your host.
  • When .agent-deck/worktree-setup.sh fails, its full output is written to the agent-deck log at warn level without a size cap. Keep secrets out of what the setup script prints.
  • Contributors: on tmux 3.3a, a few tmux-driven tests (TestCustomCommandExitStatus and two bootstrap cleanup tests) can fail intermittently because tmux sometimes does not report the exit of a pane process that ends at once. Released binaries are not affected: tracked commands read their exit code from a receipt file.

Thanks to @jaideeppyne, @koenzhao, @siraben, @007hacky007, @littleKitchen, @fpmx3bp, @arnzchen, @3rwww1, @dtvillafana, @AndreIntelas, @wallacms and @kaneda-fr for their pull requests, to @godlen4332, @bautrey, @tsblack, @scottyallen, @alx-sik, @renky, @emcodeio, @nezakoo and @netllama for their reports, and to @dependabot for the dependency updates.

Committed by Ashesh Goplani


Full Changelog: v1.16.28...v1.16.29

Don't miss a new agent-deck release

NewReleases is sending notifications on new releases.