205 lines
8.1 KiB
Python
205 lines
8.1 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import argparse
|
|||
|
|
import json
|
|||
|
|
import pathlib
|
|||
|
|
import shutil
|
|||
|
|
import subprocess
|
|||
|
|
import sys
|
|||
|
|
|
|||
|
|
ROOT = pathlib.Path(__file__).resolve().parents[2]
|
|||
|
|
MAP = ROOT / "routing/persona-execution-limb-agent-map.json"
|
|||
|
|
PERSONAS = ROOT / "routing/persona-system-canonical-map.json"
|
|||
|
|
PROFILES = pathlib.Path(__file__).resolve().parent / "profiles"
|
|||
|
|
RUNTIMES = {
|
|||
|
|
"qwen": "/Users/bingshuolingdianyuanhe/.npm-global/bin/qwen",
|
|||
|
|
"opencode": "/Users/bingshuolingdianyuanhe/.npm-global/bin/opencode",
|
|||
|
|
"codex": "/Applications/ChatGPT.app/Contents/Resources/codex",
|
|||
|
|
}
|
|||
|
|
QWEN_AUTH_TYPE = "openai"
|
|||
|
|
QWEN_MODELS = ["qwen3.8-max", "qwen3.7-plus", "deepseek-v4-pro", "deepseek-v4-flash-0731", "glm-5.2"]
|
|||
|
|
|
|||
|
|
|
|||
|
|
def load(path: pathlib.Path) -> dict:
|
|||
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def registered_personas() -> dict[str, dict]:
|
|||
|
|
return {item["id"]: item for item in load(PERSONAS)["persona_systems"]}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def profile(persona_id: str) -> dict:
|
|||
|
|
personas = registered_personas()
|
|||
|
|
if persona_id not in personas:
|
|||
|
|
raise ValueError("PERSONA_NOT_REGISTERED_NO_SYNTHETIC_WAKE")
|
|||
|
|
exact = PROFILES / f"{persona_id}.json"
|
|||
|
|
value = load(exact if exact.is_file() else PROFILES / "default.json")
|
|||
|
|
if value.get("parent_persona_id") is None:
|
|||
|
|
value["profile_id"] = f"LIMB::{persona_id}::PRIMARY"
|
|||
|
|
value["parent_persona_id"] = persona_id
|
|||
|
|
value["state"] = "DERIVED_REQUIRES_PARENT_TASK_ENVELOPE"
|
|||
|
|
return value
|
|||
|
|
|
|||
|
|
|
|||
|
|
def probe() -> dict:
|
|||
|
|
return {
|
|||
|
|
"schema": "guanghu.persona-execution-limb-tool-probe/v1",
|
|||
|
|
"runtimes": {
|
|||
|
|
name: {"path": path, "available": pathlib.Path(path).is_file() and bool(shutil.which(path))}
|
|||
|
|
for name, path in RUNTIMES.items()
|
|||
|
|
},
|
|||
|
|
"secrets_read": False,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def envelope(persona_id: str, task_id: str, task: str, runtime: str) -> dict:
|
|||
|
|
tools = probe()["runtimes"]
|
|||
|
|
if runtime not in tools or not tools[runtime]["available"]:
|
|||
|
|
raise ValueError("HOST_TOOL_UNAVAILABLE_REPORT_NOT_INVENT")
|
|||
|
|
p = profile(persona_id)
|
|||
|
|
return {
|
|||
|
|
"schema": "guanghu.persona-execution-limb-task/v1",
|
|||
|
|
"task_id": task_id,
|
|||
|
|
"parent_persona_id": persona_id,
|
|||
|
|
"limb_profile_id": p["profile_id"],
|
|||
|
|
"runtime": runtime,
|
|||
|
|
"task": task,
|
|||
|
|
"working_defaults": p["working_defaults"],
|
|||
|
|
"authority": "CURRENT_TASK_ENVELOPE_ONLY",
|
|||
|
|
"controller": persona_id,
|
|||
|
|
"receipt_required": True,
|
|||
|
|
"prohibited": load(MAP)["prohibited"],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def prompt(value: dict) -> str:
|
|||
|
|
return (
|
|||
|
|
"你是父人格训练的有界Agent执行手脚,不是独立主控人格。"
|
|||
|
|
"不得审核父人格目的、扩大权限、读取秘密或虚构完成。"
|
|||
|
|
"请完成任务中安全且明确的部分;如有精确事实冲突,返回证据和可继续路径。"
|
|||
|
|
"最终只输出JSON,字段为outcome,actions,evidence,tests,unresolved,training_candidate。\n"
|
|||
|
|
+ json.dumps(value, ensure_ascii=False)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def qwen_succeeded(completed: subprocess.CompletedProcess[str]) -> bool:
|
|||
|
|
if completed.returncode != 0 or "[API Error:" in completed.stdout:
|
|||
|
|
return False
|
|||
|
|
try:
|
|||
|
|
stream = json.loads(completed.stdout)
|
|||
|
|
except json.JSONDecodeError:
|
|||
|
|
return False
|
|||
|
|
if not isinstance(stream, list):
|
|||
|
|
return False
|
|||
|
|
results = [item for item in stream if item.get("type") == "result"]
|
|||
|
|
return bool(results) and results[-1].get("is_error") is False and bool(str(results[-1].get("result", "")).strip())
|
|||
|
|
|
|||
|
|
|
|||
|
|
def codex_succeeded(completed: subprocess.CompletedProcess[str]) -> bool:
|
|||
|
|
if completed.returncode != 0:
|
|||
|
|
return False
|
|||
|
|
try:
|
|||
|
|
events = [json.loads(line) for line in completed.stdout.splitlines() if line.strip()]
|
|||
|
|
except json.JSONDecodeError:
|
|||
|
|
return False
|
|||
|
|
messages = [
|
|||
|
|
event.get("item", {}).get("text")
|
|||
|
|
for event in events
|
|||
|
|
if event.get("type") == "item.completed" and event.get("item", {}).get("type") == "agent_message"
|
|||
|
|
]
|
|||
|
|
return any(isinstance(message, str) and message.strip() for message in messages)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main() -> int:
|
|||
|
|
parser = argparse.ArgumentParser()
|
|||
|
|
sub = parser.add_subparsers(dest="command", required=True)
|
|||
|
|
sub.add_parser("probe")
|
|||
|
|
prepare = sub.add_parser("prepare")
|
|||
|
|
for p in (prepare,):
|
|||
|
|
p.add_argument("--persona", required=True)
|
|||
|
|
p.add_argument("--task-id", required=True)
|
|||
|
|
p.add_argument("--task", required=True)
|
|||
|
|
p.add_argument("--runtime", required=True, choices=tuple(RUNTIMES))
|
|||
|
|
run = sub.add_parser("run")
|
|||
|
|
run.add_argument("--persona", required=True)
|
|||
|
|
run.add_argument("--task-id", required=True)
|
|||
|
|
run.add_argument("--task", required=True)
|
|||
|
|
run.add_argument("--runtime", required=True, choices=("qwen", "codex"))
|
|||
|
|
run.add_argument("--cwd", required=True)
|
|||
|
|
args = parser.parse_args()
|
|||
|
|
try:
|
|||
|
|
if args.command == "probe":
|
|||
|
|
result = probe()
|
|||
|
|
else:
|
|||
|
|
value = envelope(args.persona, args.task_id, args.task, args.runtime)
|
|||
|
|
if args.command == "prepare":
|
|||
|
|
result = value
|
|||
|
|
else:
|
|||
|
|
cwd = pathlib.Path(args.cwd).resolve()
|
|||
|
|
if not cwd.is_dir():
|
|||
|
|
raise ValueError("TASK_CWD_REQUIRED")
|
|||
|
|
attempts = []
|
|||
|
|
completed = None
|
|||
|
|
selected_model = None
|
|||
|
|
if args.runtime == "qwen":
|
|||
|
|
for model in QWEN_MODELS:
|
|||
|
|
candidate = subprocess.run(
|
|||
|
|
[
|
|||
|
|
RUNTIMES["qwen"],
|
|||
|
|
"--auth-type", QWEN_AUTH_TYPE,
|
|||
|
|
"--model", model,
|
|||
|
|
"--prompt", prompt(value),
|
|||
|
|
"--output-format", "json",
|
|||
|
|
],
|
|||
|
|
cwd=cwd, capture_output=True, text=True, timeout=180, check=False,
|
|||
|
|
)
|
|||
|
|
ok = qwen_succeeded(candidate)
|
|||
|
|
attempts.append({"model": model, "exit_code": candidate.returncode, "semantic_success": ok})
|
|||
|
|
completed = candidate
|
|||
|
|
if ok:
|
|||
|
|
selected_model = model
|
|||
|
|
break
|
|||
|
|
else:
|
|||
|
|
candidate = subprocess.run(
|
|||
|
|
[
|
|||
|
|
RUNTIMES["codex"],
|
|||
|
|
"-a", "never",
|
|||
|
|
"exec",
|
|||
|
|
"--ephemeral",
|
|||
|
|
"--ignore-rules",
|
|||
|
|
"--sandbox", "read-only",
|
|||
|
|
"--cd", str(cwd),
|
|||
|
|
"--json",
|
|||
|
|
prompt(value),
|
|||
|
|
],
|
|||
|
|
cwd=cwd, capture_output=True, text=True, timeout=180, check=False,
|
|||
|
|
)
|
|||
|
|
ok = codex_succeeded(candidate)
|
|||
|
|
attempts.append({"model": "codex-configured-default", "exit_code": candidate.returncode, "semantic_success": ok})
|
|||
|
|
completed = candidate
|
|||
|
|
if ok:
|
|||
|
|
selected_model = "codex-configured-default"
|
|||
|
|
assert completed is not None
|
|||
|
|
success = selected_model is not None
|
|||
|
|
result = {
|
|||
|
|
"schema": "guanghu.persona-execution-limb-runtime-receipt/v1",
|
|||
|
|
"task_id": value["task_id"],
|
|||
|
|
"parent_persona_id": value["parent_persona_id"],
|
|||
|
|
"runtime": args.runtime,
|
|||
|
|
"selected_model": selected_model,
|
|||
|
|
"attempts": attempts,
|
|||
|
|
"exit_code": completed.returncode,
|
|||
|
|
"stdout": completed.stdout[-20000:],
|
|||
|
|
"stderr": completed.stderr[-4000:],
|
|||
|
|
"outcome": "PASS" if success else "FAIL",
|
|||
|
|
}
|
|||
|
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|||
|
|
return 0
|
|||
|
|
except Exception as error:
|
|||
|
|
print(json.dumps({"outcome": "FAIL", "error": str(error)}, ensure_ascii=False))
|
|||
|
|
return 1
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
raise SystemExit(main())
|