feat(tui): use OSC 8 hyperlinks in Markdown when terminal supports them#3248
Merged
Conversation
TerminalCapabilities already tracks hyperlinks: boolean and returns true for Ghostty, Kitty, WezTerm, and iTerm2, but nothing generated OSC 8 sequences. This completes that stub. Changes to packages/tui: - terminal-image.ts: add hyperlink(text, url) and setCapabilities() - index.ts: export hyperlink and setCapabilities - utils.ts: extend AnsiCodeTracker to track active OSC 8 URLs - process() now handles OSC 8 open/close sequences - getActiveCodes() re-emits the OSC 8 open at each line start - getLineEndReset() closes the OSC 8 hyperlink before each line break This ensures hyperlinks wrap correctly across multiple lines. - components/markdown.ts: link renderer uses hyperlink() when getCapabilities().hyperlinks is true; falls back to (url) text - Tests: new wrap-ansi tests for OSC 8 line-wrapping; terminal-image tests for hyperlink(); markdown tests covering both code paths; table-cell width test pinned to hyperlinks:false (checks raw columns) closes earendil-works#3239 Co-authored-by: AI (Pi/Claude Sonnet 4.6) <noreply@pi.dev>
badlogic
added a commit
that referenced
this pull request
Apr 16, 2026
OSC 8 hyperlinks landed in #3248, but detectCapabilities() returned hyperlinks: true in the unknown-terminal fallback. Terminals that silently swallow OSC 8 (most xterm-compatible hosts, tmux/screen without passthrough) end up dropping the URL from rendered markdown links entirely, since the fallback 'text (url)' rendering is skipped whenever hyperlinks is true. - Unknown terminals now default to hyperlinks: false. - tmux and screen (TMUX env, TERM starting with tmux/screen) force hyperlinks: false even when the outer terminal would otherwise advertise OSC 8 support. Image protocols also left disabled. - Added detectCapabilities tests covering the known-capable set and the tmux/screen/unknown cases.
Collaborator
|
Merged. Pushed a follow-up in 30a8a41 to address the unknown-terminal default:
550/550 tui tests pass. |
badlogic
added a commit
that referenced
this pull request
Apr 16, 2026
Add missing entries and cross-package duplications: - after_provider_response extension hook (#3128) - Compact startup header with Ctrl+O toggle (#3267) - preset example: restore original state on (none) (#3272) - OSC 8 hyperlinks in markdown (#3248) - Hyperlink capability detection tightening (#3248) - OpenAI Responses session_id headers for proxies (#3264) Add New Features summary at the top of [Unreleased].
durdn
pushed a commit
to durdn/pi-mono
that referenced
this pull request
Apr 21, 2026
…em (earendil-works#3248) TerminalCapabilities already tracks hyperlinks: boolean and returns true for Ghostty, Kitty, WezTerm, and iTerm2, but nothing generated OSC 8 sequences. This completes that stub. Changes to packages/tui: - terminal-image.ts: add hyperlink(text, url) and setCapabilities() - index.ts: export hyperlink and setCapabilities - utils.ts: extend AnsiCodeTracker to track active OSC 8 URLs - process() now handles OSC 8 open/close sequences - getActiveCodes() re-emits the OSC 8 open at each line start - getLineEndReset() closes the OSC 8 hyperlink before each line break This ensures hyperlinks wrap correctly across multiple lines. - components/markdown.ts: link renderer uses hyperlink() when getCapabilities().hyperlinks is true; falls back to (url) text - Tests: new wrap-ansi tests for OSC 8 line-wrapping; terminal-image tests for hyperlink(); markdown tests covering both code paths; table-cell width test pinned to hyperlinks:false (checks raw columns) closes earendil-works#3239 Co-authored-by: AI (Pi/Claude Sonnet 4.6) <noreply@pi.dev> Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
durdn
pushed a commit
to durdn/pi-mono
that referenced
this pull request
Apr 21, 2026
OSC 8 hyperlinks landed in earendil-works#3248, but detectCapabilities() returned hyperlinks: true in the unknown-terminal fallback. Terminals that silently swallow OSC 8 (most xterm-compatible hosts, tmux/screen without passthrough) end up dropping the URL from rendered markdown links entirely, since the fallback 'text (url)' rendering is skipped whenever hyperlinks is true. - Unknown terminals now default to hyperlinks: false. - tmux and screen (TMUX env, TERM starting with tmux/screen) force hyperlinks: false even when the outer terminal would otherwise advertise OSC 8 support. Image protocols also left disabled. - Added detectCapabilities tests covering the known-capable set and the tmux/screen/unknown cases.
durdn
pushed a commit
to durdn/pi-mono
that referenced
this pull request
Apr 21, 2026
Add missing entries and cross-package duplications: - after_provider_response extension hook (earendil-works#3128) - Compact startup header with Ctrl+O toggle (earendil-works#3267) - preset example: restore original state on (none) (earendil-works#3272) - OSC 8 hyperlinks in markdown (earendil-works#3248) - Hyperlink capability detection tightening (earendil-works#3248) - OpenAI Responses session_id headers for proxies (earendil-works#3264) Add New Features summary at the top of [Unreleased].
byte-rose
pushed a commit
to byte-rose/pi-mono
that referenced
this pull request
Apr 28, 2026
…em (earendil-works#3248) TerminalCapabilities already tracks hyperlinks: boolean and returns true for Ghostty, Kitty, WezTerm, and iTerm2, but nothing generated OSC 8 sequences. This completes that stub. Changes to packages/tui: - terminal-image.ts: add hyperlink(text, url) and setCapabilities() - index.ts: export hyperlink and setCapabilities - utils.ts: extend AnsiCodeTracker to track active OSC 8 URLs - process() now handles OSC 8 open/close sequences - getActiveCodes() re-emits the OSC 8 open at each line start - getLineEndReset() closes the OSC 8 hyperlink before each line break This ensures hyperlinks wrap correctly across multiple lines. - components/markdown.ts: link renderer uses hyperlink() when getCapabilities().hyperlinks is true; falls back to (url) text - Tests: new wrap-ansi tests for OSC 8 line-wrapping; terminal-image tests for hyperlink(); markdown tests covering both code paths; table-cell width test pinned to hyperlinks:false (checks raw columns) closes earendil-works#3239 Co-authored-by: AI (Pi/Claude Sonnet 4.6) <noreply@pi.dev> Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
byte-rose
pushed a commit
to byte-rose/pi-mono
that referenced
this pull request
Apr 28, 2026
OSC 8 hyperlinks landed in earendil-works#3248, but detectCapabilities() returned hyperlinks: true in the unknown-terminal fallback. Terminals that silently swallow OSC 8 (most xterm-compatible hosts, tmux/screen without passthrough) end up dropping the URL from rendered markdown links entirely, since the fallback 'text (url)' rendering is skipped whenever hyperlinks is true. - Unknown terminals now default to hyperlinks: false. - tmux and screen (TMUX env, TERM starting with tmux/screen) force hyperlinks: false even when the outer terminal would otherwise advertise OSC 8 support. Image protocols also left disabled. - Added detectCapabilities tests covering the known-capable set and the tmux/screen/unknown cases.
byte-rose
pushed a commit
to byte-rose/pi-mono
that referenced
this pull request
Apr 28, 2026
Add missing entries and cross-package duplications: - after_provider_response extension hook (earendil-works#3128) - Compact startup header with Ctrl+O toggle (earendil-works#3267) - preset example: restore original state on (none) (earendil-works#3272) - OSC 8 hyperlinks in markdown (earendil-works#3248) - Hyperlink capability detection tightening (earendil-works#3248) - OpenAI Responses session_id headers for proxies (earendil-works#3264) Add New Features summary at the top of [Unreleased].
larsboes
pushed a commit
to larsboes/pi-mono
that referenced
this pull request
Apr 30, 2026
…em (earendil-works#3248) TerminalCapabilities already tracks hyperlinks: boolean and returns true for Ghostty, Kitty, WezTerm, and iTerm2, but nothing generated OSC 8 sequences. This completes that stub. Changes to packages/tui: - terminal-image.ts: add hyperlink(text, url) and setCapabilities() - index.ts: export hyperlink and setCapabilities - utils.ts: extend AnsiCodeTracker to track active OSC 8 URLs - process() now handles OSC 8 open/close sequences - getActiveCodes() re-emits the OSC 8 open at each line start - getLineEndReset() closes the OSC 8 hyperlink before each line break This ensures hyperlinks wrap correctly across multiple lines. - components/markdown.ts: link renderer uses hyperlink() when getCapabilities().hyperlinks is true; falls back to (url) text - Tests: new wrap-ansi tests for OSC 8 line-wrapping; terminal-image tests for hyperlink(); markdown tests covering both code paths; table-cell width test pinned to hyperlinks:false (checks raw columns) closes earendil-works#3239 Co-authored-by: AI (Pi/Claude Sonnet 4.6) <noreply@pi.dev> Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
larsboes
pushed a commit
to larsboes/pi-mono
that referenced
this pull request
Apr 30, 2026
OSC 8 hyperlinks landed in earendil-works#3248, but detectCapabilities() returned hyperlinks: true in the unknown-terminal fallback. Terminals that silently swallow OSC 8 (most xterm-compatible hosts, tmux/screen without passthrough) end up dropping the URL from rendered markdown links entirely, since the fallback 'text (url)' rendering is skipped whenever hyperlinks is true. - Unknown terminals now default to hyperlinks: false. - tmux and screen (TMUX env, TERM starting with tmux/screen) force hyperlinks: false even when the outer terminal would otherwise advertise OSC 8 support. Image protocols also left disabled. - Added detectCapabilities tests covering the known-capable set and the tmux/screen/unknown cases.
larsboes
pushed a commit
to larsboes/pi-mono
that referenced
this pull request
Apr 30, 2026
Add missing entries and cross-package duplications: - after_provider_response extension hook (earendil-works#3128) - Compact startup header with Ctrl+O toggle (earendil-works#3267) - preset example: restore original state on (none) (earendil-works#3272) - OSC 8 hyperlinks in markdown (earendil-works#3248) - Hyperlink capability detection tightening (earendil-works#3248) - OpenAI Responses session_id headers for proxies (earendil-works#3264) Add New Features summary at the top of [Unreleased].
PSU3D0
added a commit
to PSU3D0/pi-mono
that referenced
this pull request
May 13, 2026
Consolidates 62 upstream commits (v0.67.4 -> v0.67.68) into the fork. Preserves all fork-local work (OAuth cloak, antigravity pool, context tiers, OSC 8 file hyperlinks, gemini3 sig skip, codex_cli_rs headers, compaction context hooks, gpt-5.4 models). Conflict resolutions (4 files): - packages/ai/src/providers/google-gemini-cli.ts: layered upstream's onResponse hook call into our pool-aware retry loop; kept our finally-block pool.saveNow() path. - packages/tui/src/utils.ts: adopted upstream's AnsiCodeTracker OSC 8 impl wholesale. It's cleaner than our f16dfd5 version (preserves hyperlink across SGR reset via separate clear(), emits ST-terminated sequences). Dropped our 7d75fee SEGMENT_RESET fix since upstream's design never introduced the bug. - packages/tui/src/components/markdown.ts: kept our resolveHref hook and wrapHyperlink helper but gated OSC 8 emission on getCapabilities().hyperlinks; switched to upstream's hyperlink() helper for consistency. Merges file path linking (ours) with terminal capability detection (upstream earendil-works#3248, #30a8a41f). - packages/tui/test/{markdown,wrap-ansi}.test.ts: updated our OSC 8 assertions to ST-terminated sequences (\x1b\\) to match new emitter; added setCapabilities({hyperlinks:true}) gating where needed; afterEach resetCapabilitiesCache to isolate test state. Dependency upgrades from upstream: - @anthropic-ai/sdk 0.73.0 -> 0.90.0 - @aws-sdk/client-bedrock-runtime 3.983.0 -> 3.1030.0 - @mistralai/mistralai 1.14.1 -> 2.2.0 Upstream features folded in: - fix(ai): Opus 4.7 adaptive thinking + xhigh effort (earendil-works#3286) - feat(ai): thinkingDisplay option (summarized/omitted/raw) - feat(coding-agent): after_provider_response hook (earendil-works#3128) - fix(ai): trust requested Codex service tier (earendil-works#3307) - feat(bedrock): Bearer token auth for Converse API (earendil-works#3125) - feat(agent,coding-agent): per-tool executionMode override (earendil-works#3345) - feat(tui): OSC 8 hyperlinks with terminal capability detection (earendil-works#3248, #30a8a41f) - feat(coding-agent,tui): argument-hint frontmatter in prompts (earendil-works#2780) - Plus ~40 smaller fixes across ai/coding-agent/tui/agent. Verification: - packages/tui: 559/559 tests pass - packages/ai: 144/144 OAuth cloak tests pass; all fork-local suites pass (antigravity-pool, context-tiers, gemini3-unsigned-tool-call, codex-stream, supports-xhigh, etc.). Remaining test failures (13) are pre-existing live-API E2E tests (no credentials in this env). - packages/agent: 39/39 tests pass. - packages/coding-agent: 1020/1021 tests pass; 1 flaky bash timeout test (passes in isolation), unrelated to merge. - All four packages build cleanly.
This was referenced Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3239
The issue feedback was correct: the original approach didn't handle line wrapping. This PR addresses that by extending
AnsiCodeTrackerinutils.tsto track active OSC 8 URLs alongside SGR state.How wrapping is handled:
wrapSingleLinealready re-emits active SGR codes at each line start viatracker.getActiveCodes(), and closes problematic attributes (underline) before each line break viatracker.getLineEndReset(). This PR extends both methods to cover OSC 8:process()now handles\x1b]8;;url\x1b\\(open) and\x1b]8;;\x1b\\(close)getActiveCodes()appends the OSC 8 re-open after SGR codes when a hyperlink is activegetLineEndReset()appends the OSC 8 close before each line breakbreakLongWorduses the samegetActiveCodes()path so it's covered too.Changes:
src/utils.ts: extendAnsiCodeTrackerwithactiveHyperlinktrackingsrc/terminal-image.ts: addhyperlink(text, url)andsetCapabilities()for testssrc/index.ts: export bothsrc/components/markdown.ts: usehyperlink()incase "link":whengetCapabilities().hyperlinksis truetest/wrap-ansi.test.ts: 3 new tests for OSC 8 wrappingtest/terminal-image.test.ts: 4 new tests forhyperlink()test/markdown.test.ts: 4 new tests (both hyperlink and fallback paths), updated existing link tests to set capabilities explicitly540/540 tests pass.
biomeandtsgo --noEmitclean onpackages/tui.