fix(acp): replay native todo plans

This commit is contained in:
HenkDz 2026-05-15 16:15:04 +01:00 committed by Teknium
parent 4444d5fe4f
commit bd3a5873e1
4 changed files with 99 additions and 9 deletions

View file

@ -12,6 +12,7 @@ import acp
from acp.schema import AgentPlanUpdate, ToolCallStart, ToolCallProgress, AgentThoughtChunk, AgentMessageChunk
from acp_adapter.events import (
_build_plan_update_from_todo_result,
_send_update,
make_message_cb,
make_step_cb,
@ -296,7 +297,7 @@ class TestStepCallback:
}
mock_send.assert_called_once()
def test_todo_completion_emits_native_plan_update(self, mock_conn, event_loop_fixture):
def test_todo_completion_emits_native_plan_update_after_tool_completion(self, mock_conn, event_loop_fixture):
from collections import deque
tool_call_ids = {"todo": deque(["tc-todo"])}
@ -314,9 +315,11 @@ class TestStepCallback:
cb(1, [{"name": "todo", "result": todo_result}])
updates = [call.args[3] for call in mock_send.call_args_list]
plan_updates = [u for u in updates if getattr(u, "session_update", None) == "plan"]
assert len(plan_updates) == 1
plan = plan_updates[0]
assert [getattr(update, "session_update", None) for update in updates] == [
"tool_call_update",
"plan",
]
plan = updates[1]
assert isinstance(plan, AgentPlanUpdate)
assert [entry.content for entry in plan.entries] == [
"Inspect ACP",
@ -326,6 +329,22 @@ class TestStepCallback:
assert [entry.status for entry in plan.entries] == ["completed", "in_progress", "completed"]
assert [entry.priority for entry in plan.entries] == ["medium", "medium", "medium"]
def test_todo_plan_update_parses_json_with_trailing_hint(self):
result = '{"todos":[{"id":"ship","content":"Ship ACP plan","status":"pending"}]}\n\n[Hint: persisted]'
update = _build_plan_update_from_todo_result(result)
assert isinstance(update, AgentPlanUpdate)
assert [entry.content for entry in update.entries] == ["Ship ACP plan"]
assert [entry.status for entry in update.entries] == ["pending"]
def test_todo_plan_update_with_empty_todos_clears_plan(self):
update = _build_plan_update_from_todo_result('{"todos":[],"summary":{"total":0}}')
assert isinstance(update, AgentPlanUpdate)
assert update.session_update == "plan"
assert update.entries == []
# ---------------------------------------------------------------------------
# Message callback

View file

@ -12,6 +12,7 @@ from acp.agent.router import build_agent_router
from acp.schema import (
AgentCapabilities,
AgentMessageChunk,
AgentPlanUpdate,
AuthenticateResponse,
AvailableCommandsUpdate,
Implementation,
@ -391,6 +392,57 @@ class TestSessionOps:
assert "Search results" in tool_updates[1].content[0].content.text
assert "cli.py:42" in tool_updates[1].content[0].content.text
@pytest.mark.asyncio
async def test_load_session_replays_native_plan_for_persisted_todo_tool(self, agent):
"""Persisted todo tool results should rebuild Zed's native plan panel."""
mock_conn = MagicMock(spec=acp.Client)
mock_conn.session_update = AsyncMock()
agent._conn = mock_conn
new_resp = await agent.new_session(cwd="/tmp")
state = agent.session_manager.get_session(new_resp.session_id)
state.history = [
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_todo_1",
"type": "function",
"function": {
"name": "todo",
"arguments": '{"todos":[{"id":"ship","content":"Ship it","status":"in_progress"}]}',
},
}
],
},
{
"role": "tool",
"tool_call_id": "call_todo_1",
"content": '{"todos":[{"id":"ship","content":"Ship it","status":"in_progress"}]}',
},
]
mock_conn.session_update.reset_mock()
resp = await agent.load_session(cwd="/tmp", session_id=new_resp.session_id)
await asyncio.sleep(0)
await asyncio.sleep(0)
assert isinstance(resp, LoadSessionResponse)
relevant_updates = [
update for update in (call.kwargs["update"] for call in mock_conn.session_update.await_args_list)
if getattr(update, "session_update", None) in {"tool_call", "tool_call_update", "plan"}
]
assert [getattr(update, "session_update", None) for update in relevant_updates] == [
"tool_call",
"tool_call_update",
"plan",
]
plan = relevant_updates[2]
assert isinstance(plan, AgentPlanUpdate)
assert [entry.content for entry in plan.entries] == ["Ship it"]
assert [entry.status for entry in plan.entries] == ["in_progress"]
@pytest.mark.asyncio
async def test_resume_session_replays_persisted_history_to_client(self, agent):
mock_conn = MagicMock(spec=acp.Client)