# Sidecar Architecture — @cline/code ## Overview The sidecar is a single Bun process that handles the desktop backend runtime directly. It imports `@cline/core` directly and serves the Next.js frontend over HTTP + WebSocket. ## Directory Structure ``` sidecar/ ├── index.ts # Entry point: starts HTTP+WS server ├── server.ts # Bun HTTP server + WebSocket handlers ├── context.ts # SidecarContext type and factory ├── commands.ts # Command router ├── chat-session.ts # In-process chat session management ├── session-data/ # Shared discovery, messages, artifacts, search helpers ├── paths.ts # Path resolution ├── types.ts # Shared types └── ARCHITECTURE.md # This file ``` ## Transport Protocol (unchanged) ``` Request: { "type": "command", "id": string, "command": string, "args"?: object } Response: { "type": "response", "id": string, "ok": boolean, "result"?: unknown, "error"?: string } Event: { "type": "event", "event": { "name": string, "payload": unknown } } ``` ## Key Design Decisions ### 1. Chat Sessions — In-Process via LocalRuntimeHost Instead of spawning a separate runtime bridge process, we use `LocalRuntimeHost` directly: ```typescript import { LocalRuntimeHost } from "@cline/core"; const sessionManager = await ClineCore.create({ backendMode: "hub", capabilities: { requestToolApproval: async (request) => { // Push approval request to frontend via WebSocket event broadcastEvent("tool_approval_state", { sessionId: request.sessionId, items: [request] }); // Wait for frontend response return await waitForApprovalResponse(request.sessionId, request.toolCallId); }, }, }); // Start session const { sessionId } = await sessionManager.start({ config: coreSessionConfig, prompt: "...", }); // Send follow-up await sessionManager.send({ sessionId, prompt: "..." }); // Subscribe to streaming events sessionManager.subscribe((event) => { // Forward to WebSocket clients as chat_text, chat_reasoning, etc. broadcastEvent("chat_event", event); }); ``` ### 2. Tool Approval — In-Memory Promise Resolution No more file-system watchers. Tool approvals use in-memory promise maps: ```typescript const pendingApprovals = new Map void; request: ToolApprovalRequest; }>(); // When core requests approval → store promise, push to frontend // When frontend responds → resolve promise ``` ### 3. Provider Management — Direct ProviderSettingsManager ```typescript import { ProviderSettingsManager, listLocalProviders, ... } from "@cline/core"; const manager = new ProviderSettingsManager(); ``` ### 4. Session Storage — Direct SqliteSessionStore ```typescript import { SqliteSessionStore, resolveSessionBackend } from "@cline/core"; const store = new SqliteSessionStore(); ``` ### 5. Routine Schedules — Direct Hub Commands Routine operations now ensure the local hub server in-process and issue hub schedule commands directly. They are still called in-process, not via child script: ```typescript import { ensureHubServer, sendHubCommand } from "@cline/core"; await ensureHubServer({ runtimeHandlers: createLocalHubScheduleRuntimeHandlers() }); await sendHubCommand({}, { command: "schedule.list", payload: { limit: 200 } }); ``` ### 6. Native Commands - `pick_workspace_directory` — Uses macOS `osascript` / Linux `zenity` for directory picker - `open_mcp_settings_file` — Uses `open` / `xdg-open` to open files ### 7. Frontend Connection The frontend `desktop-client.ts` connects directly to the sidecar WebSocket: - Discovers endpoint from `window.__SIDECAR_WS_ENDPOINT__` or defaults to `ws://127.0.0.1:3126/transport` - No Tauri dependency needed - Same `invoke()` / `subscribe()` API ## Command Map Supported commands: | Command | Implementation | |---------|---------------| | `chat_session_command` | `LocalRuntimeHost` in-process | | `list_provider_catalog` | `ProviderSettingsManager` + `listLocalProviders` | | `list_provider_models` | `getLocalProviderModels` | | `save_provider_settings` | `saveLocalProviderSettings` | | `add_provider` | `addLocalProvider` | | `run_provider_oauth_login` | `loginLocalProvider` | | `list_chat_sessions` | `SqliteSessionStore` + file discovery | | `list_discovered_sessions` | Merged discovery | | `read_session_messages` | Session data readers | | `read_session_hooks` | Session data readers | | `delete_chat_session` | `SqliteSessionStore.delete` + file cleanup | | `update_chat_session_title` | `resolveSessionBackend().updateSession` | | `list_mcp_servers` | Direct file I/O | | `upsert_mcp_server` | Direct file I/O | | `delete_mcp_server` | Direct file I/O | | `get_git_branch` | `execFileSync("git", ...)` | | `list_git_branches` | `execFileSync("git", ...)` | | `checkout_git_branch` | `execFileSync("git", ...)` | | `search_workspace_files` | `getFileIndex` | | `get_process_context` | In-memory context | | `poll_tool_approvals` | In-memory pending map | | `respond_tool_approval` | In-memory promise resolution | | `list_routine_schedules` | local hub schedule commands | | `list_user_instruction_configs` | Direct core API | | `pick_workspace_directory` | OS native dialog | | `open_mcp_settings_file` | OS `open` command | ## Dev Workflow ```bash bun run dev:sidecar # Start sidecar on port 3126 bun run dev:web # Start Next.js on port 3125 bun run dev # Both concurrently ```