mirror of
https://github.com/stablyai/orca.git
synced 2026-10-12 16:03:03 +00:00
fix(daemon): skip legacy drain after discovery failure
If legacy session discovery fails after startup probing already found live work, do not let the post-discovery sweep reinterpret an empty routing map as drained. Keep that legacy daemon alive for this app session and let the next startup re-probe it instead.
This commit is contained in:
@@ -11,7 +11,8 @@ type AdapterMock = DaemonPtyAdapter & {
|
||||
function createAdapter(
|
||||
label: string,
|
||||
sessions: string[] = [],
|
||||
reconcileResult?: { alive: string[]; killed: string[] }
|
||||
reconcileResult?: { alive: string[]; killed: string[] },
|
||||
opts: { listProcessesRejects?: boolean } = {}
|
||||
): AdapterMock {
|
||||
const writes: { id: string; data: string }[] = []
|
||||
const dataListeners: ((payload: { id: string; data: string }) => void)[] = []
|
||||
@@ -22,13 +23,16 @@ function createAdapter(
|
||||
sessions.push(id)
|
||||
return { id }
|
||||
}),
|
||||
listProcesses: vi.fn(async () =>
|
||||
sessions.map((id) => ({
|
||||
listProcesses: vi.fn(async () => {
|
||||
if (opts.listProcessesRejects) {
|
||||
throw new Error('discovery failed')
|
||||
}
|
||||
return sessions.map((id) => ({
|
||||
id,
|
||||
cwd: '',
|
||||
title: label
|
||||
}))
|
||||
),
|
||||
}),
|
||||
write: vi.fn((id: string, data: string) => {
|
||||
writes.push({ id, data })
|
||||
}),
|
||||
@@ -218,6 +222,23 @@ describe('DaemonPtyRouter', () => {
|
||||
expect(onLegacyDrained).toHaveBeenCalledWith(legacy)
|
||||
})
|
||||
|
||||
it('does not drain an adapter when legacy discovery fails', async () => {
|
||||
const current = createAdapter('current')
|
||||
const legacy = createAdapter('legacy', ['legacy-session'], undefined, {
|
||||
listProcessesRejects: true
|
||||
})
|
||||
const onLegacyDrained = vi.fn()
|
||||
const router = new DaemonPtyRouter({ current, legacy: [legacy], onLegacyDrained })
|
||||
|
||||
await router.discoverLegacySessions()
|
||||
router.sweepDrainedLegacyAdapters()
|
||||
legacy.emitExit('legacy-session', 0)
|
||||
|
||||
expect(onLegacyDrained).not.toHaveBeenCalled()
|
||||
await router.listProcesses()
|
||||
expect(legacy.listProcesses).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('merges startup reconciliation and updates route mappings', async () => {
|
||||
const current = createAdapter('current', [], {
|
||||
alive: ['current-alive'],
|
||||
|
||||
@@ -6,6 +6,7 @@ export class DaemonPtyRouter implements IPtyProvider {
|
||||
private legacy: DaemonPtyAdapter[]
|
||||
private sessionAdapters = new Map<string, DaemonPtyAdapter>()
|
||||
private unsubscribersByAdapter = new Map<DaemonPtyAdapter, (() => void)[]>()
|
||||
private discoveryFailedAdapters = new Set<DaemonPtyAdapter>()
|
||||
private onLegacyDrained?: (adapter: DaemonPtyAdapter) => void
|
||||
private dataListeners: ((payload: { id: string; data: string }) => void)[] = []
|
||||
private exitListeners: ((payload: { id: string; code: number }) => void)[] = []
|
||||
@@ -42,10 +43,12 @@ export class DaemonPtyRouter implements IPtyProvider {
|
||||
for (const adapter of this.legacy) {
|
||||
try {
|
||||
const sessions = await adapter.listProcesses()
|
||||
this.discoveryFailedAdapters.delete(adapter)
|
||||
for (const session of sessions) {
|
||||
this.sessionAdapters.set(session.id, adapter)
|
||||
}
|
||||
} catch (error) {
|
||||
this.discoveryFailedAdapters.add(adapter)
|
||||
console.warn('[daemon] Failed to discover legacy daemon sessions', error)
|
||||
}
|
||||
}
|
||||
@@ -204,10 +207,15 @@ export class DaemonPtyRouter implements IPtyProvider {
|
||||
private drainLegacyAdapterIfEmpty(adapter: DaemonPtyAdapter): void {
|
||||
// Why: only previous-version adapters are cleaned up here. The current
|
||||
// daemon lifecycle must remain byte-identical when its sessions exit.
|
||||
if (!this.legacy.includes(adapter) || this.hasActiveSessionsOn(adapter)) {
|
||||
if (
|
||||
!this.legacy.includes(adapter) ||
|
||||
this.discoveryFailedAdapters.has(adapter) ||
|
||||
this.hasActiveSessionsOn(adapter)
|
||||
) {
|
||||
return
|
||||
}
|
||||
this.legacy = this.legacy.filter((a) => a !== adapter)
|
||||
this.discoveryFailedAdapters.delete(adapter)
|
||||
this.unsubscribeAdapter(adapter)
|
||||
this.onLegacyDrained?.(adapter)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user