fix(hermes): settle Claude prompts in HERDR

This commit is contained in:
jenkins 2026-08-08 22:31:11 -03:00
parent c4e0ec3d60
commit b311afe60b
3 changed files with 70 additions and 2 deletions

View File

@ -24,7 +24,7 @@ spec:
ai.bstein.dev/execution: Herdr-supervised Codex and Claude Code 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/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/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/agent-inject: "true"
vault.hashicorp.com/role: hermes-agent vault.hashicorp.com/role: hermes-agent
vault.hashicorp.com/agent-inject-secret-anthropic-token: kv/data/atlas/hermes/agent-tokens vault.hashicorp.com/agent-inject-secret-anthropic-token: kv/data/atlas/hermes/agent-tokens

View File

@ -8,6 +8,7 @@ import json
import os import os
import re import re
import subprocess import subprocess
import time
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@ -16,6 +17,7 @@ ALLOWED_EFFORTS = ("low", "medium", "high", "xhigh")
ROUTING_PATH = Path("/opt/data/workspace/coordinator/model-routing.json") ROUTING_PATH = Path("/opt/data/workspace/coordinator/model-routing.json")
HERDR_BIN = Path("/opt/data/tools/bin/herdr") HERDR_BIN = Path("/opt/data/tools/bin/herdr")
CODEX_AUTH = Path("/opt/data/home/.codex/auth.json") CODEX_AUTH = Path("/opt/data/home/.codex/auth.json")
CLAUDE_PROMPT_SETTLE_SECONDS = 3
def _load_routes(path: Path) -> dict[str, Any]: def _load_routes(path: Path) -> dict[str, Any]:
@ -178,8 +180,28 @@ def launch_worker(
started = _run(command, env) started = _run(command, env)
result = {**plan, "agent": agent_name, "pane": pane, "started": started} result = {**plan, "agent": agent_name, "pane": pane, "started": started}
if prompt: 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( 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 return result

View File

@ -49,6 +49,52 @@ def test_herdr_plan_chooses_task_shape_and_caps_effort():
dispatch.select_plan(status, "review", "max") 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): def test_auth_patch_honors_explicit_shared_store(tmp_path: Path):
source = tmp_path / "auth.py" source = tmp_path / "auth.py"
destination = tmp_path / "patched" / "auth.py" destination = tmp_path / "patched" / "auth.py"