Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/electron/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ import { startPlanningNativeSyncCoordinator, stopPlanningNativeSyncCoordinator }
import { feishuBridgeManager } from './lib/feishu-bridge-manager'
import { getFeishuMultiBotConfig } from './lib/feishu-config'
import { stopFeishuSyncSleepBlocker, syncFeishuSyncSleepBlocker } from './lib/feishu-sleep-blocker'
import { stopIdleMode, syncIdleMode } from './lib/idle-mode-electron'
import { stopTaskSleepGuard, syncTaskSleepGuard } from './lib/task-sleep-guard-electron'
import { getPersistableMainWindowState, hideMacMainWindowAfterClose } from './lib/main-window-lifecycle'
import { dingtalkBridgeManager } from './lib/dingtalk-bridge-manager'
import { getDingTalkMultiBotConfig } from './lib/dingtalk-config'
Expand Down Expand Up @@ -702,6 +704,12 @@ async function bootstrap(): Promise<void> {
// 飞书实时同步开启时,默认阻止系统自动休眠,保证远程群内继续可用。
safeRun('syncFeishuSyncSleepBlocker', () => syncFeishuSyncSleepBlocker(getSettings()))

// 挂机模式开启时,立即熄灭显示器并阻止系统休眠,保证挂机任务持续运行。
safeRun('syncIdleMode', () => syncIdleMode(getSettings()))

// 任务防休眠(默认开启):任务运行期间阻止系统休眠,锁屏/息屏/合盖不中断任务。
safeRun('syncTaskSleepGuard', () => syncTaskSleepGuard(getSettings()))

// 注册全局快捷键
safeRun('registerGlobalShortcut:quick-task', () =>
registerGlobalShortcut('quick-task', toggleQuickTaskWindow),
Expand Down Expand Up @@ -823,6 +831,10 @@ app.on('before-quit', () => {
stopPlanningNativeSyncCoordinator()
// 释放飞书同步防休眠
stopFeishuSyncSleepBlocker()
// 释放挂机模式(息屏 + 防休眠)
stopIdleMode()
// 释放任务防休眠
stopTaskSleepGuard()
// 注销全局快捷键
unregisterAllGlobalShortcuts()
// 销毁辅助窗口
Expand Down
14 changes: 14 additions & 0 deletions apps/electron/src/main/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,8 @@ import {
} from './lib/feishu-config'
import { feishuBridgeManager } from './lib/feishu-bridge-manager'
import { syncFeishuSyncSleepBlocker } from './lib/feishu-sleep-blocker'
import { syncIdleMode } from './lib/idle-mode-electron'
import { syncTaskSleepGuard } from './lib/task-sleep-guard-electron'
import { presenceService } from './lib/feishu-presence'
import { getDingTalkConfig, saveDingTalkConfig, getDecryptedClientSecret, getDingTalkMultiBotConfig, saveDingTalkBotConfig, removeDingTalkBot, getDecryptedBotClientSecret } from './lib/dingtalk-config'
import { listShallowDirectory } from './lib/directory-listing'
Expand Down Expand Up @@ -1758,6 +1760,12 @@ export function registerIpcHandlers(): void {
if (updates.feishuSessionMirror !== undefined) {
syncFeishuSyncSleepBlocker(result)
}
if (updates.idleMode !== undefined) {
syncIdleMode(result)
}
if (updates.taskSleepGuard !== undefined) {
syncTaskSleepGuard(result)
}
if (updates.agentIsland !== undefined) {
refreshAgentIslandConfiguration()
}
Expand Down Expand Up @@ -1790,6 +1798,12 @@ export function registerIpcHandlers(): void {
if (updates.feishuSessionMirror !== undefined) {
syncFeishuSyncSleepBlocker(result)
}
if (updates.idleMode !== undefined) {
syncIdleMode(result)
}
if (updates.taskSleepGuard !== undefined) {
syncTaskSleepGuard(result)
}
if (updates.agentIsland !== undefined) {
refreshAgentIslandConfiguration()
}
Expand Down
15 changes: 15 additions & 0 deletions apps/electron/src/main/lib/agent-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { getAgentSessionMeta, updateAgentSessionMeta } from './agent-session-man
import { setAgentStopper, setHeadlessAgentRunner } from './agent-headless-runner-registry'
import { getHeadlessAgentRunTarget } from './agent-headless-run-target'
import { sendAgentStreamComplete } from './agent-completion-payload'
import { taskSleepGuard } from './task-sleep-guard-electron'

// ===== 实例创建 =====

Expand Down Expand Up @@ -181,9 +182,13 @@ export async function runAgent(
}
} catch { /* 新会话可能尚未写入索引 */ }
}
// 任务防休眠:本次 run 的 token,onRunStarted 开始防休眠,onComplete/onError/异常时释放。
// 声明在函数级作用域,确保 catch 分支可访问。
const sleepGuardToken = {}
try {
await orchestrator.sendMessage(input, {
onError: (error) => {
taskSleepGuard.end(sleepGuardToken)
if (!webContents.isDestroyed()) {
webContents.send(AGENT_IPC_CHANNELS.STREAM_ERROR, {
sessionId: input.sessionId,
Expand All @@ -192,6 +197,7 @@ export async function runAgent(
}
},
onComplete: (messages, opts) => {
taskSleepGuard.end(sleepGuardToken)
publishRunStopped(input.sessionId, opts?.stoppedByUser, opts?.startedAt)
if (!webContents.isDestroyed()) {
sendAgentStreamComplete(webContents, input, {
Expand All @@ -207,6 +213,7 @@ export async function runAgent(
}
},
onRunStarted: ({ startedAt }) => {
taskSleepGuard.begin(sleepGuardToken)
eventBus.emit(input.sessionId, {
kind: 'proma_event',
event: { type: 'run_started', startedAt },
Expand All @@ -228,6 +235,7 @@ export async function runAgent(
} catch (err) {
console.error('[Agent 服务] runAgent 未处理异常:', err)
const errorMessage = err instanceof Error ? err.message : '未知错误'
taskSleepGuard.end(sleepGuardToken)
if (!webContents.isDestroyed()) {
webContents.send(AGENT_IPC_CHANNELS.STREAM_ERROR, {
sessionId: input.sessionId,
Expand Down Expand Up @@ -276,9 +284,13 @@ export async function runAgentHeadless(
registerWebContents(runInput.sessionId, wc)
}

// 任务防休眠:本次 run 的 token,onRunStarted 开始防休眠,onComplete/onError/异常时释放。
// 声明在函数级作用域,确保 catch 分支可访问。
const sleepGuardToken = {}
try {
await orchestrator.sendMessage(runInput, {
onError: (error) => {
taskSleepGuard.end(sleepGuardToken)
callbacks.onError(error)
// 同步到渲染进程
if (wc && !wc.isDestroyed()) {
Expand All @@ -289,6 +301,7 @@ export async function runAgentHeadless(
}
},
onComplete: (messages, opts) => {
taskSleepGuard.end(sleepGuardToken)
callbacks.onComplete(messages)
publishRunStopped(runInput.sessionId, opts?.stoppedByUser, opts?.startedAt)
// 同步到渲染进程
Expand Down Expand Up @@ -320,6 +333,7 @@ export async function runAgentHeadless(
}
},
onRunStarted: ({ startedAt: persistedStartedAt }) => {
taskSleepGuard.begin(sleepGuardToken)
const session = getAgentSessionMeta(runInput.sessionId)
eventBus.emit(runInput.sessionId, {
kind: 'proma_event',
Expand All @@ -339,6 +353,7 @@ export async function runAgentHeadless(
} catch (err) {
console.error('[Agent 服务] runAgentHeadless 未处理异常:', err)
const errorMessage = err instanceof Error ? err.message : '未知错误'
taskSleepGuard.end(sleepGuardToken)
callbacks.onError(errorMessage)
callbacks.onComplete()
if (wc && !wc.isDestroyed()) {
Expand Down
36 changes: 36 additions & 0 deletions apps/electron/src/main/lib/idle-mode-electron.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { powerSaveBlocker } from 'electron'
import type { AppSettings } from '../../types'
import { IdleModeManager, type SleepBlockerAdapter, type SleepBlockerType } from './idle-mode'

/**
* 挂机模式(息屏 + 防休眠)— electron 接线层
*
* 核心逻辑见 idle-mode.ts;此处负责把 powerSaveBlocker 注入为
* SleepBlockerAdapter,并导出供主进程使用的 sync/stop 入口。
*/

const electronSleepBlocker: SleepBlockerAdapter = {
start: (type: SleepBlockerType): number => powerSaveBlocker.start(type),
stop: (id: number): void => {
powerSaveBlocker.stop(id)
},
isStarted: (id: number): boolean => powerSaveBlocker.isStarted(id),
}

const manager = new IdleModeManager(electronSleepBlocker)

export function syncIdleMode(settings: Pick<AppSettings, 'idleMode'>): void {
try {
manager.sync(settings)
} catch (error) {
console.error('[挂机模式] 同步状态失败:', error)
}
}

export function stopIdleMode(): void {
try {
manager.stop()
} catch (error) {
console.error('[挂机模式] 关闭失败:', error)
}
}
Loading