From 5c1eb6702bc77e2efca8c58ebf5baaa295b715a8 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:47:33 -0700 Subject: [PATCH] fix(terminal): fold only bare identity frames into the group token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A legacy "π - - " title is Pi-compatible too, so folding every profile match collapsed two different sessions to the same signature and suppressed the change outright — reintroducing #16093 through the churn signature. Fold only exact bare identity frames, matched per wrapper segment, so semantic session titles keep comparing on their own text. Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com> --- ...p-pi-alternating-title-frame-churn.test.ts | 21 +++++++++++++ src/shared/agent-title-owner.ts | 31 +++++++++++++------ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/renderer/src/store/slices/repro-omp-pi-alternating-title-frame-churn.test.ts b/src/renderer/src/store/slices/repro-omp-pi-alternating-title-frame-churn.test.ts index 6cbc3565560..b4fe02a2e9c 100644 --- a/src/renderer/src/store/slices/repro-omp-pi-alternating-title-frame-churn.test.ts +++ b/src/renderer/src/store/slices/repro-omp-pi-alternating-title-frame-churn.test.ts @@ -224,6 +224,27 @@ describe('OMP-owned tab title across interleaved OMP/Pi spinner frames', () => { expect(store.getState().runtimePaneTitlesByTabId[TAB_ID]?.[PANE_ID]).toBe('⠙ Codex') }) + // Why: a legacy "π - - " frame is Pi-compatible too, but its text is real session + // state. Folding it into the group token would make two different sessions compare equal and + // suppress the change outright — the #16093 complaint, reintroduced through the signature. + it('still commits a change between two semantic session titles', () => { + const store = createTestStore() + seedOmpTab(store) + + store.getState().setRuntimePaneTitle(TAB_ID, PANE_ID, 'π - fixing the sidebar - orca') + let commits = 0 + const unsubscribe = store.subscribe(() => { + commits += 1 + }) + store.getState().setRuntimePaneTitle(TAB_ID, PANE_ID, 'π - writing the tests - orca') + unsubscribe() + + expect(commits).toBe(1) + expect(store.getState().runtimePaneTitlesByTabId[TAB_ID]?.[PANE_ID]).toBe( + 'π - writing the tests - orca' + ) + }) + // Why: owner-pinning must stay scoped to bare identity frames. A semantic session title carries // text no agent profile can reproduce, so relabeling it to "OMP ready" would lose real // information — the complaint in #16093, which this fix must not reintroduce. diff --git a/src/shared/agent-title-owner.ts b/src/shared/agent-title-owner.ts index d2da63fc47f..893cbdeda87 100644 --- a/src/shared/agent-title-owner.ts +++ b/src/shared/agent-title-owner.ts @@ -5,7 +5,10 @@ import { SYNTHETIC_AGENT_TITLE_PROFILES, type SyntheticAgentTitleProfile } from './synthetic-agent-title' -import { isLegacyPiCompatibleTitle } from './pi-compatible-synthetic-title' +import { + getPiCompatibleSyntheticAgentLabel, + isLegacyPiCompatibleTitle +} from './pi-compatible-synthetic-title' import { getWrapperTitleSegments } from './terminal-title-wrapper-segments' type TitleProfileMatch = { @@ -67,20 +70,30 @@ export function hasCompatibleAgentTitleIdentity(title: string): boolean { } /** - * Replaces the agent-identity segment of a title with its identity-group name, leaving any - * wrapper prefix intact ("zsh | ⠋ Pi" and "zsh | ⠋ OMP" both become "zsh | pi-compatible"). + * Replaces a bare agent-identity frame with its identity-group name, leaving any wrapper prefix + * intact ("zsh | ⠋ Pi" and "zsh | ⠙ OMP" both become "zsh | pi-compatible"). * * Why: members of a group are the same agent to a pane — OMP wraps Pi and both harnesses emit * frames — so which one a frame names is decoration, not state. Callers comparing consecutive - * frames need them to compare equal; callers rendering the title must not use this. + * frames need those to compare equal; callers rendering the title must not use this. + * + * Why only bare frames: a legacy "π - - " title is also Pi-compatible, but its + * text is real session state. Folding it would make two different sessions compare equal and + * suppress the change outright (#16093). */ export function collapseCompatibleAgentTitleIdentity(title: string): string { - const source = getProfileForTitle(title) - const group = source?.profile.titleIdentityGroup - if (!group) { - return title + for (const segment of getWrapperTitleSegments(title)) { + const label = getPiCompatibleSyntheticAgentLabel(segment) + const group = label + ? SYNTHETIC_AGENT_TITLE_PROFILES[label.toLowerCase()]?.titleIdentityGroup + : undefined + if (!group) { + continue + } + const at = title.lastIndexOf(segment) + return at === -1 ? group : title.slice(0, at) + group } - return title.replace(source.sourceTitle, group) + return title } /**