refactor(tmux-subagent): introduce dependency injection for testability (#1267)
Co-authored-by: justsisyphus <justsisyphus@users.noreply.github.com>
This commit is contained in:
parent
5f0b6d49f5
commit
d3e2b36e3d
@ -2,6 +2,7 @@ import { describe, test, expect, mock, beforeEach } from 'bun:test'
|
|||||||
import type { TmuxConfig } from '../../config/schema'
|
import type { TmuxConfig } from '../../config/schema'
|
||||||
import type { WindowState, PaneAction } from './types'
|
import type { WindowState, PaneAction } from './types'
|
||||||
import type { ActionResult, ExecuteContext } from './action-executor'
|
import type { ActionResult, ExecuteContext } from './action-executor'
|
||||||
|
import type { TmuxUtilDeps } from './manager'
|
||||||
|
|
||||||
type ExecuteActionsResult = {
|
type ExecuteActionsResult = {
|
||||||
success: boolean
|
success: boolean
|
||||||
@ -33,6 +34,11 @@ const mockExecuteAction = mock<(
|
|||||||
const mockIsInsideTmux = mock<() => boolean>(() => true)
|
const mockIsInsideTmux = mock<() => boolean>(() => true)
|
||||||
const mockGetCurrentPaneId = mock<() => string | undefined>(() => '%0')
|
const mockGetCurrentPaneId = mock<() => string | undefined>(() => '%0')
|
||||||
|
|
||||||
|
const mockTmuxDeps: TmuxUtilDeps = {
|
||||||
|
isInsideTmux: mockIsInsideTmux,
|
||||||
|
getCurrentPaneId: mockGetCurrentPaneId,
|
||||||
|
}
|
||||||
|
|
||||||
mock.module('./pane-state-querier', () => ({
|
mock.module('./pane-state-querier', () => ({
|
||||||
queryWindowState: mockQueryWindowState,
|
queryWindowState: mockQueryWindowState,
|
||||||
paneExists: mockPaneExists,
|
paneExists: mockPaneExists,
|
||||||
@ -51,15 +57,19 @@ mock.module('./action-executor', () => ({
|
|||||||
executeAction: mockExecuteAction,
|
executeAction: mockExecuteAction,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
mock.module('../../shared/tmux', () => ({
|
mock.module('../../shared/tmux', () => {
|
||||||
isInsideTmux: mockIsInsideTmux,
|
const { isInsideTmux, getCurrentPaneId } = require('../../shared/tmux/tmux-utils')
|
||||||
getCurrentPaneId: mockGetCurrentPaneId,
|
const { POLL_INTERVAL_BACKGROUND_MS, SESSION_TIMEOUT_MS, SESSION_MISSING_GRACE_MS } = require('../../shared/tmux/constants')
|
||||||
POLL_INTERVAL_BACKGROUND_MS: 2000,
|
return {
|
||||||
SESSION_TIMEOUT_MS: 600000,
|
isInsideTmux,
|
||||||
SESSION_MISSING_GRACE_MS: 6000,
|
getCurrentPaneId,
|
||||||
SESSION_READY_POLL_INTERVAL_MS: 100,
|
POLL_INTERVAL_BACKGROUND_MS,
|
||||||
SESSION_READY_TIMEOUT_MS: 500,
|
SESSION_TIMEOUT_MS,
|
||||||
}))
|
SESSION_MISSING_GRACE_MS,
|
||||||
|
SESSION_READY_POLL_INTERVAL_MS: 100,
|
||||||
|
SESSION_READY_TIMEOUT_MS: 500,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const trackedSessions = new Set<string>()
|
const trackedSessions = new Set<string>()
|
||||||
|
|
||||||
@ -148,7 +158,7 @@ describe('TmuxSessionManager', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
const manager = new TmuxSessionManager(ctx, config)
|
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
|
||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect(manager).toBeDefined()
|
expect(manager).toBeDefined()
|
||||||
@ -168,7 +178,7 @@ describe('TmuxSessionManager', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
const manager = new TmuxSessionManager(ctx, config)
|
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
|
||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect(manager).toBeDefined()
|
expect(manager).toBeDefined()
|
||||||
@ -188,7 +198,7 @@ describe('TmuxSessionManager', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
const manager = new TmuxSessionManager(ctx, config)
|
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
|
||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect(manager).toBeDefined()
|
expect(manager).toBeDefined()
|
||||||
@ -210,7 +220,7 @@ describe('TmuxSessionManager', () => {
|
|||||||
main_pane_min_width: 80,
|
main_pane_min_width: 80,
|
||||||
agent_pane_min_width: 40,
|
agent_pane_min_width: 40,
|
||||||
}
|
}
|
||||||
const manager = new TmuxSessionManager(ctx, config)
|
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
|
||||||
const event = createSessionCreatedEvent(
|
const event = createSessionCreatedEvent(
|
||||||
'ses_child',
|
'ses_child',
|
||||||
'ses_parent',
|
'ses_parent',
|
||||||
@ -271,7 +281,7 @@ describe('TmuxSessionManager', () => {
|
|||||||
main_pane_min_width: 80,
|
main_pane_min_width: 80,
|
||||||
agent_pane_min_width: 40,
|
agent_pane_min_width: 40,
|
||||||
}
|
}
|
||||||
const manager = new TmuxSessionManager(ctx, config)
|
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
|
||||||
|
|
||||||
//#when - first agent
|
//#when - first agent
|
||||||
await manager.onSessionCreated(
|
await manager.onSessionCreated(
|
||||||
@ -305,7 +315,7 @@ describe('TmuxSessionManager', () => {
|
|||||||
main_pane_min_width: 80,
|
main_pane_min_width: 80,
|
||||||
agent_pane_min_width: 40,
|
agent_pane_min_width: 40,
|
||||||
}
|
}
|
||||||
const manager = new TmuxSessionManager(ctx, config)
|
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
|
||||||
const event = createSessionCreatedEvent('ses_root', undefined, 'Root Session')
|
const event = createSessionCreatedEvent('ses_root', undefined, 'Root Session')
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
@ -327,7 +337,7 @@ describe('TmuxSessionManager', () => {
|
|||||||
main_pane_min_width: 80,
|
main_pane_min_width: 80,
|
||||||
agent_pane_min_width: 40,
|
agent_pane_min_width: 40,
|
||||||
}
|
}
|
||||||
const manager = new TmuxSessionManager(ctx, config)
|
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
|
||||||
const event = createSessionCreatedEvent(
|
const event = createSessionCreatedEvent(
|
||||||
'ses_child',
|
'ses_child',
|
||||||
'ses_parent',
|
'ses_parent',
|
||||||
@ -353,7 +363,7 @@ describe('TmuxSessionManager', () => {
|
|||||||
main_pane_min_width: 80,
|
main_pane_min_width: 80,
|
||||||
agent_pane_min_width: 40,
|
agent_pane_min_width: 40,
|
||||||
}
|
}
|
||||||
const manager = new TmuxSessionManager(ctx, config)
|
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
|
||||||
const event = {
|
const event = {
|
||||||
type: 'session.deleted',
|
type: 'session.deleted',
|
||||||
properties: {
|
properties: {
|
||||||
@ -398,7 +408,7 @@ describe('TmuxSessionManager', () => {
|
|||||||
main_pane_min_width: 120,
|
main_pane_min_width: 120,
|
||||||
agent_pane_min_width: 40,
|
agent_pane_min_width: 40,
|
||||||
}
|
}
|
||||||
const manager = new TmuxSessionManager(ctx, config)
|
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
await manager.onSessionCreated(
|
await manager.onSessionCreated(
|
||||||
@ -450,7 +460,7 @@ describe('TmuxSessionManager', () => {
|
|||||||
main_pane_min_width: 80,
|
main_pane_min_width: 80,
|
||||||
agent_pane_min_width: 40,
|
agent_pane_min_width: 40,
|
||||||
}
|
}
|
||||||
const manager = new TmuxSessionManager(ctx, config)
|
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
|
||||||
|
|
||||||
await manager.onSessionCreated(
|
await manager.onSessionCreated(
|
||||||
createSessionCreatedEvent(
|
createSessionCreatedEvent(
|
||||||
@ -487,7 +497,7 @@ describe('TmuxSessionManager', () => {
|
|||||||
main_pane_min_width: 80,
|
main_pane_min_width: 80,
|
||||||
agent_pane_min_width: 40,
|
agent_pane_min_width: 40,
|
||||||
}
|
}
|
||||||
const manager = new TmuxSessionManager(ctx, config)
|
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
await manager.onSessionDeleted({ sessionID: 'ses_unknown' })
|
await manager.onSessionDeleted({ sessionID: 'ses_unknown' })
|
||||||
@ -521,7 +531,7 @@ describe('TmuxSessionManager', () => {
|
|||||||
main_pane_min_width: 80,
|
main_pane_min_width: 80,
|
||||||
agent_pane_min_width: 40,
|
agent_pane_min_width: 40,
|
||||||
}
|
}
|
||||||
const manager = new TmuxSessionManager(ctx, config)
|
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
|
||||||
|
|
||||||
await manager.onSessionCreated(
|
await manager.onSessionCreated(
|
||||||
createSessionCreatedEvent('ses_1', 'ses_parent', 'Task 1')
|
createSessionCreatedEvent('ses_1', 'ses_parent', 'Task 1')
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import type { PluginInput } from "@opencode-ai/plugin"
|
|||||||
import type { TmuxConfig } from "../../config/schema"
|
import type { TmuxConfig } from "../../config/schema"
|
||||||
import type { TrackedSession, CapacityConfig } from "./types"
|
import type { TrackedSession, CapacityConfig } from "./types"
|
||||||
import {
|
import {
|
||||||
isInsideTmux,
|
isInsideTmux as defaultIsInsideTmux,
|
||||||
getCurrentPaneId,
|
getCurrentPaneId as defaultGetCurrentPaneId,
|
||||||
POLL_INTERVAL_BACKGROUND_MS,
|
POLL_INTERVAL_BACKGROUND_MS,
|
||||||
SESSION_MISSING_GRACE_MS,
|
SESSION_MISSING_GRACE_MS,
|
||||||
SESSION_READY_POLL_INTERVAL_MS,
|
SESSION_READY_POLL_INTERVAL_MS,
|
||||||
@ -21,6 +21,16 @@ interface SessionCreatedEvent {
|
|||||||
properties?: { info?: { id?: string; parentID?: string; title?: string } }
|
properties?: { info?: { id?: string; parentID?: string; title?: string } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TmuxUtilDeps {
|
||||||
|
isInsideTmux: () => boolean
|
||||||
|
getCurrentPaneId: () => string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultTmuxDeps: TmuxUtilDeps = {
|
||||||
|
isInsideTmux: defaultIsInsideTmux,
|
||||||
|
getCurrentPaneId: defaultGetCurrentPaneId,
|
||||||
|
}
|
||||||
|
|
||||||
const SESSION_TIMEOUT_MS = 10 * 60 * 1000
|
const SESSION_TIMEOUT_MS = 10 * 60 * 1000
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,13 +53,15 @@ export class TmuxSessionManager {
|
|||||||
private sessions = new Map<string, TrackedSession>()
|
private sessions = new Map<string, TrackedSession>()
|
||||||
private pendingSessions = new Set<string>()
|
private pendingSessions = new Set<string>()
|
||||||
private pollInterval?: ReturnType<typeof setInterval>
|
private pollInterval?: ReturnType<typeof setInterval>
|
||||||
|
private deps: TmuxUtilDeps
|
||||||
|
|
||||||
constructor(ctx: PluginInput, tmuxConfig: TmuxConfig) {
|
constructor(ctx: PluginInput, tmuxConfig: TmuxConfig, deps: TmuxUtilDeps = defaultTmuxDeps) {
|
||||||
this.client = ctx.client
|
this.client = ctx.client
|
||||||
this.tmuxConfig = tmuxConfig
|
this.tmuxConfig = tmuxConfig
|
||||||
|
this.deps = deps
|
||||||
const defaultPort = process.env.OPENCODE_PORT ?? "4096"
|
const defaultPort = process.env.OPENCODE_PORT ?? "4096"
|
||||||
this.serverUrl = ctx.serverUrl?.toString() ?? `http://localhost:${defaultPort}`
|
this.serverUrl = ctx.serverUrl?.toString() ?? `http://localhost:${defaultPort}`
|
||||||
this.sourcePaneId = getCurrentPaneId()
|
this.sourcePaneId = deps.getCurrentPaneId()
|
||||||
|
|
||||||
log("[tmux-session-manager] initialized", {
|
log("[tmux-session-manager] initialized", {
|
||||||
configEnabled: this.tmuxConfig.enabled,
|
configEnabled: this.tmuxConfig.enabled,
|
||||||
@ -60,7 +72,7 @@ export class TmuxSessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isEnabled(): boolean {
|
private isEnabled(): boolean {
|
||||||
return this.tmuxConfig.enabled && isInsideTmux()
|
return this.tmuxConfig.enabled && this.deps.isInsideTmux()
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCapacityConfig(): CapacityConfig {
|
private getCapacityConfig(): CapacityConfig {
|
||||||
@ -113,7 +125,7 @@ export class TmuxSessionManager {
|
|||||||
log("[tmux-session-manager] onSessionCreated called", {
|
log("[tmux-session-manager] onSessionCreated called", {
|
||||||
enabled,
|
enabled,
|
||||||
tmuxConfigEnabled: this.tmuxConfig.enabled,
|
tmuxConfigEnabled: this.tmuxConfig.enabled,
|
||||||
isInsideTmux: isInsideTmux(),
|
isInsideTmux: this.deps.isInsideTmux(),
|
||||||
eventType: event.type,
|
eventType: event.type,
|
||||||
infoId: event.properties?.info?.id,
|
infoId: event.properties?.info?.id,
|
||||||
infoParentID: event.properties?.info?.parentID,
|
infoParentID: event.properties?.info?.parentID,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user