aboutsummaryrefslogtreecommitdiff
path: root/crates/dinggerd/src/tmux.rs
AgeCommit message (Collapse)Author
3 daysshow each pi agent's context use: a meter in the panel and search, text in ↵Igor Soarez
the menu
6 daysshow what pi agents cost today, this week and this month, counting forked ↵Igor Soarez
replies once
7 daysmark forgotten drafts in pi agents' input boxes, and keep them across a ↵Igor Soarez
crash or restore
7 daysreload pi agents on an old extension: versioned extension, control socket, ↵Igor Soarez
menu item and reload-agents
11 daysimportance: mark an agent high, normal, or lowIgor Soarez
within a status, more important agents are listed first, and so jumped to first; status stays first. a high agent shows a bold ! before its name, a low agent's name is lightly dimmed with "low" on its second line, and it makes no sound (it still notifies and waits in line). in pi, /importance high|normal|low (completes the levels; none once one is typed out, so Enter runs it). the extension saves it in the session as a custom entry (dingger-importance), so /reload, restarts, /resume and restore bring it back; /new starts at normal. it shows a level other than normal in pi's footer, emits dingger:importance { level } for other extensions, and sends it with every report. for other agents, `dingger importance [level] [--pane]`; it refuses a pi that reports itself, whose saved level would win. the daemon stays the one writer of pane state: it writes @dingger_importance (unset for normal), which goes with the agent and survives a daemon restart. `dingger status` marks high and low. tests: e2e step 21 (order, the option, no sound for low, the menu bar, the command, the extension through a harness that now matches pi's extension API), a DINGGER_SOUND seam so sounds are observable, and a status-line check that read the counts once (flaky) now polls. pi-live: /importance in real pi 1.4, its footer and session entry, and the level after a restore; every pi there now uses the test's agent directory (a restored pi read the user's). negative controls fail each part.
13 daysterminal commands are silent when tmux runs themIgor Soarez
tmux shows a hook's output and a non-zero exit in view mode over the pane. the focus hook's normal outcomes (focus moved on, Ghostty not in front) exited 1, and the one-second settling made "focus moved on" common: "'dingger terminal focused --client ...' returned 1" kept covering panes. `dingger terminal focused` and `terminal raise` now always exit 0 and print nothing unless run by hand. tmux::client_format checks that tmux answered for the client asked about: for a client that is gone, tmux answers for the current one, so a raise for a client that had just detached raised another client's terminal. e2e step 20: no pane in view mode after the hook's failures, a gone client raises nothing, and a focus held past the settling second; the old code fails the new checks.
2026-10-23raise the terminal window that holds the tmux clientIgor Soarez
a jump (any click, a shortcut, search) activated the terminal app, which raised whichever window was used last. now: - the client: the named one (a status-line click), else one already on a session holding the agent's window (several terminal windows can show one session; it shows the agent without switching, and keeps its own session in a session group), else any; the focused one first, then the one used last (its last input or focus). - Ghostty (1.3+, scriptable by terminal id, not by tty): dingger learns which terminal holds each client when it gains focus. tmux's client-focus-in hook runs `dingger terminal focused`, which asks Ghostty for its focused terminal and records it in @dingger_terminal_<tty>, but only if the client is still focused once Ghostty answered (a fast switch must not pair the wrong terminal); a terminal holds one client, so an older pairing with it is dropped. `focus` raises the exact terminal (window, tab, split) through `tmux run-shell`: Apple Events from the tmux server count as Ghostty's own, so no permission prompt. a terminal that is gone is forgotten and the app activated, as without a pairing. - in a hook, #{client_name} can be another client (the previous event's); the snippet's hook uses #{hook_client}. e2e step 20: two nested clients on one session, focused by typing focus reports into their outer panes, a fake osascript as Ghostty, a fake open (tests never activate a real app); it loads the snippet's own hook line. each rule has a negative control that fails.
2026-10-23session groups: list each pane once, save each group onceIgor Soarez
a pane in several sessions (a session group, `new-session -t`, or a linked window) was listed once per session: every agent of a group showed twice in the menu, panel, search, `dingger status`, and the counts. and snapshots saved each session of a group with its own copy of the windows, so restore would have rebuilt them twice and resumed each agent twice on one session file. - a pane keeps its first listing (tmux lists sessions by name) and is attended if the user looks at it through any session of the group. - snapshots keep the first session of each group; the others are not restored. e2e step 19 covers both; the previous binaries fail it. two checks (step 11's and this one) waited for idle after a turn that started from idle, which passed before the turn was even processed: they now wait for working first, and the negative control for group attendance fails as it should. step 18 now waits for the snapshot it later restores from.
2026-10-23write a pane's options in one tmux command listIgor Soarez
the watcher wrote @agent_state, then @dingger_hook, as separate tmux calls. a daemon stopped between them left a done display state next to a stale "working" hook state, which the restarted daemon trusted: the agent came back as working. now every changed option of a pane is one command list, which tmux applies as a unit (and one process instead of several). tests/kill-race.sh kills the daemon the instant an agent shows done: 24 of 40 kills left the options inconsistent before, 0 of 40 now. e2e step 17 runs it 20 times; the previous binary fails it.
2026-10-23restore, step 2: resume agents, retype command linesIgor Soarez
- the pi extension reports its session file; the daemon keeps it in the pane option @agent_session, and snapshots carry it. restore runs `pi --session <file>` in the rebuilt pane, one agent every 0.5s, then reports which agents run and which were interrupted mid-turn. - other panes: the command line running in them is typed at the new prompt but not run. exactly as typed with the zsh hook (dingger install zsh: preexec/precmd keep @dingger_cmd), else the foreground program's exact argv from the kernel (KERN_PROCARGS2), shell-quoted. - "at a prompt" is the pane's own shell owning the terminal's foreground process group, not a name check: `sh -c ...` is a running program. - an agent or command that exits keeps its place in snapshots for 60s, so a shutdown that stops programs before tmux still restores them. - typing waits for each new prompt (text typed earlier echoes twice). e2e step 16 covers resume with a session path containing a space, the interrupted report, an agent that exits just before the server dies, a quoted command retyped not run, and the hook's text; dropping the grace period, judging prompts by name, running retyped commands, or not storing the session reference each fail it. the test's fake pi is guarded so a real pi never starts.
2026-10-23restore, step 1: layout snapshots and dingger restoreIgor Soarez
the daemon saves the tmux layout when it changes: sessions, windows (index, name, automatic-rename, layout, size, zoom, active), and panes (directory, active, agent). a new history file for layout changes, a rewrite for details, the last 50 kept, nothing saved while tmux is down. when a different server runs (or none), the old server's last snapshot is pinned as the restore candidate before the new server's snapshots start. dingger restore rebuilds missing sessions by tmux id: panes split from the newest so they keep their order, the saved layout applied at the saved size, then the size released so windows follow clients again. a bare session of the same name (a terminal's `new-session -As 0`) is replaced, its clients switched over first; used sessions are kept. --dry-run, --list, --from, --session. e2e step 16 kills a server with a client attached like ghostty's, restarts it, restores, and compares every window and pane; splitting the wrong pane, not replacing bare sessions, not pinning, or keeping the saved size each fail it. e2e keeps its state in its temp dir.
2026-10-20fix clicks from the menu bar app: find tmux without homebrew on PATHIgor Soarez
launchd starts the menu bar app with PATH=/usr/bin:/bin:/usr/sbin:/sbin, so the `dingger focus` it runs for every click (panel rows, menu rows, the jump shortcut) could not find homebrew's tmux and failed with a misleading "no tmux client attached"; the app discarded the error. dingger now finds tmux on PATH or in the usual install locations (DINGGER_TMUX overrides), reports a missing tmux as such, and the app logs failed jumps. also: panel rows own every click inside them (labels took the clicks and counted as window-drag background). e2e step 14 repeats the menu bar row click and dingger focus under launchd's environment; both fail with the previous binaries.
2026-10-20menu rows lead with the agent's session name; harness and place move belowIgor Soarez
herdr's sidebar shows the session name in place of the harness label (via the user's herdr-session-name extension). dingger reads it from the title pi already sets ("π - <name> - <dir>"); other agents use herdr's terminal_title_stripped rule, and claude's default "Claude Code" title or tmux's host-name title count as no name. the daemon stores it as @agent_name for every surface. menu rows: colored status symbol + name as the title (the harness, dimmed, only when unnamed), and a native subtitle "session · window · harness", plus how long done/blocked agents have been waiting; one separator between agents waiting for you and the rest. dingger status shows the same.
2026-10-20rebuild the status model as herdr's state machine, event-driven and testedIgor Soarez
the seen logic was inverted: first observations were marked done and real completions idle, so the menu showed teal for agents that did nothing and green for agents that finished, and attending changed nothing visible. - watcher::advance is herdr's apply_pane_state_change + mark_active_tab_seen as a pure function, unit-tested against herdr's own cases; attendance is window-level like herdr's active tab - hook reports are events applied in order, so a turn shorter than a poll is still a completion - event-driven: hook reports, dingger focus, and tmux focus hooks wake the watcher; notifications are delivered off-thread (the macos call blocked ticks for 2+ seconds) after the pane options are written - all per-pane state persists in tmux pane options, so restarts and deploys lose nothing, including the authoritative hook state - every click goes through dingger focus: the menu bar shells out to it and the status-line binding runs it; it picks the right client, skips the window already shown, and activates the terminal app hosting the client - the ready queue is ordered like the menu (blocked first, then oldest done) - menu bar and tmux segment render herdr's symbols in herdr's palette - tests/e2e.sh: 60 assertions against an isolated tmux server with a real client, a 30s poll interval, and a slow fake notifier
2026-10-20adopt herdr's five-status model with symbols style and focus detectionIgor Soarez
statuses are now herdr's verbatim: working (yellow), blocked (red), done (teal, finished and unattended), idle (green, attended since its last transition), unknown (gray) - priority blocked > done > working > idle > unknown. the daemon watches tmux focus (pane_active of attached clients) to mark panes attended, which is what separates done from idle. menu bar renders symbols-style glyphs with herdr's colors in priority order; the tmux segment stays after the clock and renders done/blocked/ working counts from numeric options. deployment note: do not strip the binary - it invalidates the ad-hoc signature and macOS kills it on launch.
2026-10-19ready-queue fifo with click-to-focus; redesign presentation per design panelIgor Soarez
daemon tracks when each agent became ready (idle/blocked, unacked) and publishes fifo queue + numeric counts as server-global options; unknown to idle transitions stay out of the queue (restart spam). new dingger focus subcommand jumps to the longest-waiting pane and acks it, bound to status-right clicks in tmux. menu bar redesigned per two-model design panel: monochrome sf-symbol bell + queue depth, queue-ordered rows with state glyphs and right-aligned targets, row clicks ack like focus does. tmux segment renders glyph+count with a single red accent for blocked; fixes unescaped comma in the conditional format that truncated the segment entirely.
2026-10-19menu bar app: live agent states from tmux pane optionsIgor Soarez
single-file appkit status item (menubar/main.swift, LSUIElement) polling @agent/@agent_state pane options every 2s: counts in the bar, per-pane rows with click-to-focus via switch-client. daemon now also writes an @agent label per pane so the menu bar and tmux status-right can render agent names without duplicating identification logic.
2026-10-19wait for tmux at startup, force utf-8 for tmux clients, add launchd packagingIgor Soarez
running under launchd, the tmux client defaults to the c locale, which makes tmux mangle tab-separated -f output into underscores, so every pane parsed as one field and the watcher silently saw nothing; invoke tmux with -u so format output is locale-independent. drop the startup fail-fast: the watcher now waits for a tmux server and attaches when one appears, which is the right shape for a login agent. ship the launchd plist under packaging/launchd.
2026-10-19initial dingger: tmux agent watcher with herdr's detection engineIgor Soarez
extract detect-core from herdr's src/detect (engine + 22 manifests, tests unchanged) and add the dingger binary: tmux capture-pane watcher, process-tree agent identification, herdr's notification policy, embedded notification sounds, @agent_state write-back, and catalog-based manifest refresh. viewer screens are non-observations per the engine contract; reviewed by a three-model panel (opus 5.5, gpt 6.1 sol, kimi k3) with all confirmed findings applied as deletions.