Merge branch 'fix/ub3-regression' into dev
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user