From b311afe60b17bf27baced05fe824fc7df270a0bc Mon Sep 17 00:00:00 2001 From: jenkins Date: Sat, 8 Aug 2026 22:31:11 -0300 Subject: [PATCH] fix(hermes): settle Claude prompts in HERDR --- services/hermes/agent-deployment.yaml | 2 +- services/hermes/scripts/herdr_dispatch.py | 24 +++++++++++- testing/tests/test_hermes_herdr.py | 46 +++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/services/hermes/agent-deployment.yaml b/services/hermes/agent-deployment.yaml index b61afdc6b..b551abef5 100644 --- a/services/hermes/agent-deployment.yaml +++ b/services/hermes/agent-deployment.yaml @@ -24,7 +24,7 @@ spec: ai.bstein.dev/execution: Herdr-supervised Codex and Claude Code ai.bstein.dev/model-policy: difficulty-aware low through xhigh, cross-provider fallback ai.bstein.dev/placement: rpi5 preferred; Jetson deferred until state storage is available - ai.bstein.dev/config-rev: "20260808-herdr-browser-tui" + ai.bstein.dev/config-rev: "20260808-herdr-browser-tui-prompt-ready" vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: hermes-agent vault.hashicorp.com/agent-inject-secret-anthropic-token: kv/data/atlas/hermes/agent-tokens diff --git a/services/hermes/scripts/herdr_dispatch.py b/services/hermes/scripts/herdr_dispatch.py index 4822e8db0..1a21ea8ea 100644 --- a/services/hermes/scripts/herdr_dispatch.py +++ b/services/hermes/scripts/herdr_dispatch.py @@ -8,6 +8,7 @@ import json import os import re import subprocess +import time from pathlib import Path from typing import Any @@ -16,6 +17,7 @@ ALLOWED_EFFORTS = ("low", "medium", "high", "xhigh") ROUTING_PATH = Path("/opt/data/workspace/coordinator/model-routing.json") HERDR_BIN = Path("/opt/data/tools/bin/herdr") CODEX_AUTH = Path("/opt/data/home/.codex/auth.json") +CLAUDE_PROMPT_SETTLE_SECONDS = 3 def _load_routes(path: Path) -> dict[str, Any]: @@ -178,8 +180,28 @@ def launch_worker( started = _run(command, env) result = {**plan, "agent": agent_name, "pane": pane, "started": started} if prompt: + # Claude reports interactive readiness just before its prompt finishes + # drawing. Give that terminal a moment so the submitted Enter is not lost. + if plan["worker"] == "claude": + time.sleep(CLAUDE_PROMPT_SETTLE_SECONDS) result["prompted"] = _run( - [str(HERDR_BIN), "agent", "prompt", agent_name, prompt], env + [ + str(HERDR_BIN), + "agent", + "prompt", + agent_name, + prompt, + "--wait", + "--until", + "working", + "--until", + "done", + "--until", + "blocked", + "--timeout", + "15000", + ], + env, ) return result diff --git a/testing/tests/test_hermes_herdr.py b/testing/tests/test_hermes_herdr.py index 91d7f2441..7ae8d7062 100644 --- a/testing/tests/test_hermes_herdr.py +++ b/testing/tests/test_hermes_herdr.py @@ -49,6 +49,52 @@ def test_herdr_plan_chooses_task_shape_and_caps_effort(): dispatch.select_plan(status, "review", "max") +def test_claude_worker_waits_for_prompt_readiness(tmp_path: Path, monkeypatch): + herdr = tmp_path / "herdr" + herdr.touch() + project = tmp_path / "project" + project.mkdir() + calls = [] + sleeps = [] + + def fake_run(command, env): + calls.append(command) + if command[1:3] == ["workspace", "create"]: + return {"result": {"root_pane": {"pane_id": "w2:p1"}}} + return {"result": {"ok": True}} + + monkeypatch.setattr(dispatch, "HERDR_BIN", herdr) + monkeypatch.setattr(dispatch, "_run", fake_run) + monkeypatch.setattr(dispatch.time, "sleep", sleeps.append) + plan = { + "worker": "claude", + "model": "claude-haiku-4-5-20251001", + "effort": "low", + } + + dispatch.launch_worker(plan, project, "review", "Check the implementation.") + + assert sleeps == [dispatch.CLAUDE_PROMPT_SETTLE_SECONDS] + prompt = calls[-1] + assert prompt[1:5] == [ + "agent", + "prompt", + "claude-review", + "Check the implementation.", + ] + assert prompt[-9:] == [ + "--wait", + "--until", + "working", + "--until", + "done", + "--until", + "blocked", + "--timeout", + "15000", + ] + + def test_auth_patch_honors_explicit_shared_store(tmp_path: Path): source = tmp_path / "auth.py" destination = tmp_path / "patched" / "auth.py"