Skip to content

Add an opt-in setting for suppressing surfaces while the session host is focused - #282

Open
weilinzung wants to merge 2 commits into
erha19:mainfrom
weilinzung:ide-host-focus-detection
Open

Add an opt-in setting for suppressing surfaces while the session host is focused#282
weilinzung wants to merge 2 commits into
erha19:mainfrom
weilinzung:ide-host-focus-detection

Conversation

@weilinzung

@weilinzung weilinzung commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Revised after review: this no longer touches smartSuppression.
Screenshot 2026-08-20 at 9 21 01 AM

What changed

New opt-in setting suppressWhenSessionHostFocused — 「查看会话应用时抑制」 — default off. While the session's own terminal or IDE is frontmost, the Island withholds its automatic surfaces.

Surface Off (default) On, host frontmost
Approval / question expands stays closed
Approval only Island can answer expands still expands
Completion panel (docked + detached) expands stays closed
Notification sound plays silent
smartSuppression pending auto-open unchanged unchanged

smartSuppression keeps its key, subtitle, localisations and gate — still terminal-visibility only. You were right that VS Code being frontmost does not carry that meaning, and it also should not have pulled approvals and sounds under a toggle promising neither.

Two things worth reviewing

Suppression stops at prompt ownership. With routePromptsToTerminal off the bridge blocks and the client renders nothing, so the Island holds the only copy of an approval. SessionState.islandOwnsBlockingPrompt vetoes suppression there — otherwise enabling the setting could strand an agent behind a notch indicator. This pairs naturally with 「保留终端中的提问与审批」.

One change is not behind the toggle. isSessionFocused compared the frontmost app pid against a terminal pid resolved by process name, which can never match an IDE: an agent binary under ~/.vscode/extensions/…/claude matches isTerminal itself, so resolution stopped on the agent. Ownership now comes from the parent chain (ProcessTreeBuilder.isAncestor). That repair applies unconditionally, so it slightly changes existing sound behaviour for IDE-hosted sessions. Happy to gate it too if you would rather nothing moves without the opt-in.

Sessions also may carry no pid at all — an IDE-extension agent reports none — so the host is matched from SessionClientInfo.hostBundleIdentifier (terminalBundleIdentifier, falling back to bundleIdentifier; never terminalProgram, which can be stale). Helper bundles fold onto their host, and only registry-recognised bundles count, keeping Island's own bundle out.

Limitations

  • Two windows of one IDE are indistinguishable — one process, one bundle id. The ownership veto bounds it: a blocking approval always surfaces.
  • Sessions with no host identity fall through unchanged.
  • Completion queues cannot await, so they use a synchronous check and skip the tmux pane refinement. tmux otherwise keeps pane-level resolution.
  • Suppressed completion notifications are consumed, not deferred.

Verification

Rebased on 415cc8c, clean over the locale-consistency commit. One settings key added, none removed, nothing renamed, no localisation lines removed.

Step Result
swift test --package-path Prototype 129 passed
xcodebuild -only-testing:PingIslandTests passed
New setting defaults off pinned by AppSettingsPersistenceTests
Manual, IDE-frontmost behaviour verified on the pre-toggle revision; not yet re-run since the setting was introduced

DetachedIslandWindowControllerTests.testNewAttentionSessionAutoOpensBubbleInFloatingMode is timing-bound (asserts inside asyncAfter(0.05)) and fails intermittently here under suite load. It is structurally outside this change: its session carries an intervention, so isCompletedReadySession rejects it and the gate added here is never reached.

