import type { PluginInput } from "@opencode-ai/plugin" import { log } from "../../shared/logger" import { HOOK_NAME } from "./hook-name" import { isAbortError } from "./is-abort-error" import { handleAtlasSessionIdle } from "./idle-event" import type { AtlasHookOptions, SessionState } from "./types" export function createAtlasEventHandler(input: { ctx: PluginInput options?: AtlasHookOptions sessions: Map getState: (sessionID: string) => SessionState }): (arg: { event: { type: string; properties?: unknown } }) => Promise { const { ctx, options, sessions, getState } = input return async ({ event }): Promise => { const props = event.properties as Record | undefined if (event.type === "session.error") { const sessionID = props?.sessionID as string | undefined if (!sessionID) return const state = getState(sessionID) const isAbort = isAbortError(props?.error) state.lastEventWasAbortError = isAbort log(`[${HOOK_NAME}] session.error`, { sessionID, isAbort }) if (!isAbort) { const previousInjectedAt = state.lastContinuationInjectedAt await handleAtlasSessionIdle({ ctx, options, getState, sessionID }) if ( state.lastContinuationInjectedAt !== undefined && state.lastContinuationInjectedAt !== previousInjectedAt ) { state.skipNextIdleAfterRuntimeErrorRetry = true } } return } if (event.type === "session.idle") { const sessionID = props?.sessionID as string | undefined if (!sessionID) return await handleAtlasSessionIdle({ ctx, options, getState, sessionID }) return } if (event.type === "message.updated") { const info = props?.info as Record | undefined const sessionID = info?.sessionID as string | undefined const role = info?.role as string | undefined if (!sessionID) return const state = sessions.get(sessionID) if (state) { state.lastEventWasAbortError = false state.skipNextIdleAfterRuntimeErrorRetry = false if (role === "user") { state.waitingForFinalWaveApproval = false } } return } if (event.type === "message.part.updated") { const info = props?.info as Record | undefined const sessionID = info?.sessionID as string | undefined const role = info?.role as string | undefined if (sessionID && role === "assistant") { const state = sessions.get(sessionID) if (state) { state.lastEventWasAbortError = false state.skipNextIdleAfterRuntimeErrorRetry = false } } return } if (event.type === "tool.execute.before" || event.type === "tool.execute.after") { const sessionID = props?.sessionID as string | undefined if (sessionID) { const state = sessions.get(sessionID) if (state) { state.lastEventWasAbortError = false state.skipNextIdleAfterRuntimeErrorRetry = false } } return } if (event.type === "session.deleted") { const sessionInfo = props?.info as { id?: string } | undefined if (sessionInfo?.id) { const deletedState = sessions.get(sessionInfo.id) if (deletedState?.pendingRetryTimer) { clearTimeout(deletedState.pendingRetryTimer) } sessions.delete(sessionInfo.id) log(`[${HOOK_NAME}] Session deleted: cleaned up`, { sessionID: sessionInfo.id }) } return } if (event.type === "session.compacted") { const sessionID = (props?.sessionID ?? (props?.info as { id?: string } | undefined)?.id) as string | undefined if (sessionID) { const compactedState = sessions.get(sessionID) if (compactedState?.pendingRetryTimer) { clearTimeout(compactedState.pendingRetryTimer) } sessions.delete(sessionID) log(`[${HOOK_NAME}] Session compacted: cleaned up`, { sessionID }) } } } }