fix(issue-14): keep the idle-timeout working grace alive on the pane busy token

The R1 grace after a subscriber idle timeout only asked herdr whether the
agent was still `working`. herdr never reports that for codex, so a codex
review longer than the idle timeout was closed as deadline_timeout mid-turn
and its eventual `completed` refused - the same status blindness behind the
agent_blocked misjudgement. `_agent_still_working` now also accepts the pane's
own busy token, the evidence send_keys_safe already uses to confirm delivery.

Adds a wrapper test where a codex-like agent (idle per herdr, busy per pane)
idles out the subscriber and completes out of band afterwards.

Refs #14

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
GodopuandClaude Fable 5.1 committed 2026-09-12 23:26:30 +09:00
1 parent 4b3134554a
commit eaa49aaafe
2 files changed
+77 -27

No files matched your search

@@ -585,6 +585,17 @@ _inject_acknowledged() {
done
}
# ISSUE-14: herdr's agent_status is blind for some agents (codex stays `idle`
# for the whole turn), so "still working" must also accept the pane's own busy
# token - the same evidence send_keys_safe uses to confirm delivery. Without it
# a codex review longer than the idle timeout was closed as deadline_timeout
# mid-turn and its eventual `completed` refused.
_agent_still_working() {
local sess="$1" target="$2"
[ "$(_sks_agent_status "$sess" "$target")" = "working" ] && return 0
_pane_tail "$sess" 8 2>/dev/null | grep -Fq "esc to interrupt"
}
_map_sub_rc() {
local sub_rc="$1" job_id="$2"
local job_status="running"
@@ -661,23 +672,12 @@ finalize_unfinished() {
local sess="${target_sess#herdr:}"
local target_agent
target_agent=$(_sanitize_herdr_agent_name "$sess")
local agent_st=""
local still_working=0
if [ "$sess" != "human" ] && [ -n "$sess" ]; then
local raw_st
raw_st="$(_sks_herdr agent get "$target_agent" 2>/dev/null || _sks_herdr agent get "$sess" 2>/dev/null || true)"
if [ -n "$raw_st" ]; then
agent_st="$("$(_delegate_py_bin)" -c '
import sys, json
try:
d = json.loads(sys.stdin.read())
print((d.get("result", {}).get("agent", {}) or {}).get("agent_status", "") or "")
except Exception:
print("")
' <<< "$raw_st" 2>/dev/null || true)"
fi
_agent_still_working "$sess" "$target_agent" && still_working=1
fi
if [ "$agent_st" = "working" ]; then
if [ "$still_working" = "1" ]; then
local now elapsed remaining
now=$(date +%s)
elapsed=$(( now - ${START_TIME:-now} ))
@@ -692,19 +692,7 @@ except Exception:
[ "$disk_st" = "completed" ] && return 0
break
fi
local cur_raw cur_st=""
cur_raw="$(_sks_herdr agent get "$target_agent" 2>/dev/null || _sks_herdr agent get "$sess" 2>/dev/null || true)"
if [ -n "$cur_raw" ]; then
cur_st="$("$(_delegate_py_bin)" -c '
import sys, json
try:
d = json.loads(sys.stdin.read())
print((d.get("result", {}).get("agent", {}) or {}).get("agent_status", "") or "")
except Exception:
print("")
' <<< "$cur_raw" 2>/dev/null || true)"
fi
if [ "$cur_st" != "working" ]; then
if ! _agent_still_working "$sess" "$target_agent"; then
sleep 1
disk_st="$(_disk_status "$job_id")"
if [ "$disk_st" = "completed" ] || [ "$disk_st" = "error" ] || [ "$disk_st" = "cancelled" ]; then