361 lines
15 KiB
Python
361 lines
15 KiB
Python
"""Agent-friendly MiroFish CLI."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any, Dict
|
|
|
|
from .runner import PredictionRunService
|
|
|
|
|
|
def emit(result: Dict[str, Any], as_json: bool) -> None:
|
|
if as_json:
|
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
return
|
|
|
|
status = result.get("status")
|
|
if status == "need_agent_response":
|
|
print(f"need_agent_response: {result['request_id']}")
|
|
print(f"request_file: {result['request_file']}")
|
|
print(f"expected_response_file: {result['expected_response_file']}")
|
|
elif status == "created":
|
|
print(f"created run: {result['run_id']}")
|
|
print(f"run_dir: {result['run_dir']}")
|
|
elif status == "awaiting_user_confirmation":
|
|
print(f"awaiting_user_confirmation: {result['stage']}")
|
|
elif status == "ok":
|
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
elif status == "completed":
|
|
print("completed")
|
|
print(json.dumps(result.get("artifacts", []), ensure_ascii=False, indent=2))
|
|
else:
|
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
|
|
|
|
def add_json(parser: argparse.ArgumentParser) -> None:
|
|
parser.add_argument("--json", action="store_true", help="Emit stable JSON output")
|
|
|
|
|
|
def add_create_run_args(parser: argparse.ArgumentParser) -> None:
|
|
parser.add_argument("--seed", required=True)
|
|
parser.add_argument("--requirement", required=True)
|
|
parser.add_argument("--output", required=True)
|
|
parser.add_argument("--mode", choices=["auto", "staged"], default="auto")
|
|
parser.add_argument("--rounds", type=int, default=10)
|
|
parser.add_argument("--round-unit", choices=["year", "month", "day", "step"], default="year")
|
|
parser.add_argument("--minutes-per-round", type=int, default=None)
|
|
parser.add_argument("--pause-each-round", action=argparse.BooleanOptionalAction, default=False)
|
|
parser.add_argument("--agent-count", type=int, default=None)
|
|
parser.add_argument("--simulation-name", default=None)
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(prog="mirofish-agent")
|
|
add_json(parser)
|
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
|
|
init = sub.add_parser("init")
|
|
add_json(init)
|
|
add_create_run_args(init)
|
|
|
|
create_run = sub.add_parser("create-run")
|
|
add_json(create_run)
|
|
add_create_run_args(create_run)
|
|
|
|
run = sub.add_parser("run")
|
|
add_json(run)
|
|
run.add_argument("--run", required=True)
|
|
|
|
resume = sub.add_parser("resume")
|
|
add_json(resume)
|
|
resume.add_argument("--run", required=True)
|
|
|
|
status = sub.add_parser("status")
|
|
add_json(status)
|
|
status.add_argument("--run", required=True)
|
|
|
|
stage = sub.add_parser("stage")
|
|
stage_sub = stage.add_subparsers(dest="stage_command", required=True)
|
|
stage_status = stage_sub.add_parser("status")
|
|
add_json(stage_status)
|
|
stage_status.add_argument("--run", required=True)
|
|
stage_next = stage_sub.add_parser("next")
|
|
add_json(stage_next)
|
|
stage_next.add_argument("--run", required=True)
|
|
stage_approve = stage_sub.add_parser("approve")
|
|
add_json(stage_approve)
|
|
stage_approve.add_argument("--run", required=True)
|
|
stage_reject = stage_sub.add_parser("reject")
|
|
add_json(stage_reject)
|
|
stage_reject.add_argument("--run", required=True)
|
|
stage_reject.add_argument("--reason", default="")
|
|
stage_update = stage_sub.add_parser("update-settings")
|
|
add_json(stage_update)
|
|
stage_update.add_argument("--run", required=True)
|
|
stage_update.add_argument("--rounds", type=int, default=None)
|
|
stage_update.add_argument("--round-unit", choices=["year", "month", "day", "step"], default=None)
|
|
stage_update.add_argument("--minutes-per-round", type=int, default=None)
|
|
stage_update.add_argument("--pause-each-round", action=argparse.BooleanOptionalAction, default=None)
|
|
stage_update.add_argument("--agent-count", type=int, default=None)
|
|
stage_update.add_argument("--simulation-name", default=None)
|
|
stage_rerun = stage_sub.add_parser("rerun")
|
|
add_json(stage_rerun)
|
|
stage_rerun.add_argument("--run", required=True)
|
|
stage_rerun.add_argument("--stage", required=True)
|
|
|
|
requests = sub.add_parser("requests")
|
|
req_sub = requests.add_subparsers(dest="requests_command", required=True)
|
|
req_list = req_sub.add_parser("list")
|
|
add_json(req_list)
|
|
req_list.add_argument("--run", required=True)
|
|
req_show = req_sub.add_parser("show")
|
|
add_json(req_show)
|
|
req_show.add_argument("--run", required=True)
|
|
req_show.add_argument("--request-id", required=True)
|
|
|
|
responses = sub.add_parser("responses")
|
|
resp_sub = responses.add_subparsers(dest="responses_command", required=True)
|
|
resp_validate = resp_sub.add_parser("validate")
|
|
add_json(resp_validate)
|
|
resp_validate.add_argument("--run", required=True)
|
|
resp_validate.add_argument("--response", required=True)
|
|
resp_submit = resp_sub.add_parser("submit")
|
|
add_json(resp_submit)
|
|
resp_submit.add_argument("--run", required=True)
|
|
resp_submit.add_argument("--response", required=True)
|
|
|
|
graph = sub.add_parser("graph")
|
|
graph_sub = graph.add_subparsers(dest="graph_command", required=True)
|
|
graph_build = graph_sub.add_parser("build")
|
|
add_json(graph_build)
|
|
graph_build.add_argument("--run", required=True)
|
|
graph_build.add_argument("--provider", default=None)
|
|
graph_build.add_argument("--mode", default="agent-triples")
|
|
graph_search = graph_sub.add_parser("search")
|
|
add_json(graph_search)
|
|
graph_search.add_argument("--run", required=True)
|
|
graph_search.add_argument("--query", required=True)
|
|
graph_search.add_argument("--limit", type=int, default=20)
|
|
graph_export = graph_sub.add_parser("export")
|
|
add_json(graph_export)
|
|
graph_export.add_argument("--run", required=True)
|
|
graph_export.add_argument("--output", default=None)
|
|
|
|
simulate = sub.add_parser("simulate")
|
|
sim_sub = simulate.add_subparsers(dest="simulate_command", required=True)
|
|
sim_start = sim_sub.add_parser("start")
|
|
add_json(sim_start)
|
|
sim_start.add_argument("--run", required=True)
|
|
sim_resume = sim_sub.add_parser("resume")
|
|
add_json(sim_resume)
|
|
sim_resume.add_argument("--run", required=True)
|
|
sim_status = sim_sub.add_parser("status")
|
|
add_json(sim_status)
|
|
sim_status.add_argument("--run", required=True)
|
|
|
|
report = sub.add_parser("report")
|
|
report_sub = report.add_subparsers(dest="report_command", required=True)
|
|
report_generate = report_sub.add_parser("generate")
|
|
add_json(report_generate)
|
|
report_generate.add_argument("--run", required=True)
|
|
report_show = report_sub.add_parser("show")
|
|
add_json(report_show)
|
|
report_show.add_argument("--run", required=True)
|
|
|
|
followup = sub.add_parser("followup")
|
|
followup_sub = followup.add_subparsers(dest="followup_command", required=True)
|
|
followup_ask = followup_sub.add_parser("ask")
|
|
add_json(followup_ask)
|
|
followup_ask.add_argument("--run", required=True)
|
|
followup_ask.add_argument("--question", required=True)
|
|
followup_ask.add_argument("--limit", type=int, default=20)
|
|
followup_show = followup_sub.add_parser("show")
|
|
add_json(followup_show)
|
|
followup_show.add_argument("--run", required=True)
|
|
followup_show.add_argument("--request-id", required=True)
|
|
|
|
artifacts = sub.add_parser("artifacts")
|
|
artifacts_sub = artifacts.add_subparsers(dest="artifacts_command", required=True)
|
|
artifacts_list = artifacts_sub.add_parser("list")
|
|
add_json(artifacts_list)
|
|
artifacts_list.add_argument("--run", required=True)
|
|
|
|
# ── Agent interaction commands ─────────────────────────────────────────
|
|
|
|
agents = sub.add_parser("agents")
|
|
agents_sub = agents.add_subparsers(dest="agents_command", required=True)
|
|
agents_list = agents_sub.add_parser("list")
|
|
add_json(agents_list)
|
|
agents_list.add_argument("--run", required=True)
|
|
agents_show = agents_sub.add_parser("show")
|
|
add_json(agents_show)
|
|
agents_show.add_argument("--run", required=True)
|
|
agents_show.add_argument("--agent-id", required=True)
|
|
agents_ask = agents_sub.add_parser("ask")
|
|
add_json(agents_ask)
|
|
agents_ask.add_argument("--run", required=True)
|
|
agents_ask.add_argument("--agent-id", required=True)
|
|
agents_ask.add_argument("--question", required=True)
|
|
agents_ask.add_argument("--limit", type=int, default=20)
|
|
agents_answer = agents_sub.add_parser("answer")
|
|
add_json(agents_answer)
|
|
agents_answer.add_argument("--run", required=True)
|
|
agents_answer.add_argument("--request-id", required=True)
|
|
|
|
report_question = sub.add_parser("report-question")
|
|
rq_sub = report_question.add_subparsers(dest="report_question_command", required=True)
|
|
rq_ask = rq_sub.add_parser("ask")
|
|
add_json(rq_ask)
|
|
rq_ask.add_argument("--run", required=True)
|
|
rq_ask.add_argument("--question", required=True)
|
|
rq_ask.add_argument("--limit", type=int, default=20)
|
|
rq_answer = rq_sub.add_parser("answer")
|
|
add_json(rq_answer)
|
|
rq_answer.add_argument("--run", required=True)
|
|
rq_answer.add_argument("--request-id", required=True)
|
|
|
|
questionnaire = sub.add_parser("questionnaire")
|
|
q_sub = questionnaire.add_subparsers(dest="questionnaire_command", required=True)
|
|
q_send = q_sub.add_parser("send")
|
|
add_json(q_send)
|
|
q_send.add_argument("--run", required=True)
|
|
q_send.add_argument("--questions", required=True)
|
|
q_show = q_sub.add_parser("show")
|
|
add_json(q_show)
|
|
q_show.add_argument("--run", required=True)
|
|
q_show.add_argument("--questionnaire-id", required=True)
|
|
|
|
web = sub.add_parser("web")
|
|
web_sub = web.add_subparsers(dest="web_command", required=True)
|
|
web_generate = web_sub.add_parser("generate")
|
|
add_json(web_generate)
|
|
web_generate.add_argument("--run", required=True)
|
|
|
|
doctor = sub.add_parser("doctor")
|
|
add_json(doctor)
|
|
doctor.add_argument("--runs-dir", default=None)
|
|
|
|
return parser
|
|
|
|
|
|
def dispatch(args: argparse.Namespace) -> Dict[str, Any]:
|
|
service = PredictionRunService()
|
|
if args.command in {"init", "create-run"}:
|
|
return service.create_run(
|
|
args.seed,
|
|
args.requirement,
|
|
args.output,
|
|
mode=args.mode,
|
|
rounds=args.rounds,
|
|
round_unit=args.round_unit,
|
|
minutes_per_round=args.minutes_per_round,
|
|
pause_each_round=args.pause_each_round,
|
|
agent_count=args.agent_count,
|
|
simulation_name=args.simulation_name,
|
|
)
|
|
if args.command == "run":
|
|
return service.run(args.run)
|
|
if args.command == "resume":
|
|
return service.resume(args.run)
|
|
if args.command == "status":
|
|
return service.status(args.run)
|
|
if args.command == "stage":
|
|
if args.stage_command == "status":
|
|
return service.get_current_stage(args.run)
|
|
if args.stage_command == "next":
|
|
return service.resume(args.run)
|
|
if args.stage_command == "approve":
|
|
return service.approve_stage(args.run)
|
|
if args.stage_command == "reject":
|
|
return service.reject_stage(args.run, args.reason)
|
|
if args.stage_command == "update-settings":
|
|
return service.update_simulation_settings(
|
|
args.run,
|
|
rounds=args.rounds,
|
|
round_unit=args.round_unit,
|
|
minutes_per_round=args.minutes_per_round,
|
|
pause_each_round=args.pause_each_round,
|
|
agent_count=args.agent_count,
|
|
simulation_name=args.simulation_name,
|
|
)
|
|
return service.rerun_stage(args.run, args.stage)
|
|
if args.command == "requests":
|
|
if args.requests_command == "list":
|
|
return service.list_requests(args.run)
|
|
return service.get_request(args.run, args.request_id)
|
|
if args.command == "responses":
|
|
if args.responses_command == "validate":
|
|
return service.validate_response(args.run, args.response)
|
|
return service.submit_response(args.run, args.response)
|
|
if args.command == "graph":
|
|
if args.graph_command == "build":
|
|
return service.build_graph(args.run, provider=args.provider, mode=args.mode)
|
|
if args.graph_command == "search":
|
|
return service.search_graph(args.run, args.query, args.limit)
|
|
return service.export_graph(args.run, args.output)
|
|
if args.command == "simulate":
|
|
if args.simulate_command == "start":
|
|
return service.start_simulation(args.run)
|
|
if args.simulate_command == "resume":
|
|
return service.resume(args.run)
|
|
return service.simulation_status(args.run)
|
|
if args.command == "report":
|
|
if args.report_command == "generate":
|
|
return service.generate_report(args.run)
|
|
return service.get_report(args.run)
|
|
if args.command == "followup":
|
|
if args.followup_command == "ask":
|
|
return service.ask_followup_question(args.run, args.question, args.limit)
|
|
return service.get_followup_answer(args.run, args.request_id)
|
|
if args.command == "artifacts":
|
|
return service.list_artifacts(args.run)
|
|
if args.command == "agents":
|
|
if args.agents_command == "list":
|
|
return service.list_agents(args.run)
|
|
if args.agents_command == "show":
|
|
return service.get_agent(args.run, args.agent_id)
|
|
if args.agents_command == "ask":
|
|
return service.ask_agent(args.run, args.agent_id, args.question, args.limit)
|
|
if args.agents_command == "answer":
|
|
return service.get_agent_answer(args.run, args.request_id)
|
|
if args.command == "report-question":
|
|
if args.report_question_command == "ask":
|
|
return service.ask_report_question(args.run, args.question, args.limit)
|
|
if args.report_question_command == "answer":
|
|
return service.get_report_question_answer(args.run, args.request_id)
|
|
if args.command == "questionnaire":
|
|
if args.questionnaire_command == "send":
|
|
questions = json.loads(Path(args.questions).read_text(encoding="utf-8"))
|
|
if not isinstance(questions, list):
|
|
questions = [questions]
|
|
return service.send_questionnaire(args.run, questions)
|
|
if args.questionnaire_command == "show":
|
|
return service.get_questionnaire_result(args.run, args.questionnaire_id)
|
|
if args.command == "web":
|
|
if args.web_command == "generate":
|
|
return service.generate_web_console(args.run)
|
|
if args.command == "doctor":
|
|
return service.doctor(args.runs_dir)
|
|
raise ValueError(f"unsupported command: {args.command}")
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = build_parser()
|
|
args = parser.parse_args(argv)
|
|
as_json = bool(getattr(args, "json", False))
|
|
try:
|
|
result = dispatch(args)
|
|
except Exception as exc:
|
|
result = {"status": "error", "error": str(exc), "error_type": exc.__class__.__name__}
|
|
emit(result, as_json)
|
|
return 1
|
|
emit(result, as_json)
|
|
return 0 if result.get("status") not in {"failed", "error"} else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|