feat: add agent visibility APIs and harden runtime verification

Add Day 4 visibility endpoints and response models, strengthen collaboration/task verification behavior, and patch conversation schema startup migration for agent_state compatibility. Extend backend regression coverage for runtime schemas, verifier behavior, visibility APIs, router auth, and legacy conversation list loading.
This commit is contained in:
2026-04-04 00:56:03 +08:00
parent aa0ef0fbea
commit a7b6b5eb90
24 changed files with 2986 additions and 111 deletions

View File

@@ -1,10 +1,10 @@
from __future__ import annotations
from typing import Any
from typing import Any, cast
from pydantic import BaseModel, Field
from app.agents.schemas.task import AgentTask, TaskResult, VerificationStatus
from app.agents.schemas.task import AgentTask, TaskResult, TaskResultStatus, VerificationStatus
from app.agents.state import AgentState
@@ -14,6 +14,34 @@ class VerificationVerdict(BaseModel):
evidence: list[dict[str, Any]] = Field(default_factory=list)
def normalize_task_result(
task_result: TaskResult | dict[str, Any],
*,
default_task_id: str | None = None,
) -> TaskResult:
payload = task_result.model_dump(mode="json") if isinstance(task_result, TaskResult) else dict(task_result or {})
normalized_status = payload.get("status")
if normalized_status not in {"completed", "failed", "blocked", "passed", "skipped"}:
normalized_status = "failed"
return TaskResult(
task_id=str(payload.get("task_id") or default_task_id or "unknown-task"),
status=cast(TaskResultStatus, normalized_status),
summary=payload.get("summary"),
evidence=list(payload.get("evidence") or []),
owner_agent_id=payload.get("owner_agent_id"),
parent_task_id=payload.get("parent_task_id"),
child_task_ids=list(payload.get("child_task_ids") or []),
thread_id=payload.get("thread_id"),
message_id=payload.get("message_id"),
message_index=payload.get("message_index") if isinstance(payload.get("message_index"), int) else None,
interrupt_records=list(payload.get("interrupt_records") or []),
recovery_records=list(payload.get("recovery_records") or []),
budget_snapshot=payload.get("budget_snapshot") if isinstance(payload.get("budget_snapshot"), dict) else None,
next_action=payload.get("next_action"),
output_data=payload.get("output_data") if isinstance(payload.get("output_data"), dict) else None,
)
def verify_task_result(
*,
task: AgentTask | dict[str, Any] | None = None,
@@ -30,8 +58,13 @@ def verify_task_result(
if status is not None:
return VerificationVerdict(status=status, summary=normalized_summary, evidence=normalized_evidence)
if normalized_result.get("status") in {"passed", "failed", "skipped"}:
inferred_status = normalized_result["status"]
normalized_status = normalized_result.get("status")
if normalized_status in {"passed", "failed", "skipped"}:
inferred_status = normalized_status
elif normalized_status == "completed":
inferred_status = "passed"
elif normalized_status == "blocked":
inferred_status = "skipped"
elif normalized_result.get("success") is True:
inferred_status = "passed"
elif normalized_result.get("success") is False:
@@ -57,4 +90,4 @@ def apply_verification_verdict(state: AgentState, verdict: VerificationVerdict)
return AgentState(**next_state)
__all__ = ["VerificationVerdict", "apply_verification_verdict", "verify_task_result"]
__all__ = ["VerificationVerdict", "apply_verification_verdict", "normalize_task_result", "verify_task_result"]