Skip to content

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

Open
BingqingLyu wants to merge 3 commits into
mainfrom
fork-pr-3562-feat-cli-osc-notifications-2528
Open

feat(cli): add OSC notification support for iTerm2, Kitty, and Ghostty#109
BingqingLyu wants to merge 3 commits into
mainfrom
fork-pr-3562-feat-cli-osc-notifications-2528

Conversation

@BingqingLyu

@BingqingLyu BingqingLyu commented Apr 27, 2026

Copy link
Copy Markdown
Owner

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 QwenLM#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 QwenLM#2528


🤖 Generated with Qwen Code

dreamWB added 3 commits April 23, 2026 21:36
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
- 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
- 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
@BingqingLyu BingqingLyu added conflicting-group-1 conflicting-group-1 Conflicting PR group 1 — review as a batch conflicting-pr Shares at least one cross-PR dependency with other PRs and removed conflicting-group-1 labels May 7, 2026
@BingqingLyu

BingqingLyu commented May 7, 2026

Copy link
Copy Markdown
Owner Author

Conflict Group 1

This PR shares modified functions with 8 other PR(s): #100, #107, #113, #114, #117, #52, #88, #96.

These PRs should be reviewed as a batch — merging one may affect the others.

Function File Also modified by
isToolExecuting AppContainer.tsx #100, #107, #113, #114, #117, #52, #88, #96
graph LR
    PR109["PR #109"]
    FisToolExecuting_7261["isToolExecuting<br>AppContainer.tsx"]
    PR109 -->|modifies| FisToolExecuting_7261
    PR100["PR #100"]
    PR100 -->|modifies| FisToolExecuting_7261
    PR107["PR #107"]
    PR107 -->|modifies| FisToolExecuting_7261
    PR113["PR #113"]
    PR113 -->|modifies| FisToolExecuting_7261
    PR114["PR #114"]
    PR114 -->|modifies| FisToolExecuting_7261
    PR117["PR #117"]
    PR117 -->|modifies| FisToolExecuting_7261
    PR52["PR #52"]
    PR52 -->|modifies| FisToolExecuting_7261
    PR88["PR #88"]
    PR88 -->|modifies| FisToolExecuting_7261
    PR96["PR #96"]
    PR96 -->|modifies| FisToolExecuting_7261
Loading

Posted by codegraph-ai conflict detection.

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

Labels

conflicting-group-1 Conflicting PR group 1 — review as a batch conflicting-pr Shares at least one cross-PR dependency with other PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] support OSC notify

2 participants