aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Soarez <igor@soarez.org>2026-10-26 16:40:50 +0200
committerIgor Soarez <igor@soarez.org>2026-10-26 16:40:50 +0200
commit53e86be78d7a80a114cf265dbf8189606297d040 (patch)
tree4d54e2f714f67e59103793d7f98d535852f366c9
parent185af91b4d525db6c192a050b53615772fb5fe11 (diff)
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.
-rw-r--r--crates/dinggerd/src/hooks.rs12
-rwxr-xr-xtests/e2e.sh11
2 files changed, 23 insertions, 0 deletions
diff --git a/crates/dinggerd/src/hooks.rs b/crates/dinggerd/src/hooks.rs
index 8b24659..a649fa0 100644
--- a/crates/dinggerd/src/hooks.rs
+++ b/crates/dinggerd/src/hooks.rs
@@ -18,6 +18,8 @@
//! order, so a turn that starts and finishes between two polls is still a
//! completion, and each report wakes the watcher immediately.
//!
+//! Each line is answered with `{"ok":true}`.
+//!
//! `{"method":"poke"}` only wakes the watcher; `dingger focus` and tmux's
//! focus-change hooks send it so attendance updates without waiting for the
//! next poll. The last reported state stays authoritative for its pane
@@ -123,8 +125,18 @@ struct HookParams {
}
fn handle_stream(stream: UnixStream) {
+ // Each message gets a reply line: an extension waits for one and sends
+ // the message again without it (herdr's protocol), so a report would
+ // arrive twice and take seconds.
+ let mut replies = stream.try_clone().ok();
for line in BufReader::new(stream).lines() {
let Ok(line) = line else { return };
+ if let Some(out) = replies.as_mut() {
+ use std::io::Write;
+ if out.write_all(b"{\"ok\":true}\n").is_err() {
+ replies = None; // the sender does not read replies
+ }
+ }
let Ok(message) = serde_json::from_str::<HookMessage>(&line) else {
warn!(%line, "invalid hook message");
continue;
diff --git a/tests/e2e.sh b/tests/e2e.sh
index 20ce5dc..e51b18d 100755
--- a/tests/e2e.sh
+++ b/tests/e2e.sh
@@ -909,6 +909,17 @@ if command -v node >/dev/null; then
expect "announced to other extensions" "grep -c '^event dingger:importance {\"level\":\"high\"}$' $EXTLOG" 1
report "$I2" "" normal
expect "imp: set back by hand" "opt $I2 @dingger_importance" ""
+ # Unanswered, the extension sends a report again after 0.5 s, and gives
+ # up on it after 2 s.
+ working() { sed 's/\x1b\[[0-9;]*m//g' "$DIR/daemon.log" | grep -c "hook report pane=$I2 state=Working"; }
+ before=$(working)
+ start=$(python3 -c 'import time; print(time.time())')
+ iext "$I2" agent_start
+ took=$(python3 -c 'import time,sys; print(round(time.time() - float(sys.argv[1]), 2))' "$start")
+ sleep 0.6
+ expect "the daemon answers a report: the extension sends it once" "echo \$((\$(working) - before))" 1
+ expect "and is done with it at once ($took s, with the harness's 0.3 s pause)" \
+ "python3 -c 'import sys; print(float(sys.argv[1]) < 1.5)' $took" True 1
iext "$I2" session_start
expect "a new pi on the session reports it again" "opt $I2 @dingger_importance" high
# pi starts a session before any command runs.