2026-05-02 03:11:49 +02:00
|
|
|
"""Tests for per-role spawn manifest construction."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from uuid import uuid4
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
from roboco.runtime.spawn_manifest import SpawnInputs, build_for_role, write_manifest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestBuildForRole:
|
|
|
|
|
def test_developer_manifest(self) -> None:
|
|
|
|
|
m = build_for_role(
|
|
|
|
|
SpawnInputs(
|
|
|
|
|
agent_id=uuid4(),
|
|
|
|
|
role="developer",
|
|
|
|
|
team="backend",
|
|
|
|
|
workspace_path=Path("/data/workspaces/roboco/backend/be-dev-1"),
|
2026-06-03 06:35:03 +02:00
|
|
|
agent_model="minimax-m3:cloud",
|
2026-05-02 03:11:49 +02:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
assert "i_am_done" in m.flow_tools
|
|
|
|
|
assert "commit" in m.do_tools
|
2026-06-03 06:35:03 +02:00
|
|
|
# Issue #8: the developer manifest must carry `evidence` so the
|
|
|
|
|
# do-server registers mcp__roboco-do__evidence inside the container.
|
|
|
|
|
assert "evidence" in m.do_tools
|
2026-05-02 03:11:49 +02:00
|
|
|
assert "Edit" in m.write_tools
|
|
|
|
|
assert m.subagent_allowed is False
|
|
|
|
|
assert m.subagent_model is None # devs don't dispatch
|
|
|
|
|
assert m.bash_allowed is True
|
|
|
|
|
assert "ROBOCO_SDK_URL" in m.env or "ROBOCO_PUBLIC_BASE_URL" in m.env
|
|
|
|
|
|
|
|
|
|
def test_main_pm_manifest_subagent_uses_parent_model(self) -> None:
|
|
|
|
|
m = build_for_role(
|
|
|
|
|
SpawnInputs(
|
|
|
|
|
agent_id=uuid4(),
|
|
|
|
|
role="main_pm",
|
|
|
|
|
team="board",
|
|
|
|
|
workspace_path=Path("/data/workspaces/roboco/board/main-pm"),
|
2026-06-03 06:35:03 +02:00
|
|
|
agent_model="minimax-m3:cloud",
|
2026-05-02 03:11:49 +02:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
assert m.subagent_allowed is True
|
2026-06-03 06:35:03 +02:00
|
|
|
assert m.subagent_model == "minimax-m3:cloud"
|
2026-05-02 03:11:49 +02:00
|
|
|
|
|
|
|
|
def test_qa_manifest_no_write(self) -> None:
|
|
|
|
|
m = build_for_role(
|
|
|
|
|
SpawnInputs(
|
|
|
|
|
agent_id=uuid4(),
|
|
|
|
|
role="qa",
|
|
|
|
|
team="backend",
|
|
|
|
|
workspace_path=Path("/data/workspaces/roboco/backend/be-qa"),
|
2026-06-03 06:35:03 +02:00
|
|
|
agent_model="minimax-m3:cloud",
|
2026-05-02 03:11:49 +02:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
assert m.write_tools == []
|
|
|
|
|
|
|
|
|
|
def test_unknown_role_raises(self) -> None:
|
|
|
|
|
with pytest.raises(KeyError):
|
|
|
|
|
build_for_role(
|
|
|
|
|
SpawnInputs(
|
|
|
|
|
agent_id=uuid4(),
|
|
|
|
|
role="unknown",
|
|
|
|
|
team="x",
|
|
|
|
|
workspace_path=Path("/tmp/x"),
|
|
|
|
|
agent_model="x",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2026-06-15 02:40:39 +02:00
|
|
|
@pytest.mark.parametrize("role", ["product_owner", "head_marketing"])
|
|
|
|
|
def test_board_roles_carry_read_messages(self, role: str) -> None:
|
|
|
|
|
"""Board roles must keep read_messages so they can clear unread A2A and
|
|
|
|
|
i_am_idle (clean shutdown). A board agent spawned from a manifest that
|
|
|
|
|
omits it gets soft-blocked on i_am_idle forever — guard against the grant
|
|
|
|
|
silently dropping from _BOARD_DO.
|
|
|
|
|
"""
|
|
|
|
|
m = build_for_role(
|
|
|
|
|
SpawnInputs(
|
|
|
|
|
agent_id=uuid4(),
|
|
|
|
|
role=role,
|
|
|
|
|
team="board",
|
|
|
|
|
workspace_path=Path("/tmp/x"),
|
|
|
|
|
agent_model="claude-opus-4-6",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
assert "read_messages" in m.do_tools
|
|
|
|
|
# The full A2A/notification-receiver set must be present together.
|
|
|
|
|
for tool in ("notify_list", "notify_get", "notify_ack"):
|
|
|
|
|
assert tool in m.do_tools
|
|
|
|
|
|
2026-05-02 03:11:49 +02:00
|
|
|
|
|
|
|
|
class TestWriteManifest:
|
|
|
|
|
def test_writes_json(self, tmp_path: Path) -> None:
|
|
|
|
|
m = build_for_role(
|
|
|
|
|
SpawnInputs(
|
|
|
|
|
agent_id=uuid4(),
|
|
|
|
|
role="developer",
|
|
|
|
|
team="backend",
|
|
|
|
|
workspace_path=tmp_path,
|
|
|
|
|
agent_model="claude-opus-4-7",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
manifest_path = tmp_path / "tool-manifest.json"
|
|
|
|
|
write_manifest(m, manifest_path)
|
|
|
|
|
data = json.loads(manifest_path.read_text())
|
|
|
|
|
assert data["role"] == "developer"
|
|
|
|
|
assert "i_am_done" in data["flow_tools"]
|
|
|
|
|
assert data["bash_allowed"] is True
|