fix: settle busy sessions after managed OpenCode restart (#3002)
* fix: reconcile busy sessions after managed OpenCode restart Forced health-check restarts previously rebound the event stream without settling in-flight turns, so sessions stayed busy with no terminal state. Interrupt those sessions, classify health failures, and retain bounded process diagnostics for post-restart diagnosis. Fixes #2943 Co-authored-by: serkraser <serkraser@gmail.com> * fix: surface interrupted chats after OpenCode restart Complete unfinished assistant turns as aborted once the session is authoritatively idle, and show a persistent toast so users can continue instead of remaining silently stranded. Fixes #2943 Co-authored-by: serkraser <serkraser@gmail.com> * fix: redact Basic auth credentials in restart diagnostics The key/value sanitizer stopped at whitespace, so Authorization: Basic credentials survived in stderr tails and health snapshots. Redact the scheme token before that rule runs. Co-authored-by: serkraser <serkraser@gmail.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { createSessionRuntime } from './session-runtime.js';
|
||||
|
||||
describe('managed OpenCode restart session recovery', () => {
|
||||
it('settles busy sessions and broadcasts one interruption notification', () => {
|
||||
const events = [];
|
||||
const broadcastUiNotification = vi.fn();
|
||||
const rebindUpstream = vi.fn();
|
||||
const sessionRuntime = createSessionRuntime({
|
||||
writeSseEvent() {},
|
||||
getNotificationClients: () => new Set(),
|
||||
broadcastEvent: (event) => events.push(event),
|
||||
});
|
||||
const onOpenCodeRestarted = () => {
|
||||
rebindUpstream();
|
||||
const { sessionIds } = sessionRuntime.interruptBusySessionsAfterRestart();
|
||||
if (sessionIds.length > 0) {
|
||||
const multiple = sessionIds.length > 1;
|
||||
broadcastUiNotification({
|
||||
title: multiple ? 'Chats interrupted' : 'Chat interrupted',
|
||||
body: multiple
|
||||
? 'OpenCode restarted during running responses. Send a message in each chat to continue.'
|
||||
: 'OpenCode restarted during a running response. Send a message to continue.',
|
||||
tag: 'opencode-restart-interrupted',
|
||||
kind: 'opencode-restart-interrupted',
|
||||
sessionId: sessionIds[0],
|
||||
});
|
||||
}
|
||||
};
|
||||
const markBusy = (sessionID) => sessionRuntime.processOpenCodeSsePayload({
|
||||
type: 'session.status',
|
||||
properties: { sessionID, status: { type: 'busy' } },
|
||||
});
|
||||
|
||||
try {
|
||||
markBusy('session-1');
|
||||
markBusy('session-2');
|
||||
markBusy('session-3');
|
||||
events.length = 0;
|
||||
|
||||
onOpenCodeRestarted();
|
||||
|
||||
expect(rebindUpstream).toHaveBeenCalledOnce();
|
||||
expect(sessionRuntime.getActiveSessionCount()).toBe(0);
|
||||
expect(Object.values(sessionRuntime.getSessionStateSnapshot()).map((state) => state.status))
|
||||
.toEqual(['idle', 'idle', 'idle']);
|
||||
expect(events.filter((event) => event.type === 'openchamber:session-status')).toHaveLength(3);
|
||||
expect(events.filter((event) => event.type === 'session.error')).toHaveLength(3);
|
||||
expect(broadcastUiNotification).toHaveBeenCalledOnce();
|
||||
expect(broadcastUiNotification).toHaveBeenCalledWith(expect.objectContaining({
|
||||
kind: 'opencode-restart-interrupted',
|
||||
sessionId: 'session-1',
|
||||
}));
|
||||
} finally {
|
||||
sessionRuntime.dispose();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user