# Chat Switchboard — Extension System Specification **Version:** 0.1 draft **Status:** Design **Applies to:** v0.6.x+ **Companion to:** ARCHITECTURE.md (core services + terminology) --- ## 1. Philosophy Chat Switchboard is a substrate, not an application. The core provides: authentication, provider routing, message persistence, an event bus, and a rendering surface. Everything else — editing, writing, cluster management, cost tracking, custom renderers — is an extension. The goal is that someone with a problem and some JS (or Go, or Python) can solve it without forking the project. The "modes" Jeff envisions — Editor, Article, Chat, Cluster Manager — are just extensions that register surfaces, tools, and event handlers. This project doesn't have to build all of them. It just has to make them possible. --- ## 2. Extension Tiers | | Tier 0: Browser | Tier 1: Starlark | Tier 2: Sidecar | |---|---|---|---| | **Runs in** | User's browser | Go server (embedded) | Separate container | | **Language** | JavaScript | Starlark (Python subset) | Any | | **Deployed by** | User or Admin push | Admin | Admin | | **Latency** | Zero (client-side) | Low (in-process) | Network hop | | **Can access** | DOM, EventBus, LocalStorage, user context | Message data, DB reads (sandboxed) | Anything (HTTP, filesystem, network) | | **Cannot access** | Server internals, other users' data | Network, filesystem, raw SQL | N/A (full access) | | **Trust model** | Same-origin; user-scoped or admin-pushed | Starlark sandbox (no I/O) | Container isolation | | **Use cases** | UI, rendering, shortcuts, client tools, modes | Routing rules, message transforms, logging | RAG, external APIs, webhooks, heavy compute | All three tiers communicate through the EventBus. A browser extension publishes `tool.result.{callId}` the same way a sidecar does — the bus doesn't care where the event originated. --- ## 3. Manifest Format Every extension, regardless of tier, is described by a manifest: ```json { "id": "cost-tracker", "name": "Cost Tracker", "version": "1.0.0", "tier": "browser", "author": "jeff", "description": "Real-time token counting and cost estimation", "permissions": [ "events:chat.message.*", "events:model.selected", "dom:input-area", "storage:local" ], "entry": "cost-tracker.js", "hooks": { "chat.message.send": { "priority": 10, "async": false }, "chat.message.received": { "priority": 50, "async": true } }, "tools": [], "surfaces": [], "settings": { "showInline": { "type": "boolean", "label": "Show cost inline", "default": true } } } ``` ### 3.1 Fields - **id**: Unique identifier. Namespaced by convention (`jeff.cost-tracker`). - **tier**: `browser` | `starlark` | `sidecar` - **permissions**: What the extension needs access to. The loader enforces these. Undeclared access is blocked (Tier 0 via proxy, Tier 1 via sandbox, Tier 2 via API scoping). - **entry**: For browser: JS file path. For starlark: `.star` file. For sidecar: endpoint URL or Docker image. - **hooks**: EventBus events this extension subscribes to, with priority (lower = runs first) and whether the hook is async. - **tools**: LLM-callable tools this extension provides (see §5). - **surfaces**: UI surfaces this extension registers (see §6). - **settings**: User-configurable options, rendered as a form in the extension settings UI. --- ## 4. Browser Extensions (Tier 0) ### 4.1 Lifecycle ``` Install → Load → Init → Active → Disable → Unload ``` **Install**: Admin pushes manifest + JS to server, or user adds from settings. Stored in `extensions` table with `tier = 'browser'`. **Load**: On page load, after `events.js` but before `app.js`, the extension loader injects ` ``` ### 7.2 extensions.js (Core) The extension loader and registry. ~200 lines. Responsibilities: - Fetch enabled extensions list from `/api/v1/extensions?tier=browser` - Inject script tags in dependency order - Provide `Extensions.register()` API - Build scoped `ctx` objects per extension (permission enforcement) - Manage surface activation/deactivation - Collect browser tool schemas for the completion handler - Route `tool.call.*` events to the correct handler - Provide `Extensions.list()`, `Extensions.get()`, `Extensions.settings()` ### 7.3 Backend Support New tables (migration 006): ```sql CREATE TABLE extensions ( -- already exists from 001, but needs columns: tier VARCHAR(20) NOT NULL DEFAULT 'browser', -- browser, starlark, sidecar manifest JSONB NOT NULL, assets_path TEXT, -- filesystem path for browser JS endpoint TEXT, -- URL for sidecar script TEXT, -- inline Starlark source installed_by UUID REFERENCES users(id), is_system BOOLEAN DEFAULT false ); CREATE TABLE extension_user_settings ( extension_id UUID REFERENCES extensions(id) ON DELETE CASCADE, user_id UUID REFERENCES users(id) ON DELETE CASCADE, settings JSONB DEFAULT '{}', is_enabled BOOLEAN DEFAULT true, PRIMARY KEY (extension_id, user_id) ); ``` New endpoints: ``` GET /api/v1/extensions — list enabled for current user GET /api/v1/extensions/:id/manifest — get manifest GET /api/v1/extensions/:id/assets/*path — serve browser JS POST /api/v1/extensions/:id/settings — update user settings POST /api/v1/admin/extensions — install extension DELETE /api/v1/admin/extensions/:id — uninstall PUT /api/v1/admin/extensions/:id — update (enable/disable, config) ``` ### 7.4 Tool Registry Integration The completion handler's tool collection expands: ```go func (h *CompletionHandler) collectTools(userID string, configID string) []ToolSchema { var tools []ToolSchema // 1. Model-native tools (if provider supports them) caps := h.getModelCapabilities(model, configID) if !caps.ToolCalling { return nil // Model doesn't support tools, skip everything } // 2. Server-side extension tools (Starlark + Sidecar) tools = append(tools, h.serverTools(userID)...) // 3. Browser tools (only if WebSocket connected) if h.hub.IsConnected(userID) { tools = append(tools, h.browserTools(userID)...) } return tools } ``` --- ## 8. EventBus Integration The routing table from events/types.go expands: ```go var Routes = map[string]Direction{ // Core "chat.message.*": DirBoth, "user.presence": DirToClient, // Tool execution "tool.call.*": DirToClient, // Server → specific client "tool.result.*": DirFromClient, // Client → server // Surfaces "surface.activated": DirLocal, // Client-only "surface.deactivated": DirLocal, // Extension lifecycle "extension.loaded": DirLocal, "extension.error": DirLocal, // Cross-extension (cluster manager example) "cluster.node.*": DirLocal, // Between browser extensions "cluster.alert.*": DirBoth, // Could notify server too } ``` Browser extensions use `Events.on()` and `Events.emit()` — the same API the core app uses. Events with `DirBoth` or `DirFromClient` cross the WebSocket. `DirLocal` events stay in the browser. This means cluster manager extensions can coordinate locally at zero latency, and only publish to the server when something needs persistence or notification. --- ## 9. Implementation Roadmap ### Phase A: Foundation (v0.6.0) - [ ] `extensions.js` — loader, registry, scoped context - [ ] `Extensions.register()` API with permission enforcement - [ ] Manifest format parser and validator - [ ] Admin endpoints for extension management - [ ] Asset serving endpoint - [ ] Extension settings UI in Settings modal ### Phase B: Browser Tools (v0.6.1) - [ ] `ctx.tools.handle()` API for browser tool registration - [ ] Tool schema collection in completion handler - [ ] `tool.call.*` / `tool.result.*` WebSocket routing - [ ] Timeout and error handling for browser tools - [ ] Tool execution in EventBus `WaitFor` pattern - [ ] Tool use display in chat messages ### Phase C: Surfaces (v0.6.2) - [ ] Surface registration and activation API - [ ] Mode selector in sidebar - [ ] `ctx.ui.replace()` / `ctx.ui.restore()` for region management - [ ] `surface.activated` / `surface.deactivated` events ### Phase D: First Extensions (v0.7.0) - [ ] Cost tracker (browser, proof of concept) - [ ] Slash commands (browser, message transforms) - [ ] Custom renderers (browser, mermaid/latex/etc) - [ ] Editor mode (browser, with git provider tools) ### Phase E: Server-Side Tiers (v0.8.0) - [ ] Starlark runtime integration - [ ] Sidecar HTTP tool protocol - [ ] Server-side tool execution in completion handler - [ ] Web search as sidecar extension --- ## 10. Design Principles 1. **Extensions are first-class.** The system is designed so that a mode or feature implemented as an extension is indistinguishable from one built into core. No second-class citizens. 2. **The EventBus is the spine.** Extensions don't import each other. They publish and subscribe to events. This is how the cluster manager extensions cooperate without knowing about each other at build time. 3. **Tools are location-transparent.** The LLM sees a tool schema. It doesn't know if the tool runs in the browser, in a Starlark sandbox, or in a container in another data center. The routing is the platform's problem. 4. **Permissions are declared, not discovered.** An extension says what it needs upfront. The loader enforces it. No ambient authority. 5. **The core stays small.** Auth, provider routing, message persistence, event bus, extension loader. That's core. Everything else is an extension — even if it ships with the project. 6. **Progressive capability.** A browser extension with zero tools and zero surfaces is just an EventBus subscriber. Add a tool and the LLM can call it. Add a surface and it becomes a mode. The same manifest format scales from "show token count" to "full IDE."