fix: 第一轮修复 - 记忆管理/IoT操控/历史消息持久化/动作消息/链路优化/安全配置

- 修复记忆管理数据库连接不可用 (ai-core重编译+Unicode修复)
- 修复IoT子会话工具调用链路日志缺失
- 新增最终审查子会话(review_provider) 支持消息格式解析拆分
- 实现历史消息持久化(后端存储+前端分页加载)
- 前端新增动作消息(ActionMessage)类型和渲染
- 优化对话链路速度(非阻塞子会话+快速问候通道)
- JWT密钥环境变量化(无默认值启动panic)
- Token自动刷新机制(401拦截器+refresh接口)
- WebSocket指数退避重连(jitter+最大10次)
- localStorage清理一致性(cyrene_前缀+版本检查)
- IoT环境变量统一为IOT_SERVICE_URL
This commit is contained in:
2026-05-21 23:10:07 +08:00
parent 8b7d4ec19a
commit a058b0ab8e
53 changed files with 5535 additions and 241 deletions
+198
View File
@@ -0,0 +1,198 @@
#!/usr/bin/env python3
"""CDP v3: 深度诊断前端白屏问题 — 检查 DOM、网络、JS 模块加载"""
import json, time, base64, urllib.request, os
# Step 1: 打开新页面
print("=== Step 1: 导航到前端页面 ===")
req = urllib.request.Request("http://127.0.0.1:9225/json/new?url=http://localhost:5199/", method="PUT")
resp = urllib.request.urlopen(req, timeout=10)
page_data = json.loads(resp.read())
page_id = page_data.get("id","")
ws_url = page_data.get("webSocketDebuggerUrl","")
print(f" Page ID: {page_id}")
print(f" WS URL: {ws_url[:80]}")
from websocket import create_connection
ws = create_connection(ws_url, timeout=10)
def cdp(method, params=None, msg_id=1):
ws.send(json.dumps({"id": msg_id, "method": method, "params": params or {}}))
def recv_all(timeout=3):
ws.settimeout(timeout)
msgs = []
try:
while True:
msgs.append(ws.recv())
except:
pass
return msgs
def find_result(msgs, msg_id):
for m in msgs:
try:
d = json.loads(m)
if d.get("id") == msg_id:
return d.get("result",{})
except:
pass
return None
# Step 2: 启用所有 domains
print("\n=== Step 2: 启用所有 domains ===")
cdp("Page.enable", {}, 1)
cdp("Network.enable", {}, 2)
cdp("Runtime.enable", {}, 3)
cdp("Log.enable", {}, 4)
cdp("DOM.enable", {}, 5)
recv_all(1)
# Step 3: 重新 navigate 以捕获网络事件
print("\n=== Step 3: 重新导航 ===")
cdp("Page.navigate", {"url": "http://localhost:5199/"}, 10)
time.sleep(3)
# 收集在此期间的所有消息
print(" 收集网络/运行时事件...")
all_msgs = recv_all(5)
# 分析网络事件
print("\n=== Step 4: 网络请求分析 ===")
requests = {}
for m in all_msgs:
try:
d = json.loads(m)
except:
continue
method = d.get("method","")
params = d.get("params",{})
if method == "Network.requestWillBeSent":
req_info = params.get("request", {})
url = req_info.get("url","")
req_id = params.get("requestId","")
rtype = params.get("type","")
requests[req_id] = {"url": url, "type": rtype, "status": "pending"}
elif method == "Network.responseReceived":
req_id = params.get("requestId","")
resp = params.get("response",{})
status = resp.get("status",0)
mime = resp.get("mimeType","")
if req_id in requests:
requests[req_id]["status"] = status
requests[req_id]["mime"] = mime
elif method == "Network.loadingFailed":
req_id = params.get("requestId","")
err = params.get("errorText","")
if req_id in requests:
requests[req_id]["status"] = "FAILED"
requests[req_id]["error"] = err
elif method == "Runtime.exceptionThrown":
exc = params.get("exceptionDetails",{})
print(f" [EXCEPTION] {exc.get('text','')} at {exc.get('url','')}:{exc.get('lineNumber','')}")
elif method == "Log.entryAdded":
entry = params.get("entry",{})
print(f" [CONSOLE:{entry.get('level','log')}] {entry.get('text','')[:200]}")
elif method == "Runtime.consoleAPICalled":
args = params.get("args",[])
msg_type = params.get("type","log")
texts = []
for a in args:
t = a.get("value","") or a.get("description","")
texts.append(str(t)[:150])
print(f" [CONSOLE:{msg_type}] {' '.join(texts)}")
print(f"\n Total network requests: {len(requests)}")
for rid, info in requests.items():
status_str = str(info.get("status","?"))
type_str = info.get("type","?")
url_short = info.get("url","")[-80:]
error_str = f" ERROR={info.get('error','')}" if info.get("error") else ""
print(f" [{type_str}] {status_str} {url_short}{error_str}")
# Step 5: 运行时评估 - 深度检查
print("\n=== Step 5: 运行时深度评估 ===")
checks = [
("window.location.href", "window.location.href"),
("document.readyState", "document.readyState"),
("document.title", "document.title"),
("root element", "document.getElementById('root') ? 'EXISTS' : 'NULL'"),
("body innerHTML length", "document.body ? document.body.innerHTML.length : -1"),
("body children count", "document.body ? document.body.children.length : -1"),
("head children count", "document.head ? document.head.children.length : -1"),
("all scripts", "Array.from(document.querySelectorAll('script')).map(s => ({src:s.src,type:s.type,async:s.async,defer:s.defer})).slice(0,5)"),
("React DOM check", "typeof React !== 'undefined' ? 'React global found' : (document.querySelector('#root') ? 'root exists but no React global' : 'root missing')"),
("SW registration check", "'serviceWorker' in navigator ? 'SW API available' : 'NO SW API'"),
("localStorage token", "localStorage.getItem('token') || 'no token'"),
]
for idx, (label, expr) in enumerate(checks):
msg_id = 100 + idx
cdp("Runtime.evaluate", {"expression": expr, "returnByValue": True}, msg_id)
recv_msgs = recv_all(2)
r = find_result(recv_msgs, msg_id)
if r:
val = r.get("result",{}).get("value","?")
# 处理 object 类型
if isinstance(val, dict) and "objectId" in r.get("result",{}):
val = r["result"].get("description","[object]")
err = r.get("result",{}).get("description","") or ""
print(f" {label}: {val}")
if r.get("result",{}).get("type") == "object":
# Try to get properties
obj_id = r["result"].get("objectId","")
if obj_id:
cdp("Runtime.getProperties", {"objectId": obj_id, "ownProperties": True}, 900 + idx)
prop_msgs = recv_all(2)
prop_r = find_result(prop_msgs, 900 + idx)
if prop_r:
for prop in prop_r.get("result",[]):
v = prop.get("value",{})
print(f" .{prop.get('name','?')} = {v.get('value', v.get('description','?'))}")
else:
print(f" {label}: NO RESULT")
# Step 6: 截图
print("\n=== Step 6: 截图 ===")
cdp("Page.captureScreenshot", {"format": "png", "captureBeyondViewport": True}, 20)
recv_msgs = recv_all(5)
r = find_result(recv_msgs, 20)
if r and r.get("data"):
img = base64.b64decode(r["data"])
fp = "/tmp/cyrene_screenshot_round12_v3.png"
with open(fp, "wb") as f:
f.write(img)
print(f" Screenshot: {len(img)} bytes -> {fp}")
else:
print(f" Screenshot failed: {str(r)[:200]}")
# Step 7: 检查是否有 JS 异常
print("\n=== Step 7: 最终 JS 异常检查 ===")
ws.settimeout(1)
has_exception = False
for i in range(5):
try:
d = json.loads(ws.recv())
if d.get("method") == "Runtime.exceptionThrown":
exc = d["params"]["exceptionDetails"]
print(f" [EXCEPTION] text={exc.get('text','')}")
print(f" url={exc.get('url','')}")
print(f" line={exc.get('lineNumber','')} col={exc.get('columnNumber','')}")
stack = exc.get("stackTrace",{})
for frame in stack.get("callFrames",[]):
print(f" at {frame.get('functionName','')} {frame.get('url','')}:{frame.get('lineNumber','')}")
has_exception = True
except:
break
if not has_exception:
print(" No exceptions caught")
ws.close()
print("\n[DONE v3]")