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:
@@ -6,6 +6,7 @@ import type {
|
||||
ClaudeSpawnParams,
|
||||
ClaudeSpawnStreamingParams,
|
||||
ClaudeCodeResult,
|
||||
LiveClaudeSession,
|
||||
OpenCodeRunParams,
|
||||
VncStartParams,
|
||||
} from './sidecar/protocol';
|
||||
@@ -307,6 +308,31 @@ export function interruptClaude(sessionKey: string): void {
|
||||
* it is still typing is worse than leaving a spinner up a little longer. Only a registered agent that
|
||||
* says "no", or no agent at all, counts as dead.
|
||||
*/
|
||||
/**
|
||||
* Every session the agent is holding IN MEMORY right now — distinct from `listClaudeSessions` in
|
||||
* `api/chat/claude-sessions`, which lists conversations from transcripts on disk. Those are the history;
|
||||
* these are the ones with a process behind them.
|
||||
*
|
||||
* Officer's own session records live in memory and die with `pm2 restart officer`, while the agent — a
|
||||
* PM2 peer, not a child — keeps running and keeps committing to chat_session_events. Until this existed
|
||||
* a surviving session was invisible: `adoptOrphanedSession` only fires when a browser reconnects to one
|
||||
* *by id*, so nothing could answer "what is still running".
|
||||
*
|
||||
* Fails *toward empty*, unlike `isClaudeGenerating` which fails toward alive. The asymmetry is
|
||||
* deliberate: there, not knowing means leaving a spinner up; here, not knowing would mean inventing
|
||||
* sessions, and an enumeration that reports things that may not exist is worse than a short one.
|
||||
*/
|
||||
export async function listLiveClaudeSessions(): Promise<LiveClaudeSession[]> {
|
||||
const sc = findSidecarByCapability('claude');
|
||||
if (!sc) return [];
|
||||
try {
|
||||
const res = await sendCommandToSidecar(sc, { type: 'claude:list', id: nextId() });
|
||||
return res.type === 'claude:sessions' ? res.sessions : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function isClaudeGenerating(sessionKey: string): Promise<boolean> {
|
||||
const sc = findSidecarByCapability('claude');
|
||||
if (!sc) return false;
|
||||
|
||||
Reference in New Issue
Block a user