mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 00:40:45 +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
55
scripts/db_recent_check.py
Normal file
55
scripts/db_recent_check.py
Normal 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()
|
||||
|
||||
88
scripts/debug_trace_dump.py
Normal file
88
scripts/debug_trace_dump.py
Normal 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()
|
||||
|
||||
|
|
@ -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 = ""
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
11
scripts/status_desktop.ps1
Normal file
11
scripts/status_desktop.ps1
Normal 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
|
||||
|
||||
67
scripts/trace_latest_turn.py
Normal file
67
scripts/trace_latest_turn.py
Normal 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
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())
|
||||
|
||||
88
scripts/ws_send_existing_session.py
Normal file
88
scripts/ws_send_existing_session.py
Normal 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())
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue