| 1 | #!/usr/bin/env python3
|
| 2 | """Plan, execute and resume bounded local coding-agent campaigns."""
|
| 3 | from __future__ import annotations
|
| 4 |
|
| 5 | import argparse
|
| 6 | import json
|
| 7 | from pathlib import Path
|
| 8 | import sys
|
| 9 |
|
| 10 | from oakbench.agent_campaign import (CampaignError, create_plan, load_plan, load_events,
|
| 11 | campaign_status, run_campaign)
|
| 12 |
|
| 13 |
|
| 14 | def main(argv=None) -> int:
|
| 15 | parser = argparse.ArgumentParser(description=__doc__)
|
| 16 | sub = parser.add_subparsers(dest="command", required=True)
|
| 17 | plan = sub.add_parser("plan", help="freeze a manifest, schedule and provenance; invokes no agent task")
|
| 18 | plan.add_argument("--manifest", type=Path, required=True)
|
| 19 | plan.add_argument("--campaign-dir", type=Path, required=True)
|
| 20 | for name in ("run", "resume", "status", "report"):
|
| 21 | child = sub.add_parser(name)
|
| 22 | child.add_argument("--campaign-dir", type=Path, required=True)
|
| 23 | if name in ("run", "resume"):
|
| 24 | child.add_argument("--enable-real", action="store_true")
|
| 25 | child.add_argument("--limit", type=int, help="maximum new trials this invocation")
|
| 26 | if name == "resume":
|
| 27 | child.add_argument("--acknowledge-torn-journal", action="store_true", help="archive torn journal, reserve uncertain trial, and resume without retrying it")
|
| 28 | if name == "report":
|
| 29 | child.add_argument("--format", choices=("json", "markdown"), default="json")
|
| 30 | args = parser.parse_args(argv)
|
| 31 | try:
|
| 32 | if args.command == "plan":
|
| 33 | frozen = create_plan(args.manifest, args.campaign_dir)
|
| 34 | print(json.dumps({"campaign_id": frozen["campaign_id"], "plan_sha256": frozen["plan_sha256"],
|
| 35 | "scheduled": len(frozen["schedule"]), "plan": str(args.campaign_dir / "plan.json")}, indent=2))
|
| 36 | return 0
|
| 37 | if args.command in ("run", "resume"):
|
| 38 | state = run_campaign(args.campaign_dir, resume=args.command == "resume", enable_real=args.enable_real, limit=args.limit, acknowledge_torn=getattr(args, "acknowledge_torn_journal", False))
|
| 39 | else:
|
| 40 | frozen = load_plan(args.campaign_dir)
|
| 41 | events = load_events(args.campaign_dir, frozen, tolerate_torn=True)
|
| 42 | if args.command == "report":
|
| 43 | from oakbench.campaign_report import report_campaign, render_markdown
|
| 44 | report = report_campaign(frozen, events)
|
| 45 | print(json.dumps(report, indent=2) if args.format == "json" else render_markdown(report))
|
| 46 | return 0
|
| 47 | state = campaign_status(frozen, events)
|
| 48 | print(json.dumps(state, indent=2))
|
| 49 | if args.command in ("run", "resume"):
|
| 50 | if state["pending_trial_ids"] or state["interrupted_trial_ids"]:
|
| 51 | return 3
|
| 52 | events = load_events(args.campaign_dir, load_plan(args.campaign_dir))
|
| 53 | return 1 if any(event.get("outcome") in {"error", "fail", "timeout"} for event in events) else 0
|
| 54 | return 0
|
| 55 | except (ValueError, OSError) as exc:
|
| 56 | print("campaign error: " + str(exc), file=sys.stderr)
|
| 57 | return 2
|
| 58 |
|
| 59 |
|
| 60 | if __name__ == "__main__":
|
| 61 | raise SystemExit(main())
|