feat(tester): initialize agent target from Orca selectors

This commit is contained in:
2026-10-12 01:14:52 +08:00
parent 5ba1d8e3d6
commit a35cc916cb
9 changed files with 688 additions and 10 deletions
+258
View File
@@ -0,0 +1,258 @@
#!/usr/bin/env python3
"""Resolve an Orca project or terminal into tester's structured agent config."""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
from pathlib import Path
from typing import Any
try:
import yaml
except ImportError: # pragma: no cover
print("需要 PyYAML: python3 -m pip install pyyaml", file=sys.stderr)
raise SystemExit(2)
SUPPORTED_CLI = {"codex", "cursor-agent", "grok", "omp", "pi"}
def run_orca(args: list[str], environment: str | None = None) -> dict[str, Any]:
command = ["orca", *args]
if environment and environment != "local":
command.extend(["--environment", environment])
command.append("--json")
proc = subprocess.run(command, capture_output=True, text=True, check=False)
raw = (proc.stdout or "").strip()
try:
data = json.loads(raw)
except json.JSONDecodeError as exc:
detail = (proc.stderr or raw or str(exc)).strip()
raise RuntimeError(f"orca 命令没有返回 JSON: {' '.join(command)}\n{detail}") from exc
if not data.get("ok"):
error = data.get("error") or {}
message = error.get("message") or error.get("code") or "unknown error"
raise RuntimeError(f"{' '.join(command)}: {message}")
result = data.get("result")
return result if isinstance(result, dict) else {}
def environments() -> list[str]:
result = run_orca(["environment", "list"])
names = ["local"]
for item in result.get("environments") or []:
if isinstance(item, dict) and item.get("name"):
names.append(str(item["name"]))
return names
def worktree_from_terminal(terminal: dict[str, Any], environment: str) -> dict[str, Any]:
worktree_id = str(terminal.get("worktreeId") or "")
if not worktree_id:
raise RuntimeError("terminal 没有 worktreeId")
result = run_orca(["worktree", "show", "--worktree", worktree_id], environment)
worktree = result.get("worktree")
if not isinstance(worktree, dict):
raise RuntimeError("找不到 terminal 对应的 worktree")
return worktree
def config_from_terminal(handle: str, requested_environment: str | None) -> dict[str, Any]:
candidates = [requested_environment] if requested_environment else environments()
errors: list[str] = []
for environment in candidates:
if not environment:
continue
try:
result = run_orca(["terminal", "show", "--terminal", handle], environment)
terminal = result.get("terminal")
if not isinstance(terminal, dict):
raise RuntimeError("返回缺少 terminal")
worktree = worktree_from_terminal(terminal, environment)
identity = str(terminal.get("agentIdentity") or "")
cli = identity if identity in SUPPORTED_CLI else "codex"
return {
"environment": "" if environment == "local" else environment,
"host": str(terminal.get("executionHostId") or worktree.get("hostId") or "local"),
"project": str(worktree.get("projectId") or worktree.get("repoId") or ""),
"path": str(terminal.get("worktreePath") or worktree.get("path") or ""),
"role": "test",
"cli": cli,
"tier": "standard",
"model": None,
"reasoningEffort": "medium",
"permissionMode": "workspace-write",
"_source": f"terminal:{handle}",
"_project_name": str(worktree.get("displayName") or ""),
"_terminal_agent_identity": identity,
}
except RuntimeError as exc:
errors.append(f"{environment}: {exc}")
raise RuntimeError("找不到可用 terminal: " + ";".join(errors))
def worktrees(environment: str) -> list[dict[str, Any]]:
result = run_orca(["worktree", "list", "--limit", "200"], environment)
return [item for item in result.get("worktrees") or [] if isinstance(item, dict)]
def projects(environment: str) -> list[dict[str, Any]]:
result = run_orca(["project", "list"], environment)
return [item for item in result.get("projects") or [] if isinstance(item, dict)]
def config_from_project(
selector: str,
requested_environment: str | None,
requested_path: str | None = None,
) -> dict[str, Any]:
environments_to_check = [requested_environment] if requested_environment else environments()
matches: list[tuple[str, dict[str, Any]]] = []
for environment in environments_to_check:
if not environment:
continue
project_ids: set[str] = set()
for project in projects(environment):
project_values = {
str(project.get("id") or ""),
str(project.get("displayName") or ""),
}
if selector in project_values:
project_ids.add(str(project.get("id") or ""))
project_ids.update(str(repo_id) for repo_id in project.get("sourceRepoIds") or [])
for item in worktrees(environment):
values = {
str(item.get("id") or ""),
str(item.get("projectId") or ""),
str(item.get("repoId") or ""),
str(item.get("displayName") or ""),
str(item.get("path") or ""),
Path(str(item.get("path") or "")).name,
}
direct_match = selector in values
project_match = bool(
project_ids
and project_ids.intersection(
{
str(item.get("projectId") or ""),
str(item.get("repoId") or ""),
}
)
)
path_match = not requested_path or str(item.get("path") or "") == requested_path
if path_match and (direct_match or project_match):
matches.append((environment, item))
if not matches:
raise RuntimeError(f"找不到 project id/名称: {selector}")
if len(matches) > 1:
lines = [
f"{env}: {item.get('projectId') or item.get('repoId')} "
f"{item.get('displayName') or ''} {item.get('path') or ''}"
for env, item in matches
]
raise RuntimeError("project 匹配多个 worktree,请补充 environment 或 path:\n" + "\n".join(lines))
environment, item = matches[0]
return {
"environment": "" if environment == "local" else environment,
"host": str(item.get("hostId") or "local"),
"project": str(item.get("projectId") or item.get("repoId") or item.get("id") or ""),
"path": str(item.get("path") or ""),
"role": "test",
"cli": "codex",
"tier": "standard",
"model": None,
"reasoningEffort": "medium",
"permissionMode": "workspace-write",
"_source": f"project:{selector}",
"_project_name": str(item.get("displayName") or ""),
}
def clean_config(config: dict[str, Any]) -> dict[str, Any]:
return {key: value for key, value in config.items() if not key.startswith("_")}
def write_local(root: Path, config: dict[str, Any]) -> tuple[Path, dict[str, Any]]:
path = root / ".pouch" / "tester" / "local.yaml"
path.parent.mkdir(parents=True, exist_ok=True)
data: dict[str, Any] = {}
if path.is_file():
loaded = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
if not isinstance(loaded, dict):
raise RuntimeError(f"{path} 必须是 mapping")
data = loaded
current_agent = data.get("agent")
if current_agent is None:
current_agent = {}
if not isinstance(current_agent, dict):
raise RuntimeError(f"{path} 的 agent 必须是 mapping")
resolved = clean_config(config)
# Target location always comes from the user's selector. Keep explicit local
# choices for model/policy fields, so resolving a terminal cannot erase them.
location_fields = {"environment", "host", "project", "path"}
terminal_cli = bool(config.get("_terminal_agent_identity"))
merged = dict(current_agent)
for key, value in resolved.items():
if key in location_fields or key not in merged or (key == "cli" and terminal_cli):
merged[key] = value
data["agent"] = merged
path.write_text(yaml.safe_dump(data, allow_unicode=True, sort_keys=False), encoding="utf-8")
return path, merged
def print_confirmation(config: dict[str, Any], written: bool) -> None:
model = config.get("model") or "CLI 默认模型"
print("## tester Agent 配置更新结果" if written else "## tester Agent 配置解析预览")
print()
print(f"来源: {config.get('_source', '配置文件')}")
print(f"Agent: {config.get('cli')}")
print(f"角色 / 档位: {config.get('role')} / {config.get('tier')}")
print(f"模型: {model}")
print(f"推理强度: {config.get('reasoningEffort') or 'n/a'}")
print(f"权限: {config.get('permissionMode') or 'n/a'}")
print(f"Orca 环境: {config.get('environment') or 'local'}")
print(f"执行节点: {config.get('host') or 'local'}")
print(f"项目: {config.get('project') or 'n/a'}({config.get('_project_name') or 'n/a'})")
print(f"目录: {config.get('path') or 'n/a'}")
print("Terminal: 每轮新建,结束后关闭,不复用输入的 terminal")
print()
if written:
print("以上是已写入 local.yaml.agent 的生效配置;如需调整,直接修改配置后重新运行 tester。")
else:
print("以上是解析结果预览;加上 --write 后会写入 local.yaml.agent。")
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="解析 Orca project/terminal 为 tester Agent 配置")
parser.add_argument("--root", default=".", help="项目根")
source = parser.add_mutually_exclusive_group(required=True)
source.add_argument("--terminal", help="Orca terminal handle")
source.add_argument("--project", help="Orca project id、名称或路径")
parser.add_argument("--path", help="项目有多个 worktree 时按目标机器上的绝对路径消歧")
parser.add_argument("--environment", help="限定 Orca environment")
parser.add_argument("--write", action="store_true", help="写入 .pouch/tester/local.yaml")
args = parser.parse_args(argv)
try:
config = (
config_from_terminal(args.terminal, args.environment)
if args.terminal
else config_from_project(args.project, args.environment, args.path)
)
if args.write:
path, saved_agent = write_local(Path(args.root).resolve(), config)
config = {**config, **saved_agent}
print(f"已写入: {path}")
print_confirmation(config, written=args.write)
return 0
except RuntimeError as exc:
print(f"environment-fail: {exc}", file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())