WIP: migrate to mulit process manager

This commit is contained in:
geekgeekrun
2026-01-04 10:32:43 +08:00
parent b730901775
commit 81e40230d1
9 changed files with 391 additions and 319 deletions

View File

@@ -54,13 +54,13 @@ const server = net.createServer((socket) => {
} catch (parseError) {
console.error('解析JSON消息失败:', parseError.message);
console.error('原始数据:', trimmedLine.substring(0, 100)); // 只打印前100个字符
sendResponse(socket, { error: '无效的JSON格式', details: parseError.message });
sendResponse(socket, message, { error: '无效的JSON格式', details: parseError.message });
}
});
splitStream.on('error', (err) => {
console.error('split2 流处理错误:', err);
sendResponse(socket, { error: '流处理失败' });
sendResponse(socket, message, { error: '流处理失败' });
});
socket.on('error', (err) => {
@@ -92,7 +92,7 @@ function handleMessage(socket, message) {
// 检查是否在停止列表中(防止竞态条件)
if (stoppedWorkers.has(workerId)) {
console.log(`工具进程 ${workerId} 尝试注册,但已被标记为停止,拒绝注册`);
sendResponse(socket, {
sendResponse(socket, message, {
error: `工具进程 ${workerId} 已被停止`,
shouldExit: true // 通知子进程应该退出
});
@@ -103,7 +103,7 @@ function handleMessage(socket, message) {
if (workerInfo) {
workerInfo.socket = socket;
console.log(`工具进程 ${workerId} 已注册TCP连接`);
sendResponse(socket, {
sendResponse(socket, message, {
success: true,
type: 'worker-registered',
message: `工具进程 ${workerId} 连接已注册`
@@ -120,7 +120,7 @@ function handleMessage(socket, message) {
if (!stoppedWorkers.has(workerId)) {
console.log(`工具进程 ${workerId} 尝试注册但workerInfo不存在`);
}
sendResponse(socket, {
sendResponse(socket, message, {
error: `工具进程 ${workerId} 未找到`,
shouldExit: true
});
@@ -133,7 +133,7 @@ function handleMessage(socket, message) {
const workerId = message.workerId;
const shouldExit = stoppedWorkers.has(workerId) || !workers.has(workerId);
sendResponse(socket, {
sendResponse(socket, message, {
type: 'check-should-exit-response',
workerId: workerId,
shouldExit: shouldExit
@@ -164,7 +164,7 @@ function handleMessage(socket, message) {
workerInfo.lastHeartbeat = Date.now();
}
} else {
sendResponse(socket, { error: '未注册的工具进程连接' });
sendResponse(socket, message, { error: '未注册的工具进程连接' });
}
return;
}
@@ -177,8 +177,19 @@ function handleMessage(socket, message) {
switch (message.type) {
case 'start-worker':
startWorker(message.workerId);
sendResponse(socket, {
const {
workerId,
command,
args,
env
} = message
startWorker({
workerId,
command,
args,
env
});
sendResponse(socket, message, {
success: true,
message: `工具进程 ${message.workerId} 已启动`,
workerId: message.workerId
@@ -187,7 +198,7 @@ function handleMessage(socket, message) {
case 'stop-worker':
stopWorker(message.workerId);
sendResponse(socket, {
sendResponse(socket, message, {
success: true,
message: `工具进程 ${message.workerId} 已停止`,
workerId: message.workerId
@@ -196,7 +207,7 @@ function handleMessage(socket, message) {
case 'get-status':
const status = getWorkersStatus();
sendResponse(socket, {
sendResponse(socket, message, {
success: true,
type: 'status',
workers: status
@@ -204,36 +215,26 @@ function handleMessage(socket, message) {
break;
default:
sendResponse(socket, { error: '未知的消息类型' });
sendResponse(socket, message, { error: '未知的消息类型' });
}
}
// 启动工具进程
function startWorker(workerId, restartCount = 0) {
function startWorker({ workerId, command, args, env }, restartCount = 0) {
if (workers.has(workerId)) {
console.log(`工具进程 ${workerId} 已在运行`);
return;
}
console.log(`启动工具进程: ${workerId}${restartCount > 0 ? ` (重启第${restartCount}次)` : ''}`);
// 使用 Electron 可执行程序路径,从环境变量获取,如果没有则回退到 node
const electronPath = process.env.ELECTRON_EXEC_PATH || 'node';
console.log(`使用执行程序路径: ${electronPath}`);
// 添加参数使工具进程在后台运行,不显示 UI
const workerProcess = spawn(electronPath, [
'--no-sandbox',
'--disable-gpu',
'--disable-dev-shm-usage',
path.join(__dirname, 'worker.js'),
`--worker-id=${workerId}`,
`--restart-count=${restartCount.toString()}`
], {
const workerProcess = spawn(command, args, {
stdio: ['ignore', 'pipe', 'pipe'],
env: {
...process.env,
ELECTRON_EXEC_PATH: electronPath // 继续传递给子进程(如果需要)
...env,
GEEKGEEKRUND_WORKER_ID: workerId,
GEEKGEEKRUND_RESTART_COUNT: restartCount.toString(),
}
});
@@ -281,7 +282,7 @@ function startWorker(workerId, restartCount = 0) {
setTimeout(() => {
// 再次检查确保worker不在停止列表中且当前没有运行
if (!workers.has(workerId) && !stoppedWorkers.has(workerId)) {
startWorker(workerId, restartCount);
startWorker({ workerId, command, args, env }, restartCount);
} else if (stoppedWorkers.has(workerId)) {
console.log(`工具进程 ${workerId} 在重启前被标记为停止,取消重启`);
// 从停止列表中移除,因为已经处理完毕
@@ -302,13 +303,20 @@ function startWorker(workerId, restartCount = 0) {
}
});
workerProcess.on('error', (err) => {
console.log(err)
})
workers.set(workerId, {
process: workerProcess,
status: 'running',
startTime: Date.now(),
restartCount, // 使用传入的重启次数
socket: null, // 工具进程的TCP连接稍后由工具进程注册
lastHeartbeat: null
lastHeartbeat: null,
command,
env,
workerId,
});
// 定期发送状态更新
@@ -385,7 +393,7 @@ function broadcastToGUI(message) {
guiClients.forEach(socket => {
if (!socket.destroyed) {
try {
sendResponse(socket, message);
sendResponse(socket, null, message);
} catch (e) {
console.error('广播消息失败:', e);
guiClients.delete(socket);
@@ -395,9 +403,12 @@ function broadcastToGUI(message) {
}
// 发送响应
function sendResponse(socket, response) {
function sendResponse(socket, request, response) {
try {
socket.write(JSON.stringify(response) + '\n');
socket.write(JSON.stringify({
...response,
_callbackUuid: request?._callbackUuid
}) + '\n');
} catch (e) {
console.error('发送响应失败:', e);
}

View File

@@ -7,7 +7,7 @@ import {
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
import * as fs from 'fs'
import { pipeWriteRegardlessError } from '../utils/pipe'
// import { pipeWriteRegardlessError } from '../utils/pipe'
import { getAnyAvailablePuppeteerExecutable } from '../CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable'
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../../common/enums/auto-start-chat'
@@ -16,6 +16,7 @@ import attachListenerForKillSelfOnParentExited from '../../utils/attachListenerF
import SqlitePluginModule from '@geekgeekrun/sqlite-plugin'
import gtag from '../../utils/gtag'
import GtagPlugin from '../../utils/gtag/GtagPlugin'
import { connectToDaemon } from '../OPEN_SETTING_WINDOW/connect-to-daemon'
const { default: SqlitePlugin } = SqlitePluginModule
const rerunInterval = (() => {
@@ -55,20 +56,20 @@ const runAutoChat = async () => {
} catch {
console.warn('pipe is not available')
}
pipeWriteRegardlessError(
pipe,
JSON.stringify({
type: 'INITIALIZE_PUPPETEER'
}) + '\r\n'
)
// pipeWriteRegardlessError(
// pipe,
// JSON.stringify({
// type: 'INITIALIZE_PUPPETEER'
// }) + '\r\n'
// )
try {
await initPuppeteer()
pipeWriteRegardlessError(
pipe,
JSON.stringify({
type: 'PUPPETEER_INITIALIZE_SUCCESSFULLY'
}) + '\r\n'
)
// pipeWriteRegardlessError(
// pipe,
// JSON.stringify({
// type: 'PUPPETEER_INITIALIZE_SUCCESSFULLY'
// }) + '\r\n'
// )
} catch (err) {
console.error(err)
app.exit(AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE)
@@ -101,20 +102,20 @@ const runAutoChat = async () => {
initPlugins(hooks)
gtag('run_auto_chat_with_boss_main_ready')
pipeWriteRegardlessError(
pipe,
JSON.stringify({
type: 'GEEK_AUTO_START_CHAT_WITH_BOSS_STARTED' //geek-auto-start-chat-with-boss-started
}) + '\r\n'
)
// pipeWriteRegardlessError(
// pipe,
// JSON.stringify({
// type: 'GEEK_AUTO_START_CHAT_WITH_BOSS_STARTED' //geek-auto-start-chat-with-boss-started
// }) + '\r\n'
// )
autoStartChatEventBus.once('LOGIN_STATUS_INVALID', () => {
pipeWriteRegardlessError(
pipe,
JSON.stringify({
type: 'LOGIN_STATUS_INVALID' //geek-auto-start-chat-with-boss-started
}) + '\r\n'
)
// pipeWriteRegardlessError(
// pipe,
// JSON.stringify({
// type: 'LOGIN_STATUS_INVALID' //geek-auto-start-chat-with-boss-started
// }) + '\r\n'
// )
})
while (![isParentProcessDisconnect].includes(true)) {
@@ -151,13 +152,14 @@ const runAutoChat = async () => {
}
export const waitForProcessHandShakeAndRunAutoChat = () => {
let pipe: null | fs.WriteStream = null
try {
pipe = fs.createWriteStream(null, { fd: 3 })
} catch {
console.error('pipe is not available')
app.exit(1)
}
// let pipe: null | fs.WriteStream = null
// try {
// pipe = fs.createWriteStream(null, { fd: 3 })
// } catch {
// console.error('pipe is not available')
// app.exit(1)
// }
connectToDaemon()
runAutoChat()
}

View File

@@ -1,194 +0,0 @@
const { app, ipcMain } = require('electron');
const path = require('path');
const { spawn } = require('child_process');
const net = require('net');
const split2 = require('split2');
const isUiDev = process.env.NODE_ENV === 'development'
const DAEMON_PORT = 12345;
export function launchDaemon() {
let daemonProcess = null;
// 所有窗口关闭时
app.on('window-all-closed', () => {
// if (process.platform !== 'darwin') {
// 关闭守护进程
if (daemonProcess) {
daemonProcess.kill();
}
// app.quit();
// }
});
// 应用退出前清理
app.on('before-quit', () => {
if (daemonProcess) {
daemonProcess.kill();
}
});
// 启动守护进程
function startDaemon() {
console.log('启动守护进程...');
// 使用 Electron 可执行程序路径,如果没有则回退到 node
const electronPath = process.execPath;
console.log(`使用 Electron 路径: ${electronPath}`);
// 添加参数使守护进程在后台运行,不显示 UI
daemonProcess = spawn(
process.argv[0],
isUiDev
? [process.argv[1], `--mode=launchDaemon`]
: [`--mode=launchDaemon`],
{
stdio: ['ignore', 'pipe', 'pipe'],
detached: false,
env: {
...process.env,
}
}
)
// daemonProcess = spawn(electronPath, [
// '--no-sandbox',
// '--disable-dev-shm-usage',
// path.join(__dirname, 'daemon.js')
// ], {
// stdio: ['ignore', 'pipe', 'pipe'],
// detached: false,
// env: {
// ...process.env,
// ELECTRON_EXEC_PATH: electronPath // 传递给守护进程,用于启动 worker
// }
// });
daemonProcess.stdout.on('data', (data) => {
console.log(`守护进程输出: ${data}`);
});
daemonProcess.stderr.on('data', (data) => {
console.error(`守护进程错误: ${data}`);
});
daemonProcess.on('exit', (code) => {
console.log(`守护进程退出,代码: ${code}`);
// 如果守护进程意外退出,尝试重启
if (code !== 0) {
setTimeout(() => {
console.log('尝试重启守护进程...');
startDaemon();
}, 2000);
}
});
// 等待守护进程启动后连接
setTimeout(() => {
connectToDaemon();
}, 1000);
}
// 应用准备就绪
return app.whenReady().then(() => {
startDaemon();
});
}
export function connectToDaemon() {
let daemonClient = null;
// 连接到守护进程
function _connectToDaemon() {
daemonClient = new net.Socket();
daemonClient.connect(DAEMON_PORT, 'localhost', () => {
console.log('已连接到守护进程');
// 通知渲染进程连接成功
// if (mainWindow) {
// mainWindow.webContents.send('daemon-connected');
// }
});
// 使用 split2 按行分割流式数据,处理 JSONL 格式(每行一个 JSON
const splitStream = split2();
daemonClient.pipe(splitStream).on('data', (line) => {
const trimmedLine = line.toString().trim();
if (!trimmedLine) {
return; // 跳过空行
}
try {
const message = JSON.parse(trimmedLine);
console.log('收到守护进程消息:', message);
// 转发消息到渲染进程
// if (mainWindow) {
// mainWindow.webContents.send('daemon-message', message);
// }
} catch (parseError) {
console.error('解析守护进程消息失败:', parseError.message);
console.error('原始数据:', trimmedLine.substring(0, 100));
}
});
splitStream.on('error', (err) => {
console.error('split2 流处理错误:', err);
});
daemonClient.on('error', (err) => {
console.error('守护进程连接错误:', err);
// 尝试重连
setTimeout(() => {
if (daemonClient.destroyed) {
_connectToDaemon();
}
}, 2000);
});
daemonClient.on('close', () => {
console.log('守护进程连接已关闭');
// 尝试重连
setTimeout(() => {
_connectToDaemon();
}, 2000);
});
}
// 向守护进程发送消息
function sendToDaemon(message) {
if (daemonClient && !daemonClient.destroyed) {
daemonClient.write(JSON.stringify(message) + '\n');
} else {
console.error('守护进程未连接');
}
}
// IPC处理从渲染进程接收消息并转发到守护进程
ipcMain.on('send-to-daemon', (event, message) => {
sendToDaemon(message);
});
// IPC处理启动工具进程
ipcMain.on('start-worker', (event, workerId) => {
sendToDaemon({ type: 'start-worker', workerId });
});
// IPC处理停止工具进程
ipcMain.on('stop-worker', (event, workerId) => {
sendToDaemon({ type: 'stop-worker', workerId });
});
// IPC处理获取所有工具进程状态
ipcMain.on('get-workers-status', () => {
sendToDaemon({ type: 'get-status' });
});
app.on('window-all-closed', () => {
if (daemonClient) {
daemonClient.destroy();
}
});
app.on('before-quit', () => {
if (daemonClient) {
daemonClient.destroy();
}
});
_connectToDaemon()
}

View File

@@ -0,0 +1,145 @@
import { randomUUID } from "node:crypto";
import { DAEMON_PORT } from "./daemon-config";
const net = require('net');
const split2 = require('split2');
const { app } = require('electron');
let daemonClient = null;
const waitForCallbackTaskMap = new Map()
// 连接到守护进程
export function connectToDaemon() {
daemonClient = new net.Socket();
daemonClient.connect(DAEMON_PORT, 'localhost', () => {
console.log('已连接到守护进程');
// 通知渲染进程连接成功
// if (mainWindow) {
// mainWindow.webContents.send('daemon-connected');
// }
});
// 使用 split2 按行分割流式数据,处理 JSONL 格式(每行一个 JSON
const splitStream = split2();
daemonClient.pipe(splitStream).on('data', (line) => {
const trimmedLine = line.toString().trim();
if (!trimmedLine) {
return; // 跳过空行
}
try {
const message = JSON.parse(trimmedLine);
console.log('收到守护进程消息:', message);
if (
message._callbackUuid
) {
const callbackInfo = waitForCallbackTaskMap.get(message._callbackUuid)
if (callbackInfo) {
const isError = message._isError
if (isError) {
callbackInfo.reject(message)
}
else {
callbackInfo.resolve(message)
}
waitForCallbackTaskMap.delete(message._callbackUuid)
}
}
// 转发消息到渲染进程
// if (mainWindow) {
// mainWindow.webContents.send('daemon-message', message);
// }
} catch (parseError) {
console.error('解析守护进程消息失败:', parseError.message);
console.error('原始数据:', trimmedLine.substring(0, 100));
}
});
splitStream.on('error', (err) => {
console.error('split2 流处理错误:', err);
});
daemonClient.on('error', (err) => {
console.error('守护进程连接错误:', err);
// 尝试重连
setTimeout(() => {
if (daemonClient.destroyed) {
connectToDaemon();
}
}, 2000);
});
daemonClient.on('close', () => {
console.log('守护进程连接已关闭');
// 尝试重连
setTimeout(() => {
connectToDaemon();
}, 2000);
});
}
// 向守护进程发送消息
export function sendToDaemon(message, {
needCallback = false,
timeout = undefined
} = {}) {
const _callbackUuid = randomUUID()
if (daemonClient && !daemonClient.destroyed) {
daemonClient.write(JSON.stringify({
...message,
_callbackUuid
}) + '\n');
if (needCallback) {
let resolve, reject
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve
reject = _reject
})
waitForCallbackTaskMap.set(_callbackUuid, { resolve, reject })
promise.finally(() => waitForCallbackTaskMap.delete(_callbackUuid))
let timeoutTimer
if (!isNaN(parseInt(timeout))) {
timeoutTimer = setTimeout(() => {
reject(new Error(`Callback timeout after ${timeout}ms`))
}, timeout)
}
promise.finally(() => {
clearTimeout(timeoutTimer)
})
return promise
}
} else {
console.error('守护进程未连接');
}
return undefined
}
// // IPC处理从渲染进程接收消息并转发到守护进程
// ipcMain.on('send-to-daemon', (event, message) => {
// sendToDaemon(message);
// });
// // IPC处理启动工具进程
// ipcMain.on('start-worker', (event, { workerId, command, args, env }) => {
// sendToDaemon({ type: 'start-worker', workerId, command, args, env });
// });
// // IPC处理停止工具进程
// ipcMain.on('stop-worker', (event, workerId) => {
// sendToDaemon({ type: 'stop-worker', workerId });
// });
// // IPC处理获取所有工具进程状态
// ipcMain.on('get-workers-status', () => {
// sendToDaemon({ type: 'get-status' });
// });
app.on('window-all-closed', () => {
if (daemonClient) {
daemonClient.destroy();
}
});
app.on('before-quit', () => {
if (daemonClient) {
daemonClient.destroy();
}
});

View File

@@ -0,0 +1 @@
export const DAEMON_PORT = 12345;

View File

@@ -5,7 +5,8 @@ import './app-menu'
import initIpc from './ipc'
import gtag from '../../utils/gtag'
import initPublicIpc from '../../utils/initPublicIpc'
import { connectToDaemon, launchDaemon } from './attach-daemon'
import { launchDaemon } from './launch-daemon'
import { connectToDaemon } from './connect-to-daemon'
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
export function openSettingWindow() {
// TODO: singleton lock; how can we check if there is another process should run as singleton with arguments?

View File

@@ -52,6 +52,7 @@ import {
import { RequestSceneEnum } from '../../../features/llm-request-log'
import { checkUpdateForUi } from '../../../features/updater'
import gtag from '../../../utils/gtag'
import { sendToDaemon } from '../connect-to-daemon'
export default function initIpc() {
ipcMain.handle('fetch-config-file-content', async () => {
@@ -200,17 +201,9 @@ export default function initIpc() {
// const currentExecutablePath = app.getPath('exe')
// console.log(currentExecutablePath)
ipcMain.handle('prepare-run-geek-auto-start-chat-with-boss', async () => {
mainWindow?.webContents.send('locating-puppeteer-executable')
const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable()
if (!puppeteerExecutable) {
return Promise.reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')
}
mainWindow?.webContents.send('puppeteer-executable-is-located')
})
let subProcessOfPuppeteer: ChildProcess | null = null
ipcMain.handle('run-geek-auto-start-chat-with-boss', async () => {
ipcMain.handle('run-geek-auto-start-chat-with-boss', async (ev) => {
if (subProcessOfPuppeteer) {
return
}
@@ -222,49 +215,79 @@ export default function initIpc() {
...process.env,
PUPPETEER_EXECUTABLE_PATH: puppeteerExecutable.executablePath
}
subProcessOfPuppeteer = childProcess.spawn(
process.argv[0],
[
process.argv[1],
`--mode=geekAutoStartWithBossDaemon`,
`--mode-to-daemon=geekAutoStartWithBossMain`
],
// ipcMain.emit(
// 'start-worker',
// ev,
// {
// workerId: 'geekAutoStartWithBossMain',
// command: process.argv[0],
// args: [
// process.argv[1],
// `--mode=geekAutoStartWithBossMain`
// ],
// env: subProcessEnv
// }
// )
await sendToDaemon(
{
env: subProcessEnv,
stdio: ['inherit', 'inherit', 'inherit', 'pipe', 'ipc']
type: 'start-worker',
workerId: 'geekAutoStartWithBossMain',
command: process.argv[0],
args: [
process.argv[1],
`--mode=geekAutoStartWithBossMain`
],
env: subProcessEnv
},
{
needCallback: true
}
)
// console.log(subProcessOfPuppeteer)
return new Promise((resolve, reject) => {
subProcessOfPuppeteer!.stdio[3]!.pipe(JSONStream.parse()).on('data', async (raw) => {
const data = raw
switch (data.type) {
case 'GEEK_AUTO_START_CHAT_WITH_BOSS_STARTED': {
resolve(data)
break
}
case 'LOGIN_STATUS_INVALID': {
await sleep(500)
mainWindow?.webContents.send('check-boss-zhipin-cookie-file')
return
}
default: {
return
}
}
})
subProcessOfPuppeteer!.once('exit', (exitCode) => {
subProcessOfPuppeteer = null
if (exitCode === AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE) {
// means cannot find downloaded puppeteer
reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')
} else {
mainWindow?.webContents.send('geek-auto-start-chat-with-boss-stopped')
}
})
})
// TODO:
// subProcessOfPuppeteer = childProcess.spawn(
// process.argv[0],
// [
// process.argv[1],
// `--mode=geekAutoStartWithBossDaemon`,
// `--mode-to-daemon=geekAutoStartWithBossMain`
// ],
// {
// env: subProcessEnv,
// stdio: ['inherit', 'inherit', 'inherit', 'pipe', 'ipc']
// }
// )
// // console.log(subProcessOfPuppeteer)
// return new Promise((resolve, reject) => {
// subProcessOfPuppeteer!.stdio[3]!.pipe(JSONStream.parse()).on('data', async (raw) => {
// const data = raw
// switch (data.type) {
// case 'GEEK_AUTO_START_CHAT_WITH_BOSS_STARTED': {
// resolve(data)
// break
// }
// case 'LOGIN_STATUS_INVALID': {
// await sleep(500)
// mainWindow?.webContents.send('check-boss-zhipin-cookie-file')
// return
// }
// default: {
// return
// }
// }
// })
// subProcessOfPuppeteer!.once('exit', (exitCode) => {
// subProcessOfPuppeteer = null
// if (exitCode === AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE) {
// // means cannot find downloaded puppeteer
// reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')
// } else {
// mainWindow?.webContents.send('geek-auto-start-chat-with-boss-stopped')
// }
// })
// })
// // TODO:
})
ipcMain.handle('run-read-no-reply-auto-reminder', async () => {

View File

@@ -0,0 +1,94 @@
import { connectToDaemon } from "./connect-to-daemon";
const { app } = require('electron');
const { spawn } = require('child_process');
const isUiDev = process.env.NODE_ENV === 'development'
export function launchDaemon() {
let daemonProcess = null;
// 所有窗口关闭时
app.on('window-all-closed', () => {
// if (process.platform !== 'darwin') {
// 关闭守护进程
if (daemonProcess) {
daemonProcess.kill();
}
// app.quit();
// }
});
// 应用退出前清理
app.on('before-quit', () => {
if (daemonProcess) {
daemonProcess.kill();
}
});
// 启动守护进程
function startDaemon() {
console.log('启动守护进程...');
// 使用 Electron 可执行程序路径,如果没有则回退到 node
const electronPath = process.execPath;
console.log(`使用 Electron 路径: ${electronPath}`);
// 添加参数使守护进程在后台运行,不显示 UI
daemonProcess = spawn(
process.argv[0],
isUiDev
? [process.argv[1], `--mode=launchDaemon`]
: [`--mode=launchDaemon`],
{
stdio: ['ignore', 'pipe', 'pipe'],
detached: false,
env: {
...process.env,
}
}
)
// daemonProcess = spawn(electronPath, [
// '--no-sandbox',
// '--disable-dev-shm-usage',
// path.join(__dirname, 'daemon.js')
// ], {
// stdio: ['ignore', 'pipe', 'pipe'],
// detached: false,
// env: {
// ...process.env,
// ELECTRON_EXEC_PATH: electronPath // 传递给守护进程,用于启动 worker
// }
// });
daemonProcess.stdout.on('data', (data) => {
console.log(`守护进程输出: ${data}`);
});
daemonProcess.stderr.on('data', (data) => {
console.error(`守护进程错误: ${data}`);
});
daemonProcess.on('exit', (code) => {
console.log(`守护进程退出,代码: ${code}`);
// 如果守护进程意外退出,尝试重启
if (code !== 0) {
setTimeout(() => {
console.log('尝试重启守护进程...');
startDaemon();
}, 2000);
}
});
// 等待守护进程启动后连接
setTimeout(() => {
connectToDaemon();
}, 1000);
}
// 应用准备就绪
return app.whenReady().then(() => {
startDaemon();
});
}

View File

@@ -9,18 +9,7 @@ const route = useRoute()
const currentStatus = ref('')
onMounted(() => {
const promise = electron.ipcRenderer.invoke('prepare-run-geek-auto-start-chat-with-boss')
const handleLocatingPuppeteerExecutable = () => {
currentStatus.value = 'locating-puppeteer-executable'
}
electron.ipcRenderer.once('locating-puppeteer-executable', handleLocatingPuppeteerExecutable)
onUnmounted(() => {
electron.ipcRenderer.removeListener(
'locating-puppeteer-executable',
handleLocatingPuppeteerExecutable
)
})
const promise = Promise.resolve()
promise
.then(() => {
switch (route.query.flow) {