fix(pi): stop OMP tab icon flashing to Pi while working (#9077)
OMP runs as a `shell → omp → pi` process tree and Orca recognizes both `omp` and `pi` as distinct agents, so the foreground-process reader alternates between reporting the two across command boundaries. In `resolveTabAgentFromSignals` every identity signal is re-owned onto the pane's durable owner within its title-identity group (pi and omp share one) — except the process signal, which was consumed raw. Because it ranks above launchAgent, each `pi` read repainted an OMP-owned tab's icon to the Pi glyph and the next read flipped it back. Re-own the process signal the same way the hook and title signals already are, so a same-group read collapses onto the owner while a genuine cross-group process (e.g. Codex) still stands and can reclaim a reused pane. The tab title text was unaffected — its owner resolution already excludes the process signal — so this is icon-only. Adds regression coverage for the oscillation, the mirrored/restored pane (launchAgent gone, durable hook record), and the cross-group scope guard.
This commit is contained in:
parent
de9e9f8f1b
commit
dd0f4c39c8
|
|
@ -132,6 +132,64 @@ describe('resolveTabAgentFromSignals — Pi/OMP identity', () => {
|
|||
expect(afterHookCleared).toBe('omp')
|
||||
})
|
||||
|
||||
it('does not flap between OMP and Pi as the foreground process oscillates', () => {
|
||||
// The reported flicker: OMP wraps Pi (`shell → omp → pi`), so the foreground
|
||||
// reader alternates between reporting `omp` and `pi` at command boundaries.
|
||||
// The process signal outranks launchAgent, so without owner-normalization the
|
||||
// OMP-owned tab's icon flips to Pi on every `pi` read. Both reads must land on
|
||||
// the launched owner.
|
||||
const readsPi = resolveTabAgentFromSignals({
|
||||
hasObservedAgentSignal: true,
|
||||
isRemote: false,
|
||||
title: '⠋ OMP',
|
||||
hookAgent: null,
|
||||
processAgent: 'pi',
|
||||
launchAgent: 'omp'
|
||||
})
|
||||
const readsOmp = resolveTabAgentFromSignals({
|
||||
hasObservedAgentSignal: true,
|
||||
isRemote: false,
|
||||
title: '⠋ OMP',
|
||||
hookAgent: null,
|
||||
processAgent: 'omp',
|
||||
launchAgent: 'omp'
|
||||
})
|
||||
expect(readsPi).toBe('omp')
|
||||
expect(readsOmp).toBe('omp')
|
||||
})
|
||||
|
||||
it('re-owns a Pi foreground read to a durable OMP identity when launchAgent is gone', () => {
|
||||
// A mirrored/restored OMP pane keeps only its completed-hook identity; a `pi`
|
||||
// foreground read (OMP's nested child) must not repaint it to Pi.
|
||||
expect(
|
||||
resolveTabAgentFromSignals({
|
||||
hasObservedAgentSignal: true,
|
||||
isRemote: false,
|
||||
title: '⠋ OMP',
|
||||
hookAgent: null,
|
||||
focusedCompletedHookAgent: 'omp',
|
||||
processAgent: 'pi',
|
||||
launchAgent: undefined
|
||||
})
|
||||
).toBe('omp')
|
||||
})
|
||||
|
||||
it('still lets a genuine cross-group foreground process reclaim a reused OMP pane', () => {
|
||||
// Scope guard: a different-group process (Codex is not Pi-compatible) is
|
||||
// real-time proof the pane was reused, so it overrides the OMP launch owner
|
||||
// instead of collapsing onto it.
|
||||
expect(
|
||||
resolveTabAgentFromSignals({
|
||||
hasObservedAgentSignal: true,
|
||||
isRemote: false,
|
||||
title: 'zsh',
|
||||
hookAgent: null,
|
||||
processAgent: 'codex',
|
||||
launchAgent: 'omp'
|
||||
})
|
||||
).toBe('codex')
|
||||
})
|
||||
|
||||
it('keeps a launchAgent-less Pi pane on Pi and rejects a stale OMP session record', () => {
|
||||
// The fallback must not over-reach: a genuine Pi pane (recent Pi hook) stays
|
||||
// Pi even if a stale hibernated OMP record is present.
|
||||
|
|
|
|||
|
|
@ -168,7 +168,13 @@ export function resolveTabAgentFromSignals(args: {
|
|||
processShellForeground: args.processShellForeground
|
||||
})
|
||||
const activeLaunchAgent = launchedAgentExited ? null : launchAgent
|
||||
const processAgent = args.processAgent ?? null
|
||||
// Why: the foreground process, like the hook and title signals, must be
|
||||
// re-owned within its title-identity group. OMP wraps Pi as `shell → omp → pi`,
|
||||
// so the foreground reader oscillates between reporting `omp` and `pi` across
|
||||
// command boundaries; taken raw it outranks launchAgent and flips an OMP-owned
|
||||
// tab's icon between the two glyphs. Re-owning collapses the same-group read
|
||||
// onto the durable owner while a genuine cross-group process still stands.
|
||||
const processAgent = resolveSignalAgentForLaunchOwner(args.processAgent, owner)
|
||||
const sleepingSessionAgent = args.sleepingSessionAgent ?? null
|
||||
// Identity-first precedence. The live focused hook is ground truth while the
|
||||
// agent works; process identity covers agents with neither hook nor title; the
|
||||
|
|
@ -199,7 +205,8 @@ export function resolveTabAgentFromSignals(args: {
|
|||
* 2. Process identity — the recognized foreground process, read at OSC 133
|
||||
* command boundaries (local panes only); covers agents that emit neither
|
||||
* hooks nor titles, and its shell-foreground mark is title-independent exit
|
||||
* evidence.
|
||||
* evidence. Re-owned within its title-identity group, so OMP's nested `pi`
|
||||
* child (the `shell → omp → pi` tree) cannot flip the icon back to Pi.
|
||||
* 3. Title — only as a reuse override (it names a DIFFERENT-group agent than the
|
||||
* pane's known identity, proving reuse) or as a legacy standalone identity
|
||||
* when the pane has no hook. Within the same title-identity group it carries
|
||||
|
|
|
|||
Loading…
Reference in New Issue