Co-authored-by: Carlo <carloantonio.patti@mistral.ai>
Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai>
Co-authored-by: Clement Sirieix <clem.sirieix@gmail.com>
Co-authored-by: Michel Thomazo <michel.thomazo@mistral.ai>
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Vincent Guilloux <vincent.guilloux@mistral.ai>
Co-authored-by: Thomas Kenbeek <thomas.kenbeek@mistral.ai>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Quentin 2026-02-27 17:31:58 +01:00 committed by GitHub
parent a560a47ce8
commit 5d2e01a6d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
139 changed files with 7152 additions and 1457 deletions

View file

@ -3,44 +3,67 @@ from __future__ import annotations
import pytest
from tests.conftest import build_test_agent_loop, build_test_vibe_config
from vibe.core.agents.models import BUILTIN_AGENTS, AgentProfile, BuiltinAgentName
from vibe.core.agents.models import BUILTIN_AGENTS, CHAT, AgentProfile, BuiltinAgentName
from vibe.core.config import VibeConfig
from vibe.core.middleware import (
CHAT_AGENT_EXIT,
CHAT_AGENT_REMINDER,
PLAN_AGENT_EXIT,
PLAN_AGENT_REMINDER,
ConversationContext,
MiddlewareAction,
MiddlewarePipeline,
PlanAgentMiddleware,
ReadOnlyAgentMiddleware,
ResetReason,
)
from vibe.core.types import AgentStats
from vibe.core.types import AgentStats, MessageList
REMINDER = "test reminder"
EXIT_MSG = "test exit"
TARGET_AGENT = BuiltinAgentName.PLAN
def _build_middleware(
profile_getter,
agent_name: str = TARGET_AGENT,
reminder: str = REMINDER,
exit_message: str = EXIT_MSG,
) -> ReadOnlyAgentMiddleware:
return ReadOnlyAgentMiddleware(profile_getter, agent_name, reminder, exit_message)
@pytest.fixture
def ctx(vibe_config: VibeConfig) -> ConversationContext:
return ConversationContext(messages=[], stats=AgentStats(), config=vibe_config)
return ConversationContext(
messages=MessageList(), stats=AgentStats(), config=vibe_config
)
class TestPlanAgentMiddleware:
class TestReadOnlyAgentMiddleware:
@pytest.mark.asyncio
async def test_injects_reminder_when_plan_agent_active(
async def test_injects_reminder_when_target_agent_active(
self, ctx: ConversationContext
) -> None:
middleware = PlanAgentMiddleware(lambda: BUILTIN_AGENTS[BuiltinAgentName.PLAN])
middleware = _build_middleware(lambda: BUILTIN_AGENTS[BuiltinAgentName.PLAN])
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == PLAN_AGENT_REMINDER
assert result.message == REMINDER
@pytest.mark.asyncio
async def test_does_not_inject_when_default_agent(
self, ctx: ConversationContext
@pytest.mark.parametrize(
"agent_name",
[
BuiltinAgentName.DEFAULT,
BuiltinAgentName.AUTO_APPROVE,
BuiltinAgentName.ACCEPT_EDITS,
],
)
async def test_does_not_inject_when_non_target_agent(
self, ctx: ConversationContext, agent_name: str
) -> None:
middleware = PlanAgentMiddleware(
lambda: BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
)
middleware = _build_middleware(lambda: BUILTIN_AGENTS[agent_name])
result = await middleware.before_turn(ctx)
@ -48,89 +71,56 @@ class TestPlanAgentMiddleware:
assert result.message is None
@pytest.mark.asyncio
async def test_does_not_inject_when_auto_approve_agent(
self, ctx: ConversationContext
) -> None:
middleware = PlanAgentMiddleware(
lambda: BUILTIN_AGENTS[BuiltinAgentName.AUTO_APPROVE]
)
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
assert result.message is None
@pytest.mark.asyncio
async def test_does_not_inject_when_accept_edits_agent(
self, ctx: ConversationContext
) -> None:
middleware = PlanAgentMiddleware(
lambda: BUILTIN_AGENTS[BuiltinAgentName.ACCEPT_EDITS]
)
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
assert result.message is None
@pytest.mark.asyncio
async def test_injects_reminder_only_once_while_in_plan_mode(
self, ctx: ConversationContext
) -> None:
middleware = PlanAgentMiddleware(lambda: BUILTIN_AGENTS[BuiltinAgentName.PLAN])
async def test_injects_reminder_only_once(self, ctx: ConversationContext) -> None:
middleware = _build_middleware(lambda: BUILTIN_AGENTS[BuiltinAgentName.PLAN])
result1 = await middleware.before_turn(ctx)
assert result1.action == MiddlewareAction.INJECT_MESSAGE
assert result1.message == PLAN_AGENT_REMINDER
assert result1.message == REMINDER
result2 = await middleware.before_turn(ctx)
assert result2.action == MiddlewareAction.CONTINUE
assert result2.message is None
@pytest.mark.asyncio
async def test_injects_exit_message_when_leaving_plan_mode(
async def test_injects_exit_message_when_leaving(
self, ctx: ConversationContext
) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = PlanAgentMiddleware(lambda: current_profile)
middleware = _build_middleware(lambda: current_profile)
# Enter plan mode
await middleware.before_turn(ctx)
# Leave plan mode
current_profile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == PLAN_AGENT_EXIT
assert result.message == EXIT_MSG
@pytest.mark.asyncio
async def test_reinjects_reminder_when_reentering_plan_mode(
async def test_reinjects_reminder_on_reentry(
self, ctx: ConversationContext
) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = PlanAgentMiddleware(lambda: current_profile)
middleware = _build_middleware(lambda: current_profile)
# Enter plan mode - should inject reminder
result1 = await middleware.before_turn(ctx)
assert result1.action == MiddlewareAction.INJECT_MESSAGE
assert result1.message == PLAN_AGENT_REMINDER
assert result1.message == REMINDER
# Leave plan mode - should inject exit message
current_profile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
result2 = await middleware.before_turn(ctx)
assert result2.action == MiddlewareAction.INJECT_MESSAGE
assert result2.message == PLAN_AGENT_EXIT
assert result2.message == EXIT_MSG
# Re-enter plan mode - should inject reminder again
current_profile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
result3 = await middleware.before_turn(ctx)
assert result3.action == MiddlewareAction.INJECT_MESSAGE
assert result3.message == PLAN_AGENT_REMINDER
assert result3.message == REMINDER
@pytest.mark.asyncio
async def test_custom_reminder(self, ctx: ConversationContext) -> None:
custom_reminder = "Custom plan agent reminder"
middleware = PlanAgentMiddleware(
custom_reminder = "Custom reminder"
middleware = _build_middleware(
lambda: BUILTIN_AGENTS[BuiltinAgentName.PLAN], reminder=custom_reminder
)
@ -138,246 +128,216 @@ class TestPlanAgentMiddleware:
assert result.message == custom_reminder
@pytest.mark.asyncio
async def test_reset_clears_state(self, ctx: ConversationContext) -> None:
middleware = PlanAgentMiddleware(lambda: BUILTIN_AGENTS[BuiltinAgentName.PLAN])
await middleware.before_turn(ctx) # Enter and inject
middleware.reset()
# Should inject again after reset
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
@pytest.mark.asyncio
async def test_exit_message_fires_only_once(self, ctx: ConversationContext) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = PlanAgentMiddleware(lambda: current_profile)
# Enter plan mode
await middleware.before_turn(ctx)
# Leave plan mode - first call should inject exit
current_profile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == PLAN_AGENT_EXIT
# Subsequent calls in default mode should be CONTINUE
result2 = await middleware.before_turn(ctx)
assert result2.action == MiddlewareAction.CONTINUE
assert result2.message is None
@pytest.mark.asyncio
async def test_multiple_turns_in_plan_mode_after_entry(
self, ctx: ConversationContext
) -> None:
middleware = PlanAgentMiddleware(lambda: BUILTIN_AGENTS[BuiltinAgentName.PLAN])
# First turn: inject reminder
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
# Several more turns in plan mode: all should be CONTINUE
for _ in range(5):
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
assert result.message is None
@pytest.mark.asyncio
async def test_multiple_turns_in_default_mode_after_exit(
self, ctx: ConversationContext
) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = PlanAgentMiddleware(lambda: current_profile)
await middleware.before_turn(ctx) # enter plan
current_profile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
await middleware.before_turn(ctx) # exit plan (fires exit message)
# Several more turns in default mode: all should be CONTINUE
for _ in range(5):
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
assert result.message is None
@pytest.mark.asyncio
async def test_rapid_toggling_plan_default_multiple_cycles(
self, ctx: ConversationContext
) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = PlanAgentMiddleware(lambda: current_profile)
for _ in range(3):
# Enter plan
current_profile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == PLAN_AGENT_REMINDER
# Leave plan
current_profile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == PLAN_AGENT_EXIT
@pytest.mark.asyncio
async def test_exit_to_non_default_agent(self, ctx: ConversationContext) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = PlanAgentMiddleware(lambda: current_profile)
await middleware.before_turn(ctx) # enter plan
# Switch to auto_approve (not default)
current_profile = BUILTIN_AGENTS[BuiltinAgentName.AUTO_APPROVE]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == PLAN_AGENT_EXIT
@pytest.mark.asyncio
async def test_exit_to_accept_edits_agent(self, ctx: ConversationContext) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = PlanAgentMiddleware(lambda: current_profile)
await middleware.before_turn(ctx) # enter plan
# Switch to accept_edits
current_profile = BUILTIN_AGENTS[BuiltinAgentName.ACCEPT_EDITS]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == PLAN_AGENT_EXIT
@pytest.mark.asyncio
async def test_switching_between_non_plan_agents(
self, ctx: ConversationContext
) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
middleware = PlanAgentMiddleware(lambda: current_profile)
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
current_profile = BUILTIN_AGENTS[BuiltinAgentName.AUTO_APPROVE]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
current_profile = BUILTIN_AGENTS[BuiltinAgentName.ACCEPT_EDITS]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
@pytest.mark.asyncio
async def test_non_plan_to_plan_entry(self, ctx: ConversationContext) -> None:
"""Starting in a non-plan agent then entering plan should inject reminder."""
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.AUTO_APPROVE]
middleware = PlanAgentMiddleware(lambda: current_profile)
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
# Now switch to plan
current_profile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == PLAN_AGENT_REMINDER
@pytest.mark.asyncio
async def test_reset_while_in_default_after_exiting_plan(
self, ctx: ConversationContext
) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = PlanAgentMiddleware(lambda: current_profile)
await middleware.before_turn(ctx) # enter plan
current_profile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
await middleware.before_turn(ctx) # exit plan
middleware.reset()
# Still in default mode - should CONTINUE (no phantom exit message)
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
@pytest.mark.asyncio
async def test_reset_while_in_default_then_reenter_plan(
self, ctx: ConversationContext
) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = PlanAgentMiddleware(lambda: current_profile)
await middleware.before_turn(ctx) # enter plan
current_profile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
await middleware.before_turn(ctx) # exit plan
middleware.reset()
# Re-enter plan after reset
current_profile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == PLAN_AGENT_REMINDER
@pytest.mark.asyncio
async def test_reset_with_compact_reason(self, ctx: ConversationContext) -> None:
middleware = PlanAgentMiddleware(lambda: BUILTIN_AGENTS[BuiltinAgentName.PLAN])
await middleware.before_turn(ctx) # enter and inject
middleware.reset(ResetReason.COMPACT)
# Should reinject reminder after compact reset
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == PLAN_AGENT_REMINDER
@pytest.mark.asyncio
async def test_custom_exit_message(self, ctx: ConversationContext) -> None:
custom_exit = "Custom exit message"
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = PlanAgentMiddleware(
middleware = _build_middleware(
lambda: current_profile, exit_message=custom_exit
)
await middleware.before_turn(ctx) # enter plan
await middleware.before_turn(ctx)
current_profile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
result = await middleware.before_turn(ctx)
assert result.message == custom_exit
@pytest.mark.asyncio
async def test_plan_entry_then_immediate_exit_same_not_possible(
self, ctx: ConversationContext
) -> None:
"""Even if profile changes between two calls, each call sees one transition."""
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = PlanAgentMiddleware(lambda: current_profile)
async def test_reset_clears_state(self, ctx: ConversationContext) -> None:
middleware = _build_middleware(lambda: BUILTIN_AGENTS[BuiltinAgentName.PLAN])
await middleware.before_turn(ctx)
middleware.reset()
# First call: entry
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == PLAN_AGENT_REMINDER
# Second call (still plan): no injection
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
@pytest.mark.asyncio
async def test_exit_message_fires_only_once(self, ctx: ConversationContext) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = _build_middleware(lambda: current_profile)
await middleware.before_turn(ctx)
# Third call (switched to default): exit
current_profile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == PLAN_AGENT_EXIT
assert result.message == EXIT_MSG
result2 = await middleware.before_turn(ctx)
assert result2.action == MiddlewareAction.CONTINUE
assert result2.message is None
@pytest.mark.asyncio
async def test_multiple_turns_after_entry(self, ctx: ConversationContext) -> None:
middleware = _build_middleware(lambda: BUILTIN_AGENTS[BuiltinAgentName.PLAN])
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
for _ in range(5):
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
assert result.message is None
@pytest.mark.asyncio
async def test_multiple_turns_after_exit(self, ctx: ConversationContext) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = _build_middleware(lambda: current_profile)
await middleware.before_turn(ctx)
current_profile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
await middleware.before_turn(ctx)
for _ in range(5):
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
assert result.message is None
@pytest.mark.asyncio
async def test_rapid_toggling_multiple_cycles(
self, ctx: ConversationContext
) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = _build_middleware(lambda: current_profile)
for _ in range(3):
current_profile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == REMINDER
current_profile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == EXIT_MSG
@pytest.mark.asyncio
async def test_exit_to_non_default_agent(self, ctx: ConversationContext) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = _build_middleware(lambda: current_profile)
await middleware.before_turn(ctx)
current_profile = BUILTIN_AGENTS[BuiltinAgentName.AUTO_APPROVE]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == EXIT_MSG
@pytest.mark.asyncio
async def test_switching_between_non_target_agents(
self, ctx: ConversationContext
) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
middleware = _build_middleware(lambda: current_profile)
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
current_profile = BUILTIN_AGENTS[BuiltinAgentName.AUTO_APPROVE]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
current_profile = BUILTIN_AGENTS[BuiltinAgentName.ACCEPT_EDITS]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
@pytest.mark.asyncio
async def test_non_target_to_target_entry(self, ctx: ConversationContext) -> None:
"""Starting in a non-target agent then entering target should inject reminder."""
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.AUTO_APPROVE]
middleware = _build_middleware(lambda: current_profile)
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
current_profile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == REMINDER
@pytest.mark.asyncio
async def test_reset_while_inactive_after_exit(
self, ctx: ConversationContext
) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = _build_middleware(lambda: current_profile)
await middleware.before_turn(ctx)
current_profile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
await middleware.before_turn(ctx)
middleware.reset()
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
@pytest.mark.asyncio
async def test_reset_while_inactive_then_reenter(
self, ctx: ConversationContext
) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = _build_middleware(lambda: current_profile)
await middleware.before_turn(ctx)
current_profile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
await middleware.before_turn(ctx)
middleware.reset()
current_profile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == REMINDER
@pytest.mark.asyncio
async def test_reset_with_compact_reason(self, ctx: ConversationContext) -> None:
middleware = _build_middleware(lambda: BUILTIN_AGENTS[BuiltinAgentName.PLAN])
await middleware.before_turn(ctx)
middleware.reset(ResetReason.COMPACT)
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == REMINDER
@pytest.mark.asyncio
async def test_entry_then_continuation_then_exit_then_continuation(
self, ctx: ConversationContext
) -> None:
"""Each call sees one transition at a time."""
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
middleware = _build_middleware(lambda: current_profile)
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == REMINDER
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
current_profile = BUILTIN_AGENTS[BuiltinAgentName.DEFAULT]
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert result.message == EXIT_MSG
# Fourth call (still default): no injection
result = await middleware.before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
class TestMiddlewarePipelineWithPlanAgent:
class TestMiddlewarePipelineWithReadOnlyAgent:
@pytest.mark.asyncio
async def test_pipeline_includes_plan_agent_injection(
self, ctx: ConversationContext
) -> None:
async def test_pipeline_includes_injection(self, ctx: ConversationContext) -> None:
pipeline = MiddlewarePipeline()
pipeline.add(PlanAgentMiddleware(lambda: BUILTIN_AGENTS[BuiltinAgentName.PLAN]))
pipeline.add(
ReadOnlyAgentMiddleware(
lambda: BUILTIN_AGENTS[BuiltinAgentName.PLAN],
BuiltinAgentName.PLAN,
PLAN_AGENT_REMINDER,
PLAN_AGENT_EXIT,
)
)
result = await pipeline.run_before_turn(ctx)
@ -385,20 +345,73 @@ class TestMiddlewarePipelineWithPlanAgent:
assert PLAN_AGENT_REMINDER in (result.message or "")
@pytest.mark.asyncio
async def test_pipeline_skips_injection_when_not_plan_agent(
async def test_pipeline_skips_injection_when_not_target_agent(
self, ctx: ConversationContext
) -> None:
pipeline = MiddlewarePipeline()
pipeline.add(
PlanAgentMiddleware(lambda: BUILTIN_AGENTS[BuiltinAgentName.DEFAULT])
ReadOnlyAgentMiddleware(
lambda: BUILTIN_AGENTS[BuiltinAgentName.DEFAULT],
BuiltinAgentName.PLAN,
PLAN_AGENT_REMINDER,
PLAN_AGENT_EXIT,
)
)
result = await pipeline.run_before_turn(ctx)
assert result.action == MiddlewareAction.CONTINUE
@pytest.mark.asyncio
async def test_direct_plan_to_chat_transition_delivers_both_messages(
self, ctx: ConversationContext
) -> None:
current_profile: AgentProfile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
pipeline = MiddlewarePipeline()
pipeline.add(
ReadOnlyAgentMiddleware(
lambda: current_profile,
BuiltinAgentName.PLAN,
PLAN_AGENT_REMINDER,
PLAN_AGENT_EXIT,
)
)
pipeline.add(
ReadOnlyAgentMiddleware(
lambda: current_profile,
BuiltinAgentName.CHAT,
CHAT_AGENT_REMINDER,
CHAT_AGENT_EXIT,
)
)
class TestPlanAgentMiddlewareIntegration:
result = await pipeline.run_before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert PLAN_AGENT_REMINDER in (result.message or "")
current_profile = CHAT
result = await pipeline.run_before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert PLAN_AGENT_EXIT in (result.message or "")
assert CHAT_AGENT_REMINDER in (result.message or "")
current_profile = BUILTIN_AGENTS[BuiltinAgentName.PLAN]
result = await pipeline.run_before_turn(ctx)
assert result.action == MiddlewareAction.INJECT_MESSAGE
assert CHAT_AGENT_EXIT in (result.message or "")
assert PLAN_AGENT_REMINDER in (result.message or "")
def _find_plan_middleware(agent) -> ReadOnlyAgentMiddleware:
return next(
mw
for mw in agent.middleware_pipeline.middlewares
if isinstance(mw, ReadOnlyAgentMiddleware)
and mw._agent_name == BuiltinAgentName.PLAN
)
class TestReadOnlyAgentMiddlewareIntegration:
@pytest.mark.asyncio
async def test_switch_agent_preserves_middleware_state_for_exit_message(
self,
@ -414,11 +427,7 @@ class TestPlanAgentMiddlewareIntegration:
)
agent = build_test_agent_loop(config=config, agent_name=BuiltinAgentName.PLAN)
plan_middleware = next(
mw
for mw in agent.middleware_pipeline.middlewares
if isinstance(mw, PlanAgentMiddleware)
)
plan_middleware = _find_plan_middleware(agent)
ctx = ConversationContext(
messages=agent.messages, stats=agent.stats, config=agent.config
@ -429,11 +438,7 @@ class TestPlanAgentMiddlewareIntegration:
await agent.switch_agent(BuiltinAgentName.DEFAULT)
plan_middleware_after = next(
mw
for mw in agent.middleware_pipeline.middlewares
if isinstance(mw, PlanAgentMiddleware)
)
plan_middleware_after = _find_plan_middleware(agent)
assert plan_middleware is plan_middleware_after
ctx = ConversationContext(
@ -456,11 +461,7 @@ class TestPlanAgentMiddlewareIntegration:
)
agent = build_test_agent_loop(config=config, agent_name=BuiltinAgentName.PLAN)
plan_middleware = next(
mw
for mw in agent.middleware_pipeline.middlewares
if isinstance(mw, PlanAgentMiddleware)
)
plan_middleware = _find_plan_middleware(agent)
ctx = ConversationContext(
messages=agent.messages, stats=agent.stats, config=agent.config
@ -497,11 +498,7 @@ class TestPlanAgentMiddlewareIntegration:
)
agent = build_test_agent_loop(config=config, agent_name=BuiltinAgentName.PLAN)
plan_middleware = next(
mw
for mw in agent.middleware_pipeline.middlewares
if isinstance(mw, PlanAgentMiddleware)
)
plan_middleware = _find_plan_middleware(agent)
ctx = ConversationContext(
messages=agent.messages, stats=agent.stats, config=agent.config
@ -532,11 +529,7 @@ class TestPlanAgentMiddlewareIntegration:
config=config, agent_name=BuiltinAgentName.DEFAULT
)
plan_middleware = next(
mw
for mw in agent.middleware_pipeline.middlewares
if isinstance(mw, PlanAgentMiddleware)
)
plan_middleware = _find_plan_middleware(agent)
ctx = ConversationContext(
messages=agent.messages, stats=agent.stats, config=agent.config
@ -566,11 +559,7 @@ class TestPlanAgentMiddlewareIntegration:
)
agent = build_test_agent_loop(config=config, agent_name=BuiltinAgentName.PLAN)
plan_middleware = next(
mw
for mw in agent.middleware_pipeline.middlewares
if isinstance(mw, PlanAgentMiddleware)
)
plan_middleware = _find_plan_middleware(agent)
def _ctx():
return ConversationContext(