From a1f893f0c666679d03697a694d05bec7e0b62d55 Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Sat, 20 Dec 2025 15:54:20 +0800 Subject: [PATCH] chore(chrome-qwen-bridge): connect --- packages/chrome-qwen-bridge/debug.sh | 31 +- .../native-host/host-wrapper.sh | 11 + .../chrome-qwen-bridge/native-host/host.js | 827 +++++++++++------- 3 files changed, 546 insertions(+), 323 deletions(-) create mode 100755 packages/chrome-qwen-bridge/native-host/host-wrapper.sh diff --git a/packages/chrome-qwen-bridge/debug.sh b/packages/chrome-qwen-bridge/debug.sh index bf890107..76f3445e 100755 --- a/packages/chrome-qwen-bridge/debug.sh +++ b/packages/chrome-qwen-bridge/debug.sh @@ -73,32 +73,18 @@ EOF echo -e "${GREEN}✓${NC} Native Host 已配置" -# 第三步:检查 Qwen CLI(可选) +# 第三步:检查 Qwen CLI echo -e "\n${BLUE}[3/5]${NC} 检查 Qwen CLI..." QWEN_AVAILABLE=false if command -v qwen &> /dev/null; then QWEN_AVAILABLE=true - echo -e "${GREEN}✓${NC} Qwen CLI $(qwen --version 2>/dev/null || echo "已安装")" - - # 尝试启动 Qwen server - if ! lsof -i:8080 &> /dev/null; then - echo -e "${CYAN}→${NC} 启动 Qwen server (端口 8080)..." - qwen server --port 8080 > /tmp/qwen-server.log 2>&1 & - QWEN_PID=$! - sleep 2 - - if kill -0 $QWEN_PID 2>/dev/null; then - echo -e "${GREEN}✓${NC} Qwen server 已启动 (PID: $QWEN_PID)" - else - echo -e "${YELLOW}!${NC} Qwen server 启动失败,继续运行..." - QWEN_AVAILABLE=false - fi - else - echo -e "${YELLOW}!${NC} 端口 8080 已被占用" - fi + QWEN_VERSION=$(qwen --version 2>/dev/null || echo "已安装") + echo -e "${GREEN}✓${NC} Qwen CLI ${QWEN_VERSION}" + echo -e "${CYAN}→${NC} 使用 ACP 模式与 Chrome 插件通信" else echo -e "${YELLOW}!${NC} Qwen CLI 未安装(插件基础功能仍可使用)" + echo -e " 安装方法: npm install -g @anthropic-ai/qwen-code" fi # 第四步:启动测试页面 @@ -338,7 +324,7 @@ echo -e " • 测试页面: ${BLUE}http://localhost:3000/qwen-test.html${NC}" echo -e " • 插件: 已加载到工具栏" if [ "$QWEN_AVAILABLE" = true ]; then - echo -e " • Qwen Server: ${BLUE}http://localhost:8080${NC}" + echo -e " • Qwen CLI: 可用 (ACP 模式)" fi echo "" @@ -347,10 +333,6 @@ echo -e " • 插件日志: Chrome DevTools Console" echo -e " • 后台脚本: chrome://extensions → Service Worker" echo -e " • Native Host: /tmp/qwen-bridge-host.log" -if [ "$QWEN_AVAILABLE" = true ]; then - echo -e " • Qwen 日志: /tmp/qwen-server.log" -fi - echo "" echo -e "${YELLOW}按 Ctrl+C 停止所有服务${NC}" echo "" @@ -361,7 +343,6 @@ cleanup() { # 停止进程 [ ! -z "$TEST_PID" ] && kill $TEST_PID 2>/dev/null - [ ! -z "$QWEN_PID" ] && kill $QWEN_PID 2>/dev/null echo -e "${GREEN}✓${NC} 已停止所有服务" exit 0 diff --git a/packages/chrome-qwen-bridge/native-host/host-wrapper.sh b/packages/chrome-qwen-bridge/native-host/host-wrapper.sh new file mode 100755 index 00000000..8b0564ee --- /dev/null +++ b/packages/chrome-qwen-bridge/native-host/host-wrapper.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# 添加必要的 PATH +export PATH="/usr/local/bin:/Users/yiliang/.npm-global/bin:$PATH" + +LOG="/var/folders/sy/9mwf8c3n2b57__q35fyxwdhh0000gp/T/qwen-wrapper.log" +echo "$(date): Wrapper started" >> "$LOG" +echo "$(date): PATH=$PATH" >> "$LOG" + +# 使用完整路径运行 node +exec /usr/local/bin/node "$(dirname "$0")/host.js" 2>> "$LOG" diff --git a/packages/chrome-qwen-bridge/native-host/host.js b/packages/chrome-qwen-bridge/native-host/host.js index 9c8bcfbd..420a419b 100755 --- a/packages/chrome-qwen-bridge/native-host/host.js +++ b/packages/chrome-qwen-bridge/native-host/host.js @@ -3,6 +3,7 @@ /** * Native Messaging Host for Qwen CLI Bridge * This script acts as a bridge between the Chrome extension and Qwen CLI + * Uses ACP (Agent Communication Protocol) for communication with Qwen CLI */ const { spawn } = require('child_process'); @@ -10,8 +11,31 @@ const fs = require('fs'); const path = require('path'); const os = require('os'); -// Native Messaging protocol helpers -function sendMessage(message) { +// ============================================================================ +// Logging +// ============================================================================ + +const LOG_FILE = path.join(os.tmpdir(), 'qwen-bridge-host.log'); + +function log(message, level = 'INFO') { + const timestamp = new Date().toISOString(); + const logLine = `[${timestamp}] [${level}] ${message}\n`; + fs.appendFileSync(LOG_FILE, logLine); +} + +function logError(message) { + log(message, 'ERROR'); +} + +function logDebug(message) { + log(message, 'DEBUG'); +} + +// ============================================================================ +// Native Messaging Protocol (Chrome Extension <-> Native Host) +// ============================================================================ + +function sendMessageToExtension(message) { const buffer = Buffer.from(JSON.stringify(message)); const length = Buffer.allocUnsafe(4); length.writeUInt32LE(buffer.length, 0); @@ -20,56 +44,462 @@ function sendMessage(message) { process.stdout.write(buffer); } -function readMessages() { +function readMessagesFromExtension() { let messageLength = null; let chunks = []; - process.stdin.on('readable', () => { - let chunk; + // Keep stdin open and in flowing mode + process.stdin.resume(); - while ((chunk = process.stdin.read()) !== null) { - chunks.push(chunk); + process.stdin.on('data', (chunk) => { + log(`Received ${chunk.length} bytes from extension`); + chunks.push(chunk); + + while (true) { const buffer = Buffer.concat(chunks); - // Read message length if we haven't yet + // Need at least 4 bytes for length if (messageLength === null) { - if (buffer.length >= 4) { - messageLength = buffer.readUInt32LE(0); - chunks = [buffer.slice(4)]; - } + if (buffer.length < 4) break; + messageLength = buffer.readUInt32LE(0); + chunks = [buffer.slice(4)]; + log(`Message length: ${messageLength}`); + continue; } - // Read message if we have the full length - if (messageLength !== null) { - const fullBuffer = Buffer.concat(chunks); + // Check if we have the full message + const fullBuffer = Buffer.concat(chunks); + if (fullBuffer.length < messageLength) break; - if (fullBuffer.length >= messageLength) { - const messageBuffer = fullBuffer.slice(0, messageLength); - const message = JSON.parse(messageBuffer.toString()); + // Extract and parse message + const messageBuffer = fullBuffer.slice(0, messageLength); + try { + const message = JSON.parse(messageBuffer.toString()); + log(`Received message: ${JSON.stringify(message)}`); - // Reset for next message - chunks = [fullBuffer.slice(messageLength)]; - messageLength = null; + // Reset for next message + chunks = [fullBuffer.slice(messageLength)]; + messageLength = null; - // Handle the message - handleMessage(message); - } + // Handle the message + handleExtensionMessage(message); + } catch (err) { + logError(`Failed to parse message: ${err.message}`); + chunks = [fullBuffer.slice(messageLength)]; + messageLength = null; } } }); process.stdin.on('end', () => { + log('stdin ended'); + cleanup(); process.exit(); }); + + process.stdin.on('error', (err) => { + logError(`stdin error: ${err.message}`); + }); } -// Qwen CLI process management -let qwenProcess = null; -let qwenStatus = 'disconnected'; -let qwenCapabilities = []; +// ============================================================================ +// ACP Protocol (Native Host <-> Qwen CLI) +// ============================================================================ + +const ACP_PROTOCOL_VERSION = 1; + +class AcpConnection { + constructor() { + this.process = null; + this.status = 'disconnected'; + this.sessionId = null; + this.pendingRequests = new Map(); + this.nextRequestId = 1; + this.inputBuffer = ''; + } + + async start(cwd = process.cwd()) { + if (this.process) { + return { success: false, error: 'Qwen CLI is already running' }; + } + + try { + log(`Starting Qwen CLI with ACP mode in ${cwd}`); + + this.process = spawn('qwen', ['--experimental-acp'], { + cwd, + shell: true, + windowsHide: true, + stdio: ['pipe', 'pipe', 'pipe'] + }); + + if (!this.process || !this.process.pid) { + this.process = null; + this.status = 'stopped'; + return { success: false, error: 'Failed to start Qwen CLI process' }; + } + + this.status = 'starting'; + + // Handle stdout (ACP messages from Qwen CLI) + this.process.stdout.on('data', (data) => { + this.handleAcpData(data.toString()); + }); + + // Handle stderr (logs from Qwen CLI) + this.process.stderr.on('data', (data) => { + const message = data.toString().trim(); + if (message) { + log(`Qwen stderr: ${message}`); + } + }); + + // Handle process exit + this.process.on('close', (code) => { + log(`Qwen CLI exited with code ${code}`); + this.process = null; + this.status = 'stopped'; + this.sessionId = null; + + // Reject all pending requests + for (const [id, { reject }] of this.pendingRequests) { + reject(new Error('Qwen CLI process exited')); + } + this.pendingRequests.clear(); + + sendMessageToExtension({ + type: 'event', + data: { type: 'qwen_stopped', code } + }); + }); + + this.process.on('error', (err) => { + logError(`Qwen CLI process error: ${err.message}`); + this.status = 'error'; + }); + + // Initialize ACP connection + const initResult = await this.initialize(); + if (!initResult.success) { + this.stop(); + return initResult; + } + + // Create a new session + const sessionResult = await this.createSession(cwd); + if (!sessionResult.success) { + this.stop(); + return sessionResult; + } + + this.status = 'running'; + return { + success: true, + data: { + status: 'running', + pid: this.process.pid, + sessionId: this.sessionId, + agentInfo: initResult.data.agentInfo + } + }; + } catch (error) { + logError(`Failed to start Qwen CLI: ${error.message}`); + return { success: false, error: error.message }; + } + } + + handleAcpData(data) { + this.inputBuffer += data; + const lines = this.inputBuffer.split('\n'); + this.inputBuffer = lines.pop() || ''; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + + try { + const message = JSON.parse(trimmed); + this.handleAcpMessage(message); + } catch (err) { + logError(`Failed to parse ACP message: ${trimmed}`); + } + } + } + + handleAcpMessage(message) { + logDebug(`ACP received: ${JSON.stringify(message)}`); + + // Handle response to our request + if ('id' in message && !('method' in message)) { + const pending = this.pendingRequests.get(message.id); + if (pending) { + this.pendingRequests.delete(message.id); + if ('result' in message) { + pending.resolve(message.result); + } else if ('error' in message) { + pending.reject(new Error(message.error.message || 'ACP error')); + } + } + return; + } + + // Handle notification from Qwen CLI + if ('method' in message && !('id' in message)) { + this.handleAcpNotification(message.method, message.params); + return; + } + + // Handle request from Qwen CLI (e.g., permission requests) + if ('method' in message && 'id' in message) { + this.handleAcpRequest(message.id, message.method, message.params); + return; + } + } + + handleAcpNotification(method, params) { + switch (method) { + case 'session/update': + // Forward session updates to the extension + sendMessageToExtension({ + type: 'event', + data: { + type: 'session_update', + sessionId: params.sessionId, + update: params.update + } + }); + break; + + case 'authenticate/update': + sendMessageToExtension({ + type: 'event', + data: { + type: 'auth_update', + authUri: params._meta?.authUri + } + }); + break; + + default: + log(`Unknown ACP notification: ${method}`); + } + } + + handleAcpRequest(id, method, params) { + switch (method) { + case 'session/request_permission': + // Forward permission request to extension + sendMessageToExtension({ + type: 'permission_request', + requestId: id, + sessionId: params.sessionId, + toolCall: params.toolCall, + options: params.options + }); + break; + + case 'fs/read_text_file': + // Handle file read request + this.handleFileReadRequest(id, params); + break; + + case 'fs/write_text_file': + // Handle file write request + this.handleFileWriteRequest(id, params); + break; + + default: + log(`Unknown ACP request: ${method}`); + this.sendAcpResponse(id, { error: { code: -32601, message: 'Method not found' } }); + } + } + + handleFileReadRequest(id, params) { + try { + const content = fs.readFileSync(params.path, 'utf-8'); + this.sendAcpResponse(id, { result: { content } }); + } catch (err) { + this.sendAcpResponse(id, { + error: { code: -32000, message: `Failed to read file: ${err.message}` } + }); + } + } + + handleFileWriteRequest(id, params) { + try { + fs.writeFileSync(params.path, params.content, 'utf-8'); + this.sendAcpResponse(id, { result: null }); + } catch (err) { + this.sendAcpResponse(id, { + error: { code: -32000, message: `Failed to write file: ${err.message}` } + }); + } + } + + sendAcpMessage(message) { + if (!this.process || !this.process.stdin.writable) { + throw new Error('Qwen CLI is not running'); + } + + const json = JSON.stringify(message) + '\n'; + logDebug(`ACP send: ${json.trim()}`); + this.process.stdin.write(json); + } + + sendAcpRequest(method, params) { + return new Promise((resolve, reject) => { + const id = this.nextRequestId++; + this.pendingRequests.set(id, { resolve, reject }); + + try { + this.sendAcpMessage({ + jsonrpc: '2.0', + id, + method, + params + }); + } catch (err) { + this.pendingRequests.delete(id); + reject(err); + } + + // Timeout after 30 seconds + setTimeout(() => { + if (this.pendingRequests.has(id)) { + this.pendingRequests.delete(id); + reject(new Error(`Request ${method} timed out`)); + } + }, 30000); + }); + } + + sendAcpResponse(id, response) { + this.sendAcpMessage({ + jsonrpc: '2.0', + id, + ...response + }); + } + + sendAcpNotification(method, params) { + this.sendAcpMessage({ + jsonrpc: '2.0', + method, + params + }); + } + + async initialize() { + try { + const result = await this.sendAcpRequest('initialize', { + protocolVersion: ACP_PROTOCOL_VERSION, + clientCapabilities: { + fs: { + readTextFile: true, + writeTextFile: true + } + } + }); + + log(`Qwen CLI initialized: ${JSON.stringify(result)}`); + return { success: true, data: result }; + } catch (err) { + logError(`Failed to initialize Qwen CLI: ${err.message}`); + return { success: false, error: err.message }; + } + } + + async createSession(cwd) { + try { + const result = await this.sendAcpRequest('session/new', { + cwd, + mcpServers: [] + }); + + this.sessionId = result.sessionId; + log(`Session created: ${this.sessionId}`); + return { success: true, data: result }; + } catch (err) { + logError(`Failed to create session: ${err.message}`); + return { success: false, error: err.message }; + } + } + + async prompt(text) { + if (!this.sessionId) { + return { success: false, error: 'No active session' }; + } + + try { + const result = await this.sendAcpRequest('session/prompt', { + sessionId: this.sessionId, + prompt: [{ type: 'text', text }] + }); + + return { success: true, data: result }; + } catch (err) { + logError(`Prompt failed: ${err.message}`); + return { success: false, error: err.message }; + } + } + + async cancel() { + if (!this.sessionId) { + return { success: false, error: 'No active session' }; + } + + try { + this.sendAcpNotification('session/cancel', { + sessionId: this.sessionId + }); + return { success: true }; + } catch (err) { + return { success: false, error: err.message }; + } + } + + respondToPermission(requestId, optionId) { + this.sendAcpResponse(requestId, { + result: { + outcome: optionId ? { outcome: 'selected', optionId } : { outcome: 'cancelled' } + } + }); + } + + stop() { + if (!this.process) { + return { success: false, error: 'Qwen CLI is not running' }; + } + + try { + this.process.kill('SIGTERM'); + this.process = null; + this.status = 'stopped'; + this.sessionId = null; + + return { success: true, data: 'Qwen CLI stopped' }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + getStatus() { + return { + status: this.status, + sessionId: this.sessionId, + pid: this.process?.pid || null + }; + } +} + +// ============================================================================ +// Global State +// ============================================================================ + +const acpConnection = new AcpConnection(); // Check if Qwen CLI is installed -function checkQwenInstallation() { +async function checkQwenInstallation() { return new Promise((resolve) => { try { const checkProcess = spawn('qwen', ['--version'], { @@ -77,255 +507,56 @@ function checkQwenInstallation() { windowsHide: true }); + let output = ''; + checkProcess.stdout.on('data', (data) => { + output += data.toString(); + }); + checkProcess.on('error', () => { - resolve(false); + resolve({ installed: false }); }); checkProcess.on('close', (code) => { - resolve(code === 0); + if (code === 0) { + resolve({ installed: true, version: output.trim() }); + } else { + resolve({ installed: false }); + } }); - // Timeout after 5 seconds setTimeout(() => { checkProcess.kill(); - resolve(false); + resolve({ installed: false }); }, 5000); } catch (error) { - resolve(false); + resolve({ installed: false }); } }); } -// Start Qwen CLI process -async function startQwenCli(config = {}) { - if (qwenProcess) { - return { success: false, error: 'Qwen CLI is already running' }; - } +// ============================================================================ +// Message Handlers +// ============================================================================ - try { - // Build command arguments - const args = []; - - // Add MCP servers if specified - if (config.mcpServers && config.mcpServers.length > 0) { - for (const server of config.mcpServers) { - args.push('mcp', 'add', '--transport', 'http', server, `http://localhost:${config.httpPort || 8080}/mcp/${server}`); - args.push('&&'); - } - } - - // Start the CLI server - args.push('qwen', 'server'); - - if (config.httpPort) { - args.push('--port', String(config.httpPort)); - } - - // Spawn the process - qwenProcess = spawn(args.join(' '), { - shell: true, - windowsHide: true, - detached: false, - stdio: ['pipe', 'pipe', 'pipe'] - }); - - // Check if process started successfully - if (!qwenProcess || !qwenProcess.pid) { - qwenProcess = null; - qwenStatus = 'stopped'; - return { - success: false, - error: 'Failed to start Qwen CLI process' - }; - } - - qwenStatus = 'running'; - - // Handle process output - qwenProcess.stdout.on('data', (data) => { - const output = data.toString(); - sendMessage({ - type: 'event', - data: { - type: 'qwen_output', - content: output - } - }); - }); - - qwenProcess.stderr.on('data', (data) => { - const error = data.toString(); - sendMessage({ - type: 'event', - data: { - type: 'qwen_error', - content: error - } - }); - }); - - qwenProcess.on('close', (code) => { - qwenProcess = null; - qwenStatus = 'stopped'; - sendMessage({ - type: 'event', - data: { - type: 'qwen_stopped', - code: code - } - }); - }); - - // Wait a bit for the process to start - await new Promise(resolve => setTimeout(resolve, 2000)); - - // Get capabilities - qwenCapabilities = await getQwenCapabilities(); - - return { - success: true, - data: { - status: 'running', - pid: qwenProcess && qwenProcess.pid ? qwenProcess.pid : null, - capabilities: qwenCapabilities - } - }; - } catch (error) { - return { - success: false, - error: error.message - }; - } -} - -// Stop Qwen CLI process -function stopQwenCli() { - if (!qwenProcess) { - return { success: false, error: 'Qwen CLI is not running' }; - } - - try { - qwenProcess.kill('SIGTERM'); - qwenProcess = null; - qwenStatus = 'stopped'; - - return { - success: true, - data: 'Qwen CLI stopped' - }; - } catch (error) { - return { - success: false, - error: error.message - }; - } -} - -// Get Qwen CLI capabilities (MCP servers, tools, etc.) -async function getQwenCapabilities() { - return new Promise((resolve) => { - const checkProcess = spawn('qwen', ['mcp', 'list', '--json'], { - shell: true, - windowsHide: true - }); - - let output = ''; - - checkProcess.stdout.on('data', (data) => { - output += data.toString(); - }); - - checkProcess.on('close', () => { - try { - const capabilities = JSON.parse(output); - resolve(capabilities); - } catch { - resolve([]); - } - }); - - checkProcess.on('error', () => { - resolve([]); - }); - - // Timeout after 5 seconds - setTimeout(() => { - checkProcess.kill(); - resolve([]); - }, 5000); - }); -} - -// Send request to Qwen CLI via HTTP -async function sendToQwenHttp(action, data, config = {}) { - const http = require('http'); - - const port = config.httpPort || 8080; - const hostname = 'localhost'; - - const postData = JSON.stringify({ - action, - data - }); - - const options = { - hostname, - port, - path: '/api/process', - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(postData) - } - }; - - return new Promise((resolve, reject) => { - const req = http.request(options, (res) => { - let responseData = ''; - - res.on('data', (chunk) => { - responseData += chunk; - }); - - res.on('end', () => { - try { - const response = JSON.parse(responseData); - resolve(response); - } catch (error) { - reject(new Error('Invalid response from Qwen CLI')); - } - }); - }); - - req.on('error', (error) => { - reject(error); - }); - - req.write(postData); - req.end(); - }); -} - -// Handle messages from Chrome extension -async function handleMessage(message) { +async function handleExtensionMessage(message) { + log(`Received from extension: ${JSON.stringify(message)}`); let response; switch (message.type) { case 'handshake': - // Initial handshake with extension - const isInstalled = await checkQwenInstallation(); + const installInfo = await checkQwenInstallation(); response = { type: 'handshake_response', version: '1.0.0', - qwenInstalled: isInstalled, - qwenStatus: qwenStatus, - capabilities: qwenCapabilities + qwenInstalled: installInfo.installed, + qwenVersion: installInfo.version, + qwenStatus: acpConnection.getStatus().status }; break; case 'start_qwen': - // Start Qwen CLI - const startResult = await startQwenCli(message.config); + const cwd = message.cwd || process.cwd(); + const startResult = await acpConnection.start(cwd); response = { type: 'response', id: message.id, @@ -334,8 +565,7 @@ async function handleMessage(message) { break; case 'stop_qwen': - // Stop Qwen CLI - const stopResult = stopQwenCli(); + const stopResult = acpConnection.stop(); response = { type: 'response', id: message.id, @@ -343,43 +573,43 @@ async function handleMessage(message) { }; break; - case 'qwen_request': - // Send request to Qwen CLI - try { - if (qwenStatus !== 'running') { - throw new Error('Qwen CLI is not running'); - } + case 'qwen_prompt': + const promptResult = await acpConnection.prompt(message.text); + response = { + type: 'response', + id: message.id, + ...promptResult + }; + break; - const qwenResponse = await sendToQwenHttp( - message.action, - message.data, - message.config - ); + case 'qwen_cancel': + const cancelResult = await acpConnection.cancel(); + response = { + type: 'response', + id: message.id, + ...cancelResult + }; + break; - response = { - type: 'response', - id: message.id, - data: qwenResponse - }; - } catch (error) { - response = { - type: 'response', - id: message.id, - error: error.message - }; - } + case 'permission_response': + acpConnection.respondToPermission(message.requestId, message.optionId); + response = { + type: 'response', + id: message.id, + success: true + }; break; case 'get_status': - // Get current status + const status = acpConnection.getStatus(); + const installStatus = await checkQwenInstallation(); response = { type: 'response', id: message.id, data: { - qwenInstalled: await checkQwenInstallation(), - qwenStatus: qwenStatus, - qwenPid: qwenProcess ? qwenProcess.pid : null, - capabilities: qwenCapabilities + ...status, + qwenInstalled: installStatus.installed, + qwenVersion: installStatus.version } }; break; @@ -392,30 +622,31 @@ async function handleMessage(message) { }; } - sendMessage(response); + sendMessageToExtension(response); +} + +// ============================================================================ +// Cleanup +// ============================================================================ + +function cleanup() { + log('Cleaning up...'); + acpConnection.stop(); } -// Clean up on exit process.on('SIGINT', () => { - if (qwenProcess) { - qwenProcess.kill(); - } + cleanup(); process.exit(); }); process.on('SIGTERM', () => { - if (qwenProcess) { - qwenProcess.kill(); - } + cleanup(); process.exit(); }); -// Log function for debugging (writes to a file since stdout is used for messaging) -function log(message) { - const logFile = path.join(os.tmpdir(), 'qwen-bridge-host.log'); - fs.appendFileSync(logFile, `[${new Date().toISOString()}] ${message}\n`); -} +// ============================================================================ +// Main +// ============================================================================ -// Main execution -log('Native host started'); -readMessages(); \ No newline at end of file +log('Native host started (ACP mode)'); +readMessagesFromExtension();