makecindy / makecindy/cindy

bug: Windows 上主窗口被后台插件 focus 请求抢占焦点(盖住当前前台应用)

Open
#500 16 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
2.7k
Forks
395
Avg merge
21h 48m
Merged PRs (30d)
776

Description

## 问题描述 / What happened

Windows 上,当用户正在前台使用**其它应用**(反馈者录屏中为 QQ)时,Cindy 主窗口会**在没有任何用户操作**的情况下突然被拉到前台、抢走焦点,盖住用户当前正在用的窗口。

- 实际行为:主窗口无用户手势自动 `show()+focus()`,强行前台化。
- 期望行为:后台/程序化触发的"聚焦某会话"不应打断用户当前前台应用;至多用任务栏闪烁提醒。

## 环境 / Environment

- Cindy 版本或 commit:待反馈者补充(基于当前 `main` 分析)
- 平台与版本:Windows(具体版本待补充);macOS 未复现(见下方原因)
- 安装方式:桌面客户端

## 复现步骤 / Steps to reproduce

(根因分析指向的路径,待反馈者确认当时是否有插件在跑)

1. Windows 上运行 Cindy,主窗口非前台(用户切到 QQ 等其它应用)。
2. Cindy 内有 Ghost 插件在后台 agent turn / 工具调用上下文中运行。
3. 该插件发出 `workspace({ focus: true })` 请求。
4. 主窗口被 `show()+focus()` 拉到前台,盖住 QQ。

## 根因分析 / Root cause

对 `apps/desktop/src/main/` 做了穷举审计,Windows 上**唯一**"无用户手势就 `show()+focus()` 主窗口"的链路:

```
Ghost 插件后台发 workspace({ focus: true })
→ cindy-brain/workspaceSlot.ts:254 / 259 service.focusSession(sessionId)
→ maker-ipc/register.ts:4995 focusSession = openMainWindowSession
→ deepLink.ts:317 openMainWindowSession
→ deepLink.ts dispatchDeepLink({type:'session'})
→ deepLink.ts:353-356 win.show(); win.restore(); win.focus() // 裸抢前台
```

`dispatchDeepLink` 的设计前提是"所有 deep link 都是用户点 `cindy://` 链接,用户就是想看到 app"(见该函数注释),因此**无条件**前台化、**无平台判断、无用户手势判据、无抢焦点抑制**。但经 `openMainWindowSession` 注入给 Ghost workspace slot 的这条 `focusSession` **不是用户点击**——插件在后台上下文运行,用户可能正在别的应用前台。

**为什么只在 Windows 明显**:`BrowserWindow.show()` 在 Windows 上会同时置前+抢焦点,`win.focus()` 强制前台化;macOS 对后台应用抢前台有系统级限制(通常只弹 Dock),同段代码在 Mac 上不表现为"覆盖当前应用"。

### 已排除(审计覆盖)

- Agent Island 整条链路 macOS-only(`shared/agentIsland.ts:195` 非 `darwin` 直接 return false),Windows 不触发。
- 通知(agent turn 完成 / 定时任务终态):只弹 toast,前台化严格 gate 在"点通知"之后;turn 完成本身走 `flashFrame`(任务栏闪烁,不抢焦点)。
- device-link 远程控制、hook-server 入站、scheduler、orca、IM(飞书 bot):主进程内零主窗口前台化调用。
- Ghost 面板 / 右侧栏 / 会话副窗口 / 语音浮层 / 权限引导浮层:操作独立窗口,不会让主窗口盖住 QQ。

## 建议修复方向 / Suggested fix

区分"用户手势 deep link"与"程序化(Ghost)focus":给 `openMainWindowSession` / `dispatchDeepLink` 传 `userGesture: boolean`,Ghost 注入路径传 `false`。非用户手势时:

- 应用已在前台 → 正常切会话 `show()+focus()`;
- 应用不在前台 → **不 `focus()`**,改用任务栏提醒(`flashFrame(true)` + `setOverlayIcon`,与 `appBadgeService` 现有非抢焦点做法一致),只做后台导航。

真实 `cindy://` 用户点击(open-url / second-instance / OAuth 返回)保持 `userGesture: true`,行为不变。

## 日志与截图 / Logs & screenshots

反馈者提供了录屏(QQ 前台被 Cindy 主窗口覆盖)。如能提供复现时刻 `apps/desktop/logs/` 中的日志(搜 `deep-link` / `buffered pending` / `openMainWindowSession`),可进一步确认是否为该 Ghost 路径。

> 待确认:反馈者复现时是否安装/运行了 Ghost 插件。若完全未使用任何插件,说明存在审计未覆盖的触发点,需据录屏时间点 + 日志再排查。

Contributor guide

Open the contributing guide

Research direction

Start by reading cindy-brain/workspaceSlot.ts around lines 254/259, maker-ipc/register.ts around line 4995, and deepLink.ts around lines 317 and 353-356. Check apps/desktop/logs/ for deep-link, buffered pending, and openMainWindowSession entries, then reproduce the Ghost workspace focus path on Windows. Done means background focus requests do not cover another foreground application, while genuine cindy:// user actions retain their current behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
electron, typescript
Domain
desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
64/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.