Transport
How the Transport interface enables DorkOS to run in multiple environments
Transport
The Transport interface is the core abstraction that makes DorkOS work across different environments. It defines a contract between the React client and whatever backend serves Claude responses — whether that is an Express server over HTTP or direct in-process calls inside Obsidian.
Why Transport Exists
DorkOS runs in two very different environments:
- Standalone web — A browser talking to an Express server over the network
- Obsidian plugin — A React app embedded in Electron, calling services directly in the same process
Without this abstraction, client code would need conditional logic everywhere: "if we're in a browser, use fetch; if we're in Obsidian, call the service directly." Transport eliminates this entirely. The client calls transport.sendMessage() and never knows how that message reaches Claude.
The Interface
Transport defines methods for every operation the client needs, organized by domain:
Sessions
| Method | Purpose |
|---|---|
createSession | Start a new Claude conversation |
listSessions | Get all sessions, optionally filtered by directory |
getSession | Fetch metadata for a single session |
updateSession | Change session settings (permission mode, model) |
getMessages | Load message history for a session |
sendMessage | Send a message and stream the response (accepts optional uiState for agent UI awareness) |
stopTask | Stop a running background task by ID |
interruptSession | Interrupt the active query for a session (best-effort, graceful then forceful) |
approveTool | Approve a pending tool call |
denyTool | Deny a pending tool call |
submitAnswers | Respond to an interactive question prompt |
getTasks | Get the current task list for a session |
getLastMessageIds | Get JSONL-assigned IDs for the last user/assistant messages (client-server reconciliation) |
forkSession | Fork an existing session — creates a copy with the same history up to a given message |
renameSession | Rename a session (updates the JSONL first-message summary) |
submitElicitation | Submit a response to an MCP elicitation prompt (structured form input from the agent) |
Filesystem and Config
| Method | Purpose |
|---|---|
browseDirectory | Browse the filesystem for directory selection |
getDefaultCwd | Get the server's default working directory |
getCommands | List available slash commands |
listFiles | List files in a directory |
getGitStatus | Get git branch and change information |
getMcpConfig | Read MCP server entries from .mcp.json in a project directory |
health | Server health check |
getConfig | Retrieve server configuration |
updateConfig | Partially update the persisted user config |
Runtime and Capabilities
| Method | Purpose |
|---|---|
getModels | List available Claude models |
getCapabilities | Get capability flags for all registered runtimes |
startTunnel | Start the ngrok tunnel and return the public URL |
stopTunnel | Stop the ngrok tunnel |
getTemplates | Fetch the merged template catalog (builtin + user) |
setDefaultAgent | Set the default agent by name |
createDirectory | Create a new directory (used by DirectoryPicker UI) |
reloadPlugins | Reload all MCP plugin servers for a session (picks up new MCP config) |
File Uploads
| Method | Purpose |
|---|---|
uploadFiles | Upload files to the session's working directory for agent access |
Tasks, Relay, Mesh
Transport also exposes full Tasks scheduler methods (listSchedules, createSchedule, triggerSchedule, listRuns, etc.), Relay messaging methods (sendRelayMessage, listRelayAdapters, getRelayTrace, etc.), and Mesh discovery methods (listMeshAgents, registerMeshAgent, getMeshTopology, etc.). These follow the same pattern as the core session methods.
The Transport interface is defined in @dorkos/shared so both the client and server reference the
same types.
Streaming with Callbacks
sendMessage handles real-time streaming. Rather than returning a stream object, it accepts an onEvent callback:
transport.sendMessage(sessionId, content, onEvent, signal, cwd, options)The optional options parameter accepts clientMessageId (for server-echo ID reconciliation) and uiState (a snapshot of the client's current UI state, enabling agent-driven UI control via MCP tools like controlUI and get_ui_state).
Each time a new event arrives — a text chunk, a tool call, an approval request — the transport calls onEvent with a StreamEvent object. This callback pattern works naturally with both adapters:
- HttpTransport parses SSE lines from a
ReadableStreamand callsonEventfor each parsed event - DirectTransport iterates an
AsyncGeneratorfrom the Agent SDK and callsonEventfor each yielded event
The optional AbortSignal parameter lets you cancel an in-progress request, which is useful when the user clicks "Stop" during streaming.
HttpTransport
The default adapter for standalone web deployments. It communicates with the DorkOS Express server using standard web APIs.
- CRUD operations (
listSessions,getMessages, etc.) usefetch()with JSON bodies sendMessageusesfetch()with a streaming response — the server sends Server-Sent Events, and the transport parses them intoStreamEventobjects- Constructor takes a
baseUrl(defaults to/api)
Use this for any deployment where DorkOS runs as a separate process — local development, self-hosted, or tunneled remote access.
DirectTransport
The adapter for the Obsidian plugin. It calls service instances directly without any network communication.
- Service methods are called directly as function calls in the same process
sendMessageiterates theAsyncGenerator<StreamEvent>returned by the AgentRuntime and forwards each event to the callback- Session IDs are generated locally with
crypto.randomUUID() - No HTTP overhead, no serialization, no port binding
Use this inside the Obsidian plugin, where the React client and backend services share the same Electron process.
Dependency Injection
Transport is injected into the React component tree via React Context. At the app root, a TransportProvider wraps the component tree with the appropriate adapter:
Standalone web: The entry point creates an HttpTransport pointing at the server's API base URL and wraps the app with TransportProvider.
Obsidian plugin: The plugin view creates service instances, passes them to a DirectTransport, and wraps the app with TransportProvider.
Any component or hook that needs to communicate with the backend calls useTransport() to get the current transport instance. All client code stays transport-agnostic.