make task can be run standalone via arg

This commit is contained in:
geekgeekrun
2026-01-17 15:37:54 +08:00
parent 4cf6a7e36f
commit 9639151fdd
7 changed files with 102 additions and 93 deletions

View File

@@ -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
});

View File

@@ -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",

View File

@@ -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
}
)
}

View File

@@ -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('已连接到守护进程');

View File

@@ -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

View File

@@ -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}`);
});

View File

@@ -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
}
})()