From 9639151fddc0e8b7289cbc450b2c7cfdf3293033 Mon Sep 17 00:00:00 2001 From: geekgeekrun Date: Sat, 17 Jan 2026 15:37:54 +0800 Subject: [PATCH] make task can be run standalone via arg --- packages/pm/daemon.js | 21 ++++-- packages/ui/package.json | 4 - packages/ui/src/main/features/run-common.ts | 41 +++++++++++ .../OPEN_SETTING_WINDOW/connect-to-daemon.ts | 11 +++ .../flow/OPEN_SETTING_WINDOW/ipc/index.ts | 73 +++---------------- .../flow/OPEN_SETTING_WINDOW/launch-daemon.ts | 19 ----- packages/ui/src/main/index.ts | 26 ++++++- 7 files changed, 102 insertions(+), 93 deletions(-) create mode 100644 packages/ui/src/main/features/run-common.ts diff --git a/packages/pm/daemon.js b/packages/pm/daemon.js index 7e9f57f..cbecb47 100644 --- a/packages/pm/daemon.js +++ b/packages/pm/daemon.js @@ -30,6 +30,7 @@ const path = require('path'); const split2 = require('split2'); const fs = require('fs') const { tmpdir } = require('os') +const { randomUUID } = require('crypto') const ipcWritePipe = fs.createWriteStream(null, { fd: 3 }) let ipcSocketName = process.env.GEEKGEEKRUND_PIPE_NAME @@ -43,7 +44,9 @@ if (process.platform === 'win32') { } else { ipcSocketPath = path.join(tmpdir(), `${ipcSocketName}.sock`) - fs.writeFileSync(ipcSocketPath, '') + if (!fs.existsSync(ipcSocketPath)) { + fs.writeFileSync(ipcSocketPath, '') + } // 设置权限(Unix) fs.chmodSync( ipcSocketPath, @@ -259,11 +262,11 @@ function startWorker({ workerId, command, args, env }, restartCount = 0) { console.log(`工具进程 ${workerId} 已在运行`); return; } - const noRestartExitCodeSet = new Set([0]); - (env.GEEKGEEKRUND_NO_RESTART_EXIT_CODE ?? '') + const noAutoRestartExitCodeSet = new Set([0]); + (env.GEEKGEEKRUND_NO_AUTO_RESTART_EXIT_CODE ?? '') .split(',') .map(n => parseInt(n)) - .forEach(n => noRestartExitCodeSet.add(n)) + .forEach(n => noAutoRestartExitCodeSet.add(n)) console.log(`启动工具进程: ${workerId}${restartCount > 0 ? ` (重启第${restartCount}次)` : ''}`); // 添加参数使工具进程在后台运行,不显示 UI @@ -297,7 +300,7 @@ function startWorker({ workerId, command, args, env }, restartCount = 0) { workerInfo.socket.destroy(); } - const shouldRestart = !noRestartExitCodeSet.has(code) // && code !== null; + const shouldRestart = !noAutoRestartExitCodeSet.has(code) // && code !== null; // 使用当前的 restartCount 加1,而不是从 workerInfo 中取(因为可能已经被删除) const restartCount = (workerInfo.restartCount || 0) + 1; @@ -509,3 +512,11 @@ process.on('SIGINT', () => { process.exit(0); }); }); + +// 捕获未处理的 EPIPE 错误 +process.on('uncaughtException', (err) => { + if (err.code === 'EPIPE' || err.code === 'ERR_STREAM_DESTROYED') { + return + } + throw err +}); \ No newline at end of file diff --git a/packages/ui/package.json b/packages/ui/package.json index 75c369e..4bfc0a7 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -9,10 +9,6 @@ "scripts": { "start": "node scripts/run-build-sqlite-plugin.mjs && electron-vite preview", "dev": "node scripts/run-build-sqlite-plugin.mjs && electron-vite dev", - "dev:geek-auto-start-chat-with-boss-main-only": "cross-env MAIN_BOSSGEEKGO_UI_RUN_MODE=geekAutoStartWithBossMain pnpm run dev", - "dev:geek-auto-start-chat-with-boss-daemon-only": "cross-env MAIN_BOSSGEEKGO_UI_RUN_MODE=geekAutoStartWithBossDaemon pnpm run dev", - "dev:check-and-download-dependencies-for-init-only": "cross-env MAIN_BOSSGEEKGO_UI_RUN_MODE=checkAndDownloadDependenciesForInit pnpm run dev", - "dev:launch-bosszhipin-login-page-with-preload-extension-only": "cross-env MAIN_BOSSGEEKGO_UI_RUN_MODE=launchBossZhipinLoginPageWithPreloadExtension pnpm run dev", "build": "node scripts/run-build-sqlite-plugin.mjs && electron-vite build", "format": "prettier --write .", "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix", diff --git a/packages/ui/src/main/features/run-common.ts b/packages/ui/src/main/features/run-common.ts new file mode 100644 index 0000000..452e584 --- /dev/null +++ b/packages/ui/src/main/features/run-common.ts @@ -0,0 +1,41 @@ +import { AUTO_CHAT_ERROR_EXIT_CODE } from "../../common/enums/auto-start-chat" +import { getAnyAvailablePuppeteerExecutable } from "../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable" +import { sendToDaemon } from "../flow/OPEN_SETTING_WINDOW/connect-to-daemon" +import { saveAndGetCurrentRunRecord } from "../flow/OPEN_SETTING_WINDOW/utils/db" + +export async function runCommon ({ mode }) { + const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable() + if (!puppeteerExecutable) { + return Promise.reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES') + } + const currentRunRecord = (await saveAndGetCurrentRunRecord())?.data + const subProcessEnv = { + ...process.env, + PUPPETEER_EXECUTABLE_PATH: puppeteerExecutable.executablePath, + GEEKGEEKRUND_NO_AUTO_RESTART_EXIT_CODE: [ + AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE, + AUTO_CHAT_ERROR_EXIT_CODE.LOGIN_STATUS_INVALID, + 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}` + ] + await sendToDaemon( + { + type: 'start-worker', + workerId: mode, + command: process.argv[0], + args, + env: subProcessEnv + }, + { + needCallback: true + } + ) +} \ No newline at end of file diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/connect-to-daemon.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/connect-to-daemon.ts index 7eb4303..f2749a2 100644 --- a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/connect-to-daemon.ts +++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/connect-to-daemon.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { EventEmitter } from "node:events"; import { tmpdir } from "node:os"; import path from "node:path"; +import fs from "node:fs"; const net = require('net'); const split2 = require('split2'); @@ -20,6 +21,16 @@ export async function connectToDaemon() { const ipcSocketPath = process.platform === 'win32' ? `\\\\.\\pipe\\${ipcSocketName}` : path.join(tmpdir(), `${ipcSocketName}.sock`) + if (process.platform !== 'win32' ) { + if (!fs.existsSync(ipcSocketPath)) { + fs.writeFileSync(ipcSocketPath, '') + } + // 设置权限(Unix) + fs.chmodSync( + ipcSocketPath, + 0o777 + ) + } daemonClient.connect(ipcSocketPath, 'localhost', () => { isConnected = true console.log('已连接到守护进程'); diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts index 1c85fb2..3018643 100644 --- a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts +++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts @@ -53,6 +53,7 @@ import { RequestSceneEnum } from '../../../features/llm-request-log' import { checkUpdateForUi } from '../../../features/updater' import gtag from '../../../utils/gtag' import { daemonEE, sendToDaemon } from '../connect-to-daemon' +import { runCommon } from '../../../features/run-common' export default function initIpc() { ipcMain.handle('fetch-config-file-content', async () => { @@ -200,38 +201,10 @@ export default function initIpc() { }) ipcMain.handle('run-geek-auto-start-chat-with-boss', async (ev) => { - const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable() - if (!puppeteerExecutable) { - return Promise.reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES') - } - const currentRunRecord = (await saveAndGetCurrentRunRecord())?.data - const subProcessEnv = { - ...process.env, - PUPPETEER_EXECUTABLE_PATH: puppeteerExecutable.executablePath, - GEEKGEEKRUND_NO_RESTART_EXIT_CODE: [ - AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE, - AUTO_CHAT_ERROR_EXIT_CODE.LOGIN_STATUS_INVALID, - AUTO_CHAT_ERROR_EXIT_CODE.LLM_UNAVAILABLE - ].join(',') - } - await sendToDaemon( - { - type: 'start-worker', - workerId: 'geekAutoStartWithBossMain', - command: process.argv[0], - args: [ - process.argv[1], - `--mode=geekAutoStartWithBossMain`, - `--run-record-id=${currentRunRecord?.id || 0}` - ], - env: subProcessEnv - }, - { - needCallback: true - } - ) + const mode = 'geekAutoStartWithBossMain' + await runCommon({ mode }) daemonEE.on('message', function handler (message) { - if (message.workerId !== 'geekAutoStartWithBossMain') { + if (message.workerId !== mode) { return } if (message.type === 'worker-exited') { @@ -256,38 +229,10 @@ export default function initIpc() { }) ipcMain.handle('run-read-no-reply-auto-reminder', async () => { - const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable() - if (!puppeteerExecutable) { - return Promise.reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES') - } - const currentRunRecord = (await saveAndGetCurrentRunRecord())?.data - const subProcessEnv = { - ...process.env, - PUPPETEER_EXECUTABLE_PATH: puppeteerExecutable.executablePath, - GEEKGEEKRUND_NO_RESTART_EXIT_CODE: [ - AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE, - AUTO_CHAT_ERROR_EXIT_CODE.LOGIN_STATUS_INVALID, - AUTO_CHAT_ERROR_EXIT_CODE.LLM_UNAVAILABLE - ].join(',') - } - await sendToDaemon( - { - type: 'start-worker', - workerId: 'readNoReplyAutoReminder', - command: process.argv[0], - args: [ - process.argv[1], - `--mode=readNoReplyAutoReminder`, - `--run-record-id=${currentRunRecord?.id || 0}` - ], - env: subProcessEnv - }, - { - needCallback: true - } - ) + const mode = 'readNoReplyAutoReminderMain' + await runCommon({ mode }) daemonEE.on('message', function handler (message) { - if (message.workerId !== 'readNoReplyAutoReminder') { + if (message.workerId !== mode) { return } if (message.type === 'worker-exited') { @@ -400,7 +345,7 @@ export default function initIpc() { mainWindow?.webContents.send('read-no-reply-auto-reminder-stopping') const p = new Promise(resolve => { daemonEE.on('message', function handler (message) { - if (message.workerId !== 'readNoReplyAutoReminder') { + if (message.workerId !== 'readNoReplyAutoReminderMain') { return } if (message.type === 'worker-exited') { @@ -412,7 +357,7 @@ export default function initIpc() { await sendToDaemon( { type: 'stop-worker', - workerId: 'readNoReplyAutoReminder', + workerId: 'readNoReplyAutoReminderMain', }, { needCallback: true diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/launch-daemon.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/launch-daemon.ts index e4ae53d..950c33a 100644 --- a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/launch-daemon.ts +++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/launch-daemon.ts @@ -1,4 +1,3 @@ -import { randomUUID } from "crypto"; const { app } = require('electron'); const { spawn } = require('child_process'); @@ -28,11 +27,6 @@ export function launchDaemon() { // 启动守护进程 async function startDaemon() { console.log('启动守护进程...'); - process.env.GEEKGEEKRUND_PIPE_NAME = `geekgeekrun-d_${randomUUID()}` - // 使用 Electron 可执行程序路径,如果没有则回退到 node - const electronPath = process.execPath; - console.log(`使用 Electron 路径: ${electronPath}`); - // 添加参数使守护进程在后台运行,不显示 UI daemonProcess = spawn( process.argv[0], @@ -48,19 +42,6 @@ export function launchDaemon() { } ) - // 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}`); }); diff --git a/packages/ui/src/main/index.ts b/packages/ui/src/main/index.ts index 834464a..c6441a7 100644 --- a/packages/ui/src/main/index.ts +++ b/packages/ui/src/main/index.ts @@ -1,4 +1,8 @@ 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'; const isUiDev = process.env.NODE_ENV === 'development' const commandlineArgs = minimist(isUiDev ? process.argv.slice(2) : process.argv.slice(1)) console.log(commandlineArgs) @@ -7,6 +11,7 @@ const runMode = commandlineArgs['mode']; ;(async () => { switch (runMode) { + // #region internal use case 'geekAutoStartWithBossMain': { const { waitForProcessHandShakeAndRunAutoChat } = await import( './flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index' @@ -33,7 +38,7 @@ const runMode = commandlineArgs['mode']; launchBossSite() break } - case 'readNoReplyAutoReminder': { + case 'readNoReplyAutoReminderMain': { const { runEntry } = await import('./flow/READ_NO_REPLY_AUTO_REMINDER/index') runEntry() break @@ -42,10 +47,29 @@ const runMode = commandlineArgs['mode']; await import('./flow/LAUNCH_DAEMON') break } + // #endregion + + // #region user entry + case 'geekAutoStartWithBoss': { + process.env.GEEKGEEKRUND_PIPE_NAME = `geekgeekrun-d_${randomUUID()}` + await launchDaemon() + await connectToDaemon() + await runCommon({ mode: 'geekAutoStartWithBossMain' }) + break + } + case 'readNoReplyAutoReminder': { + process.env.GEEKGEEKRUND_PIPE_NAME = `geekgeekrun-d_${randomUUID()}` + await launchDaemon() + await connectToDaemon() + await runCommon({ mode: 'readNoReplyAutoReminderMain' }) + break + } default: { + process.env.GEEKGEEKRUND_PIPE_NAME = `geekgeekrun-d_${randomUUID()}` const { openSettingWindow } = await import('./flow/OPEN_SETTING_WINDOW/index') openSettingWindow() break } + // #region } })()