feat(orc): centralize host-aware routing
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate ORC config and build a worktree-bound Codex launch plan."""
|
||||
"""Validate shared ORC config and build a worktree-bound Agent launch plan."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -18,16 +18,17 @@ from typing import Any
|
||||
|
||||
LEVELS = ("low", "mid", "high")
|
||||
STAGES = ("code", "release", "deb", "docker")
|
||||
CLIS = ("codex", "cursor-agent")
|
||||
EXPECTED_EFFORT = {"low": "low", "mid": "medium", "high": "high"}
|
||||
TOP_LEVEL_KEYS = {
|
||||
"version",
|
||||
"cliPolicy",
|
||||
"defaultLevel",
|
||||
"stageDefaults",
|
||||
"allowedWorktrees",
|
||||
"worktreePolicy",
|
||||
"profiles",
|
||||
}
|
||||
PROFILE_KEYS = {
|
||||
"cli",
|
||||
"model",
|
||||
"reasoningEffort",
|
||||
"permissionMode",
|
||||
@@ -37,7 +38,7 @@ MODEL_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}\Z")
|
||||
MAX_CONFIG_SIZE = 64 * 1024
|
||||
MAX_CONTROL_OUTPUT = 64 * 1024
|
||||
CONTROL_TIMEOUT_SECONDS = 15
|
||||
CONFIG_RELATIVE_PATH = Path("docs/orc/config.yaml")
|
||||
SHARED_CONFIG_PATH = Path(__file__).resolve().parents[1] / "config.yaml"
|
||||
COMMON_ENVIRONMENT_NAMES = {
|
||||
"COLORTERM",
|
||||
"LANG",
|
||||
@@ -50,6 +51,16 @@ MODEL_AUTH_ENVIRONMENT = {
|
||||
"codex-login": frozenset(),
|
||||
"openai": frozenset({"OPENAI_API_KEY"}),
|
||||
"azure-openai": frozenset({"AZURE_OPENAI_API_KEY"}),
|
||||
"cursor-login": frozenset(),
|
||||
"cursor-api-key": frozenset({"CURSOR_API_KEY"}),
|
||||
}
|
||||
CLI_MODEL_AUTH = {
|
||||
"codex": frozenset({"codex-login", "openai", "azure-openai"}),
|
||||
"cursor-agent": frozenset({"cursor-login", "cursor-api-key"}),
|
||||
}
|
||||
DEFAULT_MODEL_AUTH = {
|
||||
"codex": "codex-login",
|
||||
"cursor-agent": "cursor-login",
|
||||
}
|
||||
REMOTE_AUTH_ENVIRONMENT = {
|
||||
"none": frozenset(),
|
||||
@@ -169,9 +180,11 @@ def validate_config(document: Any) -> dict[str, Any]:
|
||||
_exact_keys(config, required=TOP_LEVEL_KEYS, path="config")
|
||||
|
||||
version = config["version"]
|
||||
if isinstance(version, bool) or version != 1:
|
||||
raise ConfigError("version must be integer 1")
|
||||
if isinstance(version, bool) or version != 2:
|
||||
raise ConfigError("version must be integer 2")
|
||||
|
||||
if config["cliPolicy"] != "current-host":
|
||||
raise ConfigError("cliPolicy must be current-host in ORC v2")
|
||||
_enum(config["defaultLevel"], LEVELS, "defaultLevel")
|
||||
|
||||
stage_defaults = _mapping(config["stageDefaults"], "stageDefaults")
|
||||
@@ -183,53 +196,51 @@ def validate_config(document: Any) -> dict[str, Any]:
|
||||
for stage, level in stage_defaults.items():
|
||||
_enum(level, LEVELS, f"stageDefaults.{stage}")
|
||||
|
||||
allowed = config["allowedWorktrees"]
|
||||
if not isinstance(allowed, list) or not allowed:
|
||||
raise ConfigError("allowedWorktrees must be a non-empty list")
|
||||
seen_worktrees: set[str] = set()
|
||||
for index, entry in enumerate(allowed):
|
||||
if (
|
||||
not isinstance(entry, str)
|
||||
or not entry
|
||||
or entry != entry.strip()
|
||||
or any(character in entry for character in ("\x00", "\n", "\r"))
|
||||
):
|
||||
raise ConfigError(
|
||||
f"allowedWorktrees[{index}] must be a safe non-empty path"
|
||||
)
|
||||
if entry != "." and not Path(entry).is_absolute():
|
||||
raise ConfigError(
|
||||
f"allowedWorktrees[{index}] must be '.' or an absolute path"
|
||||
)
|
||||
normalized = entry if entry == "." else str(Path(entry).absolute())
|
||||
if normalized in seen_worktrees:
|
||||
raise ConfigError(f"allowedWorktrees contains duplicate path: {entry}")
|
||||
seen_worktrees.add(normalized)
|
||||
if config["worktreePolicy"] != "registered-same-repository":
|
||||
raise ConfigError(
|
||||
"worktreePolicy must be registered-same-repository in ORC v2"
|
||||
)
|
||||
|
||||
profiles = _mapping(config["profiles"], "profiles")
|
||||
_exact_keys(profiles, required=set(LEVELS), path="profiles")
|
||||
for level in LEVELS:
|
||||
profile = _mapping(profiles[level], f"profiles.{level}")
|
||||
_exact_keys(profile, required=PROFILE_KEYS, path=f"profiles.{level}")
|
||||
if profile["cli"] != "codex":
|
||||
raise ConfigError(f"profiles.{level}.cli must be codex in ORC v1")
|
||||
model = profile["model"]
|
||||
if not isinstance(model, str) or not MODEL_RE.fullmatch(model):
|
||||
raise ConfigError(f"profiles.{level}.model is not a safe exact model ID")
|
||||
expected_effort = EXPECTED_EFFORT[level]
|
||||
if profile["reasoningEffort"] != expected_effort:
|
||||
raise ConfigError(
|
||||
f"profiles.{level}.reasoningEffort must be {expected_effort}"
|
||||
)
|
||||
if profile["permissionMode"] != "workspace-write":
|
||||
raise ConfigError(
|
||||
f"profiles.{level}.permissionMode must be workspace-write in ORC v1"
|
||||
)
|
||||
_enum(
|
||||
profile["approvalPolicy"],
|
||||
{"untrusted", "on-request", "never"},
|
||||
f"profiles.{level}.approvalPolicy",
|
||||
)
|
||||
_exact_keys(profiles, required=set(CLIS), path="profiles")
|
||||
for cli in CLIS:
|
||||
cli_profiles = _mapping(profiles[cli], f"profiles.{cli}")
|
||||
_exact_keys(cli_profiles, required=set(LEVELS), path=f"profiles.{cli}")
|
||||
for level in LEVELS:
|
||||
path = f"profiles.{cli}.{level}"
|
||||
profile = _mapping(cli_profiles[level], path)
|
||||
_exact_keys(profile, required=PROFILE_KEYS, path=path)
|
||||
model = profile["model"]
|
||||
if not isinstance(model, str) or not MODEL_RE.fullmatch(model):
|
||||
raise ConfigError(f"{path}.model is not a safe exact model ID")
|
||||
if cli == "codex":
|
||||
expected_effort = EXPECTED_EFFORT[level]
|
||||
if profile["reasoningEffort"] != expected_effort:
|
||||
raise ConfigError(
|
||||
f"{path}.reasoningEffort must be {expected_effort}"
|
||||
)
|
||||
_enum(
|
||||
profile["approvalPolicy"],
|
||||
{"untrusted", "on-request", "never"},
|
||||
f"{path}.approvalPolicy",
|
||||
)
|
||||
else:
|
||||
if profile["reasoningEffort"] is not None:
|
||||
raise ConfigError(f"{path}.reasoningEffort: Cursor requires null")
|
||||
model_tokens = set(re.split(r"[^a-z0-9]+", model.lower()))
|
||||
expected_token = "medium" if level == "mid" else level
|
||||
if model != "auto" and expected_token not in model_tokens:
|
||||
raise ConfigError(
|
||||
f"{path}.model must encode the {expected_token} effort level"
|
||||
)
|
||||
if profile["approvalPolicy"] != "auto-review":
|
||||
raise ConfigError(
|
||||
f"{path}.approvalPolicy must be auto-review for Cursor"
|
||||
)
|
||||
if profile["permissionMode"] != "workspace-write":
|
||||
raise ConfigError(
|
||||
f"{path}.permissionMode must be workspace-write in ORC v2"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
@@ -260,7 +271,8 @@ def resolve_profile(
|
||||
stage: str,
|
||||
global_level: str | None = None,
|
||||
stage_level: str | None = None,
|
||||
model_auth: str = "codex-login",
|
||||
host_cli: str | None = None,
|
||||
model_auth: str | None = None,
|
||||
remote_auth: str = "none",
|
||||
) -> dict[str, Any]:
|
||||
_enum(stage, STAGES, "stage")
|
||||
@@ -268,9 +280,19 @@ def resolve_profile(
|
||||
_enum(global_level, LEVELS, "global level")
|
||||
if stage_level is not None:
|
||||
_enum(stage_level, LEVELS, "stage level")
|
||||
_enum(model_auth, set(MODEL_AUTH_ENVIRONMENT), "model auth")
|
||||
_enum(remote_auth, STAGE_REMOTE_AUTH[stage], f"{stage} remote auth")
|
||||
|
||||
if host_cli is None:
|
||||
raise ConfigError("host CLI is required; resolve it from the current Agent")
|
||||
selected_cli = _enum(host_cli, CLIS, "host CLI")
|
||||
cli_source = "runtime.host"
|
||||
selected_auth = model_auth or DEFAULT_MODEL_AUTH[selected_cli]
|
||||
_enum(selected_auth, set(MODEL_AUTH_ENVIRONMENT), "model auth")
|
||||
if selected_auth not in CLI_MODEL_AUTH[selected_cli]:
|
||||
raise ConfigError(
|
||||
f"model auth {selected_auth} is not valid for {selected_cli}"
|
||||
)
|
||||
|
||||
if stage_level is not None:
|
||||
level, source = stage_level, "request.stage"
|
||||
elif global_level is not None:
|
||||
@@ -280,25 +302,36 @@ def resolve_profile(
|
||||
else:
|
||||
level, source = config["defaultLevel"], "config.defaultLevel"
|
||||
|
||||
profile = config["profiles"].get(level)
|
||||
profile = config["profiles"][selected_cli].get(level)
|
||||
if profile is None:
|
||||
raise ConfigError(f"requested profile does not exist: {level}")
|
||||
worker_args = [
|
||||
"--model",
|
||||
profile["model"],
|
||||
"-c",
|
||||
f'model_reasoning_effort="{profile["reasoningEffort"]}"',
|
||||
"--sandbox",
|
||||
profile["permissionMode"],
|
||||
"--ask-for-approval",
|
||||
profile["approvalPolicy"],
|
||||
"--strict-config",
|
||||
]
|
||||
raise ConfigError(f"requested profile does not exist: {selected_cli}/{level}")
|
||||
if selected_cli == "codex":
|
||||
worker_args = [
|
||||
"--model",
|
||||
profile["model"],
|
||||
"-c",
|
||||
f'model_reasoning_effort="{profile["reasoningEffort"]}"',
|
||||
"--sandbox",
|
||||
profile["permissionMode"],
|
||||
"--ask-for-approval",
|
||||
profile["approvalPolicy"],
|
||||
"--strict-config",
|
||||
]
|
||||
else:
|
||||
worker_args = [
|
||||
"--model",
|
||||
profile["model"],
|
||||
"--auto-review",
|
||||
"--sandbox",
|
||||
"enabled",
|
||||
]
|
||||
return {
|
||||
"stage": stage,
|
||||
"cli": selected_cli,
|
||||
"cliSelectionSource": cli_source,
|
||||
"level": level,
|
||||
"selectionSource": source,
|
||||
"modelAuth": model_auth,
|
||||
"modelAuth": selected_auth,
|
||||
"remoteAuth": remote_auth,
|
||||
"profile": dict(profile),
|
||||
"workerArgs": worker_args,
|
||||
@@ -400,7 +433,7 @@ def _trusted_executable(path: Path, expected_name: str) -> Path | None:
|
||||
|
||||
|
||||
def resolve_trusted_executable(name: str) -> Path:
|
||||
if name not in {"codex", "git", "orca"}:
|
||||
if name not in {"codex", "cursor-agent", "git", "orca"}:
|
||||
raise ConfigError(f"unsupported executable: {name}")
|
||||
search_paths = trusted_path_entries()
|
||||
if name == "git":
|
||||
@@ -572,17 +605,10 @@ def validate_worktree(
|
||||
except ConfigError:
|
||||
continue
|
||||
|
||||
allowed: set[Path] = set()
|
||||
for entry in config["allowedWorktrees"]:
|
||||
allowed.add(
|
||||
project_root
|
||||
if entry == "."
|
||||
else canonical_directory(entry, "allowed worktree")
|
||||
)
|
||||
if not allowed <= registered:
|
||||
raise ConfigError("allowedWorktrees contains an unregistered Git worktree")
|
||||
if worktree not in allowed:
|
||||
raise ConfigError("target worktree is not in allowedWorktrees")
|
||||
if config["worktreePolicy"] != "registered-same-repository":
|
||||
raise ConfigError("unsupported worktree policy")
|
||||
if worktree not in registered:
|
||||
raise ConfigError("target worktree is not registered in the project repository")
|
||||
|
||||
target_top = canonical_directory(
|
||||
_run_control(
|
||||
@@ -631,7 +657,7 @@ def _file_facts(path: Path) -> dict[str, Any]:
|
||||
def _executable_facts(path: Path) -> dict[str, Any]:
|
||||
return {
|
||||
**_file_facts(path),
|
||||
"version": _run_control([str(path), "--version"], "Codex version check"),
|
||||
"version": _run_control([str(path), "--version"], "Agent CLI version check"),
|
||||
}
|
||||
|
||||
|
||||
@@ -663,22 +689,19 @@ def _fingerprint(value: dict[str, Any]) -> str:
|
||||
|
||||
|
||||
def build_launch_plan(
|
||||
config_path: Path,
|
||||
*,
|
||||
project_root: str | Path,
|
||||
worktree: str | Path,
|
||||
stage: str,
|
||||
host_cli: str,
|
||||
global_level: str | None = None,
|
||||
stage_level: str | None = None,
|
||||
model_auth: str = "codex-login",
|
||||
model_auth: str | None = None,
|
||||
remote_auth: str = "none",
|
||||
) -> dict[str, Any]:
|
||||
project = canonical_directory(project_root, "project root")
|
||||
expected_config = project / CONFIG_RELATIVE_PATH
|
||||
if config_path.absolute() != expected_config:
|
||||
raise ConfigError(f"config path must be {expected_config}")
|
||||
_assert_no_symlink_components(expected_config, "config path")
|
||||
config, config_snapshot = load_config_snapshot(expected_config)
|
||||
_assert_no_symlink_components(SHARED_CONFIG_PATH, "shared config path")
|
||||
config, config_snapshot = load_config_snapshot(SHARED_CONFIG_PATH)
|
||||
worktree_facts = validate_worktree(
|
||||
config,
|
||||
project_root_value=project,
|
||||
@@ -689,16 +712,17 @@ def build_launch_plan(
|
||||
stage=stage,
|
||||
global_level=global_level,
|
||||
stage_level=stage_level,
|
||||
host_cli=host_cli,
|
||||
model_auth=model_auth,
|
||||
remote_auth=remote_auth,
|
||||
)
|
||||
selected_credential_names(
|
||||
stage,
|
||||
model_auth=model_auth,
|
||||
model_auth=profile["modelAuth"],
|
||||
remote_auth=remote_auth,
|
||||
)
|
||||
codex = resolve_trusted_executable("codex")
|
||||
executable = _executable_facts(codex)
|
||||
agent_cli = resolve_trusted_executable(profile["cli"])
|
||||
executable = _executable_facts(agent_cli)
|
||||
orca = resolve_trusted_executable("orca")
|
||||
orca_executable = _file_facts(orca)
|
||||
python = resolve_trusted_python()
|
||||
@@ -710,7 +734,7 @@ def build_launch_plan(
|
||||
**profile,
|
||||
**worktree_facts,
|
||||
"config": {
|
||||
"path": str(expected_config),
|
||||
"path": str(SHARED_CONFIG_PATH),
|
||||
**config_snapshot,
|
||||
},
|
||||
"executable": executable,
|
||||
@@ -726,14 +750,15 @@ def build_launch_plan(
|
||||
},
|
||||
}
|
||||
fingerprint = _fingerprint(launch_facts)
|
||||
worker_argv = [str(codex), *profile["workerArgs"]]
|
||||
worker_argv = [str(agent_cli), *profile["workerArgs"]]
|
||||
if profile["cli"] == "cursor-agent":
|
||||
worker_argv.extend(["--workspace", str(worktree_facts["worktree"])])
|
||||
launcher_argv = [
|
||||
str(python),
|
||||
"-I",
|
||||
"-S",
|
||||
str(script),
|
||||
"_launch",
|
||||
str(expected_config),
|
||||
"--project-root",
|
||||
str(project),
|
||||
"--worktree",
|
||||
@@ -747,13 +772,17 @@ def build_launch_plan(
|
||||
launcher_argv.extend(["--global-level", global_level])
|
||||
if stage_level is not None:
|
||||
launcher_argv.extend(["--stage-level", stage_level])
|
||||
if model_auth != "codex-login":
|
||||
launcher_argv.extend(["--host-cli", host_cli])
|
||||
if model_auth is not None:
|
||||
launcher_argv.extend(["--model-auth", model_auth])
|
||||
if remote_auth != "none":
|
||||
launcher_argv.extend(["--remote-auth", remote_auth])
|
||||
terminal_command = shlex.join(launcher_argv)
|
||||
worktree_selector = "path:" + str(worktree_facts["worktree"])
|
||||
terminal_title = f"ORC-{stage}-{profile['level']}-{fingerprint[7:15]}"
|
||||
cli_label = "CODEX" if profile["cli"] == "codex" else "CURSOR"
|
||||
terminal_title = (
|
||||
f"ORC-{stage}-{cli_label}-{profile['level']}-{fingerprint[7:15]}"
|
||||
)
|
||||
terminal_create_argv = [
|
||||
str(orca),
|
||||
"terminal",
|
||||
@@ -781,10 +810,10 @@ def build_launch_plan(
|
||||
|
||||
def execute_launch(args: argparse.Namespace) -> int:
|
||||
plan = build_launch_plan(
|
||||
args.config,
|
||||
project_root=args.project_root,
|
||||
worktree=args.worktree,
|
||||
stage=args.stage,
|
||||
host_cli=args.host_cli,
|
||||
global_level=args.global_level,
|
||||
stage_level=args.stage_level,
|
||||
model_auth=args.model_auth,
|
||||
@@ -802,7 +831,7 @@ def execute_launch(args: argparse.Namespace) -> int:
|
||||
raise ConfigError("Python executable changed before launch")
|
||||
executable = Path(plan["executable"]["path"])
|
||||
if _executable_facts(executable) != plan["executable"]:
|
||||
raise ConfigError("Codex executable changed before launch")
|
||||
raise ConfigError("Agent CLI executable changed before launch")
|
||||
try:
|
||||
os.execve(
|
||||
executable,
|
||||
@@ -814,21 +843,20 @@ def execute_launch(args: argparse.Namespace) -> int:
|
||||
),
|
||||
)
|
||||
except OSError as exc:
|
||||
raise ConfigError("Codex worker could not be launched") from exc
|
||||
raise ConfigError("Agent worker could not be launched") from exc
|
||||
return 1 # pragma: no cover - os.execve does not return on success
|
||||
|
||||
|
||||
def _add_resolution_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument("config", type=Path)
|
||||
parser.add_argument("--project-root", required=True)
|
||||
parser.add_argument("--worktree", required=True)
|
||||
parser.add_argument("--stage", required=True, choices=STAGES)
|
||||
parser.add_argument("--global-level", choices=LEVELS)
|
||||
parser.add_argument("--stage-level", choices=LEVELS)
|
||||
parser.add_argument("--host-cli", required=True, choices=CLIS)
|
||||
parser.add_argument(
|
||||
"--model-auth",
|
||||
choices=tuple(MODEL_AUTH_ENVIRONMENT),
|
||||
default="codex-login",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--remote-auth",
|
||||
@@ -842,8 +870,6 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
validate = subparsers.add_parser("validate", help="validate config only")
|
||||
validate.add_argument("config", type=Path)
|
||||
|
||||
resolve = subparsers.add_parser("resolve", help="resolve a bound launch plan")
|
||||
_add_resolution_arguments(resolve)
|
||||
|
||||
@@ -857,20 +883,22 @@ def main(argv: list[str] | None = None) -> int:
|
||||
args = build_parser().parse_args(argv)
|
||||
try:
|
||||
if args.command == "validate":
|
||||
config = load_config(args.config)
|
||||
config = load_config(SHARED_CONFIG_PATH)
|
||||
result: dict[str, Any] = {
|
||||
"ok": True,
|
||||
"config": str(args.config.absolute()),
|
||||
"config": str(SHARED_CONFIG_PATH),
|
||||
"clis": list(CLIS),
|
||||
"levels": list(LEVELS),
|
||||
"stages": list(STAGES),
|
||||
"cliPolicy": config["cliPolicy"],
|
||||
"defaultLevel": config["defaultLevel"],
|
||||
}
|
||||
elif args.command == "resolve":
|
||||
result = build_launch_plan(
|
||||
args.config,
|
||||
project_root=args.project_root,
|
||||
worktree=args.worktree,
|
||||
stage=args.stage,
|
||||
host_cli=args.host_cli,
|
||||
global_level=args.global_level,
|
||||
stage_level=args.stage_level,
|
||||
model_auth=args.model_auth,
|
||||
|
||||
Reference in New Issue
Block a user