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:
83
core/session-mapping.ts
Normal file
83
core/session-mapping.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'
|
||||
import { dirname } from 'node:path'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
export interface SessionRecord {
|
||||
openclawSessionId: string
|
||||
claudeSessionUuid: string
|
||||
createdAt: number
|
||||
lastActiveAt: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Persistent openclaw_session ↔ claude_session mapping.
|
||||
*
|
||||
* Single-writer (the OpenclawPlugin process). File-level on every mutation —
|
||||
* simple and correct for the volume we expect. If it ever matters, swap for
|
||||
* sqlite.
|
||||
*/
|
||||
export class SessionMapping {
|
||||
private records = new Map<string, SessionRecord>()
|
||||
private path: string
|
||||
|
||||
constructor(path: string) {
|
||||
this.path = path
|
||||
this.load()
|
||||
}
|
||||
|
||||
private load(): void {
|
||||
try {
|
||||
const raw = readFileSync(this.path, 'utf8')
|
||||
const parsed = JSON.parse(raw) as { records?: SessionRecord[] }
|
||||
for (const r of parsed.records ?? []) {
|
||||
this.records.set(r.openclawSessionId, r)
|
||||
}
|
||||
} catch {
|
||||
// File doesn't exist or unreadable — start fresh.
|
||||
}
|
||||
}
|
||||
|
||||
private flush(): void {
|
||||
mkdirSync(dirname(this.path), { recursive: true })
|
||||
writeFileSync(
|
||||
this.path,
|
||||
JSON.stringify({ records: [...this.records.values()] }, null, 2),
|
||||
'utf8',
|
||||
)
|
||||
}
|
||||
|
||||
get(openclawSessionId: string): SessionRecord | undefined {
|
||||
return this.records.get(openclawSessionId)
|
||||
}
|
||||
|
||||
/** Get-or-create. Returns the (possibly freshly assigned) claude_session_uuid. */
|
||||
ensure(openclawSessionId: string): SessionRecord {
|
||||
const existing = this.records.get(openclawSessionId)
|
||||
if (existing) return existing
|
||||
const now = Date.now()
|
||||
const rec: SessionRecord = {
|
||||
openclawSessionId,
|
||||
claudeSessionUuid: randomUUID(),
|
||||
createdAt: now,
|
||||
lastActiveAt: now,
|
||||
}
|
||||
this.records.set(openclawSessionId, rec)
|
||||
this.flush()
|
||||
return rec
|
||||
}
|
||||
|
||||
touch(openclawSessionId: string): void {
|
||||
const r = this.records.get(openclawSessionId)
|
||||
if (!r) return
|
||||
r.lastActiveAt = Date.now()
|
||||
this.flush()
|
||||
}
|
||||
|
||||
forget(openclawSessionId: string): void {
|
||||
if (this.records.delete(openclawSessionId)) this.flush()
|
||||
}
|
||||
|
||||
all(): SessionRecord[] {
|
||||
return [...this.records.values()]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user