Skip to main content

Elements

Cocoon

The Node.js extension host sidecar for Land. Runs VS Code extensions unmodified in a supervised Node.js process, constructs a per-extension vscode API shim via Effect-TS layers, and communicates with Mountain over the Vine gRPC protocol for native operations.

Cocoon is the Node.js extension host sidecar for Land. We run VS Code extensions in a supervised Node.js process and provide a vscode API shim via Effect-TS. That shim translates extension API calls into declarative Effects. Effects are either handled in-process or dispatched to Mountain via gRPC for native execution.

Overview 📋

Cocoon is a TypeScript application built with Effect-TS.

  • We replicate the VS Code Extension Host API.
  • We communicate with Mountain via gRPC (Vine protocol) on port 50052.
  • We are spawned and supervised by Mountain’s ProcessManagement module.
AttributeValue
LanguageTypeScript (Effect-TS v3.21)
RuntimeNode.js (managed by SideCar)
IPCgRPC (Vine protocol)
Dependencieseffect, @effect/platform, @effect/platform-node, @grpc/grpc-js, @codeeditorland/output, google-protobuf
Managed byMountain ProcessManagement/CocoonManagement.rs

Architecture 🏗️

Module Map 🗺️

PathPurpose
Source/Bootstrap/Implementation/CocoonMain.tsEntry point; initialization prelude
Source/PatchProcess/Process hardening, signal handling, log piping
Source/Core/ExtensionHost.tsExtension activation and lifecycle
Source/Core/RequireInterceptor.tsRequire() patching for VS Code module loading
Source/Core/ApiFactory.tsConstructs vscode.* API objects per extension
Source/Services/Commands.tsCommand registration and execution
Source/Services/Window.tsWindow and editor management
Source/Services/Workspace.tsWorkspace and file system operations
Source/Services/Configuration.tsConfiguration read/write
Source/Services/gRPC/Client.tsgRPC client for Mountain communication
Source/ModuleInterceptor/ESM and CommonJS module interception
Source/TypeConverter/Type conversion between extensions and gRPC
Source/Telemetry/PostHog + OTLP telemetry
Source/IPC/Internal message channel system
Source/WebviewPanel/Webview panel lifecycle management
Source/Generated/Proto-generated TypeScript types

Workbench Architecture Role 🏗️

In the broader Land workbench, Cocoon replaces the VS Code Electron main process’s extension host responsibilities. The architecture comparison across the three layers:

AspectVS Code (Electron)Land Equivalent
Main processElectron MainMountain (Rust)
Renderer processElectron RendererWind (Tauri WebView)
IPC mechanismElectron ipcRenderer/ipcMainTauri invoke/event + gRPC (Vine)
Preloadelectron preload.jsWind Preload.ts
Extension hostChild Node processCocoon (Node sidecar via gRPC)
File systemNode.js fs moduleMountain native Rust fs (Track B)
Native dialogsElectron dialog APIMountain via Tauri dialog plugin
Window managementElectron BrowserWindowTauri Window

Service Composition Patterns 🧩

Cocoon’s internal services follow the same Effect-TS Layer composition patterns used throughout Wind and Mountain.

Layer Stack Architecture

Each service in Cocoon is composed through Effect-TS Layers. The broader Wind layer stack defines three workbench variants that determine which services are wired to Cocoon versus resolved locally:

graph TB
    Sky[Sky entry point<br/>index.astro] --> Preload[Wind Preload Install.ts]
    Preload --> Layers[Effect/Layers/index.ts]
    Layers --> Tauri[Tauri/Tauri.ts<br/>TauriLiveLayer]
    Layers --> Electron[Electron/Electron.ts<br/>ElectronLiveLayer]
    Layers --> Test[Test/Test.ts<br/>TestLayer]
    Tauri --> Config[ConfigurationLive]
    Tauri --> Sandbox[SandboxLive]
    Tauri --> IPC[IPCLive]
    Tauri --> Mountain[MountainLive + MountainSyncLive]
    Tauri --> Editor[EditorLive]
    Tauri --> File[FilesLive]
    Tauri --> Terminal[TerminalLive]
    Tauri --> Clipboard[ClipboardLive]
    Tauri --> Dialog[DialogLive]
    Tauri --> Window[ActivityBarLive, PanelLive, SidebarLive, StatusBarLive]
    Tauri --> Services[... 37 service layers via Layer.mergeAll]

Layer Composition

Individual services use Layer.succeed to wrap a concrete implementation, matching the pattern used in Cocoon’s own internal providers:

// Individual service pattern (shared across Wind and Cocoon):
export const LiveEditorServiceLayer = Layer.succeed(
	EditorTag,
	makeEditorService(),
);

