Merge branch 'fix/p0-7-tmux-cleanup' into dev
This commit is contained in:
@@ -1025,6 +1025,75 @@ describe('TmuxSessionManager', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('#given session isolation with a spawned container #when the first isolated subagent is deleted #then it cleans up the isolated container and clears the anchor pane id', async () => {
|
||||||
|
// given
|
||||||
|
mockIsInsideTmux.mockReturnValue(true)
|
||||||
|
|
||||||
|
let stateCallCount = 0
|
||||||
|
mockQueryWindowState.mockImplementation(async (paneId) => {
|
||||||
|
stateCallCount++
|
||||||
|
|
||||||
|
if (paneId === '%isolated-session-ses_first') {
|
||||||
|
return createWindowState({
|
||||||
|
mainPane: {
|
||||||
|
paneId,
|
||||||
|
width: 110,
|
||||||
|
height: 44,
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
title: 'isolated',
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stateCallCount === 1) {
|
||||||
|
return createWindowState()
|
||||||
|
}
|
||||||
|
|
||||||
|
return createWindowState({
|
||||||
|
mainPane: {
|
||||||
|
paneId: '%isolated-session-ses_first',
|
||||||
|
width: 110,
|
||||||
|
height: 44,
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
title: 'isolated',
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const { TmuxSessionManager } = await import('./manager')
|
||||||
|
const ctx = createMockContext()
|
||||||
|
const config: TmuxConfig = {
|
||||||
|
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_first', 'ses_parent', 'First Task')
|
||||||
|
)
|
||||||
|
mockExecuteAction.mockClear()
|
||||||
|
|
||||||
|
// when
|
||||||
|
await manager.onSessionDeleted({ sessionID: 'ses_first' })
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(mockExecuteAction).toHaveBeenCalledTimes(1)
|
||||||
|
expect(mockExecuteAction.mock.calls[0]?.[0]).toEqual({
|
||||||
|
type: 'close',
|
||||||
|
paneId: '%isolated-session-ses_first',
|
||||||
|
sessionId: 'ses_first',
|
||||||
|
})
|
||||||
|
expect(Reflect.get(manager, 'isolatedWindowPaneId')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
test('does nothing when untracked session is deleted', async () => {
|
test('does nothing when untracked session is deleted', async () => {
|
||||||
// given
|
// given
|
||||||
mockIsInsideTmux.mockReturnValue(true)
|
mockIsInsideTmux.mockReturnValue(true)
|
||||||
|
|||||||
@@ -172,6 +172,51 @@ export class TmuxSessionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async cleanupIsolatedContainerAfterSessionDeletion(
|
||||||
|
tracked: TrackedSession,
|
||||||
|
isolatedPaneAlreadyClosed: boolean,
|
||||||
|
state: WindowState,
|
||||||
|
): Promise<void> {
|
||||||
|
if (tracked.paneId !== this.isolatedWindowPaneId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sessions.size > 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isolatedWindowPaneId = undefined
|
||||||
|
|
||||||
|
if (isolatedPaneAlreadyClosed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await executeAction(
|
||||||
|
{ type: "close", paneId: tracked.paneId, sessionId: tracked.sessionId },
|
||||||
|
{
|
||||||
|
config: this.tmuxConfig,
|
||||||
|
serverUrl: this.serverUrl,
|
||||||
|
windowState: state,
|
||||||
|
sourcePaneId: this.sourcePaneId ?? tracked.paneId,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
log("[tmux-session-manager] failed to close isolated container pane after anchor session deletion", {
|
||||||
|
sessionId: tracked.sessionId,
|
||||||
|
paneId: tracked.paneId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log("[tmux-session-manager] failed to cleanup isolated container pane after anchor session deletion", {
|
||||||
|
sessionId: tracked.sessionId,
|
||||||
|
paneId: tracked.paneId,
|
||||||
|
error: String(error),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private markSessionClosePending(sessionId: string): void {
|
private markSessionClosePending(sessionId: string): void {
|
||||||
const tracked = this.sessions.get(sessionId)
|
const tracked = this.sessions.get(sessionId)
|
||||||
if (!tracked) return
|
if (!tracked) return
|
||||||
@@ -698,9 +743,13 @@ export class TmuxSessionManager {
|
|||||||
const closeAction = decideCloseAction(state, event.sessionID, this.getSessionMappings())
|
const closeAction = decideCloseAction(state, event.sessionID, this.getSessionMappings())
|
||||||
if (!closeAction) {
|
if (!closeAction) {
|
||||||
this.removeTrackedSession(event.sessionID)
|
this.removeTrackedSession(event.sessionID)
|
||||||
|
await this.cleanupIsolatedContainerAfterSessionDeletion(tracked, false, state)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isolatedPaneAlreadyClosed =
|
||||||
|
closeAction.type === "close" && closeAction.paneId === tracked.paneId
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await executeAction(closeAction, {
|
const result = await executeAction(closeAction, {
|
||||||
config: this.tmuxConfig,
|
config: this.tmuxConfig,
|
||||||
@@ -723,6 +772,11 @@ export class TmuxSessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.removeTrackedSession(event.sessionID)
|
this.removeTrackedSession(event.sessionID)
|
||||||
|
await this.cleanupIsolatedContainerAfterSessionDeletion(
|
||||||
|
tracked,
|
||||||
|
isolatedPaneAlreadyClosed,
|
||||||
|
state,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user