fix(agent): enforce idempotent resume transition

This commit is contained in:
qzl
2026-03-03 15:43:10 +08:00
parent cff1436bc6
commit dedd23fdf9
2 changed files with 183 additions and 1 deletions
+41 -1
View File
@@ -2,10 +2,11 @@ from __future__ import annotations
from datetime import datetime, timezone
from decimal import Decimal
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
from uuid import UUID
from fastapi import HTTPException
from pydantic import BaseModel
from sqlalchemy import func, select
from sqlalchemy.exc import SQLAlchemyError
@@ -29,6 +30,10 @@ if TYPE_CHECKING:
logger = get_logger("v1.agent.service")
class ResumeDecisionResult(BaseModel):
applied: bool
def build_session_title(first_message: str, *, now: datetime) -> str:
title = first_message.strip().replace("\n", " ")[:24]
if not title:
@@ -335,3 +340,38 @@ class AgentChatService(BaseService):
raise ValueError("Interrupt ID mismatch")
snapshot["pending_tool_call"]["status"] = status
session.state_snapshot = snapshot
async def apply_resume_decision(
self,
*,
session_id: UUID,
interrupt_id: str,
decision: dict[str, Any],
) -> ResumeDecisionResult:
stmt = select(AgentChatSession).where(AgentChatSession.id == session_id)
session = await self._session.scalar(stmt)
if session is None:
raise ValueError(f"Session {session_id} not found")
snapshot = session.state_snapshot
if snapshot is None or "pending_tool_call" not in snapshot:
return ResumeDecisionResult(applied=False)
pending = snapshot["pending_tool_call"]
if pending["interrupt_id"] != interrupt_id:
return ResumeDecisionResult(applied=False)
if pending["status"] != "PENDING_APPROVAL":
return ResumeDecisionResult(applied=False)
decision_value = decision.get("decision", "approved")
if decision_value == "approved":
new_status = "APPROVED_EXECUTING"
else:
new_status = "REJECTED"
snapshot["pending_tool_call"]["status"] = new_status
snapshot["pending_tool_call"]["decision"] = decision
session.state_snapshot = snapshot
return ResumeDecisionResult(applied=True)