CodeNomad Documentation

repository·dev·Indexed 25 days ago

https://github.com/neuralnomadsai/codenomad

An AI coding cockpit that transforms the OpenCode terminal tool into a premium desktop workspace. CodeNomad provides advanced session management, remote access, and integrated web tools (SideCars) for long-duration AI coding sessions. The ecosystem includes an Electron/Tauri desktop application, a CLI server for remote development, and a specialized OpenCode Plugin that acts as a local bridge for event exchange between the server and the OpenCode environment.

Tokens
32.3K
Snippets
54
Records
155
Agent score
82%

What's inside CodeNomad

  1. Understand the CodeNomad Project Structure

    dev

    The project is organized into several key directories within packages/opencode-client/:

    • electron/: Contains the desktop application logic, including the main process (main.ts), window management (window.ts), process management for spawning OpenCode servers (process-manager.ts), and IPC handlers (ipc.ts).
    • src/components/: Houses the UI components (e.g., instance-tabs.tsx, session-tabs.tsx, message-stream-v2.tsx, prompt-input.tsx).
    • src/stores/: Contains the reactive state management for instances, sessions, and ui.
    • src/lib/: Utility modules for SDK management (sdk-manager.ts), SSE handling (sse-manager.ts), and port discovery (port-finder.ts).
    • src/hooks/: Custom hooks for interacting with state (e.g., use-instance.ts, use-session.ts).
    • src/types/: TypeScript definitions for instance, session, and message entities.
  2. What is the CodeNomad OpenCode Plugin?

    dev

    The CodeNomad OpenCode Plugin is an npm-packable package designed to be injected into OpenCode instances launched by CodeNomad. It acts as a local bridge for event exchange between the CodeNomad CLI server and the OpenCode environment.

    In production, the plugin is shipped as a local .tgz file and injected via the OPENCODE_CONFIG_CONTENT environment variable. In development, it references the TypeScript plugin entry directly using a file:// URL.

  3. Interact with the Messages Area

    dev

    The Messages Area displays the conversation between the User and the Assistant.

    Message Types

    • User Messages: Displays your text and any attached files (e.g., [@src/app.ts]).
    • Assistant Messages: Displays the AI's response, including tool calls and code blocks.

    Tool Calls

    When the assistant uses a tool (like running a bash command or editing a file), it appears as a tool call:

    • Collapsed View: Shows the tool name, a summary, and a status icon (e.g., ▶ bash: npm install vitest ✓).
    • Expanded View: Click the tool call to see the specific Input sent to the tool and the resulting Output.
    • File Changes: For edit tools, the UI displays the modified file path and a summary of changes (e.g., +12 lines, -3 lines). You can click to expand and view the diff inline.

    Status Icons

    • Pending: Spinner indicating an action is in progress.
    • Success: Green checkmark.
    • Error: Red X.
    • Warning: Yellow triangle.
    • Auto-scroll: The view automatically scrolls to the bottom when new messages arrive.
    • Manual Scroll: Scrolling up manually disables auto-scroll. A "Scroll to bottom" button will appear to return to the latest message.
  4. Determine when to transition from MVP to performance optimization

    dev

    Optimization should only occur during Phase 8 or when triggered by specific real-world indicators.

    Post-MVP Triggers

    Transition to optimization when you encounter:

    1. User Feedback: Multiple users report slowness or abandon the app due to performance.
    2. Measurable Issues: The app freezes for >2 seconds, memory usage causes crashes, or the UI becomes unresponsive.
    3. Phase 8 Milestone: The MVP is complete, validated, and a user base is established.

    Optimization Workflow

    1. Measure First: Profile actual bottlenecks using real user data rather than assumptions.
    2. Target Fixes: Fix specific bottlenecks without over-engineering.
    3. Iterate: Optimize one thing at a time and verify improvements with users.

    Acceptable MVP Performance

    It is acceptable for the MVP if:

    • 100 messages render in 1 second.
    • The UI is slightly laggy during heavy streaming.
    • Memory usage grows with message count.
    • Multiple instances slow down the app.
  5. How the CodeNomad OpenCode Plugin bridge works

    dev

    The plugin facilitates communication through the following mechanism:

    1. Injection: CodeNomad sets OPENCODE_CONFIG_CONTENT when spawning OpenCode instances. This configuration includes plugin entries that tell OpenCode to load the plugin.
    2. Connection: The CodeNomadPlugin uses the CODENOMAD_INSTANCE_ID and CODENOMAD_BASE_URL environment variables to establish a connection.
    3. Event Exchange:
      • The plugin connects to GET /workspaces/:id/plugin/events to receive events.
      • The plugin sends events via POST /workspaces/:id/plugin/event.
    4. Server Integration: The CodeNomad server exposes these plugin routes and maps incoming events into the UI's Server-Sent Events (SSE) pipeline.
  6. Understand the CodeNomad data models

    dev

    CodeNomad manages state through a hierarchy of Instances, Sessions, and Messages.

    Instance State

    An instance represents a single OpenCode server process. Key fields include:

    • id: Unique identifier.
    • folder: The local directory path.
    • port: The assigned port.
    • proxyPath: The base URL for API/SSE calls (e.g., /workspaces/:id/instance).
    • status: Current state ('starting' | 'ready' | 'error' | 'stopped').
    • client: The OpenCodeClient instance.
    • eventSource: The EventSource for SSE.
    • sessions: A map of sessionId to Session objects.

    Session State

    A session is a single conversation within an instance:

    • id: Unique identifier.
    • parentId: If this is a child session, the ID of the parent.
    • messages: An array of Message objects.
    • agent: The active agent name.
    • model: Contains providerId and modelId.
    • status: Current state ('idle' | 'streaming' | 'error').

    Message State

    Individual messages within a session:

    • type: 'user' | 'assistant'.
    • parts: The content parts of the message.
    • status: 'sending' | 'sent' | 'streaming' | 'complete' | 'error'.
  7. Handle Real-time Updates with SSEManager

    dev

    CodeNomad uses Server-Sent Events (SSE) to stream messages and session updates from the OpenCode server to the UI.

    Communication Flow:

    1. An EventSource connects to the /event endpoint of the instance.
    2. Incoming JSON events are routed to the correct instance store.
    3. Reactive state (via SolidJS signals) is updated, triggering UI re-renders.

    Reconnection Logic: To handle network issues or server restarts, the SSEConnection implements exponential backoff:

    • maxReconnectAttempts: 5
    • reconnectDelay: Starts at 1000ms and doubles with each attempt.

    Key Methods:

    • connect(instanceId: string, port: number): Establishes the connection.
    • disconnect(instanceId: string): Closes the connection.
    • onMessageUpdate(handler): Subscribes to message changes.
    • onSessionUpdate(handler): Subscribes to session changes.
    class SSEConnection {
      private reconnectAttempts = 0
      private maxReconnectAttempts = 5
      private reconnectDelay = 1000 // Start with 1s
    
      reconnect() {
        if (this.reconnectAttempts >= this.maxReconnectAttempts) {
          this.emitError(new Error("Max reconnection attempts reached"))
          return
        }
    
        setTimeout(() => {
          this.connect()
          this.reconnectAttempts++
          this.reconnectDelay *= 2 // Exponential backoff
        }, this.reconnectDelay)
      }
    }
  8. Manage Sessions via SessionStore

    dev

    Sessions are child entities of an instance, representing specific AI interactions. A session tracks the agent, the model (provider and model ID), and versioning.

    Session Statuses:

    • idle: No activity.
    • streaming: Assistant is currently responding.
    • error: An error occurred during the session.

    Key Actions:

    • createSession(instanceId: string, agent: string): Starts a new session.
    • deleteSession(instanceId: string, sessionId: string): Removes a session.
    • setActiveSession(instanceId: string, sessionId: string): Switches the active session.
    • updateSession(instanceId: string, sessionId: string, updates: Partial<Session>): Updates session metadata.
    interface SessionState {
      // Per instance
      getSessions(instanceId: string): Session[]
      getActiveSession(instanceId: string): Session | null
    
      // Actions
      createSession(instanceId: string, agent: string): Promise<Session>
      deleteSession(instanceId: string, sessionId: string): Promise<void>
      setActiveSession(instanceId: string, sessionId: string): void
      updateSession(instanceId: string, sessionId: string, updates: Partial<Session>): void
    }
  9. How message streaming and session creation flows work

    dev

    Message Streaming Flow

    1. The user submits a prompt in the active session.
    2. The renderer sends a POST request to /session/:id/message.
    3. The SSE connection receives MessageUpdated events.
    4. Events are routed to the specific instance and session.
    5. The UI re-renders automatically as message state updates.

    Child Session Creation Flow

    1. An OpenCode server creates a child session.
    2. The server emits a SessionUpdated event via SSE containing a parentId.
    3. The renderer detects this and adds the new session to the instance's session list.
    4. A new session tab is automatically created in the UI.
  10. Manage Instances via InstanceStore

    dev

    Instances represent running OpenCode servers. Use the InstanceState interface to manage their lifecycle. Each instance tracks its own port, process ID (PID), status, SDK client, and associated sessions.

    Instance Statuses:

    • starting: Server is currently spawning.
    • ready: Server is connected and operational.
    • error: Failed to start.
    • stopped: Server has been killed.

    Key Actions:

    • createInstance(folder: string): Spawns a new server in the specified folder.
    • removeInstance(id: string): Removes an instance.
    • setActiveInstance(id: string): Sets the current active instance.
    interface InstanceState {
      instances: Map<string, Instance>
      activeInstanceId: string | null
    
      // Actions
      createInstance(folder: string): Promise<void>
      removeInstance(id: string): Promise<void>
      setActiveInstance(id: string): void
    }
  11. How tool calls are rendered in CodeNomad

    dev

    CodeNomad uses specialized rendering logic for different tool types to provide context-specific displays rather than generic input/output dumps. Each tool type (e.g., read, edit, bash) has a unique title format and body content derived from its metadata to ensure the most relevant information is visible to the user.

    Rendering Principles

    • Context-specific: Each tool shows the most relevant information for its type.
    • Progressive disclosure: Tool details are collapsed by default and can be expanded.
    • Visual hierarchy: Uses icons, colors, and borders to indicate the current status.
    • Truncation: Long content is truncated (typically 6-10 lines) to prevent UI clutter.
  12. Tool call status states and visual indicators

    dev

    Tool calls transition through several status states, each with specific visual cues:

    • Pending: Icon . Title shows action text (e.g., "Writing command..."). Border uses accent color. Features a shimmer animation on the title and an expandable "Waiting for permission..." message.
    • Running: Icon . Title shows action text. Border uses warning color (yellow/orange). Features a pulse animation on the status icon.
    • Completed: Icon . Title shows tool-specific info with arguments. Border uses success color (green). Body shows tool-specific rendered content.
    • Error: Icon . Title shows tool-specific info. Border uses error color (red). Body shows the error message in a highlighted box.