@weilinzung
weilinzung force-pushed the ide-host-focus-detection branch from 561d93f to 6aa7276 Compare August 18, 2026 14:48
@weilinzung weilinzung changed the title Apply smart suppression to approvals when the session host is focused Keep approvals in the focused session host instead of expanding the Island Aug 18, 2026
@weilinzung
weilinzung marked this pull request as draft August 18, 2026 14:55
@weilinzung
weilinzung force-pushed the ide-host-focus-detection branch 4 times, most recently from f72796b to 6d98e6e Compare August 18, 2026 15:59
@weilinzung
weilinzung marked this pull request as ready for review August 18, 2026 15:59
@weilinzung
weilinzung force-pushed the ide-host-focus-detection branch from 6d98e6e to fda75aa Compare August 18, 2026 18:23
@weilinzung weilinzung changed the title Keep approvals in the focused session host instead of expanding the Island Make smart suppression honour the focused session host on every surface Aug 18, 2026
@weilinzung
weilinzung force-pushed the ide-host-focus-detection branch 2 times, most recently from 783f215 to 3c59c5d Compare August 18, 2026 20:43
@erha19

erha19 commented Aug 20, 2026

Copy link
Copy Markdown
Owner

@weilinzung 这里的实现存在问题,VS Code 可见当前还不代表终端可见含义,可以单独开个设置项作为这个逻辑的开关

@weilinzung
weilinzung force-pushed the ide-host-focus-detection branch from 3c59c5d to 6e30e49 Compare August 20, 2026 13:14
@weilinzung weilinzung changed the title Make smart suppression honour the focused session host on every surface Add an opt-in setting for suppressing surfaces while the session host is focused Aug 20, 2026
@weilinzung

Copy link
Copy Markdown
Contributor Author

@erha19 感谢 review,已按建议改好:

  1. 新增独立设置项 suppressWhenSessionHostFocused(「查看会话应用时抑制」),默认关闭。已加测试保证默认值不会被改动。
  2. smartSuppression 完全没动 —— key、副标题、两个语言的文案、以及它原本只判断「终端是否可见」的逻辑都保持原样。之前把 IDE 聚焦混进这个开关确实不对。
  3. 新开关打开后,只有在「会话所在的终端 / IDE 处于最前面」时,才会抑制审批、提问、完成面板和提示音。

另外两点想请你确认:

  • 只有 Island 能回应的审批不会被抑制。 routePromptsToTerminal 关闭时 bridge 是阻塞的、客户端不会自己弹审批,此时 Island 是唯一能操作的地方,所以加了 islandOwnsBlockingPrompt 兜底,否则会话会卡住而用户只看到刘海上一个小提示。
  • 有一处改动没有放在新开关后面: isSessionFocused 原本用进程名去找终端 pid,但 IDE 里的 agent 二进制本身就在 ~/.vscode/extensions/…/claude 下、会被 isTerminal 命中,所以永远匹配不到 IDE 的 app pid。现在改成在父进程链里找最前面的 app pid。这属于修原有逻辑的 bug,所以是无条件生效的,但它会让 IDE 会话的提示音行为有一点变化。如果你希望「不开开关就完全不变」,我也可以把这块一起放进开关里。

已 rebase 到 415cc8c,locale 那个 commit 没有冲突;只新增了 1 个设置 key,没有删除或重命名任何已有的 key 和文案。

@erha19

erha19 commented Aug 20, 2026

Copy link
Copy Markdown
Owner

@weilinzung CI 报错了,修复一下

weilinzung and others added 2 commits August 20, 2026 10:20
… is focused

Reviewer feedback on the previous revision: VS Code being frontmost does not carry
the meaning of "terminal visible", so this logic should not ride on smartSuppression.

smartSuppression is left exactly as it was — same key, same subtitle, same
localisations, same gate on the pending-session auto-open, still asking only whether
a terminal window is visible on the current space. Host focus is a different
question with a wider blast radius, since it also governs approvals, questions and
sounds, so it gets its own setting, suppressWhenSessionHostFocused, defaulting off.
An install that never touches it behaves as before, which a persistence test pins.

