From 9b22b647f24dc596cea613df6813e6cadd63c59e Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sat, 14 Mar 2026 16:02:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(remote):=20=E6=B7=BB=E5=8A=A0=20XCOpenCode?= =?UTF-8?q?Web=20=E6=9C=8D=E5=8A=A1=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 XCOpenCodeWebService.js 服务模块 - 支持启动/停止/健康检测(每10秒) - 随 remote 服务启动/退出 - 配置文件添加 xcopencodeweb 配置 - 修复 opencode 默认端口配置 --- remote/config/default.json | 5 +- remote/src/core/App.js | 20 ++- .../services/opencode/XCOpenCodeWebService.js | 149 ++++++++++++++++++ 3 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 remote/src/services/opencode/XCOpenCodeWebService.js diff --git a/remote/config/default.json b/remote/config/default.json index 684866b..452e7fc 100644 --- a/remote/config/default.json +++ b/remote/config/default.json @@ -28,8 +28,11 @@ "configPath": "./frp/frpc.toml" }, "opencode": { + "enabled": true + }, + "xcopencodeweb": { "enabled": true, - "port": 3002 + "port": 9999 }, "gitea": { "enabled": true diff --git a/remote/src/core/App.js b/remote/src/core/App.js index b226bfd..1d96925 100644 --- a/remote/src/core/App.js +++ b/remote/src/core/App.js @@ -124,7 +124,17 @@ class App { const opencodeConfig = config.getSection('opencode') || {}; return new OpenCodeService({ enabled: opencodeConfig.enabled !== false, - port: opencodeConfig.port || 3002 + port: opencodeConfig.port + }); + }); + + this.container.register('xcOpenCodeWebService', (c) => { + const XCOpenCodeWebService = require('../services/opencode/XCOpenCodeWebService'); + const config = c.resolve('config'); + const xcopencodewebConfig = config.getSection('xcopencodeweb') || {}; + return new XCOpenCodeWebService({ + enabled: xcopencodewebConfig.enabled !== false, + port: xcopencodewebConfig.port }); }); @@ -206,6 +216,10 @@ class App { openCodeService.start(); logger.info('OpenCode service started'); + const xcOpenCodeWebService = this.container.resolve('xcOpenCodeWebService'); + xcOpenCodeWebService.start(); + logger.info('XCOpenCodeWeb service started'); + const giteaService = this.container.resolve('giteaService'); giteaService.start(); logger.info('Gitea service started'); @@ -488,6 +502,10 @@ class App { openCodeService.stop(); logger.info('OpenCode service stopped'); + const xcOpenCodeWebService = this.container.resolve('xcOpenCodeWebService'); + xcOpenCodeWebService.stop(); + logger.info('XCOpenCodeWeb service stopped'); + const giteaService = this.container.resolve('giteaService'); giteaService.stop(); logger.info('Gitea service stopped'); diff --git a/remote/src/services/opencode/XCOpenCodeWebService.js b/remote/src/services/opencode/XCOpenCodeWebService.js new file mode 100644 index 0000000..07d047d --- /dev/null +++ b/remote/src/services/opencode/XCOpenCodeWebService.js @@ -0,0 +1,149 @@ +const { spawn } = require('child_process'); +const path = require('path'); +const logger = require('../../utils/logger'); + +class XCOpenCodeWebService { + constructor(options = {}) { + this.process = null; + this.isRunning = false; + this.port = options.port || 9999; + this.enabled = options.enabled !== false; + this.healthCheckInterval = 10000; + this.healthCheckTimeout = 2000; + this.healthCheckTimer = null; + } + + getExePath() { + const exeName = 'XCOpenCodeWeb.exe'; + const basePath = path.join(__dirname, '../../xcopencodeweb'); + return path.join(basePath, exeName); + } + + getExeArgs() { + return ['--port', this.port.toString()]; + } + + async checkHealth() { + try { + const timeoutMs = this.healthCheckTimeout; + + const fetchPromise = fetch(`http://127.0.0.1:${this.port}`); + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout')), timeoutMs) + ); + + const response = await Promise.race([fetchPromise, timeoutPromise]); + return response.ok || response.status === 401; + } catch (error) { + logger.warn('[XCOpenCodeWebService] Health check failed:', error.message); + return false; + } + } + + startHealthCheck() { + if (this.healthCheckTimer) { + clearInterval(this.healthCheckTimer); + } + + this.healthCheckTimer = setInterval(async () => { + const isHealthy = await this.checkHealth(); + if (!isHealthy && this.isRunning) { + logger.warn('[XCOpenCodeWebService] Service not responding'); + } + }, this.healthCheckInterval); + } + + stopHealthCheck() { + if (this.healthCheckTimer) { + clearInterval(this.healthCheckTimer); + this.healthCheckTimer = null; + } + } + + start() { + if (!this.enabled) { + logger.info('XCOpenCodeWeb service is disabled'); + return; + } + + if (this.isRunning && this.process) { + logger.warn('XCOpenCodeWeb service is already running'); + return; + } + + try { + const exePath = this.getExePath(); + const exeArgs = this.getExeArgs(); + logger.info(`[XCOpenCodeWebService] Starting from: ${exePath} with args: ${exeArgs.join(' ')}`); + + this.process = spawn(exePath, exeArgs, { + stdio: ['ignore', 'pipe', 'pipe'], + detached: false, + }); + + this.process.stdout.on('data', (data) => { + const output = data.toString().trim(); + if (output) { + logger.info(`[XCOpenCodeWeb] ${output}`); + } + }); + + this.process.stderr.on('data', (data) => { + const output = data.toString().trim(); + if (output) { + logger.error(`[XCOpenCodeWeb error] ${output}`); + } + }); + + this.process.on('error', (error) => { + logger.error('[XCOpenCodeWebService] Process error', { error: error.message }); + this.isRunning = false; + this.process = null; + }); + + this.process.on('exit', (code) => { + logger.info('[XCOpenCodeWebService] Process exited', { code }); + this.isRunning = false; + this.process = null; + }); + + this.isRunning = true; + this.startHealthCheck(); + + logger.info(`[XCOpenCodeWebService] Started successfully on port ${this.port}`); + } catch (error) { + logger.error('[XCOpenCodeWebService] Failed to start', { error: error.message }); + this.isRunning = false; + } + } + + stop() { + this.stopHealthCheck(); + + if (!this.process) { + logger.info('[XCOpenCodeWebService] Not running'); + return; + } + + try { + logger.info('[XCOpenCodeWebService] Stopping...'); + + this.process.kill(); + this.process = null; + this.isRunning = false; + + logger.info('[XCOpenCodeWebService] Stopped'); + } catch (error) { + logger.error('[XCOpenCodeWebService] Failed to stop', { error: error.message }); + } + } + + getStatus() { + return { + running: this.isRunning, + port: this.port + }; + } +} + +module.exports = XCOpenCodeWebService;