落地并存测服全端:智能体 Graph/路由、后台切 8180、主账与部署脚本齐备,便于线上重发与验收。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
"""
|
||||
停等账仓储壳:suspend_wait(waitVersion)。
|
||||
|
||||
本文件职责:登记/关闭停点;与 Graph interrupt、H5 token 对齐的版本号。
|
||||
禁止:用本表冒充主账六态;禁止用户文字伪造 Worker 完成。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import threading
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Optional
|
||||
|
||||
from agent.runtime_db import assert_safe_database, connect_psycopg
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _utcnow() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SuspendRecord:
|
||||
"""一条停等记录。"""
|
||||
|
||||
thread_id: str
|
||||
wait_version: int
|
||||
pause_name: str = ""
|
||||
inquiry_no: str = ""
|
||||
status: str = "open"
|
||||
payload: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
class MemorySuspendStore:
|
||||
"""内存停等壳。"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._rows: dict[tuple[str, int], SuspendRecord] = {}
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def open_wait(self, record: SuspendRecord) -> None:
|
||||
key = (record.thread_id, record.wait_version)
|
||||
with self._lock:
|
||||
self._rows[key] = SuspendRecord(
|
||||
thread_id=record.thread_id,
|
||||
wait_version=record.wait_version,
|
||||
pause_name=record.pause_name,
|
||||
inquiry_no=record.inquiry_no,
|
||||
status="open",
|
||||
payload=dict(record.payload),
|
||||
)
|
||||
logger.info(
|
||||
"suspend.memory.open thread=%s wait=%s pause=%s",
|
||||
record.thread_id,
|
||||
record.wait_version,
|
||||
record.pause_name,
|
||||
)
|
||||
|
||||
def get(self, thread_id: str, wait_version: int) -> Optional[SuspendRecord]:
|
||||
with self._lock:
|
||||
rec = self._rows.get((thread_id, wait_version))
|
||||
if rec is None:
|
||||
return None
|
||||
return SuspendRecord(
|
||||
thread_id=rec.thread_id,
|
||||
wait_version=rec.wait_version,
|
||||
pause_name=rec.pause_name,
|
||||
inquiry_no=rec.inquiry_no,
|
||||
status=rec.status,
|
||||
payload=dict(rec.payload),
|
||||
)
|
||||
|
||||
def close_wait(self, thread_id: str, wait_version: int) -> bool:
|
||||
with self._lock:
|
||||
rec = self._rows.get((thread_id, wait_version))
|
||||
if rec is None:
|
||||
return False
|
||||
rec.status = "closed"
|
||||
return True
|
||||
|
||||
|
||||
class PostgresSuspendStore:
|
||||
"""PG 停等壳(表 suspend_wait)。"""
|
||||
|
||||
def __init__(self, dsn: str) -> None:
|
||||
assert_safe_database(dsn)
|
||||
self._dsn = dsn
|
||||
|
||||
def open_wait(self, record: SuspendRecord) -> None:
|
||||
import json
|
||||
|
||||
with connect_psycopg(self._dsn) as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO suspend_wait (
|
||||
thread_id, wait_version, pause_name, inquiry_no, status, payload
|
||||
) VALUES (%s, %s, %s, %s, 'open', %s::jsonb)
|
||||
ON CONFLICT (thread_id, wait_version) DO UPDATE SET
|
||||
pause_name = EXCLUDED.pause_name,
|
||||
inquiry_no = EXCLUDED.inquiry_no,
|
||||
status = 'open',
|
||||
payload = EXCLUDED.payload,
|
||||
closed_at = NULL
|
||||
""",
|
||||
(
|
||||
record.thread_id,
|
||||
record.wait_version,
|
||||
record.pause_name,
|
||||
record.inquiry_no,
|
||||
json.dumps(record.payload, ensure_ascii=False),
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
logger.info(
|
||||
"suspend.pg.open thread=%s wait=%s",
|
||||
record.thread_id,
|
||||
record.wait_version,
|
||||
)
|
||||
|
||||
def get(self, thread_id: str, wait_version: int) -> Optional[SuspendRecord]:
|
||||
with connect_psycopg(self._dsn) as conn:
|
||||
row = conn.execute(
|
||||
"""
|
||||
SELECT thread_id, wait_version, pause_name, inquiry_no, status, payload
|
||||
FROM suspend_wait
|
||||
WHERE thread_id = %s AND wait_version = %s
|
||||
""",
|
||||
(thread_id, wait_version),
|
||||
).fetchone()
|
||||
if not row:
|
||||
return None
|
||||
return SuspendRecord(
|
||||
thread_id=str(row[0]),
|
||||
wait_version=int(row[1]),
|
||||
pause_name=str(row[2] or ""),
|
||||
inquiry_no=str(row[3] or ""),
|
||||
status=str(row[4] or ""),
|
||||
payload=dict(row[5] or {}),
|
||||
)
|
||||
|
||||
def close_wait(self, thread_id: str, wait_version: int) -> bool:
|
||||
with connect_psycopg(self._dsn) as conn:
|
||||
cur = conn.execute(
|
||||
"""
|
||||
UPDATE suspend_wait
|
||||
SET status = 'closed', closed_at = %s
|
||||
WHERE thread_id = %s AND wait_version = %s AND status = 'open'
|
||||
""",
|
||||
(_utcnow(), thread_id, wait_version),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
|
||||
|
||||
def create_suspend_store(*, dsn: str = "", backend: str = "auto"):
|
||||
"""工厂:Memory 或 PG。"""
|
||||
if backend == "memory" or not dsn:
|
||||
return MemorySuspendStore()
|
||||
return PostgresSuspendStore(dsn)
|
||||
Reference in New Issue
Block a user