mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 00:03:47 +00:00
[P2] fix(runtime): stop broadcasting terminalSideEffects to clients without consumers (#11619)
* fix(runtime): stop broadcasting terminalSideEffects to clients without consumers Co-authored-by: Orca <help@stably.ai> * fix(runtime): keep mobile subscribers counted for side-effect availability Excluding phones from the consumer-availability count added a new flip edge (last desktop client leaving a phone-attached host), and the flip's tracker rebuild cancels armed stale-working-title timers — stranding a 'working' spinner on the phone. Availability counts all subscribers again; the broadcast fix stays in the per-listener fan-out skip, now applied inside the delivery callback so live-Set unsubscribe semantics and allocation-free iteration are preserved. Co-authored-by: Orca <help@stably.ai> * fix(runtime): separate mobile title tracking from side-effect scans --------- Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -8468,6 +8468,81 @@ describe('OrcaRuntimeService', () => {
|
||||
expect(events).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('omits terminalSideEffects from non-consuming listeners while other events still flow', () => {
|
||||
const runtime = new OrcaRuntimeService(store)
|
||||
const desktopEvents: RuntimeClientEvent[] = []
|
||||
const mobileEvents: RuntimeClientEvent[] = []
|
||||
runtime.syncWindowGraph(HEADLESS_RUNTIME_WINDOW_ID, { tabs: [], leaves: [] })
|
||||
runtime.onClientEvent((event) => desktopEvents.push(event))
|
||||
runtime.onClientEvent((event) => mobileEvents.push(event), {
|
||||
consumesTerminalSideEffects: false
|
||||
})
|
||||
|
||||
runtime.onPtyData('pty-remote', '\x1b]0;Codex working\x07', 100)
|
||||
runtime.notifyBranchRenamed(TEST_REPO_ID)
|
||||
|
||||
expect(desktopEvents.map((event) => event.type)).toEqual([
|
||||
'terminalSideEffects',
|
||||
'worktreesChanged'
|
||||
])
|
||||
expect(mobileEvents.map((event) => event.type)).toEqual(['worktreesChanged'])
|
||||
})
|
||||
|
||||
it('keeps a phone-only host producing title state without emitting batches to it', async () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const ptyId = `${TEST_REPO_ID}::/tmp/worktree-a@@pty-a`
|
||||
const runtime = new OrcaRuntimeService(store)
|
||||
const mobileEvents: RuntimeClientEvent[] = []
|
||||
const trackerEntries = (
|
||||
runtime as unknown as {
|
||||
ptyTitleTrackersByPtyId: Map<string, { commandCodeDetector: unknown }>
|
||||
}
|
||||
).ptyTitleTrackersByPtyId
|
||||
runtime.setPtyController({
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess: async () => null,
|
||||
listProcesses: async () => [{ id: ptyId, cwd: '/tmp/worktree-a', title: 'shell' }]
|
||||
})
|
||||
runtime.syncWindowGraph(HEADLESS_RUNTIME_WINDOW_ID, { tabs: [], leaves: [] })
|
||||
runtime.onClientEvent((event) => mobileEvents.push(event), {
|
||||
consumesTerminalSideEffects: false
|
||||
})
|
||||
const unsubscribeDesktop = runtime.onClientEvent(() => {})
|
||||
|
||||
runtime.onPtyData(ptyId, '\x1b]0;Codex working\x07', 100)
|
||||
runtime.onPtyData(ptyId, 'output without a title\r\n', 101)
|
||||
// The phone is still subscribed: disposing trackers on this edge would cancel
|
||||
// its armed stale-working-title timer and strand a 'working' spinner (#1437).
|
||||
unsubscribeDesktop()
|
||||
|
||||
await vi.advanceTimersByTimeAsync(3_000)
|
||||
|
||||
expect(trackerEntries.has(ptyId)).toBe(true)
|
||||
expect(trackerEntries.get(ptyId)?.commandCodeDetector).toBeNull()
|
||||
expect((await runtime.listTerminals()).terminals[0]).toMatchObject({ title: 'Codex' })
|
||||
expect(mobileEvents.some((event) => event.type === 'terminalSideEffects')).toBe(false)
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('skips a listener unsubscribed mid-fan-out even with mobile exclusions active', () => {
|
||||
const runtime = new OrcaRuntimeService(store)
|
||||
const lateEvents: RuntimeClientEvent[] = []
|
||||
runtime.syncWindowGraph(HEADLESS_RUNTIME_WINDOW_ID, { tabs: [], leaves: [] })
|
||||
runtime.onClientEvent(() => {}, { consumesTerminalSideEffects: false })
|
||||
runtime.onClientEvent(() => {
|
||||
unsubscribeLate()
|
||||
})
|
||||
const unsubscribeLate = runtime.onClientEvent((event) => lateEvents.push(event))
|
||||
|
||||
runtime.onPtyData('pty-remote', '\x1b]0;Codex working\x07', 100)
|
||||
|
||||
expect(lateEvents).toEqual([])
|
||||
})
|
||||
|
||||
it('emits one batched event per chunk with facts in byte order and attribution', () => {
|
||||
const { runtime, batches } = createSideEffectRuntime()
|
||||
syncSinglePty(runtime)
|
||||
|
||||
Reference in New Issue
Block a user