feat: adopt the AGENTS.md expert-skill contract (#404)
* feat: detect expert skills by AGENTS.md presence * feat: let the orchestrator choose the expert dispatch tool * refactor: drop per-skill expert dispatch classification * chore: remove duplicate TestSkillManager test classes left by rebase * fix: clear legacy actor fields when AGENTS.md declares the expert * refactor: move the expert prompt into ActiveTeamMiddleware
This commit is contained in:
@@ -50,7 +50,6 @@ class _FakeSkillInfo:
|
||||
name: str
|
||||
description: str = ""
|
||||
role: str = ""
|
||||
default_dispatch: str = ""
|
||||
type: str = "expert"
|
||||
tags: list[str] = field(default_factory=list)
|
||||
source: str = "builtin"
|
||||
@@ -58,6 +57,11 @@ class _FakeSkillInfo:
|
||||
# ``list_dispatchable_experts``. Tests that specifically want to
|
||||
# exercise the empty-body reject path pass ``body=""``.
|
||||
body: str = "persona"
|
||||
# Legacy-frontmatter expert by default: ``expert_prompt_body`` reads
|
||||
# ``body`` for these. Set ``expert_source="agents_md"`` plus
|
||||
# ``agents_body`` to fake an expert on the current contract.
|
||||
expert_source: str = "frontmatter"
|
||||
agents_body: str = ""
|
||||
|
||||
|
||||
def _make_ctx(active_teams: list[str] | None = None) -> tuple[CommandContext, _FakeUI]:
|
||||
@@ -83,7 +87,6 @@ class TestExpertsList:
|
||||
_FakeSkillInfo(
|
||||
name="idea-brainstorm",
|
||||
role="Research idea brainstormer",
|
||||
default_dispatch="sync",
|
||||
),
|
||||
],
|
||||
):
|
||||
@@ -110,7 +113,6 @@ class TestExpertsList:
|
||||
_FakeSkillInfo(
|
||||
name="idea-brainstorm",
|
||||
role="Research idea brainstormer",
|
||||
default_dispatch="sync",
|
||||
),
|
||||
],
|
||||
):
|
||||
@@ -136,31 +138,27 @@ class TestExpertToggle:
|
||||
)
|
||||
assert ctx.channel_runtime.active_teams == []
|
||||
|
||||
async def test_async_expert_refused_with_reason_when_async_unavailable(self):
|
||||
"""When an installed expert declares ``default_dispatch: async`` but
|
||||
async dispatch is unavailable, ``/expert`` must refuse with the specific
|
||||
reason — not the empty-body / name-collision default — so the user
|
||||
knows to enable ``enable_async_subagents`` or start langgraph dev.
|
||||
Reviewer thread on PR #391."""
|
||||
ctx, ui = _make_ctx()
|
||||
async_expert = _FakeSkillInfo(
|
||||
name="literature-review", default_dispatch="async"
|
||||
)
|
||||
async def test_async_outage_does_not_block_invite(self):
|
||||
"""An async outage must not make an installed expert un-invitable.
|
||||
|
||||
The old per-skill classification refused here whenever
|
||||
``enable_async_subagents`` was off or langgraph dev was unreachable.
|
||||
Every expert now keeps its in-turn reach, so the outage degrades the
|
||||
reach rather than removing the expert.
|
||||
"""
|
||||
ctx, _ui = _make_ctx()
|
||||
with (
|
||||
patch(
|
||||
"EvoScientist.tools.skills_manager.list_expert_skills",
|
||||
return_value=[async_expert],
|
||||
return_value=[_FakeSkillInfo(name="literature-review")],
|
||||
),
|
||||
patch(
|
||||
"EvoScientist.subagents.expert_container.is_async_dispatch_available",
|
||||
"EvoScientist.langgraph_dev.manager.is_async_subagents_available",
|
||||
return_value=False,
|
||||
),
|
||||
):
|
||||
await ExpertCommand().execute(ctx, args=["literature-review"])
|
||||
assert any("async dispatch is unavailable" in text for text, _ in ui.lines), (
|
||||
f"expected honest async-unavailable message, got: {ui.lines}"
|
||||
)
|
||||
assert ctx.channel_runtime.active_teams == []
|
||||
assert ctx.channel_runtime.active_teams == ["literature-review"]
|
||||
|
||||
async def test_invite_adds_to_active_teams(self):
|
||||
ctx, ui = _make_ctx()
|
||||
|
||||
Reference in New Issue
Block a user