feat(team-mode): add team runtime resource cleanup with tests

This commit is contained in:
YeonGyu-Kim
2026-04-28 10:46:54 +09:00
parent 62d409085e
commit cb1c7508e7
2 changed files with 157 additions and 0 deletions
@@ -0,0 +1,77 @@
import { rm } from "node:fs/promises"
import type { TeamModeConfig } from "../../../config/schema/team-mode"
import type { BackgroundManager } from "../../background-agent/manager"
import type { TmuxSessionManager } from "../../tmux-subagent/manager"
import { removeTeamLayout } from "../team-layout-tmux/layout"
import { unregisterTeamSessionsByTeam } from "../team-session-registry"
import { loadRuntimeState, transitionRuntimeState } from "../team-state-store/store"
import type { TeamRunCreateError } from "./create"
type SpawnedMemberResource = {
taskId?: string
worktreePath?: string
}
function normalizeError(error: unknown): Error {
return error instanceof Error ? error : new Error(String(error))
}
export async function cleanupTeamRunResources(args: {
teamRunId: string
config: TeamModeConfig
resources: SpawnedMemberResource[]
bgMgr: BackgroundManager
tmuxMgr?: TmuxSessionManager
createdLayout: boolean
}): Promise<TeamRunCreateError["cleanupReport"]> {
const cleanupReport: TeamRunCreateError["cleanupReport"] = {
cancelledTaskIds: [],
removedLayout: false,
removedWorktrees: [],
errors: [],
}
for (const resource of [...args.resources].reverse()) {
if (resource.taskId) {
try {
await args.bgMgr.cancelTask(resource.taskId, {
source: "team-create-rollback",
reason: "creating_rollback",
skipNotification: true,
})
cleanupReport.cancelledTaskIds.push(resource.taskId)
} catch (cancelError) {
cleanupReport.errors.push(`cancel ${resource.taskId}: ${normalizeError(cancelError).message}`)
}
}
if (resource.worktreePath) {
try {
await rm(resource.worktreePath, { recursive: true, force: true })
cleanupReport.removedWorktrees.push(resource.worktreePath)
} catch (cleanupError) {
cleanupReport.errors.push(`worktree ${resource.worktreePath}: ${normalizeError(cleanupError).message}`)
}
}
}
if (args.createdLayout && args.tmuxMgr) {
try {
const runtimeState = await loadRuntimeState(args.teamRunId, args.config)
await removeTeamLayout(args.teamRunId, runtimeState.tmuxLayout, args.tmuxMgr)
cleanupReport.removedLayout = true
} catch (layoutError) {
cleanupReport.errors.push(`layout ${args.teamRunId}: ${normalizeError(layoutError).message}`)
}
}
await transitionRuntimeState(args.teamRunId, (runtimeState) => ({ ...runtimeState, status: "failed" }), args.config).catch((transitionError) => {
cleanupReport.errors.push(`state ${args.teamRunId}: ${normalizeError(transitionError).message}`)
return undefined
})
unregisterTeamSessionsByTeam(args.teamRunId)
return cleanupReport
}