// Tauri layer - Layer.mergeAll: flat composition
export const TauriLiveLayer = Layer.mergeAll(
	SandboxLive,
	ConfigurationWithSyncLive,
	EditorLive,
	FilesLive,
	TerminalLive,
	// ... all ~37 service layers
);

Effect-TS’s compile-time dependency tracking ensures that no service can be used without its dependencies being satisfied by the Layer stack. A missing dependency produces a TypeScript type error.

ManagedRuntime

Cocoon and Wind both rely on Effect-TS’s ManagedRuntime to bridge the Effect system with imperative call sites (such as VS Code extension API entry points):

// From Wind/Source/Effect/LandWorkbench/LandWorkbenchRuntime.ts

// Initialized eagerly via IIFE at module load time, stored on
// globalThis.__CEL_WIND_RUNTIME__ so multiple Sky chunks that import
// this module share a single runtime instance.
export const LandWorkbenchRuntime: ManagedRuntime<...> = ...;

// Service lookups are sub-5ms after initialization.
// LandWorkbenchRuntime.Dispose() tears down the runtime and clears
// the global slot (used on window unload).

Cocoon mirrors this pattern to ensure that extension API calls (e.g., vscode.commands.registerCommand) resolve through the Effect system without runtime overhead.

Workbench Variants 🚀

Cocoon participates in several workbench variants that select the extension host and IPC backend at build time:

VariantCocoon RoleBuild Profile (shorthand)
BrowserNo Cocoon (70-80% API coverage via stock Node)debug
MountainFull Cocoon via gRPC (80-90%)debug-mountain
ElectronFull Cocoon, Electron IPC backend (95%+)debug-electron
Cocoon HeadlessCocoon subprocess only, no Wind/Workbench UIdebug-cocoon-headless
Mountain OnlyNo Cocoon (core services, no extension host)debug-mountain-only
KernelNo Cocoon, no Wind, no built-insdebug-kernel

Variant Selection

Sky’s index.astro entry point selects the active variant at build time via environment variables:

const Bundle = process.env["Bundle"] === "true";
const Mountain = process.env["Mountain"] === "true";
const Electron = process.env["Electron"] === "true";
const BrowserProxy = process.env["BrowserProxy"] === "true";

const WorkbenchType =
	Electron || Mountain
		? "Electron"
		: BrowserProxy
			? "BrowserProxy"
			: "Browser";

When Mountain or Electron is selected, Cocoon is spawned as the extension host process. In Browser mode, Cocoon is omitted and the workbench runs with a simulated extension host. Unused variants are tree-shaken by Vite.

Command Dispatch Architecture 🎮

Cocoon is the extension command execution layer in Land’s three-layer command system:

Command Registration:
    Wind (UI commands)      -- editor actions, palette commands
    Mountain (native)       -- Tauri command handlers, file ops, window mgmt
    Cocoon (extensions)     -- vscode.commands.registerCommand from extensions

Command Execution Flow

Commands flow through all three layers depending on origin:

sequenceDiagram
    participant User as User
    participant Wind as Wind CommandService
    participant Tauri as Tauri invoke
    participant Mountain as Mountain CommandHandler
    participant Cocoon as Cocoon CommandRouter

    User->>Wind: Trigger command (palette, keybinding, programmatic)
    Wind->>Wind: Is it a UI command?
    alt UI command
        Wind->>Wind: Execute directly in Effect-TS
    else Native or extension command
        Wind->>Tauri: invoke('commands:execute')
        Tauri->>Mountain: Route to CommandHandler
        Mountain->>Mountain: Is it a native command?
        alt Native command
            Mountain->>Mountain: Execute Rust handler
        else Extension command
            Mountain->>Cocoon: gRPC command request
            Cocoon->>Cocoon: Execute extension command
            Cocoon-->>Mountain: gRPC response
        end
        Mountain-->>Tauri: Result
        Tauri-->>Wind: Result
    end
    Wind-->>User: Command result

Command Categories

CategoryRegistered InExamples
Native editorWindcursorMove, type, replacePreviousChar
Window/UIMountainworkbench.action.toggleSidebar, workbench.action.terminal.toggleTerminal
File operationsMountainworkbench.action.files.save, workbench.action.files.openFile
ExtensionCocooneditor.action.formatDocument, git.commit

TierIPC Routing 🌐

Cocoon is reachable from the Wind layer through TierIPC, a routing system that decides which IPC backend handles each channel. The TierIPC env var (loaded from .env.Land via turbo.json globalEnv) selects the routing mode at runtime, with per-subsystem overrides.

Routing Modes

