This document covers every public interface, class, and configuration schema in OpenBridge. Use it alongside the plugin guides and architecture overview.
- Message Types
- Plugin Interfaces
- CLI Adapter Layer
- Built-in Providers
- Discovery Types
- Master AI Types
- Core Classes
- Error Handling
- Configuration Schemas
- Memory Classes
- Master AI Classes
- Utility Functions
- Built-in Commands
- HTTP Endpoints
Source: src/types/message.ts
A message received from a messaging connector.
| Property | Type | Description |
|---|---|---|
id |
string |
Unique message ID (from the platform or generated) |
source |
string |
The connector that received this message |
sender |
string |
Sender identifier (phone number, user ID, etc.) |
rawContent |
string |
The raw message text as received |
content |
string |
Cleaned content (prefix stripped, trimmed) |
timestamp |
Date |
When the message was received |
metadata? |
Record<string, unknown> |
Optional platform-specific metadata |
A response to be sent back through a connector.
| Property | Type | Description |
|---|---|---|
target |
string |
The connector to send through |
recipient |
string |
The recipient identifier |
content |
string |
The response content |
replyTo? |
string |
Reference to the original inbound message ID |
metadata? |
Record<string, unknown> |
Optional metadata for the connector |
Source: src/types/connector.ts
Interface that every messaging connector must implement.
| Member | Type | Description |
|---|---|---|
name |
readonly string |
Unique identifier (e.g. 'whatsapp', 'slack') |
initialize() |
() => Promise<void> |
Connect to the messaging platform |
sendMessage(message) |
(message: OutboundMessage) => Promise<void> |
Send a response back through the platform |
sendTypingIndicator?() |
(chatId: string) => Promise<void> |
Send a typing indicator (optional, best-effort) |
on(event, listener) |
<E extends keyof ConnectorEvents>(event, listener) => void |
Register event listeners |
shutdown() |
() => Promise<void> |
Gracefully shut down the connector |
isConnected() |
() => boolean |
Check if currently connected |
Events emitted by a connector, consumed via connector.on(event, listener).
| Event | Listener Signature | Description |
|---|---|---|
message |
(message: InboundMessage) => void |
A valid, filtered message arrived |
ready |
() => void |
Connector is ready to send/receive |
auth |
(data: unknown) => void |
Authentication event (e.g. QR code) |
error |
(error: Error) => void |
Connector error |
disconnected |
(reason: string) => void |
Connector disconnected |
Source: src/types/provider.ts
Interface for AI providers. In V0, providers are manually registered. In V2, the Master AI uses the generalized executor to run any discovered CLI tool.
| Member | Type | Description |
|---|---|---|
name |
readonly string |
Unique identifier (e.g. 'claude-code') |
initialize() |
() => Promise<void> |
Validate credentials, warm up |
processMessage() |
(message: InboundMessage) => Promise<ProviderResult> |
Process a message and return the AI response |
streamMessage?() |
(message: InboundMessage) => AsyncGenerator<string, ProviderResult> |
Stream response chunks (optional, falls back to processMessage) |
isAvailable() |
() => Promise<boolean> |
Check if the provider is ready |
shutdown() |
() => Promise<void> |
Gracefully shut down |
Result returned by an AI provider after processing.
| Property | Type | Description |
|---|---|---|
content |
string |
The AI-generated response text |
metadata? |
object |
Optional processing metadata |
metadata may include:
| Property | Type | Description |
|---|---|---|
durationMs? |
number |
Processing duration in ms |
usage? |
Record<string, number> |
Token count or similar usage data |
[key] |
unknown |
Provider-specific data |
Source: src/core/agent-runner.ts
Options accepted by AgentRunner.spawn(). Adapter-specific fields are silently dropped by adapters that don't support them.
| Property | Type | Default | Description |
|---|---|---|---|
prompt |
string |
— | The prompt to send to the AI agent |
workspacePath |
string |
— | Working directory for the agent process |
model? |
string |
— | Tier alias ('haiku', 'sonnet', 'opus') or a full model ID |
allowedTools? |
string[] |
— | Tools the agent may use. Mapped to --allowedTools (Claude) or sandbox mode (Codex) |
maxTurns? |
number |
— | Max agentic turns before stopping. Claude only — dropped by Codex/Aider adapters |
timeout? |
number |
— | Timeout in milliseconds per attempt |
retries? |
number |
3 |
Number of retry attempts on non-zero exit codes |
retryDelay? |
number |
10000 |
Delay in milliseconds between retry attempts |
logFile? |
string |
— | Path to write the full agent log output |
sessionId? |
string |
— | Start a new named session (--session-id for Claude, named session for Codex) |
resumeSessionId? |
string |
— | Resume a prior session (--resume for Claude, exec resume --last for Codex) |
systemPrompt? |
string |
— | System prompt injected at the top of the agent context |
maxBudgetUsd? |
number |
— | Max spend in USD (--max-budget-usd for Claude; dropped by Codex/Aider) |
mcpConfigPath? |
string |
— | Path to MCP config JSON (--mcp-config for Claude, -c for Codex; Aider drops it) |
strictMcpConfig? |
boolean |
— | When true, passes --strict-mcp-config to Claude — isolates the worker from any global MCP configs (e.g. ~/.claude/claude_desktop_config.json). Always set to true by manifestToSpawnOptions() when a per-worker temp MCP config is generated. |
Source: src/core/cli-adapter.ts, src/core/adapter-registry.ts, src/core/adapters/
The CLI Adapter layer translates provider-neutral SpawnOptions into tool-specific binary, args, and env for each supported AI CLI (claude, codex, aider). This abstraction lets the rest of OpenBridge remain tool-agnostic.
SpawnOptions → CLIAdapter.buildSpawnConfig() → CLISpawnConfig → child_process.spawn()
Interface that every CLI adapter must implement.
| Member | Type | Description |
|---|---|---|
name |
readonly string |
Provider name matching DiscoveredTool.name (e.g. 'claude', 'codex', 'aider') |
buildSpawnConfig(opts) |
(opts: SpawnOptions) => CLISpawnConfig |
Translate SpawnOptions into the binary, args, and env for this CLI |
cleanEnv(env) |
(env: Record<string, string | undefined>) => Record<...> |
Strip env vars that would cause conflicts for this CLI |
mapCapabilityLevel(level) |
(level: CapabilityLevel) => string[] | undefined |
Map capability level to CLI-specific access restrictions. Returns undefined if the CLI doesn't use tool lists (e.g. Codex uses sandbox modes instead) |
isValidModel(model) |
(model: string) => boolean |
Return true if the model string is recognized by this CLI |
The output of CLIAdapter.buildSpawnConfig() — everything needed to call child_process.spawn().
| Property | Type | Description |
|---|---|---|
binary |
string |
Command name or absolute path to the binary |
args |
string[] |
CLI arguments array |
env |
Record<string, string | undefined> |
Environment variables (cleaned of conflicting vars) |
stdin? |
'ignore' | 'pipe' |
stdin behavior. 'ignore' closes stdin (default for Claude/Codex). 'pipe' provides a writable stream for CLIs that require TTY detection. |
parseOutput? |
(stdout: string) => string |
Optional post-processor for raw stdout. Used by Codex to extract the final message from --json JSONL. Falls back to raw stdout if absent or if the function throws. |
type CapabilityLevel = 'read-only' | 'code-edit' | 'full-access';Maps tool profiles to CLI-specific access mechanisms:
| Level | Claude (--allowedTools) |
Codex (--sandbox) |
|---|---|---|
'read-only' |
Read, Glob, Grep |
read-only |
'code-edit' |
Read, Edit, Write, Glob, Grep, Bash(git:*), Bash(npm:*) |
workspace-write |
'full-access' |
All tools | --full-auto (danger mode) |
Source: src/core/adapter-registry.ts
Maps discovered tool names to CLIAdapter instances. Built-in adapters (claude, codex, aider) are lazy-loaded on first access. Custom adapters registered via register() take priority over built-ins.
Methods:
| Method | Returns | Description |
|---|---|---|
register(name, adapter) |
void |
Register a CLIAdapter for a tool name (overrides built-ins) |
get(name) |
CLIAdapter | undefined |
Get the adapter for a tool name, creating from built-ins if needed |
getForTool(tool) |
CLIAdapter | undefined |
Get the adapter for a DiscoveredTool |
has(name) |
boolean |
Check if an adapter exists for a tool name (registered or built-in) |
Factory:
function createAdapterRegistry(): AdapterRegistry;Creates an AdapterRegistry pre-loaded with the ClaudeAdapter.
Built-in adapters:
| Tool name | Adapter | Source |
|---|---|---|
claude |
ClaudeAdapter |
src/core/adapters/claude-adapter.ts |
codex |
CodexAdapter |
src/core/adapters/codex-adapter.ts |
aider |
AiderAdapter |
src/core/adapters/aider-adapter.ts |
Source: src/core/adapters/codex-adapter.ts
Translates SpawnOptions into codex exec arguments for non-interactive Codex CLI execution.
Flags always included:
| Flag | Value | Reason |
|---|---|---|
--skip-git-repo-check |
(boolean flag) | Required for non-git or untrusted workspaces — Codex refuses to run without it |
--json |
(boolean flag) | Enables JSONL structured output (one JSON event per line to stdout) |
-o <tempFile> |
Auto-generated temp path | Reliable final-answer capture. Codex writes the last message to this file |
--ephemeral |
(worker spawns only) | Suppresses session persistence for short-lived worker invocations |
Flags set conditionally:
| Flag | Condition | Value |
|---|---|---|
--model <M> |
opts.model is set |
Model string passed through |
--sandbox <mode> |
allowedTools present, not Bash(*) |
read-only or workspace-write |
--full-auto |
Bash(*) in allowedTools |
Enables auto-approve + full sandbox (danger-full-access) |
-c <path> |
opts.mcpConfigPath is set |
MCP config file for Codex-native MCP passthrough |
Sandbox inference from allowedTools:
allowedTools content |
Sandbox mode |
|---|---|
Bash(*) present |
danger-full-access (via --full-auto) |
Edit or Write present |
workspace-write |
| Empty, undefined, or read-only tools | read-only (safe default) |
Output parsing priority:
- Read the
-otemp file — Codex's most reliable output path - Fall back to
--jsonJSONL parsing (parseCodexJsonlOutput()) if the temp file is absent - Fall back to raw stdout if no parseable
type: "message"event is found
OPENAI_API_KEY validation: buildSpawnConfig() throws immediately if OPENAI_API_KEY is not set, preventing confusing downstream auth failures.
Valid models (Codex CLI v0.104.0): gpt-5.2-codex (default), o3, o4-mini. Any model ID matching /^(gpt-|o[0-9]|codex)/ is also accepted for forward compatibility.
Source: src/providers/codex/codex-provider.ts
AIProvider implementation for the OpenAI Codex CLI. Uses AgentRunner + CodexAdapter internally — the same pattern as ClaudeCodeProvider.
import { CodexProvider } from './providers/codex/index.js';
const provider = new CodexProvider({
workspacePath: '/path/to/project',
timeout: 120000,
model: 'gpt-5.2-codex',
});
await provider.initialize();Methods:
| Method | Returns | Description |
|---|---|---|
initialize() |
Promise<void> |
Validates workspacePath is accessible |
processMessage(message) |
Promise<ProviderResult> |
Runs codex exec with session wiring and returns the AI response |
streamMessage(message) |
AsyncGenerator<string, ProviderResult> |
Falls back to processMessage() — yields the result in one chunk (Codex has no real-time streaming) |
isAvailable() |
Promise<boolean> |
Returns true if codex binary is on PATH and OPENAI_API_KEY is set |
shutdown() |
Promise<void> |
Clears all active sessions |
Session management: Sessions are scoped by sender:workspacePath. The first message in a session window starts a new Codex session; follow-up messages use codex exec resume --last. Sessions expire after the configured TTL (default 30 minutes).
Source: src/providers/codex/codex-config.ts
Zod schema for CodexProvider constructor options.
| Property | Type | Default | Description |
|---|---|---|---|
workspacePath |
string |
'.' |
Working directory for Codex invocations. Supports ~/ home directory expansion |
timeout |
number |
120000 (2 min) |
Timeout per invocation in milliseconds |
model? |
string |
— | Codex model override. Defaults to the Codex CLI's built-in default |
sandbox? |
string |
— | Sandbox mode override ('read-only', 'workspace-write', 'danger-full-access') |
sessionTtlMs |
number |
1800000 (30 min) |
Session inactivity TTL in milliseconds |
Source: src/types/discovery.ts
An AI tool found on the machine during startup scan.
| Property | Type | Description |
|---|---|---|
name |
string |
Tool identifier ('claude', 'codex', 'aider', etc.) |
path |
string |
Absolute path to the CLI binary |
version? |
string |
Version string (from --version) |
capabilities |
string[] |
List of capabilities (e.g. ['code-generation', 'file-editing', 'conversation']) |
role |
string |
'master' or 'delegate' |
available |
boolean |
Whether the tool was found and is usable |
Result of the full AI tool discovery scan.
| Property | Type | Description |
|---|---|---|
tools |
DiscoveredTool[] |
All discovered tools |
master |
DiscoveredTool | null |
The tool selected as Master |
scanDurationMs |
number |
How long the scan took (milliseconds) |
Source: src/types/master.ts
The lifecycle state of the Master AI.
type MasterState = 'idle' | 'exploring' | 'ready' | 'error';Summary of the Master AI's workspace exploration.
| Property | Type | Description |
|---|---|---|
name |
string |
Project name (from package.json or folder) |
description |
string |
Brief project description |
languages |
string[] |
Detected languages |
frameworks |
string[] |
Detected frameworks |
exploredAt |
string |
ISO 8601 timestamp of exploration |
Source: src/types/agent.ts
Describes everything needed to spawn a worker agent. The Master AI produces these; AgentRunner consumes them via manifestToSpawnOptions().
| Property | Type | Description |
|---|---|---|
prompt |
string |
The prompt to send to the worker agent (required) |
workspacePath |
string |
Working directory for the worker (required) |
model? |
string |
Tier alias ('haiku', 'sonnet', 'opus') or a full model ID |
profile? |
string |
Named tool profile — resolved to allowedTools by AgentRunner |
allowedTools? |
string[] |
Explicit tools list — overrides profile if both are provided |
maxTurns? |
number |
Maximum number of agentic turns |
timeout? |
number |
Timeout in milliseconds per attempt |
retries? |
number |
Number of retry attempts on failure |
retryDelay? |
number |
Delay in milliseconds between retries |
maxBudgetUsd? |
number |
Maximum spend in USD (--max-budget-usd for Claude; dropped by Codex/Aider) |
mcpServers? |
MCPServer[] |
MCP servers this worker may use. manifestToSpawnOptions() writes a per-worker temp config with only these servers and sets strictMcpConfig: true. |
Source: src/master/master-system-prompt.ts
Context object passed to generateMasterSystemPrompt() to build the Master AI's system prompt.
| Property | Type | Description |
|---|---|---|
workspacePath |
string |
Absolute path to the target workspace |
masterToolName |
string |
The Master AI tool's name (e.g. 'claude', 'codex') |
discoveredTools? |
DiscoveredTool[] |
All AI tools found on the machine |
customProfiles? |
object |
Custom tool profiles for the Master to assign to workers |
modelRegistry? |
ModelRegistry |
Provider-agnostic model registry for resolving tier aliases |
mcpServers? |
MCPServer[] |
MCP servers available for workers (sourced from V2Config.mcp.servers). When non-empty, the system prompt gains an "Available MCP Servers" section listing each server name. The Master can then assign servers to workers via the mcpServers field of TaskManifest. Omit or pass [] to suppress the section. |
Source: src/core/bridge.ts
Main orchestrator that wires connectors, auth, queue, Master AI, and all subsystems together.
import { Bridge } from './core/bridge.js';
const bridge = new Bridge(config, { configPath: './config.json' });
// Register connectors before starting
const registry = bridge.getRegistry();
registry.registerConnector('whatsapp', whatsappFactory);
// In V2: Master AI is set after discovery
bridge.setMaster(masterManager);
await bridge.start();
await bridge.stop();Methods:
| Method | Returns | Description |
|---|---|---|
getRegistry() |
PluginRegistry |
Access the plugin registry for registering plugins |
setMaster() |
void |
Wire a MasterManager into the router |
start() |
Promise<void> |
Initialize all plugins, start processing |
stop() |
Promise<void> |
Drain queue, shut down Master + plugins gracefully |
Source: src/index.ts
OpenBridge registers SIGINT and SIGTERM handlers that trigger a graceful shutdown sequence:
- Double-shutdown guard — a module-level
shutdownInProgressflag is set on the first signal. Subsequent signals (e.g., double Ctrl+C) are ignored with a WARN log rather than force-killing the process. - User-facing message —
console.log('\nShutting down gracefully... please wait')is printed immediately so users know not to press Ctrl+C again. - 10-second timeout —
bridge.stop()races against a 10-secondsetTimeout. If the timeout fires first,console.error('Shutdown timeout exceeded (10s) — forcing exit')is printed andprocess.exit(1)is called. - Critical-first ordering — inside
MasterManager.shutdown(),saveMasterSessionToStore()(fast SQLite write, <100ms) runs beforetriggerMemoryUpdate()(slow AI spawn, 10-30s). This ensures session state is always persisted even if the memory update is interrupted by the timeout. - Memory update failure isolation —
triggerMemoryUpdate()on shutdown is wrapped intry/catchso its failure cannot block or throw pastMasterManager.shutdown().
Full shutdown sequence:
SIGINT / SIGTERM
→ shutdownInProgress guard (no-op if already set)
→ console.log "Shutting down gracefully..."
→ workspaceManager.stopPolling()
→ Promise.race([bridge.stop(), 10s timeout])
→ bridge.stop()
→ queue.drain()
→ MasterManager.shutdown()
→ saveMasterSessionToStore() ← always runs (critical)
→ triggerMemoryUpdate() ← may be skipped by timeout
→ connectors[].shutdown()
→ process.exit(0)
Source: src/core/router.ts
Routes inbound messages to the Master AI (V2) or directly to a provider (V0), and sends responses back through the originating connector.
Methods:
| Method | Returns | Description |
|---|---|---|
addConnector(connector) |
void |
Register an active connector |
addProvider(provider) |
void |
Register an active provider (V0) |
setMaster(master) |
void |
Set the Master AI for routing (V2, priority) |
route(message) |
Promise<void> |
Route a message through the full pipeline |
Routing priority: Master AI > direct provider (V0 fallback).
Source: src/core/auth.ts
Handles sender authorization, prefix detection, prefix stripping, and command filtering.
Methods:
| Method | Returns | Description |
|---|---|---|
isAuthorized(sender) |
boolean |
Check if sender is whitelisted (empty list = open) |
hasPrefix(content) |
boolean |
Check if message starts with the configured prefix |
stripPrefix(content) |
string |
Remove the prefix and return cleaned content |
filterCommand(command) |
CommandFilterResult |
Check command against allow/deny pattern lists |
updateConfig(config) |
void |
Hot-reload auth configuration |
Source: src/core/queue.ts
Per-user message queue. Each sender gets its own sequential queue so one slow response does not block messages from other users.
Methods:
| Method | Returns | Description |
|---|---|---|
onMessage(handler) |
void |
Register the message processing handler |
enqueue(message) |
Promise<void> |
Add a message to the sender's queue |
drain() |
Promise<void> |
Wait for all queues to empty (used during shutdown) |
flushDeadLetters() |
DeadLetterItem[] |
Remove and return all dead letter items |
Properties:
| Property | Type | Description |
|---|---|---|
size |
number |
Total queued messages across all users |
isProcessing |
boolean |
Whether any user queue is active |
deadLetterSize |
number |
Number of dead letter items |
Source: src/core/registry.ts
Factory registry for connectors. Supports both manual registration and auto-discovery.
Methods:
| Method | Returns | Description |
|---|---|---|
registerConnector(type, factory) |
void |
Register a connector factory by type name |
createConnector(type, options) |
Connector |
Create a connector instance from config |
discoverPlugins(srcDir) |
Promise<void> |
Auto-discover plugins in connector dirs |
Source: src/core/rate-limiter.ts
Sliding-window rate limiter. Tracks per-sender message timestamps and rejects messages that exceed the configured threshold.
Methods:
| Method | Returns | Description |
|---|---|---|
isAllowed(sender) |
boolean |
true if within limit, false if rate limit exceeded |
updateConfig(config) |
void |
Hot-reload rate limiter configuration |
Source: src/core/config-watcher.ts
Watches config.json for file changes and triggers hot-reload handlers with debouncing.
Methods:
| Method | Returns | Description |
|---|---|---|
onChange(handler) |
void |
Register a config change handler |
start() |
void |
Start watching the config file |
stop() |
void |
Stop watching |
Source: src/core/audit-logger.ts
Persists message events as JSONL (one JSON object per line) to a configurable log file.
Methods:
| Method | Returns | Description |
|---|---|---|
logInbound(message) |
Promise<void> |
Log an inbound message event |
logOutbound(message) |
Promise<void> |
Log an outbound message event |
logAuthDenied(sender) |
Promise<void> |
Log an authorization denial |
logRateLimited(sender) |
Promise<void> |
Log a rate limit event |
logError(messageId, error) |
Promise<void> |
Log a processing error |
Source: src/core/health.ts
HTTP server exposing bridge health status as JSON.
Methods:
| Method | Returns | Description |
|---|---|---|
setDataProvider(provider) |
void |
Set the health data callback |
start() |
Promise<void> |
Start the HTTP server |
stop() |
Promise<void> |
Stop the HTTP server |
HealthStatus (response shape):
| Property | Type | Description |
|---|---|---|
status |
'healthy' | 'degraded' | 'unhealthy' |
Overall bridge status |
uptime_seconds |
number |
Seconds since bridge started |
memory_mb |
number |
Resident memory usage in MB |
active_workers |
number |
Current number of active worker agents |
master_status |
string |
Master AI lifecycle state |
db_status |
'connected' | 'disconnected' |
SQLite memory system status |
last_message_at |
string | null |
ISO 8601 timestamp of the most recent message |
timestamp |
string |
ISO 8601 timestamp of this health check |
connectors |
ComponentStatus[] |
Status of each connector |
providers |
ComponentStatus[] |
Status of each provider |
queue |
object |
Queue state (pending, processing, deadLetterSize) |
mcp? |
object |
MCP server health (present only when MCP servers are configured via setMcpServers()) |
mcp sub-object:
| Property | Type | Description |
|---|---|---|
enabled |
boolean |
Whether MCP is enabled |
servers |
McpServerStatus[] |
Health status of each configured MCP server |
McpServerStatus:
| Property | Type | Description |
|---|---|---|
name |
string |
MCP server name (from config) |
status |
'configured' | 'error' |
'configured' if command found on PATH, 'error' if not |
command |
string |
The server command being checked (e.g. npx) |
Source: src/core/metrics.ts
In-memory metrics collector. Tracks message counts, latency, and error rates.
Methods:
| Method | Returns | Description |
|---|---|---|
recordReceived() |
void |
Increment received message counter |
recordProcessed(duration) |
void |
Record a successfully processed message |
recordFailed(kind) |
void |
Record a failed message by error kind |
snapshot() |
MetricsSnapshot |
Return a point-in-time metrics snapshot |
Source: src/core/metrics.ts
HTTP server that exposes collected metrics as JSON.
Methods:
| Method | Returns | Description |
|---|---|---|
setDataProvider(provider) |
void |
Set the metrics data callback |
start() |
Promise<void> |
Start the HTTP server |
stop() |
Promise<void> |
Stop the HTTP server |
Source: src/providers/claude-code/provider-error.ts
Custom error class for classified provider failures.
| Property | Type | Description |
|---|---|---|
kind |
ErrorKind |
'transient' (retryable) or 'permanent' |
exitCode |
number |
CLI exit code that triggered the error |
message |
string |
Human-readable error description |
function classifyError(exitCode: number, stderr: string): ErrorKind;Classifies a CLI execution failure as transient or permanent:
- Exit code 124 (timeout) -> transient
- stderr matches transient patterns (timeout, rate limit, network errors) -> transient
- stderr matches permanent patterns (auth failure, not found, bad request) -> permanent
- Default for unrecognized errors -> transient (safer to retry)
Source: src/types/config.ts
All configuration is validated at startup using Zod schemas. The config loader tries V2 first, then falls back to V0.
The simplified config format. Three required fields.
| Property | Type | Default | Description |
|---|---|---|---|
workspacePath |
string |
— | Absolute path to the target project (required) |
channels |
array |
— | At least one channel config (required) |
auth |
object |
— | Authentication settings (required) |
master? |
object |
{} |
Override auto-detected Master AI settings |
mcp? |
MCPConfig |
— | MCP server configuration (see MCPConfig) |
queue? |
object |
see below | Queue retry configuration |
router? |
object |
see below | Router configuration |
audit? |
object |
see below | Audit logging configuration |
health? |
object |
see below | Health check endpoint config |
metrics? |
object |
see below | Metrics endpoint configuration |
logLevel? |
string |
'info' |
One of: trace, debug, info, warn, error, fatal |
Channel config:
| Property | Type | Default | Description |
|---|---|---|---|
type |
string |
— | Channel type ('whatsapp', 'console') |
enabled? |
boolean |
true |
Whether this channel is active |
options? |
Record<string, unknown> |
{} |
Channel-specific options |
The original config format. Still fully supported — auto-detected by the config loader.
| Property | Type | Default | Description |
|---|---|---|---|
connectors |
ConnectorConfig[] |
— | At least one connector (required) |
providers |
ProviderConfig[] |
— | At least one provider (required) |
defaultProvider |
string |
— | Name of the default provider |
auth |
AuthConfig |
— | Authentication configuration |
queue? |
QueueConfig |
see below | Queue retry configuration |
router? |
RouterConfig |
see below | Router configuration |
audit? |
AuditConfig |
see below | Audit logging configuration |
health? |
HealthConfig |
see below | Health check endpoint config |
metrics? |
MetricsConfig |
see below | Metrics endpoint configuration |
logLevel? |
string |
'info' |
Log level |
| Property | Type | Default | Description |
|---|---|---|---|
whitelist? |
string[] |
[] |
Allowed sender identifiers |
prefix? |
string |
'/ai' |
Message prefix to trigger the bot |
rateLimit? |
RateLimitConfig |
{} |
Rate limit settings |
commandFilter? |
CommandFilterConfig |
{} |
Command allow/deny lists |
| Property | Type | Default | Description |
|---|---|---|---|
enabled? |
boolean |
true |
Enable/disable rate limiting |
maxMessages? |
number |
10 |
Max messages per window per sender |
windowMs? |
number |
60000 |
Sliding window duration in milliseconds |
| Property | Type | Default | Description |
|---|---|---|---|
allowPatterns? |
string[] |
[] |
Regex patterns — command must match one |
denyPatterns? |
string[] |
[] |
Regex patterns — command must not match any |
denyMessage? |
string |
'That command is not allowed.' |
Message returned when command is denied |
| Property | Type | Default | Description |
|---|---|---|---|
maxRetries? |
number |
3 |
Max retry attempts for failed messages |
retryDelayMs? |
number |
1000 |
Base delay between retries (multiplied by attempt) |
| Property | Type | Default | Description |
|---|---|---|---|
progressIntervalMs? |
number |
15000 |
Interval for progress update messages (in ms) |
| Property | Type | Default | Description |
|---|---|---|---|
enabled? |
boolean |
false |
Enable/disable audit logging |
logPath? |
string |
'audit.log' |
Path to the JSONL audit log file |
| Property | Type | Default | Description |
|---|---|---|---|
enabled? |
boolean |
false |
Enable/disable health check endpoint |
port? |
number |
8080 |
HTTP port for the health endpoint |
| Property | Type | Default | Description |
|---|---|---|---|
enabled? |
boolean |
false |
Enable/disable metrics endpoint |
port? |
number |
9090 |
HTTP port for the metrics endpoint |
Source: src/types/config.ts
A single MCP server definition, used in the mcp.servers array of AppConfigV2.
| Property | Type | Description |
|---|---|---|
name |
string |
Unique server name — used by the Master AI to reference the server |
command |
string |
Command to launch the MCP server (e.g. npx, /usr/local/bin/my-mcp) |
args? |
string[] |
CLI arguments for the server command (e.g. ['-y', '@anthropic/canva-mcp-server']) |
env? |
Record<string, string> |
Environment variables injected when the MCP server process is started |
Source: src/types/config.ts
The mcp section of AppConfigV2. Controls which MCP servers are available to workers.
| Property | Type | Default | Description |
|---|---|---|---|
enabled? |
boolean |
true |
Enable or disable MCP support. When false, no MCP config is written at startup |
servers? |
MCPServer[] |
[] |
Inline server definitions |
configPath? |
string |
— | Path to an existing Claude Desktop or Claude Code MCP config file to import. Inline servers override same-name entries from the imported file. |
At bridge startup, loadConfig() transforms the mcp section into a global .openbridge/mcp-config.json file in the Claude CLI format. Use getMcpConfigPath() to retrieve the resulting file path.
Source: src/memory/conversation-store.ts
Functions for querying conversation history stored in the SQLite memory database.
function listSessions(db: Database.Database, limit?: number, offset?: number): SessionSummary[];Returns a paginated list of conversation sessions ordered by most recent activity.
| Parameter | Type | Default | Description |
|---|---|---|---|
db |
Database.Database |
— | SQLite database instance |
limit |
number |
20 |
Maximum sessions to return |
offset |
number |
0 |
Pagination offset |
Returns: SessionSummary[] — sessions ordered by last_message_at DESC.
SessionSummary:
| Property | Type | Description |
|---|---|---|
session_id |
string |
Unique session identifier |
title |
string | null |
Session title (first user message, ≤50 chars) |
first_message_at |
string |
ISO 8601 timestamp of first message |
last_message_at |
string |
ISO 8601 timestamp of most recent message |
message_count |
number |
Total messages in the session |
channel |
string | null |
Channel that originated the session |
user_id |
string | null |
Sender identifier |
function searchSessions(db: Database.Database, query: string, limit?: number): SessionSummary[];Full-text search over conversation content returning session-level results via FTS5.
| Parameter | Type | Default | Description |
|---|---|---|---|
db |
Database.Database |
— | SQLite database instance |
query |
string |
— | Search query string |
limit |
number |
10 |
Maximum sessions to return |
Returns: SessionSummary[] ranked by number of matching messages, then by recency. Returns [] if query is empty or no matches found. The query is passed through sanitizeFts5Query() before the FTS5 MATCH clause — special characters (', ", *, AND, OR, NOT, (, ), etc.) are stripped and each token is quoted to prevent SqliteError: fts5: syntax error (Phase 63 fix).
function getSessionHistory(
db: Database.Database,
sessionId: string,
limit?: number,
): ConversationEntry[];Retrieves the full message transcript for one conversation session.
| Parameter | Type | Default | Description |
|---|---|---|---|
db |
Database.Database |
— | SQLite database instance |
sessionId |
string |
— | Session ID to retrieve |
limit |
number |
50 |
Maximum messages to return (most recent, then oldest-first) |
Returns: ConversationEntry[] in chronological order (oldest first). Returns [] if session not found.
ConversationEntry:
| Property | Type | Description |
|---|---|---|
id |
number |
Auto-increment row ID |
session_id |
string |
Parent session identifier |
role |
'user' | 'master' | 'worker' | 'system' |
Who sent this message |
content |
string |
Message text |
channel |
string | undefined |
Channel that carried the message |
user_id |
string | undefined |
Sender identifier |
created_at |
string |
ISO 8601 timestamp |
Source: src/memory/retrieval.ts
function searchConversations(
db: Database.Database,
query: string,
limit?: number,
): ConversationEntry[];Full-text search over individual conversation messages using the conversations_fts FTS5 index. Results are BM25-ranked for relevance, then sorted by recency within equal-relevance groups.
| Parameter | Type | Default | Description |
|---|---|---|---|
db |
Database.Database |
— | SQLite database instance |
query |
string |
— | Search query string |
limit |
number |
10 |
Maximum messages to return |
Returns: ConversationEntry[] BM25-ranked, then by created_at DESC. Returns [] if query is empty or all special characters. The query is passed through sanitizeFts5Query() before the FTS5 MATCH clause — this prevents SqliteError: fts5: syntax error for messages containing ', ", *, AND, OR, NOT, (, ), etc. (Phase 63 fix). Previously, special characters in user messages silently caused cross-session context injection to fail.
Source: src/memory/conversation-store.ts
function getRecentMessages(db: Database.Database, limit?: number): ConversationEntry[];Returns the most recent messages across all sessions, filtered to user and master roles only, in chronological order (oldest → newest). Used by triggerMemoryUpdate() to give the stateless --print agent conversation context to write meaningful memory notes.
| Parameter | Type | Default | Description |
|---|---|---|---|
db |
Database.Database |
— | SQLite database instance |
limit |
number |
20 |
Maximum messages to return (most recent N) |
Returns: ConversationEntry[] in chronological order (oldest first). The query selects by created_at DESC then .reverse() is applied to restore chronological order. Only role IN ('user', 'master') — worker and system messages are excluded.
Source: src/memory/index.ts
Facade class over all SQLite store modules. Provides a unified async API for the rest of OpenBridge. Initialized with a Database.Database instance; all methods throw if the DB is not initialized.
async getRecentMessages(limit?: number): Promise<ConversationEntry[]>Returns the most recent user + master messages across all sessions in chronological order (oldest → newest). Delegates to the getRecentMessages() store function. Called by MasterManager.triggerMemoryUpdate() to inject conversation context into the memory-update prompt.
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
number |
20 |
Maximum messages to return |
Returns: Promise<ConversationEntry[]> — rejects with Error('MemoryManager not initialised') if db is null.
Source: src/master/master-manager.ts
The core Master AI lifecycle manager. Handles session initialization, worker spawning, exploration coordination, and graceful shutdown. 6155 LOC.
private async triggerMemoryUpdate(): Promise<void>Spawns a stateless --print mode AI agent that reads recent conversation history from SQLite and writes updated notes to .openbridge/context/memory.md. Non-blocking — callers fire-and-forget with void.
When triggered:
| Trigger | Condition |
|---|---|
| Periodic | Every MEMORY_UPDATE_INTERVAL (10) completed tasks |
| Shutdown | In MasterManager.shutdown(), after saveMasterSessionToStore() |
Behavior:
- Returns early if no master session or state is
'shutdown'. - Calls
this.memory?.getRecentMessages(20)— fetches the last 20user/mastermessages from SQLite. - Logs
{ messageCount }at INFO level. - Formats each entry as
[YYYY-MM-DD HH:MM] Role: content(content truncated to 300 chars with…). - If messages exist, prepends a
## Recent conversation history:section to the prompt. - Spawns an agent in
--printmode withallowedTools: ['Write']pointing atmemoryPath. - Prompt uses generic language ("Write your updated notes to …") — not Claude-specific tool names — so it works with Codex as Master.
Error handling: Failures are logged as WARN and do not block the caller. The triggerMemoryUpdate() call in shutdown() is wrapped in try/catch so a failed memory update cannot block session state persistence.
Source: src/master/dotfolder-manager.ts
Manages the .openbridge/ folder in the target workspace: exploration state, context memory, and the prompt library.
readPromptManifest(): Promise<PromptManifest | null>Reads .openbridge/prompts/manifest.json and returns the parsed manifest, or null if the file does not exist or fails to parse.
writePromptManifest(manifest: PromptManifest): Promise<void>Validates and writes the manifest to .openbridge/prompts/manifest.json. Creates the prompts/ directory if needed.
writePromptTemplate(
filename: string,
content: string,
metadata: Omit<PromptTemplate, 'filePath' | 'createdAt' | 'updatedAt'>,
): Promise<void>Writes a prompt template .md file to .openbridge/prompts/<filename> and creates or updates its entry in the manifest. Preserves createdAt when overwriting; sets previousVersion and previousSuccessRate when updating an existing entry.
getPromptTemplate(id: string): Promise<PromptTemplate | null>Looks up a prompt template by ID in the manifest. Returns null if the manifest does not exist or the ID is not found.
recordPromptUsage(id: string, success: boolean): Promise<void>Increments usageCount, conditionally increments successCount, recalculates successRate = successCount / usageCount, and updates lastUsedAt. No-op if the prompt ID is not found.
getLowPerformingPrompts(threshold: number): Promise<PromptTemplate[]>Returns all prompts where usageCount >= 3 AND successRate < threshold. Used by the prompt evolver to identify candidates for refinement.
resetPromptStats(id: string): Promise<void>Zeros usageCount, successCount, and successRate. Preserves the previous value in previousSuccessRate before resetting. No-op if the ID is not found.
readMemoryFile(): Promise<string | null>Reads .openbridge/context/memory.md and returns its content as a string, or null if the file does not exist. This file is the Master AI's curated cross-session memory.
writeMemoryFile(content: string): Promise<void>Writes content to .openbridge/context/memory.md. Validates that content is at most 200 lines — throws if exceeded. Creates the context/ directory if it doesn't exist.
Prompt Types — Source: src/types/master.ts
| Type | Description |
|---|---|
PromptManifest |
{ prompts: Record<string, PromptTemplate> } |
PromptTemplate |
Full metadata object for one prompt template |
PromptTemplate fields:
| Property | Type | Description |
|---|---|---|
id |
string |
Unique prompt identifier |
name |
string |
Human-readable name |
filePath |
string |
Path to the .md file |
usageCount |
number |
Total times this prompt was used |
successCount |
number |
Times the prompt produced a successful outcome |
successRate |
number |
successCount / usageCount (0–1) |
createdAt |
string |
ISO 8601 creation timestamp |
updatedAt |
string |
ISO 8601 last-modified timestamp |
lastUsedAt |
string | null |
ISO 8601 last-used timestamp |
previousVersion |
string | null |
Content of the previous version (on update) |
previousSuccessRate |
number | null |
Success rate before last reset |
Source: src/core/config.ts
function loadConfig(configPath?: string): Promise<AppConfig | AppConfigV2>;Reads and validates config.json. Tries V2 schema first, falls back to V0. Falls back to CONFIG_PATH env var, then ./config.json.
When loading a V2 config with an mcp section, loadConfig() also writes the global MCP config file (.openbridge/mcp-config.json) in the Claude CLI format ({ mcpServers: { [name]: { command, args?, env? } } }). If mcp.configPath is set, that file is imported and merged with inline servers (inline entries win on name conflicts).
Source: src/core/config.ts
function getMcpConfigPath(): string | null;Returns the absolute path to the global MCP config file written by loadConfig(), or null if no MCP config was written (no mcp section, enabled: false, or no servers defined). Used by MasterManager to pass the global MCP config to workers that don't specify per-worker servers.
Source: src/discovery/index.ts
function scanForAITools(): Promise<ScanResult>;Scans the machine for AI CLI tools and VS Code AI extensions. Returns all discovered tools with the recommended Master.
Source: src/core/logger.ts
function createLogger(name: string, level?: string): pino.Logger;Creates a Pino logger instance. Uses pino-pretty in non-production environments.
Built-in commands are intercepted by the Router before any message reaches the Master AI. They require the memory system to be initialized.
Source: src/core/router.ts
Provides access to past conversation sessions. Three subcommands:
Lists the last 10 conversation sessions with title, message count, and date.
/history
Response (WhatsApp/Telegram/Discord):
1. What is the project structure? — 12 msgs — 2026-01-15
2. Fix the auth bug — 4 msgs — 2026-01-14
...
Response (Console): ASCII table with aligned columns.
Response (WebChat): HTML table with <tr>/<td> cells.
Full-text search across all past sessions by keyword. Returns up to 10 matching sessions ranked by relevance.
/history search authentication
Error: Returns an error message if query is empty.
Displays the full conversation transcript for one session (up to 50 messages, oldest first).
/history a1b2c3d4-...
Response (WhatsApp/Telegram/Discord):
[2026-01-15 10:30] User: What is the project structure?
[2026-01-15 10:30] Master: The project has src/, tests/, and docs/ directories...
Response (Console): Plain text with separator lines.
Response (WebChat): HTML <div class="msg"> bubbles with time and role.
Error responses:
| Condition | Message |
|---|---|
| Memory not initialized | History not available — memory system not initialized |
| DB query failure | History search/list temporarily unavailable — could not query sessions |
| No sessions found (bare) | No past sessions found |
| No sessions found (search) | No sessions found matching <query> |
| Session ID not found (transcript) | No conversation found for session: <id> |
Enabled via health.enabled: true in config. Default port: 8080.
Request: GET /
Response (200 — healthy):
{
"status": "healthy",
"uptime": 3600,
"timestamp": "2026-02-20T10:30:00.000Z",
"connectors": [{ "name": "whatsapp", "status": "healthy" }],
"queue": { "pending": 0, "processing": false, "deadLetterSize": 0 }
}Response (503 — unhealthy): Same shape with "status": "unhealthy".
Enabled via metrics.enabled: true in config. Default port: 9090.
Request: GET /
Response (200):
{
"uptime": 3600,
"timestamp": "2026-02-20T10:30:00.000Z",
"messages": {
"received": 150,
"authorized": 140,
"rateLimited": 5,
"commandBlocked": 2,
"processed": 133,
"failed": 3
},
"latency": {
"count": 133,
"totalMs": 266000,
"avgMs": 2000,
"minMs": 500,
"maxMs": 15000
},
"queue": { "enqueued": 140, "retries": 4, "deadLettered": 1 },
"errors": { "total": 3, "transient": 2, "permanent": 1 }
}Source: src/connectors/webchat/webchat-connector.ts
Available when the WebChat connector is enabled. Requires the memory system to be initialized (memory must be wired via setMemory() on the connector).
Returns a paginated list of conversation sessions.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
number |
20 |
Max sessions to return (clamped to 1–100) |
offset |
number |
0 |
Pagination offset (min 0) |
Response (200 OK):
[
{
"session_id": "uuid-or-sender-id",
"title": "What is the project structure?",
"first_message_at": "2026-01-15T10:30:00.000Z",
"last_message_at": "2026-01-15T11:45:30.000Z",
"message_count": 12,
"channel": "whatsapp",
"user_id": "+1234567890"
}
]Error Responses:
| Status | Body | Condition |
|---|---|---|
503 |
{"error":"Memory not available"} |
MemoryManager not wired |
500 |
{"error":"Internal server error"} |
DB query error |
Returns the full conversation transcript for one session.
URL Parameter: id — the session ID (URL-decoded).
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
number |
100 |
Max messages to return (clamped to 1–500) |
Response (200 OK):
{
"session_id": "uuid-or-sender-id",
"messages": [
{
"id": 1,
"session_id": "uuid-or-sender-id",
"role": "user",
"content": "What is the project structure?",
"channel": "whatsapp",
"user_id": "+1234567890",
"created_at": "2026-01-15T10:30:00.000Z"
},
{
"id": 2,
"session_id": "uuid-or-sender-id",
"role": "master",
"content": "The project has src/, tests/, and docs/ directories...",
"channel": null,
"user_id": null,
"created_at": "2026-01-15T10:30:15.000Z"
}
]
}Messages are in chronological order (oldest first).
Error Responses:
| Status | Body | Condition |
|---|---|---|
503 |
{"error":"Memory not available"} |
MemoryManager not wired |
500 |
{"error":"Internal server error"} |
DB query error |