process sidecar: independent process manager for long-running work
Introduces a separate Bun process (port 5100) that owns all spawned processes and long-running work, so the API server can restart freely without disrupting active sessions. The sidecar owns: - Anthropic proxy (port 5051) with persisted secret across restarts - Claude Code process spawning and session tracking (--resume support) - Pi agent spawning and RPC lifecycle (prompt/abort/thinking) - Job queue engine (lane processing, retries, notifications) The API server becomes a thin client that forwards commands over a single WebSocket connection with auto-reconnect. send-claude-code.ts goes from 550 lines of spawn logic to 73 lines of sidecar delegation. State persisted to data/sidecar/state.json every 30s and on shutdown. Lockfile prevents duplicate instances. See SIDECAR.md for full docs and manual testing procedures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import type { MessageCost, PiEvent } from '../api/pi/types';
|
||||
import type { Job, EnqueueParams, JobProgress } from '../queue/types';
|
||||
|
||||
// ── Envelope ──
|
||||
|
||||
export type SidecarMessage = SidecarCommand | SidecarEvent;
|
||||
|
||||
// ── Commands (API server → sidecar) ──
|
||||
|
||||
export type SidecarCommand =
|
||||
| { type: 'ping'; id: string }
|
||||
| { type: 'state:sync'; id: string }
|
||||
// Proxy
|
||||
| { type: 'proxy:secret'; id: string }
|
||||
// Claude Code
|
||||
| { type: 'claude:spawn'; id: string; params: ClaudeSpawnParams }
|
||||
| { type: 'claude:spawn-streaming'; id: string; params: ClaudeSpawnStreamingParams }
|
||||
| { type: 'claude:kill'; id: string; sessionKey: string }
|
||||
| { type: 'claude:clear-session'; id: string; sessionKey: string }
|
||||
// Pi
|
||||
| { type: 'pi:spawn'; id: string; params: PiSpawnParams }
|
||||
| { type: 'pi:prompt'; id: string; sessionId: string; prompt: string; requestId: string }
|
||||
| { type: 'pi:abort'; id: string; sessionId: string; requestId: string }
|
||||
| { type: 'pi:kill'; id: string; sessionId: string }
|
||||
| { type: 'pi:set-thinking'; id: string; sessionId: string; level: string }
|
||||
// Queue
|
||||
| { type: 'queue:enqueue'; id: string; params: EnqueueParams }
|
||||
| { type: 'queue:cancel'; id: string; jobId: string }
|
||||
| { type: 'queue:list'; id: string }
|
||||
| { type: 'queue:get'; id: string; jobId: string };
|
||||
|
||||
// ── Responses/Events (sidecar → API server) ──
|
||||
|
||||
export type SidecarEvent =
|
||||
| { type: 'pong'; id: string }
|
||||
| { type: 'state:sync'; id: string; state: SidecarState }
|
||||
| { type: 'proxy:secret'; id: string; secret: string }
|
||||
// Claude Code
|
||||
| { type: 'claude:spawned'; id: string; sessionKey: string }
|
||||
| { type: 'claude:event'; sessionKey: string; event: PiEvent }
|
||||
| { type: 'claude:result'; id: string; result: ClaudeCodeResult }
|
||||
| { type: 'claude:error'; id: string; error: string }
|
||||
| { type: 'claude:killed'; id: string }
|
||||
| { type: 'claude:session-cleared'; id: string }
|
||||
// Pi
|
||||
| { type: 'pi:spawned'; id: string; sessionId: string }
|
||||
| { type: 'pi:event'; sessionId: string; event: PiEvent }
|
||||
| { type: 'pi:error'; id: string; error: string }
|
||||
| { type: 'pi:killed'; id: string }
|
||||
// Queue
|
||||
| { type: 'queue:enqueued'; id: string; job: Job }
|
||||
| { type: 'queue:cancelled'; id: string; job: Job | null }
|
||||
| { type: 'queue:list'; id: string; jobs: Job[] }
|
||||
| { type: 'queue:get'; id: string; job: Job | null }
|
||||
| { type: 'queue:error'; id: string; error: string }
|
||||
// Generic
|
||||
| { type: 'error'; id?: string; error: string };
|
||||
|
||||
// ── Shared state snapshot ──
|
||||
|
||||
export type SidecarState = {
|
||||
proxySecret: string;
|
||||
claudeSessions: Record<string, string>; // sessionKey → Claude Code session_id
|
||||
piSessions: PiSessionInfo[];
|
||||
uptime: number;
|
||||
};
|
||||
|
||||
export type PiSessionInfo = {
|
||||
sessionId: string;
|
||||
email: string;
|
||||
userId: number;
|
||||
model: string;
|
||||
cwd: string;
|
||||
pid: number;
|
||||
alive: boolean;
|
||||
};
|
||||
|
||||
// ── Param types ──
|
||||
|
||||
export type ClaudeSpawnParams = {
|
||||
userId: number;
|
||||
email: string;
|
||||
username: string;
|
||||
prompt: string;
|
||||
sessionKey: string;
|
||||
model?: string;
|
||||
};
|
||||
|
||||
export type ClaudeSpawnStreamingParams = {
|
||||
userId: number;
|
||||
email: string;
|
||||
username: string;
|
||||
prompt: string;
|
||||
sessionKey: string;
|
||||
cwd?: string;
|
||||
model?: string;
|
||||
};
|
||||
|
||||
export type ClaudeCodeResult = {
|
||||
text: string;
|
||||
sessionId: string;
|
||||
model: string;
|
||||
cost: MessageCost;
|
||||
};
|
||||
|
||||
export type PiSpawnParams = {
|
||||
sessionId: string;
|
||||
email: string;
|
||||
userId: number;
|
||||
username: string;
|
||||
role: string;
|
||||
cwd: string;
|
||||
model: string;
|
||||
sessionFile?: string;
|
||||
};
|
||||
Reference in New Issue
Block a user