test(tmux): add isolation regression coverage

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-04-04 01:21:04 +09:00
parent 53eeac3f31
commit b5f15f0371
2 changed files with 600 additions and 321 deletions
+16 -8
View File
@@ -1,14 +1,19 @@
declare module "bun:test" {
type AnyFunction = (...args: any[]) => any
interface MockMetadata<TArgs extends unknown[]> {
calls: TArgs[]
}
interface MockFunction<TArgs extends unknown[] = unknown[], TReturn = unknown> {
(...args: TArgs): TReturn
mock: MockMetadata<TArgs>
interface MockFunction<TFunction extends AnyFunction = AnyFunction> {
(...args: Parameters<TFunction>): ReturnType<TFunction>
mock: MockMetadata<Parameters<TFunction>>
mockClear(): void
mockReset(): void
mockReturnValue(value: TReturn): void
mockResolvedValue(value: Awaited<TReturn>): void
mockRestore(): void
mockReturnValue(value: ReturnType<TFunction>): void
mockResolvedValue(value: Awaited<ReturnType<TFunction>>): void
mockImplementation(fn: TFunction): MockFunction<TFunction>
}
export function describe(name: string, fn: () => void): void
@@ -18,9 +23,12 @@ declare module "bun:test" {
export function afterEach(fn: () => void | Promise<void>): void
export function beforeAll(fn: () => void | Promise<void>): void
export function afterAll(fn: () => void | Promise<void>): void
export function mock<TArgs extends unknown[], TReturn>(
fn: (...args: TArgs) => TReturn,
): MockFunction<TArgs, TReturn>
export function mock<TFunction extends AnyFunction>(fn: TFunction): MockFunction<TFunction>
export function spyOn<TObject extends object>(
object: TObject,
key: keyof TObject,
): MockFunction<AnyFunction>
export namespace mock {
function module(modulePath: string, factory: () => Record<string, unknown>): void
+399 -128
View File
@@ -1,3 +1,4 @@
/// <reference path="../../../bun-test.d.ts" />
import { describe, test, expect, mock, beforeEach, spyOn, afterAll } from 'bun:test'
import type { TmuxConfig } from '../../config/schema'
import type { WindowState, PaneAction } from './types'
@@ -155,6 +156,18 @@ function createWindowState(overrides?: Partial<WindowState>): WindowState {
}
}
function createTmuxConfig(overrides?: Partial<TmuxConfig>): TmuxConfig {
return {
enabled: true,
isolation: 'inline',
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
...overrides,
}
}
describe('TmuxSessionManager', () => {
beforeEach(() => {
mockQueryWindowState.mockClear()
@@ -168,8 +181,7 @@ describe('TmuxSessionManager', () => {
trackedSessions.clear()
mockQueryWindowState.mockImplementation(async () => createWindowState())
mockExecuteActions.mockImplementation(async (actions) => {
for (const action of actions) {
mockExecuteActions.mockImplementation(async (actions: PaneAction[]) => { for (const action of actions) {
if (action.type === 'spawn') {
trackedSessions.add(action.sessionId)
}
@@ -178,16 +190,15 @@ describe('TmuxSessionManager', () => {
success: true,
spawnedPaneId: '%mock',
results: [],
}
})
mockSpawnTmuxWindow.mockImplementation(async (sessionId) => {
} })
mockSpawnTmuxWindow.mockImplementation(async (sessionId: string) => {
trackedSessions.add(sessionId)
return {
success: true,
paneId: `%isolated-window-${sessionId}`,
}
})
mockSpawnTmuxSession.mockImplementation(async (sessionId) => {
mockSpawnTmuxSession.mockImplementation(async (sessionId: string) => {
trackedSessions.add(sessionId)
return {
success: true,
@@ -210,13 +221,11 @@ describe('TmuxSessionManager', () => {
},
},
})
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
// when
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
@@ -236,13 +245,11 @@ describe('TmuxSessionManager', () => {
},
},
})
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
// when
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
@@ -256,13 +263,11 @@ describe('TmuxSessionManager', () => {
mockIsInsideTmux.mockReturnValue(true)
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: false,
const config = createTmuxConfig({ enabled: false,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
// when
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
@@ -279,13 +284,11 @@ describe('TmuxSessionManager', () => {
...createMockContext(),
serverUrl: new URL('http://127.0.0.1:0/'),
}
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
// when
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
@@ -303,13 +306,11 @@ describe('TmuxSessionManager', () => {
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
const event = createSessionCreatedEvent(
'ses_child',
@@ -364,13 +365,11 @@ describe('TmuxSessionManager', () => {
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
// when - first agent
@@ -396,8 +395,7 @@ describe('TmuxSessionManager', () => {
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') {
mockQueryWindowState.mockImplementation(async (paneId: string) => { if (paneId === '%isolated-session-ses_first') {
return createWindowState({
mainPane: {
paneId,
@@ -411,19 +409,16 @@ describe('TmuxSessionManager', () => {
})
}
return createWindowState()
})
return createWindowState() })
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
isolation: 'session',
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
await manager.onSessionCreated(
@@ -458,18 +453,77 @@ describe('TmuxSessionManager', () => {
expect(context?.sourcePaneId).toBe('%isolated-session-ses_first')
})
test('#given window 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: string) => { if (paneId === '%isolated-window-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 = createTmuxConfig({ enabled: true,
isolation: 'window',
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(mockSpawnTmuxWindow).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-window-ses_first')
}
expect(context?.sourcePaneId).toBe('%isolated-window-ses_first')
})
test('does NOT spawn pane when session has no parentID', async () => {
// given
mockIsInsideTmux.mockReturnValue(true)
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
const event = createSessionCreatedEvent('ses_root', undefined, 'Root Session')
@@ -485,13 +539,11 @@ describe('TmuxSessionManager', () => {
mockIsInsideTmux.mockReturnValue(true)
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: false,
const config = createTmuxConfig({ enabled: false,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
const event = createSessionCreatedEvent(
'ses_child',
@@ -511,13 +563,11 @@ describe('TmuxSessionManager', () => {
mockIsInsideTmux.mockReturnValue(true)
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
const event = {
type: 'session.deleted',
@@ -556,13 +606,11 @@ describe('TmuxSessionManager', () => {
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 120,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
// when
@@ -598,13 +646,11 @@ describe('TmuxSessionManager', () => {
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 120,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
// when
@@ -641,8 +687,7 @@ describe('TmuxSessionManager', () => {
)
const attachOrder: string[] = []
mockExecuteActions.mockImplementation(async (actions) => {
for (const action of actions) {
mockExecuteActions.mockImplementation(async (actions: PaneAction[]) => { for (const action of actions) {
if (action.type === 'spawn') {
attachOrder.push(action.sessionId)
trackedSessions.add(action.sessionId)
@@ -653,18 +698,15 @@ describe('TmuxSessionManager', () => {
}
}
}
return { success: true, results: [] }
})
return { success: true, results: [] } })
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 120,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
await manager.onSessionCreated(createSessionCreatedEvent('ses_1', 'ses_parent', 'Task 1'))
@@ -705,8 +747,7 @@ describe('TmuxSessionManager', () => {
)
let attachCount = 0
mockExecuteActions.mockImplementation(async (actions) => {
for (const action of actions) {
mockExecuteActions.mockImplementation(async (actions: PaneAction[]) => { for (const action of actions) {
if (action.type === 'spawn') {
attachCount += 1
trackedSessions.add(action.sessionId)
@@ -717,18 +758,15 @@ describe('TmuxSessionManager', () => {
}
}
}
return { success: true, results: [] }
})
return { success: true, results: [] } })
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 120,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
await manager.onSessionCreated(
@@ -768,13 +806,11 @@ describe('TmuxSessionManager', () => {
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 120,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
await manager.onSessionCreated(
@@ -791,6 +827,42 @@ describe('TmuxSessionManager', () => {
})
describe('spawn failure recovery', () => {
test('#given the first isolated container spawn fails #when onSessionCreated fires #then the session is deferred for retry', async () => {
// given
mockIsInsideTmux.mockReturnValue(true)
mockSpawnTmuxSession.mockImplementation(async () => ({
success: false,
}))
const logSpy = spyOn(sharedModule, 'log').mockImplementation(() => {})
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)
// when
await manager.onSessionCreated(
createSessionCreatedEvent('ses_isolated_fail', 'ses_parent', 'Isolated Failure Task')
)
// then
expect(mockSpawnTmuxSession).toHaveBeenCalledTimes(1)
expect(mockExecuteActions).toHaveBeenCalledTimes(0)
expect(
logSpy.mock.calls.some(([message]) =>
String(message).includes('isolated container failed, deferring session for retry')
)
).toBe(true)
expect(Reflect.get(manager, 'deferredQueue')).toEqual(['ses_isolated_fail'])
logSpy.mockRestore()
})
test('#given queryWindowState returns null #when onSessionCreated fires #then session is enqueued in deferred queue', async () => {
// given
mockIsInsideTmux.mockReturnValue(true)
@@ -799,13 +871,11 @@ describe('TmuxSessionManager', () => {
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
// when
@@ -824,14 +894,70 @@ describe('TmuxSessionManager', () => {
logSpy.mockRestore()
})
test('#given isolated window state returns one transient null #when another subagent is created #then the existing container is reused', async () => {
// given
mockIsInsideTmux.mockReturnValue(true)
const isolatedPaneId = '%isolated-session-ses_first'
let isolatedPaneQueryCount = 0
mockQueryWindowState.mockImplementation(async (paneId: string) => { if (paneId === isolatedPaneId) {
isolatedPaneQueryCount += 1
if (isolatedPaneQueryCount === 1) {
return null
}
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 = 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_first', 'ses_parent', 'First Task')
)
mockSpawnTmuxSession.mockClear()
mockExecuteActions.mockClear()
// when
await manager.onSessionCreated(
createSessionCreatedEvent('ses_second', 'ses_parent', 'Second Task')
)
// then
expect(mockSpawnTmuxSession).toHaveBeenCalledTimes(0)
expect(mockExecuteActions).toHaveBeenCalledTimes(1)
expect(Reflect.get(manager, 'isolatedWindowPaneId')).toBe(isolatedPaneId)
expect(mockExecuteActions.mock.calls[0]?.[1]?.sourcePaneId).toBe(isolatedPaneId)
})
test('#given spawn fails without close action #when onSessionCreated fires #then session is enqueued in deferred queue', async () => {
// given
mockIsInsideTmux.mockReturnValue(true)
mockQueryWindowState.mockImplementation(async () => createWindowState())
mockExecuteActions.mockImplementation(async (actions) => ({
mockExecuteActions.mockImplementation(async (actions: PaneAction[]) => ({
success: false,
spawnedPaneId: undefined,
results: actions.map((action) => ({
results: actions.map((action: PaneAction) => ({
action,
result: { success: false, error: 'spawn failed' },
})),
@@ -840,13 +966,11 @@ describe('TmuxSessionManager', () => {
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
// when
@@ -893,13 +1017,11 @@ describe('TmuxSessionManager', () => {
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
// when
@@ -947,13 +1069,11 @@ describe('TmuxSessionManager', () => {
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext({ sessionStatusResult: { data: {} } })
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
await manager.onSessionCreated(
@@ -995,13 +1115,11 @@ describe('TmuxSessionManager', () => {
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
await manager.onSessionCreated(
@@ -1032,8 +1150,7 @@ describe('TmuxSessionManager', () => {
mockIsInsideTmux.mockReturnValue(true)
let stateCallCount = 0
mockQueryWindowState.mockImplementation(async (paneId) => {
stateCallCount++
mockQueryWindowState.mockImplementation(async (paneId: string) => { stateCallCount++
if (paneId === '%isolated-session-ses_first') {
return createWindowState({
@@ -1063,19 +1180,16 @@ describe('TmuxSessionManager', () => {
title: 'isolated',
isActive: true,
},
})
})
}) })
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
isolation: 'session',
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
await manager.onSessionCreated(
@@ -1097,11 +1211,76 @@ describe('TmuxSessionManager', () => {
expect(Reflect.get(manager, 'isolatedWindowPaneId')).toBeUndefined()
})
test('#given window 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: string) => { stateCallCount += 1
if (paneId === '%isolated-window-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-window-ses_first',
width: 110,
height: 44,
left: 0,
top: 0,
title: 'isolated',
isActive: true,
},
}) })
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config = createTmuxConfig({ enabled: true,
isolation: 'window',
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-window-ses_first',
sessionId: 'ses_first',
})
expect(Reflect.get(manager, 'isolatedContainerPaneId')).toBeUndefined()
expect(Reflect.get(manager, 'isolatedWindowPaneId')).toBeUndefined()
})
test('#given session isolation with another subagent still tracked #when the anchor subagent is deleted first #then it reassigns the anchor and cleans up when the last subagent exits', async () => {
// given
mockIsInsideTmux.mockReturnValue(true)
mockQueryWindowState.mockImplementation(async (paneId) => {
if (paneId === '%isolated-session-ses_first') {
mockQueryWindowState.mockImplementation(async (paneId: string) => { if (paneId === '%isolated-session-ses_first') {
return createWindowState({
mainPane: {
paneId,
@@ -1151,19 +1330,16 @@ describe('TmuxSessionManager', () => {
})
}
return createWindowState()
})
return createWindowState() })
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
isolation: 'session',
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
await manager.onSessionCreated(
@@ -1202,18 +1378,117 @@ describe('TmuxSessionManager', () => {
expect(Reflect.get(manager, 'isolatedWindowPaneId')).toBeUndefined()
})
test('#given window isolation with another subagent still tracked #when the anchor subagent is deleted first #then it reassigns the anchor and cleans up when the last subagent exits', async () => {
// given
mockIsInsideTmux.mockReturnValue(true)
mockQueryWindowState.mockImplementation(async (paneId: string) => { if (paneId === '%isolated-window-ses_first') {
return createWindowState({
mainPane: {
paneId,
width: 110,
height: 44,
left: 0,
top: 0,
title: 'isolated',
isActive: true,
},
agentPanes: [
{
paneId: '%mock',
width: 40,
height: 44,
left: 110,
top: 0,
title: 'omo-subagent-Second Task',
isActive: false,
},
],
})
}
if (paneId === '%mock') {
return createWindowState({
mainPane: {
paneId: '%isolated-window-ses_first',
width: 110,
height: 44,
left: 0,
top: 0,
title: 'isolated',
isActive: true,
},
agentPanes: [
{
paneId,
width: 40,
height: 44,
left: 110,
top: 0,
title: 'omo-subagent-Second Task',
isActive: false,
},
],
})
}
return createWindowState() })
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config = createTmuxConfig({ enabled: true,
isolation: 'window',
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')
)
await manager.onSessionCreated(
createSessionCreatedEvent('ses_second', 'ses_parent', 'Second Task')
)
mockExecuteAction.mockClear()
// when
await manager.onSessionDeleted({ sessionID: 'ses_first' })
// then
expect(mockExecuteAction).toHaveBeenCalledTimes(0)
expect(Reflect.get(manager, 'isolatedContainerPaneId')).toBe('%isolated-window-ses_first')
expect(Reflect.get(manager, 'isolatedWindowPaneId')).toBe('%mock')
// when
await manager.onSessionDeleted({ sessionID: 'ses_second' })
// then
expect(mockExecuteAction).toHaveBeenCalledTimes(2)
expect(mockExecuteAction.mock.calls[0]?.[0]).toEqual({
type: 'close',
paneId: '%mock',
sessionId: 'ses_second',
})
expect(mockExecuteAction.mock.calls[1]?.[0]).toEqual({
type: 'close',
paneId: '%isolated-window-ses_first',
sessionId: 'ses_second',
})
expect(Reflect.get(manager, 'isolatedContainerPaneId')).toBeUndefined()
expect(Reflect.get(manager, 'isolatedWindowPaneId')).toBeUndefined()
})
test('does nothing when untracked session is deleted', async () => {
// given
mockIsInsideTmux.mockReturnValue(true)
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
// when
@@ -1230,8 +1505,7 @@ describe('TmuxSessionManager', () => {
mockIsInsideTmux.mockReturnValue(true)
let callCount = 0
mockExecuteActions.mockImplementation(async (actions) => {
callCount++
mockExecuteActions.mockImplementation(async (actions: PaneAction[]) => { callCount++
for (const action of actions) {
if (action.type === 'spawn') {
trackedSessions.add(action.sessionId)
@@ -1241,18 +1515,15 @@ describe('TmuxSessionManager', () => {
success: true,
spawnedPaneId: `%${callCount}`,
results: [],
}
})
} })
const { TmuxSessionManager } = await import('./manager')
const ctx = createMockContext()
const config: TmuxConfig = {
enabled: true,
const config = createTmuxConfig({ enabled: true,
layout: 'main-vertical',
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
agent_pane_min_width: 40, })
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
await manager.onSessionCreated(