all repos — dingger @ 9eddef09c769b315f4eabef6eaf4bb58e70d0966

Watches the AI coding agents in your tmux panes on macOS and tells you which need you, from the menu bar, a floating panel, the status line and notifications; jumps to them, and restores tmux sessions and agents after a crash

9eddef09
menu, panel, search: a peach ! in a column of its own; low dimmed more

the ! was the color of the name, read as part of it, and pushed high
names two characters out of line. it is now peach (herdr's palette, no
status uses it) in a fixed-width column every row has, so names line up.
a low agent's name is dimmed to 0.5 (0.7 was hard to see).
Igor Soarez igor@soarez.org
Tue, 27 Oct 2026 15:27:14 +0200
a7e065cd
pi: no footer line for the importance

it took a line of its own in pi's footer. pi says the level when it is
set, and /importance alone shows it; pi clears the old line at /reload.
Igor Soarez igor@soarez.org
Tue, 27 Oct 2026 14:25:44 +0200
7455ddfc
docs: mark as unread does not work from inside claude code
Igor Soarez igor@soarez.org
Tue, 27 Oct 2026 14:20:08 +0200
50c433e8
mark as unread: make an idle agent done again, to come back to it

like "mark as unread" in mail: an agent you have seen (idle) becomes done
again, last among done agents of its importance, with no sound or
notification. it stays done until you come back to it: leave its tmux
window and return, or jump to it. marked while you look at it, it holds
until you look away (`held`); a new turn clears it. only idle agents can
be marked.

- daemon: `pane.mark_unread` on the hook socket; the watcher applies it
  after the tick's observations (mark_unread, advance's hold rule).
- `dingger unread [--pane]`: refuses done, working, blocked, and panes
  without an agent; returns once the agent shows as done.
- pi: /unread (refused while the agent works or is blocked).
- panel: right-click an idle agent in the full list, "Mark as unread".
- search: ⌘U or right-click marks the selected idle agent; the footer
  offers ⌘U when it applies; search stays open.

tests: unit (hold, jump, new turn, idle only), e2e step 22 (queue
position, hold, look away and back, jump, refusals, no sound, menu, the
extension), pi-live in real pi 1.4 (a mark, not a turn: an unknown
command would also end done), swift selftest (menu only for idle, footer).
Igor Soarez igor@soarez.org
Tue, 27 Oct 2026 14:12:28 +0200
9f3a567d
roadmap: the extended-keys warning is gone
Igor Soarez igor@soarez.org
Tue, 27 Oct 2026 12:15:11 +0200
a2378a3e
panel: a click that moves a point or two is still a click

any pointer movement between press and release started a panel drag and
swallowed the click, so clicking a row often did nothing and had to be
repeated: in a logged session, 12 of 17 clicks were taken as drags. the
5 that counted waited at most 4 ms and ran `dingger focus` in 60-120 ms.
a drag now needs more than 4 points of movement (isDrag, selftest).
Igor Soarez igor@soarez.org
Tue, 27 Oct 2026 12:15:03 +0200
53e86be7
hooks: answer each message, so the extension sends a report once

