diff --git a/aibox/entry.html b/aibox/entry.html index 43863f6..6d02deb 100644 --- a/aibox/entry.html +++ b/aibox/entry.html @@ -4,8 +4,8 @@ AI智能助手 - Socket.IO客户端 - - + + @@ -1449,6 +1449,7 @@ let reconnectAttempts = 0; const MAX_RECONNECT_ATTEMPTS = 5; let targetWsUrl = null; + let reconnectTimer = null; // F2(审计 2026-08-20):重连定时器句柄,手动断开时清除 function createSocketConnection() { targetWsUrl = document.getElementById('ws-url').value.trim(); @@ -1514,7 +1515,8 @@ if (reason !== 'io client disconnect' && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { reconnectAttempts++; showSystemMessage(`⚠️ 连接断开,${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS} 次重试...`); - setTimeout(() => { + reconnectTimer = setTimeout(() => { + reconnectTimer = null; if (!socket || !socket.connected) { console.log('尝试重连...'); socket = createSocketConnection(); @@ -1687,7 +1689,8 @@ // 事件绑定 document.getElementById('connect-btn').addEventListener('click', function() { - if (socket && socket.connected) { + // F3(审计 2026-08-20):CONNECTING 中的旧连接也要断开,否则重复点击产生孤儿连接/双后端会话 + if (socket) { socket.disconnect(); } reconnectAttempts = 0; @@ -1695,6 +1698,8 @@ }); document.getElementById('disconnect-btn').addEventListener('click', function() { + // F2:清除已排程的重连定时器,防止倒计时到期把连接"复活" + if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; } if (socket) { socket.disconnect(); socket = null; @@ -1728,11 +1733,13 @@ document.getElementById('clear-chat-btn').addEventListener('click', function() { const container = document.getElementById('messages-container'); + // F1(审计 2026-08-20):welcome-message 在 container 内, + // 必须先 cloneNode 缓存再清空,否则 querySelector 返回 null 抛 TypeError + const welcomeEl = document.querySelector('.welcome-message'); + const welcomeHtml = welcomeEl ? welcomeEl.cloneNode(true) : null; container.innerHTML = ''; - - const welcomeHtml = document.querySelector('.welcome-message').cloneNode(true); - container.appendChild(welcomeHtml); - + if (welcomeHtml) container.appendChild(welcomeHtml); + clearAllStreamingMessages(); }); diff --git a/aibox/entry1.html b/aibox/entry1.html index 35c06f9..e437cb3 100644 --- a/aibox/entry1.html +++ b/aibox/entry1.html @@ -5,9 +5,9 @@ 小猫AI · 智能喵语伙伴 - + - + @@ -673,7 +673,8 @@ } function connectWebSocket() { - if(socket && socket.connected) { + // F3(审计 2026-08-20):CONNECTING 中的旧连接也要断开,否则重复点击产生孤儿连接/双后端会话 + if(socket) { socket.disconnect(); } @@ -752,6 +753,8 @@ function disconnectWebSocket() { manualDisconnect = true; + // F2(审计 2026-08-20):清除已排程的重连定时器,防止倒计时到期把连接"复活" + if(reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; } if(socket) { socket.disconnect(); socket = null; diff --git a/aibox/frontend.html b/aibox/frontend.html index 2ee4d94..fd52579 100644 --- a/aibox/frontend.html +++ b/aibox/frontend.html @@ -4,8 +4,8 @@ AI智能助手 - WebSocket直连 - - + + @@ -1424,9 +1424,12 @@ if (!manualDisconnect && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { reconnectAttempts++; - showSystemMessage(`⚠️ 连接断开,${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS} 秒后重试...`); + // F6(审计 2026-08-20):实际延迟为 3000*N ms,文案与此对齐 + showSystemMessage(`⚠️ 连接断开,${3 * reconnectAttempts} 秒后重试(${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`); setTimeout(() => { - if (!ws || ws.readyState !== WebSocket.OPEN) { + // F2(审计 2026-08-20):闭包到期时再判 manualDisconnect, + // 防止手动断开后已排程定时器把连接"复活" + if (!manualDisconnect && (!ws || ws.readyState !== WebSocket.OPEN)) { console.log('尝试重连...'); ws = connectWebSocket(); } @@ -1596,7 +1599,8 @@ // 事件绑定 document.getElementById('connect-btn').addEventListener('click', function() { - if (ws && ws.readyState === WebSocket.OPEN) { + // F3(审计 2026-08-20):CONNECTING 中的旧连接也要关闭,否则重复点击产生孤儿连接/双后端会话 + if (ws && ws.readyState !== WebSocket.CLOSED) { ws.close(); } reconnectAttempts = 0; @@ -1639,11 +1643,14 @@ document.getElementById('clear-chat-btn').addEventListener('click', function() { const container = document.getElementById('messages-container'); + // F1(审计 2026-08-20):welcome-message 在 container 内, + // 必须先 cloneNode 缓存再清空,否则 querySelector 返回 null 抛 TypeError, + // 且后续 clearAllStreamingMessages/计数清零全部中断不执行 + const welcomeEl = document.querySelector('.welcome-message'); + const welcomeHtml = welcomeEl ? welcomeEl.cloneNode(true) : null; container.innerHTML = ''; - - const welcomeHtml = document.querySelector('.welcome-message').cloneNode(true); - container.appendChild(welcomeHtml); - + if (welcomeHtml) container.appendChild(welcomeHtml); + clearAllStreamingMessages(); messageCount = 0; toolCallCount = 0; diff --git a/docs/reports/aibox_audit_20260817.md b/docs/reports/aibox_audit_20260817.md index de7d783..48fd095 100644 --- a/docs/reports/aibox_audit_20260817.md +++ b/docs/reports/aibox_audit_20260817.md @@ -912,3 +912,21 @@ Linux 基线(`aibox/venv`)维持 **490 passed** 不受影响。 - 全量测试 **493 passed / 4 failed(既有平台环境项:symlink 权限、`python3` 缺 PATH、Windows 可执行位语义)/ 1 skipped**,与波次 1 基线完全一致,零回归 **波次 3~5 待落地**:前端功能(F1/F2 等)、命令面规整(W 系列)、客户端收尾(M1 等)。 + +## 二十、Batch M 修复记录(波次 3:前端功能,2026-08-20) + +范围:§17.6 波次 3——前端三页面功能缺陷(F1/F2 + F3/F4/F6)。F5(消息无限追加内存增长)按审计口径"可不做"跳过。 + +| # | 修复内容 | 关键变更 | +|---|---|---| +| F1 | 清空聊天按钮 TypeError(entry/frontend 同源) | `.welcome-message` 在 `#messages-container` 内,`innerHTML=''` 后 querySelector 必为 null——两页面统一改为"先 cloneNode 缓存 → 清空 → 回插",frontend 后续的计数清零不再被中断 | +| F2 | 手动断开后重连定时器"复活"连接(三页面) | `entry.html` 新增 `reconnectTimer` 句柄,disconnect-btn 处 `clearTimeout`;`frontend.html` 重连闭包到期时再判 `manualDisconnect`;`entry1.html` `disconnectWebSocket` 补 `clearTimeout(reconnectTimer)` | +| F3 | CONNECTING 中重复点击产生孤儿连接(三页面) | `entry.html`/`entry1.html` 连接前无条件 `disconnect()` 旧 socket(原仅 `connected` 才断开);`frontend.html` 关闭条件放宽为 `readyState !== CLOSED` | +| F4 | 三页面 6 个 CSS `` 无 SRI | font-awesome 6.0.0 与 highlight.js github-dark 11.8.0 补 `integrity`+`crossorigin`(sha384 哈希经 curl 拉取实测计算,与 J13 脚本 SRI 口径对齐) | +| F6 | frontend 重连文案与实际行为不符 | "N/5 秒后重试" → "`3*N` 秒后重试(N/5)",与 `3000*reconnectAttempts` 实际延迟对齐 | + +**验证**: +- 三页面全部内联脚本 `node --check` 通过 +- 全量测试 **493 passed / 4 failed(既有平台环境项)/ 1 skipped**,与波次 1/2 基线完全一致,零回归(前端无 pytest 覆盖,语法级验证兜底) + +**波次 4~5 待落地**:命令面规整(W1~W8/W11/W12 + W5 择优)、客户端收尾(M1~M5/M7/M8)。