From a7b8ffc71654b2f62481e3fb280eed0b2417121b Mon Sep 17 00:00:00 2001 From: catlog22 Date: Mon, 8 Dec 2025 23:21:47 +0800 Subject: [PATCH] feat(explorer): async task execution, CLI selector fix, WebSocket frame handling - Task queue now executes tasks in parallel using Promise.all - addFolderToQueue uses selected CLI tool instead of hardcoded 'gemini' - Added CSS styles for queue-cli-selector toolbar - Force refresh notification list after all tasks complete - Fixed WebSocket frame parsing to handle Ping/Pong control frames - Respond to Ping with Pong, ignore other control frames - Eliminates garbled characters in WebSocket logs --- ccw/src/core/server.js | 38 +++++++++++++++++-- .../templates/dashboard-js/views/explorer.js | 6 +++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/ccw/src/core/server.js b/ccw/src/core/server.js index 2ea951a0..a4c53010 100644 --- a/ccw/src/core/server.js +++ b/ccw/src/core/server.js @@ -507,9 +507,31 @@ function handleWebSocketUpgrade(req, socket, head) { // Handle incoming messages socket.on('data', (buffer) => { try { - const message = parseWebSocketFrame(buffer); - if (message) { - console.log('[WS] Received:', message); + const frame = parseWebSocketFrame(buffer); + if (!frame) return; + + const { opcode, payload } = frame; + + switch (opcode) { + case 0x1: // Text frame + if (payload) { + console.log('[WS] Received:', payload); + } + break; + case 0x8: // Close frame + socket.end(); + break; + case 0x9: // Ping frame - respond with Pong + const pongFrame = Buffer.alloc(2); + pongFrame[0] = 0x8A; // Pong opcode with FIN bit + pongFrame[1] = 0x00; // No payload + socket.write(pongFrame); + break; + case 0xA: // Pong frame - ignore + break; + default: + // Ignore other frame types (binary, continuation) + break; } } catch (e) { // Ignore parse errors @@ -529,10 +551,18 @@ function handleWebSocketUpgrade(req, socket, head) { /** * Parse WebSocket frame (simplified) + * Returns { opcode, payload } or null */ function parseWebSocketFrame(buffer) { if (buffer.length < 2) return null; + const firstByte = buffer[0]; + const opcode = firstByte & 0x0f; // Extract opcode (bits 0-3) + + // Opcode types: + // 0x0 = continuation, 0x1 = text, 0x2 = binary + // 0x8 = close, 0x9 = ping, 0xA = pong + const secondByte = buffer[1]; const isMasked = (secondByte & 0x80) !== 0; let payloadLength = secondByte & 0x7f; @@ -560,7 +590,7 @@ function parseWebSocketFrame(buffer) { } } - return payload.toString('utf8'); + return { opcode, payload: payload.toString('utf8') }; } /** diff --git a/ccw/src/templates/dashboard-js/views/explorer.js b/ccw/src/templates/dashboard-js/views/explorer.js index 6767476b..0fefd14a 100644 --- a/ccw/src/templates/dashboard-js/views/explorer.js +++ b/ccw/src/templates/dashboard-js/views/explorer.js @@ -836,6 +836,12 @@ async function startTaskQueue() { 'Explorer' ); + // Force refresh notification list to ensure all notifications are displayed + if (typeof renderGlobalNotifications === 'function') { + renderGlobalNotifications(); + updateGlobalNotifBadge(); + } + // Re-enable start button if there are pending tasks const hasPending = updateTaskQueue.some(t => t.status === 'pending'); document.getElementById('startQueueBtn').disabled = !hasPending;