mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(tui): split terminal tab title from window title
Terminal.app truncates background tab titles from the left, so a single long OSC 0 string (marker · session · model · cwd) leaves only the tail visible — usually the cwd or process name. Emit OSC 1 (icon/tab) with just the short session title and OSC 2 (window) with the full composed string, so background tabs show the session name instead of the cwd tail.
This commit is contained in:
parent
866c9adae3
commit
f1a91ad416
5 changed files with 48 additions and 10 deletions
1
ui-tui/packages/hermes-ink/index.d.ts
vendored
1
ui-tui/packages/hermes-ink/index.d.ts
vendored
|
|
@ -29,6 +29,7 @@ export { default as useStdin } from './src/ink/hooks/use-stdin.ts'
|
|||
export { useTabStatus } from './src/ink/hooks/use-tab-status.ts'
|
||||
export { useTerminalFocus } from './src/ink/hooks/use-terminal-focus.ts'
|
||||
export { useTerminalTitle } from './src/ink/hooks/use-terminal-title.ts'
|
||||
export type { TerminalTitlePair } from './src/ink/hooks/use-terminal-title.ts'
|
||||
export { useTerminalViewport } from './src/ink/hooks/use-terminal-viewport.ts'
|
||||
export { default as measureElement } from './src/ink/measure-element.ts'
|
||||
export { createRoot, forceRedraw, default as render, renderSync } from './src/ink/root.ts'
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export { default as useStdin } from './ink/hooks/use-stdin.js'
|
|||
export { useTabStatus } from './ink/hooks/use-tab-status.js'
|
||||
export { useTerminalFocus } from './ink/hooks/use-terminal-focus.js'
|
||||
export { useTerminalTitle } from './ink/hooks/use-terminal-title.js'
|
||||
export type { TerminalTitlePair } from './ink/hooks/use-terminal-title.js'
|
||||
export { useTerminalViewport } from './ink/hooks/use-terminal-viewport.js'
|
||||
export { default as measureElement } from './ink/measure-element.js'
|
||||
export { scrollFastPathStats, type ScrollFastPathStats } from './ink/render-node-to-output.js'
|
||||
|
|
|
|||
|
|
@ -7,15 +7,20 @@ import { TerminalWriteContext } from '../useTerminalNotification.js'
|
|||
/**
|
||||
* Declaratively set the terminal tab/window title.
|
||||
*
|
||||
* Pass a string to set the title. ANSI escape sequences are stripped
|
||||
* automatically so callers don't need to know about terminal encoding.
|
||||
* Pass a single string to set both the tab and window title (OSC 0).
|
||||
* Pass `{ tab, window }` to set them independently: the short `tab` string
|
||||
* goes to OSC 1 (icon/tab label) and the longer `window` string goes to
|
||||
* OSC 2 (window title bar). This matters for terminals like Apple
|
||||
* Terminal.app whose narrow background tabs truncate the title from the
|
||||
* left — a single long OSC 0 string leaves only the tail visible, while a
|
||||
* separate short OSC 1 keeps the session name readable.
|
||||
*
|
||||
* Pass `null` to opt out — the hook becomes a no-op and leaves the
|
||||
* terminal title untouched.
|
||||
*
|
||||
* On Windows, uses `process.title` (classic conhost doesn't support OSC).
|
||||
* Elsewhere, writes OSC 0 (set title+icon) via Ink's stdout.
|
||||
*/
|
||||
export function useTerminalTitle(title: string | null): void {
|
||||
export function useTerminalTitle(title: string | TerminalTitlePair | null): void {
|
||||
const writeRaw = useContext(TerminalWriteContext)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -23,12 +28,34 @@ export function useTerminalTitle(title: string | null): void {
|
|||
return
|
||||
}
|
||||
|
||||
const clean = stripAnsi(title)
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const clean = stripAnsi(typeof title === 'string' ? title : title.window ?? title.tab ?? '')
|
||||
process.title = clean
|
||||
} else {
|
||||
writeRaw(osc(OSC.SET_TITLE_AND_ICON, clean))
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof title === 'string') {
|
||||
writeRaw(osc(OSC.SET_TITLE_AND_ICON, stripAnsi(title)))
|
||||
return
|
||||
}
|
||||
|
||||
// Separate tab (OSC 1) and window (OSC 2) titles so narrow tab bars
|
||||
// show the short session name instead of a truncated tail.
|
||||
const tab = stripAnsi(title.tab ?? '')
|
||||
const window = stripAnsi(title.window ?? '')
|
||||
if (tab && window) {
|
||||
writeRaw(osc(OSC.SET_ICON, tab) + osc(OSC.SET_TITLE, window))
|
||||
} else if (window) {
|
||||
writeRaw(osc(OSC.SET_TITLE_AND_ICON, window))
|
||||
} else if (tab) {
|
||||
writeRaw(osc(OSC.SET_TITLE_AND_ICON, tab))
|
||||
}
|
||||
}, [title, writeRaw])
|
||||
}
|
||||
|
||||
export interface TerminalTitlePair {
|
||||
/** Short title for the tab/icon label (OSC 1). */
|
||||
tab?: string
|
||||
/** Full title for the window title bar (OSC 2). */
|
||||
window?: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -626,7 +626,12 @@ export function useMainApp(gw: GatewayClient) {
|
|||
const tabCwd = ui.info?.cwd
|
||||
|
||||
useTerminalTitle(
|
||||
model ? composeTabTitle(marker, ui.sessionTitle, model, tabCwd ? shortCwd(tabCwd, 24) : '') : 'Hermes'
|
||||
model
|
||||
? {
|
||||
tab: composeTabTitle(marker, ui.sessionTitle, '', ''),
|
||||
window: composeTabTitle(marker, ui.sessionTitle, model, tabCwd ? shortCwd(tabCwd, 24) : ''),
|
||||
}
|
||||
: 'Hermes'
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
6
ui-tui/src/types/hermes-ink.d.ts
vendored
6
ui-tui/src/types/hermes-ink.d.ts
vendored
|
|
@ -167,7 +167,11 @@ declare module '@hermes/ink' {
|
|||
readonly write: (data: string) => boolean
|
||||
}
|
||||
export function useTerminalFocus(): boolean
|
||||
export function useTerminalTitle(title: string | null): void
|
||||
export function useTerminalTitle(title: string | TerminalTitlePair | null): void
|
||||
export interface TerminalTitlePair {
|
||||
tab?: string
|
||||
window?: string
|
||||
}
|
||||
export function useDeclaredCursor(args: {
|
||||
readonly line: number
|
||||
readonly column: number
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue