Skip to content
Development
Agent

main-patterns

Each domain in `src/main/core/` exposes a `controller.ts` that defines RPC handlers:

From plugin
emdash
5.4k24 skills24 agents

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

Each domain in `src/main/core/` exposes a `controller.ts` that defines RPC handlers:

Agent definition

main-patterns.md

Main Process Patterns

Controller Pattern

Each domain in `src/main/core/` exposes a `controller.ts` that defines RPC handlers:

// src/main/core/tasks/controller.ts
import { createRPCController } from '@shared/ipc/rpc';
import { createTask } from './createTask';
import { getTasks } from './getTasks';

export const taskController = createRPCController({
  createTask,
  getTasks,
  deleteTask,
  // ...
});

Controllers are assembled into the router in `src/main/rpc.ts`:

export const rpcRouter = createRPCRouter({
  tasks: taskController,
  projects: projectController,
  // ...
});

**Rules:**

  • Controller handlers are imported functions — keep logic in separate operation files, not inline
  • Each controller becomes an RPC namespace (e.g., `rpc.tasks.createTask(...)` on the renderer)
  • New domains need their controller added to `src/main/rpc.ts`

Service Pattern

For stateful concerns, use singleton classes:

export class AppService {
  private cache = new Map();

  async initialize() { /* ... */ }
  async doSomething(id: string) { /* ... */ }
}

export const appService = new AppService();

**Rules:**

  • Module-level singleton export
  • Initialization method called from `src/main/index.ts`
  • Services hold long-lived state (caches, subscriptions, connections)

Provider Pattern

For domain logic with multiple backends (local vs SSH):

src/main/core/projects/
├── project-provider.ts          # Interface
├── impl/
│   ├── local-project-provider.ts
│   └── _ssh-project-provider.ts  # Prefixed with _ = not yet implemented
└── project-manager.ts           # Orchestrates providers

Used in: projects, filesystem (`local-fs.ts` / `ssh-fs.ts`), terminals (`local-terminal-provider.ts` / `ssh-terminal-provider.ts`)

Result Type (`src/main/lib/result.ts`)

Explicit error handling via discriminated union:

import { ok, err, type Result } from '../lib/result';

async function doSomething(): Promise<Result<Data, SomeError>> {
  if (problem) return err({ type: 'not_found' as const });
  return ok(data);
}

**Rules:**

  • Prefer `Result<T, E>` over thrown exceptions for expected failure modes
  • Controllers convert Result types to IPC-compatible responses

Event System (`src/main/lib/events.ts`)

Topic-based event emitter for main ↔ renderer communication:

import { events } from '../lib/events';

// Emit to a specific topic (e.g., session ID)
events.emit(ptyDataChannel, buffer, sessionId);

// Listen on a specific topic
const unsub = events.on(ptyDataChannel, (data) => {...}, sessionId);

Channel naming: without topic → `eventName`, with topic → `eventName.{topic}`

Event type definitions live in `src/shared/events/`.

Read more
Ships withemdash

Emdash is the Open-Source Agentic Development Environment (🧡 YC W26). Run multiple coding agents in parallel. Use any provider.

Get the whole plugin
Stats
5,373
Stars
554
Forks
Active
Maintenance
TypeScript
Language
Apache-2.0
License
21h ago
Last commit
11mo ago
Created

Repo: generalaction/emdash