fix(agent): polish interrupt-resume flow for merge readiness

This commit is contained in:
qzl
2026-03-03 17:26:04 +08:00
parent 7be8669144
commit 30a4a1af5d
16 changed files with 1179 additions and 85 deletions
@@ -17,6 +17,7 @@ class FakeAsyncSession:
def __init__(self) -> None:
self.added: list[object] = []
self._sessions: dict[UUID, AgentChatSession] = {}
self.last_fetch_with_lock = False
def add(self, obj: object) -> None:
self.added.append(obj)
@@ -35,8 +36,17 @@ class FakeAsyncSession:
async def refresh(self, obj: object) -> None:
pass
async def execute(self, stmt: object) -> None:
pass
async def execute(self, stmt: object):
self.last_fetch_with_lock = "FOR UPDATE" in str(stmt)
class _Result:
def __init__(self, session_obj: AgentChatSession | None) -> None:
self._session_obj = session_obj
def scalar_one_or_none(self) -> AgentChatSession | None:
return self._session_obj
return _Result(next(iter(self._sessions.values()), None))
async def scalar(self, stmt: object) -> AgentChatSession | None:
for session in self._sessions.values():
@@ -69,7 +79,10 @@ def service(fake_db: FakeAsyncSession) -> AgentChatService:
class TestResumeIdempotency:
@pytest.mark.asyncio
async def test_resume_is_idempotent(
self, service: AgentChatService, session: AgentChatSession
self,
service: AgentChatService,
session: AgentChatSession,
fake_db: FakeAsyncSession,
):
expires_at = datetime.now(timezone.utc) + timedelta(hours=1)
await service.set_pending_tool_call(
@@ -78,6 +91,8 @@ class TestResumeIdempotency:
tool_name="srv.transfer_funds",
tool_args={"to": "u2", "amount": 100},
expires_at=expires_at,
thread_id="t1",
run_id="r1",
)
first = await service.apply_resume_decision(
@@ -93,6 +108,7 @@ class TestResumeIdempotency:
assert first.applied is True
assert second.applied is False
assert fake_db.last_fetch_with_lock is True
@pytest.mark.asyncio
async def test_resume_updates_status_to_approved(
@@ -105,6 +121,8 @@ class TestResumeIdempotency:
tool_name="srv.delete_file",
tool_args={"file_id": "f1"},
expires_at=expires_at,
thread_id="t1",
run_id="r1",
)
result = await service.apply_resume_decision(
@@ -129,6 +147,8 @@ class TestResumeIdempotency:
tool_name="srv.transfer_funds",
tool_args={"to": "u2", "amount": 100},
expires_at=expires_at,
thread_id="t1",
run_id="r1",
)
result = await service.apply_resume_decision(
@@ -140,3 +160,28 @@ class TestResumeIdempotency:
assert result.applied is True
snapshot = await service.get_state_snapshot(session.id)
assert snapshot["pending_tool_call"]["status"] == "REJECTED"
@pytest.mark.asyncio
async def test_resume_expired_pending_marks_expired_and_not_applied(
self, service: AgentChatService, session: AgentChatSession
):
expires_at = datetime.now(timezone.utc) - timedelta(seconds=1)
await service.set_pending_tool_call(
session_id=session.id,
interrupt_id="int-expired",
tool_name="srv.transfer_funds",
tool_args={"to": "u2", "amount": 100},
expires_at=expires_at,
thread_id="t1",
run_id="r1",
)
result = await service.apply_resume_decision(
session_id=session.id,
interrupt_id="int-expired",
decision={"decision": "approved"},
)
assert result.applied is False
snapshot = await service.get_state_snapshot(session.id)
assert snapshot["pending_tool_call"]["status"] == "EXPIRED"