Skip to content

Conversation

@ammar-agent
Copy link
Collaborator

@ammar-agent ammar-agent commented Nov 8, 2025

Introduces a VS Code-style extension system for cmux, allowing users to extend functionality via custom extensions loaded from ~/.cmux/ext/.

Features

Extension Discovery:

  • Loads extensions from ~/.cmux/ext/ (global directory)
  • Supports single-file (.js) and folder-based extensions (with manifest.json)
  • Validates manifests and filters invalid extensions

Extension Execution:

  • Extensions run in isolated child process (Node.js fork)
  • Single global extension host serves all workspaces (VS Code architecture)
  • Scales efficiently: 50 workspaces = 1 process (not 50)

Extension API:

  • Post-tool-use hook: Called after any tool execution in any workspace
  • Runtime interface: Provides workspace context (path, project, read/write files)
  • Workspace registration: Runtimes created/destroyed as workspaces are used/removed

Architecture:

Main Process → spawns once → Extension Host (singleton)
                                    ↓
                        Map<workspaceId, Runtime>
                                    ↓
                            Extensions (loaded once)

Implementation

Core Components

ExtensionManager (src/services/extensions/extensionManager.ts):

  • initializeGlobal() - Discovers extensions and spawns global host once at startup
  • registerWorkspace() - Creates runtime for workspace on first use
  • unregisterWorkspace() - Removes workspace runtime when workspace removed
  • shutdown() - Cleans up global host on app exit

ExtensionHost (src/services/extensions/extensionHost.ts):

  • Runs in isolated child process
  • Maintains Map<workspaceId, Runtime> for all active workspaces
  • Loads extensions once, routes hook calls to appropriate workspace runtime
  • Handles: init, register-workspace, unregister-workspace, post-tool-use

Discovery (src/utils/extensions/discovery.ts):

  • Scans directory for valid extensions
  • Validates manifests and entry points
  • Returns array of ExtensionInfo objects

Integration

AIService (src/services/aiService.ts):

  • Calls initializeGlobal() in constructor (spawns host once)
  • Calls registerWorkspace() on first message from workspace
  • Extension host hooks into tool execution pipeline

IpcMain (src/services/ipcMain.ts):

  • Calls unregisterWorkspace() when workspace removed
  • Ensures clean runtime lifecycle

Message Protocol

// Host initialization
{ type: "init"; extensions: ExtensionInfo[] }

// Workspace lifecycle
{ type: "register-workspace"; workspaceId, workspacePath, projectPath, runtimeConfig, runtimeTempDir }
{ type: "workspace-registered"; workspaceId }
{ type: "unregister-workspace"; workspaceId }
{ type: "workspace-unregistered"; workspaceId }

// Extension hooks
{ type: "post-tool-use"; workspaceId, payload: { toolName, args, result } }

Testing

Unit Tests (14 tests, all pass):

  • src/utils/extensions/discovery.test.ts - 9 tests covering discovery edge cases
  • src/services/extensions/extensionManager.test.ts - 5 tests covering manager lifecycle

Integration Tests (3 tests, all pass):

  • tests/extensions/extensions.test.ts - End-to-end extension execution

Static Checks:

  • TypeCheck: Both main and renderer processes pass
  • Lint: No errors or warnings
  • Format: All files properly formatted

Example Extensions

Single-file extension (~/.cmux/ext/tool-logger.js):

module.exports = {
  postToolUse: async (toolName, args, result, runtime) => {
    console.log(`Tool used: ${toolName}`);
  }
};

Folder-based extension (~/.cmux/ext/echo-extension/):

// manifest.json
{
  "name": "echo-extension",
  "version": "1.0.0",
  "main": "index.js"
}

Architecture Decision: Global vs Per-Workspace

Chose global host architecture (consistent with VS Code) over per-workspace hosts:

Benefits:

  • Scalability: 50 workspaces = 1 process (not 50)
  • Memory: Extensions loaded once, not duplicated per workspace
  • Performance: No process spawn on workspace creation
  • Standards: Matches VS Code's proven architecture for multi-folder workspaces

Trade-offs:

  • Extension state must be workspace-aware (Runtime interface provides context)
  • More complex message protocol (workspace registration vs simple init)

This is the right choice for cmux's "parallel agentic development" use case where users frequently have many workspaces open.

Files Added

Core (7 files):

  • src/types/extensions.ts - Type definitions for extension system
  • src/services/extensions/extensionHost.ts - Extension host process entry point
  • src/services/extensions/extensionManager.ts - Extension manager service
  • src/utils/extensions/discovery.ts - Extension discovery utility

Tests (3 files):

  • src/utils/extensions/discovery.test.ts - Discovery unit tests
  • src/services/extensions/extensionManager.test.ts - Manager unit tests
  • tests/extensions/extensions.test.ts - Integration tests

Files Modified

Integration points (3 files):

  • src/services/aiService.ts - Initialize global host, register workspaces
  • src/services/ipcMain.ts - Unregister workspaces on removal
  • src/services/streamManager.ts - Import extension types

Next Steps

  • Update integration tests for new architecture (currently passing but using old patterns)
  • Add more hook types (pre-tool-use, on-message, on-workspace-created, etc.)
  • Document extension API in user-facing docs (docs/)
  • Consider project-local extensions (.cmux/ext/) in addition to global

Generated with cmux

@ammar-agent ammar-agent changed the title 🤖 refactor: single global extension host (VS Code architecture) 🤖 feat: VS Code-style extension system with global host architecture Nov 8, 2025
@ammar-agent ammar-agent force-pushed the plugins branch 14 times, most recently from cebbaac to dd177ee Compare November 10, 2025 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant