ask the agent what it is still running
Officer's session records are in memory and die with `pm2 restart officer`, while the agent is a PM2 peer and keeps generating. `adoptOrphanedSession` rebuilds a binding — but only when a browser reconnects to a session *by id*, which you can only do if you already knew the id. So a session that survived a restart was invisible, and nothing could answer "what is running right now". `claude:list` returns each live session with `isGenerating` and `pendingTasks` — the same two fields the agent's own `armIdle` consults before collecting a session, so a caller can tell "busy" from "merely open" the way it does. Surfaced as `GET /chat/live`, which sits beside `/chat/sessions`: those are transcripts on disk, these are the ones with a process behind them. `getActiveSessionKeys` is replaced rather than joined. It returned bare keys, could not distinguish a session mid-turn from one merely open, and had never been called by anything. `listLiveClaudeSessions` fails toward EMPTY, where `isClaudeGenerating` beside it fails toward alive. The asymmetry is deliberate: not knowing there means leaving a spinner up, and not knowing here would mean inventing sessions. Step 2 of docs/chat-session-lifetime.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,19 @@ import type { MessageCost, PromptImage, TurnMessage } from '../api/chat/types';
|
||||
|
||||
export type SidecarMessage = SidecarCommand | SidecarEvent;
|
||||
|
||||
/**
|
||||
* A session the agent is holding in memory right now — the ground truth about what is alive.
|
||||
*
|
||||
* `pendingTasks` is background work started but not yet notified, and it is the field worth having:
|
||||
* together with `isGenerating` it is exactly what the agent's own idle GC consults before deciding a
|
||||
* session may be collected, so a caller can tell "busy" from "merely open" the same way it does.
|
||||
*/
|
||||
export type LiveClaudeSession = {
|
||||
sessionKey: string;
|
||||
isGenerating: boolean;
|
||||
pendingTasks: number;
|
||||
};
|
||||
|
||||
// ── Commands (API server → sidecar) ──
|
||||
|
||||
export type SidecarCommand =
|
||||
@@ -23,6 +36,11 @@ export type SidecarCommand =
|
||||
// Which session key owns this transcript? The map lives on the agent's disk, so only it can answer —
|
||||
// see `findClaudeSessionKey` in sidecar-registry, and `attach` in the chat socket for why it is asked.
|
||||
| { type: 'claude:find-session'; id: string; claudeSessionId: string }
|
||||
// Everything the agent is holding right now. `claude:is-generating` answers for a session you can
|
||||
// already name; this is for the case where officer has forgotten every name it had — its session
|
||||
// records are in memory and die with `pm2 restart officer`, while the agent keeps running. Without it
|
||||
// a live session is invisible until a browser happens to reconnect to it by id.
|
||||
| { type: 'claude:list'; id: 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 }
|
||||
@@ -51,6 +69,7 @@ export type SidecarEvent =
|
||||
| { type: 'claude:session-cleared'; id: string }
|
||||
| { type: 'claude:generating'; id: string; generating: boolean }
|
||||
| { type: 'claude:session-key'; id: string; sessionKey: string | null }
|
||||
| { type: 'claude:sessions'; id: string; sessions: LiveClaudeSession[] }
|
||||
// VNC
|
||||
| { type: 'vnc:started'; id: string; port: number; display: number }
|
||||
| { type: 'vnc:password'; id: string; password: string }
|
||||
|
||||
Reference in New Issue
Block a user