From 8e880a0449ca95393b1d0c31d23dfc5fc35a2e70 Mon Sep 17 00:00:00 2001 From: Noel Kim Date: Sun, 4 Oct 2026 14:18:53 +0900 Subject: [PATCH] fix(omp): publish genuine idle title at session startup --- src/main/pi/titlebar-extension-source.test.ts | 43 +++++++++++++++++++ src/main/pi/titlebar-extension-source.ts | 5 ++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/main/pi/titlebar-extension-source.test.ts b/src/main/pi/titlebar-extension-source.test.ts index b60eacc7be9b..bf972c74753e 100644 --- a/src/main/pi/titlebar-extension-source.test.ts +++ b/src/main/pi/titlebar-extension-source.test.ts @@ -11,6 +11,8 @@ const BRAILLE_RE = /[⠀-⣿]/ type TitlebarContext = { ui: { setTitle: (title: string) => void } + hasUI?: boolean + agent?: { kind: 'main' | 'sub' } isIdle?: () => boolean } type HookHandler = (event?: unknown, context?: TitlebarContext) => Promise | void @@ -32,6 +34,8 @@ function createHarness( paneKey?: string isIdle?: () => boolean kind?: PiAgentKind + hasUI?: boolean + agentKind?: 'main' | 'sub' processTitle?: string cwdImpl?: () => string sessionNameImpl?: () => string @@ -42,6 +46,8 @@ function createHarness( ): Harness { const titles: string[] = [] const ctx: TitlebarContext = { + hasUI: options.hasUI, + agent: options.agentKind ? { kind: options.agentKind } : undefined, ui: { setTitle: (title: string) => { options.setTitle?.(title) @@ -125,6 +131,43 @@ describe('getPiTitlebarExtensionSource', () => { expect(createHarness({ paneKey: '' }).handlers).toEqual({}) }) + it.each(['omp', 'pi'] as const)( + 'publishes a strong idle title at %s main UI startup', + async (kind) => { + const harness = createHarness({ + kind, + processTitle: 'omp', + hasUI: true, + agentKind: 'main', + isIdle: () => true + }) + + await harness.callHook('session_start') + + expect(harness.lastTitle()).toBe(IDLE_TITLE) + expect(detectAgentStatusFromTitle(harness.lastTitle() ?? '')).toBe('idle') + expect(vi.getTimerCount()).toBe(0) + + await harness.callHook('agent_start') + expect(harness.lastTitle()).toMatch(BRAILLE_RE) + await harness.callHook('agent_end') + expect(harness.lastTitle()).toBe(IDLE_TITLE) + } + ) + + it.each([ + { name: 'busy main UI', hasUI: true, agentKind: 'main', idle: false }, + { name: 'idle subagent UI', hasUI: true, agentKind: 'sub', idle: true }, + { name: 'idle main RPC', hasUI: false, agentKind: 'main', idle: true } + ] as const)('does not publish a title at $name startup', async ({ hasUI, agentKind, idle }) => { + const harness = createHarness({ kind: 'omp', hasUI, agentKind, isIdle: () => idle }) + + await harness.callHook('session_start') + + expect(harness.titles).toEqual([]) + expect(vi.getTimerCount()).toBe(0) + }) + it('stops the spinner when the agent settles', async () => { const harness = createHarness() diff --git a/src/main/pi/titlebar-extension-source.ts b/src/main/pi/titlebar-extension-source.ts index bd77e169dd5f..24d33ad58ff6 100644 --- a/src/main/pi/titlebar-extension-source.ts +++ b/src/main/pi/titlebar-extension-source.ts @@ -250,9 +250,12 @@ export function getPiTitlebarExtensionSource(kind: PiAgentKind = 'pi'): string { ' // Why: pi drops an open dialog through resetExtensionUI without resolving its promise,', ' // so a replaced or reloaded session never sends the matching close. Both boundaries', ' // prove no dialog from the old session is still on screen.', - " on('session_start', async () => {", + " on('session_start', async (_event, ctx) => {", + " if (ctx?.hasUI === false || ctx?.agent?.kind === 'sub') return", ' clearOwnedTimers()', ' resetPromptState()', + ' // Why: startup readiness needs an idle title before the first turn can be delivered.', + ' if (ctx?.isIdle?.()) stopAnimation(ctx)', ' })', '', ' // Why: modern Pi emits agent_end mid-run and only settle later, so settlement is the',