121 lines
4.2 KiB
Python
121 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""In-process driver for the plan-review graph — NO langgraph dev server.
|
|
|
|
Builds the compiled graph in-process (workflow.build_workflow) with a SqliteSaver so
|
|
state survives between invocations, and drives interrupts directly. Sets HTTP(S)_PROXY
|
|
from the Windows system proxy (or PLAN_REVIEW_PROXY) so the codex subprocess spawned by
|
|
the review node can reach its backend through the proxy.
|
|
|
|
CLI (mirrors the old run_workflow.py):
|
|
--task-id 10 [--work-dir DIR] [--notes-dir Notes] [--prompt "..."]
|
|
[--resume-b64 <b64 json>] [--codex-review-command-b64 <b64 json>] [--timeout 600]
|
|
Prints one JSON line: {"status":"waiting","action_required":{...}} or {"status":"done",...}.
|
|
"""
|
|
from __future__ import annotations
|
|
import os, sys, json, base64, argparse, sqlite3
|
|
from pathlib import Path
|
|
|
|
SCRIPTS = Path(__file__).resolve().parent
|
|
sys.path.insert(0, str(SCRIPTS))
|
|
|
|
|
|
def resolve_proxy() -> str:
|
|
px = os.environ.get("PLAN_REVIEW_PROXY", "").strip()
|
|
if not px:
|
|
try:
|
|
import winreg
|
|
k = winreg.OpenKey(
|
|
winreg.HKEY_CURRENT_USER,
|
|
r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
|
|
)
|
|
enable, _ = winreg.QueryValueEx(k, "ProxyEnable")
|
|
server, _ = winreg.QueryValueEx(k, "ProxyServer")
|
|
if enable and server and "=" not in str(server): # simple host:port, not per-scheme
|
|
px = "http://" + str(server)
|
|
except Exception:
|
|
px = ""
|
|
if px:
|
|
for v in ("HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY",
|
|
"http_proxy", "https_proxy", "all_proxy"):
|
|
os.environ[v] = px
|
|
return px
|
|
|
|
|
|
def open_saver(db_path: str):
|
|
from langgraph.checkpoint.sqlite import SqliteSaver
|
|
conn = sqlite3.connect(db_path, check_same_thread=False)
|
|
saver = SqliteSaver(conn)
|
|
try:
|
|
saver.setup()
|
|
except Exception:
|
|
pass
|
|
return saver
|
|
|
|
|
|
def extract_interrupt(result):
|
|
if not isinstance(result, dict):
|
|
return None
|
|
ints = result.get("__interrupt__")
|
|
if not ints:
|
|
return None
|
|
first = ints[0] if isinstance(ints, (list, tuple)) else ints
|
|
return getattr(first, "value", first)
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--task-id", required=True)
|
|
ap.add_argument("--work-dir", default=".")
|
|
ap.add_argument("--notes-dir", default="Notes")
|
|
ap.add_argument("--prompt", default="")
|
|
ap.add_argument("--resume-b64", default="")
|
|
ap.add_argument("--codex-review-command-b64", default="")
|
|
ap.add_argument("--timeout", type=int, default=600)
|
|
ap.add_argument("--db", default=str(SCRIPTS / ".plan_review_state.sqlite"))
|
|
args = ap.parse_args()
|
|
|
|
proxy = resolve_proxy()
|
|
|
|
from workflow import build_workflow
|
|
from langgraph.types import Command
|
|
|
|
codex_cmd = None
|
|
if args.codex_review_command_b64:
|
|
codex_cmd = json.loads(base64.b64decode(args.codex_review_command_b64).decode("utf-8"))
|
|
|
|
saver = open_saver(args.db)
|
|
graph = build_workflow(checkpointer=saver)
|
|
|
|
config = {"configurable": {"thread_id": str(args.task_id), "codex_timeout": args.timeout}}
|
|
if codex_cmd:
|
|
config["configurable"]["codex_review_command"] = codex_cmd
|
|
|
|
if args.resume_b64:
|
|
resume_val = json.loads(base64.b64decode(args.resume_b64).decode("utf-8"))
|
|
result = graph.invoke(Command(resume=resume_val), config)
|
|
else:
|
|
init = {
|
|
"task_id": str(args.task_id),
|
|
"user_prompt": args.prompt,
|
|
"work_dir": args.work_dir,
|
|
"notes_dir": args.notes_dir,
|
|
"codex_session_id": "",
|
|
}
|
|
result = graph.invoke(init, config)
|
|
|
|
payload = extract_interrupt(result)
|
|
if payload is not None:
|
|
out = {"status": "waiting", "thread_id": str(args.task_id),
|
|
"action_required": payload, "proxy": proxy}
|
|
else:
|
|
out = {"status": "done", "thread_id": str(args.task_id),
|
|
"has_serious_issue": bool(result.get("has_serious_issue")),
|
|
"review_comment": result.get("review_comment", ""),
|
|
"proxy": proxy}
|
|
print(json.dumps(out, ensure_ascii=False))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|