Files
zhi 0324a47d13 feat: HTTP /v1/chat/completions + WS bridge + process manager
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).
2026-05-14 13:28:00 +00:00

43 lines
1.6 KiB
TypeScript

import { homedir } from 'node:os'
import { resolve as resolvePath } from 'node:path'
export interface SynthesisConfig {
bridgePort: number
channelWsPort: number
permissionMode: string
idleKillMs: number
maxProcesses: number
mappingDbPath: string
channelName: string
}
const DEFAULTS: SynthesisConfig = {
bridgePort: 18900,
channelWsPort: 18901,
permissionMode: 'bypassPermissions',
idleKillMs: 3_600_000,
maxProcesses: 16,
mappingDbPath: '~/.openclaw/synthesis/sessions.json',
channelName: 'synthesis',
}
function expand(p: string): string {
if (p.startsWith('~')) return resolvePath(homedir(), p.slice(2))
return resolvePath(p)
}
export function normalizeConfig(raw: unknown): SynthesisConfig {
const r = (raw ?? {}) as Partial<SynthesisConfig>
const merged: SynthesisConfig = {
bridgePort: typeof r.bridgePort === 'number' ? r.bridgePort : DEFAULTS.bridgePort,
channelWsPort: typeof r.channelWsPort === 'number' ? r.channelWsPort : DEFAULTS.channelWsPort,
permissionMode: typeof r.permissionMode === 'string' && r.permissionMode ? r.permissionMode : DEFAULTS.permissionMode,
idleKillMs: typeof r.idleKillMs === 'number' ? r.idleKillMs : DEFAULTS.idleKillMs,
maxProcesses: typeof r.maxProcesses === 'number' ? r.maxProcesses : DEFAULTS.maxProcesses,
mappingDbPath: typeof r.mappingDbPath === 'string' && r.mappingDbPath ? r.mappingDbPath : DEFAULTS.mappingDbPath,
channelName: typeof r.channelName === 'string' && r.channelName ? r.channelName : DEFAULTS.channelName,
}
merged.mappingDbPath = expand(merged.mappingDbPath)
return merged
}