Files
opencode/packages/mobile-voice/src/lib/opencode-events.ts
Ryan Vogel 057208e022 fix: prevent spurious session-complete notifications from APN relay
Remove message.updated -> complete classification in the mobile foreground
SSE monitor. The processor cleanup stamps time.completed on every assistant
message after each LLM step, not just at session end, causing premature
complete events. The sole reliable signal is session.status with type idle.

Remove the redundant status.set(idle) from the processor halt() path. The
Runner onIdle callback already transitions to idle when the loop exits,
so the explicit set in halt was firing a duplicate complete notification
alongside the error notification.
2026-04-04 19:43:50 +00:00

66 lines
1.7 KiB
TypeScript

export type OpenCodeEvent = {
type: string
properties?: Record<string, unknown>
}
export type MonitorEventType = "complete" | "permission" | "error"
export function extractSessionID(event: OpenCodeEvent): string | null {
const props = event.properties ?? {}
const fromDirect = props.sessionID
if (typeof fromDirect === "string" && fromDirect.length > 0) return fromDirect
const info = props.info
if (info && typeof info === "object") {
const infoSessionID = (info as Record<string, unknown>).sessionID
if (typeof infoSessionID === "string" && infoSessionID.length > 0) return infoSessionID
}
const part = props.part
if (part && typeof part === "object") {
const partSessionID = (part as Record<string, unknown>).sessionID
if (typeof partSessionID === "string" && partSessionID.length > 0) return partSessionID
}
return null
}
export function classifyMonitorEvent(event: OpenCodeEvent): MonitorEventType | null {
const type = event.type
const lowerType = type.toLowerCase()
if (lowerType === "permission.asked" || lowerType === "permission") {
return "permission"
}
if (lowerType.includes("error")) {
return "error"
}
if (type === "session.status") {
const status = event.properties?.status
if (status && typeof status === "object") {
const statusType = (status as Record<string, unknown>).type
if (statusType === "idle") {
return "complete"
}
}
}
return null
}
export function formatMonitorEventLabel(eventType: MonitorEventType): string {
switch (eventType) {
case "complete":
return "Session complete"
case "permission":
return "Action needed"
case "error":
return "Session error"
default:
return "Session update"
}
}