end a turn whose agent has gone
restarting officer-agent takes every persistent session with it and nothing downstream notices: the browser's socket is healthy, officer's subscription is a bus filter, and there is simply never another event. the spinner ran forever and a refresh didn't help, because the transcript has no ending to read. keyed off the agent *registering*, not disconnecting — a disconnect fires on every `pm2 restart officer`, when the turn is fine. a registration socket dies with its process, so an agent appearing on it is a new one. covers the sitting tab; the reconnect path covers the rest, with the client now sending its belief that a turn is in flight and officer checking it against the agent over a new claude:is-generating. the check fails toward alive. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -379,3 +379,15 @@ export function clearSession(sessionKey: string): void {
|
||||
export function getActiveSessionKeys(): string[] {
|
||||
return Array.from(sessions.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a turn actually in flight for this session, right now, in this process?
|
||||
*
|
||||
* This is the ground truth a reconnecting browser has no way to work out for itself. Officer's own view
|
||||
* dies with `pm2 restart officer` while the turn keeps running here, so "officer doesn't remember" means
|
||||
* nothing — and if *this* process was the one that restarted, the session is simply absent and the turn
|
||||
* it was running is gone, however alive the client still believes it to be.
|
||||
*/
|
||||
export function isSessionGenerating(sessionKey: string): boolean {
|
||||
return sessions.get(sessionKey)?.isGenerating ?? false;
|
||||
}
|
||||
|
||||
@@ -190,6 +190,10 @@ async function handleCommand(cmd: SidecarCommand, reply: ReplyFn) {
|
||||
reply({ type: 'claude:interrupted', id: cmd.id });
|
||||
break;
|
||||
|
||||
case 'claude:is-generating':
|
||||
reply({ type: 'claude:generating', id: cmd.id, generating: claudeManager.isSessionGenerating(cmd.sessionKey) });
|
||||
break;
|
||||
|
||||
case 'claude:clear-session':
|
||||
claudeManager.clearSession(cmd.sessionKey);
|
||||
sessionLog.drop(cmd.sessionKey);
|
||||
|
||||
@@ -17,6 +17,9 @@ export type SidecarCommand =
|
||||
| { type: 'claude:kill'; id: string; sessionKey: string }
|
||||
| { type: 'claude:interrupt'; id: string; sessionKey: string }
|
||||
| { type: 'claude:clear-session'; id: string; sessionKey: string }
|
||||
// Is a turn still running for this session? Only the process that owns the session can say, which is
|
||||
// exactly why it is asked over the wire — see `isClaudeGenerating` in sidecar-registry.
|
||||
| { type: 'claude:is-generating'; id: string; sessionKey: string }
|
||||
// OpenCode — drive a turn via `opencode run … --format json` (tools re-anchored to cwd via --dir)
|
||||
| { type: 'opencode:run-streaming'; id: string; params: OpenCodeRunParams }
|
||||
| { type: 'opencode:kill'; id: string; sessionKey: string }
|
||||
@@ -43,6 +46,7 @@ export type SidecarEvent =
|
||||
| { type: 'claude:killed'; id: string }
|
||||
| { type: 'claude:interrupted'; id: string }
|
||||
| { type: 'claude:session-cleared'; id: string }
|
||||
| { type: 'claude:generating'; id: string; generating: boolean }
|
||||
// VNC
|
||||
| { type: 'vnc:started'; id: string; port: number; display: number }
|
||||
| { type: 'vnc:password'; id: string; password: string }
|
||||
|
||||
Reference in New Issue
Block a user