chore: initial scaffolding for SynthesisAgent.OpenclawPlugin

OpenClaw plugin that manages long-lived interactive Claude Code processes
per OpenClaw session. Process manager + session-mapping persistence +
bridge WebSocket server skeleton.

Will be rewritten to follow the contractor-agent HTTP model-provider pattern
(register as `synthesis-claude-bridge` provider, receive /v1/chat/completions,
dispatch to channel notification on the bound Claude process). See parent
repo's STATUS.md for the punch list.
This commit is contained in:
zhi
2026-05-14 09:41:18 +00:00
commit 38ac6d20b7
11 changed files with 698 additions and 0 deletions

42
core/config.ts Normal file
View File

@@ -0,0 +1,42 @@
import { homedir } from 'node:os'
import { resolve as resolvePath } from 'node:path'
export interface SynthesisConfig {
bridgePort: number
bridgeToken: string
claudePluginRef: string
permissionMode: string
idleKillMs: number
maxProcesses: number
mappingDbPath: string
}
const DEFAULTS: SynthesisConfig = {
bridgePort: 18801,
bridgeToken: 'synthesis-local',
claudePluginRef: 'plugin:synthesis-claude@local',
permissionMode: 'acceptEdits',
idleKillMs: 3_600_000,
maxProcesses: 16,
mappingDbPath: '~/.openclaw/synthesis/sessions.json',
}
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,
bridgeToken: typeof r.bridgeToken === 'string' && r.bridgeToken ? r.bridgeToken : DEFAULTS.bridgeToken,
claudePluginRef: typeof r.claudePluginRef === 'string' && r.claudePluginRef ? r.claudePluginRef : DEFAULTS.claudePluginRef,
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,
}
merged.mappingDbPath = expand(merged.mappingDbPath)
return merged
}