sync: agent-ops from agentic-framework v1.1.189

This commit is contained in:
toki 2026-08-07 15:29:21 +09:00
parent 270c724664
commit be6d9d6bce
3 changed files with 113 additions and 27 deletions

View file

@ -1 +1 @@
1.1.188 1.1.189

View file

@ -426,11 +426,50 @@ def epic_cycle_script(workspace: Path) -> Path:
def dispatcher_script(workspace: Path) -> Path: def dispatcher_script(workspace: Path) -> Path:
root = workspace / "agent-ops" / "skills" / "common" / "orchestrate-agent-task-loop" skills_root = workspace / "agent-ops" / "skills"
path = root / "scripts" / "dispatch.py" project_root = skills_root / "project" / "orchestrate-agent-task-loop"
if not path.is_file(): project_dispatcher = project_root / "scripts" / "dispatch.py"
raise PreparationError(f"dispatcher script not found: {path}") if project_dispatcher.is_file():
return path private_root = skills_root / "private" / "orchestrate-agent-task-loop"
private_dispatcher = private_root / "scripts" / "dispatch.py"
if (private_root / "SKILL.md").is_file() and private_dispatcher.is_file():
return private_dispatcher
return project_dispatcher
common_dispatcher = (
skills_root / "common" / "orchestrate-agent-task-loop" / "scripts" / "dispatch.py"
)
if not common_dispatcher.is_file():
raise PreparationError(f"dispatcher script not found: {common_dispatcher}")
return common_dispatcher
def dispatcher_command(
*,
workspace: Path,
dispatcher: Path,
task_group: str,
execution_catalog: str,
) -> list[str]:
command = [
sys.executable,
str(dispatcher),
"--workspace",
str(workspace),
"--task-group",
task_group,
]
common_dispatcher = (
workspace
/ "agent-ops"
/ "skills"
/ "common"
/ "orchestrate-agent-task-loop"
/ "scripts"
/ "dispatch.py"
)
if dispatcher.resolve() == common_dispatcher.resolve():
command.extend(["--execution-catalog", execution_catalog])
return command
def epic_cycle_command( def epic_cycle_command(
@ -883,18 +922,15 @@ def coordinate_batch(
dispatcher = dispatcher_script(workspace) dispatcher = dispatcher_script(workspace)
task_group = f"m-{milestone_slug}" task_group = f"m-{milestone_slug}"
if not state.get("dispatcher_dry_run_done"): if not state.get("dispatcher_dry_run_done"):
dry_run_command = dispatcher_command(
workspace=workspace,
dispatcher=dispatcher,
task_group=task_group,
execution_catalog=args.execution_catalog,
)
dry_run_command.append("--dry-run")
dry_run = run( dry_run = run(
[ dry_run_command,
sys.executable,
str(dispatcher),
"--workspace",
str(workspace),
"--task-group",
task_group,
"--execution-catalog",
args.execution_catalog,
"--dry-run",
],
cwd=workspace, cwd=workspace,
check=False, check=False,
capture=False, capture=False,
@ -907,16 +943,12 @@ def coordinate_batch(
atomic_json(state_path, state) atomic_json(state_path, state)
emit("DISPATCHER_DRY_RUN_FINISHED", task_group=task_group) emit("DISPATCHER_DRY_RUN_FINISHED", task_group=task_group)
command = [ command = dispatcher_command(
sys.executable, workspace=workspace,
str(dispatcher), dispatcher=dispatcher,
"--workspace", task_group=task_group,
str(workspace), execution_catalog=args.execution_catalog,
"--task-group", )
task_group,
"--execution-catalog",
args.execution_catalog,
]
if resume_blocked_dispatcher and args.retry: if resume_blocked_dispatcher and args.retry:
command.append("--retry-blocked") command.append("--retry-blocked")
try: try:

View file

@ -51,6 +51,60 @@ class PrepareWorkspaceTest(unittest.TestCase):
Path("/tmp/example/sample-feature-worktree"), Path("/tmp/example/sample-feature-worktree"),
) )
def test_dispatcher_prefers_project_override_and_private_pair(self) -> None:
with tempfile.TemporaryDirectory() as raw:
workspace = Path(raw)
common = (
workspace
/ "agent-ops/skills/common/orchestrate-agent-task-loop/scripts/dispatch.py"
)
project = (
workspace
/ "agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py"
)
private_root = (
workspace / "agent-ops/skills/private/orchestrate-agent-task-loop"
)
private = private_root / "scripts/dispatch.py"
for path in (common, project, private):
path.parent.mkdir(parents=True, exist_ok=True)
path.touch()
self.assertEqual(MODULE.dispatcher_script(workspace), project)
(private_root / "SKILL.md").touch()
self.assertEqual(MODULE.dispatcher_script(workspace), private)
project.unlink()
self.assertEqual(MODULE.dispatcher_script(workspace), common)
def test_dispatcher_command_injects_catalog_only_for_common_runtime(self) -> None:
workspace = Path("/repo")
common = (
workspace
/ "agent-ops/skills/common/orchestrate-agent-task-loop/scripts/dispatch.py"
)
project = (
workspace
/ "agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py"
)
common_command = MODULE.dispatcher_command(
workspace=workspace,
dispatcher=common,
task_group="m-sample",
execution_catalog="/runtime/catalog.json",
)
project_command = MODULE.dispatcher_command(
workspace=workspace,
dispatcher=project,
task_group="m-sample",
execution_catalog="/runtime/catalog.json",
)
self.assertIn("--execution-catalog", common_command)
self.assertNotIn("--execution-catalog", project_command)
def test_epic_document_range_is_one_based_and_inclusive(self) -> None: def test_epic_document_range_is_one_based_and_inclusive(self) -> None:
epics = MODULE.parse_epics( epics = MODULE.parse_epics(
"""## 기능 """## 기능