ValueBehaviour
MountainDefault. All channel.call() invocations route to Mountain via Tauri MountainIPCInvoke.
NodeDeferredMountain first; if Mountain returns undefined or has no handler, falls through to Cocoon gRPC.
NodeAll calls bypass Mountain and go directly to Cocoon via the cocoon:request gRPC bridge.

Per-Subsystem Tier Variables

Cocoon handles extension-facing channels that default to Mountain but can be routed directly:

VariableDefaultChannels governedCocoon Interaction
TierTerminalMountainterminal, localPtyCocoon’s terminal forwarder (if enabled)
TierSCMMountaingit (localGit)Extension Git integration calls
TierDebugMountainextensionHostStarter, extensionhostdebugserviceDebugger extension API calls
TierLanguageFeaturesMountainlanguage, languagesLanguage feature providers
TierSearchMountainsearchSearch provider via extension
TierTasksNodetasksTask execution (typically Cocoon-routed)
TierAuthNodeauthAuthentication providers
TierStorageMountainstorageExtension storage APIs
TierModelMountainmodel, textFile, fileDocument model operations
TierEncryptionMountainencryptionEncryption provider calls
TierWebSocketDisabledMist WebSocket transport (Phase 6, not yet active)Future WebSocket extension transport

Editor Service Architecture ✏️

The editor service in Wind integrates the VS Code CodeEditor widget (based on Monaco) with Mountain’s native capabilities and Cocoon’s extension providers.

Text Model Flow

User opens file
    |
    v
Wind EditorService
    |
    +---> TextModelService resolves URI
    +---> IFileService.readFile() via Tauri -> Mountain
    +---> Content returned as Uint8Array
    +---> TextModel created with content
    +---> Language mode detected from file extension
    +---> Editor widget instantiated with TextModel
    |
    v
Editor renders in Sky UI

Extension Provider Integration

Language features (hover, completion, definition) requested through Cocoon’s extension host flow back to the Wind editor service:

Extension registers HoverProvider (vscode.languages.registerHoverProvider)
    |
    v
Cocoon LanguagesProvider stores provider reference
    |
    v
User hovers over symbol in editor
    |
    v
Wind EditorService detects hover intent
    |
    v
Wind -> Tauri -> Mountain -> Cocoon (via gRPC: languages:provideHover)
    |
    v
Cocoon resolves HoverProvider.ProvideHover() in extension context
    |
    v
Result flows back: Cocoon -> Mountain -> Tauri -> Wind -> Monaco editor
    |
    v
Hover tooltip rendered in Sky UI

VS Code API Coverage Strategy 🔬

Cocoon is the primary vehicle for VS Code API coverage in Land. The strategy uses a dual-track routing system.

Track A: Stock Node (Maximum Compatibility)

Cocoon loads unmodified VS Code extHost*.ts source files from @codeeditorland/output. The ExtHostContext/MainContext RPC glue that normally runs in Electron’s main process is shimmed by Cocoon to work over gRPC.

Coverage: All APIs that the stock implementation handles in-process are immediately compatible.

Track B: Rust Native (Performance)

For I/O-heavy APIs, the Cocoon vscode shim routes operations through gRPC to Mountain for native Rust execution. This provides:

  • Faster filesystem operations (native syscalls vs Node.js fs)
  • Direct OS integration (clipboard, dialogs, keychain)
  • Native terminal PTY (portable-pty crate vs node-pty)
  • Zero-copy buffer handling

Dual-Track Routing

TrackImplementationLatencyWhen Used
A - Stock NodeUnmodified VS Code extHost*.tsIn-processDefault for all APIs
B - Rust NativegRPC ActionEffect to Mountain~1msI/O-heavy APIs (fs, terminal, search, git)

Track Distribution by API

APIDefault TrackRationale
commands.registerCommandAIn-process bookkeeping
commands.executeCommandAIn-process dispatch
window.showInformationMessageAIn-process dialog
workspace.openTextDocumentAContent in memory
workspace.fs.readFileBNative file I/O (faster)
workspace.findFilesBNative search (ripgrep)
window.createWebviewPanelBMountain owns webview lifecycle
env.clipboardBNative clipboard access

Coverage Matrix

The authoritative coverage matrix is at Documentation/GitHub/VSCode-API-Coverage-Matrix.md. Status symbols:

StatusMeaning
WorkingActivation + feature render path confirmed end-to-end
PartialRPC wired but UI render gap, or missing sub-method
StubbedRegistration accepted but no effect
Not attemptedNo implementation started
LiftedPure-function stateless implementation already landed

Startup Sequence 🚀

1. Node.js process starts (bootstrap-fork.js)
2. PatchProcess/index.ts pipes logs, handles SIGTERM/SIGINT,
   monitors parent process
3. IpcProvider starts gRPC client, connects to Mountain on
   port 50052, sends $initialHandshake, waits for init request
4. globalThis.__LandTiers populated from esbuild identifiers,
   environment variables, or hard-coded defaults
5. RequireInterceptor patches require() for VS Code bundle
   loading, maps electron-less requires to Tauri equivalents
6. Mountain sends Initialize gRPC request with InitData
   (workspace, manifests, configuration snapshot)
7. InitDataLayer created from payload
8. FullAppInitialization Effect resolves ExtensionHostProvider
   and activates startup extensions

VS Code API Shim 📦

We construct VS Code API objects for each extension via ApiFactory.ts.

API Namespace Providers 📋

NamespaceProviderTrack
vscode.commandsCommandsProviderA + S (Stock + Sky-direct)
vscode.windowWindowProviderA (Stock Node)
vscode.workspaceWorkspaceProviderA + B (Stock + Rust-native)
vscode.languagesLanguagesProviderA (Stock Node)
vscode.envEnvironmentProviderA (Stock Node)
vscode.extensionsExtensionsProviderA (Stock Node)
vscode.workspace.fsFileSystemProviderB (Rust-native via gRPC)
vscode.tasksTasksProviderA (Stock Node)
vscode.debugDebugProviderA + B
vscode.testsTestsProviderA (Stock Node)
vscode.Notebook*NotebookProviderA (Stock Node)
vscode.WebviewPanelWebviewProviderB (Mountain-backed)

Service Providers 🔌

Each service is an Effect-TS Layer:

ServiceModuleKey Methods
CommandsProviderServices/Commands.tsregisterCommand, executeCommand, getCommands
WindowProviderServices/Window.tscreateWebviewPanel, showTextDocument, activeTextEditor, showInformationMessage
WorkspaceProviderServices/Workspace.tsworkspaceFolders, openTextDocument, findFiles, applyEdit, getConfiguration
LanguagesProviderServices/Language/registerHoverProvider, registerCompletionProvider, registerDefinitionProvider
ConfigurationProviderServices/Configuration.tsget, has, inspect, update, onDidChange
WebviewProviderServices/WebviewPanel/createWebviewPanel, postMessage, onDidReceiveMessage
FileSystemProviderServices/File/readFile, writeFile, stat, readDirectory, createDirectory, delete, rename

gRPC Communication 🌐

We communicate with Mountain via the Vine gRPC protocol.

FeatureImplementation
ConnectionUnary gRPC calls + bidirectional streaming
Heartbeat5-second interval via Heartbeat RPC
ReconnectionAutomatic on disconnect (exponential backoff)
Timeout30-second request timeout
BackpressuregRPC flow control

RequireInterceptor 🪝

The RequireInterceptor patches Node.js’s require() to enable VS Code module loading.

Interception Rules 📋

Module PatternReplacementBehavior
electron(empty stub)No-op module with expected method stubs
original-fsfsRedirect to Node.js standard library
keytarCustom stubOS keychain via Mountain gRPC
spdlogCustom stubNo-op logging
vscode-windows-registryCustom stubNo-op (macOS-only)
./extHost*.jsLoad from @codeeditorland/outputVS Code stock source
./mainThread*.jsLoad from @codeeditorland/outputVS Code stock source
vscodeApiFactory constructExtension-specific API surface

Extension Lifecycle 🔄

1. Mountain scans extension directories, sends manifests in InitData
2. RequireInterceptor loads extension's main module, calls activate()
3. Extension registers commands, providers via vscode.* API calls
4. API calls dispatch through providers -- in-process (Track A)
   or via gRPC to Mountain (Track B)
5. Mountain sends DeactivateExtension request, subscriptions
   disposed, module unloaded

Dual-Track Routing 🛤️

TrackImplementationLatencyWhen Used
A - Stock NodeUnmodified VS Code extHost*.tsIn-processDefault for all APIs
B - Rust NativegRPC ActionEffect to Mountain~1msI/O-heavy APIs (fs, terminal, search, git)

Track Distribution by API 📊

APIDefault TrackRationale
commands.registerCommandAIn-process bookkeeping
commands.executeCommandAIn-process dispatch
window.showInformationMessageAIn-process dialog
workspace.openTextDocumentAContent in memory
workspace.fs.readFileBNative file I/O (faster)
workspace.findFilesBNative search (ripgrep)
window.createWebviewPanelBMountain owns webview lifecycle
env.clipboardBNative clipboard access

Funding 💎

Project Maintainers: Source Open (Source/[email protected]) | GitHub Repository | Report an Issue


See Also