Merge branch 'fix/ub3-regression' into dev

This commit is contained in:
YeonGyu-Kim
2026-03-31 17:08:45 -07:00
2 changed files with 108 additions and 1 deletions
+107
View File
@@ -11,6 +11,11 @@ type ExecuteActionsResult = {
results: Array<{ action: PaneAction; result: ActionResult }>
}
type SpawnTmuxContainerResult = {
success: boolean
paneId?: string
}
const mockQueryWindowState = mock<(paneId: string) => Promise<WindowState | null>>(
async () => ({
windowWidth: 212,
@@ -32,6 +37,25 @@ const mockExecuteAction = mock<(
action: PaneAction,
ctx: ExecuteContext
) => Promise<ActionResult>>(async () => ({ success: true }))
const mockSpawnTmuxWindow = mock<(
sessionId: string,
description: string,
config: TmuxConfig,
serverUrl: string
) => Promise<SpawnTmuxContainerResult>>(async () => ({
success: true,
paneId: '%isolated-window',
}))
const mockSpawnTmuxSession = mock<(
sessionId: string,
description: string,
config: TmuxConfig,
serverUrl: string,
sourcePaneId?: string
) => Promise<SpawnTmuxContainerResult>>(async () => ({
success: true,
paneId: '%isolated-session',
}))
const mockIsInsideTmux = mock<() => boolean>(() => true)
const mockGetCurrentPaneId = mock<() => string | undefined>(() => '%0')
@@ -70,6 +94,8 @@ mock.module('../../shared/tmux', () => {
SESSION_MISSING_GRACE_MS,
SESSION_READY_POLL_INTERVAL_MS: 100,
SESSION_READY_TIMEOUT_MS: 500,
spawnTmuxWindow: mockSpawnTmuxWindow,
spawnTmuxSession: mockSpawnTmuxSession,
}
})
@@ -133,6 +159,8 @@ describe('TmuxSessionManager', () => {
mockPaneExists.mockClear()
mockExecuteActions.mockClear()
mockExecuteAction.mockClear()
mockSpawnTmuxWindow.mockClear()
mockSpawnTmuxSession.mockClear()
mockIsInsideTmux.mockClear()
mockGetCurrentPaneId.mockClear()
trackedSessions.clear()
@@ -150,6 +178,20 @@ describe('TmuxSessionManager', () => {
results: [],
}
})
mockSpawnTmuxWindow.mockImplementation(async (sessionId) => {
trackedSessions.add(sessionId)
return {
success: true,
paneId: `%isolated-window-${sessionId}`,
}
})
mockSpawnTmuxSession.mockImplementation(async (sessionId) => {
trackedSessions.add(sessionId)
return {
success: true,
paneId: `%isolated-session-${sessionId}`,
}
})
})
describe('constructor', () => {
@@ -349,6 +391,71 @@ describe('TmuxSessionManager', () => {
expect(actionsArg[0].type).toBe('spawn')
})
test('#given session isolation with healthy existing container #when second subagent is created #then it spawns inline from isolated pane', async () => {
// given
mockIsInsideTmux.mockReturnValue(true)
mockQueryWindowState.mockImplementation(async (paneId) => {
if (paneId === '%isolated-session-ses_first') {
return createWindowState({
mainPane: {
paneId,
width: 110,
height: 44,
left: 0,
top: 0,
title: 'isolated',
isActive: true,
},
})
}
return createWindowState()
})
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')
)
mockExecuteActions.mockClear()
// when
await manager.onSessionCreated(
createSessionCreatedEvent('ses_second', 'ses_parent', 'Second Task')
)
// then
expect(mockSpawnTmuxSession).toHaveBeenCalledTimes(1)
expect(mockExecuteActions).toHaveBeenCalledTimes(1)
const executeActionsCall = mockExecuteActions.mock.calls[0]
expect(executeActionsCall).toBeDefined()
const actions = executeActionsCall?.[0]
const context = executeActionsCall?.[1]
expect(actions).toBeDefined()
expect(actions).toHaveLength(1)
expect(actions?.[0]?.type).toBe('spawn')
if (actions?.[0]?.type === 'spawn') {
expect(actions[0].sessionId).toBe('ses_second')
expect(actions[0].targetPaneId).toBe('%isolated-session-ses_first')
}
expect(context?.sourcePaneId).toBe('%isolated-session-ses_first')
})
test('does NOT spawn pane when session has no parentID', async () => {
// given
mockIsInsideTmux.mockReturnValue(true)
+1 -1
View File
@@ -537,7 +537,7 @@ export class TmuxSessionManager {
return
}
if (this.isIsolated()) {
if (this.isIsolated() && !this.isolatedWindowPaneId) {
log("[tmux-session-manager] isolated container failed, skipping inline fallback to preserve isolation", { sessionId })
return
}