fix(tmux): re-attempt isolated container on deferred session retry
This commit is contained in:
@@ -863,6 +863,38 @@ describe('TmuxSessionManager', () => {
|
|||||||
logSpy.mockRestore()
|
logSpy.mockRestore()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('#given an isolated session deferred after container spawn failure #when deferred attach retries #then it re-attempts isolated container creation before normal pane fallback', async () => {
|
||||||
|
// given
|
||||||
|
mockIsInsideTmux.mockReturnValue(true)
|
||||||
|
mockSpawnTmuxSession.mockImplementation(async () => ({
|
||||||
|
success: false,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { TmuxSessionManager } = await import('./manager')
|
||||||
|
const ctx = createMockContext()
|
||||||
|
const config = createTmuxConfig({ enabled: true,
|
||||||
|
isolation: 'session',
|
||||||
|
layout: 'main-vertical',
|
||||||
|
main_pane_size: 60,
|
||||||
|
main_pane_min_width: 80,
|
||||||
|
agent_pane_min_width: 40, })
|
||||||
|
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
|
||||||
|
|
||||||
|
await manager.onSessionCreated(
|
||||||
|
createSessionCreatedEvent('ses_isolated_retry', 'ses_parent', 'Isolated Retry Task')
|
||||||
|
)
|
||||||
|
|
||||||
|
mockExecuteActions.mockClear()
|
||||||
|
|
||||||
|
// when
|
||||||
|
await Reflect.get(manager, 'tryAttachDeferredSession').call(manager)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(mockSpawnTmuxSession).toHaveBeenCalledTimes(2)
|
||||||
|
expect(mockExecuteActions).toHaveBeenCalledTimes(1)
|
||||||
|
expect(mockExecuteActions.mock.calls[0]?.[1]?.sourcePaneId).toBe('%0')
|
||||||
|
})
|
||||||
|
|
||||||
test('#given queryWindowState returns null #when onSessionCreated fires #then session is enqueued in deferred queue', async () => {
|
test('#given queryWindowState returns null #when onSessionCreated fires #then session is enqueued in deferred queue', async () => {
|
||||||
// given
|
// given
|
||||||
mockIsInsideTmux.mockReturnValue(true)
|
mockIsInsideTmux.mockReturnValue(true)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ interface DeferredSession {
|
|||||||
sessionId: string
|
sessionId: string
|
||||||
title: string
|
title: string
|
||||||
queuedAt: Date
|
queuedAt: Date
|
||||||
|
retryIsolatedContainer: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TmuxUtilDeps {
|
export interface TmuxUtilDeps {
|
||||||
@@ -366,8 +367,21 @@ export class TmuxSessionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enqueueDeferredSession(sessionId: string, title: string): void {
|
private enqueueDeferredSession(
|
||||||
if (this.deferredSessions.has(sessionId)) return
|
sessionId: string,
|
||||||
|
title: string,
|
||||||
|
retryIsolatedContainer = false,
|
||||||
|
): void {
|
||||||
|
const existingDeferredSession = this.deferredSessions.get(sessionId)
|
||||||
|
if (existingDeferredSession) {
|
||||||
|
if (retryIsolatedContainer && !existingDeferredSession.retryIsolatedContainer) {
|
||||||
|
this.deferredSessions.set(sessionId, {
|
||||||
|
...existingDeferredSession,
|
||||||
|
retryIsolatedContainer: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
if (this.deferredQueue.length >= MAX_DEFERRED_QUEUE_SIZE) {
|
if (this.deferredQueue.length >= MAX_DEFERRED_QUEUE_SIZE) {
|
||||||
log("[tmux-session-manager] deferred queue full, dropping session", {
|
log("[tmux-session-manager] deferred queue full, dropping session", {
|
||||||
sessionId,
|
sessionId,
|
||||||
@@ -380,6 +394,7 @@ export class TmuxSessionManager {
|
|||||||
sessionId,
|
sessionId,
|
||||||
title,
|
title,
|
||||||
queuedAt: new Date(),
|
queuedAt: new Date(),
|
||||||
|
retryIsolatedContainer,
|
||||||
})
|
})
|
||||||
this.deferredQueue.push(sessionId)
|
this.deferredQueue.push(sessionId)
|
||||||
log("[tmux-session-manager] deferred session queued", {
|
log("[tmux-session-manager] deferred session queued", {
|
||||||
@@ -430,8 +445,6 @@ export class TmuxSessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async tryAttachDeferredSession(): Promise<void> {
|
private async tryAttachDeferredSession(): Promise<void> {
|
||||||
const effectiveSourcePaneId = this.getEffectiveSourcePaneId()
|
|
||||||
if (!effectiveSourcePaneId) return
|
|
||||||
const sessionId = this.deferredQueue[0]
|
const sessionId = this.deferredQueue[0]
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
this.stopDeferredAttachLoop()
|
this.stopDeferredAttachLoop()
|
||||||
@@ -459,6 +472,32 @@ export class TmuxSessionManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deferred.retryIsolatedContainer) {
|
||||||
|
const isolatedPaneId = await this.spawnInIsolatedContainer(sessionId, deferred.title)
|
||||||
|
if (isolatedPaneId) {
|
||||||
|
const sessionReady = await this.waitForSessionReady(sessionId)
|
||||||
|
this.sessions.set(
|
||||||
|
sessionId,
|
||||||
|
createTrackedSession({
|
||||||
|
sessionId,
|
||||||
|
paneId: isolatedPaneId,
|
||||||
|
description: deferred.title,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
this.removeDeferredSession(sessionId)
|
||||||
|
this.pollingManager.startPolling()
|
||||||
|
log("[tmux-session-manager] deferred session attached in isolated window", {
|
||||||
|
sessionId,
|
||||||
|
paneId: isolatedPaneId,
|
||||||
|
sessionReady,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectiveSourcePaneId = this.getEffectiveSourcePaneId()
|
||||||
|
if (!effectiveSourcePaneId) return
|
||||||
|
|
||||||
const state = await queryWindowState(effectiveSourcePaneId)
|
const state = await queryWindowState(effectiveSourcePaneId)
|
||||||
if (!state) {
|
if (!state) {
|
||||||
this.nullStateCount += 1
|
this.nullStateCount += 1
|
||||||
@@ -623,7 +662,7 @@ export class TmuxSessionManager {
|
|||||||
|
|
||||||
if (this.isIsolated() && !this.isolatedWindowPaneId) {
|
if (this.isIsolated() && !this.isolatedWindowPaneId) {
|
||||||
log("[tmux-session-manager] isolated container failed, deferring session for retry", { sessionId })
|
log("[tmux-session-manager] isolated container failed, deferring session for retry", { sessionId })
|
||||||
this.enqueueDeferredSession(sessionId, title)
|
this.enqueueDeferredSession(sessionId, title, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const sourcePaneId = this.getEffectiveSourcePaneId()
|
const sourcePaneId = this.getEffectiveSourcePaneId()
|
||||||
|
|||||||
Reference in New Issue
Block a user