2026-02-08 16:25:25 +09:00
|
|
|
import type { OhMyOpenCodeConfig } from "./config"
|
|
|
|
|
import type { ModelCacheState } from "./plugin-state"
|
|
|
|
|
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"
|
2026-05-11 12:37:54 +09:00
|
|
|
import { cleanupSessionTeamRuns } from "./features/team-mode/team-runtime/session-cleanup"
|
2026-04-18 02:35:46 +09:00
|
|
|
import { createModelFallbackControllerAccessor } from "./hooks/model-fallback"
|
2026-02-08 16:25:25 +09:00
|
|
|
import { initTaskToastManager } from "./features/task-toast-manager"
|
|
|
|
|
import { TmuxSessionManager } from "./features/tmux-subagent"
|
2026-04-07 22:49:37 +09:00
|
|
|
import * as openclawRuntimeDispatch from "./openclaw/runtime-dispatch"
|
2026-03-27 15:23:48 +09:00
|
|
|
import { registerManagerForCleanup } from "./features/background-agent/process-cleanup"
|
2026-02-08 16:25:25 +09:00
|
|
|
import { createConfigHandler } from "./plugin-handlers"
|
|
|
|
|
import { log } from "./shared"
|
2026-03-27 13:59:06 +09:00
|
|
|
import { markServerRunningInProcess } from "./shared/tmux/tmux-utils/server-health"
|
2026-04-18 02:35:46 +09:00
|
|
|
import type { ModelFallbackControllerAccessor } from "./hooks/model-fallback"
|
2026-02-08 16:25:25 +09:00
|
|
|
|
2026-04-06 18:45:33 +09:00
|
|
|
type CreateManagersDeps = {
|
|
|
|
|
BackgroundManagerClass: typeof BackgroundManager
|
|
|
|
|
SkillMcpManagerClass: typeof SkillMcpManager
|
|
|
|
|
TmuxSessionManagerClass: typeof TmuxSessionManager
|
|
|
|
|
initTaskToastManagerFn: typeof initTaskToastManager
|
|
|
|
|
registerManagerForCleanupFn: typeof registerManagerForCleanup
|
2026-05-11 12:37:54 +09:00
|
|
|
cleanupSessionTeamRunsFn: typeof cleanupSessionTeamRuns
|
2026-04-06 18:45:33 +09:00
|
|
|
createConfigHandlerFn: typeof createConfigHandler
|
|
|
|
|
markServerRunningInProcessFn: typeof markServerRunningInProcess
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const defaultCreateManagersDeps: CreateManagersDeps = {
|
|
|
|
|
BackgroundManagerClass: BackgroundManager,
|
|
|
|
|
SkillMcpManagerClass: SkillMcpManager,
|
|
|
|
|
TmuxSessionManagerClass: TmuxSessionManager,
|
|
|
|
|
initTaskToastManagerFn: initTaskToastManager,
|
|
|
|
|
registerManagerForCleanupFn: registerManagerForCleanup,
|
2026-05-11 12:37:54 +09:00
|
|
|
cleanupSessionTeamRunsFn: cleanupSessionTeamRuns,
|
2026-04-06 18:45:33 +09:00
|
|
|
createConfigHandlerFn: createConfigHandler,
|
|
|
|
|
markServerRunningInProcessFn: markServerRunningInProcess,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 16:25:25 +09:00
|
|
|
export type Managers = {
|
|
|
|
|
tmuxSessionManager: TmuxSessionManager
|
|
|
|
|
backgroundManager: BackgroundManager
|
|
|
|
|
skillMcpManager: SkillMcpManager
|
|
|
|
|
configHandler: ReturnType<typeof createConfigHandler>
|
2026-04-18 02:35:46 +09:00
|
|
|
modelFallbackControllerAccessor: ModelFallbackControllerAccessor
|
2026-02-08 16:25:25 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createManagers(args: {
|
|
|
|
|
ctx: PluginContext
|
|
|
|
|
pluginConfig: OhMyOpenCodeConfig
|
|
|
|
|
tmuxConfig: TmuxConfig
|
|
|
|
|
modelCacheState: ModelCacheState
|
2026-02-16 00:58:46 +02:00
|
|
|
backgroundNotificationHookEnabled: boolean
|
2026-04-06 18:45:33 +09:00
|
|
|
deps?: Partial<CreateManagersDeps>
|
2026-02-08 16:25:25 +09:00
|
|
|
}): Managers {
|
2026-02-16 00:58:46 +02:00
|
|
|
const { ctx, pluginConfig, tmuxConfig, modelCacheState, backgroundNotificationHookEnabled } = args
|
2026-04-06 18:45:33 +09:00
|
|
|
const deps = { ...defaultCreateManagersDeps, ...args.deps }
|
2026-02-08 16:25:25 +09:00
|
|
|
|
2026-05-15 06:46:07 -04:00
|
|
|
// Only mark the server as in-process when the SDK actually exposes a
|
|
|
|
|
// serverUrl. `tmuxConfig.enabled` alone is not proof of a running server —
|
|
|
|
|
// a vanilla `opencode` session (no `opencode serve`/`opencode web`) leaves
|
|
|
|
|
// `ctx.serverUrl` undefined, and marking it running would make
|
|
|
|
|
// `isServerRunning` short-circuit to true. That bypasses the guard in
|
|
|
|
|
// `createTeamLayout` and lets it spawn tmux panes whose `opencode attach`
|
|
|
|
|
// command then fails because nothing is actually listening on the
|
|
|
|
|
// fallback port (issue #3894).
|
|
|
|
|
if (tmuxConfig.enabled && ctx.serverUrl) {
|
2026-04-06 18:45:33 +09:00
|
|
|
deps.markServerRunningInProcessFn()
|
2026-04-05 13:27:41 +09:00
|
|
|
}
|
2026-04-06 18:45:33 +09:00
|
|
|
const tmuxSessionManager = new deps.TmuxSessionManagerClass(ctx, tmuxConfig)
|
2026-04-28 15:27:34 +09:00
|
|
|
const modelFallbackControllerAccessor = createModelFallbackControllerAccessor()
|
2026-05-11 12:37:54 +09:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-08 16:25:25 +09:00
|
|
|
|
2026-04-06 18:45:33 +09:00
|
|
|
deps.registerManagerForCleanupFn({
|
2026-03-27 15:23:48 +09:00
|
|
|
shutdown: async () => {
|
2026-05-11 12:37:54 +09:00
|
|
|
await cleanupTeamModeRuns().catch((error) => {
|
|
|
|
|
log("[create-managers] team-mode cleanup error during process shutdown:", error)
|
|
|
|
|
})
|
2026-03-27 15:23:48 +09:00
|
|
|
await tmuxSessionManager.cleanup().catch((error) => {
|
|
|
|
|
log("[create-managers] tmux cleanup error during process shutdown:", error)
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
2026-05-11 12:37:54 +09:00
|
|
|
backgroundManager = new deps.BackgroundManagerClass({
|
2026-05-02 03:01:03 +09:00
|
|
|
pluginContext: ctx,
|
|
|
|
|
config: pluginConfig.background_task,
|
|
|
|
|
tmuxConfig,
|
|
|
|
|
onSubagentSessionCreated: async (event: SubagentSessionCreatedEvent) => {
|
2026-04-10 10:47:27 +09:00
|
|
|
log("[create-managers] onSubagentSessionCreated callback received", {
|
|
|
|
|
sessionID: event.sessionID,
|
|
|
|
|
parentID: event.parentID,
|
2026-02-08 16:25:25 +09:00
|
|
|
title: event.title,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await tmuxSessionManager.onSessionCreated({
|
|
|
|
|
type: "session.created",
|
|
|
|
|
properties: {
|
|
|
|
|
info: {
|
|
|
|
|
id: event.sessionID,
|
|
|
|
|
parentID: event.parentID,
|
|
|
|
|
title: event.title,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-07 22:49:37 +09:00
|
|
|
if (pluginConfig.openclaw) {
|
2026-04-10 10:47:27 +09:00
|
|
|
await openclawRuntimeDispatch.dispatchOpenClawEvent({
|
2026-04-07 22:49:37 +09:00
|
|
|
config: pluginConfig.openclaw,
|
|
|
|
|
rawEvent: "session.created",
|
|
|
|
|
context: {
|
|
|
|
|
sessionId: event.sessionID,
|
|
|
|
|
projectPath: ctx.directory,
|
|
|
|
|
tmuxPaneId: tmuxSessionManager.getTrackedPaneId?.(event.sessionID) ?? process.env.TMUX_PANE,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 10:47:27 +09:00
|
|
|
log("[create-managers] onSubagentSessionCreated callback completed")
|
2026-02-08 16:25:25 +09:00
|
|
|
},
|
2026-05-02 03:01:03 +09:00
|
|
|
onShutdown: async () => {
|
2026-05-11 12:37:54 +09:00
|
|
|
await cleanupTeamModeRuns().catch((error) => {
|
|
|
|
|
log("[create-managers] team-mode cleanup error during shutdown:", error)
|
|
|
|
|
})
|
2026-05-02 03:01:03 +09:00
|
|
|
await tmuxSessionManager.cleanup().catch((error) => {
|
|
|
|
|
log("[create-managers] tmux cleanup error during shutdown:", error)
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
enableParentSessionNotifications: backgroundNotificationHookEnabled,
|
|
|
|
|
modelFallbackControllerAccessor,
|
|
|
|
|
})
|
2026-02-08 16:25:25 +09:00
|
|
|
|
2026-04-06 18:45:33 +09:00
|
|
|
deps.initTaskToastManagerFn(ctx.client)
|
2026-02-08 16:25:25 +09:00
|
|
|
|
2026-04-06 18:45:33 +09:00
|
|
|
const skillMcpManager = new deps.SkillMcpManagerClass()
|
2026-02-08 16:25:25 +09:00
|
|
|
|
2026-04-06 18:45:33 +09:00
|
|
|
const configHandler = deps.createConfigHandlerFn({
|
2026-02-08 16:25:25 +09:00
|
|
|
ctx: { directory: ctx.directory, client: ctx.client },
|
|
|
|
|
pluginConfig,
|
|
|
|
|
modelCacheState,
|
|
|
|
|
})
|
|
|
|
|
return {
|
|
|
|
|
tmuxSessionManager,
|
|
|
|
|
backgroundManager,
|
|
|
|
|
skillMcpManager,
|
|
|
|
|
configHandler,
|
2026-04-18 02:35:46 +09:00
|
|
|
modelFallbackControllerAccessor,
|
2026-02-08 16:25:25 +09:00
|
|
|
}
|
|
|
|
|
}
|