diff --git a/src/features/tmux-subagent/cleanup.ts b/src/features/tmux-subagent/cleanup.ts deleted file mode 100644 index 414ad00bc..000000000 --- a/src/features/tmux-subagent/cleanup.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { TmuxConfig } from "../../config/schema" -import { log } from "../../shared" -import type { TrackedSession } from "./types" -import { queryWindowState } from "./pane-state-querier" -import { executeAction } from "./action-executor" - -export async function cleanupTmuxSessions(params: { - tmuxConfig: TmuxConfig - serverUrl: string - sourcePaneId: string | undefined - sessions: Map - stopPolling: () => void -}): Promise { - params.stopPolling() - - if (params.sessions.size === 0) { - log("[tmux-session-manager] cleanup complete") - return - } - - log("[tmux-session-manager] closing all panes", { count: params.sessions.size }) - const state = params.sourcePaneId ? await queryWindowState(params.sourcePaneId) : null - - if (state) { - const closePromises = Array.from(params.sessions.values()).map((tracked) => - executeAction( - { type: "close", paneId: tracked.paneId, sessionId: tracked.sessionId }, - { config: params.tmuxConfig, serverUrl: params.serverUrl, windowState: state }, - ).catch((error) => - log("[tmux-session-manager] cleanup error for pane", { - paneId: tracked.paneId, - error: String(error), - }), - ), - ) - - await Promise.all(closePromises) - } - - params.sessions.clear() - log("[tmux-session-manager] cleanup complete") -} diff --git a/src/features/tmux-subagent/event-handlers.ts b/src/features/tmux-subagent/event-handlers.ts index 0991d10e2..2916c7439 100644 --- a/src/features/tmux-subagent/event-handlers.ts +++ b/src/features/tmux-subagent/event-handlers.ts @@ -1,6 +1,2 @@ export { coerceSessionCreatedEvent } from "./session-created-event" export type { SessionCreatedEvent } from "./session-created-event" -export { handleSessionCreated } from "./session-created-handler" -export type { SessionCreatedHandlerDeps } from "./session-created-handler" -export { handleSessionDeleted } from "./session-deleted-handler" -export type { SessionDeletedHandlerDeps } from "./session-deleted-handler" diff --git a/src/features/tmux-subagent/index.ts b/src/features/tmux-subagent/index.ts index e900555fb..cba66fa6b 100644 --- a/src/features/tmux-subagent/index.ts +++ b/src/features/tmux-subagent/index.ts @@ -1,10 +1,7 @@ export * from "./manager" export * from "./event-handlers" export * from "./polling" -export * from "./cleanup" export * from "./session-created-event" -export * from "./session-created-handler" -export * from "./session-deleted-handler" export * from "./polling-constants" export * from "./session-status-parser" export * from "./session-message-count" diff --git a/src/features/tmux-subagent/session-created-handler.ts b/src/features/tmux-subagent/session-created-handler.ts deleted file mode 100644 index 6dd1f21eb..000000000 --- a/src/features/tmux-subagent/session-created-handler.ts +++ /dev/null @@ -1,175 +0,0 @@ -import type { PluginInput } from "@opencode-ai/plugin" -import type { TmuxConfig } from "../../config/schema" -import type { CapacityConfig, TrackedSession } from "./types" -import { log } from "../../shared" -import { queryWindowState } from "./pane-state-querier" -import { decideSpawnActions, type SessionMapping } from "./decision-engine" -import { executeActions } from "./action-executor" -import type { SessionCreatedEvent } from "./session-created-event" -import { createTrackedSession } from "./tracked-session-state" - -type OpencodeClient = PluginInput["client"] - -export interface SessionCreatedHandlerDeps { - client: OpencodeClient - tmuxConfig: TmuxConfig - serverUrl: string - sourcePaneId: string | undefined - sessions: Map - pendingSessions: Set - isInsideTmux: () => boolean - isEnabled: () => boolean - getCapacityConfig: () => CapacityConfig - getSessionMappings: () => SessionMapping[] - waitForSessionReady: (sessionId: string) => Promise - startPolling: () => void -} - -export async function handleSessionCreated( - deps: SessionCreatedHandlerDeps, - event: SessionCreatedEvent, -): Promise { - const enabled = deps.isEnabled() - log("[tmux-session-manager] onSessionCreated called", { - enabled, - tmuxConfigEnabled: deps.tmuxConfig.enabled, - isInsideTmux: deps.isInsideTmux(), - eventType: event.type, - infoId: event.properties?.info?.id, - infoParentID: event.properties?.info?.parentID, - }) - - if (!enabled) return - if (event.type !== "session.created") return - - const info = event.properties?.info - if (!info?.id || !info?.parentID) return - - const sessionId = info.id - const title = info.title ?? "Subagent" - - if (deps.sessions.has(sessionId) || deps.pendingSessions.has(sessionId)) { - log("[tmux-session-manager] session already tracked or pending", { sessionId }) - return - } - - if (!deps.sourcePaneId) { - log("[tmux-session-manager] no source pane id") - return - } - - deps.pendingSessions.add(sessionId) - - try { - const state = await queryWindowState(deps.sourcePaneId) - if (!state) { - log("[tmux-session-manager] failed to query window state") - return - } - - log("[tmux-session-manager] window state queried", { - windowWidth: state.windowWidth, - mainPane: state.mainPane?.paneId, - agentPaneCount: state.agentPanes.length, - agentPanes: state.agentPanes.map((p) => p.paneId), - }) - - const decision = decideSpawnActions( - state, - sessionId, - title, - deps.getCapacityConfig(), - deps.getSessionMappings(), - ) - - log("[tmux-session-manager] spawn decision", { - canSpawn: decision.canSpawn, - reason: decision.reason, - actionCount: decision.actions.length, - actions: decision.actions.map((a) => { - if (a.type === "close") return { type: "close", paneId: a.paneId } - if (a.type === "replace") { - return { type: "replace", paneId: a.paneId, newSessionId: a.newSessionId } - } - return { type: "spawn", sessionId: a.sessionId } - }), - }) - - if (!decision.canSpawn) { - log("[tmux-session-manager] cannot spawn", { reason: decision.reason }) - return - } - - const result = await executeActions(decision.actions, { - config: deps.tmuxConfig, - serverUrl: deps.serverUrl, - windowState: state, - }) - - for (const { action, result: actionResult } of result.results) { - if (action.type === "close" && actionResult.success) { - deps.sessions.delete(action.sessionId) - log("[tmux-session-manager] removed closed session from cache", { - sessionId: action.sessionId, - }) - } - if (action.type === "replace" && actionResult.success) { - deps.sessions.delete(action.oldSessionId) - log("[tmux-session-manager] removed replaced session from cache", { - oldSessionId: action.oldSessionId, - newSessionId: action.newSessionId, - }) - } - } - - if (!result.success || !result.spawnedPaneId) { - log("[tmux-session-manager] spawn failed", { - success: result.success, - results: result.results.map((r) => ({ - type: r.action.type, - success: r.result.success, - error: r.result.error, - })), - }) - return - } - - const sessionReady = await deps.waitForSessionReady(sessionId) - if (!sessionReady) { - log("[tmux-session-manager] session not ready after timeout, closing spawned pane", { - sessionId, - paneId: result.spawnedPaneId, - }) - - await executeActions( - [{ type: "close", paneId: result.spawnedPaneId, sessionId }], - { - config: deps.tmuxConfig, - serverUrl: deps.serverUrl, - windowState: state, - }, - ) - - return - } - - deps.sessions.set( - sessionId, - createTrackedSession({ - sessionId, - paneId: result.spawnedPaneId, - description: title, - }), - ) - - log("[tmux-session-manager] pane spawned and tracked", { - sessionId, - paneId: result.spawnedPaneId, - sessionReady, - }) - - deps.startPolling() - } finally { - deps.pendingSessions.delete(sessionId) - } -} diff --git a/src/features/tmux-subagent/session-deleted-handler.ts b/src/features/tmux-subagent/session-deleted-handler.ts deleted file mode 100644 index f832cf481..000000000 --- a/src/features/tmux-subagent/session-deleted-handler.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { TmuxConfig } from "../../config/schema" -import type { TrackedSession } from "./types" -import { log } from "../../shared" -import { queryWindowState } from "./pane-state-querier" -import { decideCloseAction, type SessionMapping } from "./decision-engine" -import { executeAction } from "./action-executor" - -export interface SessionDeletedHandlerDeps { - tmuxConfig: TmuxConfig - serverUrl: string - sourcePaneId: string | undefined - sessions: Map - isEnabled: () => boolean - getSessionMappings: () => SessionMapping[] - stopPolling: () => void -} - -export async function handleSessionDeleted( - deps: SessionDeletedHandlerDeps, - event: { sessionID: string }, -): Promise { - if (!deps.isEnabled()) return - if (!deps.sourcePaneId) return - - const tracked = deps.sessions.get(event.sessionID) - if (!tracked) return - - log("[tmux-session-manager] onSessionDeleted", { sessionId: event.sessionID }) - - const state = await queryWindowState(deps.sourcePaneId) - if (!state) { - deps.sessions.delete(event.sessionID) - return - } - - const closeAction = decideCloseAction(state, event.sessionID, deps.getSessionMappings()) - if (closeAction) { - await executeAction(closeAction, { - config: deps.tmuxConfig, - serverUrl: deps.serverUrl, - windowState: state, - }) - } - - deps.sessions.delete(event.sessionID) - - if (deps.sessions.size === 0) { - deps.stopPolling() - } -}