diff --git a/services/hermes/agent-deployment.yaml b/services/hermes/agent-deployment.yaml index b551abef5..326b1c70f 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-prompt-ready" + ai.bstein.dev/config-rev: "20260808-herdr-browser-tui-prompt-marker" 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 1a21ea8ea..94889e140 100644 --- a/services/hermes/scripts/herdr_dispatch.py +++ b/services/hermes/scripts/herdr_dispatch.py @@ -8,7 +8,6 @@ import json import os import re import subprocess -import time from pathlib import Path from typing import Any @@ -17,7 +16,10 @@ 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 +PROMPT_READY_MARKERS = { + "codex": "OpenAI Codex", + "claude": "accept edits on", +} def _load_routes(path: Path) -> dict[str, Any]: @@ -180,10 +182,25 @@ 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) + # Herdr can detect the process before the full-screen prompt has finished + # drawing. Wait for a pinned CLI marker so the submitted Enter is not lost. + _run( + [ + str(HERDR_BIN), + "pane", + "wait-output", + pane, + "--match", + PROMPT_READY_MARKERS[plan["worker"]], + "--source", + "recent", + "--lines", + "120", + "--timeout", + "30000", + ], + env, + ) result["prompted"] = _run( [ str(HERDR_BIN), diff --git a/testing/tests/test_hermes_herdr.py b/testing/tests/test_hermes_herdr.py index 7ae8d7062..198ccfbb7 100644 --- a/testing/tests/test_hermes_herdr.py +++ b/testing/tests/test_hermes_herdr.py @@ -55,7 +55,6 @@ def test_claude_worker_waits_for_prompt_readiness(tmp_path: Path, monkeypatch): project = tmp_path / "project" project.mkdir() calls = [] - sleeps = [] def fake_run(command, env): calls.append(command) @@ -65,7 +64,6 @@ def test_claude_worker_waits_for_prompt_readiness(tmp_path: Path, monkeypatch): 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", @@ -74,7 +72,20 @@ def test_claude_worker_waits_for_prompt_readiness(tmp_path: Path, monkeypatch): dispatch.launch_worker(plan, project, "review", "Check the implementation.") - assert sleeps == [dispatch.CLAUDE_PROMPT_SETTLE_SECONDS] + ready = calls[-2] + assert ready[1:] == [ + "pane", + "wait-output", + "w2:p1", + "--match", + "accept edits on", + "--source", + "recent", + "--lines", + "120", + "--timeout", + "30000", + ] prompt = calls[-1] assert prompt[1:5] == [ "agent",