aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorIgor Soarez <igor@soarez.org>2026-10-19 16:35:40 +0300
committerIgor Soarez <igor@soarez.org>2026-10-19 16:35:40 +0300
commit9c6b10426bd897afe6cf6f0c022f45310c5ab3d3 (patch)
tree1002c7fe76bfd2980773b1f0826b6452e5dc99ad /crates
parent3bace4ac392fb914263377369383f5ac23111c61 (diff)
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.
Diffstat (limited to 'crates')
-rw-r--r--crates/dinggerd/src/tmux.rs16
-rw-r--r--crates/dinggerd/src/watcher.rs20
2 files changed, 28 insertions, 8 deletions
diff --git a/crates/dinggerd/src/tmux.rs b/crates/dinggerd/src/tmux.rs
index e833e3c..edd44aa 100644
--- a/crates/dinggerd/src/tmux.rs
+++ b/crates/dinggerd/src/tmux.rs
@@ -4,7 +4,6 @@ use std::process::Command;
use detect_core::manifest::agent_state_label;
use detect_core::{identify_agent, Agent, AgentState};
-use tracing::debug;
#[derive(Debug, Clone)]
pub struct Pane {
@@ -25,6 +24,8 @@ pub struct Pane {
/// The `@agent_state` user option previously written for this pane,
/// read straight from the pane format.
pub written_state: String,
+ /// The `@agent` user option (agent label) previously written, if any.
+ pub written_agent: String,
}
impl Pane {
@@ -68,13 +69,14 @@ pub fn list_panes() -> Result<Vec<Pane>, String> {
"#{session_attached}\t",
"#{pane_start_command}\t",
"#{pane_pid}\t",
- "#{@agent_state}"
+ "#{@agent_state}\t",
+ "#{@agent}"
);
let out = run_tmux(&["list-panes", "-a", "-F", format])?;
let mut panes = Vec::new();
for line in out.lines() {
let fields: Vec<&str> = line.split('\t').collect();
- if fields.len() < 10 {
+ if fields.len() < 11 {
continue;
}
let current_command = fields[2].to_string();
@@ -101,6 +103,7 @@ pub fn list_panes() -> Result<Vec<Pane>, String> {
session_attached: fields[6] != "0",
agent,
written_state: fields[9].to_string(),
+ written_agent: fields[10].to_string(),
});
}
Ok(panes)
@@ -180,3 +183,10 @@ pub fn set_agent_state(pane_id: &str, state: AgentState) -> Result<(), String> {
])
.map(|_| ())
}
+
+/// Label the pane with the agent running in it, for tmux status-line and
+/// menu-bar rendering; clears the label when the pane stops running one.
+pub fn set_agent(pane_id: &str, agent: Option<Agent>) -> Result<(), String> {
+ let label = agent.map(detect_core::agent_label).unwrap_or("");
+ run_tmux(&["set-option", "-p", "-t", pane_id, "@agent", label]).map(|_| ())
+}
diff --git a/crates/dinggerd/src/watcher.rs b/crates/dinggerd/src/watcher.rs
index 56107d6..f039456 100644
--- a/crates/dinggerd/src/watcher.rs
+++ b/crates/dinggerd/src/watcher.rs
@@ -113,6 +113,20 @@ impl Watcher {
debug!(target = %pane.target, "observation skipped (viewer screen)");
continue;
};
+
+ // Keep the pane's agent label current even when the state is
+ // unchanged (e.g. an agent swap between two idle panes).
+ let agent_label = pane
+ .agent
+ .map(detect_core::agent_label)
+ .unwrap_or("")
+ .to_string();
+ if pane.written_agent != agent_label {
+ if let Err(err) = tmux::set_agent(&pane.pane_id, pane.agent) {
+ warn!(target = %pane.target, %err, "failed to write @agent");
+ }
+ }
+
if new_state == prev_state {
continue;
}
@@ -123,11 +137,7 @@ impl Watcher {
warn!(target = %pane.target, %err, "failed to write @agent_state");
}
- let agent_label = pane
- .agent
- .map(detect_core::agent_label)
- .unwrap_or("agent")
- .to_string();
+
if let Some(kind) = notification_kind_for_transition(prev_state, new_state) {
if pane.suppresses_notifications() {
debug!(target = %pane.target, "suppressed (active pane)");