fix(team-mode): cleanup team runs on shutdown

This commit is contained in:
YeonGyu-Kim
2026-05-11 12:37:54 +09:00
parent 59209f0659
commit 917398b719
2 changed files with 69 additions and 2 deletions
+46 -1
View File
@@ -8,11 +8,23 @@ import { createManagers } from "./create-managers"
import * as openclawRuntimeDispatch from "./openclaw/runtime-dispatch"
import { createModelCacheState } from "./plugin-state"
type CleanupRegistration = {
shutdown: () => void | Promise<void>
}
type CleanupSessionTeamRunsFn = typeof import("./features/team-mode/team-runtime/session-cleanup").cleanupSessionTeamRuns
const markServerRunningInProcess = mock(() => {})
let backgroundManagerOptions: {
onSubagentSessionCreated?: (event: { sessionID: string; parentID: string; title: string }) => Promise<void>
} | null = null
const trackedPaneBySession = new Map<string, string>()
const registeredCleanupManagers: CleanupRegistration[] = []
const cleanupSessionTeamRunsMock = mock(async () => ({
cleanedTeamRunIds: [],
removedLayoutTeamRunIds: [],
errors: [],
}))
class MockBackgroundManager {
constructor(config: {
@@ -51,7 +63,9 @@ function initTaskToastManager(): ReturnType<typeof import("./features/task-toast
return {} as ReturnType<typeof import("./features/task-toast-manager").initTaskToastManager>
}
function registerManagerForCleanup(): void {}
function registerManagerForCleanup(manager: CleanupRegistration): void {
registeredCleanupManagers.push(manager)
}
function createDeps(): NonNullable<Parameters<typeof createManagers>[0]["deps"]> {
return {
@@ -60,6 +74,7 @@ function createDeps(): NonNullable<Parameters<typeof createManagers>[0]["deps"]>
TmuxSessionManagerClass: MockTmuxSessionManager as typeof import("./features/tmux-subagent").TmuxSessionManager,
initTaskToastManagerFn: initTaskToastManager,
registerManagerForCleanupFn: registerManagerForCleanup,
cleanupSessionTeamRunsFn: cleanupSessionTeamRunsMock as CleanupSessionTeamRunsFn,
createConfigHandlerFn: createConfigHandler,
markServerRunningInProcessFn: markServerRunningInProcess,
}
@@ -122,6 +137,8 @@ describe("createManagers", () => {
dispatchOpenClawEvent.mockReset()
backgroundManagerOptions = null
trackedPaneBySession.clear()
registeredCleanupManagers.length = 0
cleanupSessionTeamRunsMock.mockClear()
})
afterEach(() => {
@@ -193,4 +210,32 @@ describe("createManagers", () => {
},
})
})
it("#given team mode is enabled #when process cleanup runs #then session team runs are cleaned with tmux visualization dependencies", async () => {
const args = {
ctx: createContext("/tmp/project"),
pluginConfig: OhMyOpenCodeConfigSchema.parse({
team_mode: {
enabled: true,
tmux_visualization: true,
},
}),
tmuxConfig: createTmuxConfig(true),
modelCacheState: createModelCacheState(),
backgroundNotificationHookEnabled: false,
deps: createDeps(),
}
createManagers(args)
await registeredCleanupManagers[0]?.shutdown()
expect(cleanupSessionTeamRunsMock).toHaveBeenCalledTimes(1)
const cleanupArgs = cleanupSessionTeamRunsMock.mock.calls[0]?.[0]
expect(cleanupArgs).toMatchObject({
config: args.pluginConfig.team_mode,
})
expect(cleanupArgs?.tmuxMgr).toBeInstanceOf(MockTmuxSessionManager)
expect(cleanupArgs?.bgMgr).toBeInstanceOf(MockBackgroundManager)
})
})
+23 -1
View File
@@ -5,6 +5,7 @@ import type { PluginContext, TmuxConfig } from "./plugin/types"
import type { SubagentSessionCreatedEvent } from "./features/background-agent"
import { BackgroundManager } from "./features/background-agent"
import { SkillMcpManager } from "./features/skill-mcp-manager"
import { cleanupSessionTeamRuns } from "./features/team-mode/team-runtime/session-cleanup"
import { createModelFallbackControllerAccessor } from "./hooks/model-fallback"
import { initTaskToastManager } from "./features/task-toast-manager"
import { TmuxSessionManager } from "./features/tmux-subagent"
@@ -21,6 +22,7 @@ type CreateManagersDeps = {
TmuxSessionManagerClass: typeof TmuxSessionManager
initTaskToastManagerFn: typeof initTaskToastManager
registerManagerForCleanupFn: typeof registerManagerForCleanup
cleanupSessionTeamRunsFn: typeof cleanupSessionTeamRuns
createConfigHandlerFn: typeof createConfigHandler
markServerRunningInProcessFn: typeof markServerRunningInProcess
}
@@ -31,6 +33,7 @@ const defaultCreateManagersDeps: CreateManagersDeps = {
TmuxSessionManagerClass: TmuxSessionManager,
initTaskToastManagerFn: initTaskToastManager,
registerManagerForCleanupFn: registerManagerForCleanup,
cleanupSessionTeamRunsFn: cleanupSessionTeamRuns,
createConfigHandlerFn: createConfigHandler,
markServerRunningInProcessFn: markServerRunningInProcess,
}
@@ -59,16 +62,32 @@ export function createManagers(args: {
}
const tmuxSessionManager = new deps.TmuxSessionManagerClass(ctx, tmuxConfig)
const modelFallbackControllerAccessor = createModelFallbackControllerAccessor()
let backgroundManager: BackgroundManager | undefined
const cleanupTeamModeRuns = async (): Promise<void> => {
if (!pluginConfig.team_mode?.enabled) return
const report = await deps.cleanupSessionTeamRunsFn({
config: pluginConfig.team_mode,
tmuxMgr: tmuxSessionManager,
bgMgr: backgroundManager,
})
if (report.cleanedTeamRunIds.length > 0 || report.errors.length > 0) {
log("[create-managers] team-mode session cleanup complete", report)
}
}
deps.registerManagerForCleanupFn({
shutdown: async () => {
await cleanupTeamModeRuns().catch((error) => {
log("[create-managers] team-mode cleanup error during process shutdown:", error)
})
await tmuxSessionManager.cleanup().catch((error) => {
log("[create-managers] tmux cleanup error during process shutdown:", error)
})
},
})
const backgroundManager = new deps.BackgroundManagerClass({
backgroundManager = new deps.BackgroundManagerClass({
pluginContext: ctx,
config: pluginConfig.background_task,
tmuxConfig,
@@ -105,6 +124,9 @@ export function createManagers(args: {
log("[create-managers] onSubagentSessionCreated callback completed")
},
onShutdown: async () => {
await cleanupTeamModeRuns().catch((error) => {
log("[create-managers] team-mode cleanup error during shutdown:", error)
})
await tmuxSessionManager.cleanup().catch((error) => {
log("[create-managers] tmux cleanup error during shutdown:", error)
})