Skip to content

feat(cli): add OSC notification support for iTerm2, Kitty, and Ghostty#3562

Merged
yiliang114 merged 3 commits into
QwenLM:mainfrom
dreamWB:feat/cli-osc-notifications-2528
Apr 27, 2026
Merged

feat(cli): add OSC notification support for iTerm2, Kitty, and Ghostty#3562
yiliang114 merged 3 commits into
QwenLM:mainfrom
dreamWB:feat/cli-osc-notifications-2528

Conversation

@dreamWB

@dreamWB dreamWB commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator

TLDR

This PR adds OSC (Operating System Command) notification support to replace the basic terminal bell with rich, protocol-specific system notifications.

What changed:

  • Added terminal detection logic (TERM → TERM_PROGRAM → KITTY_WINDOW_ID fallback chain)
  • Implemented OSC 9 (iTerm2), OSC 99 (Kitty 3-step), and OSC 777 (Ghostty/cmux) notification protocols
  • Added tmux/screen DCS passthrough support with ESC byte doubling
  • Created notification routing service with automatic terminal detection
  • Added dynamic tool name display in approval notifications
  • Refactored useTerminalProgress to use shared osc.ts module
  • Added 42 unit tests covering all detection paths and protocols

Why:
The original terminal bell (\x07) is barely noticeable in modern workflows. This PR enables rich system notifications that work seamlessly with terminal multiplexers like cmux, improving developer experience when Qwen Code runs in the background or requires attention.

Closes #2528

Screenshots / Video Demo

Tool approval notification:

  • Triggers when Qwen Code is waiting for permission to use a tool and the terminal window is not focused
  • Message: "Qwen needs your permission to use {toolName}"

Long task completion notification:

  • Triggers when a task that ran for more than 20 seconds completes and the terminal window is not focused
  • Message: "Qwen is waiting for your input"

Both notifications require terminalBell: true in settings (enabled by default). The notification method is automatically selected based on your
terminal: OSC 9 for iTerm2, OSC 99 for Kitty, OSC 777 for Ghostty/cmux, and terminal bell as fallback for other terminals.

cmux
cmux1
cmux2

iTerm2
iTerm 1
iTerm

Dive Deeper

Terminal Detection Strategy

The implementation uses a fallback chain to detect the terminal type:

  1. Check TERM_PROGRAM environment variable (most reliable for iTerm2, Apple_Terminal, WezTerm)
  2. Check KITTY_WINDOW_ID for Kitty terminal
  3. Fall back to basic OSC 777 for Ghostty/cmux

Protocol Support

  • OSC 9 (iTerm2): Simple one-shot notification
  • OSC 99 (Kitty): 3-step protocol for better control
  • OSC 777 (Ghostty/cmux): Modern notification format
  • DCS passthrough: Special handling for tmux/screen sessions with ESC byte doubling

Architecture

  • osc.ts: Core OSC protocol implementations
  • notificationService.ts: Routing service that auto-detects terminal and dispatches
  • React hooks: useTerminalNotification and useAttentionNotifications for UI integration

Reviewer Test Plan

  1. Test on different terminals:

    • iTerm2: Check that notifications appear with proper title/message
    • Kitty: Verify OSC 99 3-step protocol works
    • Ghostty: Confirm OSC 777 format is correct
    • tmux/screen: Ensure DCS passthrough works correctly
  2. Test scenarios:

    • Tool approval requests should show dynamic tool name
    • Background task completion should trigger notification
    • Error states should display with proper urgency
  3. Run unit tests:

    cd packages/cli && npx vitest run src/utils/osc.test.ts
    cd packages/cli && npx vitest run src/services/notificationService.test.ts
    cd packages/cli && npx vitest run src/ui/hooks/useTerminalNotification.test.ts
    cd packages/cli && npx vitest run src/ui/hooks/useAttentionNotifications.test.ts

Testing Matrix

🍏 🪟 🐧
npm run
npx
Docker
Podman - -
Seatbelt - -

Linked issues / bugs

Closes #2528


🤖 Generated with Qwen Code

Replace the basic terminal bell with protocol-specific OSC notifications
that display rich system notifications with title and message content.

- Add terminal detection (TERM → TERM_PROGRAM → KITTY_WINDOW_ID fallback)
- Add OSC 9 (iTerm2), OSC 99 (Kitty 3-step), OSC 777 (Ghostty/cmux)
- Add tmux/screen DCS passthrough with ESC byte doubling
- Add notification routing service with auto terminal detection
- Add dynamic tool name in approval notifications
- Refactor useTerminalProgress to use shared osc.ts module
- 42 unit tests covering all detection paths and protocols

