show running opencode turns in the live panel

The Live panel asked `claude:list` and nothing else, so a running OpenCode turn was invisible — the panel
claimed to show what the agent is doing and silently omitted half of it.

Adds `opencode:list` / `opencode:sessions` and merges both harnesses in `/chat/live`, asked in parallel,
each failing toward empty so one sidecar being down contributes nothing rather than breaking the panel.

The OpenCode row is deliberately thinner than the Claude one rather than faked into parity:

  isGenerating  always true — a subprocess exists only while it generates, so there is no "merely open"
  pendingTasks  always 0    — `opencode run` has no background-task concept; reporting a number would
                              suggest a capability that does not exist
  title / cwd   null        — the session store is keyed on the `ses_…` id the runner reports, not on
                              our sessionKey, so an unreported turn shows unnamed rather than guessed

This is the incremental option from docs/opencode-serve-path.md — enumeration without moving turns onto
the serve, so it buys the Live panel with no warm sessions, no SSE loop and no lifetime questions.

NOT verified end to end: no OpenCode turn was running to enumerate, so the verb is wired and typechecked
but has never returned a non-empty list. See COMMS/BLOCKERS.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 03:00:43 +00:00
co-authored by Claude Opus 5
parent 8b409e8af8
commit e8bd946272
5 changed files with 76 additions and 4 deletions
+19
View File
@@ -7,6 +7,7 @@ import type {
ClaudeSpawnStreamingParams,
ClaudeCodeResult,
LiveClaudeSession,
LiveOpenCodeSession,
OpenCodeRunParams,
VncStartParams,
} from './sidecar/protocol';
@@ -333,6 +334,24 @@ export async function listLiveClaudeSessions(): Promise<LiveClaudeSession[]> {
}
}
/**
* The OpenCode turns running right now, from the sidecar's own map.
*
* Fails toward EMPTY, matching `listLiveClaudeSessions` and for the same reason: an enumeration that
* invents sessions is worse than a short one. A sidecar that is down or does not understand the verb
* (an older build) simply contributes nothing to the Live panel rather than breaking it.
*/
export async function listLiveOpenCodeSessions(): Promise<LiveOpenCodeSession[]> {
const sc = findSidecarByCapability('opencode');
if (!sc) return [];
try {
const res = await sendCommandToSidecar(sc, { type: 'opencode:list', id: nextId() });
return res.type === 'opencode:sessions' ? res.sessions : [];
} catch {
return [];
}
}
export async function isClaudeGenerating(sessionKey: string): Promise<boolean> {
const sc = findSidecarByCapability('claude');
if (!sc) return false;