diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index 98409b1a9..3db946da5 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -533,6 +533,18 @@ export namespace SessionProcessor { sessionID: input.assistantMessage.sessionID, error: input.assistantMessage.error, }) + // altimate_change start — telemetry for unhandled streaming errors (non-retry, non-overflow) + // Covers: MessageAbortedError (Stop/dispose), UnknownError (SSE chunk timeout), + // APIError (provider failures after retry exhaustion), AuthError, and any other streaming error. + Telemetry.track({ + type: "error", + timestamp: Date.now(), + session_id: input.assistantMessage.sessionID, + error_name: error.name, + error_message: (error.data as any)?.message ?? String((e as any)?.message ?? ""), + context: "streaming", + }) + // altimate_change end // altimate_change start — SessionStatus.set became async; await so idle state flushes before exit await SessionStatus.set(input.sessionID, { type: "idle" }) // altimate_change end diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index ae8934e44..2db0d14c7 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -303,13 +303,15 @@ export namespace SessionPrompt { const s = state() const match = s[sessionID] if (!match) { + // Session already ended or was never started — set idle directly since no processor will do it await SessionStatus.set(sessionID, { type: "idle" }) return } match.abort.abort() delete s[sessionID] - await SessionStatus.set(sessionID, { type: "idle" }) - return + // Do NOT set idle status here — on abort the processor's catch block + // publishes session.error THEN sets idle, preserving correct event ordering. + // On normal completion, loop() sets idle after the while loop exits (see below). } // altimate_change end @@ -1110,6 +1112,11 @@ export namespace SessionPrompt { } continue } + // altimate_change start — set idle on normal loop exit; abort path is handled by processor catch block + if (!abort.aborted) { + await SessionStatus.set(sessionID, { type: "idle" }) + } + // altimate_change end SessionCompaction.prune({ sessionID }) // altimate_change start — session end telemetry const outcome = abort.aborted