Files
skills-win/langgraph-plan-review/scripts/review_task.py
T
2026-06-22 16:40:24 +08:00

174 lines
5.6 KiB
Python

from __future__ import annotations
"""Codex review shim called by agents.py.
argv: task_path latest_discuss_path codex_session_id work_dir
"""
import json
import logging
import os
import subprocess
import sys
import tempfile
import time
from pathlib import Path
from typing import Any
def parse_jsonl(stdout: str) -> tuple[str, str]:
session_id = ""
final_message = ""
text_parts: list[str] = []
for line in stdout.splitlines():
line = line.strip()
if not line:
continue
try:
event: dict[str, Any] = json.loads(line)
except json.JSONDecodeError:
text_parts.append(line)
continue
for key in (
"session_id",
"sessionId",
"thread_id",
"threadId",
"conversation_id",
"conversationId",
):
value = event.get(key)
if isinstance(value, str) and value:
session_id = value
event_type = str(event.get("type", ""))
if event_type in {"session.started", "session.created", "thread.started"}:
value = event.get("id") or event.get("session_id") or event.get("thread_id")
if isinstance(value, str) and value:
session_id = value
if event_type == "item.completed":
item = event.get("item")
if isinstance(item, dict) and item.get("type") == "agent_message":
value = item.get("text")
if isinstance(value, str) and value:
text_parts.append(value)
if event_type in {"agent_message", "message", "response.output_text.done"}:
value = event.get("text") or event.get("message") or event.get("content")
if isinstance(value, str) and value:
text_parts.append(value)
if event_type in {"exec.completed", "turn.completed", "task.completed"}:
value = event.get("last_message") or event.get("message") or event.get("output")
if isinstance(value, str) and value:
final_message = value
comment = (final_message or "\n".join(text_parts)).strip()
return session_id, comment
def resolve_codex() -> str:
import shutil
# 1. Explicit env var
exe = os.environ.get("CODEX_EXE", "")
if exe and Path(exe).exists():
return exe
# 2. Known install paths (checked before PATH to avoid picking up wrong codex variant)
for candidate in [
r"C:\Users\bowei\miniconda3\envs\langgraph\codex.cmd",
r"C:\Users\bowei\.conda\envs\langgraph\codex.cmd",
]:
if Path(candidate).exists():
return candidate
# 3. PATH lookup fallback
found = shutil.which("codex") or shutil.which("codex.cmd")
if found:
return found
raise FileNotFoundError(
"Cannot find codex executable. Set CODEX_EXE environment variable or add codex to PATH."
)
def main() -> int:
task_path = sys.argv[1]
latest_discuss = sys.argv[2] if len(sys.argv) > 2 and sys.argv[2] else ""
codex_session_id = sys.argv[3] if len(sys.argv) > 3 and sys.argv[3] else ""
work_dir = sys.argv[4] if len(sys.argv) > 4 and sys.argv[4] else str(Path.cwd())
timeout = int(sys.argv[5]) if len(sys.argv) > 5 and sys.argv[5] else 300
prompt = (
f"Working directory: {work_dir}\n"
f"Review the plan document at {task_path}.\n"
"Only report severe issues.\n"
"Final answer rules: if there is no severe issue, output exactly NO_SERIOUS_ISSUE.\n"
"If there is a severe issue, briefly describe it in one paragraph.\n"
)
if latest_discuss:
prompt += f"Also verify that issues raised in {latest_discuss} were addressed.\n"
codex = resolve_codex()
fd, last_message_name = tempfile.mkstemp(
prefix="codex-last-message-",
suffix=".txt",
dir=None,
text=True,
)
os.close(fd)
last_message_path = Path(last_message_name)
try:
flags = ["--json", "--skip-git-repo-check", "--output-last-message", str(last_message_path)]
if codex_session_id:
command_args = ["cmd", "/c", codex, "exec", "resume"] + flags + [codex_session_id, "-"]
else:
command_args = ["cmd", "/c", codex, "exec"] + flags + ["-"]
logging.info("Invoking codex: %s", command_args[0] if command_args else "")
t0 = time.monotonic()
result = subprocess.run(
command_args,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=timeout,
input=prompt,
cwd=work_dir,
)
logging.info("Codex finished in %.1fs, exit=%d", time.monotonic() - t0, result.returncode)
session_id, stream_comment = parse_jsonl(result.stdout)
comment = last_message_path.read_text(encoding="utf-8").strip()
if not comment:
comment = stream_comment
if comment.strip() == "NO_SERIOUS_ISSUE":
comment = ""
if codex_session_id:
session_id = codex_session_id
elif not session_id:
session_id = codex_session_id
finally:
try:
last_message_path.unlink()
except FileNotFoundError:
pass
if result.returncode != 0 and not comment:
sys.stderr.write(result.stderr)
return result.returncode
print(json.dumps(
{
"has_serious_issue": bool(comment),
"comment": comment,
"codex_session_id": session_id,
},
ensure_ascii=False,
))
return 0
if __name__ == "__main__":
raise SystemExit(main())