mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-07-08 22:15:14 +08:00
make daemon keep running in background
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import { AUTO_CHAT_ERROR_EXIT_CODE } from "../../common/enums/auto-start-chat"
|
||||
import { sendToDaemon } from "../flow/OPEN_SETTING_WINDOW/connect-to-daemon"
|
||||
import { saveAndGetCurrentRunRecord } from "../flow/OPEN_SETTING_WINDOW/utils/db"
|
||||
import { app } from 'electron'
|
||||
import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../common/enums/auto-start-chat'
|
||||
import { sendToDaemon } from '../flow/OPEN_SETTING_WINDOW/connect-to-daemon'
|
||||
import { saveAndGetCurrentRunRecord } from '../flow/OPEN_SETTING_WINDOW/utils/db'
|
||||
|
||||
export async function runCommon ({ mode }) {
|
||||
export async function runCommon({ mode }) {
|
||||
app.dock?.hide()
|
||||
const currentRunRecord = (await saveAndGetCurrentRunRecord())?.data
|
||||
const subProcessEnv = {
|
||||
...process.env,
|
||||
@@ -12,14 +14,10 @@ export async function runCommon ({ mode }) {
|
||||
AUTO_CHAT_ERROR_EXIT_CODE.LLM_UNAVAILABLE
|
||||
].join(',')
|
||||
}
|
||||
const args = process.env.NODE_ENV === 'development' ? [
|
||||
process.argv[1],
|
||||
`--mode=${mode}`,
|
||||
`--run-record-id=${currentRunRecord?.id || 0}`
|
||||
] : [
|
||||
`--mode=${mode}`,
|
||||
`--run-record-id=${currentRunRecord?.id || 0}`
|
||||
]
|
||||
const args =
|
||||
process.env.NODE_ENV === 'development'
|
||||
? [process.argv[1], `--mode=${mode}`, `--run-record-id=${currentRunRecord?.id || 0}`]
|
||||
: [`--mode=${mode}`, `--run-record-id=${currentRunRecord?.id || 0}`]
|
||||
await sendToDaemon(
|
||||
{
|
||||
type: 'start-worker',
|
||||
@@ -32,8 +30,15 @@ export async function runCommon ({ mode }) {
|
||||
needCallback: true
|
||||
}
|
||||
)
|
||||
|
||||
;['SIGINT', 'SIGTERM'].forEach((evName) => {
|
||||
process.on(evName, () => {
|
||||
sendToDaemon({
|
||||
type: 'stop-worker',
|
||||
workerId: mode
|
||||
})
|
||||
})
|
||||
})
|
||||
return {
|
||||
runRecordId: currentRunRecord?.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import attachListenerForKillSelfOnParentExited from '../utils/attachListenerForKillSelfOnParentExited'
|
||||
;(async () => {
|
||||
process.once('disconnect', () => {
|
||||
process.exit(0)
|
||||
})
|
||||
attachListenerForKillSelfOnParentExited()
|
||||
await import('@geekgeekrun/pm/daemon.js')
|
||||
})()
|
||||
|
||||
|
||||
@@ -1,40 +1,45 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { tmpdir } from 'node:os'
|
||||
import path from 'node:path'
|
||||
import net from 'node:net'
|
||||
import split2 from 'split2'
|
||||
|
||||
const net = require('net');
|
||||
const split2 = require('split2');
|
||||
|
||||
let daemonClient = null;
|
||||
let daemonClient = null
|
||||
export const daemonEE = new EventEmitter()
|
||||
const waitForCallbackTaskMap = new Map()
|
||||
|
||||
export function getSocketPath(socketName) {
|
||||
const ipcSocketPath =
|
||||
process.platform === 'win32'
|
||||
? `\\\\.\\pipe\\${socketName}`
|
||||
: path.join(tmpdir(), `${socketName}.sock`)
|
||||
return ipcSocketPath
|
||||
}
|
||||
|
||||
// 连接到守护进程
|
||||
export async function connectToDaemon() {
|
||||
daemonClient = new net.Socket();
|
||||
daemonClient = new net.Socket()
|
||||
let isConnected = false
|
||||
await new Promise((resolve, reject) => {
|
||||
const ipcSocketName = process.env.GEEKGEEKRUND_PIPE_NAME
|
||||
const ipcSocketPath = process.platform === 'win32'
|
||||
? `\\\\.\\pipe\\${ipcSocketName}`
|
||||
: path.join(tmpdir(), `${ipcSocketName}.sock`)
|
||||
const ipcSocketPath = getSocketPath(ipcSocketName)
|
||||
daemonClient.connect(ipcSocketPath, 'localhost', () => {
|
||||
isConnected = true
|
||||
console.log('已连接到守护进程');
|
||||
console.log('已连接到守护进程')
|
||||
daemonEE.emit('connect')
|
||||
// 使用 split2 按行分割流式数据,处理 JSONL 格式(每行一个 JSON)
|
||||
const splitStream = split2();
|
||||
const splitStream = split2()
|
||||
daemonClient.pipe(splitStream).on('data', (line) => {
|
||||
const trimmedLine = line.toString().trim();
|
||||
const trimmedLine = line.toString().trim()
|
||||
if (!trimmedLine) {
|
||||
return; // 跳过空行
|
||||
return // 跳过空行
|
||||
}
|
||||
try {
|
||||
const message = JSON.parse(trimmedLine);
|
||||
const message = JSON.parse(trimmedLine)
|
||||
daemonEE.emit('message', message)
|
||||
// FIXME:
|
||||
console.log('收到守护进程消息:', message);
|
||||
// console.log('收到守护进程消息:', message)
|
||||
if (message._callbackUuid) {
|
||||
const callbackInfo = waitForCallbackTaskMap.get(message._callbackUuid)
|
||||
if (callbackInfo) {
|
||||
@@ -52,55 +57,54 @@ export async function connectToDaemon() {
|
||||
// mainWindow.webContents.send('daemon-message', message);
|
||||
// }
|
||||
} catch (parseError) {
|
||||
console.error('解析守护进程消息失败:', parseError.message);
|
||||
console.error('原始数据:', trimmedLine.substring(0, 100));
|
||||
console.error('解析守护进程消息失败:', parseError.message)
|
||||
console.error('原始数据:', trimmedLine.substring(0, 100))
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
splitStream.on('error', (err) => {
|
||||
console.error('split2 流处理错误:', err);
|
||||
});
|
||||
console.error('split2 流处理错误:', err)
|
||||
})
|
||||
|
||||
daemonClient.on('close', () => {
|
||||
if (!isConnected) {
|
||||
return
|
||||
}
|
||||
console.log('守护进程连接已关闭');
|
||||
console.log('守护进程连接已关闭')
|
||||
daemonEE.emit('close')
|
||||
});
|
||||
})
|
||||
|
||||
resolve(true)
|
||||
// 通知渲染进程连接成功
|
||||
// if (mainWindow) {
|
||||
// mainWindow.webContents.send('daemon-connected');
|
||||
// }
|
||||
});
|
||||
})
|
||||
|
||||
daemonClient.on('close', () => {
|
||||
if (isConnected) {
|
||||
return
|
||||
}
|
||||
reject(new Error('连接到守护进程超时'))
|
||||
});
|
||||
})
|
||||
daemonClient.on('error', (err) => {
|
||||
console.error('守护进程连接错误:', err);
|
||||
daemonEE.emit('error', err)
|
||||
console.error('守护进程连接错误:', err)
|
||||
// daemonEE.emit('error', err)
|
||||
reject(err)
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 向守护进程发送消息
|
||||
export function sendToDaemon(message, {
|
||||
needCallback = false,
|
||||
timeout = undefined
|
||||
} = {}) {
|
||||
export function sendToDaemon(message, { needCallback = false, timeout = undefined } = {}) {
|
||||
const _callbackUuid = randomUUID()
|
||||
if (daemonClient && !daemonClient.destroyed) {
|
||||
daemonClient.write(JSON.stringify({
|
||||
...message,
|
||||
_callbackUuid
|
||||
}) + '\n');
|
||||
daemonClient.write(
|
||||
JSON.stringify({
|
||||
...message,
|
||||
_callbackUuid
|
||||
}) + '\n'
|
||||
)
|
||||
if (needCallback) {
|
||||
let resolve, reject
|
||||
const promise = new Promise((_resolve, _reject) => {
|
||||
@@ -121,7 +125,7 @@ export function sendToDaemon(message, {
|
||||
return promise
|
||||
}
|
||||
} else {
|
||||
console.error('守护进程未连接');
|
||||
console.error('守护进程未连接')
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -71,9 +71,6 @@ export function openSettingWindow() {
|
||||
})
|
||||
|
||||
whenReadyPromise.then(async () => {
|
||||
await launchDaemon()
|
||||
await sleep(2000)
|
||||
await connectToDaemon()
|
||||
await sendToDaemon(
|
||||
{
|
||||
type: 'ping'
|
||||
|
||||
@@ -306,7 +306,7 @@ export default function initIpc() {
|
||||
|
||||
ipcMain.handle('stop-geek-auto-start-chat-with-boss', async () => {
|
||||
mainWindow?.webContents.send('geek-auto-start-chat-with-boss-stopping')
|
||||
const p = new Promise(resolve => {
|
||||
const p = new Promise((resolve) => {
|
||||
daemonEE.on('message', function handler (message) {
|
||||
if (message.workerId !== 'geekAutoStartWithBossMain') {
|
||||
return
|
||||
|
||||
@@ -1,54 +1,47 @@
|
||||
const { app } = require('electron');
|
||||
const { spawn } = require('child_process');
|
||||
import { spawn } from 'child_process'
|
||||
import {
|
||||
ensureStorageFileExist,
|
||||
writeStorageFile,
|
||||
readStorageFile
|
||||
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { connectToDaemon } from './connect-to-daemon'
|
||||
|
||||
const isUiDev = process.env.NODE_ENV === 'development'
|
||||
export async function ensureIpcPipeName() {
|
||||
let ipcPipeName = readStorageFile('ipc-pipe-name', { isJson: false })
|
||||
if (!ipcPipeName) {
|
||||
ipcPipeName = `geekgeekrun-d_${randomUUID()}`
|
||||
ensureStorageFileExist()
|
||||
await writeStorageFile('ipc-pipe-name', ipcPipeName, { isJson: false })
|
||||
}
|
||||
process.env.GEEKGEEKRUND_PIPE_NAME = ipcPipeName
|
||||
return ipcPipeName
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
// 启动守护进程
|
||||
export async function launchDaemon() {
|
||||
async function startDaemon() {
|
||||
console.log('启动守护进程...');
|
||||
console.log('启动守护进程...')
|
||||
// 添加参数使守护进程在后台运行,不显示 UI
|
||||
daemonProcess = spawn(
|
||||
const daemonProcess = spawn(
|
||||
process.argv[0],
|
||||
isUiDev
|
||||
? [process.argv[1], `--mode=launchDaemon`]
|
||||
: [`--mode=launchDaemon`],
|
||||
isUiDev ? [process.argv[1], `--mode=launchDaemon`] : [`--mode=launchDaemon`],
|
||||
{
|
||||
stdio: ['ignore', 'pipe', 'pipe', 'pipe'],
|
||||
detached: false,
|
||||
detached: true,
|
||||
env: {
|
||||
...process.env,
|
||||
...process.env
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
daemonProcess.stdout.on('data', (data) => {
|
||||
console.log(`守护进程输出: ${data}`);
|
||||
});
|
||||
console.log(`守护进程输出: ${data}`)
|
||||
})
|
||||
|
||||
daemonProcess.stderr.on('data', (data) => {
|
||||
console.error(`守护进程错误: ${data}`);
|
||||
});
|
||||
console.error(`守护进程错误: ${data}`)
|
||||
})
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
daemonProcess.stdio[3].on('data', (rawData) => {
|
||||
@@ -57,19 +50,20 @@ export function launchDaemon() {
|
||||
data = JSON.parse(rawData.toString())
|
||||
if (data.type === 'DAEMON_READY') {
|
||||
resolve(true)
|
||||
}
|
||||
else if (data.type === 'DAEMON_FATAL') {
|
||||
} else if (data.type === 'DAEMON_FATAL') {
|
||||
reject(new Error(data.error))
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
console.error('', err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 应用准备就绪
|
||||
return app.whenReady().then(() => startDaemon());
|
||||
try {
|
||||
await connectToDaemon()
|
||||
} catch (err) {
|
||||
// 启动守护进程
|
||||
await startDaemon()
|
||||
await connectToDaemon()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import minimist from 'minimist'
|
||||
import { runCommon } from './features/run-common';
|
||||
import { launchDaemon } from './flow/OPEN_SETTING_WINDOW/launch-daemon';
|
||||
import { connectToDaemon } from './flow/OPEN_SETTING_WINDOW/connect-to-daemon';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { ensureIpcPipeName, launchDaemon } from './flow/OPEN_SETTING_WINDOW/launch-daemon';
|
||||
|
||||
// 捕获未处理的 EPIPE 错误
|
||||
process.on('uncaughtException', (err) => {
|
||||
@@ -60,21 +58,20 @@ const runMode = commandlineArgs['mode'];
|
||||
|
||||
// #region user entry
|
||||
case 'geekAutoStartWithBoss': {
|
||||
process.env.GEEKGEEKRUND_PIPE_NAME = `geekgeekrun-d_${randomUUID()}`
|
||||
await ensureIpcPipeName()
|
||||
await launchDaemon()
|
||||
await connectToDaemon()
|
||||
await runCommon({ mode: 'geekAutoStartWithBossMain' })
|
||||
break
|
||||
}
|
||||
case 'readNoReplyAutoReminder': {
|
||||
process.env.GEEKGEEKRUND_PIPE_NAME = `geekgeekrun-d_${randomUUID()}`
|
||||
await ensureIpcPipeName()
|
||||
await launchDaemon()
|
||||
await connectToDaemon()
|
||||
await runCommon({ mode: 'readNoReplyAutoReminderMain' })
|
||||
break
|
||||
}
|
||||
default: {
|
||||
process.env.GEEKGEEKRUND_PIPE_NAME = `geekgeekrun-d_${randomUUID()}`
|
||||
await ensureIpcPipeName()
|
||||
await launchDaemon()
|
||||
const { openSettingWindow } = await import('./flow/OPEN_SETTING_WINDOW/index')
|
||||
openSettingWindow()
|
||||
break
|
||||
|
||||
@@ -64,5 +64,8 @@ export function createMainWindow(): BrowserWindow {
|
||||
mainWindow?.webContents?.send('worker-to-gui-message', message)
|
||||
}
|
||||
})
|
||||
daemonEE.on('error', (err) => {
|
||||
console.log(err)
|
||||
})
|
||||
return mainWindow!
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user