mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 01:50:44 +08:00
重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 wiki 记忆注入与写回闭环。
同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。 Made-with: Cursor
This commit is contained in:
parent
4a23b715a2
commit
dbbe3add6a
14438 changed files with 2693620 additions and 2546 deletions
157
scripts/ws_e2e_smoke.py
Normal file
157
scripts/ws_e2e_smoke.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
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())
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue