Skip to content
Merged
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
8 changes: 8 additions & 0 deletions docs/en/wegent/user-guide/settings/desktop-app-preferences.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ When Wework moves to the background, it destroys the main window WebView to rele

When no conversation is open, the newly created main window keeps the task launcher within the window bounds. The message area scrolls as needed only after a conversation is opened.

## Prevent Sleep While Tasks Are Running

**Prevent sleep while tasks are running** is enabled by default. While at least one task is running, Wework prevents the computer from entering idle system sleep. This inhibition remains active when the main window is closed and Wework continues from the system tray.

After every task completes, fails, or is interrupted, Wework immediately releases the inhibition and restores the operating system's normal sleep policy. Wework does not force the computer to sleep and does not change the configured system sleep timeout.

To keep Wework from controlling sleep, disable this option under **Settings → General**. Disabling it immediately releases any current sleep inhibition. If it is enabled again while a task is still running, Wework resumes preventing idle sleep.

## Appshots

On macOS desktop, **Settings → Integrations → Appshots** shows the Appshots status and sound preference. The default shortcut is `⌘⇧2`. When pressed, Wework captures the frontmost application window and uses macOS Accessibility to read text exposed by that window, which can include text outside the visible scroll area. It then adds both the PNG and text context to the current composer attachments.
Expand Down
8 changes: 8 additions & 0 deletions docs/zh/wegent/user-guide/settings/desktop-app-preferences.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ Wework 桌面版的“设置 → 通用”用于控制应用窗口和后台运

如果没有打开会话,新创建的主窗口会显示固定在窗口范围内的任务启动页;只有进入会话后,消息内容区域才会按需滚动。

## 任务运行时阻止电脑休眠

“任务运行时阻止电脑休眠”默认开启。当至少有一个任务正在运行时,Wework 会阻止电脑因空闲自动进入系统休眠。关闭主窗口并进入系统托盘后,只要任务仍在运行,防休眠状态就会继续保持。

所有任务结束、失败或被中断后,Wework 会立即释放防休眠状态,并恢复操作系统原本的休眠策略。Wework 不会主动让电脑立即休眠,也不会修改系统的休眠时间设置。

如果不希望 Wework 控制休眠,可以在“设置 → 通用”中关闭该选项。关闭选项会立即释放当前的防休眠状态;之后重新开启时,如果仍有任务在运行,Wework 会重新阻止空闲休眠。

## 应用快照

在 macOS 桌面版中,**设置 → 集成 → 应用快照** 用于查看 Appshots 状态和配置快照声音。默认快捷键是 `⌘⇧2`。按下快捷键后,Wework 会截取当前最前面的应用窗口,并通过 macOS 辅助功能读取该窗口提供的文本(可能包括滚动区域外文本),然后把 PNG 和文本上下文一起添加到当前输入框附件中。
Expand Down
42 changes: 42 additions & 0 deletions wework/e2e/desktop/task-flow.e2e.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,30 @@ function processIsAlive(processId) {
}
}

function macosSleepInhibitorProcessIds(appProcessId) {
if (process.platform !== 'darwin') return []
const output = commandOutput('/bin/ps', ['-axo', 'pid=,ppid=,command='])
return output.split('\n').flatMap(line => {
const match = line.trim().match(/^(\d+)\s+(\d+)\s+(.+)$/)
if (!match || Number(match[2]) !== appProcessId || match[3] !== '/usr/bin/caffeinate -i') {
return []
}
return [Number(match[1])]
})
}

async function waitForMacosSleepInhibitor(appProcessId, expectedRunning) {
const startedAt = Date.now()
while (Date.now() - startedAt < UI_TIMEOUT_MS) {
const processIds = macosSleepInhibitorProcessIds(appProcessId)
if (processIds.length > 0 === expectedRunning) return processIds
await new Promise(resolvePromise => setTimeout(resolvePromise, 100))
}
throw new Error(
`Timed out waiting for the macOS sleep inhibitor to be ${expectedRunning ? 'running' : 'stopped'}`
)
}

async function waitForExecutorReadyEvidence(logPath, timeoutMs = UI_TIMEOUT_MS) {
const startedAt = Date.now()
while (Date.now() - startedAt < timeoutMs) {
Expand Down Expand Up @@ -749,6 +773,11 @@ async function verifyBackgroundTaskWindowLifecycle({
UI_TIMEOUT_MS,
'Timed out waiting for the streaming response to start'
)
const sleepInhibitorEvidence = []
if (process.platform === 'darwin') {
const processIds = await waitForMacosSleepInhibitor(app.pid, true)
sleepInhibitorEvidence.push({ stage: 'task-running', processIds })
}
const runningTaskSnapshot = await waitForSnapshot(
control,
snapshot => snapshot.testIds.some(testId => testId.startsWith('runtime-local-task-running-')),
Expand Down Expand Up @@ -794,6 +823,11 @@ async function verifyBackgroundTaskWindowLifecycle({
true,
'Closing to tray terminated the executor process'
)
const backgroundProcessIds = await waitForMacosSleepInhibitor(app.pid, true)
sleepInhibitorEvidence.push({
stage: 'window-closed-to-tray',
processIds: backgroundProcessIds,
})

await reactivateMacApplication(appIdentifier)
await withTimeout(
Expand Down Expand Up @@ -893,6 +927,14 @@ async function verifyBackgroundTaskWindowLifecycle({
control,
lifecycleScreenshotName('04-task-completed-after-reopen.png')
)
if (process.platform === 'darwin') {
const processIds = await waitForMacosSleepInhibitor(app.pid, false)
sleepInhibitorEvidence.push({ stage: 'task-completed', processIds })
await writeFile(
join(resultDir, 'sleep-inhibitor-lifecycle-verification.json'),
`${JSON.stringify({ appProcessId: app.pid, stages: sleepInhibitorEvidence }, null, 2)}\n`
)
}
}

async function attachAndSendOnlyFile(control, composerSelector) {
Expand Down
24 changes: 24 additions & 0 deletions wework/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod local_executor;
mod local_terminal;
mod process_environment;
mod system_drag;
mod system_sleep;
mod workbench_background;

use std::collections::{HashMap, HashSet};
Expand Down Expand Up @@ -383,6 +384,8 @@ struct AppPreferences {
show_main_window_on_launch: bool,
#[serde(default = "default_true")]
system_drag_enabled: bool,
#[serde(default = "default_true")]
prevent_sleep_while_tasks_running: bool,
#[serde(default)]
close_to_tray_hint_seen: bool,
#[serde(default = "default_language_preference")]
Expand Down Expand Up @@ -482,6 +485,7 @@ impl Default for AppPreferences {
close_to_tray_enabled: true,
show_main_window_on_launch: true,
system_drag_enabled: true,
prevent_sleep_while_tasks_running: true,
close_to_tray_hint_seen: false,
language: default_language_preference(),
terminal_context_injection_enabled: true,
Expand All @@ -507,6 +511,7 @@ struct AppPreferencesPatch {
close_to_tray_enabled: Option<bool>,
show_main_window_on_launch: Option<bool>,
system_drag_enabled: Option<bool>,
prevent_sleep_while_tasks_running: Option<bool>,
close_to_tray_hint_seen: Option<bool>,
language: Option<String>,
terminal_context_injection_enabled: Option<bool>,
Expand Down Expand Up @@ -935,6 +940,9 @@ fn update_app_preferences(
if let Some(value) = patch.system_drag_enabled {
preferences.system_drag_enabled = value;
}
if let Some(value) = patch.prevent_sleep_while_tasks_running {
preferences.prevent_sleep_while_tasks_running = value;
}
if let Some(value) = patch.close_to_tray_hint_seen {
preferences.close_to_tray_hint_seen = value;
}
Expand Down Expand Up @@ -979,6 +987,8 @@ fn update_app_preferences(
preferences.quick_phrases = value;
}
write_app_preferences_impl(&app, &preferences)?;
app.state::<system_sleep::SystemSleepState>()
.set_enabled(preferences.prevent_sleep_while_tasks_running);
Ok(preferences)
}

Expand All @@ -989,6 +999,7 @@ struct AppPreferences {
close_to_tray_enabled: bool,
show_main_window_on_launch: bool,
system_drag_enabled: bool,
prevent_sleep_while_tasks_running: bool,
close_to_tray_hint_seen: bool,
language: String,
terminal_context_injection_enabled: bool,
Expand All @@ -1012,6 +1023,7 @@ struct AppPreferencesPatch {
close_to_tray_enabled: Option<bool>,
show_main_window_on_launch: Option<bool>,
system_drag_enabled: Option<bool>,
prevent_sleep_while_tasks_running: Option<bool>,
close_to_tray_hint_seen: Option<bool>,
language: Option<String>,
terminal_context_injection_enabled: Option<bool>,
Expand All @@ -1035,6 +1047,7 @@ fn get_app_preferences(_app: tauri::AppHandle) -> Result<AppPreferences, String>
close_to_tray_enabled: true,
show_main_window_on_launch: true,
system_drag_enabled: true,
prevent_sleep_while_tasks_running: true,
close_to_tray_hint_seen: false,
language: "zh-CN".to_string(),
terminal_context_injection_enabled: true,
Expand Down Expand Up @@ -1062,6 +1075,7 @@ fn update_app_preferences(
close_to_tray_enabled: patch.close_to_tray_enabled.unwrap_or(true),
show_main_window_on_launch: patch.show_main_window_on_launch.unwrap_or(true),
system_drag_enabled: patch.system_drag_enabled.unwrap_or(true),
prevent_sleep_while_tasks_running: patch.prevent_sleep_while_tasks_running.unwrap_or(true),
close_to_tray_hint_seen: patch.close_to_tray_hint_seen.unwrap_or(false),
language: patch.language.unwrap_or_else(|| "zh-CN".to_string()),
terminal_context_injection_enabled: patch
Expand Down Expand Up @@ -2434,6 +2448,8 @@ struct TrayMenuStatePayload {
unread_more: Vec<TrayMenuTaskItem>,
running_count: usize,
#[serde(default)]
active_task_count: usize,
#[serde(default)]
show_running_status: bool,
#[serde(default)]
unread_count: usize,
Expand Down Expand Up @@ -2479,6 +2495,7 @@ impl TrayMenuStatePayload {
unread: Vec::new(),
unread_more: Vec::new(),
running_count: 0,
active_task_count: 0,
show_running_status: false,
unread_count: 0,
pinned: Vec::new(),
Expand Down Expand Up @@ -3310,6 +3327,8 @@ fn update_tray_visual<R: tauri::Runtime>(
#[cfg(desktop)]
#[tauri::command]
fn set_tray_menu_state(app: tauri::AppHandle, state: TrayMenuStatePayload) -> Result<(), String> {
app.state::<system_sleep::SystemSleepState>()
.set_running_count(state.active_task_count);
let menu = build_system_tray_menu(&app, &state)
.map_err(|error| format!("Failed to build tray menu: {error}"))?;
let Some(tray) = app.tray_by_id(TRAY_ID) else {
Expand Down Expand Up @@ -3765,6 +3784,7 @@ pub fn run() {
.manage(local_executor::LocalExecutorState::default())
.manage(local_terminal::LocalTerminalState::default())
.manage(system_drag::SystemDragState::default())
.manage(system_sleep::SystemSleepState::default())
.on_window_event(|window, event| {
#[cfg(desktop)]
if hide_main_window_on_close(window, event) {
Expand Down Expand Up @@ -3804,6 +3824,10 @@ pub fn run() {
#[cfg(desktop)]
setup_system_tray(app)?;
#[cfg(desktop)]
app.state::<system_sleep::SystemSleepState>().set_enabled(
read_app_preferences_impl(app.handle()).prevent_sleep_while_tasks_running,
);
#[cfg(desktop)]
system_drag::setup(app.handle().clone());
#[cfg(desktop)]
appshots::setup(app.handle());
Expand Down
13 changes: 13 additions & 0 deletions wework/src-tauri/src/local_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1460,6 +1460,11 @@ fn handle_executor_line_inner(
resolve_response_inner(inner, response);
}
ExecutorLine::Event(event) => {
app.state::<crate::system_sleep::SystemSleepState>()
.handle_terminal_event(
&event.event,
event.payload.get("taskId").and_then(Value::as_str),
);
if let Some((previous_runtime_instance_id, runtime_instance_id)) =
update_ready_event_inner(inner, &event)
{
Expand Down Expand Up @@ -1621,12 +1626,15 @@ fn handle_stdio_line(
}

fn finish_stdio_reader(
app: &tauri::AppHandle,
state: &SharedExecutorInner,
generation: u64,
sender: &mut Option<mpsc::Sender<Result<(), String>>>,
message: String,
terminate_child: bool,
) {
app.state::<crate::system_sleep::SystemSleepState>()
.clear_running_tasks();
if let Some(sender) = sender.take() {
let _ = sender.send(Err(message.clone()));
}
Expand Down Expand Up @@ -1688,6 +1696,7 @@ fn spawn_configured_sidecar(
Err(error) => {
let message = format!("Local executor stdout read failed: {error}");
finish_stdio_reader(
&app,
&state_handle,
generation,
&mut ready_sender,
Expand All @@ -1699,6 +1708,7 @@ fn spawn_configured_sidecar(
}
}
finish_stdio_reader(
&app,
&state_handle,
generation,
&mut ready_sender,
Expand Down Expand Up @@ -1747,6 +1757,7 @@ fn spawn_bundled_sidecar(
CommandEvent::Terminated(payload) => {
let message = format!("Local executor exited: {payload:?}");
finish_stdio_reader(
&app,
&state_handle,
generation,
&mut ready_sender,
Expand All @@ -1758,6 +1769,7 @@ fn spawn_bundled_sidecar(
CommandEvent::Error(error) => {
let message = format!("Local executor stdio failed: {error}");
finish_stdio_reader(
&app,
&state_handle,
generation,
&mut ready_sender,
Expand All @@ -1770,6 +1782,7 @@ fn spawn_bundled_sidecar(
}
}
finish_stdio_reader(
&app,
&state_handle,
generation,
&mut ready_sender,
Expand Down
Loading
Loading