diff --git a/src/features/tmux-subagent/manager.test.ts b/src/features/tmux-subagent/manager.test.ts index b28c2cf7..e1bf6e51 100644 --- a/src/features/tmux-subagent/manager.test.ts +++ b/src/features/tmux-subagent/manager.test.ts @@ -434,6 +434,53 @@ describe('TmuxSessionManager', () => { }) describe('onSessionDeleted', () => { + test('does not track session when readiness timed out', async () => { + // given + mockIsInsideTmux.mockReturnValue(true) + let stateCallCount = 0 + mockQueryWindowState.mockImplementation(async () => { + stateCallCount++ + if (stateCallCount === 1) { + return createWindowState() + } + return createWindowState({ + agentPanes: [ + { + paneId: '%mock', + width: 40, + height: 44, + left: 100, + top: 0, + title: 'omo-subagent-Timeout Task', + isActive: false, + }, + ], + }) + }) + + const { TmuxSessionManager } = await import('./manager') + const ctx = createMockContext({ sessionStatusResult: { data: {} } }) + const config: TmuxConfig = { + enabled: true, + 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_timeout', 'ses_parent', 'Timeout Task') + ) + mockExecuteAction.mockClear() + + // when + await manager.onSessionDeleted({ sessionID: 'ses_timeout' }) + + // then + expect(mockExecuteAction).toHaveBeenCalledTimes(0) + }) + test('closes pane when tracked session is deleted', async () => { // given mockIsInsideTmux.mockReturnValue(true) @@ -521,8 +568,13 @@ describe('TmuxSessionManager', () => { mockIsInsideTmux.mockReturnValue(true) let callCount = 0 - mockExecuteActions.mockImplementation(async () => { + mockExecuteActions.mockImplementation(async (actions) => { callCount++ + for (const action of actions) { + if (action.type === 'spawn') { + trackedSessions.add(action.sessionId) + } + } return { success: true, spawnedPaneId: `%${callCount}`, diff --git a/src/features/tmux-subagent/manager.ts b/src/features/tmux-subagent/manager.ts index 4ab167d5..2c40f7c9 100644 --- a/src/features/tmux-subagent/manager.ts +++ b/src/features/tmux-subagent/manager.ts @@ -213,10 +213,17 @@ export class TmuxSessionManager { const sessionReady = await this.waitForSessionReady(sessionId) if (!sessionReady) { - log("[tmux-session-manager] session not ready after timeout, tracking anyway", { + log("[tmux-session-manager] session not ready after timeout, closing spawned pane", { sessionId, paneId: result.spawnedPaneId, }) + + await executeAction( + { type: "close", paneId: result.spawnedPaneId, sessionId }, + { config: this.tmuxConfig, serverUrl: this.serverUrl, windowState: state } + ) + + return } const now = Date.now() diff --git a/src/features/tmux-subagent/session-created-handler.ts b/src/features/tmux-subagent/session-created-handler.ts index 18afb0d9..a72816ea 100644 --- a/src/features/tmux-subagent/session-created-handler.ts +++ b/src/features/tmux-subagent/session-created-handler.ts @@ -135,10 +135,21 @@ export async function handleSessionCreated( const sessionReady = await deps.waitForSessionReady(sessionId) if (!sessionReady) { - log("[tmux-session-manager] session not ready after timeout, tracking anyway", { + log("[tmux-session-manager] session not ready after timeout, closing spawned pane", { sessionId, paneId: result.spawnedPaneId, }) + + await executeActions( + [{ type: "close", paneId: result.spawnedPaneId, sessionId }], + { + config: deps.tmuxConfig, + serverUrl: deps.serverUrl, + windowState: state, + }, + ) + + return } const now = Date.now() diff --git a/src/features/tmux-subagent/session-spawner.ts b/src/features/tmux-subagent/session-spawner.ts index 433a163f..4c43b653 100644 --- a/src/features/tmux-subagent/session-spawner.ts +++ b/src/features/tmux-subagent/session-spawner.ts @@ -129,10 +129,21 @@ export class SessionSpawner { const sessionReady = await this.waitForSessionReady(sessionId) if (!sessionReady) { - log("[tmux-session-manager] session not ready after timeout, tracking anyway", { + log("[tmux-session-manager] session not ready after timeout, closing spawned pane", { sessionId, paneId: result.spawnedPaneId, }) + + await executeActions( + [{ type: "close", paneId: result.spawnedPaneId, sessionId }], + { + config: this.tmuxConfig, + serverUrl: this.serverUrl, + windowState: state, + }, + ) + + return } const now = Date.now()