3cda9894c7
* feat: bias main-agent delegation toward configurable.active_teams * feat: add /experts and /expert TUI commands for expert-skill summoning * feat: align expert-selection wording with WebUI (invite/dismiss) * chore: clear active_teams on /new, cleanup active_team.py comment * fix: use local append_to_system_message in ActiveTeamMiddleware * fix: prevent configurable_extra from overriding thread_id * fix: cache expert-skill lookup for /expert completions * fix: suppress /expert completions past first arg and on exact match * fix: invalidate /expert completion cache on skill install/uninstall * fix: refuse /expert invites for non-dispatchable expert skills * fix: fire /expert cache invalidation on every install_skill / uninstall_skill path * fix: propagate active_teams to Rich CLI and serve dispatch surfaces * fix: keep invited experts across channel shutdown * fix: match /expert completions case-insensitively
88 lines
3.3 KiB
Python
88 lines
3.3 KiB
Python
"""Tests for the /new command."""
|
|
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
|
|
class TestNewCommand:
|
|
async def test_execute_calls_start_new_session(self):
|
|
from EvoScientist.commands.base import CommandContext
|
|
from EvoScientist.commands.implementation.session import NewCommand
|
|
|
|
ui = MagicMock()
|
|
ui.start_new_session = AsyncMock()
|
|
ctx = CommandContext(
|
|
agent=None,
|
|
thread_id="old-tid",
|
|
ui=ui,
|
|
workspace_dir="/old/ws",
|
|
)
|
|
await NewCommand().execute(ctx, [])
|
|
ui.start_new_session.assert_awaited_once()
|
|
|
|
def test_requires_agent_false(self):
|
|
from EvoScientist.commands.implementation.session import NewCommand
|
|
|
|
assert NewCommand().requires_agent is False
|
|
|
|
async def test_no_agent_access(self):
|
|
"""Command body must not touch ctx.agent (it's still loading)."""
|
|
from EvoScientist.commands.base import CommandContext
|
|
from EvoScientist.commands.implementation.session import NewCommand
|
|
|
|
ui = MagicMock()
|
|
ui.start_new_session = AsyncMock()
|
|
ctx = CommandContext(agent=None, thread_id="tid", ui=ui)
|
|
# No AttributeError even though ctx.agent is None
|
|
await NewCommand().execute(ctx, [])
|
|
|
|
async def test_clears_invited_experts_and_announces(self):
|
|
"""/new dismisses invited experts uniformly with channel-shutdown clear."""
|
|
from EvoScientist.commands.base import ChannelRuntime, CommandContext
|
|
from EvoScientist.commands.implementation.session import NewCommand
|
|
|
|
ui = MagicMock()
|
|
ui.start_new_session = AsyncMock()
|
|
runtime = ChannelRuntime()
|
|
runtime.active_teams = ["idea-brainstorm"]
|
|
ctx = CommandContext(
|
|
agent=None,
|
|
thread_id="tid",
|
|
ui=ui,
|
|
channel_runtime=runtime,
|
|
)
|
|
await NewCommand().execute(ctx, [])
|
|
assert runtime.active_teams == []
|
|
messages = [call.args[0] for call in ui.append_system.call_args_list]
|
|
assert any(
|
|
"Dismissed experts on new session: idea-brainstorm" in msg
|
|
for msg in messages
|
|
)
|
|
|
|
async def test_no_announcement_when_no_experts_invited(self):
|
|
"""No noise on ``/new`` when the invite list is already empty."""
|
|
from EvoScientist.commands.base import ChannelRuntime, CommandContext
|
|
from EvoScientist.commands.implementation.session import NewCommand
|
|
|
|
ui = MagicMock()
|
|
ui.start_new_session = AsyncMock()
|
|
runtime = ChannelRuntime()
|
|
ctx = CommandContext(
|
|
agent=None,
|
|
thread_id="tid",
|
|
ui=ui,
|
|
channel_runtime=runtime,
|
|
)
|
|
await NewCommand().execute(ctx, [])
|
|
ui.append_system.assert_not_called()
|
|
|
|
async def test_no_announcement_without_channel_runtime(self):
|
|
"""Runs cleanly when no ChannelRuntime is attached."""
|
|
from EvoScientist.commands.base import CommandContext
|
|
from EvoScientist.commands.implementation.session import NewCommand
|
|
|
|
ui = MagicMock()
|
|
ui.start_new_session = AsyncMock()
|
|
ctx = CommandContext(agent=None, thread_id="tid", ui=ui, channel_runtime=None)
|
|
await NewCommand().execute(ctx, [])
|
|
ui.append_system.assert_not_called()
|