




















@@ -93,6 +93,37 @@ def run(args: list[str], cwd: Path, *, input_text: str | None = None, check: boo
9393return result
9494959596+def run_with_heartbeat(
97+args: list[str],
98+cwd: Path,
99+*,
100+input_text: str | None = None,
101+label: str,
102+heartbeat_seconds: int = 60,
103+) -> subprocess.CompletedProcess[str]:
104+started = time.monotonic()
105+proc = subprocess.Popen(
106+args,
107+cwd=cwd,
108+stdin=subprocess.PIPE if input_text is not None else None,
109+stdout=subprocess.PIPE,
110+stderr=subprocess.PIPE,
111+text=True,
112+ )
113+first_communicate = True
114+while True:
115+try:
116+stdout, stderr = proc.communicate(
117+input=input_text if first_communicate else None,
118+timeout=heartbeat_seconds,
119+ )
120+return subprocess.CompletedProcess(args, int(proc.returncode or 0), stdout, stderr)
121+except subprocess.TimeoutExpired:
122+first_communicate = False
123+elapsed = int(time.monotonic() - started)
124+print(f"review still running: {label} elapsed={elapsed}s pid={proc.pid}", file=sys.stderr, flush=True)
125+126+96127def git(repo: Path, *args: str, check: bool = True) -> str:
97128return run(["git", *args], repo, check=check).stdout
98129@@ -320,7 +351,7 @@ def run_codex(args: argparse.Namespace, repo: Path, prompt: str) -> str:
320351"-",
321352 ]
322353 )
323-result = run(cmd, repo, input_text=prompt, check=False)
354+result = run_with_heartbeat(cmd, repo, input_text=prompt, label="codex")
324355try:
325356output = output_path.read_text()
326357finally:
@@ -349,7 +380,7 @@ def run_claude(args: argparse.Namespace, repo: Path, prompt: str) -> str:
349380cmd.extend(["--model", args.model])
350381if args.thinking:
351382cmd.extend(["--effort", args.thinking])
352-result = run(cmd, repo, input_text=prompt, check=False)
383+result = run_with_heartbeat(cmd, repo, input_text=prompt, label="claude")
353384if result.returncode != 0:
354385raise SystemExit(f"claude engine failed ({result.returncode})\n{result.stderr or result.stdout}")
355386return result.stdout
@@ -374,7 +405,7 @@ def run_droid(args: argparse.Namespace, repo: Path, prompt: str) -> str:
374405cmd.extend(["--model", args.model])
375406if not args.tools:
376407cmd.extend(["--disabled-tools", "*"])
377-result = run(cmd, repo, check=False)
408+result = run_with_heartbeat(cmd, repo, label="droid")
378409prompt_path.unlink(missing_ok=True)
379410if result.returncode != 0:
380411raise SystemExit(f"droid engine failed ({result.returncode})\n{result.stderr or result.stdout}")
@@ -416,7 +447,7 @@ def run_copilot(args: argparse.Namespace, repo: Path, prompt: str) -> str:
416447 )
417448if args.web_search:
418449cmd.append("--allow-all-urls")
419-result = run(cmd, Path(tempdir), check=False)
450+result = run_with_heartbeat(cmd, Path(tempdir), label="copilot")
420451if result.returncode != 0:
421452raise SystemExit(f"copilot engine failed ({result.returncode})\n{result.stderr or result.stdout}")
422453return result.stdout
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。