chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-09-04: - update .custodian-brief.md for rein-aharness Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a06ba0-10aa-7ea0-b20a-4f3fac39efe9
This commit is contained in:
parent
7641fcde40
commit
f01b765668
20 changed files with 1027 additions and 165 deletions
|
|
@ -33,6 +33,11 @@ from llm_connect.claude_code import ClaudeCodeAdapter
|
|||
from llm_connect.exceptions import LLMSubprocessError, LLMTimeoutError
|
||||
from llm_connect.models import LLMResponse, RunConfig
|
||||
|
||||
from rein_aharness.execution_cancel import (
|
||||
ExecutionCancel,
|
||||
ExecutionCancelled,
|
||||
resolve_cancel,
|
||||
)
|
||||
from rein_aharness.profiles import ToolProfile, get_profile
|
||||
|
||||
# Backward-compatible alias for the seed profile allow-list string.
|
||||
|
|
@ -41,6 +46,29 @@ ALLOWED_TOOLS = get_profile("green-commit-only").allowed_tools
|
|||
ToolEventCallback = Callable[[dict[str, Any]], None]
|
||||
|
||||
|
||||
def _kill_process(proc: subprocess.Popen[str] | Any) -> None:
|
||||
poll = getattr(proc, "poll", None)
|
||||
if callable(poll):
|
||||
try:
|
||||
status = poll()
|
||||
except Exception:
|
||||
status = None
|
||||
if isinstance(status, int):
|
||||
return
|
||||
kill = getattr(proc, "kill", None)
|
||||
if callable(kill):
|
||||
try:
|
||||
kill()
|
||||
except Exception:
|
||||
return
|
||||
wait = getattr(proc, "wait", None)
|
||||
if callable(wait):
|
||||
try:
|
||||
wait(timeout=2)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
|
||||
def _is_tool_event(event: dict[str, Any]) -> bool:
|
||||
"""True for tool_use/tool_result content blocks and hook lifecycle events.
|
||||
|
||||
|
|
@ -64,11 +92,13 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
|
|||
*,
|
||||
tool_profile: str | ToolProfile = "green-commit-only",
|
||||
on_tool_event: ToolEventCallback | None = None,
|
||||
cancel: ExecutionCancel | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self._workdir = workdir
|
||||
self._on_tool_event = on_tool_event
|
||||
self._cancel = cancel
|
||||
if isinstance(tool_profile, ToolProfile):
|
||||
self._profile = tool_profile
|
||||
else:
|
||||
|
|
@ -105,27 +135,23 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
|
|||
return response
|
||||
|
||||
def _execute_blocking(self, cmd: list[str], prompt: str, timeout: int) -> LLMResponse:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
input=prompt,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
cwd=self._workdir,
|
||||
)
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
raise LLMTimeoutError(
|
||||
f"claude CLI timed out after {timeout}s", cause=exc
|
||||
) from exc
|
||||
if result.returncode != 0:
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
cwd=self._workdir,
|
||||
)
|
||||
stdout, stderr = self._wait_for_process(proc, prompt, timeout)
|
||||
if proc.returncode != 0:
|
||||
raise LLMSubprocessError(
|
||||
f"claude CLI exited with code {result.returncode}",
|
||||
return_code=result.returncode,
|
||||
stderr=result.stderr,
|
||||
f"claude CLI exited with code {proc.returncode}",
|
||||
return_code=proc.returncode,
|
||||
stderr=stderr,
|
||||
)
|
||||
return LLMResponse(
|
||||
content=result.stdout,
|
||||
content=stdout,
|
||||
model=self._model or "claude-code-cli",
|
||||
usage={},
|
||||
finish_reason="stop",
|
||||
|
|
@ -170,12 +196,7 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
|
|||
proc.stdin.write(prompt)
|
||||
proc.stdin.close()
|
||||
reader_thread.start()
|
||||
try:
|
||||
returncode = proc.wait(timeout=timeout)
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
proc.kill()
|
||||
proc.wait()
|
||||
raise LLMTimeoutError(f"claude CLI timed out after {timeout}s", cause=exc) from exc
|
||||
returncode = self._wait_for_process(proc, None, timeout, communicate=False)
|
||||
reader_thread.join(timeout=5)
|
||||
stderr = proc.stderr.read() if proc.stderr else ""
|
||||
|
||||
|
|
@ -200,6 +221,38 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
|
|||
},
|
||||
)
|
||||
|
||||
def _wait_for_process(
|
||||
self,
|
||||
proc: subprocess.Popen[str],
|
||||
prompt: str | None,
|
||||
timeout: int,
|
||||
*,
|
||||
communicate: bool = True,
|
||||
) -> Any:
|
||||
cancel = resolve_cancel(self._cancel)
|
||||
if cancel is not None:
|
||||
cancel.check()
|
||||
cancel.register_process(proc)
|
||||
try:
|
||||
if communicate:
|
||||
result: Any = proc.communicate(input=prompt, timeout=timeout)
|
||||
else:
|
||||
result = proc.wait(timeout=timeout)
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
_kill_process(proc)
|
||||
raise LLMTimeoutError(
|
||||
f"claude CLI timed out after {timeout}s", cause=exc
|
||||
) from exc
|
||||
except ExecutionCancelled:
|
||||
raise
|
||||
except Exception:
|
||||
if cancel is not None and cancel.cancelled:
|
||||
raise ExecutionCancelled(cancel.reason or "cancelled") from None
|
||||
raise
|
||||
if cancel is not None:
|
||||
cancel.check()
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _handle_stream_event(event: dict[str, Any], text_parts: list[str]) -> None:
|
||||
if event.get("type") != "assistant":
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue