Back to Cline

Sidecar Architecture — @cline/code

sdk/apps/examples/desktop-app/sidecar/ARCHITECTURE.md

3.83.05.4 KB
Original Source

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<string, {
  resolve: (result: ToolApprovalResult) => 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:

CommandImplementation
chat_session_commandLocalRuntimeHost in-process
list_provider_catalogProviderSettingsManager + listLocalProviders
list_provider_modelsgetLocalProviderModels
save_provider_settingssaveLocalProviderSettings
add_provideraddLocalProvider
run_provider_oauth_loginloginLocalProvider
list_chat_sessionsSqliteSessionStore + file discovery
list_discovered_sessionsMerged discovery
read_session_messagesSession data readers
read_session_hooksSession data readers
delete_chat_sessionSqliteSessionStore.delete + file cleanup
update_chat_session_titleresolveSessionBackend().updateSession
list_mcp_serversDirect file I/O
upsert_mcp_serverDirect file I/O
delete_mcp_serverDirect file I/O
get_git_branchexecFileSync("git", ...)
list_git_branchesexecFileSync("git", ...)
checkout_git_branchexecFileSync("git", ...)
search_workspace_filesgetFileIndex
get_process_contextIn-memory context
poll_tool_approvalsIn-memory pending map
respond_tool_approvalIn-memory promise resolution
list_routine_scheduleslocal hub schedule commands
list_user_instruction_configsDirect core API
pick_workspace_directoryOS native dialog
open_mcp_settings_fileOS 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