重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 wiki 记忆注入与写回闭环。

同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。

Made-with: Cursor
This commit is contained in:
oliver 2026-04-26 08:34:33 +08:00
parent 4a23b715a2
commit dbbe3add6a
14438 changed files with 2693620 additions and 2546 deletions

View file

@ -0,0 +1,55 @@
from __future__ import annotations
import json
import sqlite3
def main() -> None:
db = r"D:\project\chatgpt\oclaw\data\ai_ops.sqlite"
con = sqlite3.connect(db)
cur = con.cursor()
print("== latest response_sent trace_event ==")
rows = cur.execute(
"""
select id, session_id, trace_id, payload, timestamp
from trace_event
where event_type='response_sent'
order by id desc
limit 10
"""
).fetchall()
for rid, sid, tid, payload, ts in rows:
try:
obj = json.loads(payload) if isinstance(payload, str) else {}
except Exception:
obj = {}
print(
{
"id": rid,
"session_id": sid,
"trace_id": tid,
"mode": obj.get("mode"),
"elapsed_ms": obj.get("elapsed_ms"),
"ts": ts,
}
)
print("\n== latest chat_message ==")
rows = cur.execute(
"""
select id, session_id, role, substr(content,1,120), timestamp
from chat_message
order by id desc
limit 12
"""
).fetchall()
for row in rows:
print(row)
con.close()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,88 @@
from __future__ import annotations
import json
import sqlite3
from pathlib import Path
def _print_rows(title: str, rows: list[tuple], limit: int = 20) -> None:
print(f"\n== {title} (showing {min(len(rows), limit)}/{len(rows)}) ==")
for r in rows[:limit]:
try:
print(r)
except Exception:
print(repr(r))
def main() -> None:
db = Path(r"D:\project\chatgpt\oclaw\data\ai_ops.sqlite")
if not db.exists():
raise SystemExit(f"db not found: {db}")
con = sqlite3.connect(str(db))
cur = con.cursor()
tables = [r[0] for r in cur.execute("select name from sqlite_master where type='table' order by name").fetchall()]
# If we have a trace table with expected columns, show recent gateway_received/router_decision/manager_decision/response_sent.
trace_table = None
for t in tables:
if t.lower() in ("trace_events", "trace_event", "oclaw_trace_events", "trace"):
trace_table = t
break
if trace_table:
cols = [c[1] for c in cur.execute(f"pragma table_info({trace_table})").fetchall()]
if {"event_type", "payload"}.issubset(set(cols)):
q2 = f"""
select event_type, payload
from {trace_table}
where event_type in ('manager_decision')
order by rowid desc
limit 50
"""
rows2 = cur.execute(q2).fetchall()
parsed2 = []
for et, pl in rows2:
try:
obj = json.loads(pl) if isinstance(pl, str) and pl.strip().startswith(("{", "[")) else pl
except Exception:
obj = pl
if isinstance(obj, dict):
parsed2.append(
(
et,
{
"manager_selected_specialist": obj.get("manager_selected_specialist"),
"manager_self_mode": obj.get("manager_self_mode"),
"dispatch_reason": obj.get("dispatch_reason"),
"instruction_chars": obj.get("instruction_chars"),
"dynamic_agent_used": obj.get("dynamic_agent_used"),
"dynamic_agent_name": obj.get("dynamic_agent_name"),
},
)
)
else:
parsed2.append((et, obj))
_print_rows("recent manager_decision summary", parsed2, limit=50)
q = f"""
select event_type, payload
from {trace_table}
where event_type in ('router_decision','gateway_received','response_sent')
order by rowid desc
limit 50
"""
rows = cur.execute(q).fetchall()
parsed = []
for et, pl in rows:
try:
obj = json.loads(pl) if isinstance(pl, str) and pl.strip().startswith(("{", "[")) else pl
except Exception:
obj = pl
parsed.append((et, obj))
_print_rows("recent trace events (type,payload)", parsed, limit=50)
con.close()
if __name__ == "__main__":
main()

View file

@ -4,7 +4,7 @@ param(
[switch]$SkipInstall = $false,
[switch]$Background = $false,
[switch]$WithWeixin = $false,
[switch]$WithWikiWorker = $false,
[bool]$WithWikiWorker = $true,
[string]$WeixinChannelId = "oclaw-weixin",
[string]$WeixinGatewayBaseUrl = ""
)

View file

@ -1,6 +1,7 @@
param(
[switch]$SkipInstall = $false,
[switch]$Background = $false
[switch]$Background = $false,
[bool]$WithWikiWorker = $true
)
$ErrorActionPreference = "Stop"
@ -10,5 +11,5 @@ if (-not (Test-Path $real)) {
throw "Forward script target not found: $real"
}
& $real -SkipInstall:$SkipInstall -Background:$Background
& $real -SkipInstall:$SkipInstall -Background:$Background -WithWikiWorker:$WithWikiWorker

