route OpenCode chat through the officer-opencode sidecar
Turns now run in the sidecar via `opencode run --dir <cwd> --format json
--dangerously-skip-permissions [-s <ses_>]` instead of the serve's
`POST /session/{id}/message` path. That path was unreliable at reporting
tool completion — tools finished but the turn stayed status=running,
wedging the UI at "Working…". `run` re-anchors tools to the chat cwd via
--dir, reports completion faithfully, and exits when done.
- runner.ts (new): spawn `run`, map its JSON events (text/tool_use/
step_finish) to ChatEvent, report the `ses_` id for resume, accumulate
cost; inactivity (120s) + hard-cap (10min) watchdogs kill a hung turn
and emit a clean error instead of hanging forever.
- protocol.ts: opencode:run-streaming/kill commands; opencode:spawned/
event/session events; OpenCodeRunParams.
- sidecar index.ts: wire run/kill; sweepStaleServes() on startup kills
only an `opencode serve` whose resolved /proc/<pid>/cwd == SERVE_CWD,
so an unclean prior exit can't leave two.
- sidecar-registry.ts: spawnOpenCodeStreaming/killOpenCode/onOpenCodeEvent/
onOpenCodeSession helpers.
- send-opencode.ts: rewritten to mirror send-claude-code (subscribe →
resolve resume id → spawn → kill handle).
- sidecar-server.ts: persist reported ses_ id into state for resume.
- list-models/server-manager: route to the sidecar's reported serve URL.
The serve stays up only for read-only calls that never hung (model
listing, session history).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import type {
|
||||
ClaudeSpawnParams,
|
||||
ClaudeSpawnStreamingParams,
|
||||
ClaudeCodeResult,
|
||||
OpenCodeRunParams,
|
||||
PtyCommand,
|
||||
PtyEvent,
|
||||
VncStartParams,
|
||||
@@ -350,6 +351,41 @@ export function onClaudeEvent(handler: (sessionKey: string, event: ChatEvent) =>
|
||||
});
|
||||
}
|
||||
|
||||
// ── OpenCode Code (single sidecar, capability 'opencode') ──
|
||||
|
||||
export async function spawnOpenCodeStreaming(params: OpenCodeRunParams): Promise<void> {
|
||||
const res = await sendCommand('opencode', { type: 'opencode:run-streaming', id: nextId(), params });
|
||||
if (res.type === 'opencode:spawned') return;
|
||||
if (res.type === 'opencode:error') throw new Error(res.error);
|
||||
throw new Error('Unexpected response');
|
||||
}
|
||||
|
||||
export function killOpenCode(sessionKey: string): void {
|
||||
sendFire('opencode', { type: 'opencode:kill', id: nextId(), sessionKey });
|
||||
}
|
||||
|
||||
export function onOpenCodeEvent(handler: (sessionKey: string, event: ChatEvent) => void): () => void {
|
||||
return on('opencode:event', (msg) => {
|
||||
if (msg.type === 'opencode:event') {
|
||||
handler(
|
||||
(msg as SidecarEvent & { type: 'opencode:event' }).sessionKey,
|
||||
(msg as SidecarEvent & { type: 'opencode:event' }).event,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function onOpenCodeSession(handler: (sessionKey: string, sessionId: string) => void): () => void {
|
||||
return on('opencode:session', (msg) => {
|
||||
if (msg.type === 'opencode:session') {
|
||||
handler(
|
||||
(msg as SidecarEvent & { type: 'opencode:session' }).sessionKey,
|
||||
(msg as SidecarEvent & { type: 'opencode:session' }).sessionId,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Terminal (PTY sidecar) ──
|
||||
|
||||
export function sendPtyCommand(cmd: PtyCommand): void {
|
||||
|
||||
Reference in New Issue
Block a user