diff --git a/src/main/runtime/orca-runtime-mobile-agent-status-title-truthfulness.test.ts b/src/main/runtime/orca-runtime-mobile-agent-status-title-truthfulness.test.ts index 9534d7a3ddd..80858345aa9 100644 --- a/src/main/runtime/orca-runtime-mobile-agent-status-title-truthfulness.test.ts +++ b/src/main/runtime/orca-runtime-mobile-agent-status-title-truthfulness.test.ts @@ -207,6 +207,40 @@ describe('mobile session tabs: live-title evidence vs published agent status', ( expect.objectContaining({ state: 'done', prompt: '' }) ) }) + + it('keeps a hook-backed working status that postdates an initial Windows ConPTY shell title', async () => { + vi.useFakeTimers() + try { + vi.setSystemTime(T0 - 1_000) + const runtime = await createRuntime() + runtime.onPtyData(PTY_ID, '\x1b]0;C:\\WINDOWS\\system32\\cmd.exe\x07', T0 - 1_000) + + vi.setSystemTime(T0) + publishRendererWorkingPane(runtime, 'C:\\WINDOWS\\system32\\cmd.exe') + + expect(await projectAgentStatus(runtime)).toEqual( + expect.objectContaining({ + state: 'working', + prompt: 'Configurar y entender los comprobantes', + updatedAt: T0 + }) + ) + + // Once a newer shell title arrives after the hook, it retires the working status to done. + vi.setSystemTime(T0 + 1_000) + runtime.onPtyData(PTY_ID, '\x1b]0;C:\\WINDOWS\\system32\\cmd.exe\x07', T0 + 1_000) + + expect(await projectAgentStatus(runtime)).toEqual( + expect.objectContaining({ + state: 'done', + prompt: '', + updatedAt: T0 + 1_000 + }) + ) + } finally { + vi.useRealTimers() + } + }) }) describe('mobile session tabs: identity-only fallback freshness', () => { diff --git a/src/main/runtime/runtime-mobile-session-projection.ts b/src/main/runtime/runtime-mobile-session-projection.ts index 61ad14e22b9..8819b44dfcf 100644 --- a/src/main/runtime/runtime-mobile-session-projection.ts +++ b/src/main/runtime/runtime-mobile-session-projection.ts @@ -226,10 +226,17 @@ export function projectRuntimeMobileSessionTabs( const hasLiveAgentSignal = normalizedTabAgentStatus?.interactivePrompt != null || normalizedTabAgentStatus?.toolName != null + // A title renew already dated against the hook (e.g. ConPTY's spawn-time shell title) cannot re-demote it (#32467). + const hookOutlivedAdjudicatedTitle = + normalizedTabAgentStatus?.state !== 'done' && + statusPty?.lastOscTitleEpochMs != null && + liveTitleEvidence === (statusPty.lastOscTitle?.trim() || null) // Only shell/management proves release; neutral Terminal/editor/cwd titles cannot justify done that fights clients' live status. const keepFullAgentStatus = normalizedTabAgentStatus && - (!terminalTitleBlocksExplicitAgentStatus(liveTitleEvidence) || hasLiveAgentSignal) + (hookOutlivedAdjudicatedTitle || + !terminalTitleBlocksExplicitAgentStatus(liveTitleEvidence) || + hasLiveAgentSignal) const agentStatus = keepFullAgentStatus ? { agentStatus: normalizedTabAgentStatus } : // Idle titles drop stale working spinners but retain transcript identity for native chat.