diff --git a/openspec/changes/add-synchronized-output/design.md b/openspec/changes/add-synchronized-output/design.md index f77aeae9..a58a1e2f 100644 --- a/openspec/changes/add-synchronized-output/design.md +++ b/openspec/changes/add-synchronized-output/design.md @@ -36,7 +36,7 @@ New pure helper `detect_synchronized_output_support_with(caps, env_lookup)` in `terminal_capability`: - **Blacklist (off, highest priority):** `ConEmuPID` set (ConEmu/Cmder), `caps.is_legacy_conhost` or `caps.is_classic_conhost`, `TERM` starting with `tmux` or `screen` (multiplexer version unknowable from env; force-able via config). -- **Whitelist (on):** `WT_SESSION` set (Windows Terminal), `KITTY_WINDOW_ID` set or `TERM == "xterm-kitty"` (kitty), `TERM_PROGRAM` in `iTerm.app` / `WezTerm` / `ghostty` / `vscode` / `Apple_Terminal` / `WarpTerminal` / `contour` / `mintty`, `TERM` starting with `foot` or `ghostty`. +- **Whitelist (on):** `WT_SESSION` set (Windows Terminal), `KITTY_WINDOW_ID` set or `TERM == "xterm-kitty"` (kitty), `TERM_PROGRAM` in `iTerm.app` / `WezTerm` / `ghostty` / `vscode` / `WarpTerminal` / `contour` / `mintty` (Apple_Terminal / macOS Terminal.app excluded: older versions don't implement DEC 2026; opt in via `tui.sync_output_mode="always"`), `TERM` starting with `foot` or `ghostty`. - **Unknown (off):** anything else, including POSIX terminals with no recognizable markers (e.g. Alacritty, plain `xterm-256color`). On non-Windows builds the Windows-only signals simply never fire and the POSIX rules apply. Terminals that ignore unknown DEC modes harmlessly would tolerate unconditional sending, but the conservative default protects the legacy-Windows moat and surprises no one; `sync_output_mode: "always"` overrides for adventurous users. diff --git a/src/utils/terminal_capability.cpp b/src/utils/terminal_capability.cpp index 823af607..b55aec97 100644 --- a/src/utils/terminal_capability.cpp +++ b/src/utils/terminal_capability.cpp @@ -115,7 +115,6 @@ bool term_program_whitelisted(const std::string& value) { "WezTerm", "ghostty", "vscode", // VS Code 内嵌终端(xterm.js) - "Apple_Terminal", // macOS Terminal.app "WarpTerminal", "contour", "mintty", diff --git a/src/utils/terminal_capability.hpp b/src/utils/terminal_capability.hpp index 54ed4780..e13dc1de 100644 --- a/src/utils/terminal_capability.hpp +++ b/src/utils/terminal_capability.hpp @@ -63,7 +63,9 @@ inline bool should_use_conhost_compat_layout(const TerminalCapabilities& caps) { // - TERM 以 "tmux" / "screen" 开头(复用器) → false // - WT_SESSION / KITTY_WINDOW_ID 存在 → true // - TERM_PROGRAM ∈ {iTerm.app, WezTerm, ghostty, vscode, -// Apple_Terminal, WarpTerminal, contour, mintty} → true +// WarpTerminal, contour, mintty} → true +// (Apple_Terminal / macOS Terminal.app 不在此列:旧版本不支持 DEC 2026, +// 保守关闭;需用时用 tui.sync_output_mode="always" 强制开启) // - TERM == xterm-kitty 或以 foot/ghostty 开头 → true // - 其它(未知终端,如 Alacritty / 裸 xterm-256color) → false // diff --git a/tests/utils/terminal_capability_test.cpp b/tests/utils/terminal_capability_test.cpp index 212cc3ac..e222dbbb 100644 --- a/tests/utils/terminal_capability_test.cpp +++ b/tests/utils/terminal_capability_test.cpp @@ -255,10 +255,10 @@ TEST(SynchronizedOutputSupport, KittyTermOn) { } // 场景:TERM_PROGRAM 白名单(iTerm.app / WezTerm / ghostty / vscode / -// Apple_Terminal / WarpTerminal / contour / mintty)→ 开启 +// WarpTerminal / contour / mintty)→ 开启(Apple_Terminal 已移除,见下) TEST(SynchronizedOutputSupport, TermProgramWhitelistOn) { for (const char* name : {"iTerm.app", "WezTerm", "ghostty", "vscode", - "Apple_Terminal", "WarpTerminal", "contour", + "WarpTerminal", "contour", "mintty"}) { TerminalCapabilities caps; EXPECT_TRUE(detect_synchronized_output_support_with( @@ -268,6 +268,16 @@ TEST(SynchronizedOutputSupport, TermProgramWhitelistOn) { } } +// 场景:Apple Terminal.app 不支持 DEC mode 2026(尤其 macOS 12 及更早版本), +// 从白名单移除,auto 模式默认关闭;需要时用 tui.sync_output_mode="always" 强制开启。 +TEST(SynchronizedOutputSupport, AppleTerminalOff) { + TerminalCapabilities caps; + EXPECT_FALSE(detect_synchronized_output_support_with( + caps, make_sync_env_lookup(std::nullopt, std::nullopt, std::nullopt, + std::string("Apple_Terminal"), std::nullopt))) + << "Apple_Terminal must NOT be whitelisted for DEC 2026"; +} + // 场景:TERM 前缀白名单(foot / ghostty)→ 开启 TEST(SynchronizedOutputSupport, TermPrefixWhitelistOn) { for (const char* term : {"foot", "foot-256color", "ghostty",