With the setting on, and the session's own terminal or IDE frontmost, the Island
withholds attention presentation, both completion-notification queues, and
notification sounds. Suppression stops short of prompts the Island exclusively owns:
with routePromptsToTerminal off the bridge blocks and the client renders nothing, so
islandOwnsBlockingPrompt vetoes suppression there rather than stall the agent behind
a closed-notch indicator.

Resolving the host needed two fixes. Sessions do not always carry a pid — an agent
running as an IDE extension reports none in its hook metadata — so the host is
matched from SessionClientInfo.hostBundleIdentifier, which reads
terminalBundleIdentifier and falls back to bundleIdentifier. Helper bundles fold onto
their host, and only registry-recognised bundles count, which keeps the Island's own
bundle from suppressing its approvals. Where a pid does exist, isSessionFocused
compared the frontmost application pid against a terminal pid resolved by process
name, which cannot work when the agent binary lives inside the IDE's own extension
directory; ownership now comes from the session's parent chain, with name matching
kept as a fallback.

Matching is bundle-level, so two windows of one IDE cannot be told apart. The
ownership veto bounds that: a blocking approval always surfaces regardless.
The nine-term `+` chain building `keys` made Swift give up with "unable to
type-check this expression in reasonable time", which failed the Xcode test
build on every PR that merged against main.

`+` is overloaded across String, Array, RangeReplaceableCollection and the
numeric types, so a nine-term chain of differently-shaped flatMap/map results
leaves the solver a large space to search with no annotation to anchor it.
Annotating `keys` as [String] and appending each group in its own statement
gives every statement a known result type, so each one type-checks on its own.

Same keys, same order, same assertions.
@weilinzung
weilinzung force-pushed the ide-host-focus-detection branch from 6e30e49 to 9f9eb5d Compare August 20, 2026 14:22
@weilinzung

Copy link
Copy Markdown
Contributor Author

@weilinzung CI 报错了,修复一下

done.

@erha19 erha19 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这版整体方向没问题,设置持久化、宿主识别和新增策略测试也都通过了;但目前还有两个会影响实际行为的阻塞项:

  1. [HIGH] detached 模式没有接入审批/提问抑制。 PingIsland/UI/Views/NotchView.swift:1077-1100 只覆盖 docked 路径;PingIsland/UI/Window/DetachedIslandWindowController.swift:1334-1351presentExistingAttentionIfNeeded():1354-1372handleManualAttentionChange() 仍会直接展开 bubble,既不读取 suppressWhenSessionHostFocused,也不调用 shouldSuppressManualAttentionPresentation / islandOwnsBlockingPrompt。而 detached 时 NotchView 会被 shouldSuppressAutomaticPresentation 提前挡住,所以没有第二层兜底。结果是开启新开关、会话宿主位于前台时,悬浮模式的审批/提问仍自动弹出,与设置文案和本 PR 写入 AGENTS.md 的行为约定不一致。请让 detached 的“已有 attention”和“新 attention”两条入口复用同一 suppression policy,并补上 setting on/off 及 Island 独占 prompt 的回归测试。

  2. [HIGH] 被抑制的 attention 会提前吞掉全部完成通知。 PingIsland/UI/Views/NotchView.swift:1071 在异步宿主判定之前调用 clearCompletionNotifications(keepPanelOpen: true);如果随后在 :1089-1097 命中 host-focus suppression,函数直接返回,但队列中的通知已经由 markCompletionNotificationConsumed 永久消费。这样,正在前台查看 session A 时,一个被抑制的审批/提问可能把 session B 的完成提醒也静默吃掉。请把清理动作移到确认确实会展示 manual attention 之后,或至少只处理同一 session 的通知,并补一个多会话队列回归测试。

验证:PR head 9f9eb5d 的 GitHub Validate 为绿色;本地定向 Xcode tests(ManualAttentionSuppression、ProcessTreeBuilder、AppSettingsPersistence、SettingsWindowController)和 Prototype tests 均通过。上述问题属于当前测试没有覆盖到的跨 presentation/queue 接线缺口。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants