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:
1 parent
4b3134554a
commit
eaa49aaafe
2 files changed
+77
-27
No files matched your search
@@ -240,3 +240,65 @@ def test_wrapper_honours_registry_ack_over_unconfirmed_inject(tmp_path, mock_her
|
||||
assert "agent_blocked" not in events_log.read_text(), events_log.read_text()
|
||||
job_data = json.loads((reg_dir / f"{jid}.json").read_text())
|
||||
assert job_data.get("status") != "pending"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# wrapper: idle-timeout grace (R1) must see a working codex through the pane
|
||||
# ---------------------------------------------------------------------------
|
||||
def test_wrapper_idle_timeout_grace_accepts_pane_busy_token(tmp_path, mock_herdr):
|
||||
"""herdr keeps reporting `idle` for a codex-like agent that is visibly
|
||||
working (busy token in the pane). When the subscriber idles out, the
|
||||
wrapper must keep waiting for the on-disk result instead of closing the
|
||||
job as deadline_timeout mid-turn; the agent then completes out of band."""
|
||||
reg_dir = tmp_path / "jobs"
|
||||
reg_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
state_file = Path(mock_herdr)
|
||||
state = json.loads(state_file.read_text())
|
||||
# `timeout` from agent prompt keeps agent_status idle; the mock appends the
|
||||
# busy token to the pane on submission, exactly like a codex turn.
|
||||
state["agents"]["slow-codex"] = {
|
||||
"agent": "codex",
|
||||
"agent_status": "idle",
|
||||
"prompt_wait_result": "timeout",
|
||||
"buffer": "Ready",
|
||||
}
|
||||
state_file.write_text(json.dumps(state, indent=2))
|
||||
|
||||
env = _base_env(tmp_path, {"SKS_IDLE_TIMEOUT_MS": "500", "SKS_DELIVERY_GRACE_S": "5"})
|
||||
proc = subprocess.Popen(
|
||||
[
|
||||
"bash", str(WRAPPER), "submit",
|
||||
"--agent", "codex",
|
||||
"--agent-session", "herdr:slow-codex",
|
||||
"--prompt", "issue 14 idle grace",
|
||||
"--registry-dir", str(reg_dir),
|
||||
"--workdir", str(tmp_path),
|
||||
"--timeout", "40",
|
||||
"--idle-timeout", "3",
|
||||
],
|
||||
cwd=str(tmp_path), env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
|
||||
)
|
||||
|
||||
jid = ""
|
||||
for _ in range(100):
|
||||
job_files = list(reg_dir.glob("*.json"))
|
||||
if job_files:
|
||||
jid = job_files[0].stem
|
||||
break
|
||||
time.sleep(0.1)
|
||||
assert jid, "job was not registered"
|
||||
|
||||
# Well past the 3 s idle timeout, the agent finishes.
|
||||
time.sleep(14)
|
||||
done = subprocess.run(
|
||||
[sys.executable, str(REGISTRY_PY), "--registry-dir", str(reg_dir),
|
||||
"update", "--job", jid, "--status", "completed"],
|
||||
capture_output=True, text=True, cwd=str(tmp_path), env=env,
|
||||
)
|
||||
assert done.returncode == 0, done.stderr
|
||||
|
||||
stdout, stderr = proc.communicate(timeout=90)
|
||||
job_data = json.loads((reg_dir / f"{jid}.json").read_text())
|
||||
assert job_data.get("status") == "completed", f"status={job_data.get('status')}\n{stdout}\n{stderr}"
|
||||
assert proc.returncode == 0, f"expected rc=0 after the working grace, got {proc.returncode}\n{stdout}\n{stderr}"
|
||||
Reference in new issue
Block a user