feat(hooks): expose setTitle to HookUIContext (#446)

This commit is contained in:
Aliou Diallo 2026-01-04 20:55:52 +01:00 committed by GitHub
parent affa618b43
commit 5cb7af1ddc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 34 additions and 1 deletions

View file

@ -2,6 +2,10 @@
## [Unreleased]
### Added
- Hook API: `ctx.ui.setTitle(title)` allows hooks to set the terminal window/tab title ([#446](https://github.com/badlogic/pi-mono/pull/446) by [@aliou](https://github.com/aliou))
### Changed
- Expanded keybinding documentation to list all 32 supported symbol keys with notes on ctrl+symbol behavior ([#450](https://github.com/badlogic/pi-mono/pull/450) by [@kaofelix](https://github.com/kaofelix))
@ -1341,4 +1345,4 @@ Initial public release.
- Git branch display in footer
- Message queueing during streaming responses
- OAuth integration for Gmail and Google Calendar access
- HTML export with syntax highlighting and collapsible sections
- HTML export with syntax highlighting and collapsible sections

View file

@ -437,6 +437,9 @@ ctx.ui.setWidget("my-todos", [
]);
ctx.ui.setWidget("my-todos", undefined); // Clear widget
// Set the terminal window/tab title
ctx.ui.setTitle("pi - my-project");
// Set the core input editor text (pre-fill prompts, generated content)
ctx.ui.setEditorText("Generated prompt text here...");
@ -457,6 +460,10 @@ const currentText = ctx.ui.getEditorText();
- Supports ANSI styling via `ctx.ui.theme` (including `strikethrough`)
- **Caution:** Keep widgets small (a few lines). Large widgets from multiple hooks can cause viewport overflow and TUI flicker. Max 10 lines total across all string widgets.
**Terminal title notes:**
- Uses OSC escape sequence (works in most modern terminals like iTerm2, Terminal.app, Windows Terminal)
- Useful for showing project name, current task, or session status in the terminal tab/window title
**Custom widget components:**
For more complex widgets, pass a factory function to `setWidget()`:

View file

@ -93,6 +93,7 @@ function createNoOpUIContext(): HookUIContext {
notify: () => {},
setStatus: () => {},
setWidget: () => {},
setTitle: () => {},
custom: async () => undefined as never,
setEditorText: () => {},
getEditorText: () => "",

View file

@ -59,6 +59,7 @@ const noOpUIContext: HookUIContext = {
notify: () => {},
setStatus: () => {},
setWidget: () => {},
setTitle: () => {},
custom: async () => undefined as never,
setEditorText: () => {},
getEditorText: () => "",

View file

@ -120,6 +120,13 @@ export interface HookUIContext {
*/
setWidget(key: string, content: ((tui: TUI, theme: Theme) => Component & { dispose?(): void }) | undefined): void;
/**
* Set the terminal window/tab title.
* Uses OSC escape sequence (works in most modern terminals).
* @param title - Title text to display
*/
setTitle(title: string): void;
/**
* Show a custom component with keyboard focus.
* The factory receives TUI, theme, and a done() callback to close the component.

View file

@ -685,6 +685,7 @@ export class InteractiveMode {
notify: (message, type) => this.showHookNotify(message, type),
setStatus: (key, text) => this.setHookStatus(key, text),
setWidget: (key, content) => this.setHookWidget(key, content),
setTitle: (title) => this.ui.terminal.setTitle(title),
custom: (factory) => this.showHookCustom(factory),
setEditorText: (text) => this.editor.setText(text),
getEditorText: () => this.editor.getText(),

View file

@ -145,6 +145,16 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
// Component factories are not supported in RPC mode - would need TUI access
},
setTitle(title: string): void {
// Fire and forget - host can implement terminal title control
output({
type: "hook_ui_request",
id: crypto.randomUUID(),
method: "setTitle",
title,
} as RpcHookUIRequest);
},
async custom() {
// Custom UI not supported in RPC mode
return undefined as never;

View file

@ -196,6 +196,7 @@ export type RpcHookUIRequest =
widgetKey: string;
widgetLines: string[] | undefined;
}
| { type: "hook_ui_request"; id: string; method: "setTitle"; title: string }
| { type: "hook_ui_request"; id: string; method: "set_editor_text"; text: string };
// ============================================================================

View file

@ -121,6 +121,7 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => {
notify: () => {},
setStatus: () => {},
setWidget: () => {},
setTitle: () => {},
custom: async () => undefined as never,
setEditorText: () => {},
getEditorText: () => "",