fix(terminal): fold only bare identity frames into the group token

A legacy "π - <session> - <cwd>" 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>
This commit is contained in:
Neil
2026-08-25 01:02:28 -07:00
co-authored by Seongho.Bak
parent 323c7ae880
commit 5c1eb6702b
2 changed files with 43 additions and 9 deletions
@@ -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 "π - <session> - <cwd>" 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.
+22 -9
View File
@@ -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 "π - <session> - <cwd>" 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
}
/**