the pi extension (herdr's protocol) waits for a reply line and sends a
report again after 0.5 s without one; dingger never replied. every report
arrived twice and took about 2 s, delaying the next one (the live log: 196
of 400 reports repeated the previous one, 0.49 s later). the daemon now
answers each line with {"ok":true}; senders that do not read replies
(dingger poke, importance, the claude hook) are unaffected.

e2e step 21: one report, done in 0.38 s; without replies, two, 2.08 s.
Igor Soarez igor@soarez.org
Mon, 26 Oct 2026 16:40:50 +0200
185af91b
importance: mark an agent high, normal, or low

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.
Igor Soarez igor@soarez.org
Mon, 26 Oct 2026 16:32:18 +0200
1eb0ac37
readme: how to add the tmux snippet, and what it changes

added by hand, at the end of the tmux config. it replaces status-right,
turns on focus-events, adds hooks at index 77 and a status-line click
binding (needs mouse on); changes apply at the next reload.
Igor Soarez igor@soarez.org
Sun, 25 Oct 2026 20:58:35 +0200
5d7ed017
terminal commands are silent when tmux runs them

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.
Igor Soarez igor@soarez.org
Sun, 25 Oct 2026 07:44:25 +0200
5fe10900
roadmap: pi's status reports, dialogs, cancelled and failed runs, extended-keys

items from pi 1.0.4 to 1.4.0 worth using, and from testing pi 1.4. preview
in agent search moves last.
Igor Soarez igor@soarez.org
Sun, 25 Oct 2026 06:43:25 +0200
b7742719
terminals: ask Ghostty only once focus has settled

across a Space switch (a full screen window) tmux sees the new client
focused about 0.3 s before Ghostty's front window changes, so learn paired
the client with the terminal that had focus. live, raising a full screen
window and back crossed the two pairings, and from then on every jump
raised the wrong window (old binary: 0 of 10 jumps right).

learn now waits a second before asking Ghostty, then still requires the
client to be focused. it also drops pairings of clients no longer attached,
which piled up with every closed window.

e2e step 20: Ghostty lagging behind tmux's focus, crossed pairings healing,
pruning; the old learn fails all five. live, two Ghostty windows, one full
screen: 16 of 16 jumps right, starting from crossed pairings.
Igor Soarez igor@soarez.org
Sun, 25 Oct 2026 06:41:34 +0200
dd3462c3
tests/pi-live.sh: check a real pi release against dingger

runs a real pi binary on a tmux server and dingger daemon of its own, with
a fake OpenAI-compatible model (tests/fake-model.py, reply delay set by the
prompt) and its own pi agent directory: no network, no credentials, the
user's pi untouched. plain shells, and a stop if the panes would not run
the pi under test (a login shell's profile puts the user's pi first).

it checks identification, the extension's state and session reports, a
turn (working, then done), /reload, an extension's blocked event, screen
detection for a pi without the extension, restore by session file, and
exit.

pi 1.4.0 and 1.0.3: 23 of 23 each. a dingger whose extension listens for a
renamed event fails it. in both, a pi confirm dialog shows as not blocked:
pi tells terminals (OSC 7501, which tmux does not forward) but not
extensions.
Igor Soarez igor@soarez.org
Sat, 24 Oct 2026 14:41:49 +0300
e4d3bc24
raise the terminal window that holds the tmux client

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.
Igor Soarez igor@soarez.org
Fri, 23 Oct 2026 19:11:23 +0300
b4f45632
session groups: list each pane once, save each group once

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.
Igor Soarez igor@soarez.org
Fri, 23 Oct 2026 18:45:06 +0300
12ce9f3f
list working and idle agents most recent first

within a status, the menu, panel, search, and `dingger status` listed done
and blocked agents oldest first (the queue), and the rest by place. now
working and idle agents are most recent first: the first idle row is the
agent that finished, or that the user looked at, last.

@dingger_since now means when the pane entered its current status, for
every status (it was set only while the pane needed the user), and
survives a daemon restart. the queue selects done and blocked panes by
status, no longer by having a stamp. one ordering rule each in Rust
(watcher::list_order) and Swift (listedBefore), with tests that fail on
the old order; e2e step 11 makes an agent finish while watched and checks
it moves to the top of the idle rows.

agents idle since before this change share one stamp and keep their place
order until they next change.
Igor Soarez igor@soarez.org
Fri, 23 Oct 2026 17:58:24 +0300
7520141a
restore Claude Code agents in their own sessions

`dingger install claude` adds a SessionStart hook to Claude's settings
(every other key and hook kept, in order; the original backed up; written
through a symlink; installing again changes nothing). The hook is
`dingger hook claude`: it reports the session's transcript for its tmux
pane, prints nothing (Claude adds SessionStart output to the conversation)
and always exits 0. Claude's state still comes from its screen.

restore types `claude --resume <id>` in the directory the session ran in
(elsewhere, Claude moves the conversation there). it resumes an agent only
when its session file exists: a Claude session that never got a message
has no transcript, and `--resume` fails ("No conversation found"). the
snapshot now keeps an agent's command line too, so such a pane gets it
retyped instead of nothing.

- the hook receiver takes a report without a state: only the session.
- a report that arrives after the watcher listed the panes is not dropped
  as "agent gone": Claude reports once, as it starts.
- serde_json preserve_order (indexmap is already a dependency, via toml).

e2e step 18: install against a settings file like a real one, a fake
claude running the exact installed hook command, resume by id, retype
without a transcript, the resumed agent reporting again. a server that
restore starts now gets the test's plain shells: it got the user's login
shell, whose profile put the real claude first, which started (stuck at
its trust dialog) during development. every test restore now checks that
no real pi or claude runs in a test pane.
Igor Soarez igor@soarez.org
Fri, 23 Oct 2026 15:08:53 +0300
a7d34521
write a pane's options in one tmux command list

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.
Igor Soarez igor@soarez.org
Fri, 23 Oct 2026 14:07:15 +0300
e05ad549
restore: log an offer's withdrawal only when one was offered
Igor Soarez igor@soarez.org
Fri, 23 Oct 2026 14:01:35 +0300
9c9c9dad
restore, step 3: offer it in the panel, menu, and status line

when tmux stops (and its sessions are missing from the running server, or
no server runs), the daemon publishes the restore offer: offer.json for the
menu bar app and @dingger_restore for the status line, withdrawn once
restored or dismissed.

- panel: a first row "↺ Restore 9 sessions · 22 agents" with when it was
  saved and how many agents were mid-turn; clicking restores, "Not now"
  dismisses. an offer shows the panel like a waiting agent; off stays off.
- menu: the offer on top; restore all, one session, or not now.
- status line: a clickable blue ↺N before the clock.
- restore runs through the user's login shell, as a terminal starts tmux,
  so restored panes get a real environment, not launchd's; --report writes
  restore.log and notifies a summary. restore --dismiss.
- commands log only warnings by default; SIGPIPE ends them quietly.

e2e: the offer through the real menu bar binary, the summary notification,
the offer clearing, a restore with no tmux running, and not now staying
dismissed when a terminal starts tmux. the second test daemon is started
directly (it leaked through a function's $! before, and dozens of leaked
daemons fought over the test socket and loaded the machine); each run uses
its own socket and checks its daemons stop.
Igor Soarez igor@soarez.org
Fri, 23 Oct 2026 13:59:59 +0300
c082f504
restore, step 2: resume agents, retype command lines

- 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.
Igor Soarez igor@soarez.org
Fri, 23 Oct 2026 13:20:11 +0300
0f06b0b1
restore, step 1: layout snapshots and dingger restore

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.
Igor Soarez igor@soarez.org
Fri, 23 Oct 2026 13:01:52 +0300
de1a3ce4
roadmap: restore, raise the right window, search preview
Igor Soarez igor@soarez.org
Fri, 23 Oct 2026 12:37:22 +0300
f4a7346f
agent search: spotlight-style fuzzy finder on ctrl+opt+cmd+shift+u

a centered, non-activating key panel that lists every agent and jumps to
the chosen one through the same dingger focus path as a row click.

- fzf-style matching: each word must match one of name, session, window,
  harness, status, or directory, letters in order; the best alignment wins
  (word starts and consecutive runs score higher), matched title letters
  are highlighted.
- empty query keeps the menu's order, so enter jumps to the top waiting
  agent.
- the selection follows the agent, not the position, as states change;
  the list scrolls to keep it visible.
- keys: arrows, ctrl+n/p, ctrl+j/k, enter, esc, ctrl+u; the shortcut again
  or clicking elsewhere closes it.
- search_hotkey setting; an unedited config file is upgraded. menu item
  "Search Agents...". rows carry the pane's directory.
- --print-search and --snapshot-search debug flags; the panel's background
  and snapshot code are shared.

selftest covers matching, ranking, highlighting, and selection; e2e step
15 checks queries through the real binary (fails when search only looks at
titles). e2e resolves relative binary paths, which tmux hooks need.
Igor Soarez igor@soarez.org
Thu, 22 Oct 2026 18:01:04 +0300
7649396b
panel: on/off is persistent; what it shows is a separate setting

hiding the panel with the shortcut used to last only until another agent
started waiting: one action meant both "not now" and "off", with an
invisible override on top of a three-way mode. now there are two visible
settings: the panel is on or off (ctrl+opt+cmd+e and a checkmarked "Show
Panel" item), and while on it shows always or only when agents are waiting
("Panel Shows"). off stays off. turning it on with nothing to show peeks
for 3 seconds. the old mode setting migrates.

selftest covers the rules; it fails if a waiting agent can show a panel
that is off.
Igor Soarez igor@soarez.org
Thu, 22 Oct 2026 00:02:16 +0300
aa4d6fcd
fix clicks from the menu bar app: find tmux without homebrew on PATH

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.
Igor Soarez igor@soarez.org
Tue, 20 Oct 2026 15:01:18 +0300
eed9c9c1
panel: readable everywhere, compact/full toggle, change-driven; jump shortcut

- contrast: the summary used attributed-string colors (never vibrant) and
  the subtitles a vibrant system color, so they blended differently over
  light and dark desktops. the panel now uses herdr's mocha colors
  explicitly over a dark tint, identical on any background.
- layout: compact in "when agents are waiting"; in "always" a show all /
  show less toggle switches to the full list (waiting, separator, rest).
- rows are single-line with a trailing ellipsis instead of wrapping.
- render only when the content changes, and log each render, instead of
  rebuilding every view every second.
- --snapshot-panel renders the real panel offscreen over white and black
  after a different first render (stale content would show).
- jump shortcut ctrl+opt+cmd+u (jump_hotkey) runs dingger focus; shortcuts
  are registered by id. an unedited first config file is upgraded.

selftest covers the layout rules and content; e2e step 13 checks the full
list, its separator, and the toggle labels.
Igor Soarez igor@soarez.org
Tue, 20 Oct 2026 14:51:07 +0300
0f843854
floating panel: always-on-top agent HUD with a global shortcut

a non-activating HUD panel (every Space, over full-screen apps, never takes
focus) listing the agents waiting for you, with working and idle summarized
on one line; rows jump to the agent, drag moves the panel and it remembers
its place. modes: when agents are waiting (default), always, off; ctrl+opt+
cmd+e shows or hides it on top of the mode, and a manual hide lasts until
another agent starts waiting. the shortcut is a carbon hotkey (no
accessibility permission; clashes are reported in the menu) configured in
~/.config/dingger/config.toml, reloaded on save. the menu bar app is split
into model, panel, hotkey and main.

tested: --selftest checks the visibility rules (it fails if a newly waiting
agent no longer re-shows a dismissed panel); e2e step 13 checks the panel
content through the real binary.
Igor Soarez igor@soarez.org
Tue, 20 Oct 2026 14:24:11 +0300
6bccc5f7
menu rows lead with the agent's session name; harness and place move below

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.
Igor Soarez igor@soarez.org
Tue, 20 Oct 2026 13:59:29 +0300
3731a803
pi extension: identify the pane from TMUX_PANE

the extension asked tmux for "the current pane", which resolves to the
session's active pane, not the pane pi runs in, so agents in background
windows reported their state onto another pane. tmux sets TMUX_PANE for
every process it starts in a pane; use it.

e2e step 12 loads the shipped extension with a fake pi from a pane that
is not its session's active pane and asserts the report lands there; it
fails against the previous extension. hook reports log at info again.
Igor Soarez igor@soarez.org
Tue, 20 Oct 2026 13:04:18 +0300
4af6a4f6
rebuild the status model as herdr's state machine, event-driven and tested

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
Igor Soarez igor@soarez.org
Tue, 20 Oct 2026 12:43:07 +0300
943fc940
write @agent_state on attendance changes, not just transitions

the seen demotion (done -> idle when the user focuses the pane) only
updated the daemon's internal track; @agent_state stayed done forever,
so the menu bar and status line never reflected attendance. the display
status is now written whenever it differs from the pane option,
regardless of what triggered it.
Igor Soarez igor@soarez.org
Tue, 20 Oct 2026 12:06:03 +0300
3509e48c
adopt herdr's five-status model with symbols style and focus detection

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.
Igor Soarez igor@soarez.org
Tue, 20 Oct 2026 11:53:23 +0300
fed066bc
menu bar: herdr-style colored state glyphs; tmux segment before the clock

the design panel's monochrome template rendering made every row look
alike; restore herdr's agent-panel visual language - green ready, yellow
working, red blocked, filled when unseen in the queue. the tmux segment
moves left of the clock so it is visible at a glance; the status item
logs what it observes for diagnosis.
Igor Soarez igor@soarez.org
Mon, 19 Oct 2026 19:30:03 +0300
7081a103
menu rows show window names, not indexes

display target is session:window-name (pane index only when split);
switching keeps using the pane id. add --print debug mode to inspect the
rendered rows without opening the menu.
Igor Soarez igor@soarez.org
Mon, 19 Oct 2026 19:14:17 +0300
ea1dda2f
ready-queue fifo with click-to-focus; redesign presentation per design panel

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.
Igor Soarez igor@soarez.org
Mon, 19 Oct 2026 16:55:30 +0300
9c6b1042
menu bar app: live agent states from tmux pane options

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.
Igor Soarez igor@soarez.org
Mon, 19 Oct 2026 16:35:40 +0300
3bace4ac
wait for tmux at startup, force utf-8 for tmux clients, add launchd packaging

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.
Igor Soarez igor@soarez.org
Mon, 19 Oct 2026 16:19:10 +0300
ab33633c
initial dingger: tmux agent watcher with herdr's detection engine

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.
Igor Soarez igor@soarez.org
Mon, 19 Oct 2026 16:02:57 +0300