Skip to content

Commit 94ab1a9

Browse files
authored
fix(run): terminalize interrupted and stale runs (#382)
1 parent 1fc5855 commit 94ab1a9

28 files changed

Lines changed: 4826 additions & 269 deletions

src/brigade/aboyeur.py

Lines changed: 599 additions & 101 deletions
Large diffs are not rendered by default.

src/brigade/acpx_adapter.py

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import inspect
56
import json
67
import re
78
from dataclasses import dataclass
@@ -76,7 +77,7 @@ def _status_payload(stdout: str) -> tuple[dict[str, Any] | None, str]:
7677
return payload, _safe_diagnostic(safe_stdout)
7778

7879

79-
def cursor_auth_status() -> CursorAuthStatus:
80+
def cursor_auth_status(*, process_registry: proc.ProcessRegistry | None = None) -> CursorAuthStatus:
8081
"""Return a bounded, prompt-free diagnosis for the headless Cursor CLI."""
8182
if proc.which("cursor-agent") is None:
8283
return CursorAuthStatus(
@@ -86,10 +87,11 @@ def cursor_auth_status() -> CursorAuthStatus:
8687
"",
8788
127,
8889
)
89-
result = proc.run(
90-
["cursor-agent", "status", "--format", "json"],
91-
timeout=CURSOR_AUTH_TIMEOUT_SECONDS,
92-
)
90+
argv = ["cursor-agent", "status", "--format", "json"]
91+
if process_registry is None:
92+
result = proc.run(argv, timeout=CURSOR_AUTH_TIMEOUT_SECONDS)
93+
else:
94+
result = proc.run(argv, timeout=CURSOR_AUTH_TIMEOUT_SECONDS, process_registry=process_registry)
9395
payload, stdout = _status_payload(result.stdout)
9496
stderr = _safe_diagnostic(result.stderr)
9597
diagnostic = _diagnostic_line(stdout, stderr)
@@ -164,8 +166,11 @@ def build_argv(
164166
return argv
165167

166168

167-
def installed_version() -> tuple[str | None, str]:
168-
result = proc.run(["acpx", "--version"], timeout=10.0)
169+
def installed_version(*, process_registry: proc.ProcessRegistry | None = None) -> tuple[str | None, str]:
170+
if process_registry is None:
171+
result = proc.run(["acpx", "--version"], timeout=10.0)
172+
else:
173+
result = proc.run(["acpx", "--version"], timeout=10.0, process_registry=process_registry)
169174
if result.code != 0:
170175
return None, result.stderr.strip() or result.stdout.strip() or f"exit {result.code}"
171176
match = re.search(r"\b(\d+\.\d+\.\d+)\b", result.stdout)
@@ -174,6 +179,17 @@ def installed_version() -> tuple[str | None, str]:
174179
return match.group(1), ""
175180

176181

182+
def _call_with_process_registry(function, *, process_registry: proc.ProcessRegistry | None):
183+
parameters = inspect.signature(function).parameters.values()
184+
accepts_registry = any(
185+
parameter.name == "process_registry" or parameter.kind is inspect.Parameter.VAR_KEYWORD
186+
for parameter in parameters
187+
)
188+
if accepts_registry:
189+
return function(process_registry=process_registry)
190+
return function()
191+
192+
177193
def _permission_prompt_diagnostic(error: object) -> dict[str, object] | None:
178194
if not isinstance(error, dict):
179195
return None
@@ -349,6 +365,7 @@ def run_cursor(
349365
version: str,
350366
read_only: bool,
351367
writable_worktree: bool = False,
368+
process_registry: proc.ProcessRegistry | None = None,
352369
) -> AgentResult:
353370
if version != SUPPORTED_VERSION:
354371
return AgentResult(
@@ -377,7 +394,10 @@ def run_cursor(
377394
failure_kind="missing-executable",
378395
transport="acpx",
379396
)
380-
installed, version_error = installed_version()
397+
installed, version_error = _call_with_process_registry(
398+
installed_version,
399+
process_registry=process_registry,
400+
)
381401
if installed != version:
382402
found = installed or version_error
383403
return AgentResult(
@@ -389,7 +409,10 @@ def run_cursor(
389409
transport="acpx",
390410
acpx_version=installed,
391411
)
392-
auth = cursor_auth_status()
412+
auth = _call_with_process_registry(
413+
cursor_auth_status,
414+
process_registry=process_registry,
415+
)
393416
auth_event: dict[str, object] = {
394417
"type": "provider_auth",
395418
"status": auth.state,
@@ -436,7 +459,12 @@ def run_cursor(
436459
acpx_version=installed,
437460
safe_events=(auth_event,),
438461
)
439-
result = proc.run(argv, timeout=timeout + 5.0, cwd=cwd)
462+
result = proc.run(
463+
argv,
464+
timeout=timeout + 5.0,
465+
cwd=cwd,
466+
process_registry=process_registry,
467+
)
440468
if result.decode_failed:
441469
detail = (result.stderr.strip() or result.decode_failure_detail)[:200]
442470
return AgentResult(

src/brigade/agents.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from __future__ import annotations
88

99
import functools
10+
import inspect
1011
import json
1112
import os
1213
import re
@@ -507,7 +508,20 @@ def detect(cli_ref: str) -> bool:
507508
return resolve_agent_executable(cli_ref).runnable
508509

509510

510-
def ollama_model_present(model: str, executable: proc.ExecutableIdentity | None = None) -> tuple[bool, str]:
511+
def _accepts_process_registry(function: Callable[..., object]) -> bool:
512+
parameters = inspect.signature(function).parameters.values()
513+
return any(
514+
parameter.name == "process_registry" or parameter.kind is inspect.Parameter.VAR_KEYWORD
515+
for parameter in parameters
516+
)
517+
518+
519+
def ollama_model_present(
520+
model: str,
521+
executable: proc.ExecutableIdentity | None = None,
522+
*,
523+
process_registry: proc.ProcessRegistry | None = None,
524+
) -> tuple[bool, str]:
511525
"""Check whether an ollama model is already pulled locally.
512526
513527
`ollama run` on a missing model silently auto-pulls it (tens of GB for
@@ -517,7 +531,10 @@ def ollama_model_present(model: str, executable: proc.ExecutableIdentity | None
517531
ollama = executable or proc.resolve_executable("ollama")
518532
if not ollama.runnable or ollama.path is None:
519533
return False, "ollama is not installed"
520-
listing = proc.run([ollama.path, "list"], timeout=15.0)
534+
if process_registry is None:
535+
listing = proc.run([ollama.path, "list"], timeout=15.0)
536+
else:
537+
listing = proc.run([ollama.path, "list"], timeout=15.0, process_registry=process_registry)
521538
if listing.code != 0:
522539
reason = listing.stderr.strip() or f"exit {listing.code}"
523540
return False, f"could not list local ollama models ({reason[:120]}); is the ollama server running?"
@@ -657,6 +674,7 @@ def run_agent(
657674
reasoning: str | None = None,
658675
env: dict[str, str] | None = None,
659676
resume_session_id: str | None = None,
677+
process_registry: proc.ProcessRegistry | None = None,
660678
) -> AgentResult:
661679
child_env: dict[str, str] | None = None
662680
resolved_overrides: dict[str, str] | None = None
@@ -719,12 +737,27 @@ def run_agent(
719737
)
720738
from . import codex_cloud
721739

740+
if process_registry is not None and _accepts_process_registry(codex_cloud.run_cloud_task):
741+
return codex_cloud.run_cloud_task(
742+
prompt,
743+
env_id=env_id,
744+
timeout=timeout,
745+
cwd=cwd,
746+
process_registry=process_registry,
747+
)
722748
return codex_cloud.run_cloud_task(prompt, env_id=env_id, timeout=timeout, cwd=cwd)
723749

724750
if cli_ref.startswith(_OLLAMA_PREFIX):
725751
ollama_model = cli_ref[len(_OLLAMA_PREFIX) :]
726752
if ollama_model:
727-
present, missing_detail = ollama_model_present(ollama_model, executable)
753+
if process_registry is not None and _accepts_process_registry(ollama_model_present):
754+
present, missing_detail = ollama_model_present(
755+
ollama_model,
756+
executable,
757+
process_registry=process_registry,
758+
)
759+
else:
760+
present, missing_detail = ollama_model_present(ollama_model, executable)
728761
if not present:
729762
return AgentResult(text="", ok=False, detail=missing_detail)
730763
cursor_limitation = None
@@ -769,13 +802,15 @@ def run_agent(
769802
cwd=cwd,
770803
env=child_env,
771804
stdin=prompt.encode(),
805+
process_registry=process_registry,
772806
)
773807
else:
774808
result = proc.run(
775809
argv,
776810
timeout=timeout,
777811
cwd=cwd,
778812
env=child_env,
813+
process_registry=process_registry,
779814
)
780815

781816
def scrub_detail(detail: str) -> str:

0 commit comments

Comments
 (0)