feat(ack): support project agent routing overrides

This commit is contained in:
2026-09-19 17:52:14 +08:00
parent 44cee4be91
commit f469c8f5f5
25 changed files with 746 additions and 83 deletions
+74
View File
@@ -432,6 +432,80 @@ class EnvironmentAndExecutableTests(unittest.TestCase):
class PlanTests(unittest.TestCase):
def test_agents_yaml_overrides_board_routing_and_binds_hash(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
project = Path(temporary).resolve()
ack_dir = project / ".pouch" / "ack"
ack_dir.mkdir(parents=True)
task_board = board(project)
agent_routing = {
"profileVersion": 1,
"mode": "orca",
"modelAllowlist": {
"pi": {"developer": {"standard": [None]}},
"cursor-agent": {"test": {"standard": ["auto"]}},
},
"profiles": {
"pi-dev": {
"role": "developer",
"cli": "pi",
"tier": "standard",
"model": None,
"reasoningEffort": "medium",
"permissionMode": "workspace-write",
},
"cursor-test": {
"role": "test",
"cli": "cursor-agent",
"tier": "standard",
"model": "auto",
"reasoningEffort": None,
"permissionMode": "workspace-write",
},
},
"defaults": {"developer": "pi-dev", "test": "cursor-test"},
}
agents_path = ack_dir / "agents.yaml"
agents_path.write_text(json.dumps(agent_routing), encoding="utf-8")
(ack_dir / "tasks.yaml").write_text(
json.dumps(task_board), encoding="utf-8"
)
executable = project / "pi"
executable.write_text("#!/bin/sh\n", encoding="utf-8")
executable.chmod(0o700)
with (
mock.patch.object(
launch_worker,
"capture_worktree_identity",
return_value=worktree_identity(project),
),
mock.patch.object(
launch_worker,
"resolve_executable",
return_value=executable,
),
mock.patch.object(
launch_worker,
"run_text",
return_value="pi 0.85.1",
),
):
plan = launch_worker.build_plan(
project_root_value=str(project),
task_id="TASK-001",
attempt_id="TASK-001-A1",
role="developer",
profile_id="pi-dev",
worktree_value=str(project),
slot=1,
)
self.assertEqual(plan["routingSource"], str(agents_path))
self.assertEqual(plan["routingHash"], worker_profiles.canonical_sha256(agent_routing))
self.assertEqual(plan["requested"]["model"], None)
self.assertEqual(plan["requested"]["argv"], [str(executable), "--thinking", "medium"])
def test_authoritative_board_is_derived_from_project_root_without_repo_path(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
project = Path(temporary).resolve()
+8 -1
View File
@@ -28,6 +28,9 @@ class AckSkillContentTests(unittest.TestCase):
"select_knowledge.py",
"select_regression.py",
"references/kickoff.md",
"agents.yaml",
"validate_agents.py",
"model: null",
"不要修改项目的 `AGENTS.md`",
"当前会话担任 Coordinator",
"intents.testEnvironment",
@@ -137,6 +140,10 @@ class AckSkillContentTests(unittest.TestCase):
"scripts/run_verification.py",
"scripts/worker_profiles.py",
"scripts/launch_worker.py",
"scripts/validate_agents.py",
"templates/agents.schema.json",
"templates/agents.template.yaml",
"examples/agents.example.yaml",
"templates/delivery.template.yaml",
"templates/delivery.schema.json",
"examples/delivery.example.yaml",
@@ -144,7 +151,7 @@ class AckSkillContentTests(unittest.TestCase):
):
self.assertTrue((ack_dir / relative_path).is_file(), relative_path)
version = (ack_dir / "VERSION").read_text(encoding="utf-8").strip()
self.assertEqual(version, "0.19.0")
self.assertEqual(version, "0.20.0")
self.assertIn(
f'ackVersion: "{version}"',
(ack_dir / "examples" / "tasks.example.yaml").read_text(encoding="utf-8"),
+71
View File
@@ -224,6 +224,39 @@ class CanonicalHashTests(unittest.TestCase):
class ProfileValidationTests(unittest.TestCase):
def test_pi_default_model_is_explicitly_supported(self) -> None:
routing = {
"profileVersion": 1,
"mode": "orca",
"modelAllowlist": {
"pi": {
"developer": {"standard": [None], "strong": [None]},
},
"cursor-agent": {"test": {"standard": ["auto"]}},
},
"profiles": {
"pi-dev": {
"role": "developer",
"cli": "pi",
"tier": "standard",
"model": None,
"reasoningEffort": "medium",
"permissionMode": "workspace-write",
},
"cursor-test": {
"role": "test",
"cli": "cursor-agent",
"tier": "standard",
"model": "auto",
"reasoningEffort": None,
"permissionMode": "workspace-write",
},
},
"defaults": {"developer": "pi-dev", "test": "cursor-test"},
}
self.assertEqual(worker_profiles.validate_orchestration(routing), [])
def test_valid_orchestration_passes(self) -> None:
self.assertEqual(
worker_profiles.validate_orchestration(valid_orchestration()),
@@ -406,6 +439,44 @@ class ProfileValidationTests(unittest.TestCase):
class ArgvRendererTests(unittest.TestCase):
def test_pi_default_model_omits_model_flag(self) -> None:
profile = {
"role": "developer",
"cli": "pi",
"tier": "standard",
"model": None,
"reasoningEffort": "medium",
"permissionMode": "workspace-write",
}
self.assertEqual(
worker_profiles.render_worker_argv(
profile,
"/home/ace/.local/share/mise/shims/pi",
"/repo/demo",
),
[
"/home/ace/.local/share/mise/shims/pi",
"--thinking",
"medium",
],
)
def test_pi_read_only_limits_tools(self) -> None:
profile = {
"role": "test",
"cli": "pi",
"tier": "standard",
"model": None,
"reasoningEffort": "low",
"permissionMode": "read-only",
}
self.assertEqual(
worker_profiles.render_worker_argv(profile, "/usr/local/bin/pi", "/repo/demo"),
["/usr/local/bin/pi", "--thinking", "low", "--tools", "read,grep,find,ls"],
)
def test_codex_exact_safe_argv(self) -> None:
profile = valid_orchestration()["profiles"]["codex-dev-standard"]