View file

@ -2,7 +2,8 @@ param(
[string]$BindHost = "127.0.0.1",
[int]$Port = 8787,
[switch]$SkipInstall = $false,
[switch]$Background = $false
[switch]$Background = $false,
[bool]$WithWikiWorker = $true
)
$ErrorActionPreference = "Stop"
@ -12,5 +13,5 @@ if (-not (Test-Path $real)) {
throw "Forward script target not found: $real"
}
& $real -BindHost $BindHost -Port $Port -SkipInstall:$SkipInstall -Background:$Background
& $real -BindHost $BindHost -Port $Port -SkipInstall:$SkipInstall -Background:$Background -WithWikiWorker:$WithWikiWorker

View file

@ -0,0 +1,11 @@
param()
$ErrorActionPreference = "Stop"
$real = Join-Path $PSScriptRoot "..\\runtime\\operations\\scripts\\status_desktop.ps1"
if (-not (Test-Path $real)) {
throw "Forward script target not found: $real"
}
& $real @args

View file

@ -0,0 +1,67 @@
from __future__ import annotations
import json
import sqlite3
def main() -> None:
db = r"D:\project\chatgpt\oclaw\data\ai_ops.sqlite"
con = sqlite3.connect(db)
cur = con.cursor()
row = cur.execute(
"select trace_id,payload,timestamp from trace_event where event_type='response_sent' order by id desc limit 1"
).fetchone()
print("latest_response_sent:", row[2] if row else None)
if not row:
con.close()
return
trace_id = str(row[0] or "")
print("trace_id:", trace_id)
evts = cur.execute(
"""
select event_type,payload,timestamp
from trace_event
where trace_id=?
and event_type in (
'gateway_received','model_chat_start','ws_first_token',
'manager_decision','router_decision','response_sent'
)
order by id asc
""",
(trace_id,),
).fetchall()
keep_keys = [
"elapsed_ms_since_gateway_start",
"elapsed_ms",
"interaction_mode",
"requested_specialist",
"manager_selected_specialist",
"dispatch_reason",
"manager_self_mode",
"instruction_chars",
"ws_client_send_ms",
"ws_first_token_ms",
"channel",
"run_id",
]
for et, pl, ts in evts:
try:
obj = json.loads(pl) if isinstance(pl, str) else pl
except Exception:
obj = pl
if isinstance(obj, dict):
pick = {k: obj.get(k) for k in keep_keys if k in obj}
print(et, ts, pick)
else:
print(et, ts, obj)
con.close()
if __name__ == "__main__":
main()

157
scripts/ws_e2e_smoke.py Normal file
View 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())

View file

@ -0,0 +1,88 @@
from __future__ import annotations
import asyncio
import json
import time
import uuid
from typing import Any
import websockets
SESSION_ID = "c257a9cc64404152ac2f2b2c0e58b9d8"
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):
return {}
return obj
async def _recv_res(ws: websockets.ClientConnection, req_id: str, timeout_s: float = 20.0) -> dict[str, Any]:
end = time.time() + timeout_s
while time.time() < end:
f = await _recv_json(ws)
if f.get("type") == "res" and str(f.get("id") or "") == req_id:
return f
raise TimeoutError(req_id)
async def main() -> None:
run_id = f"manual_{int(time.time()*1000)}_{uuid.uuid4().hex[:6]}"
async with websockets.connect("ws://127.0.0.1:8787/ws", max_size=26_214_400) as ws:
await _recv_json(ws) # connect.challenge
await ws.send(
json.dumps(
{
"type": "req",
"id": "c1",
"method": "connect",
"params": {
"minProtocol": 3,
"maxProtocol": 3,
"client": {
"id": "ws-manual",
"version": "0",
"platform": "win32",
"mode": "webchat",
"instanceId": uuid.uuid4().hex[:8],
},
"role": "operator",
"scopes": [],
},
},
ensure_ascii=False,
)
)
await _recv_res(ws, "c1")
await ws.send(
json.dumps(
{
"type": "req",
"id": "a1",
"method": "agent",
"params": {
"message": "现在几点了,1句回答。",
"sessionId": SESSION_ID,
"sessionKey": SESSION_ID,
"idempotencyKey": run_id,
"interaction_mode": "comprehensive",
"specialist": "generalist",
},
},
ensure_ascii=False,
)
)
print(await _recv_res(ws, "a1"))
print("run_id=", run_id)
print("session_id=", SESSION_ID)
if __name__ == "__main__":
asyncio.run(main())