Closes QwenLM#2528

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(Superseded by inline review below)

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overall: nice feature direction — bell-only is barely noticeable in modern terminals. Architecture is clean, test coverage is solid. Inline comments below.

Comment thread packages/cli/src/ui/hooks/useTerminalNotification.ts Outdated
Comment thread packages/cli/src/ui/hooks/useTerminalNotification.ts
Comment thread packages/cli/src/ui/hooks/useAttentionNotifications.ts
Comment thread packages/cli/src/ui/hooks/useTerminalProgress.ts Outdated
Comment thread packages/cli/src/ui/hooks/useAttentionNotifications.ts Outdated
Comment thread packages/cli/src/ui/hooks/useAttentionNotifications.ts Outdated
Comment thread packages/cli/src/utils/osc.ts Outdated
Comment thread packages/cli/src/utils/osc.ts
- Reorder terminal detection: TERM_PROGRAM first, TERM fallback, KITTY_WINDOW_ID last
- Add TTY guard in sendNotification to skip OSC when stdout is piped
- Add sanitizeOscPayload to prevent control character injection in OSC payloads
- Replace hand-rolled PendingToolCall with imported TrackedToolCall type
- Extract awaiting tool name via useMemo to avoid useEffect re-fires
- Unify brand name to "Qwen Code" in all notification messages
- Remove unused TerminalWriteContext/Provider/Hook exports
- Fix docstring: OSC_PREFIX 9;4 → OSC 9;4
Comment thread packages/cli/src/utils/osc.ts
Comment thread packages/cli/src/utils/osc.ts
- Add encodeKittyPayload() to base64-encode UTF-8 text for Kitty OSC 99
- oscKittyNotify() now uses e=1 flag with base64-encoded title/body
- osc() falls back to BEL terminator for Kitty inside GNU screen to
  avoid ST conflicting with the DCS passthrough wrapper's own ST

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No issues found. LGTM! ✅ — gpt-5.5 via Qwen Code /review

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No blocking issues found. LGTM! ✅ — gpt-5.5 via Qwen Code /review

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice work — clean protocol abstraction and good test coverage across the detection paths. LGTM!

@yiliang114 yiliang114 merged commit 00ba2ef into QwenLM:main Apr 27, 2026
13 checks passed
@dreamWB dreamWB deleted the feat/cli-osc-notifications-2528 branch May 14, 2026 08:17
xaelistic pushed a commit to xaelistic/qwen-code that referenced this pull request Jun 7, 2026
QwenLM#3562)

* feat(cli): add OSC notification support for iTerm2, Kitty, and Ghostty

Replace the basic terminal bell with protocol-specific OSC notifications
that display rich system notifications with title and message content.

- Add terminal detection (TERM → TERM_PROGRAM → KITTY_WINDOW_ID fallback)
- Add OSC 9 (iTerm2), OSC 99 (Kitty 3-step), OSC 777 (Ghostty/cmux)
- Add tmux/screen DCS passthrough with ESC byte doubling
- Add notification routing service with auto terminal detection
- Add dynamic tool name in approval notifications
- Refactor useTerminalProgress to use shared osc.ts module
- 42 unit tests covering all detection paths and protocols

Closes QwenLM#2528

* fix(cli): address PR review feedback for OSC notification system

- Reorder terminal detection: TERM_PROGRAM first, TERM fallback, KITTY_WINDOW_ID last
- Add TTY guard in sendNotification to skip OSC when stdout is piped
- Add sanitizeOscPayload to prevent control character injection in OSC payloads
- Replace hand-rolled PendingToolCall with imported TrackedToolCall type
- Extract awaiting tool name via useMemo to avoid useEffect re-fires
- Unify brand name to "Qwen Code" in all notification messages
- Remove unused TerminalWriteContext/Provider/Hook exports
- Fix docstring: OSC_PREFIX 9;4 → OSC 9;4

* fix(cli): base64-encode Kitty OSC 99 payloads and fix screen ST conflict

- Add encodeKittyPayload() to base64-encode UTF-8 text for Kitty OSC 99
- oscKittyNotify() now uses e=1 flag with base64-encoded title/body
- osc() falls back to BEL terminator for Kitty inside GNU screen to
  avoid ST conflicting with the DCS passthrough wrapper's own ST
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.

[Feature Request] support OSC notify

3 participants