mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
fix: suppress unread signals during PTY replay (#569)
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createIpcPtyTransport } from './pty-transport'
|
||||
|
||||
describe('createIpcPtyTransport', () => {
|
||||
const originalWindow = (globalThis as { window?: typeof window }).window
|
||||
@@ -10,6 +9,7 @@ describe('createIpcPtyTransport', () => {
|
||||
| null = null
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules()
|
||||
onData = null
|
||||
onExit = null
|
||||
onOpenCodeStatus = null
|
||||
@@ -57,6 +57,7 @@ describe('createIpcPtyTransport', () => {
|
||||
})
|
||||
|
||||
it('maps OpenCode status events into the existing working to idle agent lifecycle', async () => {
|
||||
const { createIpcPtyTransport } = await import('./pty-transport')
|
||||
const onTitleChange = vi.fn()
|
||||
const onAgentBecameWorking = vi.fn()
|
||||
const onAgentBecameIdle = vi.fn()
|
||||
@@ -86,4 +87,33 @@ describe('createIpcPtyTransport', () => {
|
||||
expect(onData).not.toBeNull()
|
||||
expect(onExit).not.toBeNull()
|
||||
})
|
||||
|
||||
it('does not fire unread-side effects when replaying buffered data during attach', async () => {
|
||||
const { createIpcPtyTransport, registerEagerPtyBuffer } = await import('./pty-transport')
|
||||
const onTitleChange = vi.fn()
|
||||
const onAgentBecameIdle = vi.fn()
|
||||
const onBell = vi.fn()
|
||||
|
||||
const handle = registerEagerPtyBuffer('pty-restored', vi.fn())
|
||||
onData?.({
|
||||
id: 'pty-restored',
|
||||
data: '\u001b]0;. Claude working\u0007\u001b]0;* Claude done\u0007\u0007'
|
||||
})
|
||||
|
||||
const transport = createIpcPtyTransport({
|
||||
onTitleChange,
|
||||
onAgentBecameIdle,
|
||||
onBell
|
||||
})
|
||||
|
||||
transport.attach({
|
||||
existingPtyId: 'pty-restored',
|
||||
callbacks: {}
|
||||
})
|
||||
|
||||
expect(handle.flush()).toBe('')
|
||||
expect(onTitleChange).toHaveBeenCalledWith('* Claude done', '* Claude done')
|
||||
expect(onAgentBecameIdle).not.toHaveBeenCalled()
|
||||
expect(onBell).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -177,6 +177,7 @@ export function createIpcPtyTransport(opts: IpcPtyTransportOptions = {}): PtyTra
|
||||
let pendingEscape = false
|
||||
let inOsc = false
|
||||
let pendingOscEscape = false
|
||||
let suppressAttentionEvents = false
|
||||
let lastEmittedTitle: string | null = null
|
||||
let lastObservedTerminalTitle: string | null = null
|
||||
let openCodeStatus: OpenCodeStatusEvent['status'] | null = null
|
||||
@@ -184,7 +185,11 @@ export function createIpcPtyTransport(opts: IpcPtyTransportOptions = {}): PtyTra
|
||||
const agentTracker =
|
||||
onAgentBecameIdle || onAgentBecameWorking || onAgentExited
|
||||
? createAgentStatusTracker(
|
||||
onAgentBecameIdle ?? (() => {}),
|
||||
(title) => {
|
||||
if (!suppressAttentionEvents) {
|
||||
onAgentBecameIdle?.(title)
|
||||
}
|
||||
},
|
||||
onAgentBecameWorking,
|
||||
onAgentExited
|
||||
)
|
||||
@@ -316,7 +321,7 @@ export function createIpcPtyTransport(opts: IpcPtyTransportOptions = {}): PtyTra
|
||||
}, STALE_TITLE_TIMEOUT)
|
||||
}
|
||||
}
|
||||
if (onBell && chunkContainsBell(data)) {
|
||||
if (onBell && chunkContainsBell(data) && !suppressAttentionEvents) {
|
||||
onBell()
|
||||
}
|
||||
})
|
||||
@@ -390,7 +395,7 @@ export function createIpcPtyTransport(opts: IpcPtyTransportOptions = {}): PtyTra
|
||||
}, STALE_TITLE_TIMEOUT)
|
||||
}
|
||||
}
|
||||
if (onBell && chunkContainsBell(data)) {
|
||||
if (onBell && chunkContainsBell(data) && !suppressAttentionEvents) {
|
||||
onBell()
|
||||
}
|
||||
})
|
||||
@@ -419,7 +424,17 @@ export function createIpcPtyTransport(opts: IpcPtyTransportOptions = {}): PtyTra
|
||||
if (bufferHandle) {
|
||||
const buffered = bufferHandle.flush()
|
||||
if (buffered) {
|
||||
ptyDataHandlers.get(id)?.(buffered)
|
||||
// Why: eager PTY buffers contain output produced before the pane
|
||||
// attached, often from a previous app session. We still replay that
|
||||
// data so titles and scrollback restore correctly, but it must not
|
||||
// generate fresh unread badges or notifications for unrelated
|
||||
// worktrees just because Orca is reconnecting background terminals.
|
||||
suppressAttentionEvents = true
|
||||
try {
|
||||
ptyDataHandlers.get(id)?.(buffered)
|
||||
} finally {
|
||||
suppressAttentionEvents = false
|
||||
}
|
||||
}
|
||||
bufferHandle.dispose()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user