From a05c73cbdfe2b3282e319c1bc557127d6efef9be Mon Sep 17 00:00:00 2001 From: geekgeekrun Date: Sat, 17 Jan 2026 11:28:47 +0800 Subject: [PATCH] enhance daemon lifecycle logic like connect ensurance --- packages/pm/daemon.js | 33 ++- packages/pm/worker.js | 244 ------------------ .../index.ts | 53 +--- ...HIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION.ts | 6 - packages/ui/src/main/flow/LAUNCH_DAEMON.ts | 5 + .../OPEN_SETTING_WINDOW/connect-to-daemon.ts | 131 +++++----- .../main/flow/OPEN_SETTING_WINDOW/index.ts | 14 +- .../flow/OPEN_SETTING_WINDOW/launch-daemon.ts | 42 ++- .../flow/READ_NO_REPLY_AUTO_REMINDER/index.ts | 9 +- 9 files changed, 149 insertions(+), 388 deletions(-) delete mode 100644 packages/pm/worker.js diff --git a/packages/pm/daemon.js b/packages/pm/daemon.js index 909a022..92adeba 100644 --- a/packages/pm/daemon.js +++ b/packages/pm/daemon.js @@ -28,6 +28,9 @@ const net = require('net'); const { spawn } = require('child_process'); const path = require('path'); const split2 = require('split2'); +const fs = require('fs') + +const ipcWritePipe = fs.createWriteStream(null, { fd: 3 }) const PORT = 12345; const workers = new Map(); // workerId -> { process, status, restartCount, socket, latestScreenshot, latestScreenshotAt } @@ -88,6 +91,13 @@ const server = net.createServer((socket) => { function handleMessage(socket, message) { console.log('收到消息:', message); const _callbackUuid = message._callbackUuid + if (message.type === 'ping') { + sendResponse(socket, _callbackUuid, { + success: true, + message: 'pong' + }); + return + } // 工具进程注册消息 if (message.type === 'worker-register') { @@ -214,9 +224,8 @@ function handleMessage(socket, message) { case 'get-status': const status = getWorkersStatus(); - sendResponse(socket, _callbackUuid, { + sendResponse(socket, _callbackUuid, { success: true, - type: 'status', workers: status }); break; @@ -442,9 +451,23 @@ function sendResponse(socket, _callbackUuid, response) { } // 启动服务器 -server.listen(PORT, () => { - console.log(`守护进程服务器运行在端口 ${PORT}`); -}); +new Promise((resolve, reject) => { + server.once('error', (err) => { + ipcWritePipe.write( + JSON.stringify({ type: 'DAEMON_FATAL', error: err }), + (err) => void err + ) + reject(err) + }) + server.listen(PORT, () => { + console.log(`守护进程服务器运行在端口 ${PORT}`); + ipcWritePipe.write( + JSON.stringify({ type: 'DAEMON_READY' }), + (err) => void err + ) + resolve(true) + }); +}) // 优雅关闭 process.on('SIGTERM', () => { diff --git a/packages/pm/worker.js b/packages/pm/worker.js deleted file mode 100644 index 6bef35d..0000000 --- a/packages/pm/worker.js +++ /dev/null @@ -1,244 +0,0 @@ -// 如果是通过 Electron 运行,禁用 GUI 和 Dock 图标 -if (typeof require !== 'undefined') { - try { - const { app } = require('electron'); - if (app) { - // 隐藏 Dock 图标(macOS) - if (process.platform === 'darwin') { - app.dock?.hide(); - } - - // 防止显示窗口 - app.on('ready', () => { - // 不创建任何窗口,保持后台运行 - }); - - // 防止在没有窗口时退出 - app.on('window-all-closed', (e) => { - e.preventDefault(); - // 不退出应用,保持后台运行 - }); - } - } catch (e) { - // 不在 Electron 环境中,忽略 - } -} - -// 工具进程示例 -const net = require('net'); -const split2 = require('split2'); - -// 解析命令行参数 -let workerId = 'unknown'; -let restartCount = 0; -for (let i = 2; i < process.argv.length; i++) { - const arg = process.argv[i]; - if (arg.startsWith('--worker-id=')) { - workerId = arg.split('=')[1] || 'unknown'; - } else if (arg.startsWith('--restart-count=')) { - restartCount = parseInt(arg.split('=')[1] || '0', 10); - } -} - -console.log(`工具进程 ${workerId} 已启动 (PID: ${process.pid})${restartCount > 0 ? `,这是第${restartCount}次重启` : ''}`); - -const DAEMON_PORT = 12345; -let daemonSocket = null; -let reconnectTimer = null; -let isShuttingDown = false; - -// 连接到守护进程 -function connectToDaemon() { - if (isShuttingDown) return; - - daemonSocket = new net.Socket(); - - daemonSocket.connect(DAEMON_PORT, 'localhost', () => { - console.log(`[工具进程 ${workerId}] 已连接到守护进程`); - - // 注册工具进程连接 - sendToDaemon({ - type: 'worker-register', - workerId: workerId - }); - - // 连接成功后立即检查是否应该退出 - setTimeout(() => { - checkShouldIExit(); - }, 500); - }); - - // 使用 split2 按行分割流式数据,处理 JSONL 格式(每行一个 JSON) - // split2 会自动处理 TCP 分包问题,确保每条完整的消息(以换行符结尾)才会触发 - const splitStream = split2(); - - daemonSocket.pipe(splitStream).on('data', (line) => { - const trimmedLine = line.toString().trim(); - if (!trimmedLine) { - return; // 跳过空行 - } - - try { - const message = JSON.parse(trimmedLine); - handleDaemonMessage(message); - } catch (parseError) { - console.error(`[工具进程 ${workerId}] 解析JSON消息失败:`, parseError.message); - console.error('原始数据:', trimmedLine.substring(0, 100)); // 只打印前100个字符 - } - }); - - splitStream.on('error', (err) => { - console.error(`[工具进程 ${workerId}] split2 流处理错误:`, err); - }); - - daemonSocket.on('error', (err) => { - console.error(`[工具进程 ${workerId}] 守护进程连接错误:`, err.message); - daemonSocket = null; - - // 尝试重连(如果不是正在关闭) - if (!isShuttingDown) { - if (reconnectTimer) clearTimeout(reconnectTimer); - reconnectTimer = setTimeout(() => { - console.log(`[工具进程 ${workerId}] 尝试重新连接守护进程...`); - connectToDaemon(); - }, 2000); - } - }); - - daemonSocket.on('close', () => { - console.log(`[工具进程 ${workerId}] 守护进程连接已关闭`); - daemonSocket = null; - - // 尝试重连(如果不是正在关闭) - if (!isShuttingDown) { - if (reconnectTimer) clearTimeout(reconnectTimer); - reconnectTimer = setTimeout(() => { - console.log(`[工具进程 ${workerId}] 尝试重新连接守护进程...`); - connectToDaemon(); - }, 2000); - } - }); -} - -// 发送消息到守护进程 -function sendToDaemon(message) { - if (daemonSocket && !daemonSocket.destroyed) { - try { - daemonSocket.write(JSON.stringify(message) + '\n'); - } catch (e) { - console.error(`[工具进程 ${workerId}] 发送消息失败:`, e); - } - } else { - console.warn(`[工具进程 ${workerId}] 守护进程未连接,消息已丢弃:`, message.type); - } -} - -// 处理守护进程消息 -function handleDaemonMessage(message) { - if (message.type === 'worker-registered') { - console.log(`[工具进程 ${workerId}] 连接已注册到守护进程`); - } else if (message.type === 'check-should-exit') { - // 处理是否应该退出的查询响应 - if (message.shouldExit) { - console.log(`[工具进程 ${workerId}] 守护进程指示应该退出,正在退出...`); - shutdown(); - } - } else if (message.error) { - console.error(`[工具进程 ${workerId}] 守护进程错误:`, message.error); - // 如果守护进程要求退出(例如:已被标记为停止) - if (message.shouldExit) { - console.log(`[工具进程 ${workerId}] 守护进程要求退出,正在退出...`); - shutdown(); - } - } -} - -// 检查是否应该退出 -function checkShouldIExit() { - if (isShuttingDown) return; - - if (!daemonSocket || daemonSocket.destroyed) { - // 如果守护进程未连接,暂时不检查,等待重连 - return; - } - - sendToDaemon({ - type: 'check-should-exit', - workerId: workerId - }); -} - -// 初始连接 -connectToDaemon(); - -// 模拟工作负载 -let counter = 0; -const interval = setInterval(() => { - counter++; - console.log(`[工具进程 ${workerId}] 运行中... 计数: ${counter}`); - - // 发送工作数据到守护进程 - sendToDaemon({ - type: 'worker-data', - workerId: workerId, - data: { - counter: counter, - timestamp: Date.now(), - message: `工具进程 ${workerId} 工作数据` - } - }); - - // 模拟随机错误(用于测试自动重启功能) - // 取消下面的注释来测试自动重启 - // if (Math.random() < 0.001) { - // console.error(`[工具进程 ${workerId}] 模拟错误,退出`); - // process.exit(1); - // } -}, 2000); - -// 处理退出信号 -function shutdown() { - if (isShuttingDown) return; - isShuttingDown = true; - - clearInterval(interval); - if (reconnectTimer) clearTimeout(reconnectTimer); - - if (daemonSocket && !daemonSocket.destroyed) { - daemonSocket.destroy(); - } - - console.log(`[工具进程 ${workerId}] 正在退出...`); - process.exit(0); -} - -process.on('SIGTERM', () => { - console.log(`[工具进程 ${workerId}] 收到SIGTERM信号,正在退出...`); - shutdown(); -}); - -process.on('SIGINT', () => { - console.log(`[工具进程 ${workerId}] 收到SIGINT信号,正在退出...`); - shutdown(); -}); - -// 处理未捕获的异常 -process.on('uncaughtException', (error) => { - console.error(`[工具进程 ${workerId}] 未捕获的异常:`, error); - clearInterval(interval); - if (daemonSocket && !daemonSocket.destroyed) { - daemonSocket.destroy(); - } - process.exit(1); -}); - -// 定期发送心跳到守护进程,并检查是否应该退出 -setInterval(() => { - sendToDaemon({ - type: 'worker-heartbeat', - workerId: workerId, - timestamp: Date.now() - }); - // 每次心跳时也检查是否应该退出 - checkShouldIExit(); -}, 5000); diff --git a/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index.ts b/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index.ts index 38f09e6..bbb5fe4 100644 --- a/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index.ts +++ b/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index.ts @@ -5,8 +5,6 @@ import { readConfigFile, getPublicDbFilePath } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' - -import * as fs from 'fs' // import { pipeWriteRegardlessError } from '../utils/pipe' import { getAnyAvailablePuppeteerExecutable } from '../CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable' import { sleep } from '@geekgeekrun/utils/sleep.mjs' @@ -16,7 +14,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' +import { connectToDaemon, sendToDaemon } from '../OPEN_SETTING_WINDOW/connect-to-daemon' import { PeriodPushCurrentPageScreenshotPlugin } from '../../utils/screenshot' import { checkShouldExit } from '../../utils/worker' const { default: SqlitePlugin } = SqlitePluginModule @@ -48,26 +46,8 @@ const runAutoChat = async () => { app.exit() }) app.dock?.hide() - let pipe: null | fs.WriteStream = null - try { - pipe = fs.createWriteStream(null, { fd: 3 }) - } catch { - console.warn('pipe is not available') - } - // pipeWriteRegardlessError( - // pipe, - // JSON.stringify({ - // type: 'INITIALIZE_PUPPETEER' - // }) + '\r\n' - // ) try { await initPuppeteer() - // 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 +81,8 @@ 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' - // ) autoStartChatEventBus.once('LOGIN_STATUS_INVALID', () => { - // pipeWriteRegardlessError( - // pipe, - // JSON.stringify({ - // type: 'LOGIN_STATUS_INVALID' //geek-auto-start-chat-with-boss-started - // }) + '\r\n' - // ) }) while (true) { @@ -159,15 +127,16 @@ 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) - // } - connectToDaemon() +export const waitForProcessHandShakeAndRunAutoChat = async () => { + await connectToDaemon() + await sendToDaemon( + { + type: 'ping' + }, + { + needCallback: true + } + ) runAutoChat() } diff --git a/packages/ui/src/main/flow/LAUNCH_BOSS_ZHIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION.ts b/packages/ui/src/main/flow/LAUNCH_BOSS_ZHIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION.ts index e8aa9f0..d294dfc 100644 --- a/packages/ui/src/main/flow/LAUNCH_BOSS_ZHIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION.ts +++ b/packages/ui/src/main/flow/LAUNCH_BOSS_ZHIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION.ts @@ -21,12 +21,6 @@ export const launchBossZhipinLoginPageWithPreloadExtension = async () => { const { initPuppeteer } = await import('@geekgeekrun/geek-auto-start-chat-with-boss/index.mjs') try { await initPuppeteer() - pipeWriteRegardlessError( - pipe, - JSON.stringify({ - type: 'PUPPETEER_INITIALIZE_SUCCESSFULLY' - }) + '\r\n' - ) } catch (err) { console.error(err) app.exit(1) diff --git a/packages/ui/src/main/flow/LAUNCH_DAEMON.ts b/packages/ui/src/main/flow/LAUNCH_DAEMON.ts index 9bd457e..9c1cc76 100644 --- a/packages/ui/src/main/flow/LAUNCH_DAEMON.ts +++ b/packages/ui/src/main/flow/LAUNCH_DAEMON.ts @@ -1,4 +1,9 @@ +import attachListenerForKillSelfOnParentExited from '../utils/attachListenerForKillSelfOnParentExited' ;(async () => { + process.once('disconnect', () => { + process.exit(0) + }) + attachListenerForKillSelfOnParentExited() await import('@geekgeekrun/pm/daemon.js') })() 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 7fbdc79..91c08e7 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 @@ -11,76 +11,79 @@ export const daemonEE = new EventEmitter() const waitForCallbackTaskMap = new Map() // 连接到守护进程 -export function connectToDaemon() { +export async function connectToDaemon() { daemonClient = new net.Socket(); - daemonClient.connect(DAEMON_PORT, 'localhost', () => { - console.log('已连接到守护进程'); - daemonEE.emit('connect') - // 通知渲染进程连接成功 - // 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); - daemonEE.emit('message', message) - // FIXME: - 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) + let isConnected = false + await new Promise((resolve, reject) => { + daemonClient.connect(DAEMON_PORT, 'localhost', () => { + isConnected = true + console.log('已连接到守护进程'); + daemonEE.emit('connect') + // 使用 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); + daemonEE.emit('message', message) + // FIXME: + 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('close', () => { + if (!isConnected) { + return + } + console.log('守护进程连接已关闭'); + daemonEE.emit('close') + }); + + resolve(true) + // 通知渲染进程连接成功 // if (mainWindow) { - // mainWindow.webContents.send('daemon-message', message); + // mainWindow.webContents.send('daemon-connected'); // } - } 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); - daemonEE.emit('error', err) - // 尝试重连 - setTimeout(() => { - if (daemonClient.destroyed) { - connectToDaemon(); + daemonClient.on('close', () => { + if (isConnected) { + return } - }, 2000); - }); - - daemonClient.on('close', () => { - console.log('守护进程连接已关闭'); - daemonEE.emit('close') - // 尝试重连 - setTimeout(() => { - connectToDaemon(); - }, 2000); - }); + reject(new Error('连接到守护进程超时')) + }); + daemonClient.on('error', (err) => { + console.error('守护进程连接错误:', err); + daemonEE.emit('error', err) + reject(err) + }); + }) } // 向守护进程发送消息 diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/index.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/index.ts index bd4869e..153b52e 100644 --- a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/index.ts +++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/index.ts @@ -6,8 +6,9 @@ import initIpc from './ipc' import gtag from '../../utils/gtag' import initPublicIpc from '../../utils/initPublicIpc' import { launchDaemon } from './launch-daemon' -import { connectToDaemon } from './connect-to-daemon' -import { sleep } from '@geekgeekrun/utils/sleep.mjs' +import { connectToDaemon, sendToDaemon } 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? if (!app.requestSingleInstanceLock()) { @@ -71,8 +72,15 @@ export function openSettingWindow() { whenReadyPromise.then(async () => { await launchDaemon() - // FIXME: await sleep(2000) await connectToDaemon() + await sendToDaemon( + { + type: 'ping' + }, + { + 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 c250a0a..902403a 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,5 +1,3 @@ -import { connectToDaemon } from "./connect-to-daemon"; - const { app } = require('electron'); const { spawn } = require('child_process'); @@ -27,7 +25,7 @@ export function launchDaemon() { }); // 启动守护进程 - function startDaemon() { + async function startDaemon() { console.log('启动守护进程...'); // 使用 Electron 可执行程序路径,如果没有则回退到 node const electronPath = process.execPath; @@ -40,7 +38,7 @@ export function launchDaemon() { ? [process.argv[1], `--mode=launchDaemon`] : [`--mode=launchDaemon`], { - stdio: ['ignore', 'pipe', 'pipe'], + stdio: ['ignore', 'pipe', 'pipe', 'pipe'], detached: false, env: { ...process.env, @@ -69,26 +67,26 @@ export function launchDaemon() { console.error(`守护进程错误: ${data}`); }); - daemonProcess.on('exit', (code) => { - console.log(`守护进程退出,代码: ${code}`); - // 如果守护进程意外退出,尝试重启 - if (code !== 0) { - setTimeout(() => { - console.log('尝试重启守护进程...'); - startDaemon(); - }, 2000); - } - }); - - // 等待守护进程启动后连接 - setTimeout(() => { - connectToDaemon(); - }, 1000); + return new Promise((resolve, reject) => { + daemonProcess.stdio[3].on('data', (rawData) => { + let data + try { + data = JSON.parse(rawData.toString()) + if (data.type === 'DAEMON_READY') { + resolve(true) + } + else if (data.type === 'DAEMON_FATAL') { + reject(new Error(data.error)) + } + } + catch (err) { + console.error('', err) + } + }) + }) } // 应用准备就绪 - return app.whenReady().then(() => { - startDaemon(); - }); + return app.whenReady().then(() => startDaemon()); } diff --git a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/index.ts b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/index.ts index f185d49..068d593 100644 --- a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/index.ts +++ b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/index.ts @@ -24,7 +24,7 @@ import gtag from '../../utils/gtag' import { JobHireStatus } from '@geekgeekrun/sqlite-plugin/dist/enums'; import dayjs from 'dayjs' import cheerio from 'cheerio' -import { connectToDaemon } from '../OPEN_SETTING_WINDOW/connect-to-daemon' +import { connectToDaemon, sendToDaemon } from '../OPEN_SETTING_WINDOW/connect-to-daemon' import { pushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS } from '../../utils/screenshot' import { checkShouldExit } from '../../utils/worker' @@ -464,8 +464,13 @@ const rerunInterval = (() => { })() export async function runEntry() { - connectToDaemon() app.dock?.hide() + await connectToDaemon() + await sendToDaemon({ + type: 'ping' + }, { + needCallback: true + }) while (true) { try { await mainLoop()