mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 03:30:48 +08:00
同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。 Made-with: Cursor
157 lines
5.8 KiB
Python
157 lines
5.8 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import json
|
|
import sqlite3
|
|
import time
|
|
import uuid
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import websockets
|
|
|
|
|
|
def _now_ms() -> int:
|
|
return int(time.time() * 1000)
|
|
|
|
|
|
async def _recv_json(ws: websockets.ClientConnection) -> dict[str, Any]:
|
|
raw = await ws.recv()
|
|
if not isinstance(raw, str):
|
|
raw = raw.decode("utf-8", errors="replace")
|
|
obj = json.loads(raw)
|
|
if not isinstance(obj, dict):
|
|
raise RuntimeError(f"unexpected frame: {type(obj).__name__}")
|
|
return obj
|
|
|
|
|
|
async def _send_req(ws: websockets.ClientConnection, req_id: str, method: str, params: dict[str, Any]) -> None:
|
|
await ws.send(json.dumps({"type": "req", "id": req_id, "method": method, "params": params}, ensure_ascii=False))
|
|
|
|
|
|
async def _recv_res(ws: websockets.ClientConnection, req_id: str, *, timeout_s: float = 30.0) -> dict[str, Any]:
|
|
"""Receive frames until we get the response for req_id (ignore events)."""
|
|
deadline = time.time() + float(timeout_s)
|
|
while True:
|
|
if time.time() > deadline:
|
|
raise TimeoutError(f"timeout waiting for res id={req_id}")
|
|
frame = await _recv_json(ws)
|
|
if frame.get("type") == "res" and str(frame.get("id") or "") == str(req_id):
|
|
return frame
|
|
# Ignore events (chat/agent/tick/etc) during sync requests.
|
|
|
|
|
|
async def main() -> None:
|
|
ws_url = "ws://127.0.0.1:8787/ws"
|
|
session_key = f"e2e:{uuid.uuid4().hex[:8]}"
|
|
run_id = f"e2e_{_now_ms()}_{uuid.uuid4().hex[:8]}"
|
|
|
|
async with websockets.connect(ws_url, max_size=26_214_400) as ws:
|
|
# server sends connect.challenge first
|
|
frame = await _recv_json(ws)
|
|
if frame.get("type") != "event" or frame.get("event") != "connect.challenge":
|
|
raise RuntimeError(f"expected connect.challenge, got: {frame}")
|
|
|
|
# handshake
|
|
await _send_req(
|
|
ws,
|
|
"c1",
|
|
"connect",
|
|
{
|
|
"minProtocol": 3,
|
|
"maxProtocol": 3,
|
|
"client": {
|
|
"id": "ws-e2e-smoke",
|
|
"version": "0",
|
|
"platform": "win32",
|
|
"mode": "webchat",
|
|
"instanceId": uuid.uuid4().hex[:12],
|
|
},
|
|
"role": "operator",
|
|
"scopes": [],
|
|
},
|
|
)
|
|
res = await _recv_res(ws, "c1", timeout_s=15.0)
|
|
if res.get("type") != "res" or res.get("id") != "c1" or not bool(res.get("ok")):
|
|
raise RuntimeError(f"connect failed: {res}")
|
|
|
|
# create session
|
|
await _send_req(ws, "s1", "sessions.create", {"key": session_key})
|
|
res = await _recv_res(ws, "s1", timeout_s=15.0)
|
|
if res.get("type") != "res" or res.get("id") != "s1" or not bool(res.get("ok")):
|
|
raise RuntimeError(f"sessions.create failed: {res}")
|
|
|
|
# run agent in comprehensive mode (gateway will invoke manager internally)
|
|
await _send_req(
|
|
ws,
|
|
"a1",
|
|
"agent",
|
|
{
|
|
"message": "现在几点了?请用一句话回答。",
|
|
"sessionId": session_key,
|
|
"sessionKey": session_key,
|
|
"idempotencyKey": run_id,
|
|
"interaction_mode": "comprehensive",
|
|
"specialist": "generalist",
|
|
},
|
|
)
|
|
res = await _recv_res(ws, "a1", timeout_s=15.0)
|
|
if res.get("type") != "res" or res.get("id") != "a1" or not bool(res.get("ok")):
|
|
raise RuntimeError(f"agent enqueue failed: {res}")
|
|
|
|
# poll completion
|
|
final_status = "pending"
|
|
for _ in range(60):
|
|
await _send_req(ws, "w1", "agent.wait", {"runId": run_id})
|
|
w = await _recv_res(ws, "w1", timeout_s=15.0)
|
|
if w.get("type") != "res" or w.get("id") != "w1" or not bool(w.get("ok")):
|
|
raise RuntimeError(f"agent.wait failed: {w}")
|
|
payload = w.get("payload") if isinstance(w.get("payload"), dict) else {}
|
|
status = str(payload.get("status") or "").strip().lower()
|
|
final_status = status or final_status
|
|
if status in {"ok", "error"}:
|
|
break
|
|
await asyncio.sleep(0.25)
|
|
|
|
# fetch history
|
|
await _send_req(ws, "h1", "chat.history", {"sessionKey": session_key, "limit": 20})
|
|
h = await _recv_res(ws, "h1", timeout_s=15.0)
|
|
msgs = []
|
|
if isinstance(h.get("payload"), dict):
|
|
msgs = list(h["payload"].get("messages") or [])
|
|
last_assistant = ""
|
|
for m in reversed(msgs):
|
|
if isinstance(m, dict) and str(m.get("role") or "").lower() == "assistant":
|
|
c = m.get("content")
|
|
if isinstance(c, str):
|
|
last_assistant = c
|
|
elif isinstance(c, list):
|
|
# Best-effort: join text blocks.
|
|
parts: list[str] = []
|
|
for it in c:
|
|
if isinstance(it, dict) and it.get("type") == "text":
|
|
parts.append(str(it.get("text") or ""))
|
|
last_assistant = "".join(parts)
|
|
else:
|
|
last_assistant = str(c or "")
|
|
break
|
|
print(f"session_key={session_key}")
|
|
print(f"run_id={run_id}")
|
|
print(f"status={final_status}")
|
|
print(f"assistant_reply={last_assistant!r}")
|
|
|
|
# DB check: did we record manager_decision?
|
|
db = Path(r"D:\project\chatgpt\oclaw\data\ai_ops.sqlite")
|
|
con = sqlite3.connect(str(db))
|
|
cur = con.cursor()
|
|
try:
|
|
count = int(cur.execute("select count(1) from trace_event where event_type='manager_decision'").fetchone()[0])
|
|
except Exception:
|
|
count = -1
|
|
con.close()
|
|
print(f"manager_decision_count={count}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|
|
|