Real first cut. OpenClaw routes agent turns via /v1/chat/completions (OpenAI-compatible SSE) into our bridge. Bridge ensures a long-lived claude process per session-key, pushes the user message as notifications/claude/channel into the running Claude Code, awaits a reply via the WS connection, streams the reply back as SSE deltas. - core/process-manager: spawn / track / reap claude processes, auto-confirm the --dangerously-load-development-channels dev-mode prompt by piping "1\n" to stdin shortly after spawn - web/bridge-server: unified HTTP + WS server, per-session FIFO queue, SSE heartbeat (empty content delta — SSE comments don't reset OpenClaw's idle watchdog), reply buffering for streaming progress chunks - index.ts: definePluginEntry for OpenClaw runtime + standalone-mode main for laptop smoke testing (just `bun index.ts`) Same-machine simplifications: no bridge token, no permission_request reverse channel. Session key = agent_id::chat_id (contractor-agent convention).
3.3 KiB
3.3 KiB
SynthesisAgent.OpenclawPlugin
OpenClaw plugin that routes agent turns through long-lived interactive Claude Code processes. Replaces the contractor-agent claude -p spawn pattern.
Components
index.ts plugin entry + standalone-run main
core/config.ts config + defaults
core/session-mapping.ts openclaw_session ↔ claude_session_uuid (JSON file)
core/process-manager.ts spawn / track / reap claude processes
web/bridge-server.ts HTTP server + WS bridge server in one module
Two ways to run
As OpenClaw plugin (production)
OpenClaw loads index.ts via definePluginEntry. On gateway_start, the bridge server boots on the configured ports.
Standalone (development / testing)
bun index.ts
Same code path, but no definePluginEntry registration — just boots the bridge with default config.
Config (openclaw.plugin.json configSchema)
| Key | Default | Notes |
|---|---|---|
bridgePort |
18900 | HTTP port for /v1/chat/completions |
channelWsPort |
18901 | WebSocket port for ClaudePlugin connections |
permissionMode |
bypassPermissions |
Spawned Claude's permission mode |
idleKillMs |
3 600 000 | Idle TTL before SIGTERM |
maxProcesses |
16 | Process pool cap |
mappingDbPath |
~/.openclaw/synthesis/sessions.json |
Persistent session map |
channelName |
synthesis |
Used in --channels server:<channelName> |
Laptop smoke test (no real OpenClaw needed)
# 0. Globally register the ClaudePlugin as an MCP server (one-time)
claude mcp add --scope user synthesis -- bun run \
/path/to/SynthesisAgent.ClaudePlugin/server.ts
# 1. Start OpenclawPlugin standalone (this terminal)
cd SynthesisAgent.OpenclawPlugin
bun install
bun index.ts
# → HTTP listening on 127.0.0.1:18900
# → WS listening on 127.0.0.1:18901/bridge
# 2. POST a chat completion (another terminal)
curl -N -X POST http://127.0.0.1:18900/v1/chat/completions \
-H 'Content-Type: application/json' \
-H 'X-Openclaw-Agent-Id: dev' \
-H 'X-Openclaw-Chat-Id: test-1' \
-H "X-Openclaw-Workspace: $HOME/some-trusted-dir" \
-d '{
"model":"synthesis-claude-bridge",
"messages":[
{"role":"user","content":"Reply with exactly the word READY"}
]
}'
Expected flow:
- OpenclawPlugin receives request, ensures a claude process for
dev::test-1 - Spawns
claude --channels server:synthesis --dangerously-load-development-channels server:synthesis --resume <new-uuid> ... - ClaudePlugin (inside the claude process) dials
ws://127.0.0.1:18901/bridge, sendshello - OpenclawPlugin pushes
inboundframe, ClaudePlugin emitsnotifications/claude/channel - Claude reacts, eventually calls
reply(text)tool - ClaudePlugin sends
replyWS frame, OpenclawPlugin streams it as SSE delta - curl sees the reply text
Known v1 simplifications (documented punch list)
- Session-key extraction reads headers only — production OpenClaw routing will need the contractor-agent style "Conversation info" parser
- No tool-catalog proxy: ClaudePlugin only exposes
reply; the model uses Claude Code's built-in tools (Read/Edit/Bash/Grep) for everything else - No permission_request reverse channel (full perms by config)
- No bridge token / auth handshake (same-machine assumption)
- Standalone mode boots with defaults only; no config flags
License
Apache-2.0