fix(plugin): normalize event session ids

Handle OpenCode session events that carry the session ID under properties.info.id or properties.info.sessionID so background tasks and continuation hooks do not miss idle/error/delete events.

Add regression coverage for nested session.idle events completing background tasks and waking continuation hooks.
This commit is contained in:
YeonGyu-Kim
2026-05-12 11:48:18 +09:00
parent 67f90a819e
commit 4da48555ee
51 changed files with 740 additions and 254 deletions
+7 -7
View File
@@ -10,6 +10,7 @@ import { isAbortError } from "../../shared/is-abort-error"
import { resolveFallbackBootstrapModel } from "./fallback-bootstrap-model"
import { dispatchFallbackRetry } from "./fallback-retry-dispatcher"
import { createSessionStatusHandler } from "./session-status-handler"
import { resolveMessageEventSessionID, resolveSessionEventID } from "../../shared/event-session-id"
export function createEventHandler(deps: HookDeps, helpers: AutoRetryHelpers) {
const { config, pluginConfig, sessionStates, sessionLastAccess, sessionRetryInFlight, sessionAwaitingFallbackResult, sessionFallbackTimeouts, sessionStatusRetryKeys } = deps
@@ -30,7 +31,7 @@ export function createEventHandler(deps: HookDeps, helpers: AutoRetryHelpers) {
const handleSessionCreated = (props: Record<string, unknown> | undefined) => {
const sessionInfo = props?.info as { id?: string; model?: string } | undefined
const sessionID = sessionInfo?.id
const sessionID = resolveSessionEventID(props)
const model = sessionInfo?.model
if (sessionID && model) {
@@ -41,8 +42,7 @@ export function createEventHandler(deps: HookDeps, helpers: AutoRetryHelpers) {
}
const handleSessionDeleted = (props: Record<string, unknown> | undefined) => {
const sessionInfo = props?.info as { id?: string } | undefined
const sessionID = sessionInfo?.id
const sessionID = resolveSessionEventID(props)
if (sessionID) {
log(`[${HOOK_NAME}] Cleaning up session state`, { sessionID })
@@ -58,7 +58,7 @@ export function createEventHandler(deps: HookDeps, helpers: AutoRetryHelpers) {
}
const handleSessionStop = async (props: Record<string, unknown> | undefined) => {
const sessionID = props?.sessionID as string | undefined
const sessionID = resolveSessionEventID(props)
if (!sessionID) return
if (sessionRetryInFlight.has(sessionID) || sessionAwaitingFallbackResult.has(sessionID)) {
@@ -73,7 +73,7 @@ export function createEventHandler(deps: HookDeps, helpers: AutoRetryHelpers) {
const handleMessageUpdated = (props: Record<string, unknown> | undefined) => {
const info = props?.info as Record<string, unknown> | undefined
const sessionID = info?.sessionID as string | undefined
const sessionID = resolveMessageEventSessionID(props)
const role = info?.role as string | undefined
if (!sessionID || role !== "user") return
@@ -81,7 +81,7 @@ export function createEventHandler(deps: HookDeps, helpers: AutoRetryHelpers) {
}
const handleSessionIdle = (props: Record<string, unknown> | undefined) => {
const sessionID = props?.sessionID as string | undefined
const sessionID = resolveSessionEventID(props)
if (!sessionID) return
if (cancelledSessions.has(sessionID)) {
@@ -111,7 +111,7 @@ export function createEventHandler(deps: HookDeps, helpers: AutoRetryHelpers) {
}
const handleSessionError = async (props: Record<string, unknown> | undefined) => {
const sessionID = props?.sessionID as string | undefined
const sessionID = resolveSessionEventID(props)
const error = props?.error
const agent = props?.agent as string | undefined
@@ -8,6 +8,7 @@ import { getFallbackModelsForSession } from "./fallback-models"
import { resolveFallbackBootstrapModel } from "./fallback-bootstrap-model"
import { dispatchFallbackRetry } from "./fallback-retry-dispatcher"
import { hasVisibleAssistantResponse } from "./visible-assistant-response"
import { resolveMessageEventSessionID } from "../../shared/event-session-id"
export { hasVisibleAssistantResponse } from "./visible-assistant-response"
@@ -17,7 +18,7 @@ export function createMessageUpdateHandler(deps: HookDeps, helpers: AutoRetryHel
return async (props: Record<string, unknown> | undefined) => {
const info = props?.info as Record<string, unknown> | undefined
const sessionID = info?.sessionID as string | undefined
const sessionID = resolveMessageEventSessionID(props)
const timeoutEnabled = config.timeout_seconds > 0
const eventParts = props?.parts as Array<{ type?: string; text?: string }> | undefined
const infoParts = info?.parts as Array<{ type?: string; text?: string }> | undefined
@@ -8,6 +8,7 @@ import { getFallbackModelsForSession } from "./fallback-models"
import { normalizeRetryStatusMessage, extractRetryAttempt } from "../../shared/retry-status-utils"
import { resolveFallbackBootstrapModel } from "./fallback-bootstrap-model"
import { dispatchFallbackRetry } from "./fallback-retry-dispatcher"
import { resolveSessionEventID } from "../../shared/event-session-id"
export function createSessionStatusHandler(
deps: HookDeps,
@@ -22,7 +23,7 @@ export function createSessionStatusHandler(
} = deps
return async (props: Record<string, unknown> | undefined) => {
const sessionID = props?.sessionID as string | undefined
const sessionID = resolveSessionEventID(props)
const status = props?.status as { type?: string; message?: string; attempt?: number } | undefined
const agent = props?.agent as string | undefined
const model = props?.